From 28464dd33b67ecce29a47cd68437dea5daea04e2 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 | 23 +++++++++-- src/pip/_internal/resolution/base.py | 21 ++++++++++ .../_internal/resolution/legacy/resolver.py | 9 ++--- .../resolution/resolvelib/__init__.py | 0 .../resolution/resolvelib/resolver.py | 38 +++++++++++++++++++ 6 files changed, 96 insertions(+), 9 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..95589b0c40e 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=None, + 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..cca4533e30f 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, @@ -36,12 +35,13 @@ if MYPY_CHECK_RUNNING: from optparse import Values - from typing import Any, List, Optional, Tuple + from typing import Any, List, Optional, Tuple, Type from pip._internal.cache import WheelCache 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.resolver import BaseResolver from pip._internal.utils.temp_dir import ( TempDirectory, TempDirectoryTypeRegistry, @@ -234,6 +234,21 @@ def make_requirement_preparer( use_user_site=use_user_site, ) + @staticmethod + def get_resolver_cls(options): + # type: (Values) -> Type[BaseResolver] + """Get the resolver implementation for instantiation. + + This long import name is needed to convince Mypy into correctly + typecheck. 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 + import pip._internal.resolution.legacy.resolver + return pip._internal.resolution.legacy.resolver.Resolver + @staticmethod def make_resolver( preparer, # type: RequirementPreparer @@ -248,7 +263,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 +273,7 @@ def make_resolver( wheel_cache=wheel_cache, use_pep517=use_pep517, ) - return Resolver( + return RequirementCommand.get_resolver_cls(options)( 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..5ec65d8a783 --- /dev/null +++ b/src/pip/_internal/resolution/base.py @@ -0,0 +1,21 @@ +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..244e4f66d74 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.resolver 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..8f06c33de85 --- /dev/null +++ b/src/pip/_internal/resolution/resolvelib/resolver.py @@ -0,0 +1,38 @@ +from pip._internal.resolution.base import ( + BaseResolver, + InstallRequirementProvider, +) +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 + + +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()