Skip to content

Commit 79e341c

Browse files
authored
Merge pull request #253 from PolicyEngine/feat/scaffolding
CI/CD: Add format check, mypy, coverage, and fix versioning
2 parents de5bc8b + bb3a838 commit 79e341c

9 files changed

Lines changed: 133 additions & 9 deletions

File tree

.github/fetch_version.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
def fetch_version():
22
try:
3-
import importlib
3+
from importlib.metadata import version
44

5-
return importlib.import_module("policyengine").__version__
5+
return version("policyengine")
66
except Exception as e:
77
print(f"Error fetching version: {e}")
88
return None

.github/workflows/code_changes.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ jobs:
2626

2727
- name: Run ruff check
2828
run: ruff check .
29+
30+
- name: Run ruff format check
31+
run: ruff format --check .
2932
Test:
3033
runs-on: macos-latest
3134
permissions:

.github/workflows/pr_code_changes.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ jobs:
2323

2424
- name: Run ruff check
2525
run: ruff check .
26+
Mypy:
27+
runs-on: ubuntu-latest
28+
steps:
29+
- uses: actions/checkout@v4
30+
- name: Install uv
31+
uses: astral-sh/setup-uv@v5
32+
- name: Set up Python
33+
uses: actions/setup-python@v5
34+
with:
35+
python-version: '3.13'
36+
- name: Install package
37+
run: uv pip install -e .[dev] --system
38+
- name: Run mypy (informational)
39+
run: mypy src/policyengine || echo "::warning::mypy found errors (non-blocking until codebase is clean)"
2640
Test:
2741
runs-on: macos-latest
2842
strategy:

.github/workflows/versioning.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
- main
88

