@@ -123,9 +123,9 @@ def resolve_from_provider(
123123 rslvr : resolvelib .Resolver = resolvelib .Resolver (provider , reporter )
124124 try :
125125 result = rslvr .resolve ([req ])
126- except resolvelib .resolvers .exceptions .ResolutionImpossible as err :
126+ except resolvelib .resolvers .exceptions .ResolverException as err :
127127 constraint = provider .constraints .get_constraint (req .name )
128- raise ValueError (
128+ raise resolvelib . resolvers . exceptions . ResolverException (
129129 f"Unable to resolve requirement specifier { req } with constraint { constraint } "
130130 ) from err
131131 # resolvelib actually just returns one candidate per requirement.
@@ -142,6 +142,8 @@ def get_project_from_pypi(
142142 sdist_server_url : str ,
143143) -> typing .Iterable [Candidate ]:
144144 """Return candidates created from the project name and extras."""
145+ found_candidates : set [str ] = set ()
146+ ignored_candidates : set [str ] = set ()
145147 simple_index_url = sdist_server_url .rstrip ("/" ) + "/" + project + "/"
146148 logger .debug ("%s: getting available versions from %s" , project , simple_index_url )
147149 data = session .get (simple_index_url ).content
@@ -151,6 +153,7 @@ def get_project_from_pypi(
151153 py_req = i .attrib .get ("data-requires-python" )
152154 path = urlparse (candidate_url ).path
153155 filename = path .rsplit ("/" , 1 )[- 1 ]
156+ found_candidates .add (filename )
154157 if DEBUG_RESOLVER :
155158 logger .debug ("%s: candidate %r -> %r" , project , candidate_url , filename )
156159 # Skip items that need a different Python version
@@ -164,12 +167,14 @@ def get_project_from_pypi(
164167 logger .debug (
165168 f"{ project } : skipping { filename } because of an invalid python version specifier { py_req } : { err } "
166169 )
170+ ignored_candidates .add (filename )
167171 continue
168172 if PYTHON_VERSION not in spec :
169173 if DEBUG_RESOLVER :
170174 logger .debug (
171175 f"{ project } : skipping { filename } because of python version { py_req } "
172176 )
177+ ignored_candidates .add (filename )
173178 continue
174179
175180 # TODO: Handle compatibility tags?
@@ -190,13 +195,15 @@ def get_project_from_pypi(
190195 if not matching_tags :
191196 if DEBUG_RESOLVER :
192197 logger .debug (f"{ project } : ignoring { filename } with tags { tags } " )
198+ ignored_candidates .add (filename )
193199 continue
194200 except Exception as err :
195201 # Ignore files with invalid versions
196202 if DEBUG_RESOLVER :
197203 logger .debug (
198204 f'{ project } : could not determine version for "{ filename } ": { err } '
199205 )
206+ ignored_candidates .add (filename )
200207 continue
201208 # Look for and ignore cases like `cffi-1.0.2-2.tar.gz` which
202209 # produces the name `cffi-1-0-2`. We can't just compare the
@@ -208,6 +215,7 @@ def get_project_from_pypi(
208215 if len (name ) != len (project ):
209216 if DEBUG_RESOLVER :
210217 logger .debug (f'{ project } : skipping invalid filename "{ filename } "' )
218+ ignored_candidates .add (filename )
211219 continue
212220
213221 c = Candidate (
@@ -224,6 +232,11 @@ def get_project_from_pypi(
224232 )
225233 yield c
226234
235+ if not found_candidates :
236+ logger .info (f"{ project } : found no candidate files at { simple_index_url } " )
237+ elif ignored_candidates == found_candidates :
238+ logger .info (f"{ project } : ignored all candidate files at { simple_index_url } " )
239+
227240
228241RequirementsMap : typing .TypeAlias = typing .Mapping [str , typing .Iterable [Requirement ]]
229242CandidatesMap : typing .TypeAlias = typing .Mapping [str , typing .Iterable [Candidate ]]
@@ -273,37 +286,17 @@ def validate_candidate(
273286 ) -> bool :
274287 identifier_reqs = list (requirements [identifier ])
275288 bad_versions = {c .version for c in incompatibilities [identifier ]}
276- allow_prerelease = self .constraints .allow_prerelease (identifier )
277-
278289 # Skip versions that are known bad
279290 if candidate .version in bad_versions :
280291 if DEBUG_RESOLVER :
281292 logger .debug (
282293 f"{ identifier } : skipping bad version { candidate .version } from { bad_versions } "
283294 )
284295 return False
285- # Skip versions that do not match the requirement. Allow prereleases only if constraints allow prereleases
286- if not all (
287- r .specifier .contains (
288- candidate .version ,
289- prereleases = (allow_prerelease or bool (r .specifier .prereleases )),
290- )
291- for r in identifier_reqs
292- ):
293- if DEBUG_RESOLVER :
294- logger .debug (
295- f"{ identifier } : skipping { candidate .version } because it does not match { identifier_reqs } "
296- )
297- return False
298- # Skip versions that do not match the constraint
299- if not self .constraints .is_satisfied_by (identifier , candidate .version ):
300- if DEBUG_RESOLVER :
301- c = self .constraints .get_constraint (identifier )
302- logger .debug (
303- f"{ identifier } : skipping { candidate .version } due to constraint { c } "
304- )
305- return False
306- return True
296+ for r in identifier_reqs :
297+ if self .is_satisfied_by (requirement = r , candidate = candidate ):
298+ return True
299+ return False
307300
308301 def get_cache (self ) -> dict [str , list [Candidate ]]:
309302 raise NotImplementedError ()
@@ -350,9 +343,24 @@ def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> boo
350343 allow_prerelease = self .constraints .allow_prerelease (requirement .name ) or bool (
351344 requirement .specifier .prereleases
352345 )
353- return requirement .specifier .contains (
346+ if not requirement .specifier .contains (
354347 candidate .version , prereleases = allow_prerelease
355- ) and self .constraints .is_satisfied_by (requirement .name , candidate .version )
348+ ):
349+ if DEBUG_RESOLVER :
350+ logger .debug (
351+ f"{ requirement .name } : skipping candidate version { candidate .version } because it does not match { requirement .specifier } "
352+ )
353+ return False
354+
355+ if not self .constraints .is_satisfied_by (requirement .name , candidate .version ):
356+ if DEBUG_RESOLVER :
357+ c = self .constraints .get_constraint (requirement .name )
358+ logger .debug (
359+ f"{ requirement .name } : skipping { candidate .version } due to constraint { c } "
360+ )
361+ return False
362+
363+ return True
356364
357365 def get_dependencies (self , candidate : Candidate ) -> list [Requirement ]:
358366 # return candidate.dependencies
@@ -437,6 +445,23 @@ def find_matches(
437445 ):
438446 candidates .append (candidate )
439447 self .add_to_cache (identifier , candidates )
448+ if not candidates :
449+ # Try to construct a meaningful error message that points out the
450+ # type(s) of files the resolver has been told it can choose as a
451+ # hint in case that should be adjusted for the package that does not
452+ # resolve.
453+ r = next (iter (requirements [identifier ]))
454+ if self .include_sdists and self .include_wheels :
455+ raise resolvelib .resolvers .exceptions .ResolverException (
456+ f"found no match for { r } , any file type, in cache or at { self .sdist_server_url } "
457+ )
458+ elif self .include_sdists :
459+ raise resolvelib .resolvers .exceptions .ResolverException (
460+ f"found no match for { r } , limiting search to sdists, in cache or at { self .sdist_server_url } "
461+ )
462+ raise resolvelib .resolvers .exceptions .ResolverException (
463+ f"found no match for { r } , limiting search to wheels, in cache or at { self .sdist_server_url } "
464+ )
440465 return sorted (candidates , key = attrgetter ("version" , "build_tag" ), reverse = True )
441466
442467
0 commit comments