Skip to content

Commit ad2b078

Browse files
committed
Fix pip-install to respect --ignore-requires-python.
1 parent 7e142ba commit ad2b078

File tree

5 files changed

+67
-17
lines changed

5 files changed

+67
-17
lines changed

news/6371.bugfix

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

src/pip/_internal/cli/base_command.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,15 @@ def _build_package_finder(
326326
platform=None, # type: Optional[str]
327327
python_versions=None, # type: Optional[List[str]]
328328
abi=None, # type: Optional[str]
329-
implementation=None # type: Optional[str]
329+
implementation=None, # type: Optional[str]
330+
ignore_requires_python=None, # type: Optional[bool]
330331
):
331332
# type: (...) -> PackageFinder
332333
"""
333334
Create a package finder appropriate to this requirement command.
335+
336+
:param ignore_requires_python: Whether to ignore incompatible
337+
"Requires-Python" values in links. Defaults to False.
334338
"""
335339
index_urls = [options.index_url] + options.extra_index_urls
336340
if options.no_index:
@@ -352,4 +356,5 @@ def _build_package_finder(
352356
abi=abi,
353357
implementation=implementation,
354358
prefer_binary=options.prefer_binary,
359+
ignore_requires_python=ignore_requires_python,
355360
)

src/pip/_internal/commands/install.py

+1
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ def run(self, options, args):
297297
python_versions=python_versions,
298298
abi=options.abi,
299299
implementation=options.implementation,
300+
ignore_requires_python=options.ignore_requires_python,
300301
)
301302
build_delete = (not (options.no_clean or options.build_dir))
302303
wheel_cache = WheelCache(options.cache_dir, options.format_control)

src/pip/_internal/index.py

+34-6
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,20 @@ def _get_html_page(link, session=None):
256256
return None
257257

258258

