Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions news/13270.bugfix.rst
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 3 additions & 1 deletion src/pip/_internal/resolution/resolvelib/candidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,12 @@ def _prepare(self) -> BaseDistribution:
return dist

def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
# Emit the Requires-Python requirement first to fail fast on
# unsupported candidates and avoid pointless downloads/preparation.
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
Expand Down
5 changes: 3 additions & 2 deletions src/pip/_internal/resolution/resolvelib/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ 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]
# iter_dependencies() can perform nontrivial work so delay until needed.
return (r for r in candidate.iter_dependencies(with_requires) if r is not None)
8 changes: 5 additions & 3 deletions tests/functional/test_new_resolver_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ def test_new_resolver_checks_requires_python_before_dependencies(
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.
# Resolution should fail because of pkg-root's Requires-Python.
# This is done before dependencies so pkg-dep should never be pulled.
assert incompatible_python in result.stderr, str(result)
assert "pkg-b" not in result.stderr, str(result)
# Setuptools produces wheels with normalized names.
assert "pkg_dep" not in result.stderr, str(result)
assert "pkg_dep" not in result.stdout, str(result)