mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
Merge pull request #7960 from uranusjr/requires-python-2
Requires-Python implementation, take 2
This commit is contained in:
commit
6086f71cde
|
@ -1,21 +1,26 @@
|
|||
import logging
|
||||
import sys
|
||||
|
||||
from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
from pip._internal.req.constructors import install_req_from_line
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.misc import normalize_version_info
|
||||
from pip._internal.utils.packaging import get_requires_python
|
||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
from .base import Candidate, format_name
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any, Optional, Sequence, Set
|
||||
|
||||
from pip._internal.models.link import Link
|
||||
from typing import Any, Optional, Sequence, Set, Tuple
|
||||
|
||||
from pip._vendor.packaging.version import _BaseVersion
|
||||
from pip._vendor.pkg_resources import Distribution
|
||||
|
||||
from pip._internal.models.link import Link
|
||||
|
||||
from .base import Requirement
|
||||
from .factory import Factory
|
||||
|
||||
|
@ -95,12 +100,32 @@ class LinkCandidate(Candidate):
|
|||
self._version == self.dist.parsed_version)
|
||||
return self._dist
|
||||
|
||||
def _get_requires_python_specifier(self):
|
||||
# type: () -> Optional[SpecifierSet]
|
||||
requires_python = get_requires_python(self.dist)
|
||||
if requires_python is None:
|
||||
return None
|
||||
try:
|
||||
spec = SpecifierSet(requires_python)
|
||||
except InvalidSpecifier as e:
|
||||
logger.warning(
|
||||
"Package %r has an invalid Requires-Python: %s", self.name, e,
|
||||
)
|
||||
return None
|
||||
return spec
|
||||
|
||||
def get_dependencies(self):
|
||||
# type: () -> Sequence[Requirement]
|
||||
return [
|
||||
deps = [
|
||||
self._factory.make_requirement_from_spec(str(r), self._ireq)
|
||||
for r in self.dist.requires()
|
||||
]
|
||||
python_dep = self._factory.make_requires_python_requirement(
|
||||
self._get_requires_python_specifier(),
|
||||
)
|
||||
if python_dep:
|
||||
deps.append(python_dep)
|
||||
return deps
|
||||
|
||||
def get_install_requirement(self):
|
||||
# type: () -> Optional[InstallRequirement]
|
||||
|
@ -179,3 +204,32 @@ class ExtrasCandidate(LinkCandidate):
|
|||
# depend on the base candidate, and we'll get the
|
||||
# install requirement from that.
|
||||
return None
|
||||
|
||||
|
||||
class RequiresPythonCandidate(Candidate):
|
||||
def __init__(self, py_version_info):
|
||||
# type: (Optional[Tuple[int, ...]]) -> None
|
||||
if py_version_info is not None:
|
||||
version_info = normalize_version_info(py_version_info)
|
||||
else:
|
||||
version_info = sys.version_info[:3]
|
||||
self._version = Version(".".join(str(c) for c in version_info))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
# type: () -> str
|
||||
# Avoid conflicting with the PyPI package "Python".
|
||||
return "<Python fom Requires-Python>"
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
# type: () -> _BaseVersion
|
||||
return self._version
|
||||
|
||||
def get_dependencies(self):
|
||||
# type: () -> Sequence[Requirement]
|
||||
return []
|
||||
|
||||
def get_install_requirement(self):
|
||||
# type: () -> Optional[InstallRequirement]
|
||||
return None
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
from .candidates import ExtrasCandidate, LinkCandidate
|
||||
from .requirements import ExplicitRequirement, SpecifierRequirement
|
||||
from .candidates import ExtrasCandidate, LinkCandidate, RequiresPythonCandidate
|
||||
from .requirements import (
|
||||
ExplicitRequirement,
|
||||
NoMatchRequirement,
|
||||
SpecifierRequirement,
|
||||
)
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Dict, Set
|
||||
from typing import Dict, Optional, Set, Tuple
|
||||
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.models.link import Link
|
||||
|
@ -21,10 +27,14 @@ class Factory(object):
|
|||
finder, # type: PackageFinder
|
||||
preparer, # type: RequirementPreparer
|
||||
make_install_req, # type: InstallRequirementProvider
|
||||
ignore_requires_python, # type: bool
|
||||
py_version_info=None, # type: Optional[Tuple[int, ...]]
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.finder = finder
|
||||
self.preparer = preparer
|
||||
self._python_candidate = RequiresPythonCandidate(py_version_info)
|
||||
self._ignore_requires_python = ignore_requires_python
|
||||
self._make_install_req_from_spec = make_install_req
|
||||
self._candidate_cache = {} # type: Dict[Link, LinkCandidate]
|
||||
|
||||
|
@ -56,3 +66,16 @@ class Factory(object):
|
|||
# type: (str, InstallRequirement) -> Requirement
|
||||
ireq = self._make_install_req_from_spec(specifier, comes_from)
|
||||
return self.make_requirement_from_install_req(ireq)
|
||||
|
||||
def make_requires_python_requirement(self, specifier):
|
||||
# type: (Optional[SpecifierSet]) -> Optional[Requirement]
|
||||
if self._ignore_requires_python or specifier is None:
|
||||
return None
|
||||
# The logic here is different from SpecifierRequirement, for which we
|
||||
# "find" candidates matching the specifier. But for Requires-Python,
|
||||
# there is always exactly one candidate (the one specified with
|
||||
# py_version_info). Here we decide whether to return that based on
|
||||
# whether Requires-Python matches that one candidate or not.
|
||||
if self._python_candidate.version in specifier:
|
||||
return ExplicitRequirement(self._python_candidate)
|
||||
return NoMatchRequirement(self._python_candidate.name)
|
||||
|
|
|
@ -33,6 +33,30 @@ class ExplicitRequirement(Requirement):
|
|||
return candidate == self.candidate
|
||||
|
||||
|
||||
class NoMatchRequirement(Requirement):
|
||||
"""A requirement that never matches anything.
|
||||
|
||||
Note: Similar to ExplicitRequirement, the caller should handle name
|
||||
canonicalisation; this class does not perform it.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
# type: (str) -> None
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
# type: () -> str
|
||||
return self._name
|
||||
|
||||
def find_matches(self):
|
||||
# type: () -> Sequence[Candidate]
|
||||
return []
|
||||
|
||||
def is_satisfied_by(self, candidate):
|
||||
# type: (Candidate) -> bool
|
||||
return False
|
||||
|
||||
|
||||
class SpecifierRequirement(Requirement):
|
||||
def __init__(self, ireq, factory):
|
||||
# type: (InstallRequirement, Factory) -> None
|
||||
|
|
|
@ -43,6 +43,8 @@ class Resolver(BaseResolver):
|
|||
finder=finder,
|
||||
preparer=preparer,
|
||||
make_install_req=make_install_req,
|
||||
ignore_requires_python=ignore_requires_python,
|
||||
py_version_info=py_version_info,
|
||||
)
|
||||
self.ignore_dependencies = ignore_dependencies
|
||||
self._result = None # type: Optional[Result]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.lib import create_basic_wheel_for_package
|
||||
|
||||
|
||||
|
@ -137,3 +139,55 @@ def test_new_resolver_installs_extras(script):
|
|||
assert "WARNING: Invalid extras specified" in result.stderr, str(result)
|
||||
assert ": missing" in result.stderr, str(result)
|
||||
assert_installed(script, base="0.1.0", dep="0.1.0")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"requires_python, ignore_requires_python, dep_version",
|
||||
[
|
||||
# Something impossible to satisfy.
|
||||
("<2", False, "0.1.0"),
|
||||
("<2", True, "0.2.0"),
|
||||
|
||||
# Something guaranteed to satisfy.
|
||||
(">=2", False, "0.2.0"),
|
||||
(">=2", True, "0.2.0"),
|
||||
],
|
||||
)
|
||||
def test_new_resolver_requires_python(
|
||||
script,
|
||||
requires_python,
|
||||
ignore_requires_python,
|
||||
dep_version,
|
||||
):
|
||||
create_basic_wheel_for_package(
|
||||
script,
|
||||
"base",
|
||||
"0.1.0",
|
||||
depends=["dep"],
|
||||
)
|
||||
create_basic_wheel_for_package(
|
||||
script,
|
||||
"dep",
|
||||
"0.1.0",
|
||||
)
|
||||
create_basic_wheel_for_package(
|
||||
script,
|
||||
"dep",
|
||||
"0.2.0",
|
||||
requires_python=requires_python,
|
||||
)
|
||||
|
||||
args = [
|
||||
"install",
|
||||
"--unstable-feature=resolver",
|
||||
"--no-cache-dir",
|
||||
"--no-index",
|
||||
"--find-links", script.scratch_path,
|
||||
]
|
||||
if ignore_requires_python:
|
||||
args.append("--ignore-requires-python")
|
||||
args.append("base")
|
||||
|
||||
script.pip(*args)
|
||||
|
||||
assert_installed(script, base="0.1.0", dep=dep_version)
|
||||
|
|
|
@ -979,7 +979,13 @@ def create_really_basic_wheel(name, version):
|
|||
|
||||
|
||||
def create_basic_wheel_for_package(
|
||||
script, name, version, depends=None, extras=None, extra_files=None
|
||||
script,
|
||||
name,
|
||||
version,
|
||||
depends=None,
|
||||
extras=None,
|
||||
requires_python=None,
|
||||
extra_files=None,
|
||||
):
|
||||
if depends is None:
|
||||
depends = []
|
||||
|
@ -1007,14 +1013,18 @@ def create_basic_wheel_for_package(
|
|||
for package in packages
|
||||
]
|
||||
|
||||
metadata_updates = {
|
||||
"Provides-Extra": list(extras),
|
||||
"Requires-Dist": requires_dist,
|
||||
}
|
||||
if requires_python is not None:
|
||||
metadata_updates["Requires-Python"] = requires_python
|
||||
|
||||
wheel_builder = make_wheel(
|
||||
name=name,
|
||||
version=version,
|
||||
wheel_metadata_updates={"Tag": ["py2-none-any", "py3-none-any"]},
|
||||
metadata_updates={
|
||||
"Provides-Extra": list(extras),
|
||||
"Requires-Dist": requires_dist,
|
||||
},
|
||||
metadata_updates=metadata_updates,
|
||||
extra_metadata_files={"top_level.txt": name},
|
||||
extra_files=extra_files,
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@ def factory(finder, preparer):
|
|||
finder=finder,
|
||||
preparer=preparer,
|
||||
make_install_req=install_req_from_line,
|
||||
ignore_requires_python=False,
|
||||
py_version_info=None,
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue