From 4ad924a66f87002195e6815a3697c3316afc02d1 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Thu, 3 Dec 2020 16:39:11 +0800 Subject: Resolve direct and pinned requirements first --- news/9185.feature.rst | 2 + .../_internal/resolution/resolvelib/provider.py | 46 +++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 news/9185.feature.rst 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..b1df3ca52 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -56,9 +56,53 @@ 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 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 + return 2 + + 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] -- cgit v1.2.1 From 82fe333c09c11b508389d9c925fec92f0c7e1ed0 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Thu, 3 Dec 2020 18:25:17 +0800 Subject: Also prefer requirements with non-empty specifiers --- src/pip/_internal/resolution/resolvelib/provider.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index b1df3ca52..c91f252f7 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -64,7 +64,9 @@ class PipProvider(AbstractProvider): 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 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). """ @@ -90,14 +92,17 @@ class PipProvider(AbstractProvider): if any(cand is not None for cand in cands): return 0 spec_sets = (ireq.specifier for ireq in ireqs if ireq) - operators = ( + operators = [ specifier.operator for spec_set in spec_sets for specifier in spec_set - ) + ] if any(op in ("==", "===") for op in operators): return 1 - return 2 + 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) -- cgit v1.2.1