6
6
7
7
from pip ._internal .models .link import Link
8
8
from pip ._internal .req .req_install import InstallRequirement
9
+ from pip ._internal .utils .direct_url_helpers import (
10
+ direct_url_from_link ,
11
+ dist_get_direct_url ,
12
+ )
9
13
from pip ._internal .utils .hashes import Hashes
10
14
11
15
CandidateLookup = Tuple [Optional ["Candidate" ], Optional [InstallRequirement ]]
@@ -20,24 +24,26 @@ def format_name(project, extras):
20
24
21
25
22
26
class Constraint :
23
- def __init__ (self , specifier , hashes ):
24
- # type: (SpecifierSet, Hashes) -> None
27
+ def __init__ (self , specifier , hashes , links ):
28
+ # type: (SpecifierSet, Hashes, Tuple[Link, ...] ) -> None
25
29
self .specifier = specifier
26
30
self .hashes = hashes
31
+ self .links = links
27
32
28
33
@classmethod
29
34
def empty (cls ):
30
35
# type: () -> Constraint
31
- return Constraint (SpecifierSet (), Hashes ())
36
+ return Constraint (SpecifierSet (), Hashes (), () )
32
37
33
38
@classmethod
34
39
def from_ireq (cls , ireq ):
35
40
# type: (InstallRequirement) -> Constraint
36
- return Constraint (ireq .specifier , ireq .hashes (trust_internet = False ))
41
+ links = (ireq .link ,) if ireq .link else ()
42
+ return Constraint (ireq .specifier , ireq .hashes (trust_internet = False ), links )
37
43
38
44
def __nonzero__ (self ):
39
45
# type: () -> bool
40
- return bool (self .specifier ) or bool (self .hashes )
46
+ return bool (self .specifier ) or bool (self .hashes ) or bool ( self . links )
41
47
42
48
def __bool__ (self ):
43
49
# type: () -> bool
@@ -49,14 +55,19 @@ def __and__(self, other):
49
55
return NotImplemented
50
56
specifier = self .specifier & other .specifier
51
57
hashes = self .hashes & other .hashes (trust_internet = False )
52
- return Constraint (specifier , hashes )
58
+ links = self .links
59
+ if other .link and other .link not in links :
60
+ links += (other .link ,)
61
+ return Constraint (specifier , hashes , links )
53
62
54
63
def is_satisfied_by (self , candidate ):
55
64
# type: (Candidate) -> bool
56
65
# We can safely always allow prereleases here since PackageFinder
57
66
# already implements the prerelease logic, and would have filtered out
58
67
# prerelease candidates if the user does not expect them.
59
- return self .specifier .contains (candidate .version , prereleases = True )
68
+ return self .specifier .contains (candidate .version , prereleases = True ) and all (
69
+ _match_link (link , candidate ) for link in self .links
70
+ )
60
71
61
72
62
73
class Requirement :
@@ -94,6 +105,21 @@ def format_for_error(self):
94
105
raise NotImplementedError ("Subclass should override" )
95
106
96
107
108
+ def _match_link (link , candidate ):
109
+ # type: (Link, Candidate) -> bool
110
+ if candidate .source_link :
111
+ return link == candidate .source_link
112
+ # The purpose of this is to support AlreadyInstalledCandidates.
113
+ # It doesn't handle ExtrasCandidates, which *shouldn't* be necessary.
114
+ dist = getattr (candidate , "dist" , None )
115
+ if dist :
116
+ dist_url = dist_get_direct_url (dist )
117
+ if dist_url :
118
+ # This is missing some optional arguments that *hopefully* aren't needed.
119
+ return direct_url_from_link (link ).to_dict () == dist_url .to_dict ()
120
+ return False
121
+
122
+
97
123
class Candidate :
98
124
@property
99
125
def project_name (self ):
0 commit comments