From 4ce66589632bcccb6815e9b69536ec8026b8ed22 Mon Sep 17 00:00:00 2001 From: Christian Schmidbauer Date: Thu, 16 Jul 2026 11:27:11 +0200 Subject: [PATCH 1/3] build: derive version from git tags via hatch-vcs Use the hatch-vcs plugin so the package version is derived from the latest git tag (e.g. tag "v0.15.0" -> "0.15.0") instead of a version string maintained in the source tree. The runtime `__version__` is now resolved via importlib.metadata, using the importlib_metadata backport on Python 3.7 to match the pattern already used in configurator.py, and the obsolete morgan/__about__.py is removed. Co-Authored-By: Claude Opus 4.8 (1M context) --- morgan/__about__.py | 1 - morgan/__init__.py | 13 ++++++++++++- pyproject.toml | 5 +++-- 3 files changed, 15 insertions(+), 4 deletions(-) delete mode 100644 morgan/__about__.py diff --git a/morgan/__about__.py b/morgan/__about__.py deleted file mode 100644 index 224f1fb..0000000 --- a/morgan/__about__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.14.4" diff --git a/morgan/__init__.py b/morgan/__init__.py index b1a5144..d5c993b 100644 --- a/morgan/__init__.py +++ b/morgan/__init__.py @@ -7,6 +7,7 @@ import os import os.path import re +import sys import tarfile import traceback import urllib.error @@ -23,7 +24,6 @@ import packaging.version from morgan import configurator, metadata, server -from morgan.__about__ import __version__ from morgan.registry import GitLabRegistry, LocalRegistry, Registry from morgan.utils import ( Cache, @@ -33,6 +33,17 @@ touch_file, ) +if sys.version_info < (3, 8): + import importlib_metadata as _metadata +else: + from importlib import metadata as _metadata + +try: + __version__ = _metadata.version("morgan") +except _metadata.PackageNotFoundError: + # Running from a source tree that hasn't been installed. + __version__ = "0.0.0+unknown" + PYPI_ADDRESS = "https://pypi.org/simple/" PREFERRED_HASH_ALG = "sha256" diff --git a/pyproject.toml b/pyproject.toml index 65f0e4b..3831ae7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["hatchling>=1.10.0"] +requires = ["hatchling>=1.10.0", "hatch-vcs"] build-backend = "hatchling.build" [project] @@ -49,7 +49,8 @@ lint = [ ] [tool.hatch.version] -path = "morgan/__about__.py" +# Version is derived from the latest git tag, e.g. "v0.15.0" -> "0.15.0". +source = "vcs" [project.urls] "Homepage" = "https://github.com/ido50/morgan" From ed28068213f80dcaae282d539e79edf6ace7fcde Mon Sep 17 00:00:00 2001 From: Christian Schmidbauer Date: Thu, 16 Jul 2026 11:27:11 +0200 Subject: [PATCH 2/3] ci: publish to PyPI on tag push Add a release workflow that builds and publishes to PyPI on any `v*` tag push, using OIDC trusted publishing (no stored secrets). The build job checks out full history (fetch-depth: 0) so the tag is visible to hatch-vcs, and a separate publish job runs in a `pypi` environment with `id-token: write`. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/publish.yml | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..9a103f0 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,47 @@ +--- +name: Publish + +on: + push: + tags: + - "v*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + # hatch-vcs derives the version from the git tag, so the full + # history and tags must be available (default checkout is shallow). + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: "3.x" + - name: Build sdist and wheel + run: | + python -m pip install --upgrade pip build + python -m build + - name: Upload distributions + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: dist + path: dist/ + + publish: + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/morgan/ + permissions: + id-token: write # required for PyPI trusted publishing (OIDC) + steps: + - name: Download distributions + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: dist + path: dist/ + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 From b36afe4288ab8ef5f8713a1bd5e747977efe934c Mon Sep 17 00:00:00 2001 From: Christian Schmidbauer Date: Thu, 16 Jul 2026 15:01:31 +0200 Subject: [PATCH 3/3] ci: publish dev builds to TestPyPI and gate PyPI on it Publish to TestPyPI on every push to main (and on tags), and publish to PyPI only for tags. The PyPI job depends on the TestPyPI job, so a tagged release is only pushed to PyPI once the TestPyPI upload succeeds. Set setuptools-scm's local_scheme to "no-local-version" so dev builds from untagged commits produce upload-valid versions (e.g. "0.15.1.dev1"); (Test)PyPI rejects PEP 440 local version identifiers. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/publish.yml | 27 ++++++++++++++++++++++++++- pyproject.toml | 4 ++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9a103f0..bcd7629 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,6 +3,8 @@ name: Publish on: push: + branches: + - "main" tags: - "v*" @@ -29,9 +31,32 @@ jobs: name: dist path: dist/ - publish: + # Every push to main and every tag publishes to TestPyPI. + testpypi: needs: build runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/project/morgan/ + permissions: + id-token: write # required for PyPI trusted publishing (OIDC) + steps: + - name: Download distributions + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: dist + path: dist/ + - name: Publish to TestPyPI + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 + with: + repository-url: https://test.pypi.org/legacy/ + skip-existing: true # tolerate re-runs of an already-uploaded version + + # Tags additionally publish to PyPI, gated on TestPyPI succeeding. + pypi: + needs: [build, testpypi] + if: github.ref_type == 'tag' + runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/project/morgan/ diff --git a/pyproject.toml b/pyproject.toml index 3831ae7..bda425b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,10 @@ lint = [ # Version is derived from the latest git tag, e.g. "v0.15.0" -> "0.15.0". source = "vcs" +[tool.hatch.version.raw-options] +# Drop the local "+" segment; (Test)PyPI rejects local version labels. +local_scheme = "no-local-version" + [project.urls] "Homepage" = "https://github.com/ido50/morgan" "Repository" = "https://github.com/ido50/morgan"