// Config Record
>.prompt.md — Python Code Review
VS Code reusable prompt for high-signal Python code reviews with severity scoring and concrete remediation guidance.
author:
dotmd Team
license:CC0
published:Feb 23, 2026
// Installation
>Add this file to your project repository:
- VS Code--path=
.prompt.md
// File Content
.prompt.md
1---2title: "Python Code Review"3description: "Structured code review for Python — types, safety, patterns, tests"4---56# Python Code Review78Review the provided Python code systematically using the checks below. For each section, report findings as **🔴 Must Fix**, **🟡 Should Fix**, or **💡 Suggestion**. If a section has no findings, skip it entirely — don't report clean passes.910Focus on bugs, security holes, and maintainability problems that actually ship to production. Ignore style nitpicks that ruff or black would catch automatically.1112---1314## 1. Type Safety & Signatures1516Check every function signature and return type. Flag these specific patterns:1718**Flag as 🔴:**19- Functions missing return type annotations20- `Any` used where a concrete type or generic would work21- `dict` or `list` without type parameters (`dict[str, int]`, not `dict`)22- Mutable default arguments: `def f(items: list[str] = [])` → use `None` sentinel23- `Optional[X]` without a `None` check before use (potential `AttributeError`)2425**Flag as 🟡:**26- `Union` types that could be narrowed with `@overload`27- Missing `TypeVar` bounds on generic functions28- `cast()` calls — each one should have a comment justifying why29- `# type: ignore` without an error code (`# type: ignore[assignment]` is fine)3031**Check:** Would `mypy --strict` pass on this code? If not, what specific errors would it raise?3233```34# Bad: silent None bug35def get_user(id: int) -> User | None:36 ...3738def process(id: int):39 user = get_user(id)40 user.name # 🔴 no None check — AttributeError at runtime4142# Good43def process(id: int):44 user = get_user(id)45 if user is None:46 raise ValueError(f"User {id} not found")47 user.name # safe — type narrowed48```4950---5152## 2. Error Handling & Failure Modes5354Trace every code path that can raise. Flag these patterns:5556**Flag as 🔴:**57- Bare `except:` or `except Exception:` that swallows errors silently58- `except` blocks that `pass` without logging or re-raising59- Missing error handling on I/O: file ops, network calls, DB queries60- `KeyError`/`IndexError` risks on unvalidated external input (API params, env vars, config)6162**Flag as 🟡:**63- Catching too broadly: `except Exception` when `except (ValueError, TypeError)` would be precise64- Missing `finally` or context manager for cleanup (file handles, DB connections, locks)65- Error messages that don't include the failing value: `raise ValueError("invalid input")` → `raise ValueError(f"invalid input: {value!r}")`66- Retryable operations (HTTP, DB) without retry logic6768**Check for this anti-pattern specifically:**69```70# Bad: swallowed error, invisible failure71try:72 result = api_client.fetch(url)73except Exception:74 result = None # 🔴 caller has no idea this failed7576# Good: explicit, logged, typed77try:78 result = api_client.fetch(url)79except httpx.HTTPStatusError as exc:80 logger.warning("Fetch failed for %s: %s", url, exc)81 raise82```8384---8586## 3. Security & Input Validation8788Scan for these specific vulnerability patterns. Any finding here is 🔴 unless noted.8990**SQL injection:**91- Raw string formatting in queries: `f"SELECT * FROM users WHERE id = {user_id}"`92- String concatenation with `.format()` or `%` in SQL93- Flag even if using an ORM — check raw query escapes (`RawSQL`, `extra()`, `.raw()`)9495**Command injection:**96- `os.system()`, `subprocess.run(shell=True)` with user input97- `eval()`, `exec()` on any external input9899**Path traversal:**100- User-controlled file paths without sanitization101- Missing `Path.resolve()` + prefix check for uploaded file names102103**Secrets & credentials:**104- Hardcoded API keys, passwords, tokens, DSNs105- Secrets in default argument values106- Credentials logged at any level (even DEBUG)107108**Deserialization:**109- `pickle.loads()` on untrusted data110- `yaml.load()` without `Loader=SafeLoader`111- `json.loads()` on user input without schema validation (🟡)112113**Django-specific:**114- `mark_safe()` on user-controlled content115- `CSRF_COOKIE_HTTPONLY = False` or missing CSRF middleware116- `DEBUG = True` without environment gate117118**FastAPI/Flask-specific:**119- Missing request body validation (Pydantic model vs raw dict)120- `response.set_cookie()` without `httponly=True, secure=True, samesite="lax"`121122---123124## 4. Resource Management & Concurrency125126**Flag as 🔴:**127- File handles opened without `with` statement (resource leak)128- DB connections not closed in error paths129- `threading.Lock` acquired without `with lock:` pattern130- Shared mutable state in async code without synchronization131132**Flag as 🟡:**133- Missing `async with` for async context managers (httpx.AsyncClient, aiofiles)134- `time.sleep()` in async code (blocks the event loop — use `asyncio.sleep()`)135- Unbounded queues or caches (`dict` used as cache without size limit → use `functools.lru_cache` or `cachetools.TTLCache`)136- N+1 query patterns in Django: `for obj in queryset:` then `obj.related.field` without `select_related()`137138```139# Bad: blocks event loop140async def fetch_all(urls: list[str]):141 for url in urls:142 time.sleep(1) # 🟡 use asyncio.sleep143 resp = httpx.get(url) # 🔴 sync HTTP in async — use httpx.AsyncClient144145# Good146async def fetch_all(urls: list[str]):147 async with httpx.AsyncClient() as client:148 tasks = [client.get(url) for url in urls]149 return await asyncio.gather(*tasks)150```151152---153154## 5. Data Modeling & Validation155156**Flag as 🔴:**157- User/external input consumed without validation (raw `request.json`, `sys.argv`, env vars parsed manually)158- Pydantic models with `model_config = ConfigDict(extra="allow")` in API boundaries (lets garbage through)159- Django model fields missing `blank`/`null` constraints that match business rules160161**Flag as 🟡:**162- Stringly-typed data: status fields as `str` instead of `enum.StrEnum`163- Business logic in serializers/schemas — push it to the model or service layer164- Missing `__str__` and `__repr__` on domain objects (makes debugging painful)165- Dataclasses where `frozen=True` would enforce immutability166167```168# Bad: stringly typed, no validation169def create_order(status: str, priority: str): ...170171# Good: enum-typed, validated172class Status(enum.StrEnum):173 PENDING = "pending"174 SHIPPED = "shipped"175 DELIVERED = "delivered"176177class Priority(enum.IntEnum):178 LOW = 1179 MEDIUM = 2180 HIGH = 3181182def create_order(status: Status, priority: Priority): ...183```184185---186187## 6. API Contract & Interface Design188189**Flag as 🔴:**190- Public function signatures that accept `**kwargs` and forward blindly (invisible API)191- Breaking changes to public interfaces without deprecation path192- Return type inconsistency: sometimes returns `dict`, sometimes `None`, sometimes raises193194**Flag as 🟡:**195- Functions with more than 5 positional parameters (use a dataclass/Pydantic model)196- Boolean trap: `process(data, True, False)` — use keyword-only args or enums197- Missing `__all__` in `__init__.py` for public packages198199```200# Bad: boolean trap, positional overload201def send_email(to, subject, body, True, False, True, None): ...202203# Good: keyword-only after required args204def send_email(205 to: str,206 subject: str,207 body: str,208 *,209 html: bool = False,210 track_opens: bool = False,211 reply_to: str | None = None,212) -> None: ...213```214215---216217## 7. Test Quality218219If tests are included in the review, or if the code under review lacks tests:220221**Flag as 🔴:**222- Public functions/endpoints with zero test coverage223- Tests that mock the thing they're testing (mock the dependency, not the subject)224- Tests with no assertions (test runs but proves nothing)225- `@pytest.mark.skip` without a linked issue or TODO226227**Flag as 🟡:**228- Tests that depend on execution order or shared mutable state229- Missing edge case tests: empty input, `None`, boundary values, Unicode230- Integration tests hitting real external services without `responses`, `respx`, or `vcr`231- Fixtures that do too much — a fixture with 50 lines of setup is a missing factory232233**Check:** Can you run `pytest --co -q` mentally on these tests and predict what they cover? If not, the test names need work.234235```236# Bad: mocks the subject, tests nothing237def test_process_order(mocker):238 mocker.patch("app.orders.process_order", return_value=True)239 assert process_order(order) is True # 🔴 you tested the mock240241# Good: mocks the dependency242def test_process_order(mocker):243 mocker.patch("app.payments.charge", return_value=Receipt(id="r1"))244 result = process_order(order)245 assert result.status == Status.PAID246 assert result.receipt_id == "r1"247```248249---250251## 8. Performance & Scalability Flags252253Only flag performance issues that would cause real production problems. Skip micro-optimizations.254255**Flag as 🔴:**256- Unbounded queries: `Model.objects.all()` without pagination in an API endpoint257- `O(n²)` loops on collections that could grow: nested `for x in list: for y in list: if x == y`258- Loading entire files into memory: `f.read()` on user-uploaded files (use chunked reading)259260**Flag as 🟡:**261- Missing database indexes on fields used in `filter()`, `WHERE`, or `ORDER BY`262- Repeated identical queries in a request cycle (missing `select_related`/`prefetch_related`)263- Sync I/O in hot paths that could use `asyncio` or background tasks264- String concatenation in loops (`+=`) instead of `"".join()` or list building265266---267268## 9. Logging, Observability & Debugging269270**Flag as 🟡:**271- `print()` statements that should be `logger.info()` / `logger.debug()`272- Missing structured context in log messages: `logger.error("Failed")` → `logger.error("Payment failed", extra={"order_id": order.id, "amount": amount})`273- Caught exceptions without `logger.exception()` or `exc_info=True` (loses traceback)274- No logging on error recovery paths (silent retries, fallbacks)275276**Flag as 💡:**277- Opportunities for `structlog` or JSON logging in services that feed into log aggregators278- Missing request ID / correlation ID propagation in multi-service architectures279- Endpoints without timing/metrics instrumentation280281---282283## 10. Python-Specific Gotchas284285Flag these known footguns when you see them:286287| Pattern | Problem | Fix |288|---------|---------|-----|289| `datetime.now()` | No timezone — ambiguous | `datetime.now(tz=UTC)` |290| `== None` / `!= None` | Wrong comparison | `is None` / `is not None` |291| `isinstance(x, str | int)` | Python <3.10 compat | `isinstance(x, (str, int))` |292| `from module import *` | Namespace pollution | Explicit imports |293| Class-level mutable: `items = []` | Shared across instances | Use `field(default_factory=list)` or `__init__` |294| `os.environ["KEY"]` | Crashes if missing | `os.environ.get("KEY")` or `os.getenv("KEY", default)` |295| `json.dumps(obj)` with dates | `TypeError` at runtime | Custom encoder or `.isoformat()` |296| `hashlib.md5(password)` | Insecure hashing | `bcrypt`, `argon2`, or `hashlib.scrypt` |297298---299300## Output Format301302Structure your review as:303304```markdown305## Review Summary306307**Risk Level:** [Low / Medium / High / Critical]308**Estimated Issues:** X must-fix, Y should-fix, Z suggestions309310## Findings311312### 🔴 [Section Name]: Brief description313**Location:** `file.py:42` / `ClassName.method_name`314**Problem:** What's wrong and why it matters315**Fix:** Concrete code suggestion316317### 🟡 [Section Name]: Brief description318...319320### 💡 [Section Name]: Brief description321...322323## What Looks Good324325(2-3 bullets on what the code does well — don't skip this)326```327328Prioritize findings by severity. Lead with 🔴, then 🟡, then 💡. If there are more than 15 findings, group the 💡 suggestions into a summary list instead of expanding each one.329