diff --git a/.github/fetch_version.py b/.github/fetch_version.py index 7f394b76..05793d11 100644 --- a/.github/fetch_version.py +++ b/.github/fetch_version.py @@ -1,8 +1,8 @@ def fetch_version(): try: - import importlib + from importlib.metadata import version - return importlib.import_module("policyengine").__version__ + return version("policyengine") except Exception as e: print(f"Error fetching version: {e}") return None diff --git a/.github/workflows/code_changes.yaml b/.github/workflows/code_changes.yaml index c18b527c..cd64a7ef 100644 --- a/.github/workflows/code_changes.yaml +++ b/.github/workflows/code_changes.yaml @@ -26,6 +26,9 @@ jobs: - name: Run ruff check run: ruff check . + + - name: Run ruff format check + run: ruff format --check . Test: runs-on: macos-latest permissions: diff --git a/.github/workflows/pr_code_changes.yaml b/.github/workflows/pr_code_changes.yaml index 8ca3092b..a2cc1192 100644 --- a/.github/workflows/pr_code_changes.yaml +++ b/.github/workflows/pr_code_changes.yaml @@ -23,6 +23,20 @@ jobs: - name: Run ruff check run: ruff check . + Mypy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Install package + run: uv pip install -e .[dev] --system + - name: Run mypy (informational) + run: mypy src/policyengine || echo "::warning::mypy found errors (non-blocking until codebase is clean)" Test: runs-on: macos-latest strategy: diff --git a/.github/workflows/versioning.yaml b/.github/workflows/versioning.yaml index a30a0e47..dcb70429 100644 --- a/.github/workflows/versioning.yaml +++ b/.github/workflows/versioning.yaml @@ -7,7 +7,7 @@ on: - main paths: - - changelog_entry.yaml + - changelog.d/** - .github/** workflow_dispatch: @@ -27,7 +27,7 @@ jobs: with: python-version: 3.13 - name: Build changelog - run: pip install yaml-changelog && make changelog + run: pip install yaml-changelog towncrier && make changelog - name: Preview changelog update run: ".github/get-changelog-diff.sh" - name: Update changelog @@ -66,3 +66,10 @@ jobs: user: __token__ password: ${{ secrets.PYPI }} skip_existing: true + - name: Create GitHub Release + run: | + VERSION=$(python .github/fetch_version.py) + gh release create "$VERSION" \ + --title "v$VERSION" \ + --notes "See [CHANGELOG.md](https://github.com/PolicyEngine/policyengine.py/blob/main/CHANGELOG.md) for details." \ + --latest diff --git a/Makefile b/Makefile index b54164f4..f5028d39 100644 --- a/Makefile +++ b/Makefile @@ -27,4 +27,4 @@ build-package: python -m build test: - pytest tests \ No newline at end of file + pytest tests --cov=policyengine --cov-report=term-missing \ No newline at end of file diff --git a/README.md b/README.md index 8c89c37f..8d1cf2c2 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,94 @@ print(f"Total UC spending: £{agg.result / 1e9:.1f}bn") ## Installation +### As a library + ```bash pip install policyengine ``` +This installs both UK and US country models. To install only one: + +```bash +pip install policyengine[uk] # UK model only +pip install policyengine[us] # US model only +``` + +### For development + +```bash +git clone https://github.com/PolicyEngine/policyengine.py.git +cd policyengine.py +uv pip install -e .[dev] # install with dev dependencies (pytest, ruff, mypy, etc.) +``` + +## Development + +### Running configurations + +| Configuration | Install | Use case | +|---------------|---------|----------| +| **Library user** | `pip install policyengine` | Using the package in your own code | +| **UK only** | `pip install policyengine[uk]` | Only need UK simulations | +| **US only** | `pip install policyengine[us]` | Only need US simulations | +| **Developer** | `uv pip install -e .[dev]` | Contributing to the package | + +### Common commands + +```bash +make format # ruff format +make test # pytest with coverage +make docs # build Jupyter Book documentation +make clean # remove caches, build artifacts, .h5 files +``` + +### Testing + +Tests require a `HUGGING_FACE_TOKEN` environment variable for downloading datasets: + +```bash +export HUGGING_FACE_TOKEN=hf_... +make test +``` + +To run a specific test: + +```bash +pytest tests/test_models.py -v +pytest tests/test_parametric_reforms.py -k "test_uk" -v +``` + +### Linting and type checking + +```bash +ruff format . # format code +ruff check . # lint +mypy src/policyengine # type check (informational — not yet enforced in CI) +``` + +### CI pipeline + +PRs trigger the following checks: + +| Check | Status | Command | +|-------|--------|---------| +| Lint + format | Required | `ruff check .` + `ruff format --check .` | +| Tests (Python 3.13) | Required | `make test` | +| Tests (Python 3.14) | Required | `make test` | +| Mypy | Informational | `mypy src/policyengine` | +| Docs build | Required | Jupyter Book build | + +### Versioning and releases + +This project uses [towncrier](https://towncrier.readthedocs.io/) for changelog management. When making a PR, add a changelog fragment: + +```bash +# Fragment types: breaking, added, changed, fixed, removed +echo "Description of change" > changelog.d/my-change.added +``` + +On merge, the versioning workflow bumps the version, builds the changelog, and creates a GitHub Release. + ## Features - **Multi-country support**: UK and US tax-benefit systems diff --git a/pyproject.toml b/pyproject.toml index fadcc314..fc3dffe6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,10 @@ dev = [ "ruff>=0.9.0", "policyengine_core>=3.23.6", "policyengine-uk>=2.51.0", - "policyengine-us>=1.213.1", "towncrier>=24.8.0", - + "policyengine-us>=1.213.1", + "towncrier>=24.8.0", + "mypy>=1.11.0", + "pytest-cov>=5.0.0", ] [tool.setuptools] @@ -90,6 +92,12 @@ indent-style = "space" skip-magic-trailing-comma = false line-ending = "auto" +[tool.mypy] +python_version = "3.13" +warn_return_any = true +warn_unused_configs = true +ignore_missing_imports = true + [tool.towncrier] package = "policyengine" directory = "changelog.d" diff --git a/src/policyengine/tax_benefit_models/us/model.py b/src/policyengine/tax_benefit_models/us/model.py index f13cdb9b..d8c05704 100644 --- a/src/policyengine/tax_benefit_models/us/model.py +++ b/src/policyengine/tax_benefit_models/us/model.py @@ -27,7 +27,13 @@ if TYPE_CHECKING: from policyengine.core.simulation import Simulation -US_GROUP_ENTITIES = ["household", "tax_unit", "spm_unit", "family", "marital_unit"] +US_GROUP_ENTITIES = [ + "household", + "tax_unit", + "spm_unit", + "family", + "marital_unit", +] class PolicyEngineUS(TaxBenefitModel): diff --git a/src/policyengine/utils/parametric_reforms.py b/src/policyengine/utils/parametric_reforms.py index d0acc33d..71476afa 100644 --- a/src/policyengine/utils/parametric_reforms.py +++ b/src/policyengine/utils/parametric_reforms.py @@ -82,7 +82,9 @@ def modifier(simulation): return modifier -def build_reform_dict(policy_or_dynamic: Policy | Dynamic | None) -> dict | None: +def build_reform_dict( + policy_or_dynamic: Policy | Dynamic | None, +) -> dict | None: """Extract a reform dict from a Policy or Dynamic object. If the object has parameter_values, converts them to reform dict format.