From a2cbacfe1cb0a8c1a149385a819e39d4c1bd6eee Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 30 Jul 2021 19:11:01 +0800 Subject: [PATCH] Respect the base's constraint for extra-ed package --- news/10233.bugfix.rst | 3 +++ .../resolution/resolvelib/provider.py | 17 ++++++++++++++++- tests/functional/test_new_resolver.py | 18 ++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 news/10233.bugfix.rst diff --git a/news/10233.bugfix.rst b/news/10233.bugfix.rst new file mode 100644 index 00000000000..8578a7dd071 --- /dev/null +++ b/news/10233.bugfix.rst @@ -0,0 +1,3 @@ +New resolver: When a package is specified with extras in constraints, and with +extras in non-constraint requirements, the resolver now correctly identifies the +constraint's existence and avoids backtracking. diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index c86fdc31fa3..632854d3bc5 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -143,6 +143,21 @@ def get_preference( identifier, ) + def _get_constraint(self, identifier: str) -> Constraint: + if identifier in self._constraints: + return self._constraints[identifier] + + # HACK: Theoratically we should check whether this identifier is a valid + # "NAME[EXTRAS]" format, and parse out the name part with packaging or + # some regular expression. But since pip's resolver only spits out + # three kinds of identifiers: normalized PEP 503 names, normalized names + # plus extras, and Requires-Python, we can cheat a bit here. + name, open_bracket, _ = identifier.partition("[") + if open_bracket and name in self._constraints: + return self._constraints[name] + + return Constraint.empty() + def find_matches( self, identifier: str, @@ -169,7 +184,7 @@ def _eligible_for_upgrade(name: str) -> bool: return self._factory.find_candidates( identifier=identifier, requirements=requirements, - constraint=self._constraints.get(identifier, Constraint.empty()), + constraint=self._get_constraint(identifier), prefers_installed=(not _eligible_for_upgrade(identifier)), incompatibilities=incompatibilities, ) diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py index 2f8b8541d7b..1dca963e1de 100644 --- a/tests/functional/test_new_resolver.py +++ b/tests/functional/test_new_resolver.py @@ -2009,3 +2009,21 @@ def test_new_resolver_file_url_normalize(script, format_dep, format_input): format_input(lib_a), lib_b, ) script.assert_installed(lib_a="1", lib_b="1") + + +def test_new_resolver_dont_backtrack_on_extra_if_base_constrained(script): + create_basic_wheel_for_package(script, "dep", "1.0") + create_basic_wheel_for_package(script, "pkg", "1.0", extras={"ext": ["dep"]}) + create_basic_wheel_for_package(script, "pkg", "2.0", extras={"ext": ["dep"]}) + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("pkg==1.0") + + result = script.pip( + "install", + "--no-cache-dir", "--no-index", + "--find-links", script.scratch_path, + "--constraint", constraints_file, + "pkg[ext]", + ) + assert "pkg-2.0" not in result.stdout, "Should not try 2.0 due to constraint" + script.assert_installed(pkg="1.0", dep="1.0")