Files
prompts/docs/skills/python-typing/references/review-workflow.md
T
2026-06-21 15:41:22 -05:00

3.4 KiB

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:

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 <paths>
  • uv run pytest -q (or targeted tests where available)

Use repository-preferred test invocation conventions when they differ.