diff --git a/.appveyor.yml b/.appveyor.yml index 76325ab3f..a74f6ee76 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -17,6 +17,8 @@ environment: PIP: 19.0.3 - TOXENV: py27-pip19.1 PIP: 19.1 + - TOXENV: py27-pip19.3 + PIP: 19.3 - TOXENV: py27-pipmaster PIP: master - TOXENV: py27-piplatest-coverage @@ -36,6 +38,8 @@ environment: PIP: 19.0.3 - TOXENV: py35-pip19.1 PIP: 19.1 + - TOXENV: py35-pip19.3 + PIP: 19.3 - TOXENV: py35-pipmaster PIP: master - TOXENV: py35-piplatest @@ -55,6 +59,8 @@ environment: PIP: 19.0.3 - TOXENV: py36-pip19.1 PIP: 19.1 + - TOXENV: py36-pip19.3 + PIP: 19.3 - TOXENV: py36-pipmaster PIP: master - TOXENV: py36-piplatest @@ -74,6 +80,8 @@ environment: PIP: 19.0.3 - TOXENV: py37-pip19.1-coverage PIP: 19.1 + - TOXENV: py37-pip19.3 + PIP: 19.3 - TOXENV: py37-pipmaster-coverage PIP: master - TOXENV: py37-piplatest-coverage diff --git a/.travis.yml b/.travis.yml index 485ea518b..14821d436 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ env: - PIP=18.0 - PIP=19.0.3 - PIP=19.1 + - PIP=19.3 - PIP=latest - PIP=master diff --git a/piptools/_compat/__init__.py b/piptools/_compat/__init__.py index d9e881dc2..a58711d52 100644 --- a/piptools/_compat/__init__.py +++ b/piptools/_compat/__init__.py @@ -7,6 +7,7 @@ from .pip_compat import ( DEV_PKGS, FAVORITE_HASH, + PIP_VERSION, FormatControl, InstallationCandidate, InstallCommand, diff --git a/piptools/_compat/pip_compat.py b/piptools/_compat/pip_compat.py index cc34e51a2..869956245 100644 --- a/piptools/_compat/pip_compat.py +++ b/piptools/_compat/pip_compat.py @@ -4,6 +4,8 @@ import pip from pip._vendor.packaging.version import parse as parse_version +PIP_VERSION = tuple(map(int, parse_version(pip.__version__).base_version.split("."))) + def do_import(module_path, subimport=None, old_path=None): old_path = old_path or module_path @@ -34,13 +36,12 @@ def do_import(module_path, subimport=None, old_path=None): FAVORITE_HASH = do_import("utils.hashes", "FAVORITE_HASH") is_file_url = do_import("download", "is_file_url") is_dir_url = do_import("download", "is_dir_url") -is_vcs_url = do_import("download", "is_vcs_url") path_to_url = do_import("download", "path_to_url") url_to_path = do_import("download", "url_to_path") PackageFinder = do_import("index", "PackageFinder") FormatControl = do_import("index", "FormatControl") -Wheel = do_import("wheel", "Wheel") InstallCommand = do_import("commands.install", "InstallCommand") +Wheel = do_import("wheel", "Wheel") cmdoptions = do_import("cli.cmdoptions", old_path="cmdoptions") get_installed_distributions = do_import( "utils.misc", "get_installed_distributions", old_path="utils" @@ -53,7 +54,7 @@ def do_import(module_path, subimport=None, old_path=None): Resolver = do_import("legacy_resolve", "Resolver", old_path="resolve") # pip 18.1 has refactored InstallRequirement constructors use by pip-tools. -if parse_version(pip.__version__) < parse_version("18.1"): +if PIP_VERSION < (18, 1): install_req_from_line = InstallRequirement.from_line install_req_from_editable = InstallRequirement.from_editable else: @@ -61,3 +62,11 @@ def do_import(module_path, subimport=None, old_path=None): install_req_from_editable = do_import( "req.constructors", "install_req_from_editable" ) + + +def is_vcs_url(link): + if PIP_VERSION < (19, 3): + _is_vcs_url = do_import("download", "is_vcs_url") + return _is_vcs_url(link) + + return link.is_vcs diff --git a/piptools/repositories/pypi.py b/piptools/repositories/pypi.py index 3d5e35bf9..cdf885a6c 100644 --- a/piptools/repositories/pypi.py +++ b/piptools/repositories/pypi.py @@ -5,11 +5,11 @@ import hashlib import os from contextlib import contextmanager +from functools import partial from shutil import rmtree from .._compat import ( FAVORITE_HASH, - InstallCommand, Link, PyPI, RequirementSet, @@ -28,7 +28,7 @@ from ..exceptions import NoCandidateFound from ..logging import log from ..utils import ( - PIP_VERSION, + create_install_command, fs_str, is_pinned_requirement, is_url_requirement, @@ -37,6 +37,8 @@ ) from .base import BaseRepository +from piptools._compat.pip_compat import PIP_VERSION + try: from pip._internal.req.req_tracker import RequirementTracker except ImportError: @@ -71,7 +73,7 @@ def __init__(self, pip_args, build_isolation=False): # Use pip's parser for pip.conf management and defaults. # General options (find_links, index_url, extra_index_url, trusted_host, # and pre) are deferred to pip. - command = InstallCommand() + command = create_install_command() self.options, _ = command.parse_args(pip_args) self.session = command._build_session(self.options) @@ -149,9 +151,15 @@ def find_best_match(self, ireq, prereleases=None): elif PIP_VERSION < (19, 2): evaluator = self.finder.candidate_evaluator best_candidate = evaluator.get_best_candidate(matching_candidates) - else: + elif PIP_VERSION < (19, 3): evaluator = self.finder.make_candidate_evaluator(ireq.name) best_candidate = evaluator.get_best_candidate(matching_candidates) + else: + evaluator = self.finder.make_candidate_evaluator(ireq.name) + best_candidate_result = evaluator.compute_best_candidate( + matching_candidates + ) + best_candidate = best_candidate_result.best_candidate # Turn the candidate into a pinned InstallRequirement return make_install_requirement( @@ -193,10 +201,20 @@ def resolve_reqs(self, download_dir, ireq, wheel_cache): "ignore_dependencies": False, "ignore_requires_python": False, "ignore_installed": True, - "isolated": False, - "wheel_cache": wheel_cache, "use_user_site": False, } + make_install_req_kwargs = {"isolated": False, "wheel_cache": wheel_cache} + + if PIP_VERSION < (19, 3): + resolver_kwargs.update(**make_install_req_kwargs) + else: + from pip._internal.req.constructors import install_req_from_req_string + + make_install_req = partial( + install_req_from_req_string, **make_install_req_kwargs + ) + resolver_kwargs["make_install_req"] = make_install_req + resolver = None preparer = None with RequirementTracker() as req_tracker: diff --git a/piptools/scripts/compile.py b/piptools/scripts/compile.py index 24d50b662..6415227dd 100755 --- a/piptools/scripts/compile.py +++ b/piptools/scripts/compile.py @@ -8,13 +8,14 @@ from click.utils import safecall from .. import click -from .._compat import InstallCommand, install_req_from_line, parse_requirements +from .._compat import install_req_from_line, parse_requirements from ..exceptions import PipToolsError from ..logging import log from ..repositories import LocalRequirementsRepository, PyPIRepository from ..resolver import Resolver from ..utils import ( UNSAFE_PACKAGES, + create_install_command, dedup, is_pinned_requirement, key_from_ireq, @@ -26,7 +27,8 @@ DEFAULT_REQUIREMENTS_OUTPUT_FILE = "requirements.txt" # Get default values of the pip's options (including options from pip.conf). -pip_defaults = InstallCommand().parser.get_default_values() +install_command = create_install_command() +pip_defaults = install_command.parser.get_default_values() @click.command() diff --git a/piptools/utils.py b/piptools/utils.py index af6d603c3..3e0d97b00 100644 --- a/piptools/utils.py +++ b/piptools/utils.py @@ -5,16 +5,13 @@ from collections import OrderedDict from itertools import chain, groupby -import pip import six from click.utils import LazyFile -from pip._vendor.packaging.version import parse as parse_version from six.moves import shlex_quote -from ._compat import install_req_from_line +from ._compat import PIP_VERSION, InstallCommand, install_req_from_line from .click import style -PIP_VERSION = tuple(map(int, parse_version(pip.__version__).base_version.split("."))) UNSAFE_PACKAGES = {"setuptools", "distribute", "pip"} COMPILE_EXCLUDE_OPTIONS = { "--dry-run", @@ -371,3 +368,15 @@ def get_compile_command(click_ctx): ) return " ".join(["pip-compile"] + sorted(left_args) + sorted(right_args)) + + +def create_install_command(): + """ + Return an instance of InstallCommand. + """ + if PIP_VERSION < (19, 3): + return InstallCommand() + + from pip._internal import create_command + + return create_command("install") diff --git a/tests/test_cli_compile.py b/tests/test_cli_compile.py index 0dce21b73..800c100b4 100644 --- a/tests/test_cli_compile.py +++ b/tests/test_cli_compile.py @@ -10,10 +10,9 @@ from .utils import invoke -from piptools._compat.pip_compat import path_to_url +from piptools._compat.pip_compat import PIP_VERSION, path_to_url from piptools.repositories import PyPIRepository from piptools.scripts.compile import cli -from piptools.utils import PIP_VERSION TEST_DATA_PATH = os.path.join(os.path.split(__file__)[0], "test_data") MINIMAL_WHEELS_PATH = os.path.join(TEST_DATA_PATH, "minimal_wheels") diff --git a/tests/test_repositories.py b/tests/test_repositories.py index 58324b184..c5d388872 100644 --- a/tests/test_repositories.py +++ b/tests/test_repositories.py @@ -2,7 +2,7 @@ from mock import MagicMock, patch from piptools._compat import PackageFinder, install_req_from_line -from piptools.utils import PIP_VERSION +from piptools._compat.pip_compat import PIP_VERSION def test_pypirepo_build_dir_is_str(pypi_repository): diff --git a/tests/test_utils.py b/tests/test_utils.py index e8af29213..18a65cf5b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -10,6 +10,7 @@ from piptools.scripts.compile import cli as compile_cli from piptools.utils import ( as_tuple, + create_install_command, dedup, flat_map, force_text, @@ -324,3 +325,11 @@ def test_get_compile_command_sort_args(tmpdir_cwd): "--no-annotate --no-emit-trusted-host --no-index " "requirements.in setup.py" ) + + +def test_create_install_command(): + """ + Test create_install_command returns an instance of InstallCommand. + """ + install_command = create_install_command() + assert install_command.name == "install" diff --git a/tox.ini b/tox.ini index 557e47e63..63606e7cd 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = # NOTE: keep this in sync with the env list in .travis.yml for tox-travis. - py{27,35,36,37,38,py,py3}-pip{8.1.1,9.0.1,9.0.3,10.0.1,18.0,19.0.3,19.1,latest,master}-coverage + py{27,35,36,37,38,py,py3}-pip{8.1.1,9.0.1,9.0.3,10.0.1,18.0,19.0.3,19.1,19.3,latest,master}-coverage checkqa readme skip_missing_interpreters = True @@ -17,6 +17,8 @@ deps = pip18.0: pip==18.0 pip19.0.3: pip==19.0.3 pip19.1: pip==19.1 + # TODO change it to pip==19.3 after pip being released + pip19.3: -e git+https://github.com/pypa/pip.git@master#egg=pip mock pytest!=5.1.2 coverage: pytest-cov @@ -30,6 +32,7 @@ setenv = pip18.0: PIP=18.0 pip19.0.3: PIP==19.0.3 pip19.1: PIP==19.1 + pip19.3: PIP==19.3 coverage: PYTEST_ADDOPTS=--strict --doctest-modules --cov --cov-report=term-missing {env:PYTEST_ADDOPTS:} commands_pre = @@ -56,5 +59,6 @@ PIP = 18.0: pip18.0 19.0.3: pip19.0.3 19.1: pip19.1 + 19.3: pip19.3 latest: piplatest master: pipmaster