From 986fb38624015b9434832aaadd3509a764f01071 Mon Sep 17 00:00:00 2001 From: Lalatendu Mohanty Date: Mon, 13 Apr 2026 08:28:18 -0400 Subject: [PATCH] fix(bootstrap): don't call get_resolver_provider hook in cache lookups _download_wheel_from_cache() called resolver.resolve(), which triggered custom resolver hooks. Cache servers are always simple PyPI index servers and don't need hook-based resolution. Call default_resolver_provider() directly instead. Closes: #1049 Co-Authored-By: Claude Signed-off-by: Lalatendu Mohanty --- src/fromager/bootstrapper.py | 10 +++++++--- tests/test_bootstrapper.py | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/fromager/bootstrapper.py b/src/fromager/bootstrapper.py index 1dff6de0..e03d6091 100644 --- a/src/fromager/bootstrapper.py +++ b/src/fromager/bootstrapper.py @@ -1100,13 +1100,17 @@ def _download_wheel_from_cache( f"checking if wheel was already uploaded to {self.cache_wheel_server_url}" ) try: - wheel_url, _ = resolver.resolve( - ctx=self.ctx, - req=Requirement(f"{req.name}=={resolved_version}"), + # Use PyPIProvider directly for cache lookups, bypassing resolver + # hooks. Cache servers are always simple PyPI index servers. + pinned_req = Requirement(f"{req.name}=={resolved_version}") + provider = resolver.PyPIProvider( sdist_server_url=self.cache_wheel_server_url, include_sdists=False, include_wheels=True, + constraints=self.ctx.constraints, ) + results = resolver.find_all_matching_from_provider(provider, pinned_req) + wheel_url, _ = results[0] wheelfile_name = pathlib.Path(urlparse(wheel_url).path) pbi = self.ctx.package_build_info(req) expected_build_tag = pbi.build_tag(resolved_version) diff --git a/tests/test_bootstrapper.py b/tests/test_bootstrapper.py index 726125c3..62886dca 100644 --- a/tests/test_bootstrapper.py +++ b/tests/test_bootstrapper.py @@ -370,3 +370,41 @@ def mock_bootstrap_impl( success_key_10 = f"{canonicalize_name('testpkg')}==1.0" assert success_key_20 in tmp_context.dependency_graph.nodes assert success_key_10 in tmp_context.dependency_graph.nodes + + +@patch("fromager.resolver.find_all_matching_from_provider") +@patch("fromager.resolver.PyPIProvider") +def test_download_wheel_from_cache_bypasses_hooks( + mock_pypi_provider: Mock, + mock_find_all: Mock, + tmp_context: WorkContext, +) -> None: + """Verify _download_wheel_from_cache uses PyPIProvider directly, not hooks.""" + bt = bootstrapper.Bootstrapper(tmp_context) + bt.cache_wheel_server_url = "https://cache.example.com/simple/" + + mock_provider = Mock() + mock_pypi_provider.return_value = mock_provider + # Raise so the except clause returns (None, None) before hitting + # network calls later in the function. + mock_find_all.side_effect = RuntimeError("no match") + + with patch("fromager.overrides.find_and_invoke") as mock_override: + result = bt._download_wheel_from_cache( + req=Requirement("test-pkg"), + resolved_version=Version("1.0.0"), + ) + + assert result == (None, None) + + # Hook system must NOT be called for cache lookups + mock_override.assert_not_called() + + # PyPIProvider must be instantiated directly + mock_pypi_provider.assert_called_once_with( + sdist_server_url="https://cache.example.com/simple/", + include_sdists=False, + include_wheels=True, + constraints=tmp_context.constraints, + ) + mock_find_all.assert_called_once_with(mock_provider, Requirement("test-pkg==1.0.0"))