Skip to content

Commit 11cc379

Browse files
committed
Abort install if Requires-Python do not match the running version
1 parent 5f48bdd commit 11cc379

File tree

5 files changed

+68
-1
lines changed

5 files changed

+68
-1
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252

5353
* Normalize package names before using in ``pip show`` (:issue:`3976`)
5454

55+
* Raises when Requires-Python do not match the running version.
56+
5557

5658
**8.1.2 (2016-05-10)**
5759

pip/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,7 @@ def hash_then_or(hash_name):
237237
self.gots[hash_name].hexdigest())
238238
prefix = ' or'
239239
return '\n'.join(lines)
240+
241+
242+
class UnsupportedPythonVersion(InstallationError):
243+
"""Unsupported python version (related to PEP 345 Requires-Python)."""

pip/req/req_set.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
display_path, dist_in_usersite, ensure_dir, normalize_path)
2121
from pip.utils.hashes import MissingHashes
2222
from pip.utils.logging import indent_log
23+
from pip.utils.packaging import check_dist_requires_python
2324
from pip.vcs import vcs
2425
from pip.wheel import Wheel
2526

@@ -655,6 +656,7 @@ def _prepare_file(self,
655656
# # parse dependencies # #
656657
# ###################### #
657658
dist = abstract_dist.dist(finder)
659+
check_dist_requires_python(dist)
658660
more_reqs = []
659661

660662
def add_req(subreq):

pip/utils/packaging.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
from __future__ import absolute_import
2+
3+
from email.parser import FeedParser
4+
25
import logging
36
import sys
47

58
from pip._vendor.packaging import specifiers
69
from pip._vendor.packaging import version
10+
from pip._vendor import pkg_resources
11+
12+
from pip import exceptions
713

814
logger = logging.getLogger(__name__)
915

@@ -26,3 +32,32 @@ def check_requires_python(requires_python):
2632
# We only use major.minor.micro
2733
python_version = version.parse('.'.join(map(str, sys.version_info[:3])))
2834
return python_version in requires_python_specifier
35+
36+
37+
def get_metadata(dist):
38+
if (isinstance(dist, pkg_resources.DistInfoDistribution) and
39+
dist.has_metadata('METADATA')):
40+
return dist.get_metadata('METADATA')
41+
elif dist.has_metadata('PKG-INFO'):
42+
return dist.get_metadata('PKG-INFO')
43+
44+
45+
def check_dist_requires_python(dist):
46+
metadata = get_metadata(dist)
47+
feed_parser = FeedParser()
48+
feed_parser.feed(metadata)
49+
pkg_info_dict = feed_parser.close()
50+
requires_python = pkg_info_dict.get('Requires-Python')
51+
try:
52+
if not check_requires_python(requires_python):
53+
raise exceptions.UnsupportedPythonVersion(
54+
"%s requires Python '%s' but the running Python is %s" % (
55+
dist.project_name,
56+
requires_python,
57+
'.'.join(map(str, sys.version_info[:3])),)
58+
)
59+
except specifiers.InvalidSpecifier as e:
60+
logger.debug(
61+
"Package %s has an invalid Requires-Python entry %s - %s" % (
62+
dist.project_name, requires_python, e))
63+
return

tests/unit/test_utils.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
import pytest
1616

1717
from mock import Mock, patch
18-
from pip.exceptions import HashMismatch, HashMissing, InstallationError
18+
from pip.exceptions import (HashMismatch, HashMissing, InstallationError,
19+
UnsupportedPythonVersion)
1920
from pip.utils import (egg_link_path, get_installed_distributions,
2021
untar_file, unzip_file, rmtree, normalize_path)
2122
from pip.utils.build import BuildDirectory
2223
from pip.utils.encoding import auto_decode
2324
from pip.utils.hashes import Hashes, MissingHashes
2425
from pip.utils.glibc import check_glibc_version
26+
from pip.utils.packaging import check_dist_requires_python
2527
from pip._vendor.six import BytesIO
2628

2729

@@ -524,3 +526,25 @@ def test_manylinux1_check_glibc_version(self):
524526
else:
525527
# Didn't find the warning we were expecting
526528
assert False
529+
530+
531+
class TestCheckRequiresPython(object):
532+
533+
@pytest.mark.parametrize(
534+
("metadata", "should_raise"),
535+
[
536+
("Name: test\n", False),
537+
("Name: test\nRequires-Python:", False),
538+
("Name: test\nRequires-Python: invalid_spec", False),
539+
("Name: test\nRequires-Python: <=1", True),
540+
],
541+
)
542+
def test_check_requires(self, metadata, should_raise):
543+
fake_dist = Mock(
544+
has_metadata=lambda _: True,
545+
get_metadata=lambda _: metadata)
546+
if should_raise:
547+
with pytest.raises(UnsupportedPythonVersion):
548+
check_dist_requires_python(fake_dist)
549+
else:
550+
check_dist_requires_python(fake_dist)

0 commit comments

Comments
 (0)