Python Style

We use Ruff for formatting and linting, Pyright for type checking.

Source baseline: docs/src/style/python.mdx. Source: docs/src/style/python.mdx

Formatting

4 spaces, 100 character line width, double quotes, trailing commas. Configured in pyproject.toml. Run pnpm format from repo root or uv run ruff format . locally.

Explicit I/O (The io: Io Pattern)

Functions that do I/O take io: Io as their first parameter. Pure functions do not. This convention eliminates a whole class of bugs related to I/O and event loop blocking.

async def fetch_user(io: Io, user_id: str) -> User | None:   # Does I/O
def compute_discount(user: User, cart: Cart) -> Decimal:       # Pure

The Io type bundles capabilities: io.db for database, io.stripe for payments, io.http for external APIs. Route handlers create Io at the boundary and pass it down. Testing is straightforward: inject a MockIo with canned responses. No patch() gymnastics.

Functions

Use early returns instead of nesting. Do not combine conditions with or in error checks; each failure gets its own check with a specific error message. Functions do one thing. Use named parameters at call sites. Return value saved to its own variable for debuggability.

Naming

snake_case for functions/variables, PascalCase for classes, SCREAMING_SNAKE for constants. Units and qualifiers last: timeout_ms, latency_ms_p99. Terse locals for mechanical variables (res, ret, cfg), descriptive names for domain concepts (user, balance).

Typing

No Any. No object. No raw dict[str, Any]; use typed structures (dataclass, TypedDict, Pydantic model). Pydantic for the edges (request/response), plain types for the internals.

Error Handling

Fail fast and fail loud. Never catch blind exceptions. Define specific exception types namespaced under a base class. Use Result[T, E] for domain logic with explicit error contracts. No fallbacks (a or b or c); be explicit with early returns and precedence.

Constants

No raw dict constants. Use enums and frozen dataclasses. Use IntFlag for combinable boolean properties. Underscores for numeric literals above 1000.

Testing

Inline tests with inline-tests. Tests live next to the code. Test contracts, not implementation details. Test names state the invariant.

Documentation

Module docstrings: first line states what it is/does. Function docstrings: Google-style, imperative mood. Comments explain WHY. Section headers use # --- Label ---.

Python Contract

Concern Default Banned pattern Proof
I/O io: Io first parameter for side-effectful functions Hidden global clients or monkeypatch-only tests Unit test with MockIo
Types Pydantic/dataclass/TypedDict at boundaries, plain types inside Any, object, raw dict[str, Any] Pyright plus parser tests
Errors Specific exceptions or Result[T, E] Blind catch and fallback Error-path test
Flow Early returns with one failure per branch Nested condition soup or a or b or c fallbacks Review and branch tests
Constants Enums, frozen dataclasses, IntFlag for combinable state Raw mutable dict constants Typecheck and unit test

Proof

Python proof should exercise the dependency boundary. If a function takes io: Io, test it with a fake or mock Io; if a parser produces a domain type, test malformed input; if a fallback was tempting, test the explicit refusal path.

Style Rule Contract

This page follows Style Rule Contract: name the default pattern, banned pattern, reason, exception, proof, and codification path clearly enough that the next agent can apply the rule without asking Kevin again. If a local repo has a stricter rule, follow the local rule and update the wiki when the decision becomes durable.


Timeline