Write idiomatic, typed Python
Add Python that reads as pythonic, is fully type-hinted, and matches the repo's
existing idioms and tooling — clear and correct, not just runnable.
Steps
- Read the lore first. Call
search_lore (Memory MCP) for the repo's Python
conventions and respect its config: the Python version, pyproject.toml
(dependencies, tool config), the formatter/linter (ruff / black), and the type
checker (mypy / pyright). Use the project's environment manager (poetry / venv /
uv) — never install globally.
- Find a sibling module and copy its patterns — package layout, import style,
error handling, how data is modelled, and how tests are organised.
- Type everything. Add type hints on every function signature and public
attribute; prefer precise types (
Sequence, Mapping, Protocol, TypedDict,
Literal) over bare Any. Justify any Any in a comment. Run the project's type
checker and fix the cause of errors rather than # type: ignore-ing them.
- Model data with
@dataclass (frozen where it should be immutable) or Pydantic
when the repo already uses it for validation at boundaries — not loose dicts of
stringly-typed keys.
- Be pythonic. Comprehensions and generators over manual loops where readable;
context managers (
with) for resources; pathlib over string paths; f-strings for
formatting; enumerate/zip over index juggling.
- Handle errors explicitly. Catch the narrowest exception that fits — no bare
except: and no blanket except Exception that swallows. Re-raise with context
(raise X from err) or handle; never silently pass. Validate external input at the
boundary.
- Test with pytest. Use fixtures and
parametrize for table-style cases; cover
happy path, edge cases, and error conditions; assert behaviour, not incidental detail.
- Verify + evidence. Run the project's tests + lint + type check, record
test_output via the record-evidence skill, and submit for review.
Build / Test
- Tests:
pytest (or poetry run pytest); coverage via pytest --cov.
- Lint/format:
ruff check . and black --check . (or the repo's configured
equivalents); fix, don't suppress.
- Types:
mypy . / pyright per the repo config.
- The DoD is verified by the repo's configured test/coverage commands — run them and
record the output; a green run with coverage is the evidence.
Review checklist (a Python reviewer must check)
- Type hints present and precise on all signatures; no unexplained
Any; type
checker passes (no stray # type: ignore).
- No bare
except: and no swallowing except Exception: pass; exceptions are
narrowed and re-raised with context or handled deliberately.
- PEP 8 / formatter clean — ruff + black report no diff.
- Data modelled with dataclasses/Pydantic, not ad-hoc dicts; frozen where immutable.
- Resources use context managers; no leaked file handles / connections.
- Mutable default arguments avoided (
def f(x: list | None = None), not = []).
- Boundary input validated before use (request bodies, env, file content).
- Tests use pytest fixtures/
parametrize and cover error paths, not only the happy path.
Rules
- Match the repo's Python version, env manager, linter/formatter, and type checker exactly.
- Type hints everywhere;
unknown-equivalent precision over Any; validate external input.
- No bare excepts, no swallowed exceptions — narrow and handle or re-raise with context.
- Pythonic idioms (comprehensions, context managers, pathlib) where they improve clarity.
Capture lore
This skill is one of the places durable, reusable knowledge naturally surfaces:
A Python convention this repo enforces beyond the obvious — a version constraint, a typing/validation pattern, an env-manager quirk, or a lint/type-checker rule. That kind of fact is lore. Capture it via the lore-capture
protocol in your brief (CLAUDE.factory.md, step 11 "Memory contribution"):
call the Memory MCP suggest_lore once at the close of your work — reusable
conventions, gotchas, decisions, and boundaries only, never per-ticket trivia.
1---2name: python-conventions3description: Use when a ticket adds or changes Python code and it must follow the repo's Python conventions — PEP 8, full type hints, dataclasses, pythonic idioms, explicit error handling, and pytest with coverage. Invoke for "add this in Python", "fix the type/lint errors", "add the FastAPI/Django endpoint", or as the language pack for any Python change.4---56# Write idiomatic, typed Python78Add Python that reads as pythonic, is fully type-hinted, and matches the repo's9existing idioms and tooling — clear and correct, not just runnable.1011## Steps12131. **Read the lore first.** Call `search_lore` (Memory MCP) for the repo's Python14 conventions and respect its config: the Python version, `pyproject.toml`15 (dependencies, tool config), the formatter/linter (ruff / black), and the type16 checker (mypy / pyright). Use the project's environment manager (poetry / venv /17 uv) — never install globally.182. **Find a sibling module** and copy its patterns — package layout, import style,19 error handling, how data is modelled, and how tests are organised.203. **Type everything.** Add type hints on every function signature and public21 attribute; prefer precise types (`Sequence`, `Mapping`, `Protocol`, `TypedDict`,22 `Literal`) over bare `Any`. Justify any `Any` in a comment. Run the project's type23 checker and fix the cause of errors rather than `# type: ignore`-ing them.244. **Model data with `@dataclass`** (frozen where it should be immutable) or Pydantic25 when the repo already uses it for validation at boundaries — not loose dicts of26 stringly-typed keys.275. **Be pythonic.** Comprehensions and generators over manual loops where readable;28 context managers (`with`) for resources; `pathlib` over string paths; f-strings for29 formatting; `enumerate`/`zip` over index juggling.306. **Handle errors explicitly.** Catch the **narrowest** exception that fits — **no bare31 `except:`** and no blanket `except Exception` that swallows. Re-raise with context32 (`raise X from err`) or handle; never silently pass. Validate external input at the33 boundary.347. **Test with pytest.** Use fixtures and `parametrize` for table-style cases; cover35 happy path, edge cases, and error conditions; assert behaviour, not incidental detail.368. **Verify + evidence.** Run the project's tests + lint + type check, record37 `test_output` via the `record-evidence` skill, and submit for review.3839## Build / Test4041- **Tests:** `pytest` (or `poetry run pytest`); coverage via `pytest --cov`.42- **Lint/format:** `ruff check .` and `black --check .` (or the repo's configured43 equivalents); fix, don't suppress.44- **Types:** `mypy .` / `pyright` per the repo config.45- The DoD is verified by the repo's configured test/coverage commands — run them and46 record the output; a green run with coverage is the evidence.4748## Review checklist (a Python reviewer must check)4950- **Type hints present and precise** on all signatures; no unexplained `Any`; type51 checker passes (no stray `# type: ignore`).52- **No bare `except:`** and no swallowing `except Exception: pass`; exceptions are53 narrowed and re-raised with context or handled deliberately.54- **PEP 8 / formatter clean** — ruff + black report no diff.55- **Data modelled with dataclasses/Pydantic**, not ad-hoc dicts; frozen where immutable.56- **Resources use context managers**; no leaked file handles / connections.57- **Mutable default arguments** avoided (`def f(x: list | None = None)`, not `= []`).58- **Boundary input validated** before use (request bodies, env, file content).59- **Tests** use pytest fixtures/`parametrize` and cover error paths, not only the happy path.6061## Rules6263- Match the repo's Python version, env manager, linter/formatter, and type checker exactly.64- Type hints everywhere; `unknown`-equivalent precision over `Any`; validate external input.65- No bare excepts, no swallowed exceptions — narrow and handle or re-raise with context.66- Pythonic idioms (comprehensions, context managers, pathlib) where they improve clarity.6768## Capture lore6970This skill is one of the places durable, reusable knowledge naturally surfaces:71**A Python convention this repo enforces beyond the obvious — a version constraint, a typing/validation pattern, an env-manager quirk, or a lint/type-checker rule.** That kind of fact is *lore*. Capture it via the **lore-capture72protocol in your brief** (`CLAUDE.factory.md`, step 11 "Memory contribution"):73call the Memory MCP `suggest_lore` once at the close of your work — reusable74conventions, gotchas, decisions, and boundaries only, never per-ticket trivia.