@@ -51,16 +51,19 @@ 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
56- def resolve (
59+ def resolve_all (
5760 self ,
5861 req : Requirement ,
5962 req_type : RequirementType ,
6063 parent_req : Requirement | None = None ,
6164 pre_built : bool | None = None ,
62- ) -> tuple [str , Version ]:
63- """Resolve package requirement.
65+ ) -> list [ tuple [str , Version ] ]:
66+ """Resolve package requirement to all matching versions .
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+ List of (url, version) tuples sorted by version (highest first )
7982
8083 Raises:
8184 ValueError: If req contains a git URL and pre_built is False
@@ -101,20 +104,50 @@ def resolve(
101104 return cached_result
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
112+
113+ def resolve (
114+ self ,
115+ req : Requirement ,
116+ req_type : RequirementType ,
117+ parent_req : Requirement | None = None ,
118+ pre_built : bool | None = None ,
119+ ) -> tuple [str , Version ]:
120+ """Resolve package requirement to the best matching version.
121+
122+ Tries resolution strategies in order:
123+ 1. Session cache (if previously resolved)
124+ 2. Previous dependency graph
125+ 3. PyPI resolution (source or prebuilt based on package build info)
126+
127+ Args:
128+ req: Package requirement
129+ req_type: Type of requirement
130+ parent_req: Parent requirement from dependency chain
131+ pre_built: Optional override to force prebuilt (True) or source (False).
132+ If None (default), uses package build info to determine.
133+
134+ Returns:
135+ (url, version) tuple for the highest matching version
136+
137+ Raises:
138+ ValueError: If req contains a git URL and pre_built is False
139+ (git URL source resolution must be handled by Bootstrapper)
140+ """
141+ results = self .resolve_all (req , req_type , parent_req , pre_built )
142+ return results [0 ]
110143
111144 def _resolve (
112145 self ,
113146 req : Requirement ,
114147 req_type : RequirementType ,
115148 parent_req : Requirement | None ,
116149 pre_built : bool ,
117- ) -> tuple [str , Version ]:
150+ ) -> list [ tuple [str , Version ] ]:
118151 """Internal resolution logic without caching.
119152
120153 Tries resolution strategies in order:
@@ -128,7 +161,7 @@ def _resolve(
128161 pre_built: Whether to resolve prebuilt (True) or source (False)
129162
130163 Returns:
131- Tuple of (url, resolved_version )
164+ List of (url, version) tuples sorted by version (highest first )
132165 """
133166 # Try graph
134167 cached_resolution = self ._resolve_from_graph (
@@ -139,43 +172,44 @@ def _resolve(
139172 )
140173
141174 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
175+ logger .debug (
176+ f"resolved from previous bootstrap: { len (cached_resolution )} version(s)"
177+ )
178+ return cached_resolution
145179
146180 # Fallback to PyPI
181+ result : list [tuple [str , Version ]]
147182 if pre_built :
148183 # Resolve prebuilt wheel
149184 servers = wheels .get_wheel_server_urls (
150185 self .ctx , req , cache_wheel_server_url = resolver .PYPI_SERVER_URL
151186 )
152- url , resolved_version = wheels .resolve_prebuilt_wheel (
187+ result = wheels .resolve_prebuilt_wheel_all (
153188 ctx = self .ctx , req = req , wheel_server_urls = servers , req_type = req_type
154189 )
155190 else :
156191 # Resolve source (sdist)
157- url , resolved_version = sources .resolve_source (
192+ result = sources .resolve_source_all (
158193 ctx = self .ctx ,
159194 req = req ,
160195 sdist_server_url = resolver .PYPI_SERVER_URL ,
161196 req_type = req_type ,
162197 )
163-
164- return url , resolved_version
198+ return result
165199
166200 def get_cached_resolution (
167201 self ,
168202 req : Requirement ,
169203 pre_built : bool ,
170- ) -> tuple [str , Version ] | None :
204+ ) -> list [ tuple [str , Version ] ] | None :
171205 """Get a cached resolution result if it exists.
172206
173207 Args:
174208 req: Package requirement to look up in cache
175209 pre_built: Whether looking for prebuilt or source resolution
176210
177211 Returns:
178- Tuple of (source_url, resolved_version) if cached, None otherwise
212+ List of (url, version) tuples if cached, None otherwise
179213 """
180214 cache_key = (str (req ), pre_built )
181215 return self ._resolved_requirements .get (cache_key )
@@ -184,7 +218,7 @@ def cache_resolution(
184218 self ,
185219 req : Requirement ,
186220 pre_built : bool ,
187- result : tuple [str , Version ],
221+ result : list [ tuple [str , Version ] ],
188222 ) -> None :
189223 """Cache a resolution result.
190224
@@ -194,7 +228,7 @@ def cache_resolution(
194228 Args:
195229 req: Package requirement to cache
196230 pre_built: Whether this is a prebuilt or source resolution
197- result: Tuple of (source_url, resolved_version)
231+ result: List of (url, version) tuples
198232 """
199233 cache_key = (str (req ), pre_built )
200234 self ._resolved_requirements [cache_key ] = result
@@ -205,7 +239,7 @@ def _resolve_from_graph(
205239 req_type : RequirementType ,
206240 pre_built : bool ,
207241 parent_req : Requirement | None ,
208- ) -> tuple [str , Version ] | None :
242+ ) -> list [ tuple [str , Version ] ] | None :
209243 """Resolve from previous dependency graph.
210244
211245 Extracted from Bootstrapper._resolve_from_graph().
@@ -217,7 +251,7 @@ def _resolve_from_graph(
217251 parent_req: Parent requirement for graph traversal
218252
219253 Returns:
220- Tuple of (url, version) if found in graph, None otherwise
254+ List of (url, version) tuples if found in graph, None otherwise
221255 """
222256 if not self .prev_graph :
223257 return None
@@ -307,8 +341,8 @@ def _resolve_from_version_source(
307341 self ,
308342 version_source : list [tuple [str , Version ]],
309343 req : Requirement ,
310- ) -> tuple [str , Version ] | None :
311- """Select best version from candidates.
344+ ) -> list [ tuple [str , Version ] ] | None :
345+ """Filter and return all matching versions from candidates.
312346
313347 Extracted from Bootstrapper._resolve_from_version_source().
314348
@@ -317,7 +351,7 @@ def _resolve_from_version_source(
317351 req: Package requirement with version specifier
318352
319353 Returns:
320- Tuple of (url, version) for best match , None if no match
354+ List of (url, version) tuples for all matches , None if no matches
321355 """
322356 if not version_source :
323357 return None
@@ -329,6 +363,7 @@ def _resolve_from_version_source(
329363 constraints = self .ctx .constraints ,
330364 use_resolver_cache = False ,
331365 )
366+ # resolve_from_provider now returns all matching candidates
332367 return resolver .resolve_from_provider (provider , req )
333368 except Exception as err :
334369 logger .debug (f"could not resolve { req } from { version_source } : { err } " )
0 commit comments