Python Typing
Choose the strongest valid lane automatically. Do not ask the user to pick a typing philosophy.
Consult references/typing-policy.md for the full policy document.
Required Policy
- Forbid
Any, broad object, and unchecked cast() in normal internal code
- Allow them only at explicit boundaries where unknown-shape data enters
- Isolate boundary code in dedicated validator, parser, adapter, or boundary modules
- Validate immediately and return strongly typed internal objects
- Do not let raw payloads cross into the typed core
- Allow narrow lint exceptions for
Any only in approved boundary modules
Lane Selection
1. Python 3.10 Constrained or stdlib-only
- Use only compatibility-safe stdlib typing features
- Prefer
dataclasses, TypedDict, Protocol, Literal, TypeGuard, NewType
- Validate with explicit runtime checks in dedicated boundary wrappers
- No third-party type assumptions
2. Python 3.11+ stdlib-only
- Use modern stdlib typing features supported by the interpreter
- Use
Self, assert_type, and reveal_type where useful during refactoring
TypedDict with NotRequired
3. Python 3.11+ with Pydantic
- Use Pydantic models for ingress contracts
- Prefer strict mode where coercion would hide upstream errors
- Use
TypeAdapter for annotated types that do not need full models
- See
references/pydantic-boundaries.md
4. Python 3.11+ with Hypothesis
- Property-test boundaries, validators, parsers, and invariants
- Prefer
from_type() where practical
- See
references/hypothesis-boundaries.md
5. Python 3.12+
- Use
type statement for explicit type aliases: type JSONValue = str | int | ...
- Use PEP 695 generic parameter syntax for new generic helpers
6. Python 3.13+
- Use
TypeIs for clearer custom narrowing helpers (replaces TypeGuard where bidirectional narrowing needed)
- Use
ReadOnly in TypedDict fields that must not mutate after validation
7. Python 3.14+
- Keep annotation-reading infrastructure compatible with deferred evaluation (PEP 649)
- Use
annotationlib.get_annotations() in infrastructure that inspects annotations at runtime
Boundary Implementation Standard
Use dedicated wrappers named like:
parse_*
validate_*
decode_*
coerce_*
*_from_raw
Boundary modules should return typed objects only.
Example: stdlib-only boundary
from typing import TypedDict, NotRequired
from dataclasses import dataclass
class _RawIncoming(TypedDict):
user_id: int
email: str
metadata: NotRequired[dict[str, str]]
@dataclass(frozen=True, slots=True)
class IncomingPayload:
user_id: int
email: str
metadata: dict[str, str]
def parse_incoming(data: _RawIncoming) -> IncomingPayload:
return IncomingPayload(user_id=data["user_id"], email=data["email"], metadata=data.get("metadata", {}))
Example: Pydantic boundary
from pydantic import BaseModel, TypeAdapter
class IncomingPayload(BaseModel):
user_id: int
email: str
metadata: dict[str, str] = {}
model_config = {"strict": True}
def parse_incoming(data: dict[str, object]) -> IncomingPayload:
return IncomingPayload.model_validate(data)
References
references/typing-policy.md — full boundary validation policy
references/pydantic-boundaries.md — Pydantic model and TypeAdapter patterns
references/hypothesis-boundaries.md — property-based testing for validators
1---2name: python3-typing3description: Auto-selects and enforces the strongest valid Python typing lane for the detected Python version and dependencies — no user input required. Use when adding or tightening type annotations, eliminating Any usage in internal code, designing boundary validators or parsers, choosing between stdlib typing (TypedDict, Protocol, dataclasses), Pydantic models, or Hypothesis property tests, addressing ty or mypy failures, or applying version-specific features (TypeIs, ReadOnly, PEP 695 generics, PEP 649 deferred evaluation). Enforces boundary isolation — raw payloads validated immediately at ingress and returned as typed internal objects.4---56# Python Typing78Choose the strongest valid lane automatically. Do not ask the user to pick a typing philosophy.910Consult `references/typing-policy.md` for the full policy document.1112## Required Policy1314- Forbid `Any`, broad `object`, and unchecked `cast()` in normal internal code15- Allow them only at explicit boundaries where unknown-shape data enters16- Isolate boundary code in dedicated validator, parser, adapter, or boundary modules17- Validate immediately and return strongly typed internal objects18- Do not let raw payloads cross into the typed core19- Allow narrow lint exceptions for `Any` only in approved boundary modules2021## Lane Selection2223### 1. Python 3.10 Constrained or stdlib-only2425- Use only compatibility-safe stdlib typing features26- Prefer `dataclasses`, `TypedDict`, `Protocol`, `Literal`, `TypeGuard`, `NewType`27- Validate with explicit runtime checks in dedicated boundary wrappers28- No third-party type assumptions2930### 2. Python 3.11+ stdlib-only3132- Use modern stdlib typing features supported by the interpreter33- Use `Self`, `assert_type`, and `reveal_type` where useful during refactoring34- `TypedDict` with `NotRequired`3536### 3. Python 3.11+ with Pydantic3738- Use Pydantic models for ingress contracts39- Prefer strict mode where coercion would hide upstream errors40- Use `TypeAdapter` for annotated types that do not need full models41- See `references/pydantic-boundaries.md`4243### 4. Python 3.11+ with Hypothesis4445- Property-test boundaries, validators, parsers, and invariants46- Prefer `from_type()` where practical47- See `references/hypothesis-boundaries.md`4849### 5. Python 3.12+5051- Use `type` statement for explicit type aliases: `type JSONValue = str | int | ...`52- Use PEP 695 generic parameter syntax for new generic helpers5354### 6. Python 3.13+5556- Use `TypeIs` for clearer custom narrowing helpers (replaces `TypeGuard` where bidirectional narrowing needed)57- Use `ReadOnly` in `TypedDict` fields that must not mutate after validation5859### 7. Python 3.14+6061- Keep annotation-reading infrastructure compatible with deferred evaluation (PEP 649)62- Use `annotationlib.get_annotations()` in infrastructure that inspects annotations at runtime6364## Boundary Implementation Standard6566Use dedicated wrappers named like:6768- `parse_*`69- `validate_*`70- `decode_*`71- `coerce_*`72- `*_from_raw`7374Boundary modules should return typed objects only.7576### Example: stdlib-only boundary7778```python79from typing import TypedDict, NotRequired80from dataclasses import dataclass818283class _RawIncoming(TypedDict):84 user_id: int85 email: str86 metadata: NotRequired[dict[str, str]]878889@dataclass(frozen=True, slots=True)90class IncomingPayload:91 user_id: int92 email: str93 metadata: dict[str, str]949596def parse_incoming(data: _RawIncoming) -> IncomingPayload:97 return IncomingPayload(user_id=data["user_id"], email=data["email"], metadata=data.get("metadata", {}))98```99100### Example: Pydantic boundary101102```python103from pydantic import BaseModel, TypeAdapter104105106class IncomingPayload(BaseModel):107 user_id: int108 email: str109 metadata: dict[str, str] = {}110 model_config = {"strict": True}111112113def parse_incoming(data: dict[str, object]) -> IncomingPayload:114 return IncomingPayload.model_validate(data)115```116117## References118119- `references/typing-policy.md` — full boundary validation policy120- `references/pydantic-boundaries.md` — Pydantic model and TypeAdapter patterns121- `references/hypothesis-boundaries.md` — property-based testing for validators