Internal: Python rules (strict typing, domain exceptions, exhaustive match); read by work's executors before Python code.
Install
npx skillscat add idontlikedrinkpivo/web-dev-pipeline-codex-plugin/python Install via the SkillsCat registry.
SKILL.md
Codex runtime. Before using this skill, read
../executor-catalog/references/codex-runtime.md. It defines tool mappings, named-agent dispatch and stage-chat orchestration; use it for inherited Claude Code terms below.
Python
The same rules as typescript, in Python's words. A machine-checked rule is one line with its rule; the config isrepo-scaffold/references/quality-gates.md.
The machine checks
| Rule | Check |
|---|---|
| strict typing, the tests included | mypy --strict or pyright strict — the checker repo-scaffold/references/quality-gates.md names |
no bare except, no except Exception: pass, no silent fallback |
Ruff E722, BLE001, S110 |
a wrapped exception keeps its cause: raise StorageWriteFailed("put") from e |
Ruff B904 |
a match over a union or an enum ends with case _: assert_never(x) |
the type checker refuses the call when a case is missing — any checker; pyright reportMatchNotExhaustive and mypy exhaustive-match (opt-in, not part of --strict) also catch a match written without it |
an asyncio.create_task result is kept; no blocking call inside async def |
Ruff RUF006, ASYNC |
| datetimes carry a time zone | Ruff DTZ |
raise idioms, pytest style, FastAPI dependencies with Annotated |
Ruff TRY, PT, FAST, UP |
output goes through logging, never print — stdout carries only the JSON lines the formatter in bootstrap/ writes (fastapi → Service endpoints, logs and shutdown) |
Ruff T20 |
The reviewer checks
- A domain exception the route needs to tell apart is a class with fields, not an empty
pass; an
operation's expected failures are a known set of such classes, and the cause travels withfrom e. - One translation of failures to answers, in one module (the exception handlers), as a
matchclosed byassert_neveror a table keyed by the exception class — never a mapper per route. - A failure the user only sees as a generic answer writes one log line with the cause's name, never secrets,
keys or addresses. - Impossible states are unrepresentable: a union of frozen dataclasses with a
Literalstatus
(Loading | Failed(error) | Ready(data)), notis_loading,error: str | Noneanddata: … | Noneside by
side. - Narrowing is real: by
match,isinstance, or aTypeIs/TypeGuardfunction whose body actually checks
— a lying guard is worse than acast, andcaston outside data is the same lie. - Independent awaits run together (
asyncio.TaskGroup, orgather); a sequence is a sequence only when one
needs the other. Not over oneAsyncSession: a session is one connection (fastapi-sqlalchemy→ Session and
unit of work). - Ids of different things are different types (
NewType("UserId", UUID)or a value object): a user id is
not an email, and the checker refuses one where the other is expected. - Closed sets are
LiteralorStrEnum; value objects are@dataclass(frozen=True, slots=True); domain
collections are tuples orfrozenset, and a function does not change its arguments — the checker then refuses a
misspelt status and a value changed after it was checked. - One rule, one module: a format, a status list, a limit is imported from its owner (the domain model's
«Кто ещё применяет»), never typed again — a copy matches today and drifts at the next change. - A shared step lives in a module of shared steps, never imported from another use case's module — otherwise
a change to one use case silently changes the other.