Python-Aware
Loaded by: composed by repo-aware skills (airflow-aware, commitizen-aware); read by strict-review when a diff contains Python; may also trigger from its description during any Python work.
Applies to: Writing, testing, or reviewing Python code in any repository.
When to apply
This is a knowledge layer, not a review checklist. It holds Python language and
toolchain conventions that stay true no matter which repo you are in. Anything that only
holds for one repository (its class names, directory layout, CI matrix, custom tooling)
belongs in that repo's <repo>-aware skill, not here.
Once loaded, treat every convention as background — do not re-read each task.
Layering & precedence
- The repo's own
AGENTS.md/CLAUDE.md(and contributor docs) — always wins. - The repo-aware skill (
airflow-aware,commitizen-aware, …). - This skill — the default when nothing above says otherwise.
A repo-aware skill that wants to change a default here must say so explicitly (an "override: …" line); silence means the default below applies.
Toolchain
- Use the repo-managed environment (
uv run …, or whatever the repo specifies) — never the host's globally installedpytest,mypy, orruff; they run a different version than CI. - Hook runner: if the repo uses
prek, runprek, notpre-commit. Hook-runner behavior that produces false greens (stash,git add -N,rebase --continue, wrongfiles:regex) is language-neutral — seehook-runner-verification.md. - Ruff:
ruff formatthenruff check --fix, with the repo's own configuration (line length, rule selection) — do not impose defaults. uv.lockdiff you did not cause: if it returns aftergit checkout HEAD -- uv.lockplus the nextuv sync, suspect lockfile drift already onmain(someone edited apyproject.tomlwithout re-runninguv lock), not your environment. Keep the re-lock out of a feature PR.- pytest config: prefer
[tool.pytest](pytest 9.0+ reads it natively) over[tool.pytest.ini_options]; this needsrequires-python >= 3.10.
References index
| Reference | Read when |
|---|---|
references/code-style.md |
Writing or reviewing Python style: import placement and aliasing, naming and constants, raise-vs-document validation, ContextVar in async generators, mypy narrowing, docstring conventions, subprocess returncode checks |
references/test-conventions.md |
Writing or reviewing tests and one of the named patterns below applies (mock assertions, parametrize, reject-path shapes, tripwires, class-attribute / stub-shape / codespell traps, unix-socket path length…) — full rationale and recipes |
references/runtime-gotchas.md |
Code that passes a type checker and a casual read but misbehaves at runtime: pydantic Annotated constraints, nested enums, subscripted generics, Decimal → int |
references/tooling.md |
Dependency-floor bumps that make a stale venv go red, several agents sharing one worktree .venv, a script a hook runner executes directly, or measuring coverage with --cov |
Coding rules
- No
assertin production code — raise a real exception (ValueError,RuntimeError, …);assertdisappears underpython -O. time.monotonic()for elapsed time — nottime.time(), which can jump.- Imports go at the top of the file by default, including in
__init__and any function that runs on every public touch of a class. Use the function-body form for exactly four cases: (1) aTYPE_CHECKINGblock, (2) breaking a known circular-import cycle, (3) a multi-process worker-isolation path, (4) a deferred-execution callback (a body not called at module import time). Say which case it is in a comment. Self-check: "does this function run on every public touch of the class?" If yes, top-level.
Testing
- pytest only — do not subclass
unittest.TestCase. - Every mock carries
spec=orautospec=True— a bareMagicMock()is not acceptable;autospec=Trueadditionally checks call signatures at call time (a wrong call raisesTypeError); barespec=only restricts attribute access. - Time-dependent tests use the repo's existing time-travel library (
time_machine,freezegun, …) — do not monkey-patchdatetime.nowby hand. - Mock assertions: prefer
.mock_callsequality —m.method.mock_calls == [mock.call(...)]expresses "called exactly once, with these arguments, and nothing else" in one assertion, and pytest diffs it directly. Use the child-mock form (m.method.mock_calls), not the parent's. A repo that standardizes onassert_called_once_withcan say so as an explicit override. - Parametrize by default — stacked asserts or near-duplicate test methods are must-fix in review;
split into
@pytest.mark.parametrizewithpytest.param(..., id=...)naming each case, and extract repeated setup/cleanup into a fixture. - Full-sequence assertions — assert the whole iterable/list, not head + tail + length.
- Cap-boundary pairs — a numeric cap needs both an at-cap (allowed) and a one-over-cap (trips) test.
- No thin test helpers — assert only the field the function under test decides; skip one-use wrapper helpers.
- Minimize forced test churn — keep an existing test's diff to the forced minimum; add a new test for new behavior.
- Build the fixture, don't defer — a missing example fixture is not "can't test"; build the smallest purpose-built one.
- Drift guard green in its own commit — the commit that adds an "every X needs an entry" test must add the existing entries and doc rows too, or it fails when checked out alone.
- Private SDK path: fail, don't skip — a test that depends on a third-party SDK's private
module
pytest.fails when the path is missing; a skip hides the gap behind a green CI. - Tripwire tests: prove which assertion fails, name the component — mutate one deny-list entry and name the assertion that turns red; a test name must not credit a rejection that only a downstream function performs.
- Log assertion when the log IS the observable — a repo rule against
caplogtargets scraping raw log text, not asserting an operator-visible log call via a mocked logger.
Worked examples and the rest of the named patterns (parametrize + shared fixtures,
reject-path shapes, prose-guard windows) are in references/test-conventions.md.
Composing with other skills
strict-review: when the diff contains Python, the Testing and Coding rules sections above strengthen base review items 3–6. Raise a violation as a finding with the rule named; the recipe detail lives inreferences/test-conventions.md.- Repo-aware skills (
airflow-aware,commitizen-aware): they declare "read python-aware first" and list only what differs. If a repo-aware rule and a rule here conflict, the repo-aware rule wins when it is written as an explicit override; the repo'sAGENTS.mdbeats both. - Layering guidance for adding another language layer:
docs/reference/skills.md.