Use when writing or reviewing Python backend code that involves error handling, data migrations, service-layer functions, cross-module API calls, or constant definitions
Every backend function must answer: "What happens to the data if this fails halfway?" If the answer is "nothing visible," you have a bug.
Before finishing backend code, verify your code against the catalog. The Quick Reference below gives a one-line index of every pattern; load references/patterns.md for full bodies with examples and code.
| Rule | Symptom | Check |
|---|---|---|
| No Silent Swallows | Bug goes unnoticed for weeks | Every except logs or re-raises? |
| Catch What You Raise | Unhandled crash instead of fallback | Caller handles ALL exception types? Error-code sets complete? |
| Copy Before Delete | Data loss on deploy | Data copied BEFORE source NULLed/dropped? |
| One Source of Truth |
| "Fixed it but still wrong" |
| Constant/scope/config defined in exactly one place? |
| Respect Encapsulation | Double side-effects, brittle coupling | Only calling public API? |
| Verify Imports | NameError on first call | Every type/object imported before use? |
| Real DB Semantics | Query works locally, crashes in prod | DISTINCT/JSON/ORDER BY safe for Postgres? |
| Verify Model Attrs | AttributeError at runtime | Attribute exists on the model class? |
| Auxiliary Side-Effects | Notification failure aborts core op | Each side-effect wrapped independently? |
| Audit Trail | Action invisible in dashboard | State-changing endpoint creates ActionLog? |
| Error Visibility | Errors hidden from user | Dashboard feed includes ERROR-level logs? |
| Ephemeral Storage | Files vanish after deploy | All file I/O uses persistent storage? |
| Error Leaking | Internal details in HTTP response | Error messages generic, no str(e)? |
| String Datetime Compare | Future items filtered out | All datetime comparisons use parsed tz-aware objects? |
| Naive/Aware Datetime Arithmetic | TypeError: can't subtract offset-naive and offset-aware | DB datetime normalized to aware before arithmetic with datetime.now(tz.utc)? |
| Stale Connection Check | Feature disabled despite connection | Connection state read from owning service, not user column? |
| Cache-Only Primary View | "Only 1 event" when 15 exist | Primary view fetches live source, not just local cache? |
| Post-Conflict Testing | Resolved code breaks behavior | Tests re-run after conflict resolution before push? |
| Split Function Scope | NameError on variable from sibling function | Every variable in new function defined locally, as param, or imported? |
| Static Asset Cache-Busting | "Deployed but no visual change" | CSS/JS commit also bumps ?v= in all loading templates? |
| Subprocess Shell Injection | Command injection vulnerability | Using list args with shell=False and timeout? |
| Lock File TOCTOU | Race condition in concurrent ops | Using atomic `O_CREAT |
| Flag Evaluation N+1 | Slow feature flag checks | Batch-loading flags before loop evaluation? |
| Eval Built-in Usage | Security scanner flags, code injection risk | Using ast.parse() with explicit node handler? |
| SQL F-String Table Names | SQL injection via dynamic table name | Using allowlist assert + .replace() pattern? |
| Transient DB Crash | Operation fails on connection reset | Single-retry loop for OperationalError? |
| Can't Clear Nullable Field | User can set but never clear a text field | Explicit None → set to None branch exists? |
| DB fallback filters | Bare path returns different set than primary | Fallback path applies same filters as primary? Catch only SQLAlchemyError? |
| No BaseHTTPMiddleware | "Session is closed" / greenlet errors | grep -r "BaseHTTPMiddleware" app/ returns zero? |
| Audit ALL Pattern Instances | Same bug reported twice, different file | Codebase-wide grep for pattern before shipping fix? |
scalar_one_or_none on non-unique col | MultipleResultsFound crash months later | WHERE clause backed by unique DB constraint? If no, use scalars().first() |
| Test Constraints, Not Shapes | "Tests passed but feature is broken" | Mocked db.execute test asserts the bound Select? OR has companion integration test? |
| JSON Column Reassignment | model.field.append(x) doesn't persist | JSON-column writes reassign (field = list(field) + [x]) or use MutableList? |
| Phone Lookup Normalization | Twilio E.164 never matches stored phone | Lookup uses Client.phone_normalized AND Client.user_id scope? |
| State-Machine Exit Transitions | Status stuck in PENDING_* forever | Every exit action sets status explicitly, not just clears trigger field? |
| Fail-Closed Webhook Validators | Public unauthenticated POST in prod | No-token branch hard-fails when settings.is_production? |
| Escape XML/TwiML Substitutions | TwiML hijack via tag injection | AI/user content wrapped in xml_escape() before f-string? |
| Context-Aware Sanitizers | "JSON.parse fails at position 1 after sanitize" | Sanitizer is a state-machine walk (tracks in-string vs structural), not a global regex? Fallback path doesn't silently release destructive action on parse failure? |
| Stateful Callback Cleanup | "Feature works once, then wedges until restart" | Every return/raise/early-exit in a function that mutates module-level state clears that state? Prefer try/finally. |
except SomeError: pass with no loggingtry/except that catches fewer types than the callee raisesobj._method() from outside obj's moduleimportDISTINCT on a table with JSON columnsORDER BY column not in SELECT DISTINCT listmodel.attribute without checking the model definitionActionLog entrystr(e) or traceback.format_exc() in an HTTP response or redirect URLprint() debug statements in production code>=/<= instead of parsed objectsdatetime.now(timezone.utc) without normalizing timezone awarenessuser.some_token instead of the token manager"UUID | None") without module-level import of the type.css/.js files but doesn't bump ?v= params in loading templatesast.parse() + purpose-built evaluatortext(f"...{variable}...") in SQL — use allowlist assert + .replace() insteadOperationalErrorif value is not None branch (can't clear)except Exception in a DB fallback — use except SQLAlchemyError so non-DB errors propagatefrom starlette.middleware.base import BaseHTTPMiddleware — use pure ASGI middleware insteadscalar_one_or_none() on a query filtering by a non-unique column (email, phone, name) — use scalars().first() insteaddb.execute.return_value in a test with no companion assertion on the bound Select — WHERE clause is invisible.append() / nested-dict mutation on a Column(JSON) value — silent no-op without reassignment or MutableListClient.phone == ... lookup against external (E.164) input — use Client.phone_normalized and scope by user_idstatusTrue unconditionally — must hard-fail in productionxml.sax.saxutils.escape