From 1456347296c2d1ba2ba1de90937990d6b080c303 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 24 Feb 2026 06:17:55 -0500 Subject: [PATCH 1/5] Add Python 3.14 support - Add Python 3.14 to classifiers and CI test matrix (3.13 + 3.14) - Set python_requires >= 3.11 - Add .python-version for uv default - Add allow-prereleases to setup-python for 3.14 support - Add changelog entry Fixes #178 Co-Authored-By: Claude Opus 4.6 --- .github/workflows/code_changes.yaml | 8 ++++++-- .github/workflows/pr_code_changes.yaml | 8 ++++++-- .python-version | 1 + changelog_entry.yaml | 4 ++++ pyproject.toml | 10 ++++++++-- 5 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 .python-version diff --git a/.github/workflows/code_changes.yaml b/.github/workflows/code_changes.yaml index 33fc437a..78d8499a 100644 --- a/.github/workflows/code_changes.yaml +++ b/.github/workflows/code_changes.yaml @@ -28,6 +28,9 @@ jobs: permissions: contents: "read" id-token: "write" + strategy: + matrix: + python-version: ['3.13', '3.14'] steps: - name: Checkout repo uses: actions/checkout@v4 @@ -37,8 +40,9 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.13' - + python-version: ${{ matrix.python-version }} + allow-prereleases: true + - name: Install package run: uv pip install -e .[dev] --system - name: Install JB diff --git a/.github/workflows/pr_code_changes.yaml b/.github/workflows/pr_code_changes.yaml index aa03bc02..08c64e65 100644 --- a/.github/workflows/pr_code_changes.yaml +++ b/.github/workflows/pr_code_changes.yaml @@ -25,6 +25,9 @@ jobs: run: ruff check . Test: runs-on: macos-latest + strategy: + matrix: + python-version: ['3.13', '3.14'] steps: - name: Checkout repo uses: actions/checkout@v4 @@ -34,8 +37,9 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.13' - + python-version: ${{ matrix.python-version }} + allow-prereleases: true + - name: Install package run: uv pip install -e .[dev] --system - name: Install policyengine diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..6324d401 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29b..cdab11ca 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - Python 3.14 support diff --git a/pyproject.toml b/pyproject.toml index b01b0fbb..8ca0628b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,13 @@ authors = [ {name = "PolicyEngine", email = "hello@policyengine.org"}, ] license = {file = "LICENSE"} -requires-python = ">=3.13" +requires-python = ">=3.11" +classifiers = [ + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] dependencies = [ "pydantic>=2.0.0", "pandas>=2.0.0", @@ -85,7 +91,7 @@ extend-exclude = ''' [tool.ruff] line-length = 79 -target-version = "py313" +target-version = "py311" extend-exclude = ["*.ipynb"] [tool.ruff.lint] From dc6ef4a445c702c95def1586fb1e0c6bacd98ccb Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 24 Feb 2026 06:20:24 -0500 Subject: [PATCH 2/5] Fix pre-existing ruff UP042 lint errors and keep requires-python >= 3.13 - Use StrEnum instead of (str, Enum) pattern (required by ruff UP042) - Keep requires-python >= 3.13 since dependencies require it - Keep ruff target-version at py313 Co-Authored-By: Claude Opus 4.6 --- pyproject.toml | 6 ++---- src/policyengine/outputs/aggregate.py | 4 ++-- src/policyengine/outputs/change_aggregate.py | 4 ++-- src/policyengine/outputs/poverty.py | 6 +++--- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8ca0628b..07434b44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,10 +11,8 @@ authors = [ {name = "PolicyEngine", email = "hello@policyengine.org"}, ] license = {file = "LICENSE"} -requires-python = ">=3.11" +requires-python = ">=3.13" classifiers = [ - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] @@ -91,7 +89,7 @@ extend-exclude = ''' [tool.ruff] line-length = 79 -target-version = "py311" +target-version = "py313" extend-exclude = ["*.ipynb"] [tool.ruff.lint] diff --git a/src/policyengine/outputs/aggregate.py b/src/policyengine/outputs/aggregate.py index 2d41259c..cf4dbca4 100644 --- a/src/policyengine/outputs/aggregate.py +++ b/src/policyengine/outputs/aggregate.py @@ -1,10 +1,10 @@ -from enum import Enum +from enum import StrEnum from typing import Any from policyengine.core import Output, Simulation -class AggregateType(str, Enum): +class AggregateType(StrEnum): SUM = "sum" MEAN = "mean" COUNT = "count" diff --git a/src/policyengine/outputs/change_aggregate.py b/src/policyengine/outputs/change_aggregate.py index b5bfe2df..29fe5069 100644 --- a/src/policyengine/outputs/change_aggregate.py +++ b/src/policyengine/outputs/change_aggregate.py @@ -1,10 +1,10 @@ -from enum import Enum +from enum import StrEnum from typing import Any from policyengine.core import Output, Simulation -class ChangeAggregateType(str, Enum): +class ChangeAggregateType(StrEnum): COUNT = "count" SUM = "sum" MEAN = "mean" diff --git a/src/policyengine/outputs/poverty.py b/src/policyengine/outputs/poverty.py index 9b5074f4..4955b814 100644 --- a/src/policyengine/outputs/poverty.py +++ b/src/policyengine/outputs/poverty.py @@ -1,6 +1,6 @@ """Poverty analysis output types.""" -from enum import Enum +from enum import StrEnum from typing import Any import pandas as pd @@ -9,7 +9,7 @@ from policyengine.core import Output, OutputCollection, Simulation -class UKPovertyType(str, Enum): +class UKPovertyType(StrEnum): """UK poverty measure types.""" ABSOLUTE_BHC = "absolute_bhc" @@ -18,7 +18,7 @@ class UKPovertyType(str, Enum): RELATIVE_AHC = "relative_ahc" -class USPovertyType(str, Enum): +class USPovertyType(StrEnum): """US poverty measure types.""" SPM = "spm" From 84820ab16f91b17d1a010065888f0cbe8be05023 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 24 Feb 2026 06:24:15 -0500 Subject: [PATCH 3/5] Remove uv sync step that fails on Python 3.14 The uv sync step was redundant with uv pip install --system and caused failures because it tried to build pydantic-core from source for 3.14 (PyO3 0.24.1 doesn't support 3.14 yet, but pre-built wheels do exist). Co-Authored-By: Claude Opus 4.6 --- .github/workflows/code_changes.yaml | 2 -- .github/workflows/pr_code_changes.yaml | 2 -- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/code_changes.yaml b/.github/workflows/code_changes.yaml index 78d8499a..af57c408 100644 --- a/.github/workflows/code_changes.yaml +++ b/.github/workflows/code_changes.yaml @@ -47,8 +47,6 @@ jobs: run: uv pip install -e .[dev] --system - name: Install JB run: uv pip install "jupyter-book>=2.0.0a0" --system - - name: UV sync - run: uv sync - name: Run tests with coverage run: make test env: diff --git a/.github/workflows/pr_code_changes.yaml b/.github/workflows/pr_code_changes.yaml index 08c64e65..ab731a8b 100644 --- a/.github/workflows/pr_code_changes.yaml +++ b/.github/workflows/pr_code_changes.yaml @@ -44,8 +44,6 @@ jobs: run: uv pip install -e .[dev] --system - name: Install policyengine run: uv pip install policyengine --system - - name: UV sync - run: uv sync - name: Run tests with coverage run: make test env: From f7007bc8c5459ce245a466b780f6e0063779bf18 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 24 Feb 2026 06:28:35 -0500 Subject: [PATCH 4/5] Fix date parsing for Python 3.14 changed error message Python 3.14 changed the ValueError message for invalid days from "day is out of range for month" to "day N must be in range 1..M for month ...". Updated the check to handle both formats. Co-Authored-By: Claude Opus 4.6 --- src/policyengine/utils/dates.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/policyengine/utils/dates.py b/src/policyengine/utils/dates.py index e3c65fab..3bced81b 100644 --- a/src/policyengine/utils/dates.py +++ b/src/policyengine/utils/dates.py @@ -22,7 +22,12 @@ def parse_safe_date(date_string: str) -> datetime: return date_obj except ValueError as e: # Try to handle invalid day values (e.g., 2021-06-31) - if "day is out of range for month" in str(e): + # Python <3.14: "day is out of range for month" + # Python 3.14+: "day N must be in range 1..M for month ..." + error_msg = str(e) + if "day is out of range for month" in error_msg or ( + "must be in range" in error_msg and "for month" in error_msg + ): parts = date_string.split("-") if len(parts) == 3: year = int(parts[0]) From 59fd759b46e8f328d6d4bd7f918552e499698dcd Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 24 Feb 2026 06:28:50 -0500 Subject: [PATCH 5/5] Add fail-fast: false to CI matrix Prevents 3.13 job from being cancelled if 3.14 fails (or vice versa). Co-Authored-By: Claude Opus 4.6 --- .github/workflows/code_changes.yaml | 1 + .github/workflows/pr_code_changes.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/code_changes.yaml b/.github/workflows/code_changes.yaml index af57c408..486efd99 100644 --- a/.github/workflows/code_changes.yaml +++ b/.github/workflows/code_changes.yaml @@ -29,6 +29,7 @@ jobs: contents: "read" id-token: "write" strategy: + fail-fast: false matrix: python-version: ['3.13', '3.14'] steps: diff --git a/.github/workflows/pr_code_changes.yaml b/.github/workflows/pr_code_changes.yaml index ab731a8b..cf37afb4 100644 --- a/.github/workflows/pr_code_changes.yaml +++ b/.github/workflows/pr_code_changes.yaml @@ -26,6 +26,7 @@ jobs: Test: runs-on: macos-latest strategy: + fail-fast: false matrix: python-version: ['3.13', '3.14'] steps: