Python
Python services and tooling.
What this is
Python services and tooling. The BLOCKER tier is mostly injection and safety footguns Python makes easy to write by accident — mutable default arguments, bare except clauses, SQL built by string interpolation, shell=True subprocess calls, blocking I/O inside async def. The HIGH tier pushes toward a typed, timezone-aware, resource-safe style: type hints on new public functions, context managers instead of manual cleanup, timezone-aware datetimes, HTTP calls with timeouts.
How to onboard it
A repository picks up this stack by onboarding on a profile that includes it. redline init detects the profile from what is in the repository, so in most cases this is automatic:
$ npx redlinegate init # detects the profile $ npx redlinegate init --profile service-python # or name one
One profile pulls these rules in: service-python.
Once onboarded, your files match this stack when they fit any of these globs:
**/*.py
How to use it — 19 rules
Nothing to run. Once your profile includes Python, redline init renders these rules into your repository's AI tooling and the reviewer applies them on every pull request. When a review comment cites one of these ids, this table is where to look up what it catches and why.
| Rule id | Severity | Catches |
|---|---|---|
python/mutable-default-argument | BLOCKER | Mutable default arguments (`def f(items=[])`) — shared across calls; use `None` + assign inside. |
python/bare-except | BLOCKER | Bare `except:` or `except Exception: pass` — catch specific exceptions; never silence. Bare `except` also eats `KeyboardInterrupt`/`SystemExit`. |
python/sql-string-interpolation | BLOCKER | SQL built with f-strings/`%`/`.format` from external input — parameterized queries only. |
python/subprocess-shell-injection | BLOCKER | `subprocess` with `shell=True` on anything derived from external input; prefer list-form argv always. |
python/dynamic-code-execution | BLOCKER | `eval`/`exec`/`pickle.loads` on external data. |
python/blocking-in-async | BLOCKER | Blocking calls inside `async def`: `requests`, `time.sleep`, sync DB drivers — use `httpx`/`aiohttp`, `asyncio.sleep`, async drivers, or `run_in_executor`. |
python/secrets-not-centralised | BLOCKER | Secrets hardcoded or read ad hoc — central config module, validated at startup. |
python/missing-type-hints | HIGH | Missing type hints on new public functions — new code is typed; assume mypy/pyright strict. |
python/missing-context-manager | HIGH | Files/connections/locks without context managers (`with`) — leaks on exception paths. |
python/module-level-mutable-state | HIGH | Module-level mutable state used as implicit singleton across requests (Lambda warm starts share it — intentional caching must be explicit and documented). |
python/broad-except-in-loop | HIGH | Broad `except Exception` that logs and continues in loops — one poisoned item must not silently vanish; dead-letter or re-raise policy required. |
python/naive-datetime | HIGH | Datetime handling: naive `datetime.now()` in new code — `datetime.now(timezone.utc)` and timezone-aware throughout. |
python/assert-for-validation | HIGH | `assert` for runtime validation — stripped under `-O`; raise proper exceptions. |
python/boto3-client-per-call | HIGH | Boto3 clients created per call inside handlers/loops — create at module scope (Lambda) or inject. |
python/http-call-without-timeout | HIGH | Unbounded `requests`/`httpx` calls without timeout. |
python/prefer-typed-models | SUGGESTION | Dataclasses/pydantic models over dict-shaped data crossing function boundaries. |
python/prefer-pathlib | SUGGESTION | `pathlib` over `os.path` string juggling in new code. |
python/prefer-f-strings | SUGGESTION | f-strings over `%`/`.format`. |
python/prefer-comprehensions | SUGGESTION | Comprehensions over `map`/`filter` with lambdas; but no nested comprehensions beyond two levels. |
Expected output
A finding from this file, and every finding Redline produces, opens with a machine-readable first line — severity, then the rule id in brackets, then the problem in one line:
Redline/BLOCKER [python/mutable-default-argument]: <one-line problem>Ids are aggregated per rule, which is how the organisation finds out which rules earn their place and which only generate noise — so a finding without a valid id cannot be measured and counts as untagged. Of the 19 rules here, 7 BLOCKER, 8 HIGH and 4 SUGGESTION. Only a BLOCKER must not merge; a SUGGESTION may be dismissed without justification, and is never upgraded to get attention.
If a rule here fires constantly on code your team has deliberately decided to allow, that is the signal to raise with the standards owner — the rule id is what makes that conversation measurable — not to argue it away comment by comment.
How to edit it
Rules are edited in standards/stacks/python.md and nowhere else. The rendered copies in AGENTS.md, .github/copilot-instructions.md and .github/instructions/ are generated and are overwritten by the next render. A change here propagates to every onboarded repository as a pull request, so treat it as a production change.
- Edit the markdownstandards/ is the only place a human edits a rule. Everything under AGENTS.md, .github/copilot-instructions.md and .github/instructions/ is rendered from it and is overwritten by the next render.
- node scripts/assign-rule-ids.mjsAssigns a permanent <stack>/<slug> id to any new rule bullet and rewrites the file in place. Do not invent an id by hand. CI runs the same script with --check and fails if a rule is missing one.
- node scripts/render-self.mjsRe-renders this repository's own artifacts from the edited source. CI runs it with --check, so stale checked-in output fails the build.
- Bump standards/manifest.json → versionRequired in the same pull request as the rule change. Sync pull requests quote the version, so a repository's rendered artifacts always name where they came from.
- Add a CHANGELOG.md entryAlso in the same pull request. A standards change with no measurement is an opinion — record the seed score alongside it.
- node scripts/validate.mjsThe bundle self-check CI runs: manifest integrity, well-formed rule ids, the severity output contract surviving your edit, glob portability.
Rule ids are permanent. Rewording a rule is fine and keeps its id; renaming or removing an id orphans every historical telemetry record that cited it.
The full file
# Python Review Rules ## BLOCKER — request changes - `python/mutable-default-argument` — **Mutable default arguments** (`def f(items=[])`) — shared across calls; use `None` + assign inside. - `python/bare-except` — **Bare `except:` or `except Exception: pass`** — catch specific exceptions; never silence. Bare `except` also eats `KeyboardInterrupt`/`SystemExit`. - `python/sql-string-interpolation` — **SQL built with f-strings/`%`/`.format` from external input** — parameterized queries only. - `python/subprocess-shell-injection` — **`subprocess` with `shell=True`** on anything derived from external input; prefer list-form argv always. - `python/dynamic-code-execution` — **`eval`/`exec`/`pickle.loads` on external data.** - `python/blocking-in-async` — **Blocking calls inside `async def`**: `requests`, `time.sleep`, sync DB drivers — use `httpx`/`aiohttp`, `asyncio.sleep`, async drivers, or `run_in_executor`. - `python/secrets-not-centralised` — **Secrets hardcoded or read ad hoc** — central config module, validated at startup. ## HIGH - `python/missing-type-hints` — Missing type hints on new public functions — new code is typed; assume mypy/pyright strict. - `python/missing-context-manager` — Files/connections/locks without context managers (`with`) — leaks on exception paths. - `python/module-level-mutable-state` — Module-level mutable state used as implicit singleton across requests (Lambda warm starts share it — intentional caching must be explicit and documented). - `python/broad-except-in-loop` — Broad `except Exception` that logs and continues in loops — one poisoned item must not silently vanish; dead-letter or re-raise policy required. - `python/naive-datetime` — Datetime handling: naive `datetime.now()` in new code — `datetime.now(timezone.utc)` and timezone-aware throughout. - `python/assert-for-validation` — `assert` for runtime validation — stripped under `-O`; raise proper exceptions. - `python/boto3-client-per-call` — Boto3 clients created per call inside handlers/loops — create at module scope (Lambda) or inject. - `python/http-call-without-timeout` — Unbounded `requests`/`httpx` calls without timeout. ## SUGGESTION - `python/prefer-typed-models` — Dataclasses/pydantic models over dict-shaped data crossing function boundaries. - `python/prefer-pathlib` — `pathlib` over `os.path` string juggling in new code. - `python/prefer-f-strings` — f-strings over `%`/`.format`. - `python/prefer-comprehensions` — Comprehensions over `map`/`filter` with lambdas; but no nested comprehensions beyond two levels.