1
+ import collections
2
+ import math
1
3
from typing import TYPE_CHECKING , Dict , Iterable , Iterator , Mapping , Sequence , Union
2
4
3
5
from pip ._vendor .resolvelib .providers import AbstractProvider
@@ -60,6 +62,7 @@ def __init__(
60
62
self ._ignore_dependencies = ignore_dependencies
61
63
self ._upgrade_strategy = upgrade_strategy
62
64
self ._user_requested = user_requested
65
+ self ._known_depths : Dict [str , float ] = collections .defaultdict (lambda : math .inf )
63
66
64
67
def identify (self , requirement_or_candidate ):
65
68
# type: (Union[Requirement, Candidate]) -> str
@@ -79,48 +82,43 @@ def get_preference(
79
82
80
83
Currently pip considers the followings in order:
81
84
82
- * Prefer if any of the known requirements points to an explicit URL.
83
- * If equal, prefer if any requirements contain ``===`` and ``==``.
84
- * If equal, prefer if requirements include version constraints, e.g.
85
- ``>=`` and ``<``.
86
- * If equal, prefer user-specified (non-transitive) requirements, and
87
- order user-specified requirements by the order they are specified.
85
+ * Prefer if any of the known requirements is "direct", e.g. points to an
86
+ explicit URL.
87
+ * If equal, prefer if any requirement is "pinned", i.e. contains
88
+ operator ``===`` or ``==``.
89
+ * If equal, calculate an approximate "depth" and resolve requirements
90
+ closer to the user-specified requirements first.
91
+ * Order user-specified requirements by the order they are specified.
92
+ * If equal, prefers "non-free" requirements, i.e. contains at least one
93
+ operator, such as ``>=`` or ``<``.
88
94
* If equal, order alphabetically for consistency (helps debuggability).
89
95
"""
90
-
91
- def _get_restrictive_rating (requirements ):
92
- # type: (Iterable[Requirement]) -> int
93
- """Rate how restrictive a set of requirements are.
94
-
95
- ``Requirement.get_candidate_lookup()`` returns a 2-tuple for
96
- lookup. The first element is ``Optional[Candidate]`` and the
97
- second ``Optional[InstallRequirement]``.
98
-
99
- * If the requirement is an explicit one, the explicitly-required
100
- candidate is returned as the first element.
101
- * If the requirement is based on a PEP 508 specifier, the backing
102
- ``InstallRequirement`` is returned as the second element.
103
-
104
- We use the first element to check whether there is an explicit
105
- requirement, and the second for equality operator.
106
- """
107
- lookups = (r .get_candidate_lookup () for r in requirements )
108
- cands , ireqs = zip (* lookups )
109
- if any (cand is not None for cand in cands ):
110
- return 0
111
- spec_sets = (ireq .specifier for ireq in ireqs if ireq )
112
- operators = [
113
- specifier .operator for spec_set in spec_sets for specifier in spec_set
114
- ]
115
- if any (op in ("==" , "===" ) for op in operators ):
116
- return 1
117
- if operators :
118
- return 2
119
- # A "bare" requirement without any version requirements.
120
- return 3
121
-
122
- rating = _get_restrictive_rating (r for r , _ in information [identifier ])
123
- order = self ._user_requested .get (identifier , float ("inf" ))
96
+ lookups = (r .get_candidate_lookup () for r , _ in information [identifier ])
97
+ candidate , ireqs = zip (* lookups )
98
+ operators = [
99
+ specifier .operator
100
+ for specifier_set in (ireq .specifier for ireq in ireqs if ireq )
101
+ for specifier in specifier_set
102
+ ]
103
+
104
+ direct = candidate is not None
105
+ pinned = any (op [:2 ] == "==" for op in operators )
106
+ unfree = bool (operators )
107
+
108
+ try :
109
+ requested_order : Union [int , float ] = self ._user_requested [identifier ]
110
+ except KeyError :
111
+ requested_order = math .inf
112
+ parent_depths = (
113
+ self ._known_depths [parent .name ] if parent is not None else 0.0
114
+ for _ , parent in information [identifier ]
115
+ )
116
+ inferred_depth = min (d for d in parent_depths ) + 1.0
117
+ self ._known_depths [identifier ] = inferred_depth
118
+ else :
119
+ inferred_depth = 1.0
120
+
121
+ requested_order = self ._user_requested .get (identifier , math .inf )
124
122
125
123
# Requires-Python has only one candidate and the check is basically
126
124
# free, so we always do it first to avoid needless work if it fails.
@@ -136,7 +134,16 @@ def _get_restrictive_rating(requirements):
136
134
# while we work on "proper" branch pruning techniques.
137
135
delay_this = identifier == "setuptools"
138
136
139
- return (not requires_python , delay_this , rating , order , identifier )
137
+ return (
138
+ not requires_python ,
139
+ delay_this ,
140
+ not direct ,
141
+ not pinned ,
142
+ inferred_depth ,
143
+ requested_order ,
144
+ not unfree ,
145
+ identifier ,
146
+ )
140
147
141
148
def find_matches (
142
149
self ,
0 commit comments