From 1e35cd5ba2d5142ce926d66adc3d8924aae7f1c4 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 28 Aug 2026 19:33:09 +0200 Subject: [PATCH] Never cache the Python target: it describes this process, not a registry Co-Authored-By: Claude Opus 5 (1M context) --- dep_freshness/resolve.py | 21 +++++++++++++++++++-- dep_freshness/tests/test_resolve.py | 24 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/dep_freshness/resolve.py b/dep_freshness/resolve.py index 436121e..e4b392a 100644 --- a/dep_freshness/resolve.py +++ b/dep_freshness/resolve.py @@ -50,6 +50,18 @@ def _toolchain_latest(name: str) -> str | None: return None +def never_cached(ecosystem: str, name: str) -> bool: + """True for answers that describe THIS process, not a remote registry. + + `toolchain:python` is `platform.python_version()`: free to compute, and + different on every machine. Caching it made screen-locker's CI red on + 2026-08-28 -- the `actions/cache` restore-key handed a job running 3.14.7 + a 3.13.15 answer written by an older runner image, and the gate declared + the repo's own `requires-python = ">=3.14"` to exclude "latest". + """ + return ecosystem == TOOLCHAIN and name == "python" + + def _fetch(ecosystem: str, package: str) -> str | None: if ecosystem == TOOLCHAIN: return _toolchain_latest(package) @@ -71,8 +83,11 @@ class Resolver: wanted = { (d.ecosystem, d.name) for d in deps - if not (entry := self.cache.get(d.ecosystem, d.name)) - or not entry.fresh(d.ecosystem) + if not never_cached(d.ecosystem, d.name) + and ( + not (entry := self.cache.get(d.ecosystem, d.name)) + or not entry.fresh(d.ecosystem) + ) } if not wanted: return @@ -93,6 +108,8 @@ class Resolver: self.cache.save() def latest(self, dep: Dep) -> Answer: + if never_cached(dep.ecosystem, dep.name): + return Answer(_fetch(dep.ecosystem, dep.name)) entry = self.cache.get(dep.ecosystem, dep.name) if entry and entry.fresh(dep.ecosystem): return Answer(entry.version) diff --git a/dep_freshness/tests/test_resolve.py b/dep_freshness/tests/test_resolve.py index 9742546..0955850 100644 --- a/dep_freshness/tests/test_resolve.py +++ b/dep_freshness/tests/test_resolve.py @@ -85,6 +85,30 @@ def test_refresh_discards_the_existing_cache(cache_dir, monkeypatch): assert resolver.latest(dep()).version == "1.6.0" +def test_the_python_target_ignores_the_cache_entirely(resolver, monkeypatch): + """screen-locker went red on a cached interpreter from another machine. + + `actions/cache` restored a `dep-freshness-` entry written by an older + runner image, so a job that `setup-python` had put on 3.14.7 was told the + "current toolchain" was 3.13.15 -- and the repo's own + `requires-python = ">=3.14"` was reported as excluding latest. The answer + describes this process and costs nothing, so it is never cached. + """ + resolver.cache.put("toolchain", "python", "3.13.15") + monkeypatch.setattr(tc, "python_installed", lambda: "3.14.7") + assert resolver.latest(dep("toolchain", "python")).version == "3.14.7" + + +def test_prefetch_does_not_warm_the_python_target(resolver, monkeypatch): + monkeypatch.setattr( + http, "get_json", lambda *a, **k: pytest.fail("no lookup is needed") + ) + monkeypatch.setattr( + tc, "python_installed", lambda: pytest.fail("prefetch must skip python") + ) + resolver.prefetch([dep("toolchain", "python")]) + + def test_git_tags_get_a_longer_ttl_than_registries(): assert ttl_for("gittag") > ttl_for("pub")