48
48
)
49
49
from pip ._vendor .packaging .version import _BaseVersion
50
50
from pip ._vendor .requests import Response
51
+ from pip ._internal .pep425tags import Pep425Tag
51
52
from pip ._internal .req import InstallRequirement
52
53
from pip ._internal .download import PipSession
53
54
@@ -255,6 +256,71 @@ def _get_html_page(link, session=None):
255
256
return None
256
257
257
258
259
+ class CandidateEvaluator (object ):
260
+
261
+ def __init__ (
262
+ self ,
263
+ valid_tags , # type: List[Pep425Tag]
264
+ prefer_binary = False # type: bool
265
+
266
+ ):
267
+ # type: (...) -> None
268
+ self ._prefer_binary = prefer_binary
269
+ self ._valid_tags = valid_tags
270
+
271
+ def is_wheel_supported (self , wheel ):
272
+ # type: (Wheel) -> bool
273
+ return wheel .supported (self ._valid_tags )
274
+
275
+ def _sort_key (self , candidate ):
276
+ # type: (InstallationCandidate) -> CandidateSortingKey
277
+ """
278
+ Function used to generate link sort key for link tuples.
279
+ The greater the return value, the more preferred it is.
280
+ If not finding wheels, then sorted by version only.
281
+ If finding wheels, then the sort order is by version, then:
282
+ 1. existing installs
283
+ 2. wheels ordered via Wheel.support_index_min(self._valid_tags)
284
+ 3. source archives
285
+ If prefer_binary was set, then all wheels are sorted above sources.
286
+ Note: it was considered to embed this logic into the Link
287
+ comparison operators, but then different sdist links
288
+ with the same version, would have to be considered equal
289
+ """
290
+ support_num = len (self ._valid_tags )
291
+ build_tag = tuple () # type: BuildTag
292
+ binary_preference = 0
293
+ if candidate .location .is_wheel :
294
+ # can raise InvalidWheelFilename
295
+ wheel = Wheel (candidate .location .filename )
296
+ if not wheel .supported (self ._valid_tags ):
297
+ raise UnsupportedWheel (
298
+ "%s is not a supported wheel for this platform. It "
299
+ "can't be sorted." % wheel .filename
300
+ )
301
+ if self ._prefer_binary :
302
+ binary_preference = 1
303
+ pri = - (wheel .support_index_min (self ._valid_tags ))
304
+ if wheel .build_tag is not None :
305
+ match = re .match (r'^(\d+)(.*)$' , wheel .build_tag )
306
+ build_tag_groups = match .groups ()
307
+ build_tag = (int (build_tag_groups [0 ]), build_tag_groups [1 ])
308
+ else : # sdist
309
+ pri = - (support_num )
310
+ return (binary_preference , candidate .version , build_tag , pri )
311
+
312
+ def get_best_candidate (self , candidates ):
313
+ # type: (List[InstallationCandidate]) -> InstallationCandidate
314
+ """
315
+ Return the best candidate per the instance's sort order, or None if
316
+ no candidates are given.
317
+ """
318
+ if not candidates :
319
+ return None
320
+
321
+ return max (candidates , key = self ._sort_key )
322
+
323
+
258
324
class FoundCandidates (object ):
259
325
"""A collection of candidates, returned by `PackageFinder.find_candidates`.
260
326
@@ -267,18 +333,18 @@ class FoundCandidates(object):
267
333
* `specifier`: Specifier to filter applicable versions.
268
334
* `prereleases`: Whether prereleases should be accounted. Pass None to
269
335
infer from the specifier.
270
- * `sort_key `: A callable used as the key function when choosing the best
271
- candidate .
336
+ * `evaluator `: A CandidateEvaluator object to sort applicable candidates
337
+ by order of preference .
272
338
"""
273
339
def __init__ (
274
340
self ,
275
341
candidates , # type: List[InstallationCandidate]
276
342
versions , # type: Set[str]
277
- sort_key , # type: Callable[[InstallationCandidate], Any]
343
+ evaluator , # type: CandidateEvaluator
278
344
):
279
345
# type: (...) -> None
280
346
self ._candidates = candidates
281
- self ._sort_key = sort_key
347
+ self ._evaluator = evaluator
282
348
self ._versions = versions
283
349
284
350
@classmethod
@@ -287,7 +353,7 @@ def from_specifier(
287
353
candidates , # type: List[InstallationCandidate]
288
354
specifier , # type: specifiers.BaseSpecifier
289
355
prereleases , # type: Optional[bool]
290
- sort_key , # type: Callable[[InstallationCandidate], Any]
356
+ evaluator , # type: CandidateEvaluator
291
357
):
292
358
# type: (...) -> FoundCandidates
293
359
versions = {
@@ -303,7 +369,7 @@ def from_specifier(
303
369
prereleases = prereleases ,
304
370
)
305
371
}
306
- return cls (candidates , versions , sort_key )
372
+ return cls (candidates , versions , evaluator )
307
373
308
374
def iter_all (self ):
309
375
# type: () -> Iterable[InstallationCandidate]
@@ -325,9 +391,7 @@ def get_best(self):
325
391
candidates are found.
326
392
"""
327
393
candidates = list (self .iter_applicable ())
328
- if not candidates :
329
- return None
330
- return max (candidates , key = self ._sort_key )
394
+ return self ._evaluator .get_best_candidate (candidates )
331
395
332
396
333
397
class PackageFinder (object ):
@@ -368,6 +432,8 @@ def __init__(
368
432
to pep425tags.py in the get_supported() method.
369
433
:param implementation: A string or None. This is passed directly
370
434
to pep425tags.py in the get_supported() method.
435
+ :param prefer_binary: Whether to prefer an old, but valid, binary
436
+ dist over a new source dist.
371
437
"""
372
438
if session is None :
373
439
raise TypeError (
@@ -408,15 +474,15 @@ def __init__(
408
474
self .session = session
409
475
410
476
# The valid tags to check potential found wheel candidates against
411
- self . valid_tags = get_supported (
477
+ valid_tags = get_supported (
412
478
versions = versions ,
413
479
platform = platform ,
414
480
abi = abi ,
415
481
impl = implementation ,
416
482
)
417
-
418
- # Do we prefer old, but valid, binary dist over new source dist
419
- self . prefer_binary = prefer_binary
483
+ self . candidate_evaluator = CandidateEvaluator (
484
+ valid_tags = valid_tags , prefer_binary = prefer_binary ,
485
+ )
420
486
421
487
# If we don't have TLS enabled, then WARN if anyplace we're looking
422
488
# relies on TLS.
@@ -503,43 +569,6 @@ def sort_path(path):
503
569
504
570
return files , urls
505
571
506
- def _candidate_sort_key (self , candidate ):
507
- # type: (InstallationCandidate) -> CandidateSortingKey
508
- """
509
- Function used to generate link sort key for link tuples.
510
- The greater the return value, the more preferred it is.
511
- If not finding wheels, then sorted by version only.
512
- If finding wheels, then the sort order is by version, then:
513
- 1. existing installs
514
- 2. wheels ordered via Wheel.support_index_min(self.valid_tags)
515
- 3. source archives
516
- If prefer_binary was set, then all wheels are sorted above sources.
517
- Note: it was considered to embed this logic into the Link
518
- comparison operators, but then different sdist links
519
- with the same version, would have to be considered equal
520
- """
521
- support_num = len (self .valid_tags )
522
- build_tag = tuple () # type: BuildTag
523
- binary_preference = 0
524
- if candidate .location .is_wheel :
525
- # can raise InvalidWheelFilename
526
- wheel = Wheel (candidate .location .filename )
527
- if not wheel .supported (self .valid_tags ):
528
- raise UnsupportedWheel (
529
- "%s is not a supported wheel for this platform. It "
530
- "can't be sorted." % wheel .filename
531
- )
532
- if self .prefer_binary :
533
- binary_preference = 1
534
- pri = - (wheel .support_index_min (self .valid_tags ))
535
- if wheel .build_tag is not None :
536
- match = re .match (r'^(\d+)(.*)$' , wheel .build_tag )
537
- build_tag_groups = match .groups ()
538
- build_tag = (int (build_tag_groups [0 ]), build_tag_groups [1 ])
539
- else : # sdist
540
- pri = - (support_num )
541
- return (binary_preference , candidate .version , build_tag , pri )
542
-
543
572
def _validate_secure_origin (self , logger , location ):
544
573
# type: (Logger, Link) -> bool
545
574
# Determine if this url used a secure transport mechanism
@@ -722,7 +751,7 @@ def find_candidates(
722
751
self .find_all_candidates (project_name ),
723
752
specifier = specifier ,
724
753
prereleases = (self .allow_all_prereleases or None ),
725
- sort_key = self ._candidate_sort_key ,
754
+ evaluator = self .candidate_evaluator ,
726
755
)
727
756
728
757
def find_requirement (self , req , upgrade ):
@@ -893,7 +922,7 @@ def _link_package_versions(self, link, search):
893
922
link , 'wrong project name (not %s)' % search .supplied )
894
923
return None
895
924
896
- if not wheel . supported ( self .valid_tags ):
925
+ if not self .candidate_evaluator . is_wheel_supported ( wheel ):
897
926
self ._log_skipped_link (
898
927
link , 'it is not compatible with this Python' )
899
928
return None
0 commit comments