diff --git a/.github/workflows/code_changes.yaml b/.github/workflows/code_changes.yaml index 33fc437a..486efd99 100644 --- a/.github/workflows/code_changes.yaml +++ b/.github/workflows/code_changes.yaml @@ -28,6 +28,10 @@ jobs: permissions: contents: "read" id-token: "write" + strategy: + fail-fast: false + matrix: + python-version: ['3.13', '3.14'] steps: - name: Checkout repo uses: actions/checkout@v4 @@ -37,14 +41,13 @@ 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 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 aa03bc02..cf37afb4 100644 --- a/.github/workflows/pr_code_changes.yaml +++ b/.github/workflows/pr_code_changes.yaml @@ -25,6 +25,10 @@ jobs: run: ruff check . Test: runs-on: macos-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.13', '3.14'] steps: - name: Checkout repo uses: actions/checkout@v4 @@ -34,14 +38,13 @@ 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 run: uv pip install policyengine --system - - name: UV sync - run: uv sync - name: Run tests with coverage run: make test env: 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..07434b44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,10 @@ authors = [ ] license = {file = "LICENSE"} requires-python = ">=3.13" +classifiers = [ + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] dependencies = [ "pydantic>=2.0.0", "pandas>=2.0.0", 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" 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])