Merge pull request #9994 from uranusjr/requires-python-before-other-deps

Check Requires-Python before other dependencies
This commit is contained in:
Tzu-ping Chung 2021-06-15 19:49:13 +08:00 committed by GitHub
commit 7c3abccd22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 46 additions and 4 deletions

3
news/9925.feature.rst Normal file
View File

@ -0,0 +1,3 @@
New resolver: A distribution's ``Requires-Python`` metadata is now checked
before its Python dependencies. This makes the resolver fail quicker when
there's an interpreter version conflict.

View File

@ -32,6 +32,9 @@ BaseCandidate = Union[
"LinkCandidate",
]
# Avoid conflicting with the PyPI package "Python".
REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "<Python from Requires-Python>")
def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
"""The runtime version of BaseCandidate."""
@ -578,13 +581,12 @@ class RequiresPythonCandidate(Candidate):
@property
def project_name(self):
# type: () -> NormalizedName
# Avoid conflicting with the PyPI package "Python".
return cast(NormalizedName, "<Python from Requires-Python>")
return REQUIRES_PYTHON_IDENTIFIER
@property
def name(self):
# type: () -> str
return self.project_name
return REQUIRES_PYTHON_IDENTIFIER
@property
def version(self):

View File

@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Dict, Iterable, Iterator, Mapping, Sequence, U
from pip._vendor.resolvelib.providers import AbstractProvider
from .base import Candidate, Constraint, Requirement
from .candidates import REQUIRES_PYTHON_IDENTIFIER
from .factory import Factory
if TYPE_CHECKING:
@ -121,6 +122,10 @@ class PipProvider(_ProviderBase):
rating = _get_restrictive_rating(r for r, _ in information[identifier])
order = self._user_requested.get(identifier, float("inf"))
# Requires-Python has only one candidate and the check is basically
# free, so we always do it first to avoid needless work if it fails.
requires_python = identifier == REQUIRES_PYTHON_IDENTIFIER
# HACK: Setuptools have a very long and solid backward compatibility
# track record, and extremely few projects would request a narrow,
# non-recent version range of it since that would break a lot things.
@ -131,7 +136,7 @@ class PipProvider(_ProviderBase):
# while we work on "proper" branch pruning techniques.
delay_this = identifier == "setuptools"
return (delay_this, rating, order, identifier)
return (not requires_python, delay_this, rating, order, identifier)
def find_matches(
self,

View File

@ -1,3 +1,4 @@
import pathlib
import sys
from tests.lib import create_basic_wheel_for_package, create_test_package_with_setup
@ -73,3 +74,34 @@ def test_new_resolver_requires_python_error(script):
# conflict, not the compatible one.
assert incompatible_python in result.stderr, str(result)
assert compatible_python not in result.stderr, str(result)
def test_new_resolver_checks_requires_python_before_dependencies(script):
incompatible_python = "<{0.major}.{0.minor}".format(sys.version_info)
pkg_dep = create_basic_wheel_for_package(
script,
name="pkg-dep",
version="1",
)
create_basic_wheel_for_package(
script,
name="pkg-root",
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()}"],
requires_python=incompatible_python,
)
result = script.pip(
"install", "--no-cache-dir",
"--no-index", "--find-links", script.scratch_path,
"pkg-root",
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)