diff --git a/.github/bump_version.py b/.github/bump_version.py new file mode 100644 index 0000000..bb0fd6d --- /dev/null +++ b/.github/bump_version.py @@ -0,0 +1,79 @@ +"""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 f435a8d..c9ad9c2 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@v4 - 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@v5 - with: - python-version: 3.12 - - 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 + - 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: @@ -48,6 +43,6 @@ jobs: run: make install - name: Run tests run: make test - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 - name: Build package run: make build diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 2a518e2..95d0717 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -24,15 +24,16 @@ jobs: - name: Checkout repo uses: actions/checkout@v4 with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} token: ${{ secrets.POLICYENGINE_GITHUB }} - name: Setup Python uses: actions/setup-python@v5 with: - python-version: 3.14 + python-version: "3.14" - name: Build changelog - run: pip install yaml-changelog && make 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 @@ -56,12 +57,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.14 + python-version: "3.14" - name: Install package run: make install - name: Run tests run: make test - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 Publish: runs-on: ubuntu-latest if: | @@ -73,7 +74,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: 3.14 + python-version: "3.14" - name: Publish a git tag run: ".github/publish-git-tag.sh || true" - name: Install package @@ -97,13 +98,11 @@ jobs: - name: Checkout repo uses: actions/checkout@v4 with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} token: ${{ secrets.POLICYENGINE_GITHUB }} - name: Setup Python uses: actions/setup-python@v5 with: - python-version: 3.14 + python-version: "3.14" - name: Install Wheel and Pytest run: pip3 install wheel setuptools pytest==5.4.3 - name: Install package diff --git a/Makefile b/Makefile index 7a7f389..16cdb18 100644 --- a/Makefile +++ b/Makefile @@ -13,11 +13,8 @@ test: policyengine-core test policyengine_ng/tests -c policyengine_ng build: - python setup.py sdist bdist_wheel + python -m build 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/changelog.yaml b/changelog.yaml deleted file mode 100644 index 8da735d..0000000 --- a/changelog.yaml +++ /dev/null @@ -1,57 +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 -- bump: minor - changes: - added: - - Python 3.14 support. Dropped Python 3.10 and below. - date: 2026-02-24 17:31:10 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..65165a0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,77 @@ +[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.optional-dependencies] +dev = [ + "build", + "jupyter-book>=1.0.4", + "mystmd>=1.3.17", + "towncrier>=24.8.0", +] + +[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 7cb6537..0000000 --- a/setup.py +++ /dev/null @@ -1,69 +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.6.0", - 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.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Topic :: Scientific/Engineering :: Information Analysis", - ], - python_requires=">=3.11", - 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-ng", - 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*"]), -)