Загрузить файлы в «venv/Lib/site-packages/pip/_internal/cli»
This commit is contained in:
447
venv/Lib/site-packages/pip/_internal/cli/req_command.py
Normal file
447
venv/Lib/site-packages/pip/_internal/cli/req_command.py
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
"""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 (
|
||||||
|
BuildEnvironmentInstaller,
|
||||||
|
InprocessBuildEnvironmentInstaller,
|
||||||
|
SubprocessBuildEnvironmentInstaller,
|
||||||
|
)
|
||||||
|
from pip._internal.cache import WheelCache
|
||||||
|
from pip._internal.cli import cmdoptions
|
||||||
|
from pip._internal.cli.cmdoptions import make_target_python
|
||||||
|
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,
|
||||||
|
UnsupportedPythonVersion,
|
||||||
|
)
|
||||||
|
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.pep723 import PEP723Exception, pep723_metadata
|
||||||
|
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.packaging import check_requires_python
|
||||||
|
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:
|
||||||
|
# This kind of conflict can occur when the user passes an explicit
|
||||||
|
# build directory with a pre-existing folder. In that case we do
|
||||||
|
# not want to accidentally remove it.
|
||||||
|
configure_tempdir_registry(self.tempdir_registry)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def parse_constraint_files(
|
||||||
|
constraint_files: list[str],
|
||||||
|
finder: PackageFinder,
|
||||||
|
options: Values,
|
||||||
|
session: PipSession,
|
||||||
|
) -> list[InstallRequirement]:
|
||||||
|
requirements = []
|
||||||
|
for filename in constraint_files:
|
||||||
|
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)
|
||||||
|
|
||||||
|
return requirements
|
||||||
|
|
||||||
|
|
||||||
|
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."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle build constraints
|
||||||
|
build_constraints = getattr(options, "build_constraints", [])
|
||||||
|
build_constraint_feature_enabled = (
|
||||||
|
"build-constraint" in options.features_enabled
|
||||||
|
)
|
||||||
|
|
||||||
|
env_installer: BuildEnvironmentInstaller
|
||||||
|
if "inprocess-build-deps" in options.features_enabled:
|
||||||
|
build_constraint_reqs = parse_constraint_files(
|
||||||
|
build_constraints, finder, options, session
|
||||||
|
)
|
||||||
|
env_installer = InprocessBuildEnvironmentInstaller(
|
||||||
|
finder=finder,
|
||||||
|
build_tracker=build_tracker,
|
||||||
|
build_constraints=build_constraint_reqs,
|
||||||
|
verbosity=verbosity,
|
||||||
|
wheel_cache=WheelCache(options.cache_dir),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
env_installer = SubprocessBuildEnvironmentInstaller(
|
||||||
|
finder,
|
||||||
|
build_constraints=build_constraints,
|
||||||
|
build_constraint_feature_enabled=build_constraint_feature_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=env_installer,
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
@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)
|
||||||
|
# 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_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):
|
||||||
|
constraints = parse_constraint_files(
|
||||||
|
options.constraints, finder, options, session
|
||||||
|
)
|
||||||
|
requirements.extend(constraints)
|
||||||
|
|
||||||
|
for req in args:
|
||||||
|
if not req.strip():
|
||||||
|
continue
|
||||||
|
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)
|
||||||
|
|
||||||
|
# NOTE: options.require_hashes may be set if --require-hashes is True
|
||||||
|
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 options.requirements_from_scripts:
|
||||||
|
if len(options.requirements_from_scripts) > 1:
|
||||||
|
raise CommandError("--requirements-from-script can only be given once")
|
||||||
|
|
||||||
|
script = options.requirements_from_scripts[0]
|
||||||
|
try:
|
||||||
|
script_metadata = pep723_metadata(script)
|
||||||
|
except PEP723Exception as exc:
|
||||||
|
raise CommandError(exc.msg)
|
||||||
|
|
||||||
|
script_requires_python = script_metadata.get("requires-python", "")
|
||||||
|
|
||||||
|
if script_requires_python and not options.ignore_requires_python:
|
||||||
|
target_python = make_target_python(options)
|
||||||
|
|
||||||
|
if not check_requires_python(
|
||||||
|
requires_python=script_requires_python,
|
||||||
|
version_info=target_python.py_version_info,
|
||||||
|
):
|
||||||
|
raise UnsupportedPythonVersion(
|
||||||
|
f"Script {script!r} requires a different Python: "
|
||||||
|
f"{target_python.py_version} not in {script_requires_python!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
for req in script_metadata.get("dependencies", []):
|
||||||
|
req_to_add = install_req_from_req_string(
|
||||||
|
req,
|
||||||
|
isolated=options.isolated_mode,
|
||||||
|
user_supplied=True,
|
||||||
|
)
|
||||||
|
requirements.append(req_to_add)
|
||||||
|
|
||||||
|
# If any requirement has hash options, enable hash checking.
|
||||||
|
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
|
||||||
|
or options.requirements_from_scripts
|
||||||
|
):
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
# Display where finder is looking for packages
|
||||||
|
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,
|
||||||
|
release_control=options.release_control,
|
||||||
|
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,
|
||||||
|
uploaded_prior_to=options.uploaded_prior_to,
|
||||||
|
)
|
||||||
235
venv/Lib/site-packages/pip/_internal/cli/spinners.py
Normal file
235
venv/Lib/site-packages/pip/_internal/cli/spinners.py
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from collections.abc import Generator
|
||||||
|
from typing import IO, Final
|
||||||
|
|
||||||
|
from pip._vendor.rich.console import (
|
||||||
|
Console,
|
||||||
|
ConsoleOptions,
|
||||||
|
RenderableType,
|
||||||
|
RenderResult,
|
||||||
|
)
|
||||||
|
from pip._vendor.rich.live import Live
|
||||||
|
from pip._vendor.rich.measure import Measurement
|
||||||
|
from pip._vendor.rich.text import Text
|
||||||
|
|
||||||
|
from pip._internal.utils.compat import WINDOWS
|
||||||
|
from pip._internal.utils.logging import get_console, get_indentation
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SPINNER_CHARS: Final = r"-\|/"
|
||||||
|
SPINS_PER_SECOND: Final = 8
|
||||||
|
|
||||||
|
|
||||||
|
class SpinnerInterface:
|
||||||
|
def spin(self) -> None:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def finish(self, final_status: str) -> None:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class InteractiveSpinner(SpinnerInterface):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
file: IO[str] | None = None,
|
||||||
|
spin_chars: str = SPINNER_CHARS,
|
||||||
|
# Empirically, 8 updates/second looks nice
|
||||||
|
min_update_interval_seconds: float = 1 / SPINS_PER_SECOND,
|
||||||
|
):
|
||||||
|
self._message = message
|
||||||
|
if file is None:
|
||||||
|
file = sys.stdout
|
||||||
|
self._file = file
|
||||||
|
self._rate_limiter = RateLimiter(min_update_interval_seconds)
|
||||||
|
self._finished = False
|
||||||
|
|
||||||
|
self._spin_cycle = itertools.cycle(spin_chars)
|
||||||
|
|
||||||
|
self._file.write(" " * get_indentation() + self._message + " ... ")
|
||||||
|
self._width = 0
|
||||||
|
|
||||||
|
def _write(self, status: str) -> None:
|
||||||
|
assert not self._finished
|
||||||
|
# Erase what we wrote before by backspacing to the beginning, writing
|
||||||
|
# spaces to overwrite the old text, and then backspacing again
|
||||||
|
backup = "\b" * self._width
|
||||||
|
self._file.write(backup + " " * self._width + backup)
|
||||||
|
# Now we have a blank slate to add our status
|
||||||
|
self._file.write(status)
|
||||||
|
self._width = len(status)
|
||||||
|
self._file.flush()
|
||||||
|
self._rate_limiter.reset()
|
||||||
|
|
||||||
|
def spin(self) -> None:
|
||||||
|
if self._finished:
|
||||||
|
return
|
||||||
|
if not self._rate_limiter.ready():
|
||||||
|
return
|
||||||
|
self._write(next(self._spin_cycle))
|
||||||
|
|
||||||
|
def finish(self, final_status: str) -> None:
|
||||||
|
if self._finished:
|
||||||
|
return
|
||||||
|
self._write(final_status)
|
||||||
|
self._file.write("\n")
|
||||||
|
self._file.flush()
|
||||||
|
self._finished = True
|
||||||
|
|
||||||
|
|
||||||
|
# Used for dumb terminals, non-interactive installs (no tty), etc.
|
||||||
|
# We still print updates occasionally (once every 60 seconds by default) to
|
||||||
|
# act as a keep-alive for systems like Travis-CI that take lack-of-output as
|
||||||
|
# an indication that a task has frozen.
|
||||||
|
class NonInteractiveSpinner(SpinnerInterface):
|
||||||
|
def __init__(self, message: str, min_update_interval_seconds: float = 60.0) -> None:
|
||||||
|
self._message = message
|
||||||
|
self._finished = False
|
||||||
|
self._rate_limiter = RateLimiter(min_update_interval_seconds)
|
||||||
|
self._update("started")
|
||||||
|
|
||||||
|
def _update(self, status: str) -> None:
|
||||||
|
assert not self._finished
|
||||||
|
self._rate_limiter.reset()
|
||||||
|
logger.info("%s: %s", self._message, status)
|
||||||
|
|
||||||
|
def spin(self) -> None:
|
||||||
|
if self._finished:
|
||||||
|
return
|
||||||
|
if not self._rate_limiter.ready():
|
||||||
|
return
|
||||||
|
self._update("still running...")
|
||||||
|
|
||||||
|
def finish(self, final_status: str) -> None:
|
||||||
|
if self._finished:
|
||||||
|
return
|
||||||
|
self._update(f"finished with status '{final_status}'")
|
||||||
|
self._finished = True
|
||||||
|
|
||||||
|
|
||||||
|
class RateLimiter:
|
||||||
|
def __init__(self, min_update_interval_seconds: float) -> None:
|
||||||
|
self._min_update_interval_seconds = min_update_interval_seconds
|
||||||
|
self._last_update: float = 0
|
||||||
|
|
||||||
|
def ready(self) -> bool:
|
||||||
|
now = time.time()
|
||||||
|
delta = now - self._last_update
|
||||||
|
return delta >= self._min_update_interval_seconds
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
self._last_update = time.time()
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def open_spinner(message: str) -> Generator[SpinnerInterface, None, None]:
|
||||||
|
# Interactive spinner goes directly to sys.stdout rather than being routed
|
||||||
|
# through the logging system, but it acts like it has level INFO,
|
||||||
|
# i.e. it's only displayed if we're at level INFO or better.
|
||||||
|
# Non-interactive spinner goes through the logging system, so it is always
|
||||||
|
# in sync with logging configuration.
|
||||||
|
if sys.stdout.isatty() and logger.getEffectiveLevel() <= logging.INFO:
|
||||||
|
spinner: SpinnerInterface = InteractiveSpinner(message)
|
||||||
|
else:
|
||||||
|
spinner = NonInteractiveSpinner(message)
|
||||||
|
try:
|
||||||
|
with hidden_cursor(sys.stdout):
|
||||||
|
yield spinner
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
spinner.finish("canceled")
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
spinner.finish("error")
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
spinner.finish("done")
|
||||||
|
|
||||||
|
|
||||||
|
class _PipRichSpinner:
|
||||||
|
"""
|
||||||
|
Custom rich spinner that matches the style of the legacy spinners.
|
||||||
|
|
||||||
|
(*) Updates will be handled in a background thread by a rich live panel
|
||||||
|
which will call render() automatically at the appropriate time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, label: str) -> None:
|
||||||
|
self.label = label
|
||||||
|
self._spin_cycle = itertools.cycle(SPINNER_CHARS)
|
||||||
|
self._spinner_text = ""
|
||||||
|
self._finished = False
|
||||||
|
self._indent = get_indentation() * " "
|
||||||
|
|
||||||
|
def __rich_console__(
|
||||||
|
self, console: Console, options: ConsoleOptions
|
||||||
|
) -> RenderResult:
|
||||||
|
yield self.render()
|
||||||
|
|
||||||
|
def __rich_measure__(
|
||||||
|
self, console: Console, options: ConsoleOptions
|
||||||
|
) -> Measurement:
|
||||||
|
text = self.render()
|
||||||
|
return Measurement.get(console, options, text)
|
||||||
|
|
||||||
|
def render(self) -> RenderableType:
|
||||||
|
if not self._finished:
|
||||||
|
self._spinner_text = next(self._spin_cycle)
|
||||||
|
|
||||||
|
return Text.assemble(self._indent, self.label, " ... ", self._spinner_text)
|
||||||
|
|
||||||
|
def finish(self, status: str) -> None:
|
||||||
|
"""Stop spinning and set a final status message."""
|
||||||
|
self._spinner_text = status
|
||||||
|
self._finished = True
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def open_rich_spinner(label: str, console: Console | None = None) -> Generator[None]:
|
||||||
|
if not logger.isEnabledFor(logging.INFO):
|
||||||
|
# Don't show spinner if --quiet is given.
|
||||||
|
yield
|
||||||
|
return
|
||||||
|
|
||||||
|
console = console or get_console()
|
||||||
|
spinner = _PipRichSpinner(label)
|
||||||
|
with Live(spinner, refresh_per_second=SPINS_PER_SECOND, console=console):
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
spinner.finish("canceled")
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
spinner.finish("error")
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
spinner.finish("done")
|
||||||
|
|
||||||
|
|
||||||
|
HIDE_CURSOR = "\x1b[?25l"
|
||||||
|
SHOW_CURSOR = "\x1b[?25h"
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def hidden_cursor(file: IO[str]) -> Generator[None, None, None]:
|
||||||
|
# The Windows terminal does not support the hide/show cursor ANSI codes,
|
||||||
|
# even via colorama. So don't even try.
|
||||||
|
if WINDOWS:
|
||||||
|
yield
|
||||||
|
# We don't want to clutter the output with control characters if we're
|
||||||
|
# writing to a file, or if the user is running with --quiet.
|
||||||
|
# See https://github.com/pypa/pip/issues/3418
|
||||||
|
elif not file.isatty() or logger.getEffectiveLevel() > logging.INFO:
|
||||||
|
yield
|
||||||
|
else:
|
||||||
|
file.write(HIDE_CURSOR)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
file.write(SHOW_CURSOR)
|
||||||
6
venv/Lib/site-packages/pip/_internal/cli/status_codes.py
Normal file
6
venv/Lib/site-packages/pip/_internal/cli/status_codes.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
SUCCESS = 0
|
||||||
|
ERROR = 1
|
||||||
|
UNKNOWN_ERROR = 2
|
||||||
|
VIRTUALENV_NOT_FOUND = 3
|
||||||
|
PREVIOUS_BUILD_DIR_ERROR = 4
|
||||||
|
NO_MATCHES_FOUND = 23
|
||||||
Reference in New Issue
Block a user