From 6715c29943f5cb41581ebe7fede06799fc638512 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 23 Feb 2026 12:55:26 -0500 Subject: [PATCH 1/8] Migrate to pyproject.toml and towncrier fragments Co-Authored-By: Claude Opus 4.6 --- .github/bump_version.py | 81 ++++++++++++++++++++ .github/workflows/pr.yaml | 29 +++---- .github/workflows/push.yaml | 11 +-- Makefile | 7 +- changelog_entry.yaml => changelog.d/.gitkeep | 0 changelog.d/migrate-to-towncrier.changed.md | 1 + pyproject.toml | 69 +++++++++++++++++ setup.py | 67 ---------------- 8 files changed, 167 insertions(+), 98 deletions(-) create mode 100644 .github/bump_version.py rename changelog_entry.yaml => changelog.d/.gitkeep (100%) create mode 100644 changelog.d/migrate-to-towncrier.changed.md create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/.github/bump_version.py b/.github/bump_version.py new file mode 100644 index 0000000..19aa790 --- /dev/null +++ b/.github/bump_version.py @@ -0,0 +1,81 @@ +"""Infer semver bump from towncrier fragment types and update version.""" + +import re +import sys +from pathlib import Path + + +def get_current_version(pyproject_path: Path) -> str: + text = pyproject_path.read_text() + match = re.search( + r'^version\s*=\s*"(\d+\.\d+\.\d+)"', text, re.MULTILINE + ) + if not match: + print( + "Could not find version in pyproject.toml", + file=sys.stderr, + ) + sys.exit(1) + return match.group(1) + + +def infer_bump(changelog_dir: Path) -> str: + fragments = [ + f + for f in changelog_dir.iterdir() + if f.is_file() and f.name != ".gitkeep" + ] + if not fragments: + print("No changelog fragments found", file=sys.stderr) + sys.exit(1) + + categories = {f.suffix.lstrip(".") for f in fragments} + for f in fragments: + parts = f.stem.split(".") + if len(parts) >= 2: + categories.add(parts[-1]) + + if "breaking" in categories: + return "major" + if "added" in categories or "removed" in categories: + return "minor" + return "patch" + + +def bump_version(version: str, bump: str) -> str: + major, minor, patch = (int(x) for x in version.split(".")) + if bump == "major": + return f"{major + 1}.0.0" + elif bump == "minor": + return f"{major}.{minor + 1}.0" + else: + return f"{major}.{minor}.{patch + 1}" + + +def update_file(path: Path, old_version: str, new_version: str): + text = path.read_text() + updated = text.replace( + f'version = "{old_version}"', + f'version = "{new_version}"', + ) + if updated != text: + path.write_text(updated) + print(f" Updated {path}") + + +def main(): + root = Path(__file__).resolve().parent.parent + pyproject = root / "pyproject.toml" + changelog_dir = root / "changelog.d" + + current = get_current_version(pyproject) + bump = infer_bump(changelog_dir) + new = bump_version(current, bump) + + print(f"Version: {current} -> {new} ({bump})") + + update_file(pyproject, current, new) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 26953de..cb4ddff 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -11,25 +11,20 @@ jobs: uses: "lgeiger/black-action@master" with: args: ". -l 79 --check" - check-version: - name: Check version + check-changelog: + name: Check changelog fragment runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: 3.9 - - name: Build changelog - run: pip install yaml-changelog>=0.1.7 && make changelog - - name: Preview changelog update - run: ".github/get-changelog-diff.sh" - - name: Check version number has been properly updated - run: .github/is-version-number-acceptable.sh + - uses: actions/checkout@v4 + - name: Check for changelog fragment + run: | + FRAGMENTS=$(find changelog.d -type f ! -name '.gitkeep' | wc -l) + if [ "$FRAGMENTS" -eq 0 ]; then + echo "::error::No changelog fragment found in changelog.d/" + echo "Add one with: echo 'Description.' > changelog.d/\$(git branch --show-current)..md" + echo "Types: added, changed, fixed, removed, breaking" + exit 1 + fi Test: runs-on: ${{ matrix.os }} strategy: diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 0a2b460..69e0284 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -23,16 +23,12 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v3 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - token: ${{ secrets.POLICYENGINE_GITHUB }} + with: token: ${{ secrets.POLICYENGINE_GITHUB }} - name: Setup Python uses: actions/setup-python@v4 with: python-version: 3.9 - name: Build changelog - run: pip install yaml-changelog && make changelog - name: Preview changelog update run: ".github/get-changelog-diff.sh" - name: Update changelog @@ -105,10 +101,7 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v2 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - token: ${{ secrets.POLICYENGINE_GITHUB }} + with: token: ${{ secrets.POLICYENGINE_GITHUB }} - name: Setup Python uses: actions/setup-python@v2 with: diff --git a/Makefile b/Makefile index 7ce7e9a..cdaeb5d 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,5 @@ build: python setup.py sdist bdist_wheel changelog: - build-changelog changelog.yaml --output changelog.yaml --update-last-date --start-from 0.0.1 --append-file changelog_entry.yaml - build-changelog changelog.yaml --org PolicyEngine --repo policyengine-ng --output CHANGELOG.md --template .github/changelog_template.md - bump-version changelog.yaml setup.py docs/requirements.txt - rm changelog_entry.yaml || true - touch changelog_entry.yaml \ No newline at end of file + python .github/bump_version.py + towncrier build --yes --version $$(python -c "import re; print(re.search(r'version = \"(.+?)\"', open('pyproject.toml').read()).group(1))") \ No newline at end of file diff --git a/changelog_entry.yaml b/changelog.d/.gitkeep similarity index 100% rename from changelog_entry.yaml rename to changelog.d/.gitkeep diff --git a/changelog.d/migrate-to-towncrier.changed.md b/changelog.d/migrate-to-towncrier.changed.md new file mode 100644 index 0000000..865484a --- /dev/null +++ b/changelog.d/migrate-to-towncrier.changed.md @@ -0,0 +1 @@ +Migrated from changelog_entry.yaml to towncrier fragments to eliminate merge conflicts. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e6382d5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,69 @@ +[project] +name = "policyengine-ng" +version = "0.5.1" +description = "Core microsimulation engine enabling country-specific policy models." +readme = "README.md" +authors = [ + { name = "PolicyEngine", email = "hello@policyengine.org" } +] +requires-python = ">=3.10" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: GNU Affero General Public License v3", + "Operating System :: POSIX", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Scientific/Engineering :: Information Analysis", +] +dependencies = [ + "numpy", + "policyengine-core", +] + +[project.scripts] +policyengine-core = "policyengine_core.scripts.policyengine_command:main" + +[project.urls] +Homepage = "https://github.com/policyengine/policyengine-core" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["policyengine_ng"] + +[tool.towncrier] +package = "policyengine_ng" +directory = "changelog.d" +filename = "CHANGELOG.md" +title_format = "## [{version}] - {project_date}" +issue_format = "" +underlines = ["", "", ""] + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking changes" +showcontent = true + +[[tool.towncrier.type]] +directory = "added" +name = "Added" +showcontent = true + +[[tool.towncrier.type]] +directory = "changed" +name = "Changed" +showcontent = true + +[[tool.towncrier.type]] +directory = "fixed" +name = "Fixed" +showcontent = true + +[[tool.towncrier.type]] +directory = "removed" +name = "Removed" +showcontent = true diff --git a/setup.py b/setup.py deleted file mode 100644 index f84bac2..0000000 --- a/setup.py +++ /dev/null @@ -1,67 +0,0 @@ -from pathlib import Path - -from setuptools import find_packages, setup - -# Read the contents of our README file for PyPi -this_directory = Path(__file__).parent -long_description = (this_directory / "README.md").read_text() - -# Please make sure to cap all dependency versions, in order to avoid unwanted -# functional and integration breaks caused by external code updates. - -general_requirements = [ - "policyengine-core", - "numpy", -] - -dev_requirements = [ - "black", - "coverage", - "jupyter-book", - "plotly", - "pytest", - "setuptools", - "wheel", - "yaml-changelog>=0.1.7", - "linecheck", - "furo<2023", - "markupsafe==2.0.1", - "sphinx>=4.5.0,<5", - "sphinx-argparse>=0.3.2,<1", - "sphinx-math-dollar>=1.2.1,<2", -] - -setup( - name="policyengine-ng", - version="0.5.1", - author="PolicyEngine", - author_email="hello@policyengine.org", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: GNU Affero General Public License v3", - "Operating System :: POSIX", - "Programming Language :: Python", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Topic :: Scientific/Engineering :: Information Analysis", - ], - description="Core microsimulation engine enabling country-specific policy models.", - keywords="tax benefit microsimulation framework", - license="https://www.fsf.org/licensing/licenses/agpl-3.0.html", - license_files=("LICENSE",), - url="https://github.com/policyengine/policyengine-core", - long_description=long_description, - long_description_content_type="text/markdown", - entry_points={ - "console_scripts": [ - "policyengine-core=policyengine_core.scripts.policyengine_command:main", - ], - }, - extras_require={ - "dev": dev_requirements, - }, - include_package_data=True, # Will read MANIFEST.in - install_requires=general_requirements, - packages=find_packages(exclude=["tests*"]), -) From b948955b327bccf9c3a17439376045eb964ce727 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 23 Feb 2026 13:00:10 -0500 Subject: [PATCH 2/8] Format bump_version.py with black Co-Authored-By: Claude Opus 4.6 --- .github/bump_version.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/bump_version.py b/.github/bump_version.py index 19aa790..bb0fd6d 100644 --- a/.github/bump_version.py +++ b/.github/bump_version.py @@ -7,9 +7,7 @@ def get_current_version(pyproject_path: Path) -> str: text = pyproject_path.read_text() - match = re.search( - r'^version\s*=\s*"(\d+\.\d+\.\d+)"', text, re.MULTILINE - ) + match = re.search(r'^version\s*=\s*"(\d+\.\d+\.\d+)"', text, re.MULTILINE) if not match: print( "Could not find version in pyproject.toml", From b719db7e61e4a3de1bba39937ef8109f5910d63d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 23 Feb 2026 13:11:34 -0500 Subject: [PATCH 3/8] Format entities.py with black Co-Authored-By: Claude Opus 4.6 --- policyengine_ng/entities.py | 1 + 1 file changed, 1 insertion(+) diff --git a/policyengine_ng/entities.py b/policyengine_ng/entities.py index 465bd2e..c45f507 100644 --- a/policyengine_ng/entities.py +++ b/policyengine_ng/entities.py @@ -5,6 +5,7 @@ See https://openfisca.org/doc/key-concepts/person,_entities,_role.html """ + from typing import Any from policyengine_core.entities import build_entity From b8cd27dde4313706b41578171f73a9df20737700 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 23 Feb 2026 13:44:47 -0500 Subject: [PATCH 4/8] Fix Makefile and dev dependencies after setup.py removal Co-Authored-By: Claude Opus 4.6 --- Makefile | 2 +- pyproject.toml | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index cdaeb5d..7739551 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ test: policyengine-core test policyengine_ng/tests -c policyengine_ng build: - python setup.py sdist bdist_wheel + python -m build changelog: python .github/bump_version.py diff --git a/pyproject.toml b/pyproject.toml index e6382d5..7ceeda4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,23 @@ dependencies = [ "policyengine-core", ] +[project.optional-dependencies] +dev = [ + "build", + "furo==2022.9.29", + "jupyter-book==0.13.1", + "markupsafe==2.0.1", + "mypy==0.991", + "sphinx==4.5.0", + "sphinx-argparse==0.4.0", + "sphinx-math-dollar==1.2.1", + "towncrier>=24.8.0", + "types-PyYAML==6.0.12.2", + "types-requests==2.28.11.7", + "types-setuptools==65.6.0.2", + "types-urllib3==1.26.25.4", +] + [project.scripts] policyengine-core = "policyengine_core.scripts.policyengine_command:main" From 02cb206577d78546ddb2b15477ae2c31fbb2dcff Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 23 Feb 2026 13:57:04 -0500 Subject: [PATCH 5/8] Update CI Python version to match package requirements pyproject.toml requires >=3.10 but CI was using 3.9, causing installation failures. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/pr.yaml | 2 +- .github/workflows/push.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index cb4ddff..a563c79 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -36,7 +36,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: "3.10" - name: Install package run: make install - name: Run tests diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 69e0284..9f101f0 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -27,7 +27,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: "3.10" - name: Build changelog - name: Preview changelog update run: ".github/get-changelog-diff.sh" @@ -52,7 +52,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: "3.10" - name: Install package run: make install - name: Run tests @@ -78,7 +78,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: "3.10" - name: Publish a git tag run: ".github/publish-git-tag.sh || true" - name: Install package @@ -105,7 +105,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: "3.10" - name: Install Wheel and Pytest run: pip3 install wheel setuptools pytest==5.4.3 - name: Install package From b5d10a6cfd943f6a17094bb018aefd6e2a9c5341 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 23 Feb 2026 14:13:22 -0500 Subject: [PATCH 6/8] Fix documentation build: use myst build instead of jb Replace `jb clean docs && jb build docs` with `myst build docs` to use Jupyter Book 2.0 (MyST-NB). Replace old pinned sphinx/furo/mypy deps with `jupyter-book>=1.0.4` and `mystmd>=1.3.17` to match other PE repos. Co-Authored-By: Claude Opus 4.6 --- Makefile | 3 +-- pyproject.toml | 13 ++----------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 7739551..16cdb18 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,7 @@ all: install format test build changelog documentation: - jb clean docs - jb build docs + myst build docs format: black . -l 79 diff --git a/pyproject.toml b/pyproject.toml index 7ceeda4..65165a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,18 +25,9 @@ dependencies = [ [project.optional-dependencies] dev = [ "build", - "furo==2022.9.29", - "jupyter-book==0.13.1", - "markupsafe==2.0.1", - "mypy==0.991", - "sphinx==4.5.0", - "sphinx-argparse==0.4.0", - "sphinx-math-dollar==1.2.1", + "jupyter-book>=1.0.4", + "mystmd>=1.3.17", "towncrier>=24.8.0", - "types-PyYAML==6.0.12.2", - "types-requests==2.28.11.7", - "types-setuptools==65.6.0.2", - "types-urllib3==1.26.25.4", ] [project.scripts] From 2942bd491cbfe9518ff7aec5455b15aa1c1ba7fb Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 24 Feb 2026 05:42:07 -0500 Subject: [PATCH 7/8] Delete old changelog files --- changelog.yaml | 52 -------------------------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 changelog.yaml diff --git a/changelog.yaml b/changelog.yaml deleted file mode 100644 index 9b018fb..0000000 --- a/changelog.yaml +++ /dev/null @@ -1,52 +0,0 @@ -- changes: - added: - - Initial Nigeria template. - date: 2023-02-09 00:00:00 - version: 0.0.1 -- bump: patch - changes: - fixed: - - Entity bugs. - date: 2023-02-10 01:54:05 -- bump: minor - changes: - added: - - Consolidated relief allowance. - date: 2023-02-11 12:49:53 -- bump: minor - changes: - added: - - Live site requirements. - date: 2023-02-11 13:23:04 -- bump: minor - changes: - added: - - Personal income tax logic. - date: 2023-02-11 13:43:36 -- bump: minor - changes: - added: - - Formula for marginal_tax_rate. - date: 2023-02-11 14:44:00 -- bump: patch - changes: - added: - - Tax integration test. - date: 2023-02-12 03:34:30 -- bump: patch - changes: - fixed: - - Data syntax updated for new Core version. - date: 2023-03-25 14:38:37 -- bump: minor - changes: - added: - - Parameter metadata for all parameters. - fixed: - - MTR calculation bugs. - date: 2023-04-19 13:01:35 -- bump: patch - changes: - fixed: - - API deployment action. - date: 2023-04-19 14:12:33 From aa70630c9b2d2e5276bfec07b8ff38e5e3aa7838 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 24 Feb 2026 06:06:00 -0500 Subject: [PATCH 8/8] Fix empty Build changelog step in push workflow Co-Authored-By: Claude Opus 4.6 --- .github/workflows/push.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 9f101f0..379cf20 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -29,6 +29,10 @@ jobs: with: python-version: "3.10" - name: Build changelog + run: | + pip install towncrier + python .github/bump_version.py + towncrier build --yes --version $(python -c "import re; print(re.search(r'version = \"(.+?)\"', open('pyproject.toml').read()).group(1))") - name: Preview changelog update run: ".github/get-changelog-diff.sh" - name: Update changelog