Skip to content

Commit dcd8a84

Browse files
fix(resolver): bypass cooldown for transitive deps when top-level pin exists
When a package is both a top-level exact-pinned requirement (e.g., `foo==1.0`) and a transitive dependency of another package (e.g., `bar` depends on `foo>=0.9`), the cooldown check was blocking the transitive resolution even though the user explicitly approved that version via the pin. This could cause version downgrades or resolution failures. `resolve_package_cooldown()` now checks the dependency graph for an existing top-level exact pin before enforcing cooldown on transitive dependencies. Closes: #1153 Co-Authored-By: Claude <claude@anthropic.com> Signed-off-by: Lalatendu Mohanty <lmohanty@redhat.com>
1 parent 75431aa commit dcd8a84

2 files changed

Lines changed: 83 additions & 0 deletions

File tree

src/fromager/resolver.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,17 @@ def resolve_package_cooldown(
167167
logger.info("cooldown bypassed as the top-level requirement uses == pin")
168168
return None
169169

170+
if req_type != RequirementType.TOP_LEVEL:
171+
root = ctx.dependency_graph.get_root_node()
172+
top_level_edges = root.get_outgoing_edges(req.name, RequirementType.TOP_LEVEL)
173+
if any(_has_equality_pin(edge.req) for edge in top_level_edges):
174+
if ctx.cooldown is not None:
175+
logger.info(
176+
"cooldown bypassed — package has a top-level == pin "
177+
"in the dependency graph"
178+
)
179+
return None
180+
170181
per_package_days = ctx.package_build_info(req).resolver_min_release_age
171182
global_cooldown = ctx.cooldown
172183
if per_package_days is None:

tests/test_cooldown.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,3 +941,75 @@ def test_resolve_package_cooldown_toplevel_compound_specifier_not_exempt(
941941
ctx, Requirement("test-pkg==1.0,>0.9"), req_type=RequirementType.TOP_LEVEL
942942
)
943943
assert result is _COOLDOWN
944+
945+
946+
def test_transitive_dep_bypasses_cooldown_when_toplevel_pin_exists(
947+
tmp_path: pathlib.Path,
948+
) -> None:
949+
"""Transitive dep should bypass cooldown when the same package has a top-level exact pin.
950+
951+
If a requirements file pins test-pkg==2.0.0 (top-level) and another
952+
top-level package depends on test-pkg>=1.0 (transitive), cooldown should
953+
not block version 2.0.0 for the transitive resolution — the user already
954+
explicitly approved that version via the pin.
955+
"""
956+
ctx = _make_ctx(tmp_path, cooldown=_COOLDOWN)
957+
958+
# Simulate: test-pkg==2.0.0 was already resolved as a top-level pin
959+
ctx.dependency_graph.add_dependency(
960+
parent_name=None,
961+
parent_version=None,
962+
req_type=RequirementType.TOP_LEVEL,
963+
req=Requirement("test-pkg==2.0.0"),
964+
req_version=Version("2.0.0"),
965+
download_url="https://files.pythonhosted.org/packages/test_pkg-2.0.0-py3-none-any.whl",
966+
pre_built=False,
967+
)
968+
969+
# Transitive resolution of the same package should bypass cooldown
970+
result = resolver.resolve_package_cooldown(
971+
ctx, Requirement("test-pkg>=1.0"), req_type=RequirementType.INSTALL
972+
)
973+
assert result is None
974+
975+
976+
def test_transitive_dep_resolves_to_toplevel_pinned_version(
977+
tmp_path: pathlib.Path,
978+
) -> None:
979+
"""End-to-end: transitive dep selects the top-level pinned version, not an older one.
980+
981+
With cooldown active, test-pkg 2.0.0 (2 days old) is within the cooldown
982+
window. A top-level pin test-pkg==2.0.0 bypasses cooldown. When the same
983+
package appears as a transitive dependency (test-pkg>=1.0), it should
984+
resolve to 2.0.0 — not fall back to 1.3.2.
985+
"""
986+
ctx = _make_ctx(tmp_path, cooldown=_COOLDOWN)
987+
988+
# Simulate: test-pkg==2.0.0 was already resolved as a top-level pin
989+
ctx.dependency_graph.add_dependency(
990+
parent_name=None,
991+
parent_version=None,
992+
req_type=RequirementType.TOP_LEVEL,
993+
req=Requirement("test-pkg==2.0.0"),
994+
req_version=Version("2.0.0"),
995+
download_url="https://files.pythonhosted.org/packages/test_pkg-2.0.0-py3-none-any.whl",
996+
pre_built=False,
997+
)
998+
999+
with requests_mock.Mocker() as r:
1000+
r.get(
1001+
"https://pypi.org/simple/test-pkg/",
1002+
json=_cooldown_json_response,
1003+
headers={"Content-Type": _PYPI_SIMPLE_JSON_CONTENT_TYPE},
1004+
)
1005+
1006+
# Transitive resolution should select 2.0.0, not 1.3.2
1007+
_, version = resolver.resolve(
1008+
ctx=ctx,
1009+
req=Requirement("test-pkg>=1.0"),
1010+
sdist_server_url="https://pypi.org/simple/",
1011+
include_sdists=True,
1012+
include_wheels=True,
1013+
req_type=RequirementType.INSTALL,
1014+
)
1015+
assert str(version) == "2.0.0"

0 commit comments

Comments
 (0)