| """Contains the RequirementCommand base class. |
| |
| This class is in a separate module so the commands that do not always |
| need PackageFinder capability don't unnecessarily import the |
| PackageFinder machinery and all its vendored dependencies, etc. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import logging |
| import os |
| from functools import partial |
| from optparse import Values |
| from typing import Any, Callable, TypeVar |
|
|
| from pip._internal.build_env import SubprocessBuildEnvironmentInstaller |
| from pip._internal.cache import WheelCache |
| from pip._internal.cli import cmdoptions |
| from pip._internal.cli.index_command import IndexGroupCommand |
| from pip._internal.cli.index_command import SessionCommandMixin as SessionCommandMixin |
| from pip._internal.exceptions import CommandError, PreviousBuildDirError |
| from pip._internal.index.collector import LinkCollector |
| from pip._internal.index.package_finder import PackageFinder |
| from pip._internal.models.selection_prefs import SelectionPreferences |
| from pip._internal.models.target_python import TargetPython |
| from pip._internal.network.session import PipSession |
| from pip._internal.operations.build.build_tracker import BuildTracker |
| from pip._internal.operations.prepare import RequirementPreparer |
| from pip._internal.req.constructors import ( |
| install_req_from_editable, |
| install_req_from_line, |
| install_req_from_parsed_requirement, |
| install_req_from_req_string, |
| ) |
| from pip._internal.req.req_dependency_group import parse_dependency_groups |
| from pip._internal.req.req_file import parse_requirements |
| from pip._internal.req.req_install import InstallRequirement |
| from pip._internal.resolution.base import BaseResolver |
| from pip._internal.utils.temp_dir import ( |
| TempDirectory, |
| TempDirectoryTypeRegistry, |
| tempdir_kinds, |
| ) |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| def should_ignore_regular_constraints(options: Values) -> bool: |
| """ |
| Check if regular constraints should be ignored because |
| we are in a isolated build process and build constraints |
| feature is enabled but no build constraints were passed. |
| """ |
|
|
| return os.environ.get("_PIP_IN_BUILD_IGNORE_CONSTRAINTS") == "1" |
|
|
|
|
| KEEPABLE_TEMPDIR_TYPES = [ |
| tempdir_kinds.BUILD_ENV, |
| tempdir_kinds.EPHEM_WHEEL_CACHE, |
| tempdir_kinds.REQ_BUILD, |
| ] |
|
|
|
|
| _CommandT = TypeVar("_CommandT", bound="RequirementCommand") |
|
|
|
|
| def with_cleanup( |
| func: Callable[[_CommandT, Values, list[str]], int], |
| ) -> Callable[[_CommandT, Values, list[str]], int]: |
| """Decorator for common logic related to managing temporary |
| directories. |
| """ |
|
|
| def configure_tempdir_registry(registry: TempDirectoryTypeRegistry) -> None: |
| for t in KEEPABLE_TEMPDIR_TYPES: |
| registry.set_delete(t, False) |
|
|
| def wrapper(self: _CommandT, options: Values, args: list[str]) -> int: |
| assert self.tempdir_registry is not None |
| if options.no_clean: |
| configure_tempdir_registry(self.tempdir_registry) |
|
|
| try: |
| return func(self, options, args) |
| except PreviousBuildDirError: |
| |
| |
| |
| configure_tempdir_registry(self.tempdir_registry) |
| raise |
|
|
| return wrapper |
|
|
|
|
| class RequirementCommand(IndexGroupCommand): |
| def __init__(self, *args: Any, **kw: Any) -> None: |
| super().__init__(*args, **kw) |
|
|
| self.cmd_opts.add_option(cmdoptions.dependency_groups()) |
| self.cmd_opts.add_option(cmdoptions.no_clean()) |
|
|
| @staticmethod |
| def determine_resolver_variant(options: Values) -> str: |
| """Determines which resolver should be used, based on the given options.""" |
| if "legacy-resolver" in options.deprecated_features_enabled: |
| return "legacy" |
|
|
| return "resolvelib" |
|
|
| @classmethod |
| def make_requirement_preparer( |
| cls, |
| temp_build_dir: TempDirectory, |
| options: Values, |
| build_tracker: BuildTracker, |
| session: PipSession, |
| finder: PackageFinder, |
| use_user_site: bool, |
| download_dir: str | None = None, |
| verbosity: int = 0, |
| ) -> RequirementPreparer: |
| """ |
| Create a RequirementPreparer instance for the given parameters. |
| """ |
| temp_build_dir_path = temp_build_dir.path |
| assert temp_build_dir_path is not None |
| legacy_resolver = False |
|
|
| resolver_variant = cls.determine_resolver_variant(options) |
| if resolver_variant == "resolvelib": |
| lazy_wheel = "fast-deps" in options.features_enabled |
| if lazy_wheel: |
| logger.warning( |
| "pip is using lazily downloaded wheels using HTTP " |
| "range requests to obtain dependency information. " |
| "This experimental feature is enabled through " |
| "--use-feature=fast-deps and it is not ready for " |
| "production." |
| ) |
| else: |
| legacy_resolver = True |
| lazy_wheel = False |
| if "fast-deps" in options.features_enabled: |
| logger.warning( |
| "fast-deps has no effect when used with the legacy resolver." |
| ) |
|
|
| |
| build_constraints = getattr(options, "build_constraints", []) |
| build_constraint_feature_enabled = ( |
| "build-constraint" in options.features_enabled |
| ) |
|
|
| return RequirementPreparer( |
| build_dir=temp_build_dir_path, |
| src_dir=options.src_dir, |
| download_dir=download_dir, |
| build_isolation=options.build_isolation, |
| build_isolation_installer=SubprocessBuildEnvironmentInstaller( |
| finder, |
| build_constraints=build_constraints, |
| build_constraint_feature_enabled=build_constraint_feature_enabled, |
| ), |
| check_build_deps=options.check_build_deps, |
| build_tracker=build_tracker, |
| session=session, |
| progress_bar=options.progress_bar, |
| finder=finder, |
| require_hashes=options.require_hashes, |
| use_user_site=use_user_site, |
| lazy_wheel=lazy_wheel, |
| verbosity=verbosity, |
| legacy_resolver=legacy_resolver, |
| resume_retries=options.resume_retries, |
| ) |
|
|
| @classmethod |
| def make_resolver( |
| cls, |
| preparer: RequirementPreparer, |
| finder: PackageFinder, |
| options: Values, |
| wheel_cache: WheelCache | None = None, |
| use_user_site: bool = False, |
| ignore_installed: bool = True, |
| ignore_requires_python: bool = False, |
| force_reinstall: bool = False, |
| upgrade_strategy: str = "to-satisfy-only", |
| py_version_info: tuple[int, ...] | None = None, |
| ) -> BaseResolver: |
| """ |
| Create a Resolver instance for the given parameters. |
| """ |
| make_install_req = partial( |
| install_req_from_req_string, |
| isolated=options.isolated_mode, |
| ) |
| resolver_variant = cls.determine_resolver_variant(options) |
| |
| |
| |
| if resolver_variant == "resolvelib": |
| import pip._internal.resolution.resolvelib.resolver |
|
|
| return pip._internal.resolution.resolvelib.resolver.Resolver( |
| preparer=preparer, |
| finder=finder, |
| wheel_cache=wheel_cache, |
| 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, |
| wheel_cache=wheel_cache, |
| 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, |
| ) |
|
|
| def get_requirements( |
| self, |
| args: list[str], |
| options: Values, |
| finder: PackageFinder, |
| session: PipSession, |
| ) -> list[InstallRequirement]: |
| """ |
| Parse command-line arguments into the corresponding requirements. |
| """ |
| requirements: list[InstallRequirement] = [] |
|
|
| if not should_ignore_regular_constraints(options): |
| for filename in options.constraints: |
| for parsed_req in parse_requirements( |
| filename, |
| constraint=True, |
| finder=finder, |
| options=options, |
| session=session, |
| ): |
| req_to_add = install_req_from_parsed_requirement( |
| parsed_req, |
| isolated=options.isolated_mode, |
| user_supplied=False, |
| ) |
| requirements.append(req_to_add) |
|
|
| for req in args: |
| req_to_add = install_req_from_line( |
| req, |
| comes_from=None, |
| isolated=options.isolated_mode, |
| user_supplied=True, |
| config_settings=getattr(options, "config_settings", None), |
| ) |
| requirements.append(req_to_add) |
|
|
| if options.dependency_groups: |
| for req in parse_dependency_groups(options.dependency_groups): |
| req_to_add = install_req_from_req_string( |
| req, |
| isolated=options.isolated_mode, |
| user_supplied=True, |
| ) |
| requirements.append(req_to_add) |
|
|
| for req in options.editables: |
| req_to_add = install_req_from_editable( |
| req, |
| user_supplied=True, |
| isolated=options.isolated_mode, |
| config_settings=getattr(options, "config_settings", None), |
| ) |
| requirements.append(req_to_add) |
|
|
| |
| for filename in options.requirements: |
| for parsed_req in parse_requirements( |
| filename, finder=finder, options=options, session=session |
| ): |
| req_to_add = install_req_from_parsed_requirement( |
| parsed_req, |
| isolated=options.isolated_mode, |
| user_supplied=True, |
| config_settings=( |
| parsed_req.options.get("config_settings") |
| if parsed_req.options |
| else None |
| ), |
| ) |
| requirements.append(req_to_add) |
|
|
| |
| if any(req.has_hash_options for req in requirements): |
| options.require_hashes = True |
|
|
| if not ( |
| args |
| or options.editables |
| or options.requirements |
| or options.dependency_groups |
| ): |
| opts = {"name": self.name} |
| if options.find_links: |
| raise CommandError( |
| "You must give at least one requirement to {name} " |
| '(maybe you meant "pip {name} {links}"?)'.format( |
| **dict(opts, links=" ".join(options.find_links)) |
| ) |
| ) |
| else: |
| raise CommandError( |
| "You must give at least one requirement to {name} " |
| '(see "pip help {name}")'.format(**opts) |
| ) |
|
|
| return requirements |
|
|
| @staticmethod |
| def trace_basic_info(finder: PackageFinder) -> None: |
| """ |
| Trace basic information about the provided objects. |
| """ |
| |
| search_scope = finder.search_scope |
| locations = search_scope.get_formatted_locations() |
| if locations: |
| logger.info(locations) |
|
|
| def _build_package_finder( |
| self, |
| options: Values, |
| session: PipSession, |
| target_python: TargetPython | None = None, |
| ignore_requires_python: bool | None = None, |
| ) -> PackageFinder: |
| """ |
| Create a package finder appropriate to this requirement command. |
| |
| :param ignore_requires_python: Whether to ignore incompatible |
| "Requires-Python" values in links. Defaults to False. |
| """ |
| link_collector = LinkCollector.create(session, options=options) |
| selection_prefs = SelectionPreferences( |
| allow_yanked=True, |
| format_control=options.format_control, |
| allow_all_prereleases=options.pre, |
| prefer_binary=options.prefer_binary, |
| ignore_requires_python=ignore_requires_python, |
| ) |
|
|
| return PackageFinder.create( |
| link_collector=link_collector, |
| selection_prefs=selection_prefs, |
| target_python=target_python, |
| ) |
|
|