259-
def _check_link_requires_python(link, version_info):
259+
def _check_link_requires_python(
260+
link, # type: Link
261+
version_info, # type: Tuple[int, ...]
262+
ignore_requires_python=False, # type: bool
263+
):
264+
# type: (...) -> bool
260265
"""
261-
Return whether the link's Requires-Python supports the given Python
262-
version.
266+
Return whether the given Python version is compatible with a link's
267+
"Requires-Python" value.
268+
269+
:param version_info: The Python version to use to check, as a 3-tuple
270+
of ints (major-minor-micro).
271+
:param ignore_requires_python: Whether to ignore the "Requires-Python"
272+
value if the given Python version isn't compatible.
263273
"""
264274
try:
265275
support_this_python = check_requires_python(
@@ -273,11 +283,18 @@ def _check_link_requires_python(link, version_info):
273283
else:
274284
if not support_this_python:
275285
version = '.'.join(map(str, version_info))
286+
if not ignore_requires_python:
287+
logger.debug(
288+
'Link requires a different Python (%s not in: %r): %s',
289+
version, link.requires_python, link,
290+
)
291+
return False
292+
276293
logger.debug(
277-
'Link requires a different Python (%s not in: %r): %s',
294+
'Ignoring failed Requires-Python check (%s not in: %r) '
295+
'for link: %s',
278296
version, link.requires_python, link,
279297
)
280-
return False
281298

282299
return True
283300

@@ -295,6 +312,7 @@ def __init__(
295312
prefer_binary=False, # type: bool
296313
allow_all_prereleases=False, # type: bool
297314
py_version_info=None, # type: Optional[Tuple[int, ...]]
315+
ignore_requires_python=None, # type: Optional[bool]
298316
):
299317
# type: (...) -> None
300318
"""
@@ -303,12 +321,17 @@ def __init__(
303321
representing a major-minor-micro version, to use to check both
304322
the Python version embedded in the filename and the package's
305323
"Requires-Python" metadata. Defaults to `sys.version_info[:3]`.
324+
:param ignore_requires_python: Whether to ignore incompatible
325+
"Requires-Python" values in links. Defaults to False.
306326
"""
307327
if py_version_info is None:
308328
py_version_info = sys.version_info[:3]
329+
if ignore_requires_python is None:
330+
ignore_requires_python = False
309331

310332
py_version = '.'.join(map(str, py_version_info[:2]))
311333

334+
self._ignore_requires_python = ignore_requires_python
312335
self._prefer_binary = prefer_binary
313336
self._py_version = py_version
314337
self._py_version_info = py_version_info
@@ -383,6 +406,7 @@ def evaluate_link(self, link, search):
383406

384407
supports_python = _check_link_requires_python(
385408
link, version_info=self._py_version_info,
409+
ignore_requires_python=self._ignore_requires_python,
386410
)
387411
if not supports_python:
388412
# Return None for the reason text to suppress calling
@@ -575,7 +599,8 @@ def create(
575599
versions=None, # type: Optional[List[str]]
576600
abi=None, # type: Optional[str]
577601
implementation=None, # type: Optional[str]
578-
prefer_binary=False # type: bool
602+
prefer_binary=False, # type: bool
603+
ignore_requires_python=None, # type: Optional[bool]
579604
):
580605
# type: (...) -> PackageFinder
581606
"""Create a PackageFinder.
@@ -599,6 +624,8 @@ def create(
599624
to pep425tags.py in the get_supported() method.
600625
:param prefer_binary: Whether to prefer an old, but valid, binary
601626
dist over a new source dist.
627+
:param ignore_requires_python: Whether to ignore incompatible
628+
"Requires-Python" values in links. Defaults to False.
602629
"""
603630
if session is None:
604631
raise TypeError(
@@ -634,6 +661,7 @@ def create(
634661
candidate_evaluator = CandidateEvaluator(
635662
valid_tags=valid_tags, prefer_binary=prefer_binary,
636663
allow_all_prereleases=allow_all_prereleases,
664+
ignore_requires_python=ignore_requires_python,
637665
)
638666

639667
# If we don't have TLS enabled, then WARN if anyplace we're looking

tests/unit/test_index.py

+24-10
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,34 @@ def check_caplog(caplog, expected_level, expected_message):
3434
assert record.message == expected_message
3535

3636

37-
def test_check_link_requires_python__incompatible_python(caplog):
37+
@pytest.mark.parametrize('ignore_requires_python, expected', [
38+
(None, (
39+
False, 'DEBUG',
40+
"Link requires a different Python (3.6.5 not in: '== 3.6.4'): "
41+
"https://example.com"
42+
)),
43+
(True, (
44+
True, 'DEBUG',
45+
"Ignoring failed Requires-Python check (3.6.5 not in: '== 3.6.4') "
46+
"for link: https://example.com"
47+
)),
48+
])
49+
def test_check_link_requires_python__incompatible_python(
50+
caplog, ignore_requires_python, expected,
51+
):
3852
"""
39-
Test the log message for an incompatible Python.
53+
Test an incompatible Python.
4054
"""
55+
expected_return, expected_level, expected_message = expected
4156
link = Link('https://example.com', requires_python='== 3.6.4')
4257
caplog.set_level(logging.DEBUG)
43-
actual = _check_link_requires_python(link, version_info=(3, 6, 5))
44-
assert actual == False
45-
46-
expected_message = (
47-
"Link requires a different Python (3.6.5 not in: '== 3.6.4'): "
48-
"https://example.com"
58+
actual = _check_link_requires_python(
59+
link, version_info=(3, 6, 5),
60+
ignore_requires_python=ignore_requires_python,
4961
)
50-
check_caplog(caplog, 'DEBUG', expected_message)
62+
assert actual == expected_return
63+
64+
check_caplog(caplog, expected_level, expected_message)
5165

5266

5367
def test_check_link_requires_python__invalid_requires(caplog):
@@ -57,7 +71,7 @@ def test_check_link_requires_python__invalid_requires(caplog):
5771
link = Link('https://example.com', requires_python='invalid')
5872
caplog.set_level(logging.DEBUG)
5973
actual = _check_link_requires_python(link, version_info=(3, 6, 5))
60-
assert actual == True
74+
assert actual
6175

6276
expected_message = (
6377
"Ignoring invalid Requires-Python ('invalid') for link: "

0 commit comments

Comments
 (0)