From b6cd48e0702e1a8dd781521ab2df2b0c538ca734 Mon Sep 17 00:00:00 2001 From: Hugh Runyan Date: Mon, 28 Sep 2026 15:53:18 -0700 Subject: [PATCH] SWEET_python reports which commit it was installed from The DST endpoints call this package when a request comes in, so whatever model code is in the deployed API image is live immediately -- there is no pipeline run and no manifest to look it up in. Nothing logged which commit that was. What we did record could also be wrong. WasteMAP's Dockerfiles install this with `pip install ...@$REF || pip install ...@main`, so an image built from a ref this repo does not have silently gets main, while the release manifest goes on recording the ref. __version__ is now the commit this copy was installed from: SWEET_GIT_SHA if the image build passed it, else a .git read beside the package, else the string "unknown" with VERSION_SOURCE saying which. It invents nothing. A wrong SHA in a log is worse than no SHA, because a reader believes it. This is not a version number and does not add one. SWEET_python never ships on its own -- it reaches production only inside a Climate TRACE run or a WasteMAP deploy, and both are already identified. A number maintained by hand here would be a second, weaker name for a commit already recorded automatically in every run's manifest. setup.py's version="0.1" is unchanged, now with a comment saying it is decoration. The .git check runs before any subprocess, so importing this in a container costs nothing: the image holds source, not a repository, and every multiprocessing worker imports it. Pairs with the WasteMAP branch of the same name, which passes SWEET_GIT_SHA into both images and logs it at API startup. Co-Authored-By: Claude Opus 5 --- SWEET_python/__init__.py | 81 ++++++++++++++++++++++++++++++++ setup.py | 6 +++ tests/test_version_stamp.py | 92 +++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 tests/test_version_stamp.py diff --git a/SWEET_python/__init__.py b/SWEET_python/__init__.py index e69de29..c5b5cf6 100644 --- a/SWEET_python/__init__.py +++ b/SWEET_python/__init__.py @@ -0,0 +1,81 @@ +"""SWEET_python: the waste model shared by the Climate TRACE pipeline and WasteMAP. + +WHAT ``__version__`` IS, AND WHY IT IS A SHA +-------------------------------------------- +It is the commit this copy was installed from -- not a hand-maintained number. + +SWEET never ships on its own. It reaches the world only inside a Climate TRACE run or a +WasteMAP deploy, and both of those already have identities of their own (a ``run_id`` and +a ``wastemap/YYYY.MM.DD`` tag). A ``MAJOR.MINOR`` maintained by hand here would be a +second, weaker name for a commit that is already recorded by sha in every run manifest, +and it would need bumping on every model change by the same person who has to remember +the tag. So there is no SWEET version number. There is a build stamp. + +WHAT IT IS FOR. The live DST endpoints (``/sdst``, ``/adst``, ``/cdst``) call this +package at request time, which makes them the one place in the system where SWEET's +behaviour is user-visible with no run and no manifest behind it. Until now nothing could +answer "which model is the live site running" -- the API knew the *ref* it asked for, and +the ref could be a lie, because the images fell back to ``main`` when it did not resolve. +A process that imports this can now log what it actually got. + +HOW IT RESOLVES, in order, inventing nothing: + +1. ``SWEET_GIT_SHA``, which the image build sets from the ref it actually installed. +2. A ``.git`` directory beside the package, for an editable checkout. +3. ``"unknown"`` -- an honest answer, and the one a reader must be able to distinguish + from a real sha. ``VERSION_SOURCE`` says which of the three it was. + +Same shape, deliberately, as the Climate TRACE pipeline's +``results_tables.resolve_git_shas``: env first, then a repository read, then a recorded +absence. + +See ``VERSIONING.md`` in RMI_Climate_TRACE_Waste_Methane for the whole scheme. +""" + +from __future__ import annotations + +import os +import subprocess +from pathlib import Path +from typing import Optional + +__all__ = ["__version__", "VERSION_SOURCE", "version_info"] + + +def _sha_from_git(repo_dir: Path) -> Optional[str]: + if not (repo_dir / ".git").exists(): + return None + try: + out = subprocess.run( + ["git", "-C", str(repo_dir), "rev-parse", "HEAD"], + capture_output=True, + text=True, + timeout=10, + check=False, + ) + except Exception: + return None + return (out.stdout or "").strip() or None + + +def _resolve_version() -> "tuple[str, str]": + sha = (os.environ.get("SWEET_GIT_SHA") or "").strip() + if sha: + return sha, "env" + sha = _sha_from_git(Path(__file__).resolve().parents[1]) + if sha: + return sha, "git" + return "unknown", "unavailable" + + +__version__, VERSION_SOURCE = _resolve_version() + + +def version_info() -> dict: + """``{"sweet_version": ..., "sweet_version_source": ...}``, for a startup log line. + + A dict rather than a string so a caller logging it cannot drop the source: a bare + ``"unknown"`` in a log reads as a missing feature, while ``unknown (unavailable)`` + reads as what it is -- an install that cannot say what it is. + """ + return {"sweet_version": __version__, "sweet_version_source": VERSION_SOURCE} diff --git a/setup.py b/setup.py index df0619b..45eacaa 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,12 @@ setup( name="SWEET_python", + # Decoration, not a version. SWEET never ships on its own -- it reaches the world + # only inside a Climate TRACE run or a WasteMAP deploy, each of which has an + # identity already -- so there is no hand-maintained number here to keep honest. + # What a caller should read is SWEET_python.__version__, which is the commit this + # copy was installed from. See the package docstring, and VERSIONING.md in + # RMI_Climate_TRACE_Waste_Methane. version="0.1", packages=find_packages(), include_package_data=True, diff --git a/tests/test_version_stamp.py b/tests/test_version_stamp.py new file mode 100644 index 0000000..f6f4770 --- /dev/null +++ b/tests/test_version_stamp.py @@ -0,0 +1,92 @@ +"""``SWEET_python.__version__`` is the commit this copy came from, or it says it cannot tell. + +There is no hand-maintained SWEET version; see the package docstring for why. What there +is has one job: let a process that imported this package say which model it is running. +The live DST endpoints are the reason -- they call SWEET at request time, so they are the +one place where its behaviour ships with no run manifest behind it. + +The rule these tests exist to hold: it never invents a value. A wrong sha in a log is +worse than none, because a reader believes it. +""" + +from __future__ import annotations + +import importlib +import subprocess + +import pytest + +import SWEET_python + + +def _reimport(monkeypatch, **env): + for key, value in env.items(): + if value is None: + monkeypatch.delenv(key, raising=False) + else: + monkeypatch.setenv(key, value) + return importlib.reload(SWEET_python) + + +def test_an_env_sha_wins(monkeypatch): + """The image build knows the ref it actually installed and passes it in. That beats + reading a repository, because the image holds source, not a repository.""" + mod = _reimport(monkeypatch, SWEET_GIT_SHA="a" * 40) + + assert mod.__version__ == "a" * 40 + assert mod.VERSION_SOURCE == "env" + + +def test_a_blank_env_sha_does_not_count(monkeypatch): + """An unset variable arrives as an empty string often enough that it has to be + handled: a deploy that forgot to pass one must fall through, not stamp ''.""" + mod = _reimport(monkeypatch, SWEET_GIT_SHA=" ") + + assert mod.__version__ != "" + assert mod.VERSION_SOURCE in {"git", "unavailable"} + + +def test_an_editable_checkout_reads_its_own_git(monkeypatch): + mod = _reimport(monkeypatch, SWEET_GIT_SHA=None) + + if mod.VERSION_SOURCE == "unavailable": + pytest.skip("no .git beside the package; nothing to compare against") + + head = subprocess.run( + ["git", "rev-parse", "HEAD"], + capture_output=True, + text=True, + check=True, + ).stdout.strip() + assert mod.__version__ == head + assert mod.VERSION_SOURCE == "git" + + +def test_an_unreadable_install_says_unknown_rather_than_guessing(monkeypatch): + monkeypatch.setattr(SWEET_python, "_sha_from_git", lambda _: None) + monkeypatch.delenv("SWEET_GIT_SHA", raising=False) + + version, source = SWEET_python._resolve_version() + + assert version == "unknown" + assert source == "unavailable" + + +def test_version_info_carries_the_source(monkeypatch): + """A bare "unknown" in a log reads as a missing feature. "unknown (unavailable)" + reads as an install that cannot say what it is, which is the true statement.""" + info = SWEET_python.version_info() + + assert set(info) == {"sweet_version", "sweet_version_source"} + assert info["sweet_version"] == SWEET_python.__version__ + assert info["sweet_version_source"] == SWEET_python.VERSION_SOURCE + + +def test_importing_the_package_costs_no_subprocess_without_a_git_dir(monkeypatch): + """The `.git` existence check comes first on purpose. In the container there is no + repository, and SWEET is imported by every multiprocessing worker.""" + calls = [] + monkeypatch.setattr(subprocess, "run", lambda *a, **k: calls.append(a)) + + assert SWEET_python._sha_from_git(pytest.importorskip("pathlib").Path("/nonexistent")) is None + assert calls == []