# Typing Review Workflow This workflow is distilled from practical typing modernization passes and is designed for latest-syntax-first upgrades. ## Step-by-Step Process 1. Identify the Python baseline from project config (`requires-python`, lint target version, toolchain constraints). 2. Scan target files for legacy typing patterns and repeated opportunities. 3. Apply highest-value modern syntax updates first: - `typing.List`/`typing.Dict` -> built-in generics. - `Optional[T]`/`Union[A, B]` -> `T | None` / `A | B`. 4. Upgrade generic declarations to PEP 695 syntax where baseline allows: - `TypeVar` module globals -> local type parameters in classes/functions. 5. Tighten domain contracts where clear: - replace unconstrained `str` with `Literal[...]` for finite known values. - use `Self` for fluent APIs. 6. Keep edits minimal and avoid behavior changes unless requested. 7. Validate with lint and editor diagnostics. 8. Report applied changes, hard-blocker deferrals, and sources consulted. ## Decision Points and Branching - If Python baseline is below 3.12: - use the newest syntax available under that baseline, and document exactly what blocked PEP 695. - If a legacy annotation is public API and downstream tooling compatibility is unknown: - still modernize syntax unless there is a confirmed breakage risk with a named downstream constraint. - If replacing `TypeVar` with PEP 695 affects readability debates only: - still prefer PEP 695; readability preference alone is not a blocker. - If a stricter type (for example `Literal`) may reject existing runtime inputs: - apply only when the input contract is already finite; otherwise defer with a contract-change note. ## Deterministic Narrowing with `match` Use structural pattern matching when the domain is a closed set (for example tagged unions, enum dispatch, or finite literal variants). 1. Prefer `match` over long `if`/`elif` ladders when each branch represents a distinct variant. 2. For tagged unions, match the discriminant and extract payload fields in the same case. 3. Add a default `case _:` branch with `assert_never(...)` to enforce exhaustiveness in static analysis. 4. Keep patterns explicit and side-effect-light; avoid relying on bindings from failed matches. Example with a tagged `TypedDict` union: ```python from typing import Literal, TypedDict, assert_never class NewJobEvent(TypedDict): tag: Literal["new-job"] job_name: str class CancelJobEvent(TypedDict): tag: Literal["cancel-job"] job_id: int type Event = NewJobEvent | CancelJobEvent def route(event: Event) -> str: match event: case {"tag": "new-job", "job_name": job_name}: return f"enqueue:{job_name}" case {"tag": "cancel-job", "job_id": job_id}: return f"cancel:{job_id}" case _: assert_never(event) ``` This pattern makes narrowing deterministic per branch and surfaces missing variants as type-checker errors during review. ## Quality Criteria 1. All edits are syntax-valid for the target Python versions. 2. Lint and diagnostics pass for edited files. 3. Runtime behavior is unchanged for modernization-only tasks. 4. Recommendations cite authoritative sources. 5. Output clearly separates "changed now" from hard-blocked follow-up items. ## Suggested Validation Commands - `uv run ruff check ` - `uv run pytest -q` (or targeted tests where available) Use repository-preferred test invocation conventions when they differ.