99
paths:
10-
- changelog_entry.yaml
10+
- changelog.d/**
1111
- .github/**
1212
workflow_dispatch:
1313

@@ -27,7 +27,7 @@ jobs:
2727
with:
2828
python-version: 3.13
2929
- name: Build changelog
30-
run: pip install yaml-changelog && make changelog
30+
run: pip install yaml-changelog towncrier && make changelog
3131
- name: Preview changelog update
3232
run: ".github/get-changelog-diff.sh"
3333
- name: Update changelog
@@ -66,3 +66,10 @@ jobs:
6666
user: __token__
6767
password: ${{ secrets.PYPI }}
6868
skip_existing: true
69+
- name: Create GitHub Release
70+
run: |
71+
VERSION=$(python .github/fetch_version.py)
72+
gh release create "$VERSION" \
73+
--title "v$VERSION" \
74+
--notes "See [CHANGELOG.md](https://github.com/PolicyEngine/policyengine.py/blob/main/CHANGELOG.md) for details." \
75+
--latest

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ build-package:
2727
python -m build
2828

2929
test:
30-
pytest tests
30+
pytest tests --cov=policyengine --cov-report=term-missing

README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,94 @@ print(f"Total UC spending: £{agg.result / 1e9:.1f}bn")
4848

4949
## Installation
5050

51+
### As a library
52+
5153
```bash
5254
pip install policyengine
5355
```
5456

57+
This installs both UK and US country models. To install only one:
58+
59+
```bash
60+
pip install policyengine[uk] # UK model only
61+
pip install policyengine[us] # US model only
62+
```
63+
64+
### For development
65+
66+
```bash
67+
git clone https://github.com/PolicyEngine/policyengine.py.git
68+
cd policyengine.py
69+
uv pip install -e .[dev] # install with dev dependencies (pytest, ruff, mypy, etc.)
70+
```
71+
72+
## Development
73+
74+
### Running configurations
75+
76+
| Configuration | Install | Use case |
77+
|---------------|---------|----------|
78+
| **Library user** | `pip install policyengine` | Using the package in your own code |
79+
| **UK only** | `pip install policyengine[uk]` | Only need UK simulations |
80+
| **US only** | `pip install policyengine[us]` | Only need US simulations |
81+
| **Developer** | `uv pip install -e .[dev]` | Contributing to the package |
82+
83+
### Common commands
84+
85+
```bash
86+
make format # ruff format
87+
make test # pytest with coverage
88+
make docs # build Jupyter Book documentation
89+
make clean # remove caches, build artifacts, .h5 files
90+
```
91+
92+
### Testing
93+
94+
Tests require a `HUGGING_FACE_TOKEN` environment variable for downloading datasets:
95+
96+
```bash
97+
export HUGGING_FACE_TOKEN=hf_...
98+
make test
99+
```
100+
101+
To run a specific test:
102+
103+
```bash
104+
pytest tests/test_models.py -v
105+
pytest tests/test_parametric_reforms.py -k "test_uk" -v
106+
```
107+
108+
### Linting and type checking
109+
110+
```bash
111+
ruff format . # format code
112+
ruff check . # lint
113+
mypy src/policyengine # type check (informational — not yet enforced in CI)
114+
```
115+
116+
### CI pipeline
117+
118+
PRs trigger the following checks:
119+
120+
| Check | Status | Command |
121+
|-------|--------|---------|
122+
| Lint + format | Required | `ruff check .` + `ruff format --check .` |
123+
| Tests (Python 3.13) | Required | `make test` |
124+
| Tests (Python 3.14) | Required | `make test` |
125+
| Mypy | Informational | `mypy src/policyengine` |
126+
| Docs build | Required | Jupyter Book build |
127+
128+
### Versioning and releases
129+
130+
This project uses [towncrier](https://towncrier.readthedocs.io/) for changelog management. When making a PR, add a changelog fragment:
131+
132+
```bash
133+
# Fragment types: breaking, added, changed, fixed, removed
134+
echo "Description of change" > changelog.d/my-change.added
135+
```
136+
137+
On merge, the versioning workflow bumps the version, builds the changelog, and creates a GitHub Release.
138+
55139
## Features
56140

57141
- **Multi-country support**: UK and US tax-benefit systems

pyproject.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@ dev = [
4646
"ruff>=0.9.0",
4747
"policyengine_core>=3.23.6",
4848
"policyengine-uk>=2.51.0",
49-
"policyengine-us>=1.213.1", "towncrier>=24.8.0",
50-
49+
"policyengine-us>=1.213.1",
50+
"towncrier>=24.8.0",
51+
"mypy>=1.11.0",
52+
"pytest-cov>=5.0.0",
5153
]
5254

5355
[tool.setuptools]
@@ -90,6 +92,12 @@ indent-style = "space"
9092
skip-magic-trailing-comma = false
9193
line-ending = "auto"
9294

95+
[tool.mypy]
96+
python_version = "3.13"
97+
warn_return_any = true
98+
warn_unused_configs = true
99+
ignore_missing_imports = true
100+
93101
[tool.towncrier]
94102
package = "policyengine"
95103
directory = "changelog.d"

src/policyengine/tax_benefit_models/us/model.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@
2727
if TYPE_CHECKING:
2828
from policyengine.core.simulation import Simulation
2929

30-
US_GROUP_ENTITIES = ["household", "tax_unit", "spm_unit", "family", "marital_unit"]
30+
US_GROUP_ENTITIES = [
31+
"household",
32+
"tax_unit",
33+
"spm_unit",
34+
"family",
35+
"marital_unit",
36+
]
3137

3238

3339
class PolicyEngineUS(TaxBenefitModel):

src/policyengine/utils/parametric_reforms.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ def modifier(simulation):
8282
return modifier
8383

8484

85-
def build_reform_dict(policy_or_dynamic: Policy | Dynamic | None) -> dict | None:
85+
def build_reform_dict(
86+
policy_or_dynamic: Policy | Dynamic | None,
87+
) -> dict | None:
8688
"""Extract a reform dict from a Policy or Dynamic object.
8789
8890
If the object has parameter_values, converts them to reform dict format.

0 commit comments

Comments
 (0)