Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions SWEET_python/__init__.py
Original file line number Diff line number Diff line change
@@ -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}
6 changes: 6 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
92 changes: 92 additions & 0 deletions tests/test_version_stamp.py
Original file line number Diff line number Diff line change
@@ -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 == []
Loading