Change PackageFinder to use Tuple[int, ...] instead of List[str] for --python-version.

This commit is contained in:
Chris Jerdonek 2019-05-25 11:48:51 -07:00
parent fada348b00
commit 798d814629
9 changed files with 106 additions and 20 deletions

View File

@ -324,7 +324,7 @@ class RequirementCommand(Command):
options, # type: Values
session, # type: PipSession
platform=None, # type: Optional[str]
python_versions=None, # type: Optional[List[str]]
py_version_info=None, # type: Optional[Tuple[int, ...]]
abi=None, # type: Optional[str]
implementation=None, # type: Optional[str]
ignore_requires_python=None, # type: Optional[bool]
@ -352,7 +352,7 @@ class RequirementCommand(Command):
allow_all_prereleases=options.pre,
session=session,
platform=platform,
versions=python_versions,
py_version_info=py_version_info,
abi=abi,
implementation=implementation,
prefer_binary=options.prefer_binary,

View File

@ -24,7 +24,7 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.ui import BAR_TYPES
if MYPY_CHECK_RUNNING:
from typing import Any, Callable, Dict, Optional
from typing import Any, Callable, Dict, Optional, Tuple
from optparse import OptionParser, Values
from pip._internal.cli.parser import ConfigOptionParser
@ -478,11 +478,36 @@ platform = partial(
) # type: Callable[..., Option]
# This was made a separate function for unit-testing purposes.
def _convert_python_version(value):
# type: (str) -> Tuple[int, ...]
"""
Convert a string like "3" or "34" into a tuple of ints.
"""
if len(value) == 1:
parts = [value]
else:
parts = [value[0], value[1:]]
return tuple(int(part) for part in parts)
def _handle_python_version(option, opt_str, value, parser):
# type: (Option, str, str, OptionParser) -> None
"""
Convert a string like "3" or "34" into a tuple of ints.
"""
version_info = _convert_python_version(value)
parser.values.python_version = version_info
python_version = partial(
Option,
'--python-version',
dest='python_version',
metavar='python_version',
action='callback',
callback=_handle_python_version, type='str',
default=None,
help=("Only use wheels compatible with Python "
"interpreter version <version>. If not specified, then the "

View File

@ -88,11 +88,6 @@ class DownloadCommand(RequirementCommand):
# of the RequirementSet code require that property.
options.editables = []
if options.python_version:
python_versions = [options.python_version]
else:
python_versions = None
cmdoptions.check_dist_restriction(options)
options.src_dir = os.path.abspath(options.src_dir)
@ -105,7 +100,7 @@ class DownloadCommand(RequirementCommand):
options=options,
session=session,
platform=options.platform,
python_versions=python_versions,
py_version_info=options.python_version,
abi=options.abi,
implementation=options.implementation,
)

View File

@ -251,11 +251,6 @@ class InstallCommand(RequirementCommand):
cmdoptions.check_dist_restriction(options, check_target=True)
if options.python_version:
python_versions = [options.python_version]
else:
python_versions = None
options.src_dir = os.path.abspath(options.src_dir)
install_options = options.install_options or []
if options.use_user_site:
@ -294,7 +289,7 @@ class InstallCommand(RequirementCommand):
options=options,
session=session,
platform=options.platform,
python_versions=python_versions,
py_version_info=options.python_version,
abi=options.abi,
implementation=options.implementation,
ignore_requires_python=options.ignore_requires_python,

View File

@ -29,7 +29,7 @@ from pip._internal.models.candidate import InstallationCandidate
from pip._internal.models.format_control import FormatControl
from pip._internal.models.index import PyPI
from pip._internal.models.link import Link
from pip._internal.pep425tags import get_supported
from pip._internal.pep425tags import get_supported, version_info_to_nodot
from pip._internal.utils.compat import ipaddress
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import (
@ -604,7 +604,7 @@ class PackageFinder(object):
session=None, # type: Optional[PipSession]
format_control=None, # type: Optional[FormatControl]
platform=None, # type: Optional[str]
versions=None, # type: Optional[List[str]]
py_version_info=None, # type: Optional[Tuple[int, ...]]
abi=None, # type: Optional[str]
implementation=None, # type: Optional[str]
prefer_binary=False, # type: bool
@ -624,8 +624,10 @@ class PackageFinder(object):
packages that can be built on the platform passed in. These
packages will only be downloaded for distribution: they will
not be built locally.
:param versions: A list of strings or None. This is passed directly
to pep425tags.py in the get_supported() method.
:param py_version_info: An optional tuple of ints representing the
Python version information to use (e.g. `sys.version_info[:3]`).
This can have length 1, 2, or 3. This is used to construct the
value passed to pep425tags.py's get_supported() function.
:param abi: A string or None. This is passed directly
to pep425tags.py in the get_supported() method.
:param implementation: A string or None. This is passed directly
@ -659,6 +661,11 @@ class PackageFinder(object):
for host in (trusted_hosts if trusted_hosts else [])
] # type: List[SecureOrigin]
if py_version_info:
versions = [version_info_to_nodot(py_version_info)]
else:
versions = None
# The valid tags to check potential found wheel candidates against
valid_tags = get_supported(
versions=versions,

View File

@ -49,6 +49,12 @@ def get_abbr_impl():
return pyimpl
def version_info_to_nodot(version_info):
# type: (Tuple[int, ...]) -> str
# Only use up to the first two numbers.
return ''.join(map(str, version_info[:2]))
def get_impl_ver():
# type: () -> str
"""Return implementation version."""

View File

@ -0,0 +1,15 @@
import pytest
from pip._internal.cli.cmdoptions import _convert_python_version
@pytest.mark.parametrize('value, expected', [
('2', (2,)),
('3', (3,)),
('34', (3, 4)),
# Test a 2-digit minor version.
('310', (3, 10)),
])
def test_convert_python_version(value, expected):
actual = _convert_python_version(value)
assert actual == expected

View File

@ -3,7 +3,7 @@ import os.path
import sys
import pytest
from mock import Mock
from mock import Mock, patch
from pip._vendor import html5lib, requests
from pip._internal.download import PipSession
@ -149,6 +149,34 @@ class TestCandidateEvaluator:
assert actual == expected
class TestPackageFinder:
@pytest.mark.parametrize('version_info, expected', [
((2,), ['2']),
((3,), ['3']),
((3, 6,), ['36']),
# Test a tuple of length 3.
((3, 6, 5), ['36']),
# Test a 2-digit minor version.
((3, 10), ['310']),
# Test falsey values.
(None, None),
((), None),
])
@patch('pip._internal.index.get_supported')
def test_create__py_version_info(
self, mock_get_supported, version_info, expected,
):
"""
Test that the py_version_info argument is handled correctly.
"""
PackageFinder.create(
[], [], py_version_info=version_info, session=object(),
)
actual = mock_get_supported.call_args[1]['versions']
assert actual == expected
def test_sort_locations_file_expand_dir(data):
"""
Test that a file:// dir gets listdir run with expand_dir

View File

@ -6,6 +6,21 @@ from mock import patch
from pip._internal import pep425tags
@pytest.mark.parametrize('version_info, expected', [
((2,), '2'),
((2, 8), '28'),
((3,), '3'),
((3, 6), '36'),
# Test a tuple of length 3.
((3, 6, 5), '36'),
# Test a 2-digit minor version.
((3, 10), '310'),
])
def test_version_info_to_nodot(version_info, expected):
actual = pep425tags.version_info_to_nodot(version_info)
assert actual == expected
class TestPEP425Tags(object):
def mock_get_config_var(self, **kwd):