diff --git a/news/11142.bugfix.rst b/news/11142.bugfix.rst new file mode 100644 index 00000000000..9828b666daf --- /dev/null +++ b/news/11142.bugfix.rst @@ -0,0 +1,3 @@ +Fix a regression that causes dependencies to be checked *before* ``Requires-Python`` +project metadata is checked, leading to wasted cycles when the Python version is +unsupported. diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 6617644fe53..4ecc2059c2d 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -249,10 +249,10 @@ def _prepare(self) -> BaseDistribution: return dist def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: + yield self._factory.make_requires_python_requirement(self.dist.requires_python) requires = self.dist.iter_dependencies() if with_requires else () for r in requires: yield from self._factory.make_requirements_from_spec(str(r), self._ireq) - yield self._factory.make_requires_python_requirement(self.dist.requires_python) def get_install_requirement(self) -> Optional[InstallRequirement]: return self._ireq diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index fb0dd85f112..93df4fd01af 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -242,9 +242,9 @@ def _eligible_for_upgrade(identifier: str) -> bool: def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> bool: return requirement.is_satisfied_by(candidate) - def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]: + def get_dependencies(self, candidate: Candidate) -> Iterable[Requirement]: with_requires = not self._ignore_dependencies - return [r for r in candidate.iter_dependencies(with_requires) if r is not None] + return (r for r in candidate.iter_dependencies(with_requires) if r is not None) @staticmethod def is_backtrack_cause( diff --git a/tests/functional/test_new_resolver_errors.py b/tests/functional/test_new_resolver_errors.py index 5976de52e39..f1983a5dc58 100644 --- a/tests/functional/test_new_resolver_errors.py +++ b/tests/functional/test_new_resolver_errors.py @@ -101,32 +101,33 @@ def test_new_resolver_checks_requires_python_before_dependencies( ) -> None: incompatible_python = f"<{sys.version_info.major}.{sys.version_info.minor}" - pkg_dep = create_basic_wheel_for_package( + pkgdep = create_basic_wheel_for_package( script, - name="pkg-dep", + name="pkgdep", version="1", ) create_basic_wheel_for_package( script, - name="pkg-root", + name="pkgroot", version="1", - # Refer the dependency by URL to prioritise it as much as possible, - # to test that Requires-Python is *still* inspected first. - depends=[f"pkg-dep@{pathlib.Path(pkg_dep).as_uri()}"], + depends=[ + f"pkgdep@{pathlib.Path(pkgdep).as_uri()}", + ], requires_python=incompatible_python, ) - result = script.pip( + r = script.pip( "install", "--no-cache-dir", "--no-index", "--find-links", script.scratch_path, - "pkg-root", + "pkgroot", expect_error=True, ) - # Resolution should fail because of pkg-a's Requires-Python. - # This check should be done before pkg-b, so pkg-b should never be pulled. - assert incompatible_python in result.stderr, str(result) - assert "pkg-b" not in result.stderr, str(result) + # Resolution should fail because of pkgroot's Requires-Python. + # This is done before dependencies so pkgdep should never be pulled. + assert incompatible_python in r.stderr, str(r) + assert "pkgdep" not in r.stderr, str(r) + assert "pkgdep" not in r.stdout, str(r)