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:

terminal
$ 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 idSeverityCatches
python/mutable-default-argumentBLOCKERMutable default arguments (`def f(items=[])`) — shared across calls; use `None` + assign inside.
python/bare-exceptBLOCKERBare `except:` or `except Exception: pass` — catch specific exceptions; never silence. Bare `except` also eats `KeyboardInterrupt`/`SystemExit`.
python/sql-string-interpolationBLOCKERSQL built with f-strings/`%`/`.format` from external input — parameterized queries only.
python/subprocess-shell-injectionBLOCKER`subprocess` with `shell=True` on anything derived from external input; prefer list-form argv always.
python/dynamic-code-executionBLOCKER`eval`/`exec`/`pickle.loads` on external data.
python/blocking-in-asyncBLOCKERBlocking calls inside `async def`: `requests`, `time.sleep`, sync DB drivers — use `httpx`/`aiohttp`, `asyncio.sleep`, async drivers, or `run_in_executor`.
python/secrets-not-centralisedBLOCKERSecrets hardcoded or read ad hoc — central config module, validated at startup.
python/missing-type-hintsHIGHMissing type hints on new public functions — new code is typed; assume mypy/pyright strict.
python/missing-context-managerHIGHFiles/connections/locks without context managers (`with`) — leaks on exception paths.
python/module-level-mutable-stateHIGHModule-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-loopHIGHBroad `except Exception` that logs and continues in loops — one poisoned item must not silently vanish; dead-letter or re-raise policy required.
python/naive-datetimeHIGHDatetime handling: naive `datetime.now()` in new code — `datetime.now(timezone.utc)` and timezone-aware throughout.
python/assert-for-validationHIGH`assert` for runtime validation — stripped under `-O`; raise proper exceptions.
python/boto3-client-per-callHIGHBoto3 clients created per call inside handlers/loops — create at module scope (Lambda) or inject.
python/http-call-without-timeoutHIGHUnbounded `requests`/`httpx` calls without timeout.
python/prefer-typed-modelsSUGGESTIONDataclasses/pydantic models over dict-shaped data crossing function boundaries.
python/prefer-pathlibSUGGESTION`pathlib` over `os.path` string juggling in new code.
python/prefer-f-stringsSUGGESTIONf-strings over `%`/`.format`.
python/prefer-comprehensionsSUGGESTIONComprehensions 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:

a finding from this file
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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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

standards/stacks/python.md · 30 lines · 2.6 KB
# 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.