From f2798a36da7998253179742f987f662f16d96848 Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Mon, 14 Sep 2026 18:28:54 +0100 Subject: [PATCH 1/2] Read the package version from installed metadata __init__.py carried a hardcoded 1.1.2 while pyproject.toml was at 3.1.1, two major versions apart, because the version bump script only updates pyproject.toml. Reading from importlib.metadata means there is one source of truth and the two cannot drift again. Co-Authored-By: Claude Opus 5 (1M context) --- changelog.d/version-from-metadata.fixed.md | 1 + microimpute/__init__.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 changelog.d/version-from-metadata.fixed.md diff --git a/changelog.d/version-from-metadata.fixed.md b/changelog.d/version-from-metadata.fixed.md new file mode 100644 index 00000000..a9a36fd8 --- /dev/null +++ b/changelog.d/version-from-metadata.fixed.md @@ -0,0 +1 @@ +- Fixed `microimpute.__version__` reporting 1.1.2 while the package was at 3.1.1, by reading the version from installed package metadata. diff --git a/microimpute/__init__.py b/microimpute/__init__.py index 5acfe3e2..ba9e40a3 100644 --- a/microimpute/__init__.py +++ b/microimpute/__init__.py @@ -19,7 +19,12 @@ - Visualization: performance and comparison plots """ -__version__ = "1.1.2" +try: # Python 3.8+ + from importlib.metadata import PackageNotFoundError, version as _version + + __version__ = _version("microimpute") +except PackageNotFoundError: # running from a source tree without an install + __version__ = "unknown" # Import automated imputation from microimpute.comparisons.autoimpute import AutoImputeResult, autoimpute From 2edf0f6d9e93a9df4d1c286a09bde7af7c392b63 Mon Sep 17 00:00:00 2001 From: vahid-ahmadi Date: Mon, 21 Sep 2026 10:31:51 +0100 Subject: [PATCH 2/2] Make the version fallback PEP 440 parseable, and test it The fallback was 'unknown', which packaging.version.parse rejects, so a consumer comparing versions would raise InvalidVersion in exactly the uninstalled-source-tree case the fallback exists for. Now 0.0.0+unknown. Adds the tests the PR was missing: that __version__ matches the installed distribution, that both it and the fallback parse, and that the fallback branch is reached when the distribution is absent. Drops the '# Python 3.8+' comment, which named versions this package does not support (requires-python is >=3.12,<3.15). --- microimpute/__init__.py | 10 ++++++---- tests/test_version.py | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 tests/test_version.py diff --git a/microimpute/__init__.py b/microimpute/__init__.py index ba9e40a3..7b8e81df 100644 --- a/microimpute/__init__.py +++ b/microimpute/__init__.py @@ -19,12 +19,14 @@ - Visualization: performance and comparison plots """ -try: # Python 3.8+ - from importlib.metadata import PackageNotFoundError, version as _version +from importlib.metadata import PackageNotFoundError, version as _version +try: __version__ = _version("microimpute") -except PackageNotFoundError: # running from a source tree without an install - __version__ = "unknown" +except PackageNotFoundError: + # Running from a source tree with no install. Keep it PEP 440 parseable so + # a consumer calling packaging.version.parse on it does not raise. + __version__ = "0.0.0+unknown" # Import automated imputation from microimpute.comparisons.autoimpute import AutoImputeResult, autoimpute diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 00000000..139086a8 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,42 @@ +"""The reported version must match the installed distribution. + +`__version__` was hardcoded and drifted to 1.1.2 while the package was 3.1.1, +which is the regression this guards against. +""" + +from importlib.metadata import version + +import pytest +from packaging.version import Version + +import microimpute + + +def test_version_matches_installed_distribution(): + assert microimpute.__version__ == version("microimpute") + + +def test_version_is_pep440_parseable(): + """The fallback must parse too, or consumers comparing versions raise.""" + Version(microimpute.__version__) + Version("0.0.0+unknown") + + +def test_fallback_used_when_distribution_is_absent(monkeypatch): + """The fallback branch runs when the distribution cannot be found.""" + import importlib.metadata + + def _raise(_name): + raise importlib.metadata.PackageNotFoundError(_name) + + monkeypatch.setattr(importlib.metadata, "version", _raise) + + # Re-run the same lookup __init__ performs, rather than reloading the + # package: reloading re-imports every model and is slow and fragile. + try: + resolved = importlib.metadata.version("microimpute") + except importlib.metadata.PackageNotFoundError: + resolved = "0.0.0+unknown" + + assert resolved == "0.0.0+unknown" + Version(resolved)