1
+ from typing import Any
2
+
1
3
from pip ._vendor .packaging .specifiers import SpecifierSet
2
4
from pip ._vendor .packaging .utils import NormalizedName , canonicalize_name
3
5
4
6
from pip ._internal .req .constructors import install_req_drop_extras
5
7
from pip ._internal .req .req_install import InstallRequirement
8
+ from pip ._internal .utils .models import KeyBasedCompareMixin
6
9
7
10
from .base import Candidate , CandidateLookup , Requirement , format_name
8
11
@@ -17,6 +20,14 @@ def __str__(self) -> str:
17
20
def __repr__ (self ) -> str :
18
21
return f"{ self .__class__ .__name__ } ({ self .candidate !r} )"
19
22
23
+ def __hash__ (self ) -> int :
24
+ return hash (self .candidate )
25
+
26
+ def __eq__ (self , other : Any ) -> bool :
27
+ if not isinstance (other , ExplicitRequirement ):
28
+ return False
29
+ return self .candidate == other .candidate
30
+
20
31
@property
21
32
def project_name (self ) -> NormalizedName :
22
33
# No need to canonicalize - the candidate did this
@@ -37,11 +48,14 @@ def is_satisfied_by(self, candidate: Candidate) -> bool:
37
48
return candidate == self .candidate
38
49
39
50
40
- class SpecifierRequirement (Requirement ):
51
+ class SpecifierRequirement (Requirement , KeyBasedCompareMixin ):
41
52
def __init__ (self , ireq : InstallRequirement ) -> None :
42
53
assert ireq .link is None , "This is a link, not a specifier"
43
54
self ._ireq = ireq
44
55
self ._extras = frozenset (canonicalize_name (e ) for e in self ._ireq .extras )
56
+ KeyBasedCompareMixin .__init__ (
57
+ self , key = str (ireq ), defining_class = SpecifierRequirement
58
+ )
45
59
46
60
def __str__ (self ) -> str :
47
61
return str (self ._ireq .req )
@@ -97,13 +111,17 @@ def __init__(self, ireq: InstallRequirement) -> None:
97
111
assert ireq .link is None , "This is a link, not a specifier"
98
112
self ._ireq = install_req_drop_extras (ireq )
99
113
self ._extras = frozenset (canonicalize_name (e ) for e in self ._ireq .extras )
114
+ KeyBasedCompareMixin .__init__ (
115
+ self , key = str (ireq ), defining_class = SpecifierRequirement
116
+ )
100
117
101
118
102
119
class RequiresPythonRequirement (Requirement ):
103
120
"""A requirement representing Requires-Python metadata."""
104
121
105
122
def __init__ (self , specifier : SpecifierSet , match : Candidate ) -> None :
106
123
self .specifier = specifier
124
+ self ._specifier_string = str (specifier ) # for faster __eq__
107
125
self ._candidate = match
108
126
109
127
def __str__ (self ) -> str :
@@ -112,6 +130,17 @@ def __str__(self) -> str:
112
130
def __repr__ (self ) -> str :
113
131
return f"{ self .__class__ .__name__ } ({ str (self .specifier )!r} )"
114
132
133
+ def __hash__ (self ) -> int :
134
+ return hash ((self ._specifier_string , self ._candidate ))
135
+
136
+ def __eq__ (self , other : Any ) -> bool :
137
+ if not isinstance (other , RequiresPythonRequirement ):
138
+ return False
139
+ return (
140
+ self ._specifier_string == other ._specifier_string
141
+ and self ._candidate == other ._candidate
142
+ )
143
+
115
144
@property
116
145
def project_name (self ) -> NormalizedName :
117
146
return self ._candidate .project_name
@@ -136,11 +165,14 @@ def is_satisfied_by(self, candidate: Candidate) -> bool:
136
165
return self .specifier .contains (candidate .version , prereleases = True )
137
166
138
167
139
- class UnsatisfiableRequirement (Requirement ):
168
+ class UnsatisfiableRequirement (Requirement , KeyBasedCompareMixin ):
140
169
"""A requirement that cannot be satisfied."""
141
170
142
171
def __init__ (self , name : NormalizedName ) -> None :
143
172
self ._name = name
173
+ KeyBasedCompareMixin .__init__ (
174
+ self , key = str (name ), defining_class = UnsatisfiableRequirement
175
+ )
144
176
145
177
def __str__ (self ) -> str :
146
178
return f"{ self ._name } (unavailable)"
0 commit comments