50
50
from pip ._internal .req import InstallRequirement
51
51
from pip ._internal .download import PipSession
52
52
from pip ._internal .pep425tags import Pep425Tag
53
+ from pip ._internal .utils .hashes import Hashes
53
54
54
55
BuildTag = Tuple [Any , ...] # either empty tuple or Tuple[int, str]
55
56
CandidateSortingKey = (
56
- Tuple [int , int , _BaseVersion , BuildTag , Optional [int ]]
57
+ Tuple [int , int , int , _BaseVersion , BuildTag , Optional [int ]]
57
58
)
58
59
HTMLElement = xml .etree .ElementTree .Element
59
60
SecureOrigin = Tuple [str , str , Optional [str ]]
@@ -440,6 +441,45 @@ def evaluate_link(self, link):
440
441
return (True , version )
441
442
442
443
444
+ def filter_unallowed_hashes (
445
+ candidates , # type: List[InstallationCandidate]
446
+ hashes , # type: Hashes
447
+ ):
448
+ # type: (...) -> List[InstallationCandidate]
449
+ """
450
+ Filter out candidates whose hashes aren't allowed, and return a new
451
+ list of candidates.
452
+
453
+ If at least one candidate has an allowed hash, then all candidates with
454
+ either an allowed hash or no hash specified are returned. Otherwise,
455
+ the given candidates are returned.
456
+
457
+ Including the candidates with no hash specified when there is a match
458
+ allows a warning to be logged if there is a more preferred candidate
459
+ with no hash specified. Returning all candidates in the case of no
460
+ matches lets pip report the hash of the candidate that would otherwise
461
+ have been installed (e.g. permitting the user to more easily update
462
+ their requirements file with the desired hash).
463
+ """
464
+ applicable = []
465
+ found_allowed_hash = False
466
+ for candidate in candidates :
467
+ link = candidate .location
468
+ if not link .has_hash :
469
+ applicable .append (candidate )
470
+ continue
471
+
472
+ if link .is_hash_allowed (hashes = hashes ):
473
+ found_allowed_hash = True
474
+ applicable .append (candidate )
475
+
476
+ if found_allowed_hash :
477
+ return applicable
478
+
479
+ # Make sure we're not returning back the given value.
480
+ return list (candidates )
481
+
482
+
443
483
class CandidatePreferences (object ):
444
484
445
485
"""
@@ -473,13 +513,15 @@ def create(
473
513
target_python = None , # type: Optional[TargetPython]
474
514
prefer_binary = False , # type: bool
475
515
allow_all_prereleases = False , # type: bool
516
+ hashes = None , # type: Optional[Hashes]
476
517
):
477
518
# type: (...) -> CandidateEvaluator
478
519
"""Create a CandidateEvaluator object.
479
520
480
521
:param target_python: The target Python interpreter to use when
481
522
checking compatibility. If None (the default), a TargetPython
482
523
object will be constructed from the running Python.
524
+ :param hashes: An optional collection of allowed hashes.
483
525
"""
484
526
if target_python is None :
485
527
target_python = TargetPython ()
@@ -490,20 +532,23 @@ def create(
490
532
supported_tags = supported_tags ,
491
533
prefer_binary = prefer_binary ,
492
534
allow_all_prereleases = allow_all_prereleases ,
535
+ hashes = hashes ,
493
536
)
494
537
495
538
def __init__ (
496
539
self ,
497
540
supported_tags , # type: List[Pep425Tag]
498
541
prefer_binary = False , # type: bool
499
542
allow_all_prereleases = False , # type: bool
543
+ hashes = None , # type: Optional[Hashes]
500
544
):
501
545
# type: (...) -> None
502
546
"""
503
547
:param supported_tags: The PEP 425 tags supported by the target
504
548
Python in order of preference (most preferred first).
505
549
"""
506
550
self ._allow_all_prereleases = allow_all_prereleases
551
+ self ._hashes = hashes
507
552
self ._prefer_binary = prefer_binary
508
553
self ._supported_tags = supported_tags
509
554
@@ -536,7 +581,10 @@ def get_applicable_candidates(
536
581
applicable_candidates = [
537
582
c for c in candidates if str (c .version ) in versions
538
583
]
539
- return applicable_candidates
584
+
585
+ return filter_unallowed_hashes (
586
+ candidates = applicable_candidates , hashes = self ._hashes ,
587
+ )
540
588
541
589
def make_found_candidates (
542
590
self ,
@@ -576,8 +624,14 @@ def _sort_key(self, candidate):
576
624
577
625
The preference is as follows:
578
626
579
- First and foremost, yanked candidates (in the sense of PEP 592) are
580
- always less preferred than candidates that haven't been yanked. Then:
627
+ First and foremost, candidates with allowed (matching) hashes are
628
+ always preferred over candidates without matching hashes. This is
629
+ because e.g. if the only candidate with an allowed hash is yanked,
630
+ we still want to use that candidate.
631
+
632
+ Second, excepting hash considerations, candidates that have been
633
+ yanked (in the sense of PEP 592) are always less preferred than
634
+ candidates that haven't been yanked. Then:
581
635
582
636
If not finding wheels, they are sorted by version only.
583
637
If finding wheels, then the sort order is by version, then:
@@ -612,9 +666,11 @@ def _sort_key(self, candidate):
612
666
build_tag = (int (build_tag_groups [0 ]), build_tag_groups [1 ])
613
667
else : # sdist
614
668
pri = - (support_num )
669
+ has_allowed_hash = int (link .is_hash_allowed (self ._hashes ))
615
670
yank_value = - 1 * int (link .is_yanked ) # -1 for yanked.
616
671
return (
617
- yank_value , binary_preference , candidate .version , build_tag , pri ,
672
+ has_allowed_hash , yank_value , binary_preference , candidate .version ,
673
+ build_tag , pri ,
618
674
)
619
675
620
676
def get_best_candidate (
@@ -1049,21 +1105,23 @@ def find_all_candidates(self, project_name):
1049
1105
# This is an intentional priority ordering
1050
1106
return file_versions + find_links_versions + page_versions
1051
1107
1052
- def make_candidate_evaluator (self ):
1053
- # type: (... ) -> CandidateEvaluator
1108
+ def make_candidate_evaluator (self , hashes = None ):
1109
+ # type: (Optional[Hashes] ) -> CandidateEvaluator
1054
1110
"""Create a CandidateEvaluator object to use.
1055
1111
"""
1056
1112
candidate_prefs = self ._candidate_prefs
1057
1113
return CandidateEvaluator .create (
1058
1114
target_python = self ._target_python ,
1059
1115
prefer_binary = candidate_prefs .prefer_binary ,
1060
1116
allow_all_prereleases = candidate_prefs .allow_all_prereleases ,
1117
+ hashes = hashes ,
1061
1118
)
1062
1119
1063
1120
def find_candidates (
1064
1121
self ,
1065
1122
project_name , # type: str
1066
1123
specifier = None , # type: Optional[specifiers.BaseSpecifier]
1124
+ hashes = None , # type: Optional[Hashes]
1067
1125
):
1068
1126
# type: (...) -> FoundCandidates
1069
1127
"""Find matches for the given project and specifier.
@@ -1075,7 +1133,7 @@ def find_candidates(
1075
1133
:return: A `FoundCandidates` instance.
1076
1134
"""
1077
1135
candidates = self .find_all_candidates (project_name )
1078
- candidate_evaluator = self .make_candidate_evaluator ()
1136
+ candidate_evaluator = self .make_candidate_evaluator (hashes = hashes )
1079
1137
return candidate_evaluator .make_found_candidates (
1080
1138
candidates , specifier = specifier ,
1081
1139
)
@@ -1088,7 +1146,10 @@ def find_requirement(self, req, upgrade):
1088
1146
Returns a Link if found,
1089
1147
Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise
1090
1148
"""
1091
- candidates = self .find_candidates (req .name , req .specifier )
1149
+ hashes = req .hashes (trust_internet = False )
1150
+ candidates = self .find_candidates (
1151
+ req .name , specifier = req .specifier , hashes = hashes ,
1152
+ )
1092
1153
best_candidate = candidates .get_best ()
1093
1154
1094
1155
installed_version = None # type: Optional[_BaseVersion]
0 commit comments