diff --git a/news/11142.bugfix.rst b/news/11142.bugfix.rst new file mode 100644 index 00000000000..df7da86c4d8 --- /dev/null +++ b/news/11142.bugfix.rst @@ -0,0 +1,2 @@ +New resolver: Fix a regression that prevents the ``Requires-Python`` metadata +to be correctly checked before Python package dependencies. diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index f5bc343b91b..0b03eb92c80 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -1,3 +1,4 @@ +import functools import logging import sys from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast @@ -141,7 +142,6 @@ class exposes appropriate information to the resolver. found remote link (e.g. from pypi.org). """ - dist: BaseDistribution is_installed = False def __init__( @@ -159,7 +159,6 @@ def __init__( self._ireq = ireq self._name = name self._version = version - self.dist = self._prepare() def __str__(self) -> str: return f"{self.name} {self.version}" @@ -226,7 +225,10 @@ def _check_metadata_consistency(self, dist: BaseDistribution) -> None: str(dist.version), ) - def _prepare(self) -> BaseDistribution: + @functools.lru_cache(maxsize=None) + def __prepare_dist_property(self) -> BaseDistribution: + # TODO: When we drop python 3.7 support, move this to 'dist' and use + # functools.cached_property instead of lru_cache. try: dist = self._prepare_distribution() except HashError as e: @@ -243,11 +245,15 @@ def _prepare(self) -> BaseDistribution: self._check_metadata_consistency(dist) return dist + @property + def dist(self) -> BaseDistribution: + return self.__prepare_dist_property() + 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 self._factory.make_requirement_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/tests/functional/test_new_resolver_errors.py b/tests/functional/test_new_resolver_errors.py index 62304131283..4dc6682246c 100644 --- a/tests/functional/test_new_resolver_errors.py +++ b/tests/functional/test_new_resolver_errors.py @@ -101,32 +101,32 @@ def test_new_resolver_checks_requires_python_before_dependencies( ) -> None: incompatible_python = "<{0.major}.{0.minor}".format(sys.version_info) - 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 and "pkgdep" not in r.stdout, str(r)