Add a --prefer-binary flag. (#5370)

The flag makes pip prefer an older but valid binary distributions over a newer source distributions.

Fixes #3785.
This commit is contained in:
Daniel Shaulov 2018-05-11 08:47:32 +03:00 committed by Pradyun Gedam
parent df45aafd15
commit d67d98dd91
8 changed files with 82 additions and 2 deletions

1
news/3785.feature Normal file
View File

@ -0,0 +1 @@
Introduce a new --prefer-binary flag, to prefer older wheels over newer source packages.

View File

@ -370,4 +370,5 @@ class RequirementCommand(Command):
versions=python_versions,
abi=abi,
implementation=implementation,
prefer_binary=options.prefer_binary,
)

View File

@ -406,6 +406,16 @@ def only_binary():
)
def prefer_binary():
return Option(
"--prefer-binary",
dest="prefer_binary",
action="store_true",
default=False,
help="Prefer older binary packages over newer source packages."
)
cache_dir = partial(
Option,
"--cache-dir",

View File

@ -52,6 +52,7 @@ class DownloadCommand(RequirementCommand):
cmd_opts.add_option(cmdoptions.global_options())
cmd_opts.add_option(cmdoptions.no_binary())
cmd_opts.add_option(cmdoptions.only_binary())
cmd_opts.add_option(cmdoptions.prefer_binary())
cmd_opts.add_option(cmdoptions.src())
cmd_opts.add_option(cmdoptions.pre())
cmd_opts.add_option(cmdoptions.no_clean())

View File

@ -183,6 +183,7 @@ class InstallCommand(RequirementCommand):
cmd_opts.add_option(cmdoptions.no_binary())
cmd_opts.add_option(cmdoptions.only_binary())
cmd_opts.add_option(cmdoptions.prefer_binary())
cmd_opts.add_option(cmdoptions.no_clean())
cmd_opts.add_option(cmdoptions.require_hashes())
cmd_opts.add_option(cmdoptions.progress_bar())

View File

@ -57,6 +57,7 @@ class WheelCommand(RequirementCommand):
)
cmd_opts.add_option(cmdoptions.no_binary())
cmd_opts.add_option(cmdoptions.only_binary())
cmd_opts.add_option(cmdoptions.prefer_binary())
cmd_opts.add_option(
'--build-option',
dest='build_options',

View File

@ -108,7 +108,8 @@ class PackageFinder(object):
def __init__(self, find_links, index_urls, allow_all_prereleases=False,
trusted_hosts=None, process_dependency_links=False,
session=None, format_control=None, platform=None,
versions=None, abi=None, implementation=None):
versions=None, abi=None, implementation=None,
prefer_binary=False):
"""Create a PackageFinder.
:param format_control: A FormatControl object or None. Used to control
@ -176,6 +177,9 @@ class PackageFinder(object):
impl=implementation,
)
# Do we prefer old, but valid, binary dist over new source dist
self.prefer_binary = prefer_binary
# If we don't have TLS enabled, then WARN if anyplace we're looking
# relies on TLS.
if not HAS_TLS:
@ -275,12 +279,14 @@ class PackageFinder(object):
1. existing installs
2. wheels ordered via Wheel.support_index_min(self.valid_tags)
3. source archives
If prefer_binary was set, then all wheels are sorted above sources.
Note: it was considered to embed this logic into the Link
comparison operators, but then different sdist links
with the same version, would have to be considered equal
"""
support_num = len(self.valid_tags)
build_tag = tuple()
binary_preference = 0
if candidate.location.is_wheel:
# can raise InvalidWheelFilename
wheel = Wheel(candidate.location.filename)
@ -289,6 +295,8 @@ class PackageFinder(object):
"%s is not a supported wheel for this platform. It "
"can't be sorted." % wheel.filename
)
if self.prefer_binary:
binary_preference = 1
pri = -(wheel.support_index_min(self.valid_tags))
if wheel.build_tag is not None:
match = re.match(r'^(\d+)(.*)$', wheel.build_tag)
@ -296,7 +304,7 @@ class PackageFinder(object):
build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
else: # sdist
pri = -(support_num)
return (candidate.version, build_tag, pri)
return (binary_preference, candidate.version, build_tag, pri)
def _validate_secure_origin(self, logger, location):
# Determine if this url used a secure transport mechanism

View File

@ -602,3 +602,60 @@ def test_download_exit_status_code_when_blank_requirements_file(script):
"""
script.scratch_path.join("blank.txt").write("\n")
script.pip('download', '-r', 'blank.txt')
def test_download_prefer_binary_when_tarball_higher_than_wheel(script, data):
fake_wheel(data, 'source-0.8-py2.py3-none-any.whl')
result = script.pip(
'download',
'--prefer-binary',
'--no-index',
'-f', data.packages,
'-d', '.', 'source'
)
assert (
Path('scratch') / 'source-0.8-py2.py3-none-any.whl'
in result.files_created
)
assert (
Path('scratch') / 'source-1.0.tar.gz'
not in result.files_created
)
def test_download_prefer_binary_when_wheel_doesnt_satisfy_req(script, data):
fake_wheel(data, 'source-0.8-py2.py3-none-any.whl')
script.scratch_path.join("test-req.txt").write(textwrap.dedent("""
source>0.9
"""))
result = script.pip(
'download',
'--prefer-binary',
'--no-index',
'-f', data.packages,
'-d', '.',
'-r', script.scratch_path / 'test-req.txt'
)
assert (
Path('scratch') / 'source-1.0.tar.gz'
in result.files_created
)
assert (
Path('scratch') / 'source-0.8-py2.py3-none-any.whl'
not in result.files_created
)
def test_download_prefer_binary_when_only_tarball_exists(script, data):
result = script.pip(
'download',
'--prefer-binary',
'--no-index',
'-f', data.packages,
'-d', '.', 'source'
)
assert (
Path('scratch') / 'source-1.0.tar.gz'
in result.files_created
)