diff --git a/chained_eclipse/__init__.py b/chained_eclipse/__init__.py index 9684cec..5944444 100644 --- a/chained_eclipse/__init__.py +++ b/chained_eclipse/__init__.py @@ -1,6 +1,24 @@ -"""Chained solar-eclipse search package.""" +"""Chained solar-eclipse search package. + +Two distinct version identifiers live here on purpose: + +- ``__version__`` is the *package* version, derived from the installed + distribution metadata (single-sourced from ``pyproject.toml``). It moves + with releases. +- ``MODEL_VERSION`` is the *model* version, defined in ``constants``. It is + bumped when computed outputs change for fixed inputs (see the CHANGELOG + policy) and is therefore allowed to differ from ``__version__``. +""" + +from __future__ import annotations + +from importlib.metadata import PackageNotFoundError, version from .constants import MODEL_VERSION -__all__ = ["MODEL_VERSION"] +try: + __version__ = version("chained-eclipse") +except PackageNotFoundError: # source tree without an installed distribution + __version__ = "0+unknown" +__all__ = ["MODEL_VERSION", "__version__"] diff --git a/tests/test_versions.py b/tests/test_versions.py new file mode 100644 index 0000000..613accd --- /dev/null +++ b/tests/test_versions.py @@ -0,0 +1,46 @@ +"""Version single-sourcing checks. + +``pyproject.toml`` is the only place the package version is written by hand. +``__version__`` must mirror the installed distribution, and ``CITATION.cff`` +must be bumped in lockstep with releases. ``MODEL_VERSION`` is deliberately +independent (it tracks output-changing model revisions, not packaging), so it +is only checked for shape, never for equality with the package version. +""" + +from __future__ import annotations + +from importlib.metadata import PackageNotFoundError, version +from pathlib import Path + +import pytest +import yaml + +import chained_eclipse + +CITATION_FILE = Path(__file__).resolve().parents[1] / "CITATION.cff" + + +def _installed_version() -> str: + try: + return version("chained-eclipse") + except PackageNotFoundError: # pragma: no cover - CI always installs + pytest.skip("chained-eclipse is not installed") + + +def test_dunder_version_matches_installed_distribution() -> None: + assert chained_eclipse.__version__ == _installed_version() + + +def test_citation_cff_version_matches_installed_distribution() -> None: + citation = yaml.safe_load(CITATION_FILE.read_text(encoding="utf-8")) + assert str(citation["version"]) == _installed_version(), ( + "CITATION.cff `version:` must be bumped in the same commit that changes " + "the package version in pyproject.toml" + ) + + +def test_model_version_is_present_and_separate() -> None: + assert isinstance(chained_eclipse.MODEL_VERSION, str) + assert chained_eclipse.MODEL_VERSION + # No equality assertion with __version__: the model version is allowed to + # differ from the package version by design.