@@ -56,9 +56,58 @@ def get_preference(
5656 information # type: Sequence[Tuple[Requirement, Candidate]]
5757 ):
5858 # 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 )
59108 transitive = all (parent is not None for _ , parent in information )
60109 key = next (iter (candidates )).name if candidates else ""
61- return (transitive , key )
110+ return (restrictive , transitive , key )
62111
63112 def find_matches (self , requirements ):
64113 # type: (Sequence[Requirement]) -> Iterable[Candidate]
0 commit comments