2222logger = logging .getLogger (__name__ )
2323
2424
25- class RequirementResolver :
26- """Resolve package requirements from PyPI or dependency graph.
25+ class BootstrapRequirementResolver :
26+ """Resolve package requirements from PyPI or dependency graph during bootstrap .
2727
28- Single Responsibility: Coordinate resolution strategies.
28+ Single Responsibility: Coordinate resolution strategies for bootstrap process .
2929 Reason to Change: Resolution algorithm or provider priorities change.
3030
3131 Resolution strategies (in order):
@@ -51,7 +51,10 @@ def __init__(
5151 self .prev_graph = prev_graph
5252 # Session-level resolution cache to avoid re-resolving same requirements
5353 # Key: (requirement_string, pre_built) to distinguish source vs prebuilt
54- self ._resolved_requirements : dict [tuple [str , bool ], tuple [str , Version ]] = {}
54+ # Value: list of (url, version) tuples sorted by version (highest first)
55+ self ._resolved_requirements : dict [
56+ tuple [str , bool ], list [tuple [str , Version ]]
57+ ] = {}
5558
5659 def resolve (
5760 self ,
@@ -60,7 +63,7 @@ def resolve(
6063 parent_req : Requirement | None = None ,
6164 pre_built : bool | None = None ,
6265 ) -> tuple [str , Version ]:
63- """Resolve package requirement.
66+ """Resolve package requirement to the best matching version .
6467
6568 Tries resolution strategies in order:
6669 1. Session cache (if previously resolved)
@@ -75,7 +78,7 @@ def resolve(
7578 If None (default), uses package build info to determine.
7679
7780 Returns:
78- Tuple of (url, resolved_version)
81+ (url, version) tuple for the highest matching version
7982
8083 Raises:
8184 ValueError: If req contains a git URL and pre_built is False
@@ -98,23 +101,22 @@ def resolve(
98101 cached_result = self .get_cached_resolution (req , pre_built )
99102 if cached_result is not None :
100103 logger .debug (f"resolved { req } from cache" )
101- return cached_result
104+ return cached_result [ 0 ]
102105
103106 # Resolve using strategies
104- url , resolved_version = self ._resolve (req , req_type , parent_req , pre_built )
107+ results = self ._resolve (req , req_type , parent_req , pre_built )
105108
106109 # Cache the result
107- result = (url , resolved_version )
108- self .cache_resolution (req , pre_built , result )
109- return url , resolved_version
110+ self .cache_resolution (req , pre_built , results )
111+ return results [0 ]
110112
111113 def _resolve (
112114 self ,
113115 req : Requirement ,
114116 req_type : RequirementType ,
115117 parent_req : Requirement | None ,
116118 pre_built : bool ,
117- ) -> tuple [str , Version ]:
119+ ) -> list [ tuple [str , Version ] ]:
118120 """Internal resolution logic without caching.
119121
120122 Tries resolution strategies in order:
@@ -128,7 +130,7 @@ def _resolve(
128130 pre_built: Whether to resolve prebuilt (True) or source (False)
129131
130132 Returns:
131- Tuple of (url, resolved_version )
133+ List of (url, version) tuples sorted by version (highest first )
132134 """
133135 # Try graph
134136 cached_resolution = self ._resolve_from_graph (
@@ -139,43 +141,48 @@ def _resolve(
139141 )
140142
141143 if cached_resolution and not req .url :
142- url , resolved_version = cached_resolution
143- logger .debug (f"resolved from previous bootstrap to { resolved_version } " )
144- return url , resolved_version
144+ logger .debug (
145+ f"resolved from previous bootstrap: { len (cached_resolution )} version(s)"
146+ )
147+ return cached_resolution
145148
146- # Fallback to PyPI
149+ # Fallback to PyPI using provider pattern
147150 if pre_built :
148151 # Resolve prebuilt wheel
149- servers = wheels .get_wheel_server_urls (
152+ # Get wheel server URLs (use PyPI as cache/fallback server)
153+ wheel_server_urls = wheels .get_wheel_server_urls (
150154 self .ctx , req , cache_wheel_server_url = resolver .PYPI_SERVER_URL
151155 )
152- url , resolved_version = wheels .resolve_prebuilt_wheel (
153- ctx = self .ctx , req = req , wheel_server_urls = servers , req_type = req_type
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 ,
154162 )
155163 else :
156164 # Resolve source (sdist)
157- url , resolved_version = sources .resolve_source (
165+ provider = sources .get_source_provider (
158166 ctx = self .ctx ,
159167 req = req ,
160168 sdist_server_url = resolver .PYPI_SERVER_URL ,
161169 req_type = req_type ,
162170 )
163-
164- return url , resolved_version
171+ return resolver .find_all_matching_from_provider (provider , req )
165172
166173 def get_cached_resolution (
167174 self ,
168175 req : Requirement ,
169176 pre_built : bool ,
170- ) -> tuple [str , Version ] | None :
177+ ) -> list [ tuple [str , Version ] ] | None :
171178 """Get a cached resolution result if it exists.
172179
173180 Args:
174181 req: Package requirement to look up in cache
175182 pre_built: Whether looking for prebuilt or source resolution
176183
177184 Returns:
178- Tuple of (source_url, resolved_version) if cached, None otherwise
185+ List of (url, version) tuples if cached, None otherwise
179186 """
180187 cache_key = (str (req ), pre_built )
181188 return self ._resolved_requirements .get (cache_key )
@@ -184,7 +191,7 @@ def cache_resolution(
184191 self ,
185192 req : Requirement ,
186193 pre_built : bool ,
187- result : tuple [str , Version ],
194+ result : list [ tuple [str , Version ] ],
188195 ) -> None :
189196 """Cache a resolution result.
190197
@@ -194,7 +201,7 @@ def cache_resolution(
194201 Args:
195202 req: Package requirement to cache
196203 pre_built: Whether this is a prebuilt or source resolution
197- result: Tuple of (source_url, resolved_version)
204+ result: List of (url, version) tuples
198205 """
199206 cache_key = (str (req ), pre_built )
200207 self ._resolved_requirements [cache_key ] = result
@@ -205,7 +212,7 @@ def _resolve_from_graph(
205212 req_type : RequirementType ,
206213 pre_built : bool ,
207214 parent_req : Requirement | None ,
208- ) -> tuple [str , Version ] | None :
215+ ) -> list [ tuple [str , Version ] ] | None :
209216 """Resolve from previous dependency graph.
210217
211218 Extracted from Bootstrapper._resolve_from_graph().
@@ -217,7 +224,7 @@ def _resolve_from_graph(
217224 parent_req: Parent requirement for graph traversal
218225
219226 Returns:
220- Tuple of (url, version) if found in graph, None otherwise
227+ List of (url, version) tuples if found in graph, None otherwise
221228 """
222229 if not self .prev_graph :
223230 return None
@@ -307,8 +314,8 @@ def _resolve_from_version_source(
307314 self ,
308315 version_source : list [tuple [str , Version ]],
309316 req : Requirement ,
310- ) -> tuple [str , Version ] | None :
311- """Select best version from candidates.
317+ ) -> list [ tuple [str , Version ] ] | None :
318+ """Filter and return all matching versions from candidates.
312319
313320 Extracted from Bootstrapper._resolve_from_version_source().
314321
@@ -317,7 +324,7 @@ def _resolve_from_version_source(
317324 req: Package requirement with version specifier
318325
319326 Returns:
320- Tuple of (url, version) for best match , None if no match
327+ List of (url, version) tuples for all matches , None if no matches
321328 """
322329 if not version_source :
323330 return None
@@ -329,7 +336,8 @@ def _resolve_from_version_source(
329336 constraints = self .ctx .constraints ,
330337 use_resolver_cache = False ,
331338 )
332- return resolver .resolve_from_provider (provider , req )
339+ # find_all_matching_from_provider now returns all matching candidates
340+ return resolver .find_all_matching_from_provider (provider , req )
333341 except Exception as err :
334342 logger .debug (f"could not resolve { req } from { version_source } : { err } " )
335343 return None
0 commit comments