Skip to content

Commit 9dd921a

Browse files
ryanpetrelloclaude
andcommitted
feat(resolver): move cooldown filtering to find_matches, log summary at INFO
Move cooldown filtering from is_satisfied_by() into find_matches(), aligning with resolvelib's intended API: is_satisfied_by() handles specifier/constraint matching for backtracking, while find_matches() produces the filtered candidate list. Per-candidate cooldown detail remains at DEBUG (gated behind DEBUG_RESOLVER). A single INFO summary line per package lists all blocked versions, keeping production logs concise while still surfacing enforcement. Add E2E assertions verifying that enforcement messages appear in logs for all cooldown test scenarios (basic, transitive, prebuilt, gitlab). Co-Authored-By: Claude <claude@anthropic.com>
1 parent 8cf4c74 commit 9dd921a

6 files changed

Lines changed: 50 additions & 32 deletions

e2e/test_bootstrap_cooldown.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ if ! find "$OUTDIR/wheels-repo/downloads/" -name 'stevedore-5.3.0*.whl' | grep -
5959
pass=false
6060
fi
6161

62+
# The cooldown must have logged that it blocked stevedore version(s).
63+
if ! grep -q "cooldown blocked .* version(s):" "$OUTDIR/bootstrap-flag.log"; then
64+
echo "FAIL (flag): no cooldown enforcement message for stevedore found in log" 1>&2
65+
pass=false
66+
fi
67+
6268
# --- Pass 2: enforce the same cooldown via environment variable (FROMAGER_MIN_RELEASE_AGE) ---
6369

6470
# Wipe output so the second run starts clean.
@@ -82,4 +88,9 @@ if ! find "$OUTDIR/wheels-repo/downloads/" -name 'stevedore-5.3.0*.whl' | grep -
8288
pass=false
8389
fi
8490

91+
if ! grep -q "cooldown blocked .* version(s):" "$OUTDIR/bootstrap-envvar.log"; then
92+
echo "FAIL (envvar): no cooldown enforcement message for stevedore found in log" 1>&2
93+
pass=false
94+
fi
95+
8596
$pass

e2e/test_bootstrap_cooldown_gitlab.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,10 @@ if ! grep -q "GitLabTagProvider" "$OUTDIR/bootstrap.log"; then
7575
pass=false
7676
fi
7777

78+
# The cooldown must have logged that it blocked python-gitlab version(s).
79+
if ! grep -q "cooldown blocked .* version(s):" "$OUTDIR/bootstrap.log"; then
80+
echo "FAIL: no cooldown enforcement message for python-gitlab found in log" 1>&2
81+
pass=false
82+
fi
83+
7884
$pass

e2e/test_bootstrap_cooldown_prebuilt.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,10 @@ if find "$OUTDIR/sdists-repo/" \( -name 'stevedore*.tar.gz' -o -name 'stevedore*
6262
pass=false
6363
fi
6464

65+
# The cooldown must have logged that it blocked stevedore version(s).
66+
if ! grep -q "cooldown blocked .* version(s):" "$OUTDIR/bootstrap.log"; then
67+
echo "FAIL: no cooldown enforcement message for stevedore found in log" 1>&2
68+
pass=false
69+
fi
70+
6571
$pass

e2e/test_bootstrap_cooldown_transitive.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,10 @@ if ! find "$OUTDIR/wheels-repo/downloads/" -name 'pbr-6.1.1*.whl' | grep -q .; t
6969
pass=false
7070
fi
7171

72+
# The cooldown must have logged that it blocked pbr version(s).
73+
if ! grep -q "cooldown blocked .* version(s):" "$OUTDIR/bootstrap.log"; then
74+
echo "FAIL: no cooldown enforcement message for pbr found in log" 1>&2
75+
pass=false
76+
fi
77+
7278
$pass

src/fromager/resolver.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -680,9 +680,6 @@ def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> boo
680680
)
681681
return False
682682

683-
if self.is_blocked_by_cooldown(candidate):
684-
return False
685-
686683
return True
687684

