From 45cb0fbe3a0b9830b5ea152a7ba8cf43087eb0bd Mon Sep 17 00:00:00 2001 From: Daniel Shaulov Date: Sat, 5 May 2018 02:51:45 +0300 Subject: [PATCH 1/5] Added --prefer-binary flag. The flag makes pip prefer an old but valid binary over a newer source package. Fixes #3785. --- news/3785.feature | 1 + src/pip/_internal/basecommand.py | 1 + src/pip/_internal/cmdoptions.py | 10 ++++++++++ src/pip/_internal/commands/download.py | 1 + src/pip/_internal/commands/install.py | 1 + src/pip/_internal/commands/wheel.py | 1 + src/pip/_internal/index.py | 11 +++++++++-- 7 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 news/3785.feature diff --git a/news/3785.feature b/news/3785.feature new file mode 100644 index 00000000000..fa13f58a02c --- /dev/null +++ b/news/3785.feature @@ -0,0 +1 @@ +Add the --prefer-binary flag, to prefer older wheels over newer source packages. diff --git a/src/pip/_internal/basecommand.py b/src/pip/_internal/basecommand.py index 38bdff24a04..cfd5d67577b 100644 --- a/src/pip/_internal/basecommand.py +++ b/src/pip/_internal/basecommand.py @@ -370,4 +370,5 @@ def _build_package_finder(self, options, session, versions=python_versions, abi=abi, implementation=implementation, + prefer_binary=options.prefer_binary, ) diff --git a/src/pip/_internal/cmdoptions.py b/src/pip/_internal/cmdoptions.py index 6319995d4e9..eb113457f30 100644 --- a/src/pip/_internal/cmdoptions.py +++ b/src/pip/_internal/cmdoptions.py @@ -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", diff --git a/src/pip/_internal/commands/download.py b/src/pip/_internal/commands/download.py index 5713d07ce5d..66bcbd5c822 100644 --- a/src/pip/_internal/commands/download.py +++ b/src/pip/_internal/commands/download.py @@ -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()) diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 9138683acdd..aa0988c43ad 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -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()) diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py index ac55f91e62e..0fb72c1a92b 100644 --- a/src/pip/_internal/commands/wheel.py +++ b/src/pip/_internal/commands/wheel.py @@ -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', diff --git a/src/pip/_internal/index.py b/src/pip/_internal/index.py index 3c3a92b7c1a..8306ab9c94e 100644 --- a/src/pip/_internal/index.py +++ b/src/pip/_internal/index.py @@ -108,7 +108,7 @@ 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 +176,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: @@ -275,12 +278,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) @@ -289,6 +294,8 @@ 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) @@ -296,7 +303,7 @@ def _candidate_sort_key(self, candidate): 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 From d895ed7831d51e61a4276a934c5799730e60b141 Mon Sep 17 00:00:00 2001 From: Daniel Shaulov Date: Sat, 5 May 2018 03:16:54 +0300 Subject: [PATCH 2/5] pep8: fix long line in index.py --- src/pip/_internal/index.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pip/_internal/index.py b/src/pip/_internal/index.py index 8306ab9c94e..17aee7246c4 100644 --- a/src/pip/_internal/index.py +++ b/src/pip/_internal/index.py @@ -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, prefer_binary=False): + versions=None, abi=None, implementation=None, + prefer_binary=False): """Create a PackageFinder. :param format_control: A FormatControl object or None. Used to control From 7648fe3be1b68ebda202ae0bc5c47496c5ced49f Mon Sep 17 00:00:00 2001 From: Daniel Shaulov Date: Sun, 6 May 2018 02:21:42 +0300 Subject: [PATCH 3/5] tests: Added tests for the --prefer-binary flag --- tests/functional/test_download.py | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/functional/test_download.py b/tests/functional/test_download.py index bdb46523d34..829d144aa87 100644 --- a/tests/functional/test_download.py +++ b/tests/functional/test_download.py @@ -602,3 +602,55 @@ 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') + result = script.pip( + 'download', + '--prefer-binary', + '--no-index', + '-f', data.packages, + '-d', '.', 'source>=0.9' + ) + 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 + ) From c38afb28fa1355a977643f3763fc075fdfc03c17 Mon Sep 17 00:00:00 2001 From: Daniel Shaulov Date: Sun, 6 May 2018 22:53:10 +0300 Subject: [PATCH 4/5] tests: fix prefer-binary tests on windows --- tests/functional/test_download.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/functional/test_download.py b/tests/functional/test_download.py index 829d144aa87..4cbfb5665a3 100644 --- a/tests/functional/test_download.py +++ b/tests/functional/test_download.py @@ -625,12 +625,17 @@ def test_download_prefer_binary_when_tarball_higher_than_wheel(script, data): 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', '.', 'source>=0.9' + '-d', '.', + '-r', script.scratch_path / 'test-req.txt' ) assert ( Path('scratch') / 'source-1.0.tar.gz' From 7a77ca40114fcd420889ef5e19870544c55ca02e Mon Sep 17 00:00:00 2001 From: Daniel Shaulov Date: Tue, 8 May 2018 08:47:59 +0300 Subject: [PATCH 5/5] news: reworded the news fragment for prefer-binary --- news/3785.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/3785.feature b/news/3785.feature index fa13f58a02c..ca5463ec860 100644 --- a/news/3785.feature +++ b/news/3785.feature @@ -1 +1 @@ -Add the --prefer-binary flag, to prefer older wheels over newer source packages. +Introduce a new --prefer-binary flag, to prefer older wheels over newer source packages.