From fc810d73537544ead26cd7fcc1d2b6345f9bbc5f Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Wed, 11 Mar 2020 17:16:11 +0800 Subject: [PATCH] Add --unstable-feature=resolver This introduces a new general option --unstable-feature that can be used to opt into "preview" features in pip not enabled by default. Currently the only available feature is "resolver". A stub resolver interface (which would fail on invocation) is provided to respond to the flag. The --unstable-feature option is hidden from --help since the resolver does not yet work. This suppression should be removed when we release the resolver for general/public testing. --- src/pip/_internal/cli/cmdoptions.py | 14 ++++++++ src/pip/_internal/cli/req_command.py | 24 +++++++++++-- src/pip/_internal/resolution/base.py | 20 +++++++++++ .../_internal/resolution/legacy/resolver.py | 9 +++-- .../resolution/resolvelib/__init__.py | 0 .../resolution/resolvelib/resolver.py | 36 +++++++++++++++++++ 6 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 src/pip/_internal/resolution/base.py create mode 100644 src/pip/_internal/resolution/resolvelib/__init__.py create mode 100644 src/pip/_internal/resolution/resolvelib/resolver.py diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index c74d2b632a6..79958550ec4 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -915,6 +915,19 @@ def check_list_path_option(options): ) # type: Callable[..., Option] +unstable_feature = partial( + Option, + '--unstable-feature', + dest='unstable_features', + metavar='feature', + action='append', + default=[], + choices=['resolver'], + help=SUPPRESS_HELP, # TODO: Enable this when the resolver actually works. + # help='Enable unstable feature(s) that may be backward incompatible.', +) # type: Callable[..., Option] + + ########## # groups # ########## @@ -943,6 +956,7 @@ def check_list_path_option(options): disable_pip_version_check, no_color, no_python_version_warning, + unstable_feature, ] } # type: Dict[str, Any] diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index 45ddb8b47f3..9a98335b4fc 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -26,7 +26,6 @@ ) from pip._internal.req.req_file import parse_requirements from pip._internal.req.req_set import RequirementSet -from pip._internal.resolution.legacy.resolver import Resolver from pip._internal.self_outdated_check import ( make_link_collector, pip_self_version_check, @@ -42,6 +41,7 @@ from pip._internal.models.target_python import TargetPython from pip._internal.req.req_install import InstallRequirement from pip._internal.req.req_tracker import RequirementTracker + from pip._internal.resolution.base import BaseResolver from pip._internal.utils.temp_dir import ( TempDirectory, TempDirectoryTypeRegistry, @@ -248,7 +248,7 @@ def make_resolver( use_pep517=None, # type: Optional[bool] py_version_info=None # type: Optional[Tuple[int, ...]] ): - # type: (...) -> Resolver + # type: (...) -> BaseResolver """ Create a Resolver instance for the given parameters. """ @@ -258,7 +258,25 @@ def make_resolver( wheel_cache=wheel_cache, use_pep517=use_pep517, ) - return Resolver( + # The long import name and duplicated invocation is needed to convince + # Mypy into correctly typechecking. Otherwise it would complain the + # "Resolver" class being redefined. + if 'resolver' in options.unstable_features: + import pip._internal.resolution.resolvelib.resolver + return pip._internal.resolution.resolvelib.resolver.Resolver( + preparer=preparer, + finder=finder, + make_install_req=make_install_req, + use_user_site=use_user_site, + ignore_dependencies=options.ignore_dependencies, + ignore_installed=ignore_installed, + ignore_requires_python=ignore_requires_python, + force_reinstall=force_reinstall, + upgrade_strategy=upgrade_strategy, + py_version_info=py_version_info, + ) + import pip._internal.resolution.legacy.resolver + return pip._internal.resolution.legacy.resolver.Resolver( preparer=preparer, finder=finder, make_install_req=make_install_req, diff --git a/src/pip/_internal/resolution/base.py b/src/pip/_internal/resolution/base.py new file mode 100644 index 00000000000..2fa118bd894 --- /dev/null +++ b/src/pip/_internal/resolution/base.py @@ -0,0 +1,20 @@ +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Callable, List + from pip._internal.req.req_install import InstallRequirement + from pip._internal.req.req_set import RequirementSet + + InstallRequirementProvider = Callable[ + [str, InstallRequirement], InstallRequirement + ] + + +class BaseResolver(object): + def resolve(self, root_reqs, check_supported_wheels): + # type: (List[InstallRequirement], bool) -> RequirementSet + raise NotImplementedError() + + def get_installation_order(self, req_set): + # type: (RequirementSet) -> List[InstallRequirement] + raise NotImplementedError() diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py index 2f32631dc60..d6800352614 100644 --- a/src/pip/_internal/resolution/legacy/resolver.py +++ b/src/pip/_internal/resolution/legacy/resolver.py @@ -29,6 +29,7 @@ UnsupportedPythonVersion, ) from pip._internal.req.req_set import RequirementSet +from pip._internal.resolution.base import BaseResolver from pip._internal.utils.logging import indent_log from pip._internal.utils.misc import dist_in_usersite, normalize_version_info from pip._internal.utils.packaging import ( @@ -38,17 +39,15 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING if MYPY_CHECK_RUNNING: - from typing import Callable, DefaultDict, List, Optional, Set, Tuple + from typing import DefaultDict, List, Optional, Set, Tuple from pip._vendor import pkg_resources from pip._internal.distributions import AbstractDistribution from pip._internal.index.package_finder import PackageFinder from pip._internal.operations.prepare import RequirementPreparer from pip._internal.req.req_install import InstallRequirement + from pip._internal.resolution.base import InstallRequirementProvider - InstallRequirementProvider = Callable[ - [str, InstallRequirement], InstallRequirement - ] DiscoveredDependencies = DefaultDict[str, List[InstallRequirement]] logger = logging.getLogger(__name__) @@ -102,7 +101,7 @@ def _check_dist_requires_python( )) -class Resolver(object): +class Resolver(BaseResolver): """Resolves which packages need to be installed/uninstalled to perform \ the requested operation without breaking the requirements of any package. """ diff --git a/src/pip/_internal/resolution/resolvelib/__init__.py b/src/pip/_internal/resolution/resolvelib/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py new file mode 100644 index 00000000000..2d9b14751ce --- /dev/null +++ b/src/pip/_internal/resolution/resolvelib/resolver.py @@ -0,0 +1,36 @@ +from pip._internal.resolution.base import BaseResolver +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import List, Optional, Tuple + + from pip._internal.index.package_finder import PackageFinder + from pip._internal.operations.prepare import RequirementPreparer + from pip._internal.req.req_install import InstallRequirement + from pip._internal.req.req_set import RequirementSet + from pip._internal.resolution.base import InstallRequirementProvider + + +class Resolver(BaseResolver): + def __init__( + self, + preparer, # type: RequirementPreparer + finder, # type: PackageFinder + make_install_req, # type: InstallRequirementProvider + use_user_site, # type: bool + ignore_dependencies, # type: bool + ignore_installed, # type: bool + ignore_requires_python, # type: bool + force_reinstall, # type: bool + upgrade_strategy, # type: str + py_version_info=None, # type: Optional[Tuple[int, ...]] + ): + super(Resolver, self).__init__() + + def resolve(self, root_reqs, check_supported_wheels): + # type: (List[InstallRequirement], bool) -> RequirementSet + raise NotImplementedError() + + def get_installation_order(self, req_set): + # type: (RequirementSet) -> List[InstallRequirement] + raise NotImplementedError()