@@ -56,9 +56,53 @@ 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 user-specified (non-transitive) requirements.
69
+ * If equal, order alphabetically for consistency (helps debuggability).
70
+ """
71
+
72
+ def _get_restrictive_rating (requirements ):
73
+ # type: (Iterable[Requirement]) -> int
74
+ """Rate how restrictive a set of requirements are.
75
+
76
+ ``Requirement.get_candidate_lookup()`` returns a 2-tuple for
77
+ lookup. The first element is ``Optional[Candidate]`` and the
78
+ second ``Optional[InstallRequirement]``.
79
+
80
+ * If the requirement is an explicit one, the explicitly-required
81
+ candidate is returned as the first element.
82
+ * If the requirement is based on a PEP 508 specifier, the backing
83
+ ``InstallRequirement`` is returned as the second element.
84
+
85
+ We use the first element to check whether there is an explicit
86
+ requirement, and the second for equality operator.
87
+ """
88
+ lookups = (r .get_candidate_lookup () for r in requirements )
89
+ cands , ireqs = zip (* lookups )
90
+ if any (cand is not None for cand in cands ):
91
+ return 0
92
+ spec_sets = (ireq .specifier for ireq in ireqs if ireq )
93
+ operators = (
94
+ specifier .operator
95
+ for spec_set in spec_sets
96
+ for specifier in spec_set
97
+ )
98
+ if any (op in ("==" , "===" ) for op in operators ):
99
+ return 1
100
+ return 2
101
+
102
+ restrictive = _get_restrictive_rating (req for req , _ in information )
59
103
transitive = all (parent is not None for _ , parent in information )
60
104
key = next (iter (candidates )).name if candidates else ""
61
- return (transitive , key )
105
+ return (restrictive , transitive , key )
62
106
63
107
def find_matches (self , requirements ):
64
108
# type: (Sequence[Requirement]) -> Iterable[Candidate]
0 commit comments