Writing Python (3.14+)
Language-level Python for this project.
Scope routing
| If you need to… |
Read |
| Configure ruff/ty, naming, imports, docstrings (Google style) |
references/code-style.md |
| Add type annotations, generics, protocols, narrowing |
references/type-safety.md |
| Decide between composition vs inheritance, layer code, inject dependencies |
references/design-patterns.md |
| Audit code against a checklist of known bad patterns |
references/anti-patterns.md |
| Validate inputs, design exception hierarchies, handle partial failures |
references/error-handling.md |
| Manage connections/file handles/streams via context managers |
references/resource-management.md |
Load env vars, set up pydantic-settings, validate config at boot |
references/configuration.md |
| Look up everyday patterns (project layout, async, functools caching/dispatch, pathlib, logging) |
references/patterns.md |
Build a CLI (typer, output formats, progress, exit codes) |
references/cli.md |
Write tests with pytest (fixtures, async, parametrize, coverage) |
references/testing.md |
House style — non-negotiable
- Pydantic, not dataclasses. Every structured value object is a
BaseModel. Settings are pydantic-settings.BaseSettings. Don't reach for @dataclass.
- Type hints on every public signature.
ty runs in CI; untyped code fails.
uv for everything Python-level. Don't use pip/poetry/pyenv directly.
ruff for lint + format. Don't add black/isort/flake8.
ty for type checking. Don't add mypy/pyright. CI runs uvx ty check.
- Stdlib first. Only add a dep when stdlib genuinely doesn't fit (e.g.
httpx for async HTTP, typer/rich for CLIs, pydantic for validation).
- Fail fast. Validate at boundaries; raise informative errors immediately.
- Self-documenting code, not noise comments. Comment only the non-obvious why; the default is no comment. See
references/code-style.md.
Quick patterns
# Type hints — all signatures, modern union syntax
def get_user(user_id: str) -> User | None: ...
async def fetch(url: str, timeout: float = 30.0) -> dict[str, object]: ...
# Pydantic model (instead of @dataclass)
from pydantic import BaseModel, Field
class Config(BaseModel):
host: str
port: int = Field(default=8080, ge=1, le=65535)
tags: list[str] = Field(default_factory=list)
# Pattern matching for shape dispatch
match event:
case {"type": "click", "x": x, "y": y}:
handle_click(x, y)
case {"type": "key", "code": code}:
handle_key(code)
case _:
raise ValueError(f"Unknown event: {event}")
Toolchain
uv sync # install deps from uv.lock
uv add <pkg> # add a runtime dep
uv add --dev <pkg> # add a dev dep
uvx ruff check --fix . # lint + autofix (isolated env, no install needed)
uvx ruff format . # format
uvx ty check # type-check
uv run pytest -v # tests (project-aware — pytest must import your package)
uv run pytest --cov=src # tests + coverage
uv run fastapi dev # FastAPI app, when applicable
Rule of thumb: uvx <tool> for tools that only need the file (ruff, ty); uv run <tool> for tools that need your project's environment (pytest, fastapi, anything that imports your code).
Python 3.14 features worth knowing
- Template strings —
t"Hello {name}" returns a Template object (use for SQL/HTML where literal interpolation is unsafe).
except without parens — except ValueError, TypeError: is valid.
concurrent.interpreters — true parallelism via subinterpreters.
compression.zstd — Zstandard in stdlib.
- Free-threaded build (PEP 703) — no GIL, opt-in.
Cross-skill boundaries
python-infrastructure — what to do when the function above needs to retry across a network boundary, run in a worker, or be observable in prod.
fastapi — HTTP routing, dependency injection, async-vs-sync runtime semantics, FastAPI/Pydantic conventions.
testing-python — this repo's pytest wiring (config, markers, fixture layout); references/testing.md here covers technique only.
astral:uv / astral:ruff / astral:ty — deep tool reference for the toolchain.
Top gotchas
- Mutable default arguments share state —
def f(x=[]): reuses the same list across calls. Use x: list | None = None and create inside.
assert is stripped by python -O — never use for runtime validation; only for code-internal invariants.
- Pydantic API surface to remember —
model_dump / model_validate / model_dump_json / model_validate_json, Field(alias=..., default_factory=...), model_config = {...}, @field_validator / @model_validator. Don't paste snippets using dict(), parse_obj(), Config class, or @validator — those are from an older API and won't work.
1---2name: writing-python3description: Idiomatic Python 3.14+, Pydantic-first (not dataclasses), uv/ruff/ty toolchain. Use when writing or reviewing Python code — style, typing, design patterns, anti-patterns, error handling, resources, config, CLI, testing — or establishing project conventions. NOT for background jobs, retries, or observability (use `python-infrastructure`).4---56# Writing Python (3.14+)78Language-level Python for this project.910## Scope routing1112| If you need to… | Read |13| -------------------------------------------------------------------------- | ----------------------------------- |14| Configure ruff/ty, naming, imports, docstrings (Google style) | `references/code-style.md` |15| Add type annotations, generics, protocols, narrowing | `references/type-safety.md` |16| Decide between composition vs inheritance, layer code, inject dependencies | `references/design-patterns.md` |17| Audit code against a checklist of known bad patterns | `references/anti-patterns.md` |18| Validate inputs, design exception hierarchies, handle partial failures | `references/error-handling.md` |19| Manage connections/file handles/streams via context managers | `references/resource-management.md` |20| Load env vars, set up `pydantic-settings`, validate config at boot | `references/configuration.md` |21| Look up everyday patterns (project layout, async, functools caching/dispatch, pathlib, logging) | `references/patterns.md` |22| Build a CLI (`typer`, output formats, progress, exit codes) | `references/cli.md` |23| Write tests with `pytest` (fixtures, async, parametrize, coverage) | `references/testing.md` |2425## House style — non-negotiable2627- **Pydantic, not dataclasses.** Every structured value object is a `BaseModel`. Settings are `pydantic-settings.BaseSettings`. Don't reach for `@dataclass`.28- **Type hints on every public signature.** `ty` runs in CI; untyped code fails.29- **`uv` for everything Python-level.** Don't use `pip`/`poetry`/`pyenv` directly.30- **`ruff` for lint + format.** Don't add `black`/`isort`/`flake8`.31- **`ty` for type checking.** Don't add `mypy`/`pyright`. CI runs `uvx ty check`.32- **Stdlib first.** Only add a dep when stdlib genuinely doesn't fit (e.g. `httpx` for async HTTP, `typer`/`rich` for CLIs, `pydantic` for validation).33- **Fail fast.** Validate at boundaries; raise informative errors immediately.34- **Self-documenting code, not noise comments.** Comment only the non-obvious _why_; the default is no comment. See `references/code-style.md`.3536## Quick patterns3738```python39# Type hints — all signatures, modern union syntax40def get_user(user_id: str) -> User | None: ...41async def fetch(url: str, timeout: float = 30.0) -> dict[str, object]: ...4243# Pydantic model (instead of @dataclass)44from pydantic import BaseModel, Field4546class Config(BaseModel):47 host: str48 port: int = Field(default=8080, ge=1, le=65535)49 tags: list[str] = Field(default_factory=list)5051# Pattern matching for shape dispatch52match event:53 case {"type": "click", "x": x, "y": y}:54 handle_click(x, y)55 case {"type": "key", "code": code}:56 handle_key(code)57 case _:58 raise ValueError(f"Unknown event: {event}")59```6061## Toolchain6263```bash64uv sync # install deps from uv.lock65uv add <pkg> # add a runtime dep66uv add --dev <pkg> # add a dev dep6768uvx ruff check --fix . # lint + autofix (isolated env, no install needed)69uvx ruff format . # format70uvx ty check # type-check7172uv run pytest -v # tests (project-aware — pytest must import your package)73uv run pytest --cov=src # tests + coverage74uv run fastapi dev # FastAPI app, when applicable75```7677**Rule of thumb**: `uvx <tool>` for tools that only need the file (`ruff`, `ty`); `uv run <tool>` for tools that need your project's environment (`pytest`, `fastapi`, anything that imports your code).7879## Python 3.14 features worth knowing8081- **Template strings** — `t"Hello {name}"` returns a `Template` object (use for SQL/HTML where literal interpolation is unsafe).82- **`except` without parens** — `except ValueError, TypeError:` is valid.83- **`concurrent.interpreters`** — true parallelism via subinterpreters.84- **`compression.zstd`** — Zstandard in stdlib.85- **Free-threaded build (PEP 703)** — no GIL, opt-in.8687## Cross-skill boundaries8889- **`python-infrastructure`** — what to do when the function above needs to retry across a network boundary, run in a worker, or be observable in prod.90- **`fastapi`** — HTTP routing, dependency injection, async-vs-sync runtime semantics, FastAPI/Pydantic conventions.91- **`testing-python`** — this repo's pytest wiring (config, markers, fixture layout); `references/testing.md` here covers technique only.92- **`astral:uv`** / **`astral:ruff`** / **`astral:ty`** — deep tool reference for the toolchain.9394## Top gotchas9596- **Mutable default arguments share state** — `def f(x=[]):` reuses the same list across calls. Use `x: list | None = None` and create inside.97- **`assert` is stripped by `python -O`** — never use for runtime validation; only for code-internal invariants.98- **Pydantic API surface to remember** — `model_dump` / `model_validate` / `model_dump_json` / `model_validate_json`, `Field(alias=..., default_factory=...)`, `model_config = {...}`, `@field_validator` / `@model_validator`. Don't paste snippets using `dict()`, `parse_obj()`, `Config` class, or `@validator` — those are from an older API and won't work.