diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index c74d2b632..79958550e 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -915,6 +915,19 @@ no_python_version_warning = partial( ) # 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 @@ general_group = { 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 45ddb8b47..9a98335b4 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.constructors import ( ) 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 @@ if MYPY_CHECK_RUNNING: 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 @@ class RequirementCommand(IndexGroupCommand): 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 @@ class RequirementCommand(IndexGroupCommand): 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 000000000..2fa118bd8 --- /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 2f32631dc..d68003526 100644 --- a/src/pip/_internal/resolution/legacy/resolver.py +++ b/src/pip/_internal/resolution/legacy/resolver.py @@ -29,6 +29,7 @@ from pip._internal.exceptions import ( 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.packaging import ( 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 000000000..e69de29bb diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py new file mode 100644 index 000000000..2d9b14751 --- /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()