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
- 2026-07-01 | Aligned this page with Style Rule Contract so style guidance names default behavior, banned patterns, exceptions, proof, and codification expectations. Source: User request, 2026-07-01
- 2026-07-01 | Added a Python contract and proof rule for
io: Io, typed boundaries, explicit errors, early flow, constants, and refusal-path tests. Source: User request, 2026-07-01