Python
Purpose
Write production Python that is type-safe, async-first, and testable. This skill sets a single quality bar — annotated, linted, tested — and applies it consistently to new code and to code being modernized.
When to Use
- Writing new Python modules, packages, or services.
- Adding type coverage to an untyped or partially typed codebase.
- Converting blocking I/O to
asyncio, or debugging async behavior.
- Standing up a pytest suite, fixtures, or parametrized tests.
- Modernizing Python 2-era or pre-3.10 idioms.
Capabilities
- Full type annotation, including generics,
Protocol, TypedDict, and ParamSpec.
- Async design: task groups, timeouts, cancellation, structured concurrency.
- Data modeling with
dataclasses, enum, and Pydantic when validation is needed.
- Test authoring: fixtures, factories, mocking, property-based tests via Hypothesis.
- Tooling configuration:
pyproject.toml, ruff, mypy, uv or Poetry.
- Profiling and hot-path optimization.
Inputs
- Source files or a package path.
- Target Python version (default: 3.12).
- Existing tooling config, if any.
- Runtime constraints: sync vs async, framework, deployment target.
Outputs
- Type-annotated source that passes
mypy --strict.
- A pytest suite with meaningful assertions, not coverage padding.
- A
pyproject.toml section configuring ruff and mypy.
- A short summary of behavioral changes when refactoring.
Workflow
- Survey — Read the module and its imports. Identify the runtime model (sync, async, threaded) and existing conventions. Do not fight established conventions without a reason.
- Model the data — Define dataclasses, enums, and protocols before writing logic. Type the boundaries first.
- Implement — Write the smallest correct version. Prefer standard library over dependencies.
- Test — Cover the contract and the failure modes, not the implementation details.
- Gate — Run
ruff check --fix, ruff format, mypy --strict, pytest. Fix each failure and re-run until all four are clean.
Best Practices
- Use
X | None, not Optional[X]. Use list[str], not List[str].
- Never use a bare
except:. Catch the narrowest exception that can actually be raised.
- Raise domain-specific exceptions; do not signal failure with
None return values.
- Use
pathlib.Path for every filesystem path.
- Never mutate a default argument. Use
field(default_factory=...).
- Guard async code with explicit timeouts; an un-timed
await on a network call is a latency bug waiting to happen.
- Log with the
logging module and structured extras — never print in library code.
Examples
Typed, async, cancellation-safe fetch:
import asyncio
from dataclasses import dataclass
import httpx
@dataclass(frozen=True, slots=True)
class Quote:
symbol: str
price: float
class QuoteUnavailable(Exception):
"""Raised when the upstream cannot serve a quote."""
async def fetch_quotes(symbols: list[str], *, timeout: float = 5.0) -> list[Quote]:
async with httpx.AsyncClient(timeout=timeout) as client:
async with asyncio.TaskGroup() as tg:
tasks = {s: tg.create_task(client.get(f"/quote/{s}")) for s in symbols}
quotes: list[Quote] = []
for symbol, task in tasks.items():
response = task.result()
if response.status_code != 200:
raise QuoteUnavailable(symbol)
quotes.append(Quote(symbol=symbol, price=response.json()["price"]))
return quotes
Test that covers the contract and the failure:
import pytest
@pytest.mark.asyncio
async def test_fetch_quotes_raises_on_upstream_error(mock_client):
mock_client.get.return_value.status_code = 503
with pytest.raises(QuoteUnavailable, match="AAPL"):
await fetch_quotes(["AAPL"])
Notes
TaskGroup requires Python 3.11+. On 3.10, use asyncio.gather(..., return_exceptions=True) and re-raise explicitly.
mypy --strict on a large legacy codebase is a project, not a task. Enable it per-module with disallow_untyped_defs and expand the surface gradually.
- Prefer
uv for new projects; it is materially faster than Poetry and pip for resolution and installs.
1---2name: python3description: Use when writing, reviewing, or modernizing Python 3.11+ code. Produces fully type-annotated modules, async I/O, dataclasses and protocols, pytest suites, and a lint/type gate built on ruff and mypy --strict.4---56# Python78## Purpose910Write production Python that is type-safe, async-first, and testable. This skill sets a single quality bar — annotated, linted, tested — and applies it consistently to new code and to code being modernized.1112## When to Use1314- Writing new Python modules, packages, or services.15- Adding type coverage to an untyped or partially typed codebase.16- Converting blocking I/O to `asyncio`, or debugging async behavior.17- Standing up a pytest suite, fixtures, or parametrized tests.18- Modernizing Python 2-era or pre-3.10 idioms.1920## Capabilities2122- Full type annotation, including generics, `Protocol`, `TypedDict`, and `ParamSpec`.23- Async design: task groups, timeouts, cancellation, structured concurrency.24- Data modeling with `dataclasses`, `enum`, and Pydantic when validation is needed.25- Test authoring: fixtures, factories, mocking, property-based tests via Hypothesis.26- Tooling configuration: `pyproject.toml`, ruff, mypy, uv or Poetry.27- Profiling and hot-path optimization.2829## Inputs3031- Source files or a package path.32- Target Python version (default: 3.12).33- Existing tooling config, if any.34- Runtime constraints: sync vs async, framework, deployment target.3536## Outputs3738- Type-annotated source that passes `mypy --strict`.39- A pytest suite with meaningful assertions, not coverage padding.40- A `pyproject.toml` section configuring ruff and mypy.41- A short summary of behavioral changes when refactoring.4243## Workflow44451. **Survey** — Read the module and its imports. Identify the runtime model (sync, async, threaded) and existing conventions. Do not fight established conventions without a reason.462. **Model the data** — Define dataclasses, enums, and protocols before writing logic. Type the boundaries first.473. **Implement** — Write the smallest correct version. Prefer standard library over dependencies.484. **Test** — Cover the contract and the failure modes, not the implementation details.495. **Gate** — Run `ruff check --fix`, `ruff format`, `mypy --strict`, `pytest`. Fix each failure and re-run until all four are clean.5051## Best Practices5253- Use `X | None`, not `Optional[X]`. Use `list[str]`, not `List[str]`.54- Never use a bare `except:`. Catch the narrowest exception that can actually be raised.55- Raise domain-specific exceptions; do not signal failure with `None` return values.56- Use `pathlib.Path` for every filesystem path.57- Never mutate a default argument. Use `field(default_factory=...)`.58- Guard async code with explicit timeouts; an un-timed `await` on a network call is a latency bug waiting to happen.59- Log with the `logging` module and structured extras — never `print` in library code.6061## Examples6263**Typed, async, cancellation-safe fetch:**6465```python66import asyncio67from dataclasses import dataclass6869import httpx707172@dataclass(frozen=True, slots=True)73class Quote:74 symbol: str75 price: float767778class QuoteUnavailable(Exception):79 """Raised when the upstream cannot serve a quote."""808182async def fetch_quotes(symbols: list[str], *, timeout: float = 5.0) -> list[Quote]:83 async with httpx.AsyncClient(timeout=timeout) as client:84 async with asyncio.TaskGroup() as tg:85 tasks = {s: tg.create_task(client.get(f"/quote/{s}")) for s in symbols}8687 quotes: list[Quote] = []88 for symbol, task in tasks.items():89 response = task.result()90 if response.status_code != 200:91 raise QuoteUnavailable(symbol)92 quotes.append(Quote(symbol=symbol, price=response.json()["price"]))93 return quotes94```9596**Test that covers the contract and the failure:**9798```python99import pytest100101102@pytest.mark.asyncio103async def test_fetch_quotes_raises_on_upstream_error(mock_client):104 mock_client.get.return_value.status_code = 503105 with pytest.raises(QuoteUnavailable, match="AAPL"):106 await fetch_quotes(["AAPL"])107```108109## Notes110111- `TaskGroup` requires Python 3.11+. On 3.10, use `asyncio.gather(..., return_exceptions=True)` and re-raise explicitly.112- `mypy --strict` on a large legacy codebase is a project, not a task. Enable it per-module with `disallow_untyped_defs` and expand the surface gradually.113- Prefer `uv` for new projects; it is materially faster than Poetry and pip for resolution and installs.