From 2cfc854e05f733c00eda7280441757bd53a2ea9c Mon Sep 17 00:00:00 2001 From: Remis Lape Date: Fri, 11 Sep 2026 11:16:02 +0100 Subject: [PATCH] Derive __version__ from the metadata, and gate it dcio 0.1.1 was published with pyproject.toml saying 0.1.1 and dcio/__init__.py still saying 0.1.0, so `pip install "dcio>=0.1.1"` succeeds and `dcio.__version__` then reports 0.1.0. My error, in the release that lowered the Python floor: I bumped one of the two places. Rather than bump the other, __version__ now comes from the installed package metadata, so there is one place for it to be wrong and pyproject.toml is that place. A source tree that was never installed reports 0.0.0+unknown rather than lying. SCALCS has a versions-agree gate for exactly this and dcio had none. It gets two: the tag must match the version it claims to publish, which SCALCS and HJCFIT both check and dcio did not; and the built wheel, installed into a clean environment, must report the version pyproject declares. The second is the one that would have caught this, because it asserts against the artifact rather than against the source tree. 0.1.1 cannot be replaced, so this is 0.1.2. Co-Authored-By: Claude Opus 5 --- .github/workflows/release.yml | 33 +++++++++++++++++++++++++++++++++ dcio/__init__.py | 14 +++++++++++++- pyproject.toml | 2 +- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e1f107..c6efb89 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,11 +38,44 @@ jobs: with: python-version: '3.12' + # A tag that disagrees with the version it claims to publish names the + # release one thing on GitHub and another on PyPI, and the number cannot + # be taken back. SCALCS and HJCFIT both have this gate; dcio did not, and + # 0.1.1 went out with dcio.__version__ still reading 0.1.0 -- a different + # drift, now fixed at the source by deriving it from the metadata. + - name: Tag matches the version + if: startsWith(github.ref, 'refs/tags/v') + run: | + python - <<'PY' + import os, pathlib, re + proj = re.search(r'^version = "([^"]+)"', + pathlib.Path('pyproject.toml').read_text(), re.M).group(1) + tag = os.environ['GITHUB_REF'].removeprefix('refs/tags/v') + if proj != tag: + raise SystemExit(f'tag v{tag} does not match version {proj}') + print('tag and version agree:', proj) + PY + - name: Build run: | python -m pip install --upgrade pip build twine python -m build + # The one thing a hardcoded __version__ could get wrong, asserted against + # the built artifact rather than the source tree. + - name: The wheel reports its own version + run: | + python -m venv /tmp/vcheck + /tmp/vcheck/bin/pip install --quiet dist/*.whl + /tmp/vcheck/bin/python - <<'PY' + import re, pathlib, dcio + proj = re.search(r'^version = "([^"]+)"', + pathlib.Path('pyproject.toml').read_text(), re.M).group(1) + if dcio.__version__ != proj: + raise SystemExit(f'wheel reports {dcio.__version__}, pyproject says {proj}') + print('installed wheel reports', dcio.__version__) + PY + # twine check catches the metadata problems that make a PyPI page render # badly or an upload fail outright -- a README that is not valid # reStructuredText or Markdown, a missing long_description content type. diff --git a/dcio/__init__.py b/dcio/__init__.py index a1fb59b..6f59958 100644 --- a/dcio/__init__.py +++ b/dcio/__init__.py @@ -14,5 +14,17 @@ >>> print(rec) """ -__version__ = "0.1.0" +# Taken from the installed package metadata rather than written here, so it +# cannot drift from pyproject.toml. It already had: 0.1.1 was published with +# pyproject saying 0.1.1 and this saying 0.1.0, so `pip install "dcio>=0.1.1"` +# succeeded and `dcio.__version__` then reported 0.1.0. +try: + from importlib.metadata import PackageNotFoundError, version as _version + try: + __version__ = _version("dcio") + except PackageNotFoundError: # a source tree that was never installed + __version__ = "0.0.0+unknown" + del _version, PackageNotFoundError +except ImportError: # pragma: no cover + __version__ = "0.0.0+unknown" __author__ = "Remis Lape" diff --git a/pyproject.toml b/pyproject.toml index 6d5374c..bb075ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "dcio" -version = "0.1.1" +version = "0.1.2" description = "I/O and record layer for electrophysiology file formats used by the DCPROGS suite" readme = "README.md" license = "MIT"