@@ -56,9 +56,58 @@ def get_preference(
56
56
information # type: Sequence[Tuple[Requirement, Candidate]]
57
57
):
58
58
# type: (...) -> Any
59
+ """Produce a sort key for given requirement based on preference.
60
+
61
+ The lower the return value is, the more preferred this group of
62
+ arguments is.
63
+
64
+ Currently pip considers the followings in order:
65
+
66
+ * Prefer if any of the known requirements points to an explicit URL.
67
+ * If equal, prefer if any requirements contain ``===`` and ``==``.
68
+ * If equal, prefer if requirements include version constraints, e.g.
69
+ ``>=`` and ``<``.
70
+ * If equal, prefer user-specified (non-transitive) requirements.
71
+ * If equal, order alphabetically for consistency (helps debuggability).
72
+ """
73
+
74
+ def _get_restrictive_rating (requirements ):
75
+ # type: (Iterable[Requirement]) -> int
76
+ """Rate how restrictive a set of requirements are.
77
+
78
+ ``Requirement.get_candidate_lookup()`` returns a 2-tuple for
79
+ lookup. The first element is ``Optional[Candidate]`` and the
80
+ second ``Optional[InstallRequirement]``.
81
+
82
+ * If the requirement is an explicit one, the explicitly-required
83
+ candidate is returned as the first element.
84
+ * If the requirement is based on a PEP 508 specifier, the backing
85
+ ``InstallRequirement`` is returned as the second element.
86
+
87
+ We use the first element to check whether there is an explicit
88
+ requirement, and the second for equality operator.
89
+ """
90
+ lookups = (r .get_candidate_lookup () for r in requirements )
91
+ cands , ireqs = zip (* lookups )
92
+ if any (cand is not None for cand in cands ):
93
+ return 0
94
+ spec_sets = (ireq .specifier for ireq in ireqs if ireq )
95
+ operators = [
96
+ specifier .operator
97
+ for spec_set in spec_sets
98
+ for specifier in spec_set
99
+ ]
100
+ if any (op in ("==" , "===" ) for op in operators ):
101
+ return 1
102
+ if operators :
103
+ return 2
104
+ # A "bare" requirement without any version requirements.
105
+ return 3
106
+
107
+ restrictive = _get_restrictive_rating (req for req , _ in information )
59
108
transitive = all (parent is not None for _ , parent in information )
60
109
key = next (iter (candidates )).name if candidates else ""
61
- return (transitive , key )
110
+ return (restrictive , transitive , key )
62
111
63
112
def find_matches (self , requirements ):
64
113
# type: (Sequence[Requirement]) -> Iterable[Candidate]
0 commit comments