Skip to content

Commit ab7ff0a

Browse files
authored
Merge pull request #9211 from uranusjr/new-resolver-pinned-first
2 parents e6da461 + 82fe333 commit ab7ff0a

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

news/9185.feature.rst

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
New resolver: Resolve direct and pinned (``==`` or ``===``) requirements first
2+
to improve resolver performance.

src/pip/_internal/resolution/resolvelib/provider.py

+50-1
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)