summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPradyun Gedam <3275593+pradyunsg@users.noreply.github.com>2020-12-03 13:36:25 +0000
committerGitHub <noreply@github.com>2020-12-03 13:36:25 +0000
commitab7ff0a1b50dadfe8da1fe44ee115440b844426c (patch)
treed1a13ffff9daf46e3e2c337f96aecea61da58da6
parente6da461a1d9803763e8a9a5fcf488508d4d1d22f (diff)
parent82fe333c09c11b508389d9c925fec92f0c7e1ed0 (diff)
downloadpip-ab7ff0a1b50dadfe8da1fe44ee115440b844426c.tar.gz
Merge pull request #9211 from uranusjr/new-resolver-pinned-first
-rw-r--r--news/9185.feature.rst2
-rw-r--r--src/pip/_internal/resolution/resolvelib/provider.py51
2 files changed, 52 insertions, 1 deletions
diff --git a/news/9185.feature.rst b/news/9185.feature.rst
new file mode 100644
index 000000000..a9d9ae718
--- /dev/null
+++ b/news/9185.feature.rst
@@ -0,0 +1,2 @@
+New resolver: Resolve direct and pinned (``==`` or ``===``) requirements first
+to improve resolver performance.
diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py
index c0e6b60d9..c91f252f7 100644
--- a/src/pip/_internal/resolution/resolvelib/provider.py
+++ b/src/pip/_internal/resolution/resolvelib/provider.py
@@ -56,9 +56,58 @@ class PipProvider(AbstractProvider):
information # type: Sequence[Tuple[Requirement, Candidate]]
):
# type: (...) -> Any
+ """Produce a sort key for given requirement based on preference.
+
+ The lower the return value is, the more preferred this group of
+ arguments is.
+
+ Currently pip considers the followings in order:
+
+ * Prefer if any of the known requirements points to an explicit URL.
+ * If equal, prefer if any requirements contain ``===`` and ``==``.
+ * If equal, prefer if requirements include version constraints, e.g.
+ ``>=`` and ``<``.
+ * If equal, prefer user-specified (non-transitive) requirements.
+ * If equal, order alphabetically for consistency (helps debuggability).
+ """
+
+ def _get_restrictive_rating(requirements):
+ # type: (Iterable[Requirement]) -> int
+ """Rate how restrictive a set of requirements are.
+
+ ``Requirement.get_candidate_lookup()`` returns a 2-tuple for
+ lookup. The first element is ``Optional[Candidate]`` and the
+ second ``Optional[InstallRequirement]``.
+
+ * If the requirement is an explicit one, the explicitly-required
+ candidate is returned as the first element.
+ * If the requirement is based on a PEP 508 specifier, the backing
+ ``InstallRequirement`` is returned as the second element.
+
+ We use the first element to check whether there is an explicit
+ requirement, and the second for equality operator.
+ """
+ lookups = (r.get_candidate_lookup() for r in requirements)
+ cands, ireqs = zip(*lookups)
+ if any(cand is not None for cand in cands):
+ return 0
+ spec_sets = (ireq.specifier for ireq in ireqs if ireq)
+ operators = [
+ specifier.operator
+ for spec_set in spec_sets
+ for specifier in spec_set
+ ]
+ if any(op in ("==", "===") for op in operators):
+ return 1
+ if operators:
+ return 2
+ # A "bare" requirement without any version requirements.
+ return 3
+
+ restrictive = _get_restrictive_rating(req for req, _ in information)
transitive = all(parent is not None for _, parent in information)
key = next(iter(candidates)).name if candidates else ""
- return (transitive, key)
+ return (restrictive, transitive, key)
def find_matches(self, requirements):
# type: (Sequence[Requirement]) -> Iterable[Candidate]