Skip to content

Commit

Permalink
Add a --prefer-binary flag. (#5370)
Browse files Browse the repository at this point in the history
The flag makes pip prefer an older but valid binary distributions over a newer source distributions.

Fixes #3785.
  • Loading branch information
DanielShaulov authored and pradyunsg committed May 11, 2018
1 parent df45aaf commit d67d98d
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 2 deletions.
1 change: 1 addition & 0 deletions news/3785.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Introduce a new --prefer-binary flag, to prefer older wheels over newer source packages.
1 change: 1 addition & 0 deletions src/pip/_internal/basecommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,4 +370,5 @@ def _build_package_finder(self, options, session,
versions=python_versions,
abi=abi,
implementation=implementation,
prefer_binary=options.prefer_binary,
)
10 changes: 10 additions & 0 deletions src/pip/_internal/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/commands/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def __init__(self, *args, **kw):
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())
Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ def __init__(self, *args, **kw):

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())
Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/commands/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def __init__(self, *args, **kw):
)
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',
Expand Down
12 changes: 10 additions & 2 deletions src/pip/_internal/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -176,6 +177,9 @@ def __init__(self, find_links, index_urls, allow_all_prereleases=False,
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:
Expand Down Expand Up @@ -275,12 +279,14 @@ def _candidate_sort_key(self, candidate):
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)
Expand All @@ -289,14 +295,16 @@ def _candidate_sort_key(self, candidate):
"%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)
build_tag_groups = match.groups()
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
Expand Down
57 changes: 57 additions & 0 deletions tests/functional/test_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

0 comments on commit d67d98d

Please sign in to comment.