Skip to content

Drop Python 3.9 support (cleanup checklist) #279

@MaxGhenis

Description

@MaxGhenis

Trigger

File when all confirmed 3.9 users (currently: Census) have migrated to 3.10+.

Background

Supporting 3.9 across the stack required working around non-trivial syntax and dependency cruft in four repos. ~95% of the cruft is 3.9-specific — dropping 3.9 alone recovers most of it, even if 3.10 support remains (3.10 hits EOL Oct 2026, ~6 months out, so keeping it through natural life is cheap).

Landed as: PolicyEngine/policyengine-core#454, PolicyEngine/policyengine-us#8035, PolicyEngine/policyengine-uk#1625, #278.

Checklist

All 4 repos

  • pyproject.toml: raise requires-python from >=3.9 to >=3.10
  • Remove Programming Language :: Python :: 3.9 classifier
  • Remove "3.9" from all CI Python matrices

policyengine-core (PolicyEngine/policyengine-core)

  • Remove numpy env marker; go back to single numpy>=2.1.0,<3 (2.1+ supports 3.10+)
  • Hand-revert Optional[X]/Tuple[X]X | None/tuple[X] in policyengine_core/tools/hugging_face.py and policyengine_core/tools/google_cloud.py (~5 sites; this repo's ruff config doesn't have UP rules enabled)

policyengine-us (PolicyEngine/policyengine-us)

  • Remove [tool.uv] environments block in pyproject.toml if it still exists (likely already gone once downstream core picks up 3.9-compat release)
  • Hand-revert Optional[X]X | None in: policyengine_us/system.py, data/economic_assumptions.py, data/dataset_schema.py, tests/core/test_payroll_contributions.py, tests/microsimulation/data/fixtures/test_extend_single_year_dataset.py (5 sites)
  • Regenerate uv.lock

policyengine-uk (PolicyEngine/policyengine-uk)

  • Remove [tool.uv] environments block (if still present)
  • Hand-revert 3 sites in policyengine_uk/data/dataset_schema.py and policyengine_uk/simulation.py
  • policyengine_uk/tests/test_build_metadata.py: revert ExitStack back to parenthesized with (a, b, c):
  • [tool.ruff] target-version = "py39""py310"
  • Regenerate uv.lock

policyengine.py (this repo)

  • pyproject.toml:
    • [tool.ruff] target-version = "py39""py310"
    • [tool.mypy] python_version = "3.9""3.10"
    • Remove UP006, UP007, UP035, UP045 from [tool.ruff.lint] ignore
  • Auto-revert ~210 annotation sites: ruff check --fix --unsafe-fixes . — this re-applies UP045 (Optional[X]X | None), UP007 (Union[X, Y]X | Y), UP006/UP035 (Listlist).
  • Hand-revert the two discriminated-union sites ruff's UP rules don't cover: src/policyengine/core/scoping_strategy.py:222 (inside Annotated[]) and src/policyengine/core/region.py:18 (module-level type alias).
  • Simplify Python-Compat CI job in .github/workflows/pr_code_changes.yaml — drop the explicit h5py install (needed because scoping_strategy.py imports it but extras weren't available; once [us]/[uk] extras are bumped to versions that pull h5py via core, it's redundant).
  • Bump [us]/[uk]/[dev] extras off policyengine-us==1.602.0 / policyengine-uk==2.74.0 to versions that don't pin 3.11+ themselves (separate follow-up from this cleanup — may already be bumped by the time this issue is actioned).

Not in scope

Keep until 3.10 EOL (October 2026):

  • class Foo(str, Enum) pattern (4 files: outputs/{aggregate,change_aggregate,inequality,poverty}.py) — StrEnum is 3.11+
  • timezone.utc in core/tax_benefit_model_version.pydatetime.UTC is 3.11+
  • Gotcha when StrEnum returns: str(Foo.X) differs — StrEnum returns "x", (str, Enum) returns "Foo.X". Audit any code relying on str() of enum members before reverting.

Keep until 3.12 EOL (October 2028):

  • TypeVar + Generic[T] pattern in core/cache.py and core/output.py — PEP 695 class Foo[T] is 3.12+.

Verification

After cleanup, each repo's CI should be green with reduced matrix (3.10+). Cross-repo smoke: pip install policyengine[us] and pip install policyengine[uk] succeed on 3.10 in a fresh venv.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions