Fix pip-install to respect --ignore-requires-python.

This commit is contained in:
Chris Jerdonek 2019-05-21 11:10:59 -07:00
parent 7e142bae64
commit ad2b07898d
5 changed files with 69 additions and 19 deletions

2
news/6371.bugfix Normal file
View File

@ -0,0 +1,2 @@
Fix ``pip install`` to respect ``--ignore-requires-python`` when evaluating
links.

View File

@ -326,11 +326,15 @@ class RequirementCommand(Command):
platform=None, # type: Optional[str] platform=None, # type: Optional[str]
python_versions=None, # type: Optional[List[str]] python_versions=None, # type: Optional[List[str]]
abi=None, # type: Optional[str] abi=None, # type: Optional[str]
implementation=None # type: Optional[str] implementation=None, # type: Optional[str]
ignore_requires_python=None, # type: Optional[bool]
): ):
# type: (...) -> PackageFinder # type: (...) -> PackageFinder
""" """
Create a package finder appropriate to this requirement command. Create a package finder appropriate to this requirement command.
:param ignore_requires_python: Whether to ignore incompatible
"Requires-Python" values in links. Defaults to False.
""" """
index_urls = [options.index_url] + options.extra_index_urls index_urls = [options.index_url] + options.extra_index_urls
if options.no_index: if options.no_index:
@ -352,4 +356,5 @@ class RequirementCommand(Command):
abi=abi, abi=abi,
implementation=implementation, implementation=implementation,
prefer_binary=options.prefer_binary, prefer_binary=options.prefer_binary,
ignore_requires_python=ignore_requires_python,
) )

View File

@ -297,6 +297,7 @@ class InstallCommand(RequirementCommand):
python_versions=python_versions, python_versions=python_versions,
abi=options.abi, abi=options.abi,
implementation=options.implementation, implementation=options.implementation,
ignore_requires_python=options.ignore_requires_python,
) )
build_delete = (not (options.no_clean or options.build_dir)) build_delete = (not (options.no_clean or options.build_dir))
wheel_cache = WheelCache(options.cache_dir, options.format_control) wheel_cache = WheelCache(options.cache_dir, options.format_control)

View File

@ -256,10 +256,20 @@ def _get_html_page(link, session=None):
return None return None
def _check_link_requires_python(link, version_info): def _check_link_requires_python(
link, # type: Link
version_info, # type: Tuple[int, ...]
ignore_requires_python=False, # type: bool
):
# type: (...) -> bool
""" """
Return whether the link's Requires-Python supports the given Python Return whether the given Python version is compatible with a link's
version. "Requires-Python" value.
:param version_info: The Python version to use to check, as a 3-tuple
of ints (major-minor-micro).
:param ignore_requires_python: Whether to ignore the "Requires-Python"
value if the given Python version isn't compatible.
""" """
try: try:
support_this_python = check_requires_python( support_this_python = check_requires_python(
@ -273,11 +283,18 @@ def _check_link_requires_python(link, version_info):
else: else:
if not support_this_python: if not support_this_python:
version = '.'.join(map(str, version_info)) version = '.'.join(map(str, version_info))
if not ignore_requires_python:
logger.debug(
'Link requires a different Python (%s not in: %r): %s',
version, link.requires_python, link,
)
return False
logger.debug( logger.debug(
'Link requires a different Python (%s not in: %r): %s', 'Ignoring failed Requires-Python check (%s not in: %r) '
'for link: %s',
version, link.requires_python, link, version, link.requires_python, link,
) )
return False
return True return True
@ -295,6 +312,7 @@ class CandidateEvaluator(object):
prefer_binary=False, # type: bool prefer_binary=False, # type: bool
allow_all_prereleases=False, # type: bool allow_all_prereleases=False, # type: bool
py_version_info=None, # type: Optional[Tuple[int, ...]] py_version_info=None, # type: Optional[Tuple[int, ...]]
ignore_requires_python=None, # type: Optional[bool]
): ):
# type: (...) -> None # type: (...) -> None
""" """
@ -303,12 +321,17 @@ class CandidateEvaluator(object):
representing a major-minor-micro version, to use to check both representing a major-minor-micro version, to use to check both
the Python version embedded in the filename and the package's the Python version embedded in the filename and the package's
"Requires-Python" metadata. Defaults to `sys.version_info[:3]`. "Requires-Python" metadata. Defaults to `sys.version_info[:3]`.
:param ignore_requires_python: Whether to ignore incompatible
"Requires-Python" values in links. Defaults to False.
""" """
if py_version_info is None: if py_version_info is None:
py_version_info = sys.version_info[:3] py_version_info = sys.version_info[:3]
if ignore_requires_python is None:
ignore_requires_python = False
py_version = '.'.join(map(str, py_version_info[:2])) py_version = '.'.join(map(str, py_version_info[:2]))
self._ignore_requires_python = ignore_requires_python
self._prefer_binary = prefer_binary self._prefer_binary = prefer_binary
self._py_version = py_version self._py_version = py_version
self._py_version_info = py_version_info self._py_version_info = py_version_info
@ -383,6 +406,7 @@ class CandidateEvaluator(object):
supports_python = _check_link_requires_python( supports_python = _check_link_requires_python(
link, version_info=self._py_version_info, link, version_info=self._py_version_info,
ignore_requires_python=self._ignore_requires_python,
) )
if not supports_python: if not supports_python:
# Return None for the reason text to suppress calling # Return None for the reason text to suppress calling
@ -575,7 +599,8 @@ class PackageFinder(object):
versions=None, # type: Optional[List[str]] versions=None, # type: Optional[List[str]]
abi=None, # type: Optional[str] abi=None, # type: Optional[str]
implementation=None, # type: Optional[str] implementation=None, # type: Optional[str]
prefer_binary=False # type: bool prefer_binary=False, # type: bool
ignore_requires_python=None, # type: Optional[bool]
): ):
# type: (...) -> PackageFinder # type: (...) -> PackageFinder
"""Create a PackageFinder. """Create a PackageFinder.
@ -599,6 +624,8 @@ class PackageFinder(object):
to pep425tags.py in the get_supported() method. to pep425tags.py in the get_supported() method.
:param prefer_binary: Whether to prefer an old, but valid, binary :param prefer_binary: Whether to prefer an old, but valid, binary
dist over a new source dist. dist over a new source dist.
:param ignore_requires_python: Whether to ignore incompatible
"Requires-Python" values in links. Defaults to False.
""" """
if session is None: if session is None:
raise TypeError( raise TypeError(
@ -634,6 +661,7 @@ class PackageFinder(object):
candidate_evaluator = CandidateEvaluator( candidate_evaluator = CandidateEvaluator(
valid_tags=valid_tags, prefer_binary=prefer_binary, valid_tags=valid_tags, prefer_binary=prefer_binary,
allow_all_prereleases=allow_all_prereleases, allow_all_prereleases=allow_all_prereleases,
ignore_requires_python=ignore_requires_python,
) )
# If we don't have TLS enabled, then WARN if anyplace we're looking # If we don't have TLS enabled, then WARN if anyplace we're looking

View File

@ -34,20 +34,34 @@ def check_caplog(caplog, expected_level, expected_message):
assert record.message == expected_message assert record.message == expected_message
def test_check_link_requires_python__incompatible_python(caplog): @pytest.mark.parametrize('ignore_requires_python, expected', [
""" (None, (
Test the log message for an incompatible Python. False, 'DEBUG',
"""
link = Link('https://example.com', requires_python='== 3.6.4')
caplog.set_level(logging.DEBUG)
actual = _check_link_requires_python(link, version_info=(3, 6, 5))
assert actual == False
expected_message = (
"Link requires a different Python (3.6.5 not in: '== 3.6.4'): " "Link requires a different Python (3.6.5 not in: '== 3.6.4'): "
"https://example.com" "https://example.com"
)),
(True, (
True, 'DEBUG',
"Ignoring failed Requires-Python check (3.6.5 not in: '== 3.6.4') "
"for link: https://example.com"
)),
])
def test_check_link_requires_python__incompatible_python(
caplog, ignore_requires_python, expected,
):
"""
Test an incompatible Python.
"""
expected_return, expected_level, expected_message = expected
link = Link('https://example.com', requires_python='== 3.6.4')
caplog.set_level(logging.DEBUG)
actual = _check_link_requires_python(
link, version_info=(3, 6, 5),
ignore_requires_python=ignore_requires_python,
) )
check_caplog(caplog, 'DEBUG', expected_message) assert actual == expected_return
check_caplog(caplog, expected_level, expected_message)
def test_check_link_requires_python__invalid_requires(caplog): def test_check_link_requires_python__invalid_requires(caplog):
@ -57,7 +71,7 @@ def test_check_link_requires_python__invalid_requires(caplog):
link = Link('https://example.com', requires_python='invalid') link = Link('https://example.com', requires_python='invalid')
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
actual = _check_link_requires_python(link, version_info=(3, 6, 5)) actual = _check_link_requires_python(link, version_info=(3, 6, 5))
assert actual == True assert actual
expected_message = ( expected_message = (
"Ignoring invalid Requires-Python ('invalid') for link: " "Ignoring invalid Requires-Python ('invalid') for link: "