@@ -59,6 +59,7 @@ def __init__(
59
59
self ._ignore_dependencies = ignore_dependencies
60
60
self ._upgrade_strategy = upgrade_strategy
61
61
self ._user_requested = user_requested
62
+ self ._known_depths = {key : 1.0 for key in user_requested }
62
63
63
64
def identify (self , requirement_or_candidate ):
64
65
# type: (Union[Requirement, Candidate]) -> str
@@ -78,47 +79,36 @@ def get_preference(
78
79
79
80
Currently pip considers the followings in order:
80
81
81
- * Prefer if any of the known requirements points to an explicit URL.
82
- * If equal, prefer if any requirements contain ``===`` and ``==``.
83
- * If equal, prefer if requirements include version constraints, e.g.
84
- ``>=`` and ``<``.
85
- * If equal, prefer user-specified (non-transitive) requirements, and
86
- order user-specified requirements by the order they are specified.
82
+ * Prefer if any of the known requirements is "direct", e.g. points to an
83
+ explicit URL.
84
+ * If equal, prefer if any requirement is "pinned", i.e. contains
85
+ operator ``===`` or ``==``.
86
+ * If equal, calculate an approximate "depth" and resolve requirements
87
+ closer to the user-specified requirements first.
88
+ * Order user-specified requirements by the order they are specified.
89
+ * If equal, prefers "non-free" requirements, i.e. contains at least one
90
+ operator, such as ``>=`` or ``<``.
87
91
* If equal, order alphabetically for consistency (helps debuggability).
88
92
"""
93
+ lookups = (r .get_candidate_lookup () for r , _ in information [identifier ])
94
+ candidate , ireqs = zip (* lookups )
95
+ operators = [
96
+ specifier .operator
97
+ for specifier_set in (ireq .specifier for ireq in ireqs if ireq )
98
+ for specifier in specifier_set
99
+ ]
100
+
101
+ direct = candidate is not None
102
+ pinned = any (op [:2 ] == "==" for op in operators )
103
+ free = bool (operators )
104
+
105
+ parent_depths = (
106
+ self ._known_depths .get (parent .name , float ("inf" )) if parent else 0.0
107
+ for _ , parent in information [identifier ]
108
+ )
109
+ inferred_depth = min (d for d in parent_depths if d is not None ) + 1.0
110
+ depth = self ._known_depths [identifier ] = inferred_depth
89
111
90
- def _get_restrictive_rating (requirements ):
91
- # type: (Iterable[Requirement]) -> int
92
- """Rate how restrictive a set of requirements are.
93
-
94
- ``Requirement.get_candidate_lookup()`` returns a 2-tuple for
95
- lookup. The first element is ``Optional[Candidate]`` and the
96
- second ``Optional[InstallRequirement]``.
97
-
98
- * If the requirement is an explicit one, the explicitly-required
99
- candidate is returned as the first element.
100
- * If the requirement is based on a PEP 508 specifier, the backing
101
- ``InstallRequirement`` is returned as the second element.
102
-
103
- We use the first element to check whether there is an explicit
104
- requirement, and the second for equality operator.
105
- """
106
- lookups = (r .get_candidate_lookup () for r in requirements )
107
- cands , ireqs = zip (* lookups )
108
- if any (cand is not None for cand in cands ):
109
- return 0
110
- spec_sets = (ireq .specifier for ireq in ireqs if ireq )
111
- operators = [
112
- specifier .operator for spec_set in spec_sets for specifier in spec_set
113
- ]
114
- if any (op in ("==" , "===" ) for op in operators ):
115
- return 1
116
- if operators :
117
- return 2
118
- # A "bare" requirement without any version requirements.
119
- return 3
120
-
121
- rating = _get_restrictive_rating (r for r , _ in information [identifier ])
122
112
order = self ._user_requested .get (identifier , float ("inf" ))
123
113
124
114
# HACK: Setuptools have a very long and solid backward compatibility
@@ -129,9 +119,9 @@ def _get_restrictive_rating(requirements):
129
119
# delaying Setuptools helps reduce branches the resolver has to check.
130
120
# This serves as a temporary fix for issues like "apache-airlfow[all]"
131
121
# while we work on "proper" branch pruning techniques.
132
- delay_this = identifier == "setuptools"
122
+ delayed = identifier == "setuptools"
133
123
134
- return (delay_this , rating , order , identifier )
124
+ return (delayed , not direct , not pinned , depth , order , free , identifier )
135
125
136
126
def find_matches (
137
127
self ,
0 commit comments