688685
def is_blocked_by_cooldown(self, candidate: Candidate) -> bool:
@@ -700,10 +697,9 @@ def is_blocked_by_cooldown(self, candidate: Candidate) -> bool:
700697
if candidate.name not in BaseProvider._cooldown_unsupported_warned:
701698
BaseProvider._cooldown_unsupported_warned.add(candidate.name)
702699
logger.warning(
703-
"%s: release-age cooldown cannot be enforced — upload "
700+
"release-age cooldown cannot be enforced — upload "
704701
"timestamp support is not yet implemented for %s; "
705702
"cooldown check skipped",
706-
candidate.name,
707703
self.get_provider_description(),
708704
)
709705
return False
@@ -712,8 +708,7 @@ def is_blocked_by_cooldown(self, candidate: Candidate) -> bool:
712708
# Fail closed: we cannot verify the age of this candidate, so reject it.
713709
if DEBUG_RESOLVER:
714710
logger.debug(
715-
"%s: skipping %s — upload_time unknown, required for cooldown",
716-
candidate.name,
711+
"skipping %s — upload_time unknown, required for cooldown",
717712
candidate.version,
718713
)
719714
return True
@@ -727,8 +722,7 @@ def is_blocked_by_cooldown(self, candidate: Candidate) -> bool:
727722
if DEBUG_RESOLVER:
728723
age = self.cooldown.bootstrap_time - candidate.upload_time
729724
logger.debug(
730-
"%s: skipping %s uploaded %s ago (cooldown: %s)",
731-
candidate.name,
725+
"skipping %s uploaded %s ago (cooldown: %s)",
732726
candidate.version,
733727
age,
734728
self.cooldown.min_age,
@@ -809,6 +803,17 @@ def find_matches(
809803
identifier, requirements, incompatibilities, candidate
810804
)
811805
]
806+
# Apply cooldown filtering after specifier/constraint validation
807+
blocked = [c for c in candidates if self.is_blocked_by_cooldown(c)]
808+
if blocked:
809+
for b in blocked:
810+
candidates.remove(b)
811+
versions = ", ".join(str(b.version) for b in blocked)
812+
logger.info(
813+
"cooldown blocked %d version(s): %s",
814+
len(blocked),
815+
versions,
816+
)
812817
if not candidates:
813818
raise resolvelib.resolvers.ResolverException(
814819
self._get_no_match_error_message(identifier, requirements)

tests/test_cooldown.py

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import pathlib
1111
import re
1212
import typing
13-
from collections import defaultdict
1413

1514
import pytest
1615
import requests_mock
@@ -117,10 +116,8 @@ def test_cooldown_filters_recent_version(
117116
candidate = result.mapping["test-pkg"]
118117
# 2.0.0 is 2 days old (within cooldown); 1.3.2 is 11 days old (outside).
119118
assert str(candidate.version) == "1.3.2"
120-
# 2.0.0 should be logged as skipped; 1.3.2 should not.
121-
assert "skipping 2.0.0" in caplog.text
122-
assert "cooldown" in caplog.text
123-
assert "skipping 1.3.2" not in caplog.text
119+
# 2.0.0 should be logged as blocked; 1.3.2 should not appear in the summary.
120+
assert "cooldown blocked 1 version(s): 2.0.0" in caplog.text
124121

125122

126123
def test_cooldown_disabled_selects_latest() -> None:
@@ -171,17 +168,11 @@ def test_cooldown_rejects_candidate_without_upload_time(
171168
upload_time=None,
172169
)
173170
provider = resolver.PyPIProvider(cooldown=_COOLDOWN)
174-
req = Requirement("test-pkg")
175-
requirements: typing.Any = defaultdict(list)
176-
requirements["test-pkg"].append(req)
177-
incompatibilities: typing.Any = defaultdict(list)
178171

179172
with caplog.at_level(logging.DEBUG, logger="fromager.resolver"):
180-
result = provider.validate_candidate(
181-
"test-pkg", requirements, incompatibilities, candidate
182-
)
173+
result = provider.is_blocked_by_cooldown(candidate)
183174

184-
assert result is False
175+
assert result is True
185176
assert "upload_time unknown" in caplog.text
186177
assert "1.0.0" in caplog.text
187178

@@ -567,8 +558,7 @@ def test_gitlab_cooldown_filters_recent_tag(
567558
candidate = result.mapping["test-pkg"]
568559
# v0.0.3 (2025-05-14) is inside the 7-day window; v0.0.2 is the next newest.
569560
assert str(candidate.version) == "0.0.2"
570-
assert "skipping 0.0.3" in caplog.text
571-
assert "cooldown" in caplog.text
561+
assert "cooldown blocked 1 version(s): 0.0.3" in caplog.text
572562

573563

574564
def test_gitlab_cooldown_disabled_selects_latest() -> None:
@@ -639,17 +629,11 @@ def test_github_cooldown_skips_with_warning(
639629
url="https://github.com/example/pkg/archive/v1.0.0.tar.gz",
640630
upload_time=None,
641631
)
642-
req = Requirement("test-pkg")
643-
requirements: typing.Any = defaultdict(list)
644-
requirements["test-pkg"].append(req)
645-
incompatibilities: typing.Any = defaultdict(list)
646632

647633
with caplog.at_level(logging.WARNING, logger="fromager.resolver"):
648-
result = provider.validate_candidate(
649-
"test-pkg", requirements, incompatibilities, candidate
650-
)
634+
result = provider.is_blocked_by_cooldown(candidate)
651635

652-
assert result is True
636+
assert result is False
653637
assert "cooldown cannot be enforced" in caplog.text
654638

655639

0 commit comments

Comments
 (0)