Files
siftlode/backend/tests/test_python_engine_contract.py
T
peter cc9fc3b544 build: pin one Python version across the app, the images and the gate
0.93.2 shipped a week's worth of confusion about which Node this repo
runs on; the Python side had the identical shape and nobody had looked.
Prod (Dockerfile), the test image (Dockerfile.test) and CI (check.yml)
each spelled 3.12 as an independent literal, there was no
`.python-version`, no `requires-python`, and ruff had no target-version
so it judged our syntax against some other interpreter. The dev machine
had no 3.12 installed at all — only 3.13 and 3.14 — so every ad-hoc host
script ran two majors ahead of production.

`.python-version` is now the single source of truth, and the contract is
held by tests rather than by prose. It is split in two because no single
runner can see every link: the pytest suite runs inside the test image,
which mounts backend/ alone and cannot read the repo-root Dockerfile or
the workflow.

  test_python_engine_contract.py : running interpreter == pyrightconfig
                                   == ruff.toml
  pythonEngine.contract.test.ts  : .python-version == pyrightconfig ==
                                   ruff.toml == Dockerfile ==
                                   Dockerfile.test == check.yml

Together they close the loop, and the backend half carries the assertion
that pays — that the interpreter running the suite is the pinned one. A
green suite on another minor does not mean the shipped image is green;
it means they have drifted and nobody has noticed yet.
2026-09-20 23:02:11 +02:00

67 lines
3.3 KiB
Python

"""The Python major is a contract, not a coincidence — the backend half (E27 S3).
`0.93.2` was released to fix exactly this bug on the JavaScript side: four machines built or checked
the app and none of them agreed on which Node they ran, the server gate went red for 73 of its last
90 runs, and nothing told anyone. The Python side had the same shape and nobody had looked: prod
(`Dockerfile`), the test image (`Dockerfile.test`) and CI (`check.yml`) each spelled **3.12** as an
independent literal, there was no `.python-version`, no `requires-python`, and ruff had no
`target-version` — while the dev machine had no 3.12 installed at all, only 3.13 and 3.14.
**The contract is a chain, and it is split across two test files because no single runner can see
every link.** This suite runs inside the test image, which mounts `backend/` and nothing else, so it
cannot read the repo-root Dockerfile or the workflow. It therefore holds the link it CAN see, and the
one that matters most here:
the interpreter actually running == backend/pyrightconfig.json == backend/ruff.toml
and `frontend/src/lib/pythonEngine.contract.test.ts` — which runs on the host, with the whole tree —
holds the rest of the chain:
/.python-version == pyrightconfig.json == ruff.toml == Dockerfile == Dockerfile.test == check.yml
Together they close the loop. To move the version: edit `/.python-version`, then follow the failures
in both files. Every place they name is a place that would otherwise have been forgotten.
"""
import re
import sys
from pathlib import Path
BACKEND = Path(__file__).resolve().parents[1]
def _version_in(path: Path, pattern: str) -> tuple[int, int]:
"""The (major, minor) the given file declares. A rename or a moved line lands here — re-point
the test, the contract did not go away with it."""
text = path.read_text(encoding="utf-8")
match = re.search(pattern, text, re.MULTILINE)
assert match is not None, f"{pattern!r} not found in {path.name}"
return int(match.group(1)), int(match.group(2))
# pyrightconfig.json is JSONC (it carries comments), so it is read by regex rather than json.loads.
PINNED = _version_in(BACKEND / "pyrightconfig.json", r'"pythonVersion"\s*:\s*"(\d+)\.(\d+)"')
def test_pins_a_plausible_python_version():
"""Guards against a typo agreeing with nothing at all."""
assert PINNED >= (3, 12), PINNED
def test_this_interpreter_is_the_pinned_one():
"""The assertion that pays. A green suite on another minor does not mean the shipped image is
green — it means the suite and the image have drifted and nobody noticed yet."""
running = sys.version_info[:2]
assert running == PINNED, (
f"these tests are running on Python {running[0]}.{running[1]}, but the app is pinned to "
f"{PINNED[0]}.{PINNED[1]} (backend/pyrightconfig.json, /.python-version). The suite runs in "
f"the test image, so this failing means backend/Dockerfile.test has drifted from the pin."
)
def test_ruff_judges_the_pinned_version():
"""Unset, ruff falls back to a default that has nothing to do with us, and its syntax and
upgrade advice are then aimed at some other interpreter."""
major, minor = _version_in(BACKEND / "ruff.toml", r'^target-version\s*=\s*"py(\d)(\d+)"')
assert (major, minor) == PINNED