Skip to content

Commit ca16167

Browse files
rd4398claude
andcommitted
refactor: eliminate duplicated wheel server retry loop
BootstrapRequirementResolver._resolve() and wheels.resolve_prebuilt_wheel() both implemented the "try each wheel server until one succeeds" pattern with different error handling: - Bootstrap path caught narrow exceptions (ResolverException | NoSuchProjectError) and raised ValueError with no exception details - Wheel path caught broad Exception and raised ExceptionGroup with collected errors This inconsistency meant: - Different exception types were swallowed in different code paths - Bootstrap path lost exception details (no ExceptionGroup) - Callers couldn't distinguish failure types Solution: - Created resolve_all_prebuilt_wheels() that returns all matching versions - Made resolve_prebuilt_wheel() call it and return first result - Made bootstrap resolver use resolve_all_prebuilt_wheels() - Now both code paths use consistent ExceptionGroup-based error handling Benefits: - Single source of truth for wheel server retry logic - Consistent exception handling across all resolution paths - Better error reporting with ExceptionGroup containing details from all servers - Reduces code duplication by ~20 lines Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Rohan Devasthale <rdevasth@redhat.com>
1 parent 1bb213c commit ca16167

2 files changed

Lines changed: 35 additions & 32 deletions

File tree

src/fromager/bootstrap_requirement_resolver.py

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
import logging
1010
import typing
1111

12-
import pypi_simple.errors
13-
import resolvelib.resolvers
1412
from packaging.requirements import Requirement
1513
from packaging.version import Version
1614

@@ -155,27 +153,12 @@ def _resolve(
155153
wheel_server_urls = wheels.get_wheel_server_urls(
156154
self.ctx, req, cache_wheel_server_url=resolver.PYPI_SERVER_URL
157155
)
158-
159-
# Try each wheel server until one succeeds
160-
for url in wheel_server_urls:
161-
try:
162-
provider = wheels.get_prebuilt_wheel_provider(
163-
ctx=self.ctx, req=req, wheel_server_url=url, req_type=req_type
164-
)
165-
results = resolver.find_all_matching_from_provider(provider, req)
166-
# find_all_matching_from_provider never returns empty list - raises instead
167-
return results
168-
except (
169-
resolvelib.resolvers.ResolverException,
170-
pypi_simple.errors.NoSuchProjectError,
171-
) as e:
172-
# Only catch expected "not found" exceptions - try next server
173-
# Let auth failures, network errors, and plugin bugs propagate
174-
logger.debug(f"{req.name}: no matching wheel on {url}: {e}")
175-
continue
176-
# If we get here, no wheel server succeeded
177-
raise ValueError(
178-
f"Could not find a prebuilt wheel for {req} on {' or '.join(wheel_server_urls)}"
156+
# Use shared retry loop logic from wheels module
157+
return wheels.resolve_all_prebuilt_wheels(
158+
ctx=self.ctx,
159+
req=req,
160+
wheel_server_urls=wheel_server_urls,
161+
req_type=req_type,
179162
)
180163
else:
181164
# Resolve source (sdist)

src/fromager/wheels.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -485,18 +485,19 @@ def get_prebuilt_wheel_provider(
485485
)
486486

487487

488-
@metrics.timeit(description="resolve wheel")
489-
def resolve_prebuilt_wheel(
488+
def resolve_all_prebuilt_wheels(
490489
*,
491490
ctx: context.WorkContext,
492491
req: Requirement,
493492
wheel_server_urls: list[str],
494493
req_type: requirements_file.RequirementType | None = None,
495-
) -> tuple[str, Version]:
496-
"""Return (URL, version) for the best matching wheel version.
494+
) -> list[tuple[str, Version]]:
495+
"""Return all matching wheel versions from the first successful server.
497496
498-
Tries wheel servers in order and returns result from the first that succeeds.
499-
Returns the highest matching version.
497+
Tries wheel servers in order and returns all matching versions from the
498+
first server that has any matches. Results are sorted by version (highest first).
499+
500+
Raises ExceptionGroup if no server has matching wheels.
500501
"""
501502
excs: list[Exception] = []
502503
for url in wheel_server_urls:
@@ -509,13 +510,32 @@ def resolve_prebuilt_wheel(
509510
# Get all matching candidates from provider
510511
results = resolver.find_all_matching_from_provider(provider, req)
511512
# find_all_matching_from_provider never returns empty list - raises instead
512-
# Return highest version (first in sorted list)
513-
wheel_url, version = results[0]
514-
return str(wheel_url), version
513+
return results
515514
except Exception as e:
516515
excs.append(e)
517516

518517
raise ExceptionGroup(
519518
f"Could not find a prebuilt wheel for {req} on {' or '.join(wheel_server_urls)}",
520519
excs,
521520
)
521+
522+
523+
@metrics.timeit(description="resolve wheel")
524+
def resolve_prebuilt_wheel(
525+
*,
526+
ctx: context.WorkContext,
527+
req: Requirement,
528+
wheel_server_urls: list[str],
529+
req_type: requirements_file.RequirementType | None = None,
530+
) -> tuple[str, Version]:
531+
"""Return (URL, version) for the best matching wheel version.
532+
533+
Tries wheel servers in order and returns result from the first that succeeds.
534+
Returns the highest matching version.
535+
"""
536+
results = resolve_all_prebuilt_wheels(
537+
ctx=ctx, req=req, wheel_server_urls=wheel_server_urls, req_type=req_type
538+
)
539+
# Return highest version (first in sorted list)
540+
wheel_url, version = results[0]
541+
return str(wheel_url), version

0 commit comments

Comments
 (0)