Voice et bot modif
This commit is contained in:
parent
189d56026b
commit
7333a22bcd
10774 changed files with 634644 additions and 933308 deletions
4
venv/lib/python3.12/site-packages/pip/_internal/__init__.py
Executable file → Normal file
4
venv/lib/python3.12/site-packages/pip/_internal/__init__.py
Executable file → Normal file
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import annotations
|
||||
from typing import List, Optional
|
||||
|
||||
from pip._internal.utils import _log
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ from pip._internal.utils import _log
|
|||
_log.init_logging()
|
||||
|
||||
|
||||
def main(args: list[str] | None = None) -> int:
|
||||
def main(args: (Optional[List[str]]) = None) -> int:
|
||||
"""This is preserved for old console scripts that may still be referencing
|
||||
it.
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,6 +1,5 @@
|
|||
"""Build Environment used for isolation during sdist building"""
|
||||
|
||||
from __future__ import annotations
|
||||
"""Build Environment used for isolation during sdist building
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -9,34 +8,27 @@ import site
|
|||
import sys
|
||||
import textwrap
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Iterable
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Protocol, TypedDict
|
||||
from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union
|
||||
|
||||
from pip._vendor.certifi import where
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
from pip import __file__ as pip_location
|
||||
from pip._internal.cli.spinners import open_spinner
|
||||
from pip._internal.locations import get_platlib, get_purelib, get_scheme
|
||||
from pip._internal.metadata import get_default_environment, get_environment
|
||||
from pip._internal.utils.deprecation import deprecated
|
||||
from pip._internal.utils.logging import VERBOSE
|
||||
from pip._internal.utils.packaging import get_requirement
|
||||
from pip._internal.utils.subprocess import call_subprocess
|
||||
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
|
||||
class ExtraEnviron(TypedDict, total=False):
|
||||
extra_environ: dict[str, str]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _dedup(a: str, b: str) -> tuple[str] | tuple[str, str]:
|
||||
def _dedup(a: str, b: str) -> Union[Tuple[str], Tuple[str, str]]:
|
||||
return (a, b) if a != b else (a,)
|
||||
|
||||
|
||||
|
|
@ -65,7 +57,7 @@ def get_runnable_pip() -> str:
|
|||
return os.fsdecode(source / "__pip-runner__.py")
|
||||
|
||||
|
||||
def _get_system_sitepackages() -> set[str]:
|
||||
def _get_system_sitepackages() -> Set[str]:
|
||||
"""Get system site packages
|
||||
|
||||
Usually from site.getsitepackages,
|
||||
|
|
@ -85,171 +77,10 @@ def _get_system_sitepackages() -> set[str]:
|
|||
return {os.path.normcase(path) for path in system_sites}
|
||||
|
||||
|
||||
class BuildEnvironmentInstaller(Protocol):
|
||||
"""
|
||||
Interface for installing build dependencies into an isolated build
|
||||
environment.
|
||||
"""
|
||||
|
||||
def install(
|
||||
self,
|
||||
requirements: Iterable[str],
|
||||
prefix: _Prefix,
|
||||
*,
|
||||
kind: str,
|
||||
for_req: InstallRequirement | None,
|
||||
) -> None: ...
|
||||
|
||||
|
||||
class SubprocessBuildEnvironmentInstaller:
|
||||
"""
|
||||
Install build dependencies by calling pip in a subprocess.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
finder: PackageFinder,
|
||||
build_constraints: list[str] | None = None,
|
||||
build_constraint_feature_enabled: bool = False,
|
||||
) -> None:
|
||||
self.finder = finder
|
||||
self._build_constraints = build_constraints or []
|
||||
self._build_constraint_feature_enabled = build_constraint_feature_enabled
|
||||
|
||||
def _deprecation_constraint_check(self) -> None:
|
||||
"""
|
||||
Check for deprecation warning: PIP_CONSTRAINT affecting build environments.
|
||||
|
||||
This warns when build-constraint feature is NOT enabled and PIP_CONSTRAINT
|
||||
is not empty.
|
||||
"""
|
||||
if self._build_constraint_feature_enabled or self._build_constraints:
|
||||
return
|
||||
|
||||
pip_constraint = os.environ.get("PIP_CONSTRAINT")
|
||||
if not pip_constraint or not pip_constraint.strip():
|
||||
return
|
||||
|
||||
deprecated(
|
||||
reason=(
|
||||
"Setting PIP_CONSTRAINT will not affect "
|
||||
"build constraints in the future,"
|
||||
),
|
||||
replacement=(
|
||||
"to specify build constraints using --build-constraint or "
|
||||
"PIP_BUILD_CONSTRAINT. To disable this warning without "
|
||||
"any build constraints set --use-feature=build-constraint or "
|
||||
'PIP_USE_FEATURE="build-constraint"'
|
||||
),
|
||||
gone_in="26.2",
|
||||
issue=None,
|
||||
)
|
||||
|
||||
def install(
|
||||
self,
|
||||
requirements: Iterable[str],
|
||||
prefix: _Prefix,
|
||||
*,
|
||||
kind: str,
|
||||
for_req: InstallRequirement | None,
|
||||
) -> None:
|
||||
self._deprecation_constraint_check()
|
||||
|
||||
finder = self.finder
|
||||
args: list[str] = [
|
||||
sys.executable,
|
||||
get_runnable_pip(),
|
||||
"install",
|
||||
"--ignore-installed",
|
||||
"--no-user",
|
||||
"--prefix",
|
||||
prefix.path,
|
||||
"--no-warn-script-location",
|
||||
"--disable-pip-version-check",
|
||||
# As the build environment is ephemeral, it's wasteful to
|
||||
# pre-compile everything, especially as not every Python
|
||||
# module will be used/compiled in most cases.
|
||||
"--no-compile",
|
||||
# The prefix specified two lines above, thus
|
||||
# target from config file or env var should be ignored
|
||||
"--target",
|
||||
"",
|
||||
]
|
||||
if logger.getEffectiveLevel() <= logging.DEBUG:
|
||||
args.append("-vv")
|
||||
elif logger.getEffectiveLevel() <= VERBOSE:
|
||||
args.append("-v")
|
||||
for format_control in ("no_binary", "only_binary"):
|
||||
formats = getattr(finder.format_control, format_control)
|
||||
args.extend(
|
||||
(
|
||||
"--" + format_control.replace("_", "-"),
|
||||
",".join(sorted(formats or {":none:"})),
|
||||
)
|
||||
)
|
||||
|
||||
index_urls = finder.index_urls
|
||||
if index_urls:
|
||||
args.extend(["-i", index_urls[0]])
|
||||
for extra_index in index_urls[1:]:
|
||||
args.extend(["--extra-index-url", extra_index])
|
||||
else:
|
||||
args.append("--no-index")
|
||||
for link in finder.find_links:
|
||||
args.extend(["--find-links", link])
|
||||
|
||||
if finder.proxy:
|
||||
args.extend(["--proxy", finder.proxy])
|
||||
for host in finder.trusted_hosts:
|
||||
args.extend(["--trusted-host", host])
|
||||
if finder.custom_cert:
|
||||
args.extend(["--cert", finder.custom_cert])
|
||||
if finder.client_cert:
|
||||
args.extend(["--client-cert", finder.client_cert])
|
||||
if finder.allow_all_prereleases:
|
||||
args.append("--pre")
|
||||
if finder.prefer_binary:
|
||||
args.append("--prefer-binary")
|
||||
|
||||
# Handle build constraints
|
||||
if self._build_constraint_feature_enabled:
|
||||
args.extend(["--use-feature", "build-constraint"])
|
||||
|
||||
if self._build_constraints:
|
||||
# Build constraints must be passed as both constraints
|
||||
# and build constraints, so that nested builds receive
|
||||
# build constraints
|
||||
for constraint_file in self._build_constraints:
|
||||
args.extend(["--constraint", constraint_file])
|
||||
args.extend(["--build-constraint", constraint_file])
|
||||
|
||||
extra_environ: ExtraEnviron = {}
|
||||
if self._build_constraint_feature_enabled and not self._build_constraints:
|
||||
# If there are no build constraints but the build constraints
|
||||
# feature is enabled then we must ignore regular constraints
|
||||
# in the isolated build environment
|
||||
extra_environ = {"extra_environ": {"_PIP_IN_BUILD_IGNORE_CONSTRAINTS": "1"}}
|
||||
|
||||
args.append("--")
|
||||
args.extend(requirements)
|
||||
|
||||
identify_requirement = (
|
||||
f" for {for_req.name}" if for_req and for_req.name else ""
|
||||
)
|
||||
with open_spinner(f"Installing {kind}") as spinner:
|
||||
call_subprocess(
|
||||
args,
|
||||
command_desc=f"installing {kind}{identify_requirement}",
|
||||
spinner=spinner,
|
||||
**extra_environ,
|
||||
)
|
||||
|
||||
|
||||
class BuildEnvironment:
|
||||
"""Creates and manages an isolated environment to install build deps"""
|
||||
|
||||
def __init__(self, installer: BuildEnvironmentInstaller) -> None:
|
||||
self.installer = installer
|
||||
def __init__(self) -> None:
|
||||
temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True)
|
||||
|
||||
self._prefixes = OrderedDict(
|
||||
|
|
@ -257,8 +88,8 @@ class BuildEnvironment:
|
|||
for name in ("normal", "overlay")
|
||||
)
|
||||
|
||||
self._bin_dirs: list[str] = []
|
||||
self._lib_dirs: list[str] = []
|
||||
self._bin_dirs: List[str] = []
|
||||
self._lib_dirs: List[str] = []
|
||||
for prefix in reversed(list(self._prefixes.values())):
|
||||
self._bin_dirs.append(prefix.bin_dir)
|
||||
self._lib_dirs.extend(prefix.lib_dirs)
|
||||
|
|
@ -326,9 +157,9 @@ class BuildEnvironment:
|
|||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
) -> None:
|
||||
for varname, old_value in self._save_env.items():
|
||||
if old_value is None:
|
||||
|
|
@ -338,7 +169,7 @@ class BuildEnvironment:
|
|||
|
||||
def check_requirements(
|
||||
self, reqs: Iterable[str]
|
||||
) -> tuple[set[tuple[str, str]], set[str]]:
|
||||
) -> Tuple[Set[Tuple[str, str]], Set[str]]:
|
||||
"""Return 2 sets:
|
||||
- conflicting requirements: set of (installed, wanted) reqs tuples
|
||||
- missing requirements: set of reqs
|
||||
|
|
@ -352,7 +183,7 @@ class BuildEnvironment:
|
|||
else get_default_environment()
|
||||
)
|
||||
for req_str in reqs:
|
||||
req = get_requirement(req_str)
|
||||
req = Requirement(req_str)
|
||||
# We're explicitly evaluating with an empty extra value, since build
|
||||
# environments are not provided any mechanism to select specific extras.
|
||||
if req.marker is not None and not req.marker.evaluate({"extra": ""}):
|
||||
|
|
@ -372,18 +203,81 @@ class BuildEnvironment:
|
|||
|
||||
def install_requirements(
|
||||
self,
|
||||
finder: "PackageFinder",
|
||||
requirements: Iterable[str],
|
||||
prefix_as_string: str,
|
||||
*,
|
||||
kind: str,
|
||||
for_req: InstallRequirement | None = None,
|
||||
) -> None:
|
||||
prefix = self._prefixes[prefix_as_string]
|
||||
assert not prefix.setup
|
||||
prefix.setup = True
|
||||
if not requirements:
|
||||
return
|
||||
self.installer.install(requirements, prefix, kind=kind, for_req=for_req)
|
||||
self._install_requirements(
|
||||
get_runnable_pip(),
|
||||
finder,
|
||||
requirements,
|
||||
prefix,
|
||||
kind=kind,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _install_requirements(
|
||||
pip_runnable: str,
|
||||
finder: "PackageFinder",
|
||||
requirements: Iterable[str],
|
||||
prefix: _Prefix,
|
||||
*,
|
||||
kind: str,
|
||||
) -> None:
|
||||
args: List[str] = [
|
||||
sys.executable,
|
||||
pip_runnable,
|
||||
"install",
|
||||
"--ignore-installed",
|
||||
"--no-user",
|
||||
"--prefix",
|
||||
prefix.path,
|
||||
"--no-warn-script-location",
|
||||
]
|
||||
if logger.getEffectiveLevel() <= logging.DEBUG:
|
||||
args.append("-v")
|
||||
for format_control in ("no_binary", "only_binary"):
|
||||
formats = getattr(finder.format_control, format_control)
|
||||
args.extend(
|
||||
(
|
||||
"--" + format_control.replace("_", "-"),
|
||||
",".join(sorted(formats or {":none:"})),
|
||||
)
|
||||
)
|
||||
|
||||
index_urls = finder.index_urls
|
||||
if index_urls:
|
||||
args.extend(["-i", index_urls[0]])
|
||||
for extra_index in index_urls[1:]:
|
||||
args.extend(["--extra-index-url", extra_index])
|
||||
else:
|
||||
args.append("--no-index")
|
||||
for link in finder.find_links:
|
||||
args.extend(["--find-links", link])
|
||||
|
||||
for host in finder.trusted_hosts:
|
||||
args.extend(["--trusted-host", host])
|
||||
if finder.allow_all_prereleases:
|
||||
args.append("--pre")
|
||||
if finder.prefer_binary:
|
||||
args.append("--prefer-binary")
|
||||
args.append("--")
|
||||
args.extend(requirements)
|
||||
extra_environ = {"_PIP_STANDALONE_CERT": where()}
|
||||
with open_spinner(f"Installing {kind}") as spinner:
|
||||
call_subprocess(
|
||||
args,
|
||||
command_desc=f"pip subprocess to install {kind}",
|
||||
spinner=spinner,
|
||||
extra_environ=extra_environ,
|
||||
)
|
||||
|
||||
|
||||
class NoOpBuildEnvironment(BuildEnvironment):
|
||||
|
|
@ -397,9 +291,9 @@ class NoOpBuildEnvironment(BuildEnvironment):
|
|||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
|
@ -408,10 +302,10 @@ class NoOpBuildEnvironment(BuildEnvironment):
|
|||
|
||||
def install_requirements(
|
||||
self,
|
||||
finder: "PackageFinder",
|
||||
requirements: Iterable[str],
|
||||
prefix_as_string: str,
|
||||
*,
|
||||
kind: str,
|
||||
for_req: InstallRequirement | None = None,
|
||||
) -> None:
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
"""Cache Management"""
|
||||
|
||||
from __future__ import annotations
|
||||
"""Cache Management
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
|
@ -24,7 +23,7 @@ logger = logging.getLogger(__name__)
|
|||
ORIGIN_JSON_NAME = "origin.json"
|
||||
|
||||
|
||||
def _hash_dict(d: dict[str, str]) -> str:
|
||||
def _hash_dict(d: Dict[str, str]) -> str:
|
||||
"""Return a stable sha224 of a dictionary."""
|
||||
s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
||||
return hashlib.sha224(s.encode("ascii")).hexdigest()
|
||||
|
|
@ -41,11 +40,11 @@ class Cache:
|
|||
assert not cache_dir or os.path.isabs(cache_dir)
|
||||
self.cache_dir = cache_dir or None
|
||||
|
||||
def _get_cache_path_parts(self, link: Link) -> list[str]:
|
||||
def _get_cache_path_parts(self, link: Link) -> List[str]:
|
||||
"""Get parts of part that must be os.path.joined with cache_dir"""
|
||||
|
||||
# We want to generate an url to use as our cache key, we don't want to
|
||||
# just reuse the URL because it might have other items in the fragment
|
||||
# just re-use the URL because it might have other items in the fragment
|
||||
# and we don't care about those.
|
||||
key_parts = {"url": link.url_without_fragment}
|
||||
if link.hash_name is not None and link.hash is not None:
|
||||
|
|
@ -74,7 +73,7 @@ class Cache:
|
|||
|
||||
return parts
|
||||
|
||||
def _get_candidates(self, link: Link, canonical_package_name: str) -> list[Any]:
|
||||
def _get_candidates(self, link: Link, canonical_package_name: str) -> List[Any]:
|
||||
can_not_cache = not self.cache_dir or not canonical_package_name or not link
|
||||
if can_not_cache:
|
||||
return []
|
||||
|
|
@ -91,8 +90,8 @@ class Cache:
|
|||
def get(
|
||||
self,
|
||||
link: Link,
|
||||
package_name: str | None,
|
||||
supported_tags: list[Tag],
|
||||
package_name: Optional[str],
|
||||
supported_tags: List[Tag],
|
||||
) -> Link:
|
||||
"""Returns a link to a cached item if it exists, otherwise returns the
|
||||
passed link.
|
||||
|
|
@ -129,8 +128,8 @@ class SimpleWheelCache(Cache):
|
|||
def get(
|
||||
self,
|
||||
link: Link,
|
||||
package_name: str | None,
|
||||
supported_tags: list[Tag],
|
||||
package_name: Optional[str],
|
||||
supported_tags: List[Tag],
|
||||
) -> Link:
|
||||
candidates = []
|
||||
|
||||
|
|
@ -143,7 +142,7 @@ class SimpleWheelCache(Cache):
|
|||
wheel = Wheel(wheel_name)
|
||||
except InvalidWheelFilename:
|
||||
continue
|
||||
if wheel.name != canonical_package_name:
|
||||
if canonicalize_name(wheel.name) != canonical_package_name:
|
||||
logger.debug(
|
||||
"Ignoring cached wheel %s for %s as it "
|
||||
"does not match the expected distribution name %s.",
|
||||
|
|
@ -190,7 +189,7 @@ class CacheEntry:
|
|||
):
|
||||
self.link = link
|
||||
self.persistent = persistent
|
||||
self.origin: DirectUrl | None = None
|
||||
self.origin: Optional[DirectUrl] = None
|
||||
origin_direct_url_path = Path(self.link.file_path).parent / ORIGIN_JSON_NAME
|
||||
if origin_direct_url_path.exists():
|
||||
try:
|
||||
|
|
@ -227,8 +226,8 @@ class WheelCache(Cache):
|
|||
def get(
|
||||
self,
|
||||
link: Link,
|
||||
package_name: str | None,
|
||||
supported_tags: list[Tag],
|
||||
package_name: Optional[str],
|
||||
supported_tags: List[Tag],
|
||||
) -> Link:
|
||||
cache_entry = self.get_cache_entry(link, package_name, supported_tags)
|
||||
if cache_entry is None:
|
||||
|
|
@ -238,9 +237,9 @@ class WheelCache(Cache):
|
|||
def get_cache_entry(
|
||||
self,
|
||||
link: Link,
|
||||
package_name: str | None,
|
||||
supported_tags: list[Tag],
|
||||
) -> CacheEntry | None:
|
||||
package_name: Optional[str],
|
||||
supported_tags: List[Tag],
|
||||
) -> Optional[CacheEntry]:
|
||||
"""Returns a CacheEntry with a link to a cached item if it exists or
|
||||
None. The cache entry indicates if the item was found in the persistent
|
||||
or ephemeral cache.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
"""Subpackage containing all of pip's command line interface related code"""
|
||||
"""Subpackage containing all of pip's command line interface related code
|
||||
"""
|
||||
|
||||
# This file intentionally does not import submodules
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,13 +1,11 @@
|
|||
"""Logic that powers autocompletion installed by ``pip completion``."""
|
||||
|
||||
from __future__ import annotations
|
||||
"""Logic that powers autocompletion installed by ``pip completion``.
|
||||
"""
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
from itertools import chain
|
||||
from typing import Any
|
||||
from typing import Any, Iterable, List, Optional
|
||||
|
||||
from pip._internal.cli.main_parser import create_main_parser
|
||||
from pip._internal.commands import commands_dict, create_command
|
||||
|
|
@ -19,10 +17,6 @@ def autocomplete() -> None:
|
|||
# Don't complete if user hasn't sourced bash_completion file.
|
||||
if "PIP_AUTO_COMPLETE" not in os.environ:
|
||||
return
|
||||
# Don't complete if autocompletion environment variables
|
||||
# are not present
|
||||
if not os.environ.get("COMP_WORDS") or not os.environ.get("COMP_CWORD"):
|
||||
return
|
||||
cwords = os.environ["COMP_WORDS"].split()[1:]
|
||||
cword = int(os.environ["COMP_CWORD"])
|
||||
try:
|
||||
|
|
@ -35,7 +29,7 @@ def autocomplete() -> None:
|
|||
options = []
|
||||
|
||||
# subcommand
|
||||
subcommand_name: str | None = None
|
||||
subcommand_name: Optional[str] = None
|
||||
for word in cwords:
|
||||
if word in subcommands:
|
||||
subcommand_name = word
|
||||
|
|
@ -103,12 +97,6 @@ def autocomplete() -> None:
|
|||
if option[1] and option[0][:2] == "--":
|
||||
opt_label += "="
|
||||
print(opt_label)
|
||||
|
||||
# Complete sub-commands (unless one is already given).
|
||||
if not any(name in cwords for name in subcommand.handler_map()):
|
||||
for handler_name in subcommand.handler_map():
|
||||
if handler_name.startswith(current):
|
||||
print(handler_name)
|
||||
else:
|
||||
# show main parser options only when necessary
|
||||
|
||||
|
|
@ -130,8 +118,8 @@ def autocomplete() -> None:
|
|||
|
||||
|
||||
def get_path_completion_type(
|
||||
cwords: list[str], cword: int, opts: Iterable[Any]
|
||||
) -> str | None:
|
||||
cwords: List[str], cword: int, opts: Iterable[Any]
|
||||
) -> Optional[str]:
|
||||
"""Get the type of path completion (``file``, ``dir``, ``path`` or None)
|
||||
|
||||
:param cwords: same as the environmental variable ``COMP_WORDS``
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"""Base Command class, and related routines"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import logging.config
|
||||
import optparse
|
||||
|
|
@ -9,9 +8,8 @@ import os
|
|||
import sys
|
||||
import traceback
|
||||
from optparse import Values
|
||||
from typing import Callable
|
||||
from typing import Any, Callable, List, Optional, Tuple
|
||||
|
||||
from pip._vendor.rich import reconfigure
|
||||
from pip._vendor.rich import traceback as rich_traceback
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
|
|
@ -30,6 +28,7 @@ from pip._internal.exceptions import (
|
|||
InstallationError,
|
||||
NetworkConnectionError,
|
||||
PreviousBuildDirError,
|
||||
UninstallationError,
|
||||
)
|
||||
from pip._internal.utils.filesystem import check_path_owner
|
||||
from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
|
||||
|
|
@ -62,7 +61,7 @@ class Command(CommandContextMixIn):
|
|||
isolated=isolated,
|
||||
)
|
||||
|
||||
self.tempdir_registry: TempDirRegistry | None = None
|
||||
self.tempdir_registry: Optional[TempDirRegistry] = None
|
||||
|
||||
# Commands should add options to this option group
|
||||
optgroup_name = f"{self.name.capitalize()} Options"
|
||||
|
|
@ -89,78 +88,21 @@ class Command(CommandContextMixIn):
|
|||
# are present.
|
||||
assert not hasattr(options, "no_index")
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
def _run_wrapper(self, level_number: int, options: Values, args: list[str]) -> int:
|
||||
def _inner_run() -> int:
|
||||
try:
|
||||
return self.run(options, args)
|
||||
finally:
|
||||
self.handle_pip_version_check(options)
|
||||
|
||||
if options.debug_mode:
|
||||
rich_traceback.install(show_locals=True)
|
||||
return _inner_run()
|
||||
|
||||
try:
|
||||
status = _inner_run()
|
||||
assert isinstance(status, int)
|
||||
return status
|
||||
except DiagnosticPipError as exc:
|
||||
logger.error("%s", exc, extra={"rich": True})
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
|
||||
return ERROR
|
||||
except PreviousBuildDirError as exc:
|
||||
logger.critical(str(exc))
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
|
||||
return PREVIOUS_BUILD_DIR_ERROR
|
||||
except (
|
||||
InstallationError,
|
||||
BadCommand,
|
||||
NetworkConnectionError,
|
||||
) as exc:
|
||||
logger.critical(str(exc))
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
|
||||
return ERROR
|
||||
except CommandError as exc:
|
||||
logger.critical("%s", exc)
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
|
||||
return ERROR
|
||||
except BrokenStdoutLoggingError:
|
||||
# Bypass our logger and write any remaining messages to
|
||||
# stderr because stdout no longer works.
|
||||
print("ERROR: Pipe to stdout was broken", file=sys.stderr)
|
||||
if level_number <= logging.DEBUG:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
return ERROR
|
||||
except KeyboardInterrupt:
|
||||
logger.critical("Operation cancelled by user")
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
|
||||
return ERROR
|
||||
except BaseException:
|
||||
logger.critical("Exception:", exc_info=True)
|
||||
|
||||
return UNKNOWN_ERROR
|
||||
|
||||
def parse_args(self, args: list[str]) -> tuple[Values, list[str]]:
|
||||
def parse_args(self, args: List[str]) -> Tuple[Values, List[str]]:
|
||||
# factored out for testability
|
||||
return self.parser.parse_args(args)
|
||||
|
||||
def main(self, args: list[str]) -> int:
|
||||
def main(self, args: List[str]) -> int:
|
||||
try:
|
||||
with self.main_context():
|
||||
return self._main(args)
|
||||
finally:
|
||||
logging.shutdown()
|
||||
|
||||
def _main(self, args: list[str]) -> int:
|
||||
def _main(self, args: List[str]) -> int:
|
||||
# We must initialize this before the tempdir manager, otherwise the
|
||||
# configuration would not be accessible by the time we clean up the
|
||||
# tempdir manager.
|
||||
|
|
@ -173,13 +115,7 @@ class Command(CommandContextMixIn):
|
|||
|
||||
# Set verbosity so that it can be used elsewhere.
|
||||
self.verbosity = options.verbose - options.quiet
|
||||
if options.debug_mode:
|
||||
self.verbosity = 2
|
||||
|
||||
if hasattr(options, "progress_bar") and options.progress_bar == "auto":
|
||||
options.progress_bar = "on" if self.verbosity >= 0 else "off"
|
||||
|
||||
reconfigure(no_color=options.no_color)
|
||||
level_number = setup_logging(
|
||||
verbosity=self.verbosity,
|
||||
no_color=options.no_color,
|
||||
|
|
@ -235,10 +171,66 @@ class Command(CommandContextMixIn):
|
|||
)
|
||||
options.cache_dir = None
|
||||
|
||||
return self._run_wrapper(level_number, options, args)
|
||||
def intercepts_unhandled_exc(
|
||||
run_func: Callable[..., int]
|
||||
) -> Callable[..., int]:
|
||||
@functools.wraps(run_func)
|
||||
def exc_logging_wrapper(*args: Any) -> int:
|
||||
try:
|
||||
status = run_func(*args)
|
||||
assert isinstance(status, int)
|
||||
return status
|
||||
except DiagnosticPipError as exc:
|
||||
logger.error("%s", exc, extra={"rich": True})
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
|
||||
def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
|
||||
"""
|
||||
map of names to handler actions for commands with sub-actions
|
||||
"""
|
||||
return {}
|
||||
return ERROR
|
||||
except PreviousBuildDirError as exc:
|
||||
logger.critical(str(exc))
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
|
||||
return PREVIOUS_BUILD_DIR_ERROR
|
||||
except (
|
||||
InstallationError,
|
||||
UninstallationError,
|
||||
BadCommand,
|
||||
NetworkConnectionError,
|
||||
) as exc:
|
||||
logger.critical(str(exc))
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
|
||||
return ERROR
|
||||
except CommandError as exc:
|
||||
logger.critical("%s", exc)
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
|
||||
return ERROR
|
||||
except BrokenStdoutLoggingError:
|
||||
# Bypass our logger and write any remaining messages to
|
||||
# stderr because stdout no longer works.
|
||||
print("ERROR: Pipe to stdout was broken", file=sys.stderr)
|
||||
if level_number <= logging.DEBUG:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
return ERROR
|
||||
except KeyboardInterrupt:
|
||||
logger.critical("Operation cancelled by user")
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
|
||||
return ERROR
|
||||
except BaseException:
|
||||
logger.critical("Exception:", exc_info=True)
|
||||
|
||||
return UNKNOWN_ERROR
|
||||
|
||||
return exc_logging_wrapper
|
||||
|
||||
try:
|
||||
if not options.debug_mode:
|
||||
run = intercepts_unhandled_exc(self.run)
|
||||
else:
|
||||
run = self.run
|
||||
rich_traceback.install(show_locals=True)
|
||||
return run(options, args)
|
||||
finally:
|
||||
self.handle_pip_version_check(options)
|
||||
|
|
|
|||
|
|
@ -9,16 +9,15 @@ pass on state. To be consistent, all options will follow this design.
|
|||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import textwrap
|
||||
from functools import partial
|
||||
from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values
|
||||
from textwrap import dedent
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Dict, Optional, Tuple
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
|
|
@ -48,7 +47,7 @@ def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None:
|
|||
parser.error(msg)
|
||||
|
||||
|
||||
def make_option_group(group: dict[str, Any], parser: ConfigOptionParser) -> OptionGroup:
|
||||
def make_option_group(group: Dict[str, Any], parser: ConfigOptionParser) -> OptionGroup:
|
||||
"""
|
||||
Return an OptionGroup object
|
||||
group -- assumed to be dict with 'name' and 'options' keys
|
||||
|
|
@ -100,29 +99,6 @@ def check_dist_restriction(options: Values, check_target: bool = False) -> None:
|
|||
)
|
||||
|
||||
|
||||
def check_build_constraints(options: Values) -> None:
|
||||
"""Function for validating build constraints options.
|
||||
|
||||
:param options: The OptionParser options.
|
||||
"""
|
||||
if hasattr(options, "build_constraints") and options.build_constraints:
|
||||
if not options.build_isolation:
|
||||
raise CommandError(
|
||||
"--build-constraint cannot be used with --no-build-isolation."
|
||||
)
|
||||
|
||||
# Import here to avoid circular imports
|
||||
from pip._internal.network.session import PipSession
|
||||
from pip._internal.req.req_file import get_file_content
|
||||
|
||||
# Eagerly check build constraints file contents
|
||||
# is valid so that we don't fail in when trying
|
||||
# to check constraints in isolated build process
|
||||
with PipSession() as session:
|
||||
for constraint_file in options.build_constraints:
|
||||
get_file_content(constraint_file, session)
|
||||
|
||||
|
||||
def _path_option_check(option: Option, opt: str, value: str) -> str:
|
||||
return os.path.expanduser(value)
|
||||
|
||||
|
|
@ -183,7 +159,8 @@ require_virtualenv: Callable[..., Option] = partial(
|
|||
action="store_true",
|
||||
default=False,
|
||||
help=(
|
||||
"Allow pip to only run in a virtual environment; exit with an error otherwise."
|
||||
"Allow pip to only run in a virtual environment; "
|
||||
"exit with an error otherwise."
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -249,13 +226,9 @@ progress_bar: Callable[..., Option] = partial(
|
|||
"--progress-bar",
|
||||
dest="progress_bar",
|
||||
type="choice",
|
||||
choices=["auto", "on", "off", "raw"],
|
||||
default="auto",
|
||||
help=(
|
||||
"Specify whether the progress bar should be used. In 'auto'"
|
||||
" mode, --quiet will suppress all progress bars."
|
||||
" [auto, on, off, raw] (default: auto)"
|
||||
),
|
||||
choices=["on", "off"],
|
||||
default="on",
|
||||
help="Specify whether the progress bar should be used [on, off] (default: on)",
|
||||
)
|
||||
|
||||
log: Callable[..., Option] = partial(
|
||||
|
|
@ -287,8 +260,8 @@ keyring_provider: Callable[..., Option] = partial(
|
|||
default="auto",
|
||||
help=(
|
||||
"Enable the credential lookup via the keyring library if user input is allowed."
|
||||
" Specify which mechanism to use [auto, disabled, import, subprocess]."
|
||||
" (default: %default)"
|
||||
" Specify which mechanism to use [disabled, import, subprocess]."
|
||||
" (default: disabled)"
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -307,17 +280,8 @@ retries: Callable[..., Option] = partial(
|
|||
dest="retries",
|
||||
type="int",
|
||||
default=5,
|
||||
help="Maximum attempts to establish a new HTTP connection. (default: %default)",
|
||||
)
|
||||
|
||||
resume_retries: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--resume-retries",
|
||||
dest="resume_retries",
|
||||
type="int",
|
||||
default=5,
|
||||
help="Maximum attempts to resume or restart an incomplete download. "
|
||||
"(default: %default)",
|
||||
help="Maximum number of retries each connection should attempt "
|
||||
"(default %default times).",
|
||||
)
|
||||
|
||||
timeout: Callable[..., Option] = partial(
|
||||
|
|
@ -451,21 +415,6 @@ def constraints() -> Option:
|
|||
)
|
||||
|
||||
|
||||
def build_constraints() -> Option:
|
||||
return Option(
|
||||
"--build-constraint",
|
||||
dest="build_constraints",
|
||||
action="append",
|
||||
type="str",
|
||||
default=[],
|
||||
metavar="file",
|
||||
help=(
|
||||
"Constrain build dependencies using the given constraints file. "
|
||||
"This option can be used multiple times."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def requirements() -> Option:
|
||||
return Option(
|
||||
"-r",
|
||||
|
|
@ -596,7 +545,7 @@ platforms: Callable[..., Option] = partial(
|
|||
|
||||
|
||||
# This was made a separate function for unit-testing purposes.
|
||||
def _convert_python_version(value: str) -> tuple[tuple[int, ...], str | None]:
|
||||
def _convert_python_version(value: str) -> Tuple[Tuple[int, ...], Optional[str]]:
|
||||
"""
|
||||
Convert a version string like "3", "37", or "3.7.3" into a tuple of ints.
|
||||
|
||||
|
|
@ -784,46 +733,6 @@ no_deps: Callable[..., Option] = partial(
|
|||
help="Don't install package dependencies.",
|
||||
)
|
||||
|
||||
|
||||
def _handle_dependency_group(
|
||||
option: Option, opt: str, value: str, parser: OptionParser
|
||||
) -> None:
|
||||
"""
|
||||
Process a value provided for the --group option.
|
||||
|
||||
Splits on the rightmost ":", and validates that the path (if present) ends
|
||||
in `pyproject.toml`. Defaults the path to `pyproject.toml` when one is not given.
|
||||
|
||||
`:` cannot appear in dependency group names, so this is a safe and simple parse.
|
||||
|
||||
This is an optparse.Option callback for the dependency_groups option.
|
||||
"""
|
||||
path, sep, groupname = value.rpartition(":")
|
||||
if not sep:
|
||||
path = "pyproject.toml"
|
||||
else:
|
||||
# check for 'pyproject.toml' filenames using pathlib
|
||||
if pathlib.PurePath(path).name != "pyproject.toml":
|
||||
msg = "group paths use 'pyproject.toml' filenames"
|
||||
raise_option_error(parser, option=option, msg=msg)
|
||||
|
||||
parser.values.dependency_groups.append((path, groupname))
|
||||
|
||||
|
||||
dependency_groups: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--group",
|
||||
dest="dependency_groups",
|
||||
default=[],
|
||||
type=str,
|
||||
action="callback",
|
||||
callback=_handle_dependency_group,
|
||||
metavar="[path:]group",
|
||||
help='Install a named dependency-group from a "pyproject.toml" file. '
|
||||
'If a path is given, the name of the file must be "pyproject.toml". '
|
||||
'Defaults to using "pyproject.toml" in the current directory.',
|
||||
)
|
||||
|
||||
ignore_requires_python: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--ignore-requires-python",
|
||||
|
|
@ -849,16 +758,62 @@ check_build_deps: Callable[..., Option] = partial(
|
|||
dest="check_build_deps",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Check the build dependencies.",
|
||||
help="Check the build dependencies when PEP517 is used.",
|
||||
)
|
||||
|
||||
|
||||
def _handle_no_use_pep517(
|
||||
option: Option, opt: str, value: str, parser: OptionParser
|
||||
) -> None:
|
||||
"""
|
||||
Process a value provided for the --no-use-pep517 option.
|
||||
|
||||
This is an optparse.Option callback for the no_use_pep517 option.
|
||||
"""
|
||||
# Since --no-use-pep517 doesn't accept arguments, the value argument
|
||||
# will be None if --no-use-pep517 is passed via the command-line.
|
||||
# However, the value can be non-None if the option is triggered e.g.
|
||||
# by an environment variable, for example "PIP_NO_USE_PEP517=true".
|
||||
if value is not None:
|
||||
msg = """A value was passed for --no-use-pep517,
|
||||
probably using either the PIP_NO_USE_PEP517 environment variable
|
||||
or the "no-use-pep517" config file option. Use an appropriate value
|
||||
of the PIP_USE_PEP517 environment variable or the "use-pep517"
|
||||
config file option instead.
|
||||
"""
|
||||
raise_option_error(parser, option=option, msg=msg)
|
||||
|
||||
# If user doesn't wish to use pep517, we check if setuptools and wheel are installed
|
||||
# and raise error if it is not.
|
||||
packages = ("setuptools", "wheel")
|
||||
if not all(importlib.util.find_spec(package) for package in packages):
|
||||
msg = (
|
||||
f"It is not possible to use --no-use-pep517 "
|
||||
f"without {' and '.join(packages)} installed."
|
||||
)
|
||||
raise_option_error(parser, option=option, msg=msg)
|
||||
|
||||
# Otherwise, --no-use-pep517 was passed via the command-line.
|
||||
parser.values.use_pep517 = False
|
||||
|
||||
|
||||
use_pep517: Any = partial(
|
||||
Option,
|
||||
"--use-pep517",
|
||||
dest="use_pep517",
|
||||
action="store_true",
|
||||
default=True,
|
||||
default=None,
|
||||
help="Use PEP 517 for building source distributions "
|
||||
"(use --no-use-pep517 to force legacy behaviour).",
|
||||
)
|
||||
|
||||
no_use_pep517: Any = partial(
|
||||
Option,
|
||||
"--no-use-pep517",
|
||||
dest="use_pep517",
|
||||
action="callback",
|
||||
callback=_handle_no_use_pep517,
|
||||
default=None,
|
||||
help=SUPPRESS_HELP,
|
||||
)
|
||||
|
||||
|
|
@ -891,11 +846,30 @@ config_settings: Callable[..., Option] = partial(
|
|||
action="callback",
|
||||
callback=_handle_config_settings,
|
||||
metavar="settings",
|
||||
help="Configuration settings to be passed to the build backend. "
|
||||
help="Configuration settings to be passed to the PEP 517 build backend. "
|
||||
"Settings take the form KEY=VALUE. Use multiple --config-settings options "
|
||||
"to pass multiple keys to the backend.",
|
||||
)
|
||||
|
||||
build_options: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--build-option",
|
||||
dest="build_options",
|
||||
metavar="options",
|
||||
action="append",
|
||||
help="Extra arguments to be supplied to 'setup.py bdist_wheel'.",
|
||||
)
|
||||
|
||||
global_options: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--global-option",
|
||||
dest="global_options",
|
||||
action="append",
|
||||
metavar="options",
|
||||
help="Extra global options to be supplied to the setup.py "
|
||||
"call before the install or bdist_wheel command.",
|
||||
)
|
||||
|
||||
no_clean: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--no-clean",
|
||||
|
|
@ -913,20 +887,12 @@ pre: Callable[..., Option] = partial(
|
|||
"pip only finds stable versions.",
|
||||
)
|
||||
|
||||
json: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--json",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Output data in a machine-readable JSON format.",
|
||||
)
|
||||
|
||||
disable_pip_version_check: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--disable-pip-version-check",
|
||||
dest="disable_pip_version_check",
|
||||
action="store_true",
|
||||
default=False,
|
||||
default=True,
|
||||
help="Don't periodically check PyPI to determine whether a new version "
|
||||
"of pip is available for download. Implied with --no-index.",
|
||||
)
|
||||
|
|
@ -937,7 +903,7 @@ root_user_action: Callable[..., Option] = partial(
|
|||
dest="root_user_action",
|
||||
default="warn",
|
||||
choices=["warn", "ignore"],
|
||||
help="Action if pip is run as a root user [warn, ignore] (default: warn)",
|
||||
help="Action if pip is run as a root user. By default, a warning message is shown.",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1024,13 +990,12 @@ no_python_version_warning: Callable[..., Option] = partial(
|
|||
dest="no_python_version_warning",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=SUPPRESS_HELP, # No-op, a hold-over from the Python 2->3 transition.
|
||||
help="Silence deprecation warnings for upcoming unsupported Pythons.",
|
||||
)
|
||||
|
||||
|
||||
# Features that are now always on. A warning is printed if they are used.
|
||||
ALWAYS_ENABLED_FEATURES = [
|
||||
"truststore", # always on since 24.2
|
||||
"no-binary-enable-wheel-cache", # always on since 23.1
|
||||
]
|
||||
|
||||
|
|
@ -1043,7 +1008,7 @@ use_new_feature: Callable[..., Option] = partial(
|
|||
default=[],
|
||||
choices=[
|
||||
"fast-deps",
|
||||
"build-constraint",
|
||||
"truststore",
|
||||
]
|
||||
+ ALWAYS_ENABLED_FEATURES,
|
||||
help="Enable new functionality, that may be backward incompatible.",
|
||||
|
|
@ -1058,16 +1023,16 @@ use_deprecated_feature: Callable[..., Option] = partial(
|
|||
default=[],
|
||||
choices=[
|
||||
"legacy-resolver",
|
||||
"legacy-certs",
|
||||
],
|
||||
help=("Enable deprecated functionality, that will be removed in the future."),
|
||||
)
|
||||
|
||||
|
||||
##########
|
||||
# groups #
|
||||
##########
|
||||
|
||||
general_group: dict[str, Any] = {
|
||||
general_group: Dict[str, Any] = {
|
||||
"name": "General Options",
|
||||
"options": [
|
||||
help_,
|
||||
|
|
@ -1095,11 +1060,10 @@ general_group: dict[str, Any] = {
|
|||
no_python_version_warning,
|
||||
use_new_feature,
|
||||
use_deprecated_feature,
|
||||
resume_retries,
|
||||
],
|
||||
}
|
||||
|
||||
index_group: dict[str, Any] = {
|
||||
index_group: Dict[str, Any] = {
|
||||
"name": "Package Index Options",
|
||||
"options": [
|
||||
index_url,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from collections.abc import Generator
|
||||
from contextlib import AbstractContextManager, ExitStack, contextmanager
|
||||
from typing import TypeVar
|
||||
from contextlib import ExitStack, contextmanager
|
||||
from typing import ContextManager, Generator, TypeVar
|
||||
|
||||
_T = TypeVar("_T", covariant=True)
|
||||
|
||||
|
|
@ -22,7 +21,7 @@ class CommandContextMixIn:
|
|||
finally:
|
||||
self._in_main_context = False
|
||||
|
||||
def enter_context(self, context_provider: AbstractContextManager[_T]) -> _T:
|
||||
def enter_context(self, context_provider: ContextManager[_T]) -> _T:
|
||||
assert self._in_main_context
|
||||
|
||||
return self._main_context.enter_context(context_provider)
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
"""
|
||||
Contains command classes which may interact with an index / the network.
|
||||
|
||||
Unlike its sister module, req_command, this module still uses lazy imports
|
||||
so commands which don't always hit the network (e.g. list w/o --outdated or
|
||||
--uptodate) don't need waste time importing PipSession and friends.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from optparse import Values
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pip._vendor import certifi
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.command_context import CommandContextMixIn
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ssl import SSLContext
|
||||
|
||||
from pip._internal.network.session import PipSession
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def _create_truststore_ssl_context() -> SSLContext | None:
|
||||
if sys.version_info < (3, 10):
|
||||
logger.debug("Disabling truststore because Python version isn't 3.10+")
|
||||
return None
|
||||
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
logger.warning("Disabling truststore since ssl support is missing")
|
||||
return None
|
||||
|
||||
try:
|
||||
from pip._vendor import truststore
|
||||
except ImportError:
|
||||
logger.warning("Disabling truststore because platform isn't supported")
|
||||
return None
|
||||
|
||||
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
ctx.load_verify_locations(certifi.where())
|
||||
return ctx
|
||||
|
||||
|
||||
class SessionCommandMixin(CommandContextMixIn):
|
||||
"""
|
||||
A class mixin for command classes needing _build_session().
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._session: PipSession | None = None
|
||||
|
||||
@classmethod
|
||||
def _get_index_urls(cls, options: Values) -> list[str] | None:
|
||||
"""Return a list of index urls from user-provided options."""
|
||||
index_urls = []
|
||||
if not getattr(options, "no_index", False):
|
||||
url = getattr(options, "index_url", None)
|
||||
if url:
|
||||
index_urls.append(url)
|
||||
urls = getattr(options, "extra_index_urls", None)
|
||||
if urls:
|
||||
index_urls.extend(urls)
|
||||
# Return None rather than an empty list
|
||||
return index_urls or None
|
||||
|
||||
def get_default_session(self, options: Values) -> PipSession:
|
||||
"""Get a default-managed session."""
|
||||
if self._session is None:
|
||||
self._session = self.enter_context(self._build_session(options))
|
||||
# there's no type annotation on requests.Session, so it's
|
||||
# automatically ContextManager[Any] and self._session becomes Any,
|
||||
# then https://github.com/python/mypy/issues/7696 kicks in
|
||||
assert self._session is not None
|
||||
return self._session
|
||||
|
||||
def _build_session(
|
||||
self,
|
||||
options: Values,
|
||||
retries: int | None = None,
|
||||
timeout: int | None = None,
|
||||
) -> PipSession:
|
||||
from pip._internal.network.session import PipSession
|
||||
|
||||
cache_dir = options.cache_dir
|
||||
assert not cache_dir or os.path.isabs(cache_dir)
|
||||
|
||||
if "legacy-certs" not in options.deprecated_features_enabled:
|
||||
ssl_context = _create_truststore_ssl_context()
|
||||
else:
|
||||
ssl_context = None
|
||||
|
||||
session = PipSession(
|
||||
cache=os.path.join(cache_dir, "http-v2") if cache_dir else None,
|
||||
retries=retries if retries is not None else options.retries,
|
||||
trusted_hosts=options.trusted_hosts,
|
||||
index_urls=self._get_index_urls(options),
|
||||
ssl_context=ssl_context,
|
||||
)
|
||||
|
||||
# Handle custom ca-bundles from the user
|
||||
if options.cert:
|
||||
session.verify = options.cert
|
||||
|
||||
# Handle SSL client certificate
|
||||
if options.client_cert:
|
||||
session.cert = options.client_cert
|
||||
|
||||
# Handle timeouts
|
||||
if options.timeout or timeout:
|
||||
session.timeout = timeout if timeout is not None else options.timeout
|
||||
|
||||
# Handle configured proxies
|
||||
if options.proxy:
|
||||
session.proxies = {
|
||||
"http": options.proxy,
|
||||
"https": options.proxy,
|
||||
}
|
||||
session.trust_env = False
|
||||
session.pip_proxy = options.proxy
|
||||
|
||||
# Determine if we can prompt the user for authentication or not
|
||||
session.auth.prompting = not options.no_input
|
||||
session.auth.keyring_provider = options.keyring_provider
|
||||
|
||||
return session
|
||||
|
||||
|
||||
def _pip_self_version_check(session: PipSession, options: Values) -> None:
|
||||
from pip._internal.self_outdated_check import pip_self_version_check as check
|
||||
|
||||
check(session, options)
|
||||
|
||||
|
||||
class IndexGroupCommand(Command, SessionCommandMixin):
|
||||
"""
|
||||
Abstract base class for commands with the index_group options.
|
||||
|
||||
This also corresponds to the commands that permit the pip version check.
|
||||
"""
|
||||
|
||||
def handle_pip_version_check(self, options: Values) -> None:
|
||||
"""
|
||||
Do the pip version check if not disabled.
|
||||
|
||||
This overrides the default behavior of not doing the check.
|
||||
"""
|
||||
# Make sure the index_group options are present.
|
||||
assert hasattr(options, "no_index")
|
||||
|
||||
if options.disable_pip_version_check or options.no_index:
|
||||
return
|
||||
|
||||
try:
|
||||
# Otherwise, check if we're using the latest version of pip available.
|
||||
session = self._build_session(
|
||||
options,
|
||||
retries=0,
|
||||
timeout=min(5, options.timeout),
|
||||
)
|
||||
with session:
|
||||
_pip_self_version_check(session, options)
|
||||
except Exception:
|
||||
logger.warning("There was an error checking the latest version of pip.")
|
||||
logger.debug("See below for error", exc_info=True)
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
"""Primary application entrypoint."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
"""Primary application entrypoint.
|
||||
"""
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from typing import List, Optional
|
||||
|
||||
from pip._internal.cli.autocompletion import autocomplete
|
||||
from pip._internal.cli.main_parser import parse_command
|
||||
|
|
@ -44,7 +43,7 @@ logger = logging.getLogger(__name__)
|
|||
# main, this should not be an issue in practice.
|
||||
|
||||
|
||||
def main(args: list[str] | None = None) -> int:
|
||||
def main(args: Optional[List[str]] = None) -> int:
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
"""A single place for constructing and exposing the main parser"""
|
||||
|
||||
from __future__ import annotations
|
||||
"""A single place for constructing and exposing the main parser
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from pip._internal.build_env import get_runnable_pip
|
||||
from pip._internal.cli import cmdoptions
|
||||
|
|
@ -47,7 +47,7 @@ def create_main_parser() -> ConfigOptionParser:
|
|||
return parser
|
||||
|
||||
|
||||
def identify_python_interpreter(python: str) -> str | None:
|
||||
def identify_python_interpreter(python: str) -> Optional[str]:
|
||||
# If the named file exists, use it.
|
||||
# If it's a directory, assume it's a virtual environment and
|
||||
# look for the environment's Python executable.
|
||||
|
|
@ -66,7 +66,7 @@ def identify_python_interpreter(python: str) -> str | None:
|
|||
return None
|
||||
|
||||
|
||||
def parse_command(args: list[str]) -> tuple[str, list[str]]:
|
||||
def parse_command(args: List[str]) -> Tuple[str, List[str]]:
|
||||
parser = create_main_parser()
|
||||
|
||||
# Note: parser calls disable_interspersed_args(), so the result of this
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
"""Base option parser setup"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import optparse
|
||||
import shutil
|
||||
import sys
|
||||
import textwrap
|
||||
from collections.abc import Generator
|
||||
from contextlib import suppress
|
||||
from typing import Any, NoReturn
|
||||
from typing import Any, Dict, Generator, List, Tuple
|
||||
|
||||
from pip._internal.cli.status_codes import UNKNOWN_ERROR
|
||||
from pip._internal.configuration import Configuration, ConfigurationError
|
||||
|
|
@ -70,7 +67,7 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
|
|||
msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " "))
|
||||
return msg
|
||||
|
||||
def format_description(self, description: str | None) -> str:
|
||||
def format_description(self, description: str) -> str:
|
||||
# leave full control over description to us
|
||||
if description:
|
||||
if hasattr(self.parser, "main"):
|
||||
|
|
@ -88,7 +85,7 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
|
|||
else:
|
||||
return ""
|
||||
|
||||
def format_epilog(self, epilog: str | None) -> str:
|
||||
def format_epilog(self, epilog: str) -> str:
|
||||
# leave full control over epilog to us
|
||||
if epilog:
|
||||
return epilog
|
||||
|
|
@ -145,7 +142,7 @@ class CustomOptionParser(optparse.OptionParser):
|
|||
return group
|
||||
|
||||
@property
|
||||
def option_list_all(self) -> list[optparse.Option]:
|
||||
def option_list_all(self) -> List[optparse.Option]:
|
||||
"""Get a list of all options, including those in option groups."""
|
||||
res = self.option_list[:]
|
||||
for i in self.option_groups:
|
||||
|
|
@ -180,34 +177,33 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
|
||||
def _get_ordered_configuration_items(
|
||||
self,
|
||||
) -> Generator[tuple[str, Any], None, None]:
|
||||
) -> Generator[Tuple[str, Any], None, None]:
|
||||
# Configuration gives keys in an unordered manner. Order them.
|
||||
override_order = ["global", self.name, ":env:"]
|
||||
|
||||
# Pool the options into different groups
|
||||
section_items: dict[str, list[tuple[str, Any]]] = {
|
||||
section_items: Dict[str, List[Tuple[str, Any]]] = {
|
||||
name: [] for name in override_order
|
||||
}
|
||||
for section_key, val in self.config.items():
|
||||
# ignore empty values
|
||||
if not val:
|
||||
logger.debug(
|
||||
"Ignoring configuration key '%s' as it's value is empty.",
|
||||
section_key,
|
||||
)
|
||||
continue
|
||||
|
||||
for _, value in self.config.items(): # noqa: PERF102
|
||||
for section_key, val in value.items():
|
||||
# ignore empty values
|
||||
if not val:
|
||||
logger.debug(
|
||||
"Ignoring configuration key '%s' as its value is empty.",
|
||||
section_key,
|
||||
)
|
||||
continue
|
||||
|
||||
section, key = section_key.split(".", 1)
|
||||
if section in override_order:
|
||||
section_items[section].append((key, val))
|
||||
section, key = section_key.split(".", 1)
|
||||
if section in override_order:
|
||||
section_items[section].append((key, val))
|
||||
|
||||
# Yield each group in their override order
|
||||
for section in override_order:
|
||||
yield from section_items[section]
|
||||
for key, val in section_items[section]:
|
||||
yield key, val
|
||||
|
||||
def _update_defaults(self, defaults: dict[str, Any]) -> dict[str, Any]:
|
||||
def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Updates the given defaults with values from the config files and
|
||||
the environ. Does a little special handling for certain types of
|
||||
options (lists)."""
|
||||
|
|
@ -293,6 +289,6 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
defaults[option.dest] = option.check_value(opt_str, default)
|
||||
return optparse.Values(defaults)
|
||||
|
||||
def error(self, msg: str) -> NoReturn:
|
||||
def error(self, msg: str) -> None:
|
||||
self.print_usage(sys.stderr)
|
||||
self.exit(UNKNOWN_ERROR, f"{msg}\n")
|
||||
|
|
|
|||
|
|
@ -1,15 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import sys
|
||||
from collections.abc import Generator, Iterable, Iterator
|
||||
from typing import Callable, Literal, TypeVar
|
||||
from typing import Callable, Generator, Iterable, Iterator, Optional, Tuple
|
||||
|
||||
from pip._vendor.rich.progress import (
|
||||
BarColumn,
|
||||
DownloadColumn,
|
||||
FileSizeColumn,
|
||||
MofNCompleteColumn,
|
||||
Progress,
|
||||
ProgressColumn,
|
||||
SpinnerColumn,
|
||||
|
|
@ -19,27 +14,22 @@ from pip._vendor.rich.progress import (
|
|||
TransferSpeedColumn,
|
||||
)
|
||||
|
||||
from pip._internal.cli.spinners import RateLimiter
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.logging import get_console, get_indentation
|
||||
from pip._internal.utils.logging import get_indentation
|
||||
|
||||
T = TypeVar("T")
|
||||
ProgressRenderer = Callable[[Iterable[T]], Iterator[T]]
|
||||
BarType = Literal["on", "off", "raw"]
|
||||
DownloadProgressRenderer = Callable[[Iterable[bytes]], Iterator[bytes]]
|
||||
|
||||
|
||||
def _rich_download_progress_bar(
|
||||
def _rich_progress_bar(
|
||||
iterable: Iterable[bytes],
|
||||
*,
|
||||
bar_type: BarType,
|
||||
size: int | None,
|
||||
initial_progress: int | None = None,
|
||||
bar_type: str,
|
||||
size: int,
|
||||
) -> Generator[bytes, None, None]:
|
||||
assert bar_type == "on", "This should only be used in the default mode."
|
||||
|
||||
if not size:
|
||||
total = float("inf")
|
||||
columns: tuple[ProgressColumn, ...] = (
|
||||
columns: Tuple[ProgressColumn, ...] = (
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
SpinnerColumn("line", speed=1.5),
|
||||
FileSizeColumn(),
|
||||
|
|
@ -53,99 +43,26 @@ def _rich_download_progress_bar(
|
|||
BarColumn(),
|
||||
DownloadColumn(),
|
||||
TransferSpeedColumn(),
|
||||
TextColumn("{task.fields[time_description]}"),
|
||||
TimeRemainingColumn(elapsed_when_finished=True),
|
||||
TextColumn("eta"),
|
||||
TimeRemainingColumn(),
|
||||
)
|
||||
|
||||
progress = Progress(*columns, refresh_per_second=5)
|
||||
task_id = progress.add_task(
|
||||
" " * (get_indentation() + 2), total=total, time_description="eta"
|
||||
)
|
||||
if initial_progress is not None:
|
||||
progress.update(task_id, advance=initial_progress)
|
||||
progress = Progress(*columns, refresh_per_second=30)
|
||||
task_id = progress.add_task(" " * (get_indentation() + 2), total=total)
|
||||
with progress:
|
||||
for chunk in iterable:
|
||||
yield chunk
|
||||
progress.update(task_id, advance=len(chunk))
|
||||
progress.update(task_id, time_description="")
|
||||
|
||||
|
||||
def _rich_install_progress_bar(
|
||||
iterable: Iterable[InstallRequirement], *, total: int
|
||||
) -> Iterator[InstallRequirement]:
|
||||
columns = (
|
||||
TextColumn("{task.fields[indent]}"),
|
||||
BarColumn(),
|
||||
MofNCompleteColumn(),
|
||||
TextColumn("{task.description}"),
|
||||
)
|
||||
console = get_console()
|
||||
|
||||
bar = Progress(*columns, refresh_per_second=6, console=console, transient=True)
|
||||
# Hiding the progress bar at initialization forces a refresh cycle to occur
|
||||
# until the bar appears, avoiding very short flashes.
|
||||
task = bar.add_task("", total=total, indent=" " * get_indentation(), visible=False)
|
||||
with bar:
|
||||
for req in iterable:
|
||||
bar.update(task, description=rf"\[{req.name}]", visible=True)
|
||||
yield req
|
||||
bar.advance(task)
|
||||
|
||||
|
||||
def _raw_progress_bar(
|
||||
iterable: Iterable[bytes],
|
||||
*,
|
||||
size: int | None,
|
||||
initial_progress: int | None = None,
|
||||
) -> Generator[bytes, None, None]:
|
||||
def write_progress(current: int, total: int) -> None:
|
||||
sys.stdout.write(f"Progress {current} of {total}\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
current = initial_progress or 0
|
||||
total = size or 0
|
||||
rate_limiter = RateLimiter(0.25)
|
||||
|
||||
write_progress(current, total)
|
||||
for chunk in iterable:
|
||||
current += len(chunk)
|
||||
if rate_limiter.ready() or current == total:
|
||||
write_progress(current, total)
|
||||
rate_limiter.reset()
|
||||
yield chunk
|
||||
|
||||
|
||||
def get_download_progress_renderer(
|
||||
*, bar_type: BarType, size: int | None = None, initial_progress: int | None = None
|
||||
) -> ProgressRenderer[bytes]:
|
||||
*, bar_type: str, size: Optional[int] = None
|
||||
) -> DownloadProgressRenderer:
|
||||
"""Get an object that can be used to render the download progress.
|
||||
|
||||
Returns a callable, that takes an iterable to "wrap".
|
||||
"""
|
||||
if bar_type == "on":
|
||||
return functools.partial(
|
||||
_rich_download_progress_bar,
|
||||
bar_type=bar_type,
|
||||
size=size,
|
||||
initial_progress=initial_progress,
|
||||
)
|
||||
elif bar_type == "raw":
|
||||
return functools.partial(
|
||||
_raw_progress_bar,
|
||||
size=size,
|
||||
initial_progress=initial_progress,
|
||||
)
|
||||
return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size)
|
||||
else:
|
||||
return iter # no-op, when passed an iterator
|
||||
|
||||
|
||||
def get_install_progress_renderer(
|
||||
*, bar_type: BarType, total: int
|
||||
) -> ProgressRenderer[InstallRequirement]:
|
||||
"""Get an object that can be used to render the install progress.
|
||||
Returns a callable, that takes an iterable to "wrap".
|
||||
"""
|
||||
if bar_type == "on":
|
||||
return functools.partial(_rich_install_progress_bar, total=total)
|
||||
else:
|
||||
return iter
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
"""Contains the RequirementCommand base class.
|
||||
"""Contains the Command base classes that depend on PipSession.
|
||||
|
||||
This class is in a separate module so the commands that do not always
|
||||
need PackageFinder capability don't unnecessarily import the
|
||||
The classes in this module are in a separate module so the commands not
|
||||
needing download / PackageFinder capability don't unnecessarily import the
|
||||
PackageFinder machinery and all its vendored dependencies, etc.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from functools import partial
|
||||
from optparse import Values
|
||||
from typing import Any, Callable, TypeVar
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Tuple
|
||||
|
||||
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.cli.base_command import Command
|
||||
from pip._internal.cli.command_context import CommandContextMixIn
|
||||
from pip._internal.exceptions import CommandError, PreviousBuildDirError
|
||||
from pip._internal.index.collector import LinkCollector
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
|
|
@ -32,27 +30,162 @@ from pip._internal.req.constructors import (
|
|||
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.self_outdated_check import pip_self_version_check
|
||||
from pip._internal.utils.temp_dir import (
|
||||
TempDirectory,
|
||||
TempDirectoryTypeRegistry,
|
||||
tempdir_kinds,
|
||||
)
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ssl import SSLContext
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def should_ignore_regular_constraints(options: Values) -> bool:
|
||||
def _create_truststore_ssl_context() -> Optional["SSLContext"]:
|
||||
if sys.version_info < (3, 10):
|
||||
raise CommandError("The truststore feature is only available for Python 3.10+")
|
||||
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
logger.warning("Disabling truststore since ssl support is missing")
|
||||
return None
|
||||
|
||||
try:
|
||||
from pip._vendor import truststore
|
||||
except ImportError as e:
|
||||
raise CommandError(f"The truststore feature is unavailable: {e}")
|
||||
|
||||
return truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
|
||||
|
||||
class SessionCommandMixin(CommandContextMixIn):
|
||||
|
||||
"""
|
||||
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.
|
||||
A class mixin for command classes needing _build_session().
|
||||
"""
|
||||
|
||||
return os.environ.get("_PIP_IN_BUILD_IGNORE_CONSTRAINTS") == "1"
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._session: Optional[PipSession] = None
|
||||
|
||||
@classmethod
|
||||
def _get_index_urls(cls, options: Values) -> Optional[List[str]]:
|
||||
"""Return a list of index urls from user-provided options."""
|
||||
index_urls = []
|
||||
if not getattr(options, "no_index", False):
|
||||
url = getattr(options, "index_url", None)
|
||||
if url:
|
||||
index_urls.append(url)
|
||||
urls = getattr(options, "extra_index_urls", None)
|
||||
if urls:
|
||||
index_urls.extend(urls)
|
||||
# Return None rather than an empty list
|
||||
return index_urls or None
|
||||
|
||||
def get_default_session(self, options: Values) -> PipSession:
|
||||
"""Get a default-managed session."""
|
||||
if self._session is None:
|
||||
self._session = self.enter_context(self._build_session(options))
|
||||
# there's no type annotation on requests.Session, so it's
|
||||
# automatically ContextManager[Any] and self._session becomes Any,
|
||||
# then https://github.com/python/mypy/issues/7696 kicks in
|
||||
assert self._session is not None
|
||||
return self._session
|
||||
|
||||
def _build_session(
|
||||
self,
|
||||
options: Values,
|
||||
retries: Optional[int] = None,
|
||||
timeout: Optional[int] = None,
|
||||
fallback_to_certifi: bool = False,
|
||||
) -> PipSession:
|
||||
cache_dir = options.cache_dir
|
||||
assert not cache_dir or os.path.isabs(cache_dir)
|
||||
|
||||
if "truststore" in options.features_enabled:
|
||||
try:
|
||||
ssl_context = _create_truststore_ssl_context()
|
||||
except Exception:
|
||||
if not fallback_to_certifi:
|
||||
raise
|
||||
ssl_context = None
|
||||
else:
|
||||
ssl_context = None
|
||||
|
||||
session = PipSession(
|
||||
cache=os.path.join(cache_dir, "http-v2") if cache_dir else None,
|
||||
retries=retries if retries is not None else options.retries,
|
||||
trusted_hosts=options.trusted_hosts,
|
||||
index_urls=self._get_index_urls(options),
|
||||
ssl_context=ssl_context,
|
||||
)
|
||||
|
||||
# Handle custom ca-bundles from the user
|
||||
if options.cert:
|
||||
session.verify = options.cert
|
||||
|
||||
# Handle SSL client certificate
|
||||
if options.client_cert:
|
||||
session.cert = options.client_cert
|
||||
|
||||
# Handle timeouts
|
||||
if options.timeout or timeout:
|
||||
session.timeout = timeout if timeout is not None else options.timeout
|
||||
|
||||
# Handle configured proxies
|
||||
if options.proxy:
|
||||
session.proxies = {
|
||||
"http": options.proxy,
|
||||
"https": options.proxy,
|
||||
}
|
||||
|
||||
# Determine if we can prompt the user for authentication or not
|
||||
session.auth.prompting = not options.no_input
|
||||
session.auth.keyring_provider = options.keyring_provider
|
||||
|
||||
return session
|
||||
|
||||
|
||||
class IndexGroupCommand(Command, SessionCommandMixin):
|
||||
|
||||
"""
|
||||
Abstract base class for commands with the index_group options.
|
||||
|
||||
This also corresponds to the commands that permit the pip version check.
|
||||
"""
|
||||
|
||||
def handle_pip_version_check(self, options: Values) -> None:
|
||||
"""
|
||||
Do the pip version check if not disabled.
|
||||
|
||||
This overrides the default behavior of not doing the check.
|
||||
"""
|
||||
# Make sure the index_group options are present.
|
||||
assert hasattr(options, "no_index")
|
||||
|
||||
if options.disable_pip_version_check or options.no_index:
|
||||
return
|
||||
|
||||
# Otherwise, check if we're using the latest version of pip available.
|
||||
session = self._build_session(
|
||||
options,
|
||||
retries=0,
|
||||
timeout=min(5, options.timeout),
|
||||
# This is set to ensure the function does not fail when truststore is
|
||||
# specified in use-feature but cannot be loaded. This usually raises a
|
||||
# CommandError and shows a nice user-facing error, but this function is not
|
||||
# called in that try-except block.
|
||||
fallback_to_certifi=True,
|
||||
)
|
||||
with session:
|
||||
pip_self_version_check(session, options)
|
||||
|
||||
|
||||
KEEPABLE_TEMPDIR_TYPES = [
|
||||
|
|
@ -62,12 +195,37 @@ KEEPABLE_TEMPDIR_TYPES = [
|
|||
]
|
||||
|
||||
|
||||
_CommandT = TypeVar("_CommandT", bound="RequirementCommand")
|
||||
def warn_if_run_as_root() -> None:
|
||||
"""Output a warning for sudo users on Unix.
|
||||
|
||||
In a virtual environment, sudo pip still writes to virtualenv.
|
||||
On Windows, users may run pip as Administrator without issues.
|
||||
This warning only applies to Unix root users outside of virtualenv.
|
||||
"""
|
||||
if running_under_virtualenv():
|
||||
return
|
||||
if not hasattr(os, "getuid"):
|
||||
return
|
||||
# On Windows, there are no "system managed" Python packages. Installing as
|
||||
# Administrator via pip is the correct way of updating system environments.
|
||||
#
|
||||
# We choose sys.platform over utils.compat.WINDOWS here to enable Mypy platform
|
||||
# checks: https://mypy.readthedocs.io/en/stable/common_issues.html
|
||||
if sys.platform == "win32" or sys.platform == "cygwin":
|
||||
return
|
||||
|
||||
if os.getuid() != 0:
|
||||
return
|
||||
|
||||
logger.warning(
|
||||
"Running pip as the 'root' user can result in broken permissions and "
|
||||
"conflicting behaviour with the system package manager. "
|
||||
"It is recommended to use a virtual environment instead: "
|
||||
"https://pip.pypa.io/warnings/venv"
|
||||
)
|
||||
|
||||
|
||||
def with_cleanup(
|
||||
func: Callable[[_CommandT, Values, list[str]], int],
|
||||
) -> Callable[[_CommandT, Values, list[str]], int]:
|
||||
def with_cleanup(func: Any) -> Any:
|
||||
"""Decorator for common logic related to managing temporary
|
||||
directories.
|
||||
"""
|
||||
|
|
@ -76,7 +234,9 @@ def with_cleanup(
|
|||
for t in KEEPABLE_TEMPDIR_TYPES:
|
||||
registry.set_delete(t, False)
|
||||
|
||||
def wrapper(self: _CommandT, options: Values, args: list[str]) -> int:
|
||||
def wrapper(
|
||||
self: RequirementCommand, options: Values, args: List[Any]
|
||||
) -> Optional[int]:
|
||||
assert self.tempdir_registry is not None
|
||||
if options.no_clean:
|
||||
configure_tempdir_registry(self.tempdir_registry)
|
||||
|
|
@ -97,7 +257,6 @@ 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
|
||||
|
|
@ -117,7 +276,7 @@ class RequirementCommand(IndexGroupCommand):
|
|||
session: PipSession,
|
||||
finder: PackageFinder,
|
||||
use_user_site: bool,
|
||||
download_dir: str | None = None,
|
||||
download_dir: Optional[str] = None,
|
||||
verbosity: int = 0,
|
||||
) -> RequirementPreparer:
|
||||
"""
|
||||
|
|
@ -146,22 +305,11 @@ class RequirementCommand(IndexGroupCommand):
|
|||
"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
|
||||
)
|
||||
|
||||
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,
|
||||
|
|
@ -172,7 +320,6 @@ class RequirementCommand(IndexGroupCommand):
|
|||
lazy_wheel=lazy_wheel,
|
||||
verbosity=verbosity,
|
||||
legacy_resolver=legacy_resolver,
|
||||
resume_retries=options.resume_retries,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -181,13 +328,14 @@ class RequirementCommand(IndexGroupCommand):
|
|||
preparer: RequirementPreparer,
|
||||
finder: PackageFinder,
|
||||
options: Values,
|
||||
wheel_cache: WheelCache | None = None,
|
||||
wheel_cache: Optional[WheelCache] = 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,
|
||||
use_pep517: Optional[bool] = None,
|
||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
||||
) -> BaseResolver:
|
||||
"""
|
||||
Create a Resolver instance for the given parameters.
|
||||
|
|
@ -195,6 +343,7 @@ class RequirementCommand(IndexGroupCommand):
|
|||
make_install_req = partial(
|
||||
install_req_from_req_string,
|
||||
isolated=options.isolated_mode,
|
||||
use_pep517=use_pep517,
|
||||
)
|
||||
resolver_variant = cls.determine_resolver_variant(options)
|
||||
# The long import name and duplicated invocation is needed to convince
|
||||
|
|
@ -234,56 +383,47 @@ class RequirementCommand(IndexGroupCommand):
|
|||
|
||||
def get_requirements(
|
||||
self,
|
||||
args: list[str],
|
||||
args: List[str],
|
||||
options: Values,
|
||||
finder: PackageFinder,
|
||||
session: PipSession,
|
||||
) -> list[InstallRequirement]:
|
||||
) -> 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)
|
||||
requirements: List[InstallRequirement] = []
|
||||
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,
|
||||
use_pep517=options.use_pep517,
|
||||
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,
|
||||
use_pep517=options.use_pep517,
|
||||
config_settings=getattr(options, "config_settings", None),
|
||||
)
|
||||
requirements.append(req_to_add)
|
||||
|
|
@ -296,12 +436,11 @@ class RequirementCommand(IndexGroupCommand):
|
|||
req_to_add = install_req_from_parsed_requirement(
|
||||
parsed_req,
|
||||
isolated=options.isolated_mode,
|
||||
use_pep517=options.use_pep517,
|
||||
user_supplied=True,
|
||||
config_settings=(
|
||||
parsed_req.options.get("config_settings")
|
||||
if parsed_req.options
|
||||
else None
|
||||
),
|
||||
config_settings=parsed_req.options.get("config_settings")
|
||||
if parsed_req.options
|
||||
else None,
|
||||
)
|
||||
requirements.append(req_to_add)
|
||||
|
||||
|
|
@ -309,12 +448,7 @@ class RequirementCommand(IndexGroupCommand):
|
|||
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
|
||||
):
|
||||
if not (args or options.editables or options.requirements):
|
||||
opts = {"name": self.name}
|
||||
if options.find_links:
|
||||
raise CommandError(
|
||||
|
|
@ -346,8 +480,8 @@ class RequirementCommand(IndexGroupCommand):
|
|||
self,
|
||||
options: Values,
|
||||
session: PipSession,
|
||||
target_python: TargetPython | None = None,
|
||||
ignore_requires_python: bool | None = None,
|
||||
target_python: Optional[TargetPython] = None,
|
||||
ignore_requires_python: Optional[bool] = None,
|
||||
) -> PackageFinder:
|
||||
"""
|
||||
Create a package finder appropriate to this requirement command.
|
||||
|
|
|
|||
|
|
@ -1,31 +1,15 @@
|
|||
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 typing import IO, Generator, Optional
|
||||
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.logging import get_console, get_indentation
|
||||
from pip._internal.utils.logging import get_indentation
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SPINNER_CHARS: Final = r"-\|/"
|
||||
SPINS_PER_SECOND: Final = 8
|
||||
|
||||
|
||||
class SpinnerInterface:
|
||||
def spin(self) -> None:
|
||||
|
|
@ -39,10 +23,10 @@ class InteractiveSpinner(SpinnerInterface):
|
|||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
file: IO[str] | None = None,
|
||||
spin_chars: str = SPINNER_CHARS,
|
||||
file: Optional[IO[str]] = None,
|
||||
spin_chars: str = "-\\|/",
|
||||
# Empirically, 8 updates/second looks nice
|
||||
min_update_interval_seconds: float = 1 / SPINS_PER_SECOND,
|
||||
min_update_interval_seconds: float = 0.125,
|
||||
):
|
||||
self._message = message
|
||||
if file is None:
|
||||
|
|
@ -152,66 +136,6 @@ def open_spinner(message: str) -> Generator[SpinnerInterface, None, None]:
|
|||
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"
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,9 @@
|
|||
Package containing all pip commands
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
from collections import namedtuple
|
||||
from typing import Any
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
|
||||
|
|
@ -19,17 +17,12 @@ CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")
|
|||
# Even though the module path starts with the same "pip._internal.commands"
|
||||
# prefix, the full path makes testing easier (specifically when modifying
|
||||
# `commands_dict` in test setup / teardown).
|
||||
commands_dict: dict[str, CommandInfo] = {
|
||||
commands_dict: Dict[str, CommandInfo] = {
|
||||
"install": CommandInfo(
|
||||
"pip._internal.commands.install",
|
||||
"InstallCommand",
|
||||
"Install packages.",
|
||||
),
|
||||
"lock": CommandInfo(
|
||||
"pip._internal.commands.lock",
|
||||
"LockCommand",
|
||||
"Generate a lock file.",
|
||||
),
|
||||
"download": CommandInfo(
|
||||
"pip._internal.commands.download",
|
||||
"DownloadCommand",
|
||||
|
|
@ -125,7 +118,7 @@ def create_command(name: str, **kwargs: Any) -> Command:
|
|||
return command
|
||||
|
||||
|
||||
def get_similar_commands(name: str) -> str | None:
|
||||
def get_similar_commands(name: str) -> Optional[str]:
|
||||
"""Command name auto-correct."""
|
||||
from difflib import get_close_matches
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,14 +1,13 @@
|
|||
import os
|
||||
import textwrap
|
||||
from optparse import Values
|
||||
from typing import Callable
|
||||
from typing import Any, List
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pip._internal.exceptions import CommandError, PipError
|
||||
from pip._internal.utils import filesystem
|
||||
from pip._internal.utils.logging import getLogger
|
||||
from pip._internal.utils.misc import format_size
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
|
@ -49,8 +48,8 @@ class CacheCommand(Command):
|
|||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
|
||||
return {
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
handlers = {
|
||||
"dir": self.get_cache_dir,
|
||||
"info": self.get_cache_info,
|
||||
"list": self.list_cache_items,
|
||||
|
|
@ -58,18 +57,15 @@ class CacheCommand(Command):
|
|||
"purge": self.purge_cache,
|
||||
}
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
handler_map = self.handler_map()
|
||||
|
||||
if not options.cache_dir:
|
||||
logger.error("pip cache commands can not function since cache is disabled.")
|
||||
return ERROR
|
||||
|
||||
# Determine action
|
||||
if not args or args[0] not in handler_map:
|
||||
if not args or args[0] not in handlers:
|
||||
logger.error(
|
||||
"Need an action (%s) to perform.",
|
||||
", ".join(sorted(handler_map)),
|
||||
", ".join(sorted(handlers)),
|
||||
)
|
||||
return ERROR
|
||||
|
||||
|
|
@ -77,20 +73,20 @@ class CacheCommand(Command):
|
|||
|
||||
# Error handling happens here, not in the action-handlers.
|
||||
try:
|
||||
handler_map[action](options, args[1:])
|
||||
handlers[action](options, args[1:])
|
||||
except PipError as e:
|
||||
logger.error(e.args[0])
|
||||
return ERROR
|
||||
|
||||
return SUCCESS
|
||||
|
||||
def get_cache_dir(self, options: Values, args: list[str]) -> None:
|
||||
def get_cache_dir(self, options: Values, args: List[Any]) -> None:
|
||||
if args:
|
||||
raise CommandError("Too many arguments")
|
||||
|
||||
logger.info(options.cache_dir)
|
||||
|
||||
def get_cache_info(self, options: Values, args: list[str]) -> None:
|
||||
def get_cache_info(self, options: Values, args: List[Any]) -> None:
|
||||
if args:
|
||||
raise CommandError("Too many arguments")
|
||||
|
||||
|
|
@ -132,7 +128,7 @@ class CacheCommand(Command):
|
|||
|
||||
logger.info(message)
|
||||
|
||||
def list_cache_items(self, options: Values, args: list[str]) -> None:
|
||||
def list_cache_items(self, options: Values, args: List[Any]) -> None:
|
||||
if len(args) > 1:
|
||||
raise CommandError("Too many arguments")
|
||||
|
||||
|
|
@ -147,7 +143,7 @@ class CacheCommand(Command):
|
|||
else:
|
||||
self.format_for_abspath(files)
|
||||
|
||||
def format_for_human(self, files: list[str]) -> None:
|
||||
def format_for_human(self, files: List[str]) -> None:
|
||||
if not files:
|
||||
logger.info("No locally built wheels cached.")
|
||||
return
|
||||
|
|
@ -160,11 +156,11 @@ class CacheCommand(Command):
|
|||
logger.info("Cache contents:\n")
|
||||
logger.info("\n".join(sorted(results)))
|
||||
|
||||
def format_for_abspath(self, files: list[str]) -> None:
|
||||
def format_for_abspath(self, files: List[str]) -> None:
|
||||
if files:
|
||||
logger.info("\n".join(sorted(files)))
|
||||
|
||||
def remove_cache_items(self, options: Values, args: list[str]) -> None:
|
||||
def remove_cache_items(self, options: Values, args: List[Any]) -> None:
|
||||
if len(args) > 1:
|
||||
raise CommandError("Too many arguments")
|
||||
|
||||
|
|
@ -184,14 +180,12 @@ class CacheCommand(Command):
|
|||
if not files:
|
||||
logger.warning(no_matching_msg)
|
||||
|
||||
bytes_removed = 0
|
||||
for filename in files:
|
||||
bytes_removed += os.stat(filename).st_size
|
||||
os.unlink(filename)
|
||||
logger.verbose("Removed %s", filename)
|
||||
logger.info("Files removed: %s (%s)", len(files), format_size(bytes_removed))
|
||||
logger.info("Files removed: %s", len(files))
|
||||
|
||||
def purge_cache(self, options: Values, args: list[str]) -> None:
|
||||
def purge_cache(self, options: Values, args: List[Any]) -> None:
|
||||
if args:
|
||||
raise CommandError("Too many arguments")
|
||||
|
||||
|
|
@ -200,14 +194,14 @@ class CacheCommand(Command):
|
|||
def _cache_dir(self, options: Values, subdir: str) -> str:
|
||||
return os.path.join(options.cache_dir, subdir)
|
||||
|
||||
def _find_http_files(self, options: Values) -> list[str]:
|
||||
def _find_http_files(self, options: Values) -> List[str]:
|
||||
old_http_dir = self._cache_dir(options, "http")
|
||||
new_http_dir = self._cache_dir(options, "http-v2")
|
||||
return filesystem.find_files(old_http_dir, "*") + filesystem.find_files(
|
||||
new_http_dir, "*"
|
||||
)
|
||||
|
||||
def _find_wheels(self, options: Values, pattern: str) -> list[str]:
|
||||
def _find_wheels(self, options: Values, pattern: str) -> List[str]:
|
||||
wheel_dir = self._cache_dir(options, "wheels")
|
||||
|
||||
# The wheel filename format, as specified in PEP 427, is:
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import logging
|
||||
from optparse import Values
|
||||
from typing import List
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pip._internal.metadata import get_default_environment
|
||||
from pip._internal.operations.check import (
|
||||
check_package_set,
|
||||
check_unsupported,
|
||||
create_package_set_from_installed,
|
||||
warn_legacy_versions_and_specifiers,
|
||||
)
|
||||
from pip._internal.utils.compatibility_tags import get_supported
|
||||
from pip._internal.utils.misc import write_output
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -18,19 +17,13 @@ logger = logging.getLogger(__name__)
|
|||
class CheckCommand(Command):
|
||||
"""Verify installed packages have compatible dependencies."""
|
||||
|
||||
ignore_require_venv = True
|
||||
usage = """
|
||||
%prog [options]"""
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
package_set, parsing_probs = create_package_set_from_installed()
|
||||
warn_legacy_versions_and_specifiers(package_set)
|
||||
missing, conflicting = check_package_set(package_set)
|
||||
unsupported = list(
|
||||
check_unsupported(
|
||||
get_default_environment().iter_installed_distributions(),
|
||||
get_supported(),
|
||||
)
|
||||
)
|
||||
|
||||
for project_name in missing:
|
||||
version = package_set[project_name].version
|
||||
|
|
@ -53,13 +46,8 @@ class CheckCommand(Command):
|
|||
dep_name,
|
||||
dep_version,
|
||||
)
|
||||
for package in unsupported:
|
||||
write_output(
|
||||
"%s %s is not supported on this platform",
|
||||
package.raw_name,
|
||||
package.version,
|
||||
)
|
||||
if missing or conflicting or parsing_probs or unsupported:
|
||||
|
||||
if missing or conflicting or parsing_probs:
|
||||
return ERROR
|
||||
else:
|
||||
write_output("No broken requirements found.")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import sys
|
||||
import textwrap
|
||||
from optparse import Values
|
||||
from typing import List
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
|
|
@ -37,18 +38,12 @@ COMPLETION_SCRIPTS = {
|
|||
""",
|
||||
"fish": """
|
||||
function __fish_complete_pip
|
||||
set -lx COMP_WORDS \\
|
||||
(commandline --current-process --tokenize --cut-at-cursor) \\
|
||||
(commandline --current-token --cut-at-cursor)
|
||||
set -lx COMP_CWORD (math (count $COMP_WORDS) - 1)
|
||||
set -lx COMP_WORDS (commandline -o) ""
|
||||
set -lx COMP_CWORD ( \\
|
||||
math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
|
||||
)
|
||||
set -lx PIP_AUTO_COMPLETE 1
|
||||
set -l completions
|
||||
if string match -q '2.*' $version
|
||||
set completions (eval $COMP_WORDS[1])
|
||||
else
|
||||
set completions ($COMP_WORDS[1])
|
||||
end
|
||||
string split \\ -- $completions
|
||||
string split \\ -- (eval $COMP_WORDS[1])
|
||||
end
|
||||
complete -fa "(__fish_complete_pip)" -c {prog}
|
||||
""",
|
||||
|
|
@ -118,7 +113,7 @@ class CompletionCommand(Command):
|
|||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
"""Prints the completion code of the given shell"""
|
||||
shells = COMPLETION_SCRIPTS.keys()
|
||||
shell_options = ["--" + shell for shell in sorted(shells)]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
from optparse import Values
|
||||
from typing import Any, Callable
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
|
|
@ -95,8 +93,8 @@ class ConfigurationCommand(Command):
|
|||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
|
||||
return {
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
handlers = {
|
||||
"list": self.list_values,
|
||||
"edit": self.open_in_editor,
|
||||
"get": self.get_name,
|
||||
|
|
@ -105,14 +103,11 @@ class ConfigurationCommand(Command):
|
|||
"debug": self.list_config_values,
|
||||
}
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
handler_map = self.handler_map()
|
||||
|
||||
# Determine action
|
||||
if not args or args[0] not in handler_map:
|
||||
if not args or args[0] not in handlers:
|
||||
logger.error(
|
||||
"Need an action (%s) to perform.",
|
||||
", ".join(sorted(handler_map)),
|
||||
", ".join(sorted(handlers)),
|
||||
)
|
||||
return ERROR
|
||||
|
||||
|
|
@ -136,14 +131,14 @@ class ConfigurationCommand(Command):
|
|||
|
||||
# Error handling happens here, not in the action-handlers.
|
||||
try:
|
||||
handler_map[action](options, args[1:])
|
||||
handlers[action](options, args[1:])
|
||||
except PipError as e:
|
||||
logger.error(e.args[0])
|
||||
return ERROR
|
||||
|
||||
return SUCCESS
|
||||
|
||||
def _determine_file(self, options: Values, need_value: bool) -> Kind | None:
|
||||
def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]:
|
||||
file_options = [
|
||||
key
|
||||
for key, value in (
|
||||
|
|
@ -173,32 +168,31 @@ class ConfigurationCommand(Command):
|
|||
"(--user, --site, --global) to perform."
|
||||
)
|
||||
|
||||
def list_values(self, options: Values, args: list[str]) -> None:
|
||||
def list_values(self, options: Values, args: List[str]) -> None:
|
||||
self._get_n_args(args, "list", n=0)
|
||||
|
||||
for key, value in sorted(self.configuration.items()):
|
||||
for key, value in sorted(value.items()):
|
||||
write_output("%s=%r", key, value)
|
||||
write_output("%s=%r", key, value)
|
||||
|
||||
def get_name(self, options: Values, args: list[str]) -> None:
|
||||
def get_name(self, options: Values, args: List[str]) -> None:
|
||||
key = self._get_n_args(args, "get [name]", n=1)
|
||||
value = self.configuration.get_value(key)
|
||||
|
||||
write_output("%s", value)
|
||||
|
||||
def set_name_value(self, options: Values, args: list[str]) -> None:
|
||||
def set_name_value(self, options: Values, args: List[str]) -> None:
|
||||
key, value = self._get_n_args(args, "set [name] [value]", n=2)
|
||||
self.configuration.set_value(key, value)
|
||||
|
||||
self._save_configuration()
|
||||
|
||||
def unset_name(self, options: Values, args: list[str]) -> None:
|
||||
def unset_name(self, options: Values, args: List[str]) -> None:
|
||||
key = self._get_n_args(args, "unset [name]", n=1)
|
||||
self.configuration.unset_value(key)
|
||||
|
||||
self._save_configuration()
|
||||
|
||||
def list_config_values(self, options: Values, args: list[str]) -> None:
|
||||
def list_config_values(self, options: Values, args: List[str]) -> None:
|
||||
"""List config key-value pairs across different config files"""
|
||||
self._get_n_args(args, "debug", n=0)
|
||||
|
||||
|
|
@ -212,15 +206,13 @@ class ConfigurationCommand(Command):
|
|||
file_exists = os.path.exists(fname)
|
||||
write_output("%s, exists: %r", fname, file_exists)
|
||||
if file_exists:
|
||||
self.print_config_file_values(variant, fname)
|
||||
self.print_config_file_values(variant)
|
||||
|
||||
def print_config_file_values(self, variant: Kind, fname: str) -> None:
|
||||
def print_config_file_values(self, variant: Kind) -> None:
|
||||
"""Get key-value pairs from the file of a variant"""
|
||||
for name, value in self.configuration.get_values_in_config(variant).items():
|
||||
with indent_log():
|
||||
if name == fname:
|
||||
for confname, confvalue in value.items():
|
||||
write_output("%s: %s", confname, confvalue)
|
||||
write_output("%s: %s", name, value)
|
||||
|
||||
def print_env_var_values(self) -> None:
|
||||
"""Get key-values pairs present as environment variables"""
|
||||
|
|
@ -230,7 +222,7 @@ class ConfigurationCommand(Command):
|
|||
env_var = f"PIP_{key.upper()}"
|
||||
write_output("%s=%r", env_var, value)
|
||||
|
||||
def open_in_editor(self, options: Values, args: list[str]) -> None:
|
||||
def open_in_editor(self, options: Values, args: List[str]) -> None:
|
||||
editor = self._determine_editor(options)
|
||||
|
||||
fname = self.configuration.get_file_to_edit()
|
||||
|
|
@ -252,7 +244,7 @@ class ConfigurationCommand(Command):
|
|||
except subprocess.CalledProcessError as e:
|
||||
raise PipError(f"Editor Subprocess exited with exit code {e.returncode}")
|
||||
|
||||
def _get_n_args(self, args: list[str], example: str, n: int) -> Any:
|
||||
def _get_n_args(self, args: List[str], example: str, n: int) -> Any:
|
||||
"""Helper to make sure the command got the right number of arguments"""
|
||||
if len(args) != n:
|
||||
msg = (
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib.resources
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from optparse import Values
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import pip._vendor
|
||||
from pip._vendor.certifi import where
|
||||
|
|
@ -18,7 +17,6 @@ from pip._internal.cli.cmdoptions import make_target_python
|
|||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.configuration import Configuration
|
||||
from pip._internal.metadata import get_environment
|
||||
from pip._internal.utils.compat import open_text_resource
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import get_pip_version
|
||||
|
||||
|
|
@ -36,8 +34,8 @@ def show_sys_implementation() -> None:
|
|||
show_value("name", implementation_name)
|
||||
|
||||
|
||||
def create_vendor_txt_map() -> dict[str, str]:
|
||||
with open_text_resource("pip._vendor", "vendor.txt") as f:
|
||||
def create_vendor_txt_map() -> Dict[str, str]:
|
||||
with importlib.resources.open_text("pip._vendor", "vendor.txt") as f:
|
||||
# Purge non version specifying lines.
|
||||
# Also, remove any space prefix or suffixes (including comments).
|
||||
lines = [
|
||||
|
|
@ -48,7 +46,7 @@ def create_vendor_txt_map() -> dict[str, str]:
|
|||
return dict(line.split("==", 1) for line in lines)
|
||||
|
||||
|
||||
def get_module_from_module_name(module_name: str) -> ModuleType | None:
|
||||
def get_module_from_module_name(module_name: str) -> Optional[ModuleType]:
|
||||
# Module name can be uppercase in vendor.txt for some reason...
|
||||
module_name = module_name.lower().replace("-", "_")
|
||||
# PATCH: setuptools is actually only pkg_resources.
|
||||
|
|
@ -66,7 +64,7 @@ def get_module_from_module_name(module_name: str) -> ModuleType | None:
|
|||
raise
|
||||
|
||||
|
||||
def get_vendor_version_from_module(module_name: str) -> str | None:
|
||||
def get_vendor_version_from_module(module_name: str) -> Optional[str]:
|
||||
module = get_module_from_module_name(module_name)
|
||||
version = getattr(module, "__version__", None)
|
||||
|
||||
|
|
@ -81,7 +79,7 @@ def get_vendor_version_from_module(module_name: str) -> str | None:
|
|||
return version
|
||||
|
||||
|
||||
def show_actual_vendor_versions(vendor_txt_versions: dict[str, str]) -> None:
|
||||
def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None:
|
||||
"""Log the actual version and print extra info if there is
|
||||
a conflict or if the actual version could not be imported.
|
||||
"""
|
||||
|
|
@ -171,7 +169,7 @@ class DebugCommand(Command):
|
|||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
self.parser.config.load()
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
logger.warning(
|
||||
"This command is only meant for debugging. "
|
||||
"Do not use this with automation for parsing and getting these "
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import logging
|
||||
import os
|
||||
from optparse import Values
|
||||
from typing import List
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.cmdoptions import make_target_python
|
||||
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.operations.build.build_tracker import get_build_tracker
|
||||
from pip._internal.req.req_install import check_legacy_setup_py_options
|
||||
from pip._internal.utils.misc import ensure_dir, normalize_path, write_output
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
|
|
@ -35,9 +37,9 @@ class DownloadCommand(RequirementCommand):
|
|||
|
||||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(cmdoptions.constraints())
|
||||
self.cmd_opts.add_option(cmdoptions.build_constraints())
|
||||
self.cmd_opts.add_option(cmdoptions.requirements())
|
||||
self.cmd_opts.add_option(cmdoptions.no_deps())
|
||||
self.cmd_opts.add_option(cmdoptions.global_options())
|
||||
self.cmd_opts.add_option(cmdoptions.no_binary())
|
||||
self.cmd_opts.add_option(cmdoptions.only_binary())
|
||||
self.cmd_opts.add_option(cmdoptions.prefer_binary())
|
||||
|
|
@ -47,6 +49,7 @@ class DownloadCommand(RequirementCommand):
|
|||
self.cmd_opts.add_option(cmdoptions.progress_bar())
|
||||
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||
self.cmd_opts.add_option(cmdoptions.use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.check_build_deps())
|
||||
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||
|
||||
|
|
@ -72,14 +75,13 @@ class DownloadCommand(RequirementCommand):
|
|||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
@with_cleanup
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
options.ignore_installed = True
|
||||
# editable doesn't really make sense for `pip download`, but the bowels
|
||||
# of the RequirementSet code require that property.
|
||||
options.editables = []
|
||||
|
||||
cmdoptions.check_dist_restriction(options)
|
||||
cmdoptions.check_build_constraints(options)
|
||||
|
||||
options.download_dir = normalize_path(options.download_dir)
|
||||
ensure_dir(options.download_dir)
|
||||
|
|
@ -103,6 +105,7 @@ class DownloadCommand(RequirementCommand):
|
|||
)
|
||||
|
||||
reqs = self.get_requirements(args, options, finder, session)
|
||||
check_legacy_setup_py_options(options, reqs)
|
||||
|
||||
preparer = self.make_requirement_preparer(
|
||||
temp_build_dir=directory,
|
||||
|
|
@ -120,6 +123,7 @@ class DownloadCommand(RequirementCommand):
|
|||
finder=finder,
|
||||
options=options,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
use_pep517=options.use_pep517,
|
||||
py_version_info=options.python_version,
|
||||
)
|
||||
|
||||
|
|
@ -127,15 +131,16 @@ class DownloadCommand(RequirementCommand):
|
|||
|
||||
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
|
||||
|
||||
preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
|
||||
|
||||
downloaded: list[str] = []
|
||||
downloaded: List[str] = []
|
||||
for req in requirement_set.requirements.values():
|
||||
if req.satisfied_by is None:
|
||||
assert req.name is not None
|
||||
preparer.save_linked_requirement(req)
|
||||
downloaded.append(req.name)
|
||||
|
||||
preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
|
||||
requirement_set.warn_legacy_versions_and_specifiers()
|
||||
|
||||
if downloaded:
|
||||
write_output("Successfully downloaded %s", " ".join(downloaded))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import sys
|
||||
from optparse import Values
|
||||
from typing import AbstractSet, List
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.base_command import Command
|
||||
|
|
@ -12,11 +13,12 @@ def _should_suppress_build_backends() -> bool:
|
|||
return sys.version_info < (3, 12)
|
||||
|
||||
|
||||
def _dev_pkgs() -> set[str]:
|
||||
def _dev_pkgs() -> AbstractSet[str]:
|
||||
pkgs = {"pip"}
|
||||
|
||||
if _should_suppress_build_backends():
|
||||
pkgs |= {"setuptools", "distribute", "wheel"}
|
||||
pkgs |= {"setuptools", "distribute", "wheel", "pkg-resources"}
|
||||
|
||||
return pkgs
|
||||
|
||||
|
|
@ -28,9 +30,9 @@ class FreezeCommand(Command):
|
|||
packages are listed in a case-insensitive sorted order.
|
||||
"""
|
||||
|
||||
ignore_require_venv = True
|
||||
usage = """
|
||||
%prog [options]"""
|
||||
log_streams = ("ext://sys.stderr", "ext://sys.stderr")
|
||||
|
||||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(
|
||||
|
|
@ -84,7 +86,7 @@ class FreezeCommand(Command):
|
|||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
skip = set(stdlib_pkgs)
|
||||
if not options.freeze_all:
|
||||
skip.update(_dev_pkgs())
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import hashlib
|
|||
import logging
|
||||
import sys
|
||||
from optparse import Values
|
||||
from typing import List
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
|
|
@ -36,7 +37,7 @@ class HashCommand(Command):
|
|||
)
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
if not args:
|
||||
self.parser.print_usage(sys.stderr)
|
||||
return ERROR
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from optparse import Values
|
||||
from typing import List
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
|
|
@ -12,7 +13,7 @@ class HelpCommand(Command):
|
|||
%prog <command>"""
|
||||
ignore_require_venv = True
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
from pip._internal.commands import (
|
||||
commands_dict,
|
||||
create_command,
|
||||
|
|
|
|||
|
|
@ -1,20 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from collections.abc import Iterable
|
||||
from optparse import Values
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Iterable, List, Optional, Union
|
||||
|
||||
from pip._vendor.packaging.version import Version
|
||||
from pip._vendor.packaging.version import LegacyVersion, Version
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.req_command import IndexGroupCommand
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pip._internal.commands.search import (
|
||||
get_installed_distribution,
|
||||
print_dist_installation_info,
|
||||
)
|
||||
from pip._internal.commands.search import print_dist_installation_info
|
||||
from pip._internal.exceptions import CommandError, DistributionNotFound, PipError
|
||||
from pip._internal.index.collector import LinkCollector
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
|
|
@ -41,7 +34,6 @@ class IndexCommand(IndexGroupCommand):
|
|||
|
||||
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||
self.cmd_opts.add_option(cmdoptions.pre())
|
||||
self.cmd_opts.add_option(cmdoptions.json())
|
||||
self.cmd_opts.add_option(cmdoptions.no_binary())
|
||||
self.cmd_opts.add_option(cmdoptions.only_binary())
|
||||
|
||||
|
|
@ -53,19 +45,22 @@ class IndexCommand(IndexGroupCommand):
|
|||
self.parser.insert_option_group(0, index_opts)
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
|
||||
return {
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
handlers = {
|
||||
"versions": self.get_available_package_versions,
|
||||
}
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
handler_map = self.handler_map()
|
||||
logger.warning(
|
||||
"pip index is currently an experimental command. "
|
||||
"It may be removed/changed in a future release "
|
||||
"without prior warning."
|
||||
)
|
||||
|
||||
# Determine action
|
||||
if not args or args[0] not in handler_map:
|
||||
if not args or args[0] not in handlers:
|
||||
logger.error(
|
||||
"Need an action (%s) to perform.",
|
||||
", ".join(sorted(handler_map)),
|
||||
", ".join(sorted(handlers)),
|
||||
)
|
||||
return ERROR
|
||||
|
||||
|
|
@ -73,7 +68,7 @@ class IndexCommand(IndexGroupCommand):
|
|||
|
||||
# Error handling happens here, not in the action-handlers.
|
||||
try:
|
||||
handler_map[action](options, args[1:])
|
||||
handlers[action](options, args[1:])
|
||||
except PipError as e:
|
||||
logger.error(e.args[0])
|
||||
return ERROR
|
||||
|
|
@ -84,8 +79,8 @@ class IndexCommand(IndexGroupCommand):
|
|||
self,
|
||||
options: Values,
|
||||
session: PipSession,
|
||||
target_python: TargetPython | None = None,
|
||||
ignore_requires_python: bool | None = None,
|
||||
target_python: Optional[TargetPython] = None,
|
||||
ignore_requires_python: Optional[bool] = None,
|
||||
) -> PackageFinder:
|
||||
"""
|
||||
Create a package finder appropriate to the index command.
|
||||
|
|
@ -105,7 +100,7 @@ class IndexCommand(IndexGroupCommand):
|
|||
target_python=target_python,
|
||||
)
|
||||
|
||||
def get_available_package_versions(self, options: Values, args: list[Any]) -> None:
|
||||
def get_available_package_versions(self, options: Values, args: List[Any]) -> None:
|
||||
if len(args) != 1:
|
||||
raise CommandError("You need to specify exactly one argument")
|
||||
|
||||
|
|
@ -120,7 +115,7 @@ class IndexCommand(IndexGroupCommand):
|
|||
ignore_requires_python=options.ignore_requires_python,
|
||||
)
|
||||
|
||||
versions: Iterable[Version] = (
|
||||
versions: Iterable[Union[LegacyVersion, Version]] = (
|
||||
candidate.version for candidate in finder.find_all_candidates(query)
|
||||
)
|
||||
|
||||
|
|
@ -139,21 +134,6 @@ class IndexCommand(IndexGroupCommand):
|
|||
formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)]
|
||||
latest = formatted_versions[0]
|
||||
|
||||
dist = get_installed_distribution(query)
|
||||
|
||||
if options.json:
|
||||
structured_output = {
|
||||
"name": query,
|
||||
"versions": formatted_versions,
|
||||
"latest": latest,
|
||||
}
|
||||
|
||||
if dist is not None:
|
||||
structured_output["installed_version"] = str(dist.version)
|
||||
|
||||
write_output(json.dumps(structured_output))
|
||||
|
||||
else:
|
||||
write_output(f"{query} ({latest})")
|
||||
write_output("Available versions: {}".format(", ".join(formatted_versions)))
|
||||
print_dist_installation_info(latest, dist)
|
||||
write_output(f"{query} ({latest})")
|
||||
write_output("Available versions: {}".format(", ".join(formatted_versions)))
|
||||
print_dist_installation_info(query, latest)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import logging
|
||||
from optparse import Values
|
||||
from typing import Any
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pip._vendor.packaging.markers import default_environment
|
||||
from pip._vendor.rich import print_json
|
||||
|
||||
from pip import __version__
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.req_command import Command
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.metadata import BaseDistribution, get_environment
|
||||
from pip._internal.utils.compat import stdlib_pkgs
|
||||
|
|
@ -45,7 +45,7 @@ class InspectCommand(Command):
|
|||
self.cmd_opts.add_option(cmdoptions.list_path())
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
cmdoptions.check_list_path_option(options)
|
||||
dists = get_environment(options.path).iter_installed_distributions(
|
||||
local_only=options.local,
|
||||
|
|
@ -62,8 +62,8 @@ class InspectCommand(Command):
|
|||
print_json(data=output)
|
||||
return SUCCESS
|
||||
|
||||
def _dist_to_dict(self, dist: BaseDistribution) -> dict[str, Any]:
|
||||
res: dict[str, Any] = {
|
||||
def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]:
|
||||
res: Dict[str, Any] = {
|
||||
"metadata": dist.metadata_dict,
|
||||
"metadata_location": dist.info_location,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import errno
|
||||
import json
|
||||
import operator
|
||||
|
|
@ -7,32 +5,20 @@ import os
|
|||
import shutil
|
||||
import site
|
||||
from optparse import SUPPRESS_HELP, Values
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.requests.exceptions import InvalidProxyURL
|
||||
from pip._vendor.rich import print_json
|
||||
|
||||
# Eagerly import self_outdated_check to avoid crashes. Otherwise,
|
||||
# this module would be imported *after* pip was replaced, resulting
|
||||
# in crashes if the new self_outdated_check module was incompatible
|
||||
# with the rest of pip that's already imported, or allowing a
|
||||
# wheel to execute arbitrary code on install by replacing
|
||||
# self_outdated_check.
|
||||
import pip._internal.self_outdated_check # noqa: F401
|
||||
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.req_command import (
|
||||
RequirementCommand,
|
||||
warn_if_run_as_root,
|
||||
with_cleanup,
|
||||
)
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pip._internal.exceptions import (
|
||||
CommandError,
|
||||
InstallationError,
|
||||
InstallWheelBuildError,
|
||||
)
|
||||
from pip._internal.exceptions import CommandError, InstallationError
|
||||
from pip._internal.locations import get_scheme
|
||||
from pip._internal.metadata import get_environment
|
||||
from pip._internal.models.installation_report import InstallationReport
|
||||
|
|
@ -41,6 +27,7 @@ from pip._internal.operations.check import ConflictDetails, check_install_confli
|
|||
from pip._internal.req import install_given_reqs
|
||||
from pip._internal.req.req_install import (
|
||||
InstallRequirement,
|
||||
check_legacy_setup_py_options,
|
||||
)
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.filesystem import test_writable_dir
|
||||
|
|
@ -50,7 +37,6 @@ from pip._internal.utils.misc import (
|
|||
ensure_dir,
|
||||
get_pip_version,
|
||||
protect_pip_from_modification_on_windows,
|
||||
warn_if_run_as_root,
|
||||
write_output,
|
||||
)
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
|
@ -58,7 +44,7 @@ from pip._internal.utils.virtualenv import (
|
|||
running_under_virtualenv,
|
||||
virtualenv_no_global,
|
||||
)
|
||||
from pip._internal.wheel_builder import build
|
||||
from pip._internal.wheel_builder import build, should_build_for_install_command
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
|
@ -86,7 +72,6 @@ class InstallCommand(RequirementCommand):
|
|||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(cmdoptions.requirements())
|
||||
self.cmd_opts.add_option(cmdoptions.constraints())
|
||||
self.cmd_opts.add_option(cmdoptions.build_constraints())
|
||||
self.cmd_opts.add_option(cmdoptions.no_deps())
|
||||
self.cmd_opts.add_option(cmdoptions.pre())
|
||||
|
||||
|
|
@ -210,10 +195,12 @@ class InstallCommand(RequirementCommand):
|
|||
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||
self.cmd_opts.add_option(cmdoptions.use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.check_build_deps())
|
||||
self.cmd_opts.add_option(cmdoptions.override_externally_managed())
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.config_settings())
|
||||
self.cmd_opts.add_option(cmdoptions.global_options())
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--compile",
|
||||
|
|
@ -276,7 +263,7 @@ class InstallCommand(RequirementCommand):
|
|||
)
|
||||
|
||||
@with_cleanup
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
if options.use_user_site and options.target_dir is not None:
|
||||
raise CommandError("Can not combine '--user' and '--target'")
|
||||
|
||||
|
|
@ -301,7 +288,6 @@ class InstallCommand(RequirementCommand):
|
|||
if options.upgrade:
|
||||
upgrade_strategy = options.upgrade_strategy
|
||||
|
||||
cmdoptions.check_build_constraints(options)
|
||||
cmdoptions.check_dist_restriction(options, check_target=True)
|
||||
|
||||
logger.verbose("Using %s", get_pip_version())
|
||||
|
|
@ -313,8 +299,8 @@ class InstallCommand(RequirementCommand):
|
|||
isolated_mode=options.isolated_mode,
|
||||
)
|
||||
|
||||
target_temp_dir: TempDirectory | None = None
|
||||
target_temp_dir_path: str | None = None
|
||||
target_temp_dir: Optional[TempDirectory] = None
|
||||
target_temp_dir_path: Optional[str] = None
|
||||
if options.target_dir:
|
||||
options.ignore_installed = True
|
||||
options.target_dir = os.path.abspath(options.target_dir)
|
||||
|
|
@ -333,6 +319,8 @@ class InstallCommand(RequirementCommand):
|
|||
target_temp_dir_path = target_temp_dir.path
|
||||
self.enter_context(target_temp_dir)
|
||||
|
||||
global_options = options.global_options or []
|
||||
|
||||
session = self.get_default_session(options)
|
||||
|
||||
target_python = make_target_python(options)
|
||||
|
|
@ -352,6 +340,7 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
try:
|
||||
reqs = self.get_requirements(args, options, finder, session)
|
||||
check_legacy_setup_py_options(options, reqs)
|
||||
|
||||
wheel_cache = WheelCache(options.cache_dir)
|
||||
|
||||
|
|
@ -380,7 +369,7 @@ class InstallCommand(RequirementCommand):
|
|||
ignore_requires_python=options.ignore_requires_python,
|
||||
force_reinstall=options.force_reinstall,
|
||||
upgrade_strategy=upgrade_strategy,
|
||||
py_version_info=options.python_version,
|
||||
use_pep517=options.use_pep517,
|
||||
)
|
||||
|
||||
self.trace_basic_info(finder)
|
||||
|
|
@ -398,6 +387,9 @@ class InstallCommand(RequirementCommand):
|
|||
json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
|
||||
|
||||
if options.dry_run:
|
||||
# In non dry-run mode, the legacy versions and specifiers check
|
||||
# will be done as part of conflict detection.
|
||||
requirement_set.warn_legacy_versions_and_specifiers()
|
||||
would_install_items = sorted(
|
||||
(r.metadata["name"], r.metadata["version"])
|
||||
for r in requirement_set.requirements_to_install
|
||||
|
|
@ -409,13 +401,6 @@ class InstallCommand(RequirementCommand):
|
|||
)
|
||||
return SUCCESS
|
||||
|
||||
# If there is any more preparation to do for the actual installation, do
|
||||
# so now. This includes actually downloading the files in the case that
|
||||
# we have been using PEP-658 metadata so far.
|
||||
preparer.prepare_linked_requirements_more(
|
||||
requirement_set.requirements.values()
|
||||
)
|
||||
|
||||
try:
|
||||
pip_req = requirement_set.get_requirement("pip")
|
||||
except KeyError:
|
||||
|
|
@ -427,22 +412,31 @@ class InstallCommand(RequirementCommand):
|
|||
protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
|
||||
|
||||
reqs_to_build = [
|
||||
r for r in requirement_set.requirements_to_install if not r.is_wheel
|
||||
r
|
||||
for r in requirement_set.requirements.values()
|
||||
if should_build_for_install_command(r)
|
||||
]
|
||||
|
||||
_, build_failures = build(
|
||||
reqs_to_build,
|
||||
wheel_cache=wheel_cache,
|
||||
verify=True,
|
||||
build_options=[],
|
||||
global_options=global_options,
|
||||
)
|
||||
|
||||
if build_failures:
|
||||
raise InstallWheelBuildError(build_failures)
|
||||
raise InstallationError(
|
||||
"Could not build wheels for {}, which is required to "
|
||||
"install pyproject.toml-based projects".format(
|
||||
", ".join(r.name for r in build_failures) # type: ignore
|
||||
)
|
||||
)
|
||||
|
||||
to_install = resolver.get_installation_order(requirement_set)
|
||||
|
||||
# Check for conflicts in the package set we're installing.
|
||||
conflicts: ConflictDetails | None = None
|
||||
conflicts: Optional[ConflictDetails] = None
|
||||
should_warn_about_conflicts = (
|
||||
not options.ignore_dependencies and options.warn_about_conflicts
|
||||
)
|
||||
|
|
@ -457,13 +451,13 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
installed = install_given_reqs(
|
||||
to_install,
|
||||
global_options,
|
||||
root=options.root_path,
|
||||
home=target_temp_dir_path,
|
||||
prefix=options.prefix_path,
|
||||
warn_script_location=warn_script_location,
|
||||
use_user_site=options.use_user_site,
|
||||
pycompile=options.compile,
|
||||
progress_bar=options.progress_bar,
|
||||
)
|
||||
|
||||
lib_locations = get_lib_location_guesses(
|
||||
|
|
@ -475,21 +469,17 @@ class InstallCommand(RequirementCommand):
|
|||
)
|
||||
env = get_environment(lib_locations)
|
||||
|
||||
# Display a summary of installed packages, with extra care to
|
||||
# display a package name as it was requested by the user.
|
||||
installed.sort(key=operator.attrgetter("name"))
|
||||
summary = []
|
||||
installed_versions = {}
|
||||
for distribution in env.iter_all_distributions():
|
||||
installed_versions[distribution.canonical_name] = distribution.version
|
||||
for package in installed:
|
||||
display_name = package.name
|
||||
version = installed_versions.get(canonicalize_name(display_name), None)
|
||||
if version:
|
||||
text = f"{display_name}-{version}"
|
||||
else:
|
||||
text = display_name
|
||||
summary.append(text)
|
||||
items = []
|
||||
for result in installed:
|
||||
item = result.name
|
||||
try:
|
||||
installed_dist = env.get_distribution(item)
|
||||
if installed_dist is not None:
|
||||
item = f"{item}-{installed_dist.version}"
|
||||
except Exception:
|
||||
pass
|
||||
items.append(item)
|
||||
|
||||
if conflicts is not None:
|
||||
self._warn_about_conflicts(
|
||||
|
|
@ -497,7 +487,7 @@ class InstallCommand(RequirementCommand):
|
|||
resolver_variant=self.determine_resolver_variant(options),
|
||||
)
|
||||
|
||||
installed_desc = " ".join(summary)
|
||||
installed_desc = " ".join(items)
|
||||
if installed_desc:
|
||||
write_output(
|
||||
"Successfully installed %s",
|
||||
|
|
@ -579,8 +569,8 @@ class InstallCommand(RequirementCommand):
|
|||
shutil.move(os.path.join(lib_dir, item), target_item_dir)
|
||||
|
||||
def _determine_conflicts(
|
||||
self, to_install: list[InstallRequirement]
|
||||
) -> ConflictDetails | None:
|
||||
self, to_install: List[InstallRequirement]
|
||||
) -> Optional[ConflictDetails]:
|
||||
try:
|
||||
return check_install_conflicts(to_install)
|
||||
except Exception:
|
||||
|
|
@ -597,7 +587,7 @@ class InstallCommand(RequirementCommand):
|
|||
if not missing and not conflicting:
|
||||
return
|
||||
|
||||
parts: list[str] = []
|
||||
parts: List[str] = []
|
||||
if resolver_variant == "legacy":
|
||||
parts.append(
|
||||
"pip's legacy dependency resolver does not consider dependency "
|
||||
|
|
@ -643,11 +633,11 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
def get_lib_location_guesses(
|
||||
user: bool = False,
|
||||
home: str | None = None,
|
||||
root: str | None = None,
|
||||
home: Optional[str] = None,
|
||||
root: Optional[str] = None,
|
||||
isolated: bool = False,
|
||||
prefix: str | None = None,
|
||||
) -> list[str]:
|
||||
prefix: Optional[str] = None,
|
||||
) -> List[str]:
|
||||
scheme = get_scheme(
|
||||
"",
|
||||
user=user,
|
||||
|
|
@ -659,7 +649,7 @@ def get_lib_location_guesses(
|
|||
return [scheme.purelib, scheme.platlib]
|
||||
|
||||
|
||||
def site_packages_writable(root: str | None, isolated: bool) -> bool:
|
||||
def site_packages_writable(root: Optional[str], isolated: bool) -> bool:
|
||||
return all(
|
||||
test_writable_dir(d)
|
||||
for d in set(get_lib_location_guesses(root=root, isolated=isolated))
|
||||
|
|
@ -667,10 +657,10 @@ def site_packages_writable(root: str | None, isolated: bool) -> bool:
|
|||
|
||||
|
||||
def decide_user_install(
|
||||
use_user_site: bool | None,
|
||||
prefix_path: str | None = None,
|
||||
target_dir: str | None = None,
|
||||
root_path: str | None = None,
|
||||
use_user_site: Optional[bool],
|
||||
prefix_path: Optional[str] = None,
|
||||
target_dir: Optional[str] = None,
|
||||
root_path: Optional[str] = None,
|
||||
isolated_mode: bool = False,
|
||||
) -> bool:
|
||||
"""Determine whether to do a user install based on the input options.
|
||||
|
|
@ -687,7 +677,6 @@ def decide_user_install(
|
|||
logger.debug("Non-user install by explicit request")
|
||||
return False
|
||||
|
||||
# If we have been asked for a user install explicitly, check compatibility.
|
||||
if use_user_site:
|
||||
if prefix_path:
|
||||
raise CommandError(
|
||||
|
|
@ -699,13 +688,6 @@ def decide_user_install(
|
|||
"Can not perform a '--user' install. User site-packages "
|
||||
"are not visible in this virtualenv."
|
||||
)
|
||||
# Catch all remaining cases which honour the site.ENABLE_USER_SITE
|
||||
# value, such as a plain Python installation (e.g. no virtualenv).
|
||||
if not site.ENABLE_USER_SITE:
|
||||
raise InstallationError(
|
||||
"Can not perform a '--user' install. User site-packages "
|
||||
"are disabled for this Python."
|
||||
)
|
||||
logger.debug("User install by explicit request")
|
||||
return True
|
||||
|
||||
|
|
@ -773,31 +755,20 @@ def create_os_error_message(
|
|||
parts.append(permissions_part)
|
||||
parts.append(".\n")
|
||||
|
||||
# Suggest to check "pip config debug" in case of invalid proxy
|
||||
if type(error) is InvalidProxyURL:
|
||||
# Suggest the user to enable Long Paths if path length is
|
||||
# more than 260
|
||||
if (
|
||||
WINDOWS
|
||||
and error.errno == errno.ENOENT
|
||||
and error.filename
|
||||
and len(error.filename) > 260
|
||||
):
|
||||
parts.append(
|
||||
'Consider checking your local proxy configuration with "pip config debug"'
|
||||
"HINT: This error might have occurred since "
|
||||
"this system does not have Windows Long Path "
|
||||
"support enabled. You can find information on "
|
||||
"how to enable this at "
|
||||
"https://pip.pypa.io/warnings/enable-long-paths\n"
|
||||
)
|
||||
parts.append(".\n")
|
||||
|
||||
# On Windows, errors like EINVAL or ENOENT may occur
|
||||
# if a file or folder name exceeds 255 characters,
|
||||
# or if the full path exceeds 260 characters and long path support isn't enabled.
|
||||
# This condition checks for such cases and adds a hint to the error output.
|
||||
|
||||
if WINDOWS and error.errno in (errno.EINVAL, errno.ENOENT) and error.filename:
|
||||
if any(len(part) > 255 for part in Path(error.filename).parts):
|
||||
parts.append(
|
||||
"HINT: This error might be caused by a file or folder name exceeding "
|
||||
"255 characters, which is a Windows limitation even if long paths "
|
||||
"are enabled.\n "
|
||||
)
|
||||
if len(error.filename) > 260:
|
||||
parts.append(
|
||||
"HINT: This error might have occurred since "
|
||||
"this system does not have Windows Long Path "
|
||||
"support enabled. You can find information on "
|
||||
"how to enable this at "
|
||||
"https://pip.pypa.io/warnings/enable-long-paths\n"
|
||||
)
|
||||
return "".join(parts).strip() + "\n"
|
||||
|
|
|
|||
|
|
@ -1,27 +1,24 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from collections.abc import Generator, Sequence
|
||||
from email.parser import Parser
|
||||
from optparse import Values
|
||||
from typing import TYPE_CHECKING, cast
|
||||
from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import InvalidVersion, Version
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.index_command import IndexGroupCommand
|
||||
from pip._internal.cli.req_command import IndexGroupCommand
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.index.collector import LinkCollector
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import BaseDistribution, get_environment
|
||||
from pip._internal.models.selection_prefs import SelectionPreferences
|
||||
from pip._internal.network.session import PipSession
|
||||
from pip._internal.utils.compat import stdlib_pkgs
|
||||
from pip._internal.utils.misc import tabulate, write_output
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.network.session import PipSession
|
||||
from pip._internal.metadata.base import DistributionVersion
|
||||
|
||||
class _DistWithLatestInfo(BaseDistribution):
|
||||
"""Give the distribution object a couple of extra fields.
|
||||
|
|
@ -30,12 +27,14 @@ if TYPE_CHECKING:
|
|||
makes the rest of the code much cleaner.
|
||||
"""
|
||||
|
||||
latest_version: Version
|
||||
latest_version: DistributionVersion
|
||||
latest_filetype: str
|
||||
|
||||
_ProcessedDists = Sequence[_DistWithLatestInfo]
|
||||
|
||||
|
||||
from pip._vendor.packaging.version import parse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
@ -129,7 +128,7 @@ class ListCommand(IndexGroupCommand):
|
|||
"--include-editable",
|
||||
action="store_true",
|
||||
dest="include_editable",
|
||||
help="Include editable package in output.",
|
||||
help="Include editable package from output.",
|
||||
default=True,
|
||||
)
|
||||
self.cmd_opts.add_option(cmdoptions.list_exclude())
|
||||
|
|
@ -138,20 +137,12 @@ class ListCommand(IndexGroupCommand):
|
|||
self.parser.insert_option_group(0, index_opts)
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def handle_pip_version_check(self, options: Values) -> None:
|
||||
if options.outdated or options.uptodate:
|
||||
super().handle_pip_version_check(options)
|
||||
|
||||
def _build_package_finder(
|
||||
self, options: Values, session: PipSession
|
||||
) -> PackageFinder:
|
||||
"""
|
||||
Create a package finder appropriate to this list command.
|
||||
"""
|
||||
# Lazy import the heavy index modules as most list invocations won't need 'em.
|
||||
from pip._internal.index.collector import LinkCollector
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
|
||||
link_collector = LinkCollector.create(session, options=options)
|
||||
|
||||
# Pass allow_yanked=False to ignore yanked versions.
|
||||
|
|
@ -165,7 +156,7 @@ class ListCommand(IndexGroupCommand):
|
|||
selection_prefs=selection_prefs,
|
||||
)
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
if options.outdated and options.uptodate:
|
||||
raise CommandError("Options --outdated and --uptodate cannot be combined.")
|
||||
|
||||
|
|
@ -180,7 +171,7 @@ class ListCommand(IndexGroupCommand):
|
|||
if options.excludes:
|
||||
skip.update(canonicalize_name(n) for n in options.excludes)
|
||||
|
||||
packages: _ProcessedDists = [
|
||||
packages: "_ProcessedDists" = [
|
||||
cast("_DistWithLatestInfo", d)
|
||||
for d in get_environment(options.path).iter_installed_distributions(
|
||||
local_only=options.local,
|
||||
|
|
@ -207,26 +198,26 @@ class ListCommand(IndexGroupCommand):
|
|||
return SUCCESS
|
||||
|
||||
def get_outdated(
|
||||
self, packages: _ProcessedDists, options: Values
|
||||
) -> _ProcessedDists:
|
||||
self, packages: "_ProcessedDists", options: Values
|
||||
) -> "_ProcessedDists":
|
||||
return [
|
||||
dist
|
||||
for dist in self.iter_packages_latest_infos(packages, options)
|
||||
if dist.latest_version > dist.version
|
||||
if parse(str(dist.latest_version)) > parse(str(dist.version))
|
||||
]
|
||||
|
||||
def get_uptodate(
|
||||
self, packages: _ProcessedDists, options: Values
|
||||
) -> _ProcessedDists:
|
||||
self, packages: "_ProcessedDists", options: Values
|
||||
) -> "_ProcessedDists":
|
||||
return [
|
||||
dist
|
||||
for dist in self.iter_packages_latest_infos(packages, options)
|
||||
if dist.latest_version == dist.version
|
||||
if parse(str(dist.latest_version)) == parse(str(dist.version))
|
||||
]
|
||||
|
||||
def get_not_required(
|
||||
self, packages: _ProcessedDists, options: Values
|
||||
) -> _ProcessedDists:
|
||||
self, packages: "_ProcessedDists", options: Values
|
||||
) -> "_ProcessedDists":
|
||||
dep_keys = {
|
||||
canonicalize_name(dep.name)
|
||||
for dist in packages
|
||||
|
|
@ -239,14 +230,14 @@ class ListCommand(IndexGroupCommand):
|
|||
return list({pkg for pkg in packages if pkg.canonical_name not in dep_keys})
|
||||
|
||||
def iter_packages_latest_infos(
|
||||
self, packages: _ProcessedDists, options: Values
|
||||
) -> Generator[_DistWithLatestInfo, None, None]:
|
||||
self, packages: "_ProcessedDists", options: Values
|
||||
) -> Generator["_DistWithLatestInfo", None, None]:
|
||||
with self._build_session(options) as session:
|
||||
finder = self._build_package_finder(options, session)
|
||||
|
||||
def latest_info(
|
||||
dist: _DistWithLatestInfo,
|
||||
) -> _DistWithLatestInfo | None:
|
||||
dist: "_DistWithLatestInfo",
|
||||
) -> Optional["_DistWithLatestInfo"]:
|
||||
all_candidates = finder.find_all_candidates(dist.canonical_name)
|
||||
if not options.pre:
|
||||
# Remove prereleases
|
||||
|
|
@ -277,7 +268,7 @@ class ListCommand(IndexGroupCommand):
|
|||
yield dist
|
||||
|
||||
def output_package_listing(
|
||||
self, packages: _ProcessedDists, options: Values
|
||||
self, packages: "_ProcessedDists", options: Values
|
||||
) -> None:
|
||||
packages = sorted(
|
||||
packages,
|
||||
|
|
@ -288,19 +279,17 @@ class ListCommand(IndexGroupCommand):
|
|||
self.output_package_listing_columns(data, header)
|
||||
elif options.list_format == "freeze":
|
||||
for dist in packages:
|
||||
try:
|
||||
req_string = f"{dist.raw_name}=={dist.version}"
|
||||
except InvalidVersion:
|
||||
req_string = f"{dist.raw_name}==={dist.raw_version}"
|
||||
if options.verbose >= 1:
|
||||
write_output("%s (%s)", req_string, dist.location)
|
||||
write_output(
|
||||
"%s==%s (%s)", dist.raw_name, dist.version, dist.location
|
||||
)
|
||||
else:
|
||||
write_output(req_string)
|
||||
write_output("%s==%s", dist.raw_name, dist.version)
|
||||
elif options.list_format == "json":
|
||||
write_output(format_for_json(packages, options))
|
||||
|
||||
def output_package_listing_columns(
|
||||
self, data: list[list[str]], header: list[str]
|
||||
self, data: List[List[str]], header: List[str]
|
||||
) -> None:
|
||||
# insert the header first: we need to know the size of column names
|
||||
if len(data) > 0:
|
||||
|
|
@ -317,8 +306,8 @@ class ListCommand(IndexGroupCommand):
|
|||
|
||||
|
||||
def format_for_columns(
|
||||
pkgs: _ProcessedDists, options: Values
|
||||
) -> tuple[list[list[str]], list[str]]:
|
||||
pkgs: "_ProcessedDists", options: Values
|
||||
) -> Tuple[List[List[str]], List[str]]:
|
||||
"""
|
||||
Convert the package data into something usable
|
||||
by output_package_listing_columns.
|
||||
|
|
@ -329,40 +318,25 @@ def format_for_columns(
|
|||
if running_outdated:
|
||||
header.extend(["Latest", "Type"])
|
||||
|
||||
def wheel_build_tag(dist: BaseDistribution) -> str | None:
|
||||
try:
|
||||
wheel_file = dist.read_text("WHEEL")
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
return Parser().parsestr(wheel_file).get("Build")
|
||||
|
||||
build_tags = [wheel_build_tag(p) for p in pkgs]
|
||||
has_build_tags = any(build_tags)
|
||||
if has_build_tags:
|
||||
header.append("Build")
|
||||
has_editables = any(x.editable for x in pkgs)
|
||||
if has_editables:
|
||||
header.append("Editable project location")
|
||||
|
||||
if options.verbose >= 1:
|
||||
header.append("Location")
|
||||
if options.verbose >= 1:
|
||||
header.append("Installer")
|
||||
|
||||
has_editables = any(x.editable for x in pkgs)
|
||||
if has_editables:
|
||||
header.append("Editable project location")
|
||||
|
||||
data = []
|
||||
for i, proj in enumerate(pkgs):
|
||||
for proj in pkgs:
|
||||
# if we're working on the 'outdated' list, separate out the
|
||||
# latest_version and type
|
||||
row = [proj.raw_name, proj.raw_version]
|
||||
row = [proj.raw_name, str(proj.version)]
|
||||
|
||||
if running_outdated:
|
||||
row.append(str(proj.latest_version))
|
||||
row.append(proj.latest_filetype)
|
||||
|
||||
if has_build_tags:
|
||||
row.append(build_tags[i] or "")
|
||||
|
||||
if has_editables:
|
||||
row.append(proj.editable_project_location or "")
|
||||
|
||||
|
|
@ -376,16 +350,12 @@ def format_for_columns(
|
|||
return data, header
|
||||
|
||||
|
||||
def format_for_json(packages: _ProcessedDists, options: Values) -> str:
|
||||
def format_for_json(packages: "_ProcessedDists", options: Values) -> str:
|
||||
data = []
|
||||
for dist in packages:
|
||||
try:
|
||||
version = str(dist.version)
|
||||
except InvalidVersion:
|
||||
version = dist.raw_version
|
||||
info = {
|
||||
"name": dist.raw_name,
|
||||
"version": version,
|
||||
"version": str(dist.version),
|
||||
}
|
||||
if options.verbose >= 1:
|
||||
info["location"] = dist.location or ""
|
||||
|
|
|
|||
|
|
@ -1,167 +0,0 @@
|
|||
import sys
|
||||
from optparse import Values
|
||||
from pathlib import Path
|
||||
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.req_command import (
|
||||
RequirementCommand,
|
||||
with_cleanup,
|
||||
)
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.models.pylock import Pylock, is_valid_pylock_file_name
|
||||
from pip._internal.operations.build.build_tracker import get_build_tracker
|
||||
from pip._internal.utils.logging import getLogger
|
||||
from pip._internal.utils.misc import (
|
||||
get_pip_version,
|
||||
)
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class LockCommand(RequirementCommand):
|
||||
"""
|
||||
EXPERIMENTAL - Lock packages and their dependencies from:
|
||||
|
||||
- PyPI (and other indexes) using requirement specifiers.
|
||||
- VCS project urls.
|
||||
- Local project directories.
|
||||
- Local or remote source archives.
|
||||
|
||||
pip also supports locking from "requirements files", which provide an easy
|
||||
way to specify a whole environment to be installed.
|
||||
|
||||
The generated lock file is only guaranteed to be valid for the current
|
||||
python version and platform.
|
||||
"""
|
||||
|
||||
usage = """
|
||||
%prog [options] [-e] <local project path> ...
|
||||
%prog [options] <requirement specifier> [package-index-options] ...
|
||||
%prog [options] -r <requirements file> [package-index-options] ...
|
||||
%prog [options] <archive url/path> ..."""
|
||||
|
||||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(
|
||||
cmdoptions.PipOption(
|
||||
"--output",
|
||||
"-o",
|
||||
dest="output_file",
|
||||
metavar="path",
|
||||
type="path",
|
||||
default="pylock.toml",
|
||||
help="Lock file name (default=pylock.toml). Use - for stdout.",
|
||||
)
|
||||
)
|
||||
self.cmd_opts.add_option(cmdoptions.requirements())
|
||||
self.cmd_opts.add_option(cmdoptions.constraints())
|
||||
self.cmd_opts.add_option(cmdoptions.build_constraints())
|
||||
self.cmd_opts.add_option(cmdoptions.no_deps())
|
||||
self.cmd_opts.add_option(cmdoptions.pre())
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.editable())
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.src())
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||
self.cmd_opts.add_option(cmdoptions.use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.check_build_deps())
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.config_settings())
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.no_binary())
|
||||
self.cmd_opts.add_option(cmdoptions.only_binary())
|
||||
self.cmd_opts.add_option(cmdoptions.prefer_binary())
|
||||
self.cmd_opts.add_option(cmdoptions.require_hashes())
|
||||
self.cmd_opts.add_option(cmdoptions.progress_bar())
|
||||
|
||||
index_opts = cmdoptions.make_option_group(
|
||||
cmdoptions.index_group,
|
||||
self.parser,
|
||||
)
|
||||
|
||||
self.parser.insert_option_group(0, index_opts)
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
@with_cleanup
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
logger.verbose("Using %s", get_pip_version())
|
||||
|
||||
logger.warning(
|
||||
"pip lock is currently an experimental command. "
|
||||
"It may be removed/changed in a future release "
|
||||
"without prior warning."
|
||||
)
|
||||
|
||||
cmdoptions.check_build_constraints(options)
|
||||
|
||||
session = self.get_default_session(options)
|
||||
|
||||
finder = self._build_package_finder(
|
||||
options=options,
|
||||
session=session,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
)
|
||||
build_tracker = self.enter_context(get_build_tracker())
|
||||
|
||||
directory = TempDirectory(
|
||||
delete=not options.no_clean,
|
||||
kind="install",
|
||||
globally_managed=True,
|
||||
)
|
||||
|
||||
reqs = self.get_requirements(args, options, finder, session)
|
||||
|
||||
wheel_cache = WheelCache(options.cache_dir)
|
||||
|
||||
# Only when installing is it permitted to use PEP 660.
|
||||
# In other circumstances (pip wheel, pip download) we generate
|
||||
# regular (i.e. non editable) metadata and wheels.
|
||||
for req in reqs:
|
||||
req.permit_editable_wheels = True
|
||||
|
||||
preparer = self.make_requirement_preparer(
|
||||
temp_build_dir=directory,
|
||||
options=options,
|
||||
build_tracker=build_tracker,
|
||||
session=session,
|
||||
finder=finder,
|
||||
use_user_site=False,
|
||||
verbosity=self.verbosity,
|
||||
)
|
||||
resolver = self.make_resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
options=options,
|
||||
wheel_cache=wheel_cache,
|
||||
use_user_site=False,
|
||||
ignore_installed=True,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
upgrade_strategy="to-satisfy-only",
|
||||
)
|
||||
|
||||
self.trace_basic_info(finder)
|
||||
|
||||
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
|
||||
|
||||
if options.output_file == "-":
|
||||
base_dir = Path.cwd()
|
||||
else:
|
||||
output_file_path = Path(options.output_file)
|
||||
if not is_valid_pylock_file_name(output_file_path):
|
||||
logger.warning(
|
||||
"%s is not a valid lock file name.",
|
||||
output_file_path,
|
||||
)
|
||||
base_dir = output_file_path.parent
|
||||
pylock_toml = Pylock.from_install_requirements(
|
||||
requirement_set.requirements.values(), base_dir=base_dir
|
||||
).as_toml()
|
||||
if options.output_file == "-":
|
||||
sys.stdout.write(pylock_toml)
|
||||
else:
|
||||
output_file_path.write_text(pylock_toml, encoding="utf-8")
|
||||
|
||||
return SUCCESS
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import shutil
|
||||
import sys
|
||||
|
|
@ -7,7 +5,7 @@ import textwrap
|
|||
import xmlrpc.client
|
||||
from collections import OrderedDict
|
||||
from optparse import Values
|
||||
from typing import TypedDict
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional
|
||||
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
|
||||
|
|
@ -16,17 +14,18 @@ from pip._internal.cli.req_command import SessionCommandMixin
|
|||
from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.metadata import get_default_environment
|
||||
from pip._internal.metadata.base import BaseDistribution
|
||||
from pip._internal.models.index import PyPI
|
||||
from pip._internal.network.xmlrpc import PipXmlrpcTransport
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import write_output
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import TypedDict
|
||||
|
||||
class TransformedHit(TypedDict):
|
||||
name: str
|
||||
summary: str
|
||||
versions: list[str]
|
||||
class TransformedHit(TypedDict):
|
||||
name: str
|
||||
summary: str
|
||||
versions: List[str]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -51,7 +50,7 @@ class SearchCommand(Command, SessionCommandMixin):
|
|||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
if not args:
|
||||
raise CommandError("Missing required argument (search query).")
|
||||
query = args
|
||||
|
|
@ -67,7 +66,7 @@ class SearchCommand(Command, SessionCommandMixin):
|
|||
return SUCCESS
|
||||
return NO_MATCHES_FOUND
|
||||
|
||||
def search(self, query: list[str], options: Values) -> list[dict[str, str]]:
|
||||
def search(self, query: List[str], options: Values) -> List[Dict[str, str]]:
|
||||
index_url = options.index
|
||||
|
||||
session = self.get_default_session(options)
|
||||
|
|
@ -77,21 +76,22 @@ class SearchCommand(Command, SessionCommandMixin):
|
|||
try:
|
||||
hits = pypi.search({"name": query, "summary": query}, "or")
|
||||
except xmlrpc.client.Fault as fault:
|
||||
message = (
|
||||
f"XMLRPC request failed [code: {fault.faultCode}]\n{fault.faultString}"
|
||||
message = "XMLRPC request failed [code: {code}]\n{string}".format(
|
||||
code=fault.faultCode,
|
||||
string=fault.faultString,
|
||||
)
|
||||
raise CommandError(message)
|
||||
assert isinstance(hits, list)
|
||||
return hits
|
||||
|
||||
|
||||
def transform_hits(hits: list[dict[str, str]]) -> list[TransformedHit]:
|
||||
def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]:
|
||||
"""
|
||||
The list from pypi is really a list of versions. We want a list of
|
||||
packages with the list of versions stored inline. This converts the
|
||||
list from pypi into one we can use.
|
||||
"""
|
||||
packages: dict[str, TransformedHit] = OrderedDict()
|
||||
packages: Dict[str, "TransformedHit"] = OrderedDict()
|
||||
for hit in hits:
|
||||
name = hit["name"]
|
||||
summary = hit["summary"]
|
||||
|
|
@ -113,7 +113,9 @@ def transform_hits(hits: list[dict[str, str]]) -> list[TransformedHit]:
|
|||
return list(packages.values())
|
||||
|
||||
|
||||
def print_dist_installation_info(latest: str, dist: BaseDistribution | None) -> None:
|
||||
def print_dist_installation_info(name: str, latest: str) -> None:
|
||||
env = get_default_environment()
|
||||
dist = env.get_distribution(name)
|
||||
if dist is not None:
|
||||
with indent_log():
|
||||
if dist.version == latest:
|
||||
|
|
@ -130,15 +132,10 @@ def print_dist_installation_info(latest: str, dist: BaseDistribution | None) ->
|
|||
write_output("LATEST: %s", latest)
|
||||
|
||||
|
||||
def get_installed_distribution(name: str) -> BaseDistribution | None:
|
||||
env = get_default_environment()
|
||||
return env.get_distribution(name)
|
||||
|
||||
|
||||
def print_results(
|
||||
hits: list[TransformedHit],
|
||||
name_column_width: int | None = None,
|
||||
terminal_width: int | None = None,
|
||||
hits: List["TransformedHit"],
|
||||
name_column_width: Optional[int] = None,
|
||||
terminal_width: Optional[int] = None,
|
||||
) -> None:
|
||||
if not hits:
|
||||
return
|
||||
|
|
@ -168,11 +165,10 @@ def print_results(
|
|||
line = f"{name_latest:{name_column_width}} - {summary}"
|
||||
try:
|
||||
write_output(line)
|
||||
dist = get_installed_distribution(name)
|
||||
print_dist_installation_info(latest, dist)
|
||||
print_dist_installation_info(name, latest)
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
|
||||
|
||||
def highest_version(versions: list[str]) -> str:
|
||||
def highest_version(versions: List[str]) -> str:
|
||||
return max(versions, key=parse_version)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import string
|
||||
from collections.abc import Generator, Iterable, Iterator
|
||||
from optparse import Values
|
||||
from typing import NamedTuple
|
||||
from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional
|
||||
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
|
|
@ -17,13 +12,6 @@ from pip._internal.utils.misc import write_output
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def normalize_project_url_label(label: str) -> str:
|
||||
# This logic is from PEP 753 (Well-known Project URLs in Metadata).
|
||||
chars_to_remove = string.punctuation + string.whitespace
|
||||
removal_map = str.maketrans("", "", chars_to_remove)
|
||||
return label.translate(removal_map).lower()
|
||||
|
||||
|
||||
class ShowCommand(Command):
|
||||
"""
|
||||
Show information about one or more installed packages.
|
||||
|
|
@ -47,7 +35,7 @@ class ShowCommand(Command):
|
|||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
if not args:
|
||||
logger.warning("ERROR: Please provide a package name or names.")
|
||||
return ERROR
|
||||
|
|
@ -65,24 +53,23 @@ class _PackageInfo(NamedTuple):
|
|||
name: str
|
||||
version: str
|
||||
location: str
|
||||
editable_project_location: str | None
|
||||
requires: list[str]
|
||||
required_by: list[str]
|
||||
editable_project_location: Optional[str]
|
||||
requires: List[str]
|
||||
required_by: List[str]
|
||||
installer: str
|
||||
metadata_version: str
|
||||
classifiers: list[str]
|
||||
classifiers: List[str]
|
||||
summary: str
|
||||
homepage: str
|
||||
project_urls: list[str]
|
||||
project_urls: List[str]
|
||||
author: str
|
||||
author_email: str
|
||||
license: str
|
||||
license_expression: str
|
||||
entry_points: list[str]
|
||||
files: list[str] | None
|
||||
entry_points: List[str]
|
||||
files: Optional[List[str]]
|
||||
|
||||
|
||||
def search_packages_info(query: list[str]) -> Generator[_PackageInfo, None, None]:
|
||||
def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None]:
|
||||
"""
|
||||
Gather details from installed distributions. Print distribution name,
|
||||
version, location, and installed files. Installed files requires a
|
||||
|
|
@ -113,19 +100,8 @@ def search_packages_info(query: list[str]) -> Generator[_PackageInfo, None, None
|
|||
except KeyError:
|
||||
continue
|
||||
|
||||
try:
|
||||
requires = sorted(
|
||||
# Avoid duplicates in requirements (e.g. due to environment markers).
|
||||
{req.name for req in dist.iter_dependencies()},
|
||||
key=str.lower,
|
||||
)
|
||||
except InvalidRequirement:
|
||||
requires = sorted(dist.iter_raw_dependencies(), key=str.lower)
|
||||
|
||||
try:
|
||||
required_by = sorted(_get_requiring_packages(dist), key=str.lower)
|
||||
except InvalidRequirement:
|
||||
required_by = ["#N/A"]
|
||||
requires = sorted((req.name for req in dist.iter_dependencies()), key=str.lower)
|
||||
required_by = sorted(_get_requiring_packages(dist), key=str.lower)
|
||||
|
||||
try:
|
||||
entry_points_text = dist.read_text("entry_points.txt")
|
||||
|
|
@ -135,27 +111,15 @@ def search_packages_info(query: list[str]) -> Generator[_PackageInfo, None, None
|
|||
|
||||
files_iter = dist.iter_declared_entries()
|
||||
if files_iter is None:
|
||||
files: list[str] | None = None
|
||||
files: Optional[List[str]] = None
|
||||
else:
|
||||
files = sorted(files_iter)
|
||||
|
||||
metadata = dist.metadata
|
||||
|
||||
project_urls = metadata.get_all("Project-URL", [])
|
||||
homepage = metadata.get("Home-page", "")
|
||||
if not homepage:
|
||||
# It's common that there is a "homepage" Project-URL, but Home-page
|
||||
# remains unset (especially as PEP 621 doesn't surface the field).
|
||||
for url in project_urls:
|
||||
url_label, url = url.split(",", maxsplit=1)
|
||||
normalized_label = normalize_project_url_label(url_label)
|
||||
if normalized_label == "homepage":
|
||||
homepage = url.strip()
|
||||
break
|
||||
|
||||
yield _PackageInfo(
|
||||
name=dist.raw_name,
|
||||
version=dist.raw_version,
|
||||
version=str(dist.version),
|
||||
location=dist.location or "",
|
||||
editable_project_location=dist.editable_project_location,
|
||||
requires=requires,
|
||||
|
|
@ -164,12 +128,11 @@ def search_packages_info(query: list[str]) -> Generator[_PackageInfo, None, None
|
|||
metadata_version=dist.metadata_version or "",
|
||||
classifiers=metadata.get_all("Classifier", []),
|
||||
summary=metadata.get("Summary", ""),
|
||||
homepage=homepage,
|
||||
project_urls=project_urls,
|
||||
homepage=metadata.get("Home-page", ""),
|
||||
project_urls=metadata.get_all("Project-URL", []),
|
||||
author=metadata.get("Author", ""),
|
||||
author_email=metadata.get("Author-email", ""),
|
||||
license=metadata.get("License", ""),
|
||||
license_expression=metadata.get("License-Expression", ""),
|
||||
entry_points=entry_points,
|
||||
files=files,
|
||||
)
|
||||
|
|
@ -189,18 +152,13 @@ def print_results(
|
|||
if i > 0:
|
||||
write_output("---")
|
||||
|
||||
metadata_version_tuple = tuple(map(int, dist.metadata_version.split(".")))
|
||||
|
||||
write_output("Name: %s", dist.name)
|
||||
write_output("Version: %s", dist.version)
|
||||
write_output("Summary: %s", dist.summary)
|
||||
write_output("Home-page: %s", dist.homepage)
|
||||
write_output("Author: %s", dist.author)
|
||||
write_output("Author-email: %s", dist.author_email)
|
||||
if metadata_version_tuple >= (2, 4) and dist.license_expression:
|
||||
write_output("License-Expression: %s", dist.license_expression)
|
||||
else:
|
||||
write_output("License: %s", dist.license)
|
||||
write_output("License: %s", dist.license)
|
||||
write_output("Location: %s", dist.location)
|
||||
if dist.editable_project_location is not None:
|
||||
write_output(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import logging
|
||||
from optparse import Values
|
||||
from typing import List
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.index_command import SessionCommandMixin
|
||||
from pip._internal.cli.req_command import SessionCommandMixin, warn_if_run_as_root
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.req import parse_requirements
|
||||
|
|
@ -16,7 +17,6 @@ from pip._internal.req.constructors import (
|
|||
from pip._internal.utils.misc import (
|
||||
check_externally_managed,
|
||||
protect_pip_from_modification_on_windows,
|
||||
warn_if_run_as_root,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -61,7 +61,7 @@ class UninstallCommand(Command, SessionCommandMixin):
|
|||
self.cmd_opts.add_option(cmdoptions.override_externally_managed())
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
session = self.get_default_session(options)
|
||||
|
||||
reqs_to_uninstall = {}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import logging
|
|||
import os
|
||||
import shutil
|
||||
from optparse import Values
|
||||
from typing import List
|
||||
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.cli import cmdoptions
|
||||
|
|
@ -11,10 +12,11 @@ from pip._internal.exceptions import CommandError
|
|||
from pip._internal.operations.build.build_tracker import get_build_tracker
|
||||
from pip._internal.req.req_install import (
|
||||
InstallRequirement,
|
||||
check_legacy_setup_py_options,
|
||||
)
|
||||
from pip._internal.utils.misc import ensure_dir, normalize_path
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.wheel_builder import build
|
||||
from pip._internal.wheel_builder import build, should_build_for_wheel_command
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -56,9 +58,9 @@ class WheelCommand(RequirementCommand):
|
|||
self.cmd_opts.add_option(cmdoptions.prefer_binary())
|
||||
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||
self.cmd_opts.add_option(cmdoptions.use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.check_build_deps())
|
||||
self.cmd_opts.add_option(cmdoptions.constraints())
|
||||
self.cmd_opts.add_option(cmdoptions.build_constraints())
|
||||
self.cmd_opts.add_option(cmdoptions.editable())
|
||||
self.cmd_opts.add_option(cmdoptions.requirements())
|
||||
self.cmd_opts.add_option(cmdoptions.src())
|
||||
|
|
@ -75,6 +77,8 @@ class WheelCommand(RequirementCommand):
|
|||
)
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.config_settings())
|
||||
self.cmd_opts.add_option(cmdoptions.build_options())
|
||||
self.cmd_opts.add_option(cmdoptions.global_options())
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--pre",
|
||||
|
|
@ -97,9 +101,7 @@ class WheelCommand(RequirementCommand):
|
|||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
@with_cleanup
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
cmdoptions.check_build_constraints(options)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
session = self.get_default_session(options)
|
||||
|
||||
finder = self._build_package_finder(options, session)
|
||||
|
|
@ -116,6 +118,7 @@ class WheelCommand(RequirementCommand):
|
|||
)
|
||||
|
||||
reqs = self.get_requirements(args, options, finder, session)
|
||||
check_legacy_setup_py_options(options, reqs)
|
||||
|
||||
wheel_cache = WheelCache(options.cache_dir)
|
||||
|
||||
|
|
@ -136,26 +139,30 @@ class WheelCommand(RequirementCommand):
|
|||
options=options,
|
||||
wheel_cache=wheel_cache,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
use_pep517=options.use_pep517,
|
||||
)
|
||||
|
||||
self.trace_basic_info(finder)
|
||||
|
||||
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
|
||||
|
||||
preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
|
||||
|
||||
reqs_to_build: list[InstallRequirement] = []
|
||||
reqs_to_build: List[InstallRequirement] = []
|
||||
for req in requirement_set.requirements.values():
|
||||
if req.is_wheel:
|
||||
preparer.save_linked_requirement(req)
|
||||
else:
|
||||
elif should_build_for_wheel_command(req):
|
||||
reqs_to_build.append(req)
|
||||
|
||||
preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
|
||||
requirement_set.warn_legacy_versions_and_specifiers()
|
||||
|
||||
# build wheels
|
||||
build_successes, build_failures = build(
|
||||
reqs_to_build,
|
||||
wheel_cache=wheel_cache,
|
||||
verify=(not options.no_verify),
|
||||
build_options=options.build_options or [],
|
||||
global_options=options.global_options or [],
|
||||
)
|
||||
for req in build_successes:
|
||||
assert req.link and req.link.is_wheel
|
||||
|
|
|
|||
|
|
@ -11,14 +11,11 @@ Some terminology:
|
|||
A single word describing where the configuration key-value pair came from
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, NewType
|
||||
from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple
|
||||
|
||||
from pip._internal.exceptions import (
|
||||
ConfigurationError,
|
||||
|
|
@ -53,11 +50,12 @@ logger = getLogger(__name__)
|
|||
def _normalize_name(name: str) -> str:
|
||||
"""Make a name consistent regardless of source (environment or file)"""
|
||||
name = name.lower().replace("_", "-")
|
||||
name = name.removeprefix("--") # only prefer long opts
|
||||
if name.startswith("--"):
|
||||
name = name[2:] # only prefer long opts
|
||||
return name
|
||||
|
||||
|
||||
def _disassemble_key(name: str) -> list[str]:
|
||||
def _disassemble_key(name: str) -> List[str]:
|
||||
if "." not in name:
|
||||
error_message = (
|
||||
"Key does not contain dot separated section and key. "
|
||||
|
|
@ -67,7 +65,7 @@ def _disassemble_key(name: str) -> list[str]:
|
|||
return name.split(".", 1)
|
||||
|
||||
|
||||
def get_configuration_files() -> dict[Kind, list[str]]:
|
||||
def get_configuration_files() -> Dict[Kind, List[str]]:
|
||||
global_config_files = [
|
||||
os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip")
|
||||
]
|
||||
|
|
@ -100,7 +98,7 @@ class Configuration:
|
|||
and the data stored is also nice.
|
||||
"""
|
||||
|
||||
def __init__(self, isolated: bool, load_only: Kind | None = None) -> None:
|
||||
def __init__(self, isolated: bool, load_only: Optional[Kind] = None) -> None:
|
||||
super().__init__()
|
||||
|
||||
if load_only is not None and load_only not in VALID_LOAD_ONLY:
|
||||
|
|
@ -113,13 +111,13 @@ class Configuration:
|
|||
self.load_only = load_only
|
||||
|
||||
# Because we keep track of where we got the data from
|
||||
self._parsers: dict[Kind, list[tuple[str, RawConfigParser]]] = {
|
||||
self._parsers: Dict[Kind, List[Tuple[str, RawConfigParser]]] = {
|
||||
variant: [] for variant in OVERRIDE_ORDER
|
||||
}
|
||||
self._config: dict[Kind, dict[str, dict[str, Any]]] = {
|
||||
self._config: Dict[Kind, Dict[str, Any]] = {
|
||||
variant: {} for variant in OVERRIDE_ORDER
|
||||
}
|
||||
self._modified_parsers: list[tuple[str, RawConfigParser]] = []
|
||||
self._modified_parsers: List[Tuple[str, RawConfigParser]] = []
|
||||
|
||||
def load(self) -> None:
|
||||
"""Loads configuration from configuration files and environment"""
|
||||
|
|
@ -127,7 +125,7 @@ class Configuration:
|
|||
if not self.isolated:
|
||||
self._load_environment_vars()
|
||||
|
||||
def get_file_to_edit(self) -> str | None:
|
||||
def get_file_to_edit(self) -> Optional[str]:
|
||||
"""Returns the file with highest priority in configuration"""
|
||||
assert self.load_only is not None, "Need to be specified a file to be editing"
|
||||
|
||||
|
|
@ -136,7 +134,7 @@ class Configuration:
|
|||
except IndexError:
|
||||
return None
|
||||
|
||||
def items(self) -> Iterable[tuple[str, Any]]:
|
||||
def items(self) -> Iterable[Tuple[str, Any]]:
|
||||
"""Returns key-value pairs like dict.items() representing the loaded
|
||||
configuration
|
||||
"""
|
||||
|
|
@ -147,10 +145,7 @@ class Configuration:
|
|||
orig_key = key
|
||||
key = _normalize_name(key)
|
||||
try:
|
||||
clean_config: dict[str, Any] = {}
|
||||
for file_values in self._dictionary.values():
|
||||
clean_config.update(file_values)
|
||||
return clean_config[key]
|
||||
return self._dictionary[key]
|
||||
except KeyError:
|
||||
# disassembling triggers a more useful error message than simply
|
||||
# "No such key" in the case that the key isn't in the form command.option
|
||||
|
|
@ -173,8 +168,7 @@ class Configuration:
|
|||
parser.add_section(section)
|
||||
parser.set(section, name, value)
|
||||
|
||||
self._config[self.load_only].setdefault(fname, {})
|
||||
self._config[self.load_only][fname][key] = value
|
||||
self._config[self.load_only][key] = value
|
||||
self._mark_as_modified(fname, parser)
|
||||
|
||||
def unset_value(self, key: str) -> None:
|
||||
|
|
@ -184,14 +178,11 @@ class Configuration:
|
|||
self._ensure_have_load_only()
|
||||
|
||||
assert self.load_only
|
||||
fname, parser = self._get_parser_to_modify()
|
||||
|
||||
if (
|
||||
key not in self._config[self.load_only][fname]
|
||||
and key not in self._config[self.load_only]
|
||||
):
|
||||
if key not in self._config[self.load_only]:
|
||||
raise ConfigurationError(f"No such key - {orig_key}")
|
||||
|
||||
fname, parser = self._get_parser_to_modify()
|
||||
|
||||
if parser is not None:
|
||||
section, name = _disassemble_key(key)
|
||||
if not (
|
||||
|
|
@ -206,10 +197,8 @@ class Configuration:
|
|||
if not parser.items(section):
|
||||
parser.remove_section(section)
|
||||
self._mark_as_modified(fname, parser)
|
||||
try:
|
||||
del self._config[self.load_only][fname][key]
|
||||
except KeyError:
|
||||
del self._config[self.load_only][key]
|
||||
|
||||
del self._config[self.load_only][key]
|
||||
|
||||
def save(self) -> None:
|
||||
"""Save the current in-memory state."""
|
||||
|
|
@ -241,7 +230,7 @@ class Configuration:
|
|||
logger.debug("Will be working with %s variant only", self.load_only)
|
||||
|
||||
@property
|
||||
def _dictionary(self) -> dict[str, dict[str, Any]]:
|
||||
def _dictionary(self) -> Dict[str, Any]:
|
||||
"""A dictionary representing the loaded configuration."""
|
||||
# NOTE: Dictionaries are not populated if not loaded. So, conditionals
|
||||
# are not needed here.
|
||||
|
|
@ -281,8 +270,7 @@ class Configuration:
|
|||
|
||||
for section in parser.sections():
|
||||
items = parser.items(section)
|
||||
self._config[variant].setdefault(fname, {})
|
||||
self._config[variant][fname].update(self._normalized_keys(section, items))
|
||||
self._config[variant].update(self._normalized_keys(section, items))
|
||||
|
||||
return parser
|
||||
|
||||
|
|
@ -309,14 +297,13 @@ class Configuration:
|
|||
|
||||
def _load_environment_vars(self) -> None:
|
||||
"""Loads configuration from environment variables"""
|
||||
self._config[kinds.ENV_VAR].setdefault(":env:", {})
|
||||
self._config[kinds.ENV_VAR][":env:"].update(
|
||||
self._config[kinds.ENV_VAR].update(
|
||||
self._normalized_keys(":env:", self.get_environ_vars())
|
||||
)
|
||||
|
||||
def _normalized_keys(
|
||||
self, section: str, items: Iterable[tuple[str, Any]]
|
||||
) -> dict[str, Any]:
|
||||
self, section: str, items: Iterable[Tuple[str, Any]]
|
||||
) -> Dict[str, Any]:
|
||||
"""Normalizes items to construct a dictionary with normalized keys.
|
||||
|
||||
This routine is where the names become keys and are made the same
|
||||
|
|
@ -328,7 +315,7 @@ class Configuration:
|
|||
normalized[key] = val
|
||||
return normalized
|
||||
|
||||
def get_environ_vars(self) -> Iterable[tuple[str, str]]:
|
||||
def get_environ_vars(self) -> Iterable[Tuple[str, str]]:
|
||||
"""Returns a generator with all environmental vars with prefix PIP_"""
|
||||
for key, val in os.environ.items():
|
||||
if key.startswith("PIP_"):
|
||||
|
|
@ -337,13 +324,13 @@ class Configuration:
|
|||
yield name, val
|
||||
|
||||
# XXX: This is patched in the tests.
|
||||
def iter_config_files(self) -> Iterable[tuple[Kind, list[str]]]:
|
||||
def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]:
|
||||
"""Yields variant and configuration files associated with it.
|
||||
|
||||
This should be treated like items of a dictionary. The order
|
||||
here doesn't affect what gets overridden. That is controlled
|
||||
by OVERRIDE_ORDER. However this does control the order they are
|
||||
displayed to the user. It's probably most ergonomic to display
|
||||
displayed to the user. It's probably most ergononmic to display
|
||||
things in the same order as OVERRIDE_ORDER
|
||||
"""
|
||||
# SMELL: Move the conditions out of this function
|
||||
|
|
@ -369,11 +356,11 @@ class Configuration:
|
|||
else:
|
||||
yield kinds.ENV, []
|
||||
|
||||
def get_values_in_config(self, variant: Kind) -> dict[str, Any]:
|
||||
def get_values_in_config(self, variant: Kind) -> Dict[str, Any]:
|
||||
"""Get values present in a config file"""
|
||||
return self._config[variant]
|
||||
|
||||
def _get_parser_to_modify(self) -> tuple[str, RawConfigParser]:
|
||||
def _get_parser_to_modify(self) -> Tuple[str, RawConfigParser]:
|
||||
# Determine which parser to modify
|
||||
assert self.load_only
|
||||
parsers = self._parsers[self.load_only]
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,14 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata.base import BaseDistribution
|
||||
from pip._internal.req import InstallRequirement
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._internal.build_env import BuildEnvironmentInstaller
|
||||
|
||||
|
||||
class AbstractDistribution(metaclass=abc.ABCMeta):
|
||||
"""A base class for handling installable artifacts.
|
||||
|
|
@ -34,7 +30,7 @@ class AbstractDistribution(metaclass=abc.ABCMeta):
|
|||
self.req = req
|
||||
|
||||
@abc.abstractproperty
|
||||
def build_tracker_id(self) -> str | None:
|
||||
def build_tracker_id(self) -> Optional[str]:
|
||||
"""A string that uniquely identifies this requirement to the build tracker.
|
||||
|
||||
If None, then this dist has no work to do in the build tracker, and
|
||||
|
|
@ -48,7 +44,7 @@ class AbstractDistribution(metaclass=abc.ABCMeta):
|
|||
@abc.abstractmethod
|
||||
def prepare_distribution_metadata(
|
||||
self,
|
||||
build_env_installer: BuildEnvironmentInstaller,
|
||||
finder: PackageFinder,
|
||||
build_isolation: bool,
|
||||
check_build_deps: bool,
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.distributions.base import AbstractDistribution
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._internal.build_env import BuildEnvironmentInstaller
|
||||
|
||||
|
||||
class InstalledDistribution(AbstractDistribution):
|
||||
"""Represents an installed package.
|
||||
|
|
@ -17,7 +13,7 @@ class InstalledDistribution(AbstractDistribution):
|
|||
"""
|
||||
|
||||
@property
|
||||
def build_tracker_id(self) -> str | None:
|
||||
def build_tracker_id(self) -> Optional[str]:
|
||||
return None
|
||||
|
||||
def get_metadata_distribution(self) -> BaseDistribution:
|
||||
|
|
@ -26,7 +22,7 @@ class InstalledDistribution(AbstractDistribution):
|
|||
|
||||
def prepare_distribution_metadata(
|
||||
self,
|
||||
build_env_installer: BuildEnvironmentInstaller,
|
||||
finder: PackageFinder,
|
||||
build_isolation: bool,
|
||||
check_build_deps: bool,
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Iterable, Optional, Set, Tuple
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.distributions.base import AbstractDistribution
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._internal.build_env import BuildEnvironmentInstaller
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
@ -20,11 +15,11 @@ class SourceDistribution(AbstractDistribution):
|
|||
"""Represents a source distribution.
|
||||
|
||||
The preparation step for these needs metadata for the packages to be
|
||||
generated.
|
||||
generated, either using PEP 517 or using the legacy `setup.py egg_info`.
|
||||
"""
|
||||
|
||||
@property
|
||||
def build_tracker_id(self) -> str | None:
|
||||
def build_tracker_id(self) -> Optional[str]:
|
||||
"""Identify this requirement uniquely by its link."""
|
||||
assert self.req.link
|
||||
return self.req.link.url_without_fragment
|
||||
|
|
@ -34,31 +29,32 @@ class SourceDistribution(AbstractDistribution):
|
|||
|
||||
def prepare_distribution_metadata(
|
||||
self,
|
||||
build_env_installer: BuildEnvironmentInstaller,
|
||||
finder: PackageFinder,
|
||||
build_isolation: bool,
|
||||
check_build_deps: bool,
|
||||
) -> None:
|
||||
# Load pyproject.toml
|
||||
# Load pyproject.toml, to determine whether PEP 517 is to be used
|
||||
self.req.load_pyproject_toml()
|
||||
|
||||
# Set up the build isolation, if this requirement should be isolated
|
||||
if build_isolation:
|
||||
should_isolate = self.req.use_pep517 and build_isolation
|
||||
if should_isolate:
|
||||
# Setup an isolated environment and install the build backend static
|
||||
# requirements in it.
|
||||
self._prepare_build_backend(build_env_installer)
|
||||
# Check that the build backend supports PEP 660. This cannot be done
|
||||
# earlier because we need to setup the build backend to verify it
|
||||
# supports build_editable, nor can it be done later, because we want
|
||||
# to avoid installing build requirements needlessly.
|
||||
self.req.editable_sanity_check()
|
||||
self._prepare_build_backend(finder)
|
||||
# Check that if the requirement is editable, it either supports PEP 660 or
|
||||
# has a setup.py or a setup.cfg. This cannot be done earlier because we need
|
||||
# to setup the build backend to verify it supports build_editable, nor can
|
||||
# it be done later, because we want to avoid installing build requirements
|
||||
# needlessly. Doing it here also works around setuptools generating
|
||||
# UNKNOWN.egg-info when running get_requires_for_build_wheel on a directory
|
||||
# without setup.py nor setup.cfg.
|
||||
self.req.isolated_editable_sanity_check()
|
||||
# Install the dynamic build requirements.
|
||||
self._install_build_reqs(build_env_installer)
|
||||
else:
|
||||
# When not using build isolation, we still need to check that
|
||||
# the build backend supports PEP 660.
|
||||
self.req.editable_sanity_check()
|
||||
self._install_build_reqs(finder)
|
||||
# Check if the current environment provides build dependencies
|
||||
if check_build_deps:
|
||||
should_check_deps = self.req.use_pep517 and check_build_deps
|
||||
if should_check_deps:
|
||||
pyproject_requires = self.req.pyproject_requires
|
||||
assert pyproject_requires is not None
|
||||
conflicting, missing = self.req.build_env.check_requirements(
|
||||
|
|
@ -70,17 +66,15 @@ class SourceDistribution(AbstractDistribution):
|
|||
self._raise_missing_reqs(missing)
|
||||
self.req.prepare_metadata()
|
||||
|
||||
def _prepare_build_backend(
|
||||
self, build_env_installer: BuildEnvironmentInstaller
|
||||
) -> None:
|
||||
def _prepare_build_backend(self, finder: PackageFinder) -> None:
|
||||
# Isolate in a BuildEnvironment and install the build-time
|
||||
# requirements.
|
||||
pyproject_requires = self.req.pyproject_requires
|
||||
assert pyproject_requires is not None
|
||||
|
||||
self.req.build_env = BuildEnvironment(build_env_installer)
|
||||
self.req.build_env = BuildEnvironment()
|
||||
self.req.build_env.install_requirements(
|
||||
pyproject_requires, "overlay", kind="build dependencies", for_req=self.req
|
||||
finder, pyproject_requires, "overlay", kind="build dependencies"
|
||||
)
|
||||
conflicting, missing = self.req.build_env.check_requirements(
|
||||
self.req.requirements_to_check
|
||||
|
|
@ -116,16 +110,14 @@ class SourceDistribution(AbstractDistribution):
|
|||
with backend.subprocess_runner(runner):
|
||||
return backend.get_requires_for_build_editable()
|
||||
|
||||
def _install_build_reqs(
|
||||
self, build_env_installer: BuildEnvironmentInstaller
|
||||
) -> None:
|
||||
def _install_build_reqs(self, finder: PackageFinder) -> None:
|
||||
# Install any extra build dependencies that the backend requests.
|
||||
# This must be done in a second pass, as the pyproject.toml
|
||||
# dependencies must be installed before we can call the backend.
|
||||
if (
|
||||
self.req.editable
|
||||
and self.req.permit_editable_wheels
|
||||
and self.req.supports_pyproject_editable
|
||||
and self.req.supports_pyproject_editable()
|
||||
):
|
||||
build_reqs = self._get_build_requires_editable()
|
||||
else:
|
||||
|
|
@ -134,11 +126,11 @@ class SourceDistribution(AbstractDistribution):
|
|||
if conflicting:
|
||||
self._raise_conflicts("the backend dependencies", conflicting)
|
||||
self.req.build_env.install_requirements(
|
||||
missing, "normal", kind="backend dependencies", for_req=self.req
|
||||
finder, missing, "normal", kind="backend dependencies"
|
||||
)
|
||||
|
||||
def _raise_conflicts(
|
||||
self, conflicting_with: str, conflicting_reqs: set[tuple[str, str]]
|
||||
self, conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]]
|
||||
) -> None:
|
||||
format_string = (
|
||||
"Some build dependencies for {requirement} "
|
||||
|
|
@ -154,7 +146,7 @@ class SourceDistribution(AbstractDistribution):
|
|||
)
|
||||
raise InstallationError(error_message)
|
||||
|
||||
def _raise_missing_reqs(self, missing: set[str]) -> None:
|
||||
def _raise_missing_reqs(self, missing: Set[str]) -> None:
|
||||
format_string = (
|
||||
"Some build dependencies for {requirement} are missing: {missing}."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Optional
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pip._internal.distributions.base import AbstractDistribution
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import (
|
||||
BaseDistribution,
|
||||
FilesystemWheel,
|
||||
get_wheel_distribution,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._internal.build_env import BuildEnvironmentInstaller
|
||||
|
||||
|
||||
class WheelDistribution(AbstractDistribution):
|
||||
"""Represents a wheel distribution.
|
||||
|
|
@ -22,7 +18,7 @@ class WheelDistribution(AbstractDistribution):
|
|||
"""
|
||||
|
||||
@property
|
||||
def build_tracker_id(self) -> str | None:
|
||||
def build_tracker_id(self) -> Optional[str]:
|
||||
return None
|
||||
|
||||
def get_metadata_distribution(self) -> BaseDistribution:
|
||||
|
|
@ -37,7 +33,7 @@ class WheelDistribution(AbstractDistribution):
|
|||
|
||||
def prepare_distribution_metadata(
|
||||
self,
|
||||
build_env_installer: BuildEnvironmentInstaller,
|
||||
finder: PackageFinder,
|
||||
build_isolation: bool,
|
||||
check_build_deps: bool,
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ operate. This is expected to be importable from any/all files within the
|
|||
subpackage and, thus, should not depend on them.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import contextlib
|
||||
import locale
|
||||
|
|
@ -14,23 +12,19 @@ import logging
|
|||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Iterator
|
||||
from itertools import chain, groupby, repeat
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Union
|
||||
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement
|
||||
from pip._vendor.packaging.version import InvalidVersion
|
||||
from pip._vendor.requests.models import Request, Response
|
||||
from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult
|
||||
from pip._vendor.rich.markup import escape
|
||||
from pip._vendor.rich.text import Text
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from hashlib import _Hash
|
||||
|
||||
from pip._vendor.requests.models import Request, Response
|
||||
from typing import Literal
|
||||
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.network.download import _FileDownload
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -44,7 +38,7 @@ def _is_kebab_case(s: str) -> bool:
|
|||
|
||||
|
||||
def _prefix_with_indent(
|
||||
s: Text | str,
|
||||
s: Union[Text, str],
|
||||
console: Console,
|
||||
*,
|
||||
prefix: str,
|
||||
|
|
@ -80,13 +74,13 @@ class DiagnosticPipError(PipError):
|
|||
def __init__(
|
||||
self,
|
||||
*,
|
||||
kind: Literal["error", "warning"] = "error",
|
||||
reference: str | None = None,
|
||||
message: str | Text,
|
||||
context: str | Text | None,
|
||||
hint_stmt: str | Text | None,
|
||||
note_stmt: str | Text | None = None,
|
||||
link: str | None = None,
|
||||
kind: 'Literal["error", "warning"]' = "error",
|
||||
reference: Optional[str] = None,
|
||||
message: Union[str, Text],
|
||||
context: Optional[Union[str, Text]],
|
||||
hint_stmt: Optional[Union[str, Text]],
|
||||
note_stmt: Optional[Union[str, Text]] = None,
|
||||
link: Optional[str] = None,
|
||||
) -> None:
|
||||
# Ensure a proper reference is provided.
|
||||
if reference is None:
|
||||
|
|
@ -190,21 +184,8 @@ class InstallationError(PipError):
|
|||
"""General exception during installation"""
|
||||
|
||||
|
||||
class FailedToPrepareCandidate(InstallationError):
|
||||
"""Raised when we fail to prepare a candidate (i.e. fetch and generate metadata).
|
||||
|
||||
This is intentionally not a diagnostic error, since the output will be presented
|
||||
above this error, when this occurs. This should instead present information to the
|
||||
user.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, *, package_name: str, requirement_chain: str, failed_step: str
|
||||
) -> None:
|
||||
super().__init__(f"Failed to build '{package_name}' when {failed_step.lower()}")
|
||||
self.package_name = package_name
|
||||
self.requirement_chain = requirement_chain
|
||||
self.failed_step = failed_step
|
||||
class UninstallationError(PipError):
|
||||
"""General exception during uninstallation"""
|
||||
|
||||
|
||||
class MissingPyProjectBuildRequires(DiagnosticPipError):
|
||||
|
|
@ -252,7 +233,7 @@ class NoneMetadataError(PipError):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
dist: BaseDistribution,
|
||||
dist: "BaseDistribution",
|
||||
metadata_name: str,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -313,8 +294,8 @@ class NetworkConnectionError(PipError):
|
|||
def __init__(
|
||||
self,
|
||||
error_msg: str,
|
||||
response: Response | None = None,
|
||||
request: Request | None = None,
|
||||
response: Optional[Response] = None,
|
||||
request: Optional[Request] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize NetworkConnectionError with `request` and `response`
|
||||
|
|
@ -363,7 +344,7 @@ class MetadataInconsistent(InstallationError):
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, ireq: InstallRequirement, field: str, f_val: str, m_val: str
|
||||
self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str
|
||||
) -> None:
|
||||
self.ireq = ireq
|
||||
self.field = field
|
||||
|
|
@ -377,17 +358,6 @@ class MetadataInconsistent(InstallationError):
|
|||
)
|
||||
|
||||
|
||||
class MetadataInvalid(InstallationError):
|
||||
"""Metadata is invalid."""
|
||||
|
||||
def __init__(self, ireq: InstallRequirement, error: str) -> None:
|
||||
self.ireq = ireq
|
||||
self.error = error
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Requested {self.ireq} has invalid metadata: {self.error}"
|
||||
|
||||
|
||||
class InstallationSubprocessError(DiagnosticPipError, InstallationError):
|
||||
"""A subprocess call failed."""
|
||||
|
||||
|
|
@ -398,10 +368,10 @@ class InstallationSubprocessError(DiagnosticPipError, InstallationError):
|
|||
*,
|
||||
command_description: str,
|
||||
exit_code: int,
|
||||
output_lines: list[str] | None,
|
||||
output_lines: Optional[List[str]],
|
||||
) -> None:
|
||||
if output_lines is None:
|
||||
output_prompt = Text("No available output.")
|
||||
output_prompt = Text("See above for output.")
|
||||
else:
|
||||
output_prompt = (
|
||||
Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
|
||||
|
|
@ -429,7 +399,7 @@ class InstallationSubprocessError(DiagnosticPipError, InstallationError):
|
|||
return f"{self.command_description} exited with {self.exit_code}"
|
||||
|
||||
|
||||
class MetadataGenerationFailed(DiagnosticPipError, InstallationError):
|
||||
class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
|
||||
reference = "metadata-generation-failed"
|
||||
|
||||
def __init__(
|
||||
|
|
@ -437,7 +407,7 @@ class MetadataGenerationFailed(DiagnosticPipError, InstallationError):
|
|||
*,
|
||||
package_details: str,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
super(InstallationSubprocessError, self).__init__(
|
||||
message="Encountered error while generating package metadata.",
|
||||
context=escape(package_details),
|
||||
hint_stmt="See above for details.",
|
||||
|
|
@ -452,9 +422,9 @@ class HashErrors(InstallationError):
|
|||
"""Multiple HashError instances rolled into one for reporting"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.errors: list[HashError] = []
|
||||
self.errors: List["HashError"] = []
|
||||
|
||||
def append(self, error: HashError) -> None:
|
||||
def append(self, error: "HashError") -> None:
|
||||
self.errors.append(error)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
@ -488,7 +458,7 @@ class HashError(InstallationError):
|
|||
|
||||
"""
|
||||
|
||||
req: InstallRequirement | None = None
|
||||
req: Optional["InstallRequirement"] = None
|
||||
head = ""
|
||||
order: int = -1
|
||||
|
||||
|
|
@ -610,7 +580,7 @@ class HashMismatch(HashError):
|
|||
"someone may have tampered with them."
|
||||
)
|
||||
|
||||
def __init__(self, allowed: dict[str, list[str]], gots: dict[str, _Hash]) -> None:
|
||||
def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:
|
||||
"""
|
||||
:param allowed: A dict of algorithm names pointing to lists of allowed
|
||||
hex digests
|
||||
|
|
@ -635,12 +605,12 @@ class HashMismatch(HashError):
|
|||
|
||||
"""
|
||||
|
||||
def hash_then_or(hash_name: str) -> chain[str]:
|
||||
def hash_then_or(hash_name: str) -> "chain[str]":
|
||||
# For now, all the decent hashes have 6-char names, so we can get
|
||||
# away with hard-coding space literals.
|
||||
return chain([hash_name], repeat(" or"))
|
||||
|
||||
lines: list[str] = []
|
||||
lines: List[str] = []
|
||||
for hash_name, expecteds in self.allowed.items():
|
||||
prefix = hash_then_or(hash_name)
|
||||
lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds)
|
||||
|
|
@ -661,8 +631,8 @@ class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
|
|||
def __init__(
|
||||
self,
|
||||
reason: str = "could not be loaded",
|
||||
fname: str | None = None,
|
||||
error: configparser.Error | None = None,
|
||||
fname: Optional[str] = None,
|
||||
error: Optional[configparser.Error] = None,
|
||||
) -> None:
|
||||
super().__init__(error)
|
||||
self.reason = reason
|
||||
|
|
@ -697,7 +667,7 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
|
|||
|
||||
reference = "externally-managed-environment"
|
||||
|
||||
def __init__(self, error: str | None) -> None:
|
||||
def __init__(self, error: Optional[str]) -> None:
|
||||
if error is None:
|
||||
context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)
|
||||
else:
|
||||
|
|
@ -724,7 +694,7 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
|
|||
try:
|
||||
category = locale.LC_MESSAGES
|
||||
except AttributeError:
|
||||
lang: str | None = None
|
||||
lang: Optional[str] = None
|
||||
else:
|
||||
lang, _ = locale.getlocale(category)
|
||||
if lang is not None:
|
||||
|
|
@ -739,8 +709,8 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
|
|||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
config: pathlib.Path | str,
|
||||
) -> ExternallyManagedEnvironment:
|
||||
config: Union[pathlib.Path, str],
|
||||
) -> "ExternallyManagedEnvironment":
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
try:
|
||||
parser.read(config, encoding="utf-8")
|
||||
|
|
@ -756,143 +726,3 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
|
|||
exc_info = logger.isEnabledFor(VERBOSE)
|
||||
logger.warning("Failed to read %s", config, exc_info=exc_info)
|
||||
return cls(None)
|
||||
|
||||
|
||||
class UninstallMissingRecord(DiagnosticPipError):
|
||||
reference = "uninstall-no-record-file"
|
||||
|
||||
def __init__(self, *, distribution: BaseDistribution) -> None:
|
||||
installer = distribution.installer
|
||||
if not installer or installer == "pip":
|
||||
dep = f"{distribution.raw_name}=={distribution.version}"
|
||||
hint = Text.assemble(
|
||||
"You might be able to recover from this via: ",
|
||||
(f"pip install --force-reinstall --no-deps {dep}", "green"),
|
||||
)
|
||||
else:
|
||||
hint = Text(
|
||||
f"The package was installed by {installer}. "
|
||||
"You should check if it can uninstall the package."
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
message=Text(f"Cannot uninstall {distribution}"),
|
||||
context=(
|
||||
"The package's contents are unknown: "
|
||||
f"no RECORD file was found for {distribution.raw_name}."
|
||||
),
|
||||
hint_stmt=hint,
|
||||
)
|
||||
|
||||
|
||||
class LegacyDistutilsInstall(DiagnosticPipError):
|
||||
reference = "uninstall-distutils-installed-package"
|
||||
|
||||
def __init__(self, *, distribution: BaseDistribution) -> None:
|
||||
super().__init__(
|
||||
message=Text(f"Cannot uninstall {distribution}"),
|
||||
context=(
|
||||
"It is a distutils installed project and thus we cannot accurately "
|
||||
"determine which files belong to it which would lead to only a partial "
|
||||
"uninstall."
|
||||
),
|
||||
hint_stmt=None,
|
||||
)
|
||||
|
||||
|
||||
class InvalidInstalledPackage(DiagnosticPipError):
|
||||
reference = "invalid-installed-package"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
dist: BaseDistribution,
|
||||
invalid_exc: InvalidRequirement | InvalidVersion,
|
||||
) -> None:
|
||||
installed_location = dist.installed_location
|
||||
|
||||
if isinstance(invalid_exc, InvalidRequirement):
|
||||
invalid_type = "requirement"
|
||||
else:
|
||||
invalid_type = "version"
|
||||
|
||||
super().__init__(
|
||||
message=Text(
|
||||
f"Cannot process installed package {dist} "
|
||||
+ (f"in {installed_location!r} " if installed_location else "")
|
||||
+ f"because it has an invalid {invalid_type}:\n{invalid_exc.args[0]}"
|
||||
),
|
||||
context=(
|
||||
"Starting with pip 24.1, packages with invalid "
|
||||
f"{invalid_type}s can not be processed."
|
||||
),
|
||||
hint_stmt="To proceed this package must be uninstalled.",
|
||||
)
|
||||
|
||||
|
||||
class IncompleteDownloadError(DiagnosticPipError):
|
||||
"""Raised when the downloader receives fewer bytes than advertised
|
||||
in the Content-Length header."""
|
||||
|
||||
reference = "incomplete-download"
|
||||
|
||||
def __init__(self, download: _FileDownload) -> None:
|
||||
# Dodge circular import.
|
||||
from pip._internal.utils.misc import format_size
|
||||
|
||||
assert download.size is not None
|
||||
download_status = (
|
||||
f"{format_size(download.bytes_received)}/{format_size(download.size)}"
|
||||
)
|
||||
if download.reattempts:
|
||||
retry_status = f"after {download.reattempts + 1} attempts "
|
||||
hint = "Use --resume-retries to configure resume attempt limit."
|
||||
else:
|
||||
# Download retrying is not enabled.
|
||||
retry_status = ""
|
||||
hint = "Consider using --resume-retries to enable download resumption."
|
||||
message = Text(
|
||||
f"Download failed {retry_status}because not enough bytes "
|
||||
f"were received ({download_status})"
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
context=f"URL: {download.link.redacted_url}",
|
||||
hint_stmt=hint,
|
||||
note_stmt="This is an issue with network connectivity, not pip.",
|
||||
)
|
||||
|
||||
|
||||
class ResolutionTooDeepError(DiagnosticPipError):
|
||||
"""Raised when the dependency resolver exceeds the maximum recursion depth."""
|
||||
|
||||
reference = "resolution-too-deep"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
message="Dependency resolution exceeded maximum depth",
|
||||
context=(
|
||||
"Pip cannot resolve the current dependencies as the dependency graph "
|
||||
"is too complex for pip to solve efficiently."
|
||||
),
|
||||
hint_stmt=(
|
||||
"Try adding lower bounds to constrain your dependencies, "
|
||||
"for example: 'package>=2.0.0' instead of just 'package'. "
|
||||
),
|
||||
link="https://pip.pypa.io/en/stable/topics/dependency-resolution/#handling-resolution-too-deep-errors",
|
||||
)
|
||||
|
||||
|
||||
class InstallWheelBuildError(DiagnosticPipError):
|
||||
reference = "failed-wheel-build-for-install"
|
||||
|
||||
def __init__(self, failed: list[InstallRequirement]) -> None:
|
||||
super().__init__(
|
||||
message=(
|
||||
"Failed to build installable wheels for some "
|
||||
"pyproject.toml based projects"
|
||||
),
|
||||
context=", ".join(r.name for r in failed), # type: ignore
|
||||
hint_stmt=None,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
"""Index interaction code"""
|
||||
"""Index interaction code
|
||||
"""
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -2,8 +2,6 @@
|
|||
The main purpose of this module is to expose LinkCollector.collect_sources().
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import email.message
|
||||
import functools
|
||||
|
|
@ -13,14 +11,20 @@ import logging
|
|||
import os
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from collections.abc import Iterable, MutableMapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from html.parser import HTMLParser
|
||||
from optparse import Values
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
MutableMapping,
|
||||
NamedTuple,
|
||||
Protocol,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
from pip._vendor import requests
|
||||
|
|
@ -38,12 +42,17 @@ from pip._internal.vcs import vcs
|
|||
|
||||
from .sources import CandidatesFromPage, LinkSource, build_source
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
else:
|
||||
Protocol = object
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ResponseHeaders = MutableMapping[str, str]
|
||||
|
||||
|
||||
def _match_vcs_scheme(url: str) -> str | None:
|
||||
def _match_vcs_scheme(url: str) -> Optional[str]:
|
||||
"""Look for VCS schemes in the URL.
|
||||
|
||||
Returns the matched VCS scheme, or None if there's no match.
|
||||
|
|
@ -168,7 +177,7 @@ def _get_simple_response(url: str, session: PipSession) -> Response:
|
|||
return resp
|
||||
|
||||
|
||||
def _get_encoding_from_headers(headers: ResponseHeaders) -> str | None:
|
||||
def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]:
|
||||
"""Determine if we have any encoding information in our headers."""
|
||||
if headers and "Content-Type" in headers:
|
||||
m = email.message.Message()
|
||||
|
|
@ -180,7 +189,7 @@ def _get_encoding_from_headers(headers: ResponseHeaders) -> str | None:
|
|||
|
||||
|
||||
class CacheablePageContent:
|
||||
def __init__(self, page: IndexContent) -> None:
|
||||
def __init__(self, page: "IndexContent") -> None:
|
||||
assert page.cache_link_parsing
|
||||
self.page = page
|
||||
|
||||
|
|
@ -192,7 +201,8 @@ class CacheablePageContent:
|
|||
|
||||
|
||||
class ParseLinks(Protocol):
|
||||
def __call__(self, page: IndexContent) -> Iterable[Link]: ...
|
||||
def __call__(self, page: "IndexContent") -> Iterable[Link]:
|
||||
...
|
||||
|
||||
|
||||
def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
|
||||
|
|
@ -202,12 +212,12 @@ def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
|
|||
`page` has `page.cache_link_parsing == False`.
|
||||
"""
|
||||
|
||||
@functools.cache
|
||||
def wrapper(cacheable_page: CacheablePageContent) -> list[Link]:
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def wrapper(cacheable_page: CacheablePageContent) -> List[Link]:
|
||||
return list(fn(cacheable_page.page))
|
||||
|
||||
@functools.wraps(fn)
|
||||
def wrapper_wrapper(page: IndexContent) -> list[Link]:
|
||||
def wrapper_wrapper(page: "IndexContent") -> List[Link]:
|
||||
if page.cache_link_parsing:
|
||||
return wrapper(CacheablePageContent(page))
|
||||
return list(fn(page))
|
||||
|
|
@ -216,7 +226,7 @@ def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
|
|||
|
||||
|
||||
@with_cached_index_content
|
||||
def parse_links(page: IndexContent) -> Iterable[Link]:
|
||||
def parse_links(page: "IndexContent") -> Iterable[Link]:
|
||||
"""
|
||||
Parse a Simple API's Index Content, and yield its anchor elements as Link objects.
|
||||
"""
|
||||
|
|
@ -244,22 +254,29 @@ def parse_links(page: IndexContent) -> Iterable[Link]:
|
|||
yield link
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class IndexContent:
|
||||
"""Represents one response (or page), along with its URL.
|
||||
"""Represents one response (or page), along with its URL"""
|
||||
|
||||
:param encoding: the encoding to decode the given content.
|
||||
:param url: the URL from which the HTML was downloaded.
|
||||
:param cache_link_parsing: whether links parsed from this page's url
|
||||
should be cached. PyPI index urls should
|
||||
have this set to False, for example.
|
||||
"""
|
||||
|
||||
content: bytes
|
||||
content_type: str
|
||||
encoding: str | None
|
||||
url: str
|
||||
cache_link_parsing: bool = True
|
||||
def __init__(
|
||||
self,
|
||||
content: bytes,
|
||||
content_type: str,
|
||||
encoding: Optional[str],
|
||||
url: str,
|
||||
cache_link_parsing: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
:param encoding: the encoding to decode the given content.
|
||||
:param url: the URL from which the HTML was downloaded.
|
||||
:param cache_link_parsing: whether links parsed from this page's url
|
||||
should be cached. PyPI index urls should
|
||||
have this set to False, for example.
|
||||
"""
|
||||
self.content = content
|
||||
self.content_type = content_type
|
||||
self.encoding = encoding
|
||||
self.url = url
|
||||
self.cache_link_parsing = cache_link_parsing
|
||||
|
||||
def __str__(self) -> str:
|
||||
return redact_auth_from_url(self.url)
|
||||
|
|
@ -275,10 +292,10 @@ class HTMLLinkParser(HTMLParser):
|
|||
super().__init__(convert_charrefs=True)
|
||||
|
||||
self.url: str = url
|
||||
self.base_url: str | None = None
|
||||
self.anchors: list[dict[str, str | None]] = []
|
||||
self.base_url: Optional[str] = None
|
||||
self.anchors: List[Dict[str, Optional[str]]] = []
|
||||
|
||||
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
||||
def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None:
|
||||
if tag == "base" and self.base_url is None:
|
||||
href = self.get_href(attrs)
|
||||
if href is not None:
|
||||
|
|
@ -286,7 +303,7 @@ class HTMLLinkParser(HTMLParser):
|
|||
elif tag == "a":
|
||||
self.anchors.append(dict(attrs))
|
||||
|
||||
def get_href(self, attrs: list[tuple[str, str | None]]) -> str | None:
|
||||
def get_href(self, attrs: List[Tuple[str, Optional[str]]]) -> Optional[str]:
|
||||
for name, value in attrs:
|
||||
if name == "href":
|
||||
return value
|
||||
|
|
@ -295,8 +312,8 @@ class HTMLLinkParser(HTMLParser):
|
|||
|
||||
def _handle_get_simple_fail(
|
||||
link: Link,
|
||||
reason: str | Exception,
|
||||
meth: Callable[..., None] | None = None,
|
||||
reason: Union[str, Exception],
|
||||
meth: Optional[Callable[..., None]] = None,
|
||||
) -> None:
|
||||
if meth is None:
|
||||
meth = logger.debug
|
||||
|
|
@ -316,7 +333,7 @@ def _make_index_content(
|
|||
)
|
||||
|
||||
|
||||
def _get_index_content(link: Link, *, session: PipSession) -> IndexContent | None:
|
||||
def _get_index_content(link: Link, *, session: PipSession) -> Optional["IndexContent"]:
|
||||
url = link.url.split("#", 1)[0]
|
||||
|
||||
# Check for VCS schemes that do not support lookup as web pages.
|
||||
|
|
@ -378,11 +395,12 @@ def _get_index_content(link: Link, *, session: PipSession) -> IndexContent | Non
|
|||
|
||||
|
||||
class CollectedSources(NamedTuple):
|
||||
find_links: Sequence[LinkSource | None]
|
||||
index_urls: Sequence[LinkSource | None]
|
||||
find_links: Sequence[Optional[LinkSource]]
|
||||
index_urls: Sequence[Optional[LinkSource]]
|
||||
|
||||
|
||||
class LinkCollector:
|
||||
|
||||
"""
|
||||
Responsible for collecting Link objects from all configured locations,
|
||||
making network requests as needed.
|
||||
|
|
@ -404,7 +422,7 @@ class LinkCollector:
|
|||
session: PipSession,
|
||||
options: Values,
|
||||
suppress_no_index: bool = False,
|
||||
) -> LinkCollector:
|
||||
) -> "LinkCollector":
|
||||
"""
|
||||
:param session: The Session to use to make requests.
|
||||
:param suppress_no_index: Whether to ignore the --no-index option
|
||||
|
|
@ -433,10 +451,10 @@ class LinkCollector:
|
|||
return link_collector
|
||||
|
||||
@property
|
||||
def find_links(self) -> list[str]:
|
||||
def find_links(self) -> List[str]:
|
||||
return self.search_scope.find_links
|
||||
|
||||
def fetch_response(self, location: Link) -> IndexContent | None:
|
||||
def fetch_response(self, location: Link) -> Optional[IndexContent]:
|
||||
"""
|
||||
Fetch an HTML page containing package links.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,24 +1,16 @@
|
|||
"""Routines related to PyPI, indexes"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import functools
|
||||
import itertools
|
||||
import logging
|
||||
import re
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Set, Tuple, Union
|
||||
|
||||
from pip._vendor.packaging import specifiers
|
||||
from pip._vendor.packaging.tags import Tag
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pip._vendor.packaging.version import InvalidVersion, _BaseVersion
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import _BaseVersion
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
|
||||
from pip._internal.exceptions import (
|
||||
|
|
@ -45,20 +37,20 @@ from pip._internal.utils.packaging import check_requires_python
|
|||
from pip._internal.utils.unpacking import SUPPORTED_EXTENSIONS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import TypeGuard
|
||||
from pip._vendor.typing_extensions import TypeGuard
|
||||
|
||||
__all__ = ["FormatControl", "BestCandidateResult", "PackageFinder"]
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
BuildTag = Union[tuple[()], tuple[int, str]]
|
||||
CandidateSortingKey = tuple[int, int, int, _BaseVersion, Optional[int], BuildTag]
|
||||
BuildTag = Union[Tuple[()], Tuple[int, str]]
|
||||
CandidateSortingKey = Tuple[int, int, int, _BaseVersion, Optional[int], BuildTag]
|
||||
|
||||
|
||||
def _check_link_requires_python(
|
||||
link: Link,
|
||||
version_info: tuple[int, int, int],
|
||||
version_info: Tuple[int, int, int],
|
||||
ignore_requires_python: bool = False,
|
||||
) -> bool:
|
||||
"""
|
||||
|
|
@ -114,6 +106,7 @@ class LinkType(enum.Enum):
|
|||
|
||||
|
||||
class LinkEvaluator:
|
||||
|
||||
"""
|
||||
Responsible for evaluating links for a particular project.
|
||||
"""
|
||||
|
|
@ -127,11 +120,11 @@ class LinkEvaluator:
|
|||
def __init__(
|
||||
self,
|
||||
project_name: str,
|
||||
canonical_name: NormalizedName,
|
||||
formats: frozenset[str],
|
||||
canonical_name: str,
|
||||
formats: FrozenSet[str],
|
||||
target_python: TargetPython,
|
||||
allow_yanked: bool,
|
||||
ignore_requires_python: bool | None = None,
|
||||
ignore_requires_python: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param project_name: The user supplied package name.
|
||||
|
|
@ -161,7 +154,7 @@ class LinkEvaluator:
|
|||
|
||||
self.project_name = project_name
|
||||
|
||||
def evaluate_link(self, link: Link) -> tuple[LinkType, str]:
|
||||
def evaluate_link(self, link: Link) -> Tuple[LinkType, str]:
|
||||
"""
|
||||
Determine whether a link is a candidate for installation.
|
||||
|
||||
|
|
@ -201,7 +194,7 @@ class LinkEvaluator:
|
|||
LinkType.format_invalid,
|
||||
"invalid wheel filename",
|
||||
)
|
||||
if wheel.name != self._canonical_name:
|
||||
if canonicalize_name(wheel.name) != self._canonical_name:
|
||||
reason = f"wrong project name (not {self.project_name})"
|
||||
return (LinkType.different_project, reason)
|
||||
|
||||
|
|
@ -248,19 +241,7 @@ class LinkEvaluator:
|
|||
ignore_requires_python=self._ignore_requires_python,
|
||||
)
|
||||
if not supports_python:
|
||||
requires_python = link.requires_python
|
||||
if requires_python:
|
||||
|
||||
def get_version_sort_key(v: str) -> tuple[int, ...]:
|
||||
return tuple(int(s) for s in v.split(".") if s.isdigit())
|
||||
|
||||
requires_python = ",".join(
|
||||
sorted(
|
||||
(str(s) for s in specifiers.SpecifierSet(requires_python)),
|
||||
key=get_version_sort_key,
|
||||
)
|
||||
)
|
||||
reason = f"{version} Requires-Python {requires_python}"
|
||||
reason = f"{version} Requires-Python {link.requires_python}"
|
||||
return (LinkType.requires_python_mismatch, reason)
|
||||
|
||||
logger.debug("Found link %s, version: %s", link, version)
|
||||
|
|
@ -269,10 +250,10 @@ class LinkEvaluator:
|
|||
|
||||
|
||||
def filter_unallowed_hashes(
|
||||
candidates: list[InstallationCandidate],
|
||||
hashes: Hashes | None,
|
||||
candidates: List[InstallationCandidate],
|
||||
hashes: Optional[Hashes],
|
||||
project_name: str,
|
||||
) -> list[InstallationCandidate]:
|
||||
) -> List[InstallationCandidate]:
|
||||
"""
|
||||
Filter out candidates whose hashes aren't allowed, and return a new
|
||||
list of candidates.
|
||||
|
|
@ -342,44 +323,67 @@ def filter_unallowed_hashes(
|
|||
return filtered
|
||||
|
||||
|
||||
@dataclass
|
||||
class CandidatePreferences:
|
||||
|
||||
"""
|
||||
Encapsulates some of the preferences for filtering and sorting
|
||||
InstallationCandidate objects.
|
||||
"""
|
||||
|
||||
prefer_binary: bool = False
|
||||
allow_all_prereleases: bool = False
|
||||
def __init__(
|
||||
self,
|
||||
prefer_binary: bool = False,
|
||||
allow_all_prereleases: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
:param allow_all_prereleases: Whether to allow all pre-releases.
|
||||
"""
|
||||
self.allow_all_prereleases = allow_all_prereleases
|
||||
self.prefer_binary = prefer_binary
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BestCandidateResult:
|
||||
"""A collection of candidates, returned by `PackageFinder.find_best_candidate`.
|
||||
|
||||
This class is only intended to be instantiated by CandidateEvaluator's
|
||||
`compute_best_candidate()` method.
|
||||
|
||||
:param all_candidates: A sequence of all available candidates found.
|
||||
:param applicable_candidates: The applicable candidates.
|
||||
:param best_candidate: The most preferred candidate found, or None
|
||||
if no applicable candidates were found.
|
||||
"""
|
||||
|
||||
all_candidates: list[InstallationCandidate]
|
||||
applicable_candidates: list[InstallationCandidate]
|
||||
best_candidate: InstallationCandidate | None
|
||||
def __init__(
|
||||
self,
|
||||
candidates: List[InstallationCandidate],
|
||||
applicable_candidates: List[InstallationCandidate],
|
||||
best_candidate: Optional[InstallationCandidate],
|
||||
) -> None:
|
||||
"""
|
||||
:param candidates: A sequence of all available candidates found.
|
||||
:param applicable_candidates: The applicable candidates.
|
||||
:param best_candidate: The most preferred candidate found, or None
|
||||
if no applicable candidates were found.
|
||||
"""
|
||||
assert set(applicable_candidates) <= set(candidates)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
assert set(self.applicable_candidates) <= set(self.all_candidates)
|
||||
|
||||
if self.best_candidate is None:
|
||||
assert not self.applicable_candidates
|
||||
if best_candidate is None:
|
||||
assert not applicable_candidates
|
||||
else:
|
||||
assert self.best_candidate in self.applicable_candidates
|
||||
assert best_candidate in applicable_candidates
|
||||
|
||||
self._applicable_candidates = applicable_candidates
|
||||
self._candidates = candidates
|
||||
|
||||
self.best_candidate = best_candidate
|
||||
|
||||
def iter_all(self) -> Iterable[InstallationCandidate]:
|
||||
"""Iterate through all candidates."""
|
||||
return iter(self._candidates)
|
||||
|
||||
def iter_applicable(self) -> Iterable[InstallationCandidate]:
|
||||
"""Iterate through the applicable candidates."""
|
||||
return iter(self._applicable_candidates)
|
||||
|
||||
|
||||
class CandidateEvaluator:
|
||||
|
||||
"""
|
||||
Responsible for filtering and sorting candidates for installation based
|
||||
on what tags are valid.
|
||||
|
|
@ -389,12 +393,12 @@ class CandidateEvaluator:
|
|||
def create(
|
||||
cls,
|
||||
project_name: str,
|
||||
target_python: TargetPython | None = None,
|
||||
target_python: Optional[TargetPython] = None,
|
||||
prefer_binary: bool = False,
|
||||
allow_all_prereleases: bool = False,
|
||||
specifier: specifiers.BaseSpecifier | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
) -> CandidateEvaluator:
|
||||
specifier: Optional[specifiers.BaseSpecifier] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
) -> "CandidateEvaluator":
|
||||
"""Create a CandidateEvaluator object.
|
||||
|
||||
:param target_python: The target Python interpreter to use when
|
||||
|
|
@ -424,11 +428,11 @@ class CandidateEvaluator:
|
|||
def __init__(
|
||||
self,
|
||||
project_name: str,
|
||||
supported_tags: list[Tag],
|
||||
supported_tags: List[Tag],
|
||||
specifier: specifiers.BaseSpecifier,
|
||||
prefer_binary: bool = False,
|
||||
allow_all_prereleases: bool = False,
|
||||
hashes: Hashes | None = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param supported_tags: The PEP 425 tags supported by the target
|
||||
|
|
@ -449,31 +453,32 @@ class CandidateEvaluator:
|
|||
|
||||
def get_applicable_candidates(
|
||||
self,
|
||||
candidates: list[InstallationCandidate],
|
||||
) -> list[InstallationCandidate]:
|
||||
candidates: List[InstallationCandidate],
|
||||
) -> List[InstallationCandidate]:
|
||||
"""
|
||||
Return the applicable candidates from a list of candidates.
|
||||
"""
|
||||
# Using None infers from the specifier instead.
|
||||
allow_prereleases = self._allow_all_prereleases or None
|
||||
specifier = self._specifier
|
||||
|
||||
# We turn the version object into a str here because otherwise
|
||||
# when we're debundled but setuptools isn't, Python will see
|
||||
# packaging.version.Version and
|
||||
# pkg_resources._vendor.packaging.version.Version as different
|
||||
# types. This way we'll use a str as a common data interchange
|
||||
# format. If we stop using the pkg_resources provided specifier
|
||||
# and start using our own, we can drop the cast to str().
|
||||
candidates_and_versions = [(c, str(c.version)) for c in candidates]
|
||||
versions = set(
|
||||
specifier.filter(
|
||||
(v for _, v in candidates_and_versions),
|
||||
versions = {
|
||||
str(v)
|
||||
for v in specifier.filter(
|
||||
# We turn the version object into a str here because otherwise
|
||||
# when we're debundled but setuptools isn't, Python will see
|
||||
# packaging.version.Version and
|
||||
# pkg_resources._vendor.packaging.version.Version as different
|
||||
# types. This way we'll use a str as a common data interchange
|
||||
# format. If we stop using the pkg_resources provided specifier
|
||||
# and start using our own, we can drop the cast to str().
|
||||
(str(c.version) for c in candidates),
|
||||
prereleases=allow_prereleases,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
# Again, converting version to str to deal with debundling.
|
||||
applicable_candidates = [c for c in candidates if str(c.version) in versions]
|
||||
|
||||
applicable_candidates = [c for c, v in candidates_and_versions if v in versions]
|
||||
filtered_applicable_candidates = filter_unallowed_hashes(
|
||||
candidates=applicable_candidates,
|
||||
hashes=self._hashes,
|
||||
|
|
@ -533,7 +538,11 @@ class CandidateEvaluator:
|
|||
)
|
||||
if self._prefer_binary:
|
||||
binary_preference = 1
|
||||
build_tag = wheel.build_tag
|
||||
if wheel.build_tag is not None:
|
||||
match = re.match(r"^(\d+)(.*)$", wheel.build_tag)
|
||||
assert match is not None, "guaranteed by filename validation"
|
||||
build_tag_groups = match.groups()
|
||||
build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
|
||||
else: # sdist
|
||||
pri = -(support_num)
|
||||
has_allowed_hash = int(link.is_hash_allowed(self._hashes))
|
||||
|
|
@ -549,8 +558,8 @@ class CandidateEvaluator:
|
|||
|
||||
def sort_best_candidate(
|
||||
self,
|
||||
candidates: list[InstallationCandidate],
|
||||
) -> InstallationCandidate | None:
|
||||
candidates: List[InstallationCandidate],
|
||||
) -> Optional[InstallationCandidate]:
|
||||
"""
|
||||
Return the best candidate per the instance's sort order, or None if
|
||||
no candidate is acceptable.
|
||||
|
|
@ -562,7 +571,7 @@ class CandidateEvaluator:
|
|||
|
||||
def compute_best_candidate(
|
||||
self,
|
||||
candidates: list[InstallationCandidate],
|
||||
candidates: List[InstallationCandidate],
|
||||
) -> BestCandidateResult:
|
||||
"""
|
||||
Compute and return a `BestCandidateResult` instance.
|
||||
|
|
@ -590,9 +599,9 @@ class PackageFinder:
|
|||
link_collector: LinkCollector,
|
||||
target_python: TargetPython,
|
||||
allow_yanked: bool,
|
||||
format_control: FormatControl | None = None,
|
||||
candidate_prefs: CandidatePreferences | None = None,
|
||||
ignore_requires_python: bool | None = None,
|
||||
format_control: Optional[FormatControl] = None,
|
||||
candidate_prefs: Optional[CandidatePreferences] = None,
|
||||
ignore_requires_python: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""
|
||||
This constructor is primarily meant to be used by the create() class
|
||||
|
|
@ -618,14 +627,7 @@ class PackageFinder:
|
|||
self.format_control = format_control
|
||||
|
||||
# These are boring links that have already been logged somehow.
|
||||
self._logged_links: set[tuple[Link, LinkType, str]] = set()
|
||||
|
||||
# Cache of the result of finding candidates
|
||||
self._all_candidates: dict[str, list[InstallationCandidate]] = {}
|
||||
self._best_candidates: dict[
|
||||
tuple[str, specifiers.BaseSpecifier | None, Hashes | None],
|
||||
BestCandidateResult,
|
||||
] = {}
|
||||
self._logged_links: Set[Tuple[Link, LinkType, str]] = set()
|
||||
|
||||
# Don't include an allow_yanked default value to make sure each call
|
||||
# site considers whether yanked releases are allowed. This also causes
|
||||
|
|
@ -636,8 +638,8 @@ class PackageFinder:
|
|||
cls,
|
||||
link_collector: LinkCollector,
|
||||
selection_prefs: SelectionPreferences,
|
||||
target_python: TargetPython | None = None,
|
||||
) -> PackageFinder:
|
||||
target_python: Optional[TargetPython] = None,
|
||||
) -> "PackageFinder":
|
||||
"""Create a PackageFinder.
|
||||
|
||||
:param selection_prefs: The candidate selection preferences, as a
|
||||
|
|
@ -676,36 +678,18 @@ class PackageFinder:
|
|||
self._link_collector.search_scope = search_scope
|
||||
|
||||
@property
|
||||
def find_links(self) -> list[str]:
|
||||
def find_links(self) -> List[str]:
|
||||
return self._link_collector.find_links
|
||||
|
||||
@property
|
||||
def index_urls(self) -> list[str]:
|
||||
def index_urls(self) -> List[str]:
|
||||
return self.search_scope.index_urls
|
||||
|
||||
@property
|
||||
def proxy(self) -> str | None:
|
||||
return self._link_collector.session.pip_proxy
|
||||
|
||||
@property
|
||||
def trusted_hosts(self) -> Iterable[str]:
|
||||
for host_port in self._link_collector.session.pip_trusted_origins:
|
||||
yield build_netloc(*host_port)
|
||||
|
||||
@property
|
||||
def custom_cert(self) -> str | None:
|
||||
# session.verify is either a boolean (use default bundle/no SSL
|
||||
# verification) or a string path to a custom CA bundle to use. We only
|
||||
# care about the latter.
|
||||
verify = self._link_collector.session.verify
|
||||
return verify if isinstance(verify, str) else None
|
||||
|
||||
@property
|
||||
def client_cert(self) -> str | None:
|
||||
cert = self._link_collector.session.cert
|
||||
assert not isinstance(cert, tuple), "pip only supports PEM client certs"
|
||||
return cert
|
||||
|
||||
@property
|
||||
def allow_all_prereleases(self) -> bool:
|
||||
return self._candidate_prefs.allow_all_prereleases
|
||||
|
|
@ -720,7 +704,7 @@ class PackageFinder:
|
|||
def set_prefer_binary(self) -> None:
|
||||
self._candidate_prefs.prefer_binary = True
|
||||
|
||||
def requires_python_skipped_reasons(self) -> list[str]:
|
||||
def requires_python_skipped_reasons(self) -> List[str]:
|
||||
reasons = {
|
||||
detail
|
||||
for _, result, detail in self._logged_links
|
||||
|
|
@ -741,13 +725,13 @@ class PackageFinder:
|
|||
ignore_requires_python=self._ignore_requires_python,
|
||||
)
|
||||
|
||||
def _sort_links(self, links: Iterable[Link]) -> list[Link]:
|
||||
def _sort_links(self, links: Iterable[Link]) -> List[Link]:
|
||||
"""
|
||||
Returns elements of links in order, non-egg links first, egg links
|
||||
second, while eliminating duplicates
|
||||
"""
|
||||
eggs, no_eggs = [], []
|
||||
seen: set[Link] = set()
|
||||
seen: Set[Link] = set()
|
||||
for link in links:
|
||||
if link not in seen:
|
||||
seen.add(link)
|
||||
|
|
@ -767,7 +751,7 @@ class PackageFinder:
|
|||
|
||||
def get_install_candidate(
|
||||
self, link_evaluator: LinkEvaluator, link: Link
|
||||
) -> InstallationCandidate | None:
|
||||
) -> Optional[InstallationCandidate]:
|
||||
"""
|
||||
If the link is a candidate for install, convert it to an
|
||||
InstallationCandidate and return it. Otherwise, return None.
|
||||
|
|
@ -777,18 +761,15 @@ class PackageFinder:
|
|||
self._log_skipped_link(link, result, detail)
|
||||
return None
|
||||
|
||||
try:
|
||||
return InstallationCandidate(
|
||||
name=link_evaluator.project_name,
|
||||
link=link,
|
||||
version=detail,
|
||||
)
|
||||
except InvalidVersion:
|
||||
return None
|
||||
return InstallationCandidate(
|
||||
name=link_evaluator.project_name,
|
||||
link=link,
|
||||
version=detail,
|
||||
)
|
||||
|
||||
def evaluate_links(
|
||||
self, link_evaluator: LinkEvaluator, links: Iterable[Link]
|
||||
) -> list[InstallationCandidate]:
|
||||
) -> List[InstallationCandidate]:
|
||||
"""
|
||||
Convert links that are candidates to InstallationCandidate objects.
|
||||
"""
|
||||
|
|
@ -802,7 +783,7 @@ class PackageFinder:
|
|||
|
||||
def process_project_url(
|
||||
self, project_url: Link, link_evaluator: LinkEvaluator
|
||||
) -> list[InstallationCandidate]:
|
||||
) -> List[InstallationCandidate]:
|
||||
logger.debug(
|
||||
"Fetching project page and analyzing links: %s",
|
||||
project_url,
|
||||
|
|
@ -821,7 +802,8 @@ class PackageFinder:
|
|||
|
||||
return package_links
|
||||
|
||||
def find_all_candidates(self, project_name: str) -> list[InstallationCandidate]:
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def find_all_candidates(self, project_name: str) -> List[InstallationCandidate]:
|
||||
"""Find all available InstallationCandidate for project_name
|
||||
|
||||
This checks index_urls and find_links.
|
||||
|
|
@ -830,9 +812,6 @@ class PackageFinder:
|
|||
See LinkEvaluator.evaluate_link() for details on which files
|
||||
are accepted.
|
||||
"""
|
||||
if project_name in self._all_candidates:
|
||||
return self._all_candidates[project_name]
|
||||
|
||||
link_evaluator = self.make_link_evaluator(project_name)
|
||||
|
||||
collected_sources = self._link_collector.collect_sources(
|
||||
|
|
@ -874,15 +853,13 @@ class PackageFinder:
|
|||
logger.debug("Local files found: %s", ", ".join(paths))
|
||||
|
||||
# This is an intentional priority ordering
|
||||
self._all_candidates[project_name] = file_candidates + page_candidates
|
||||
|
||||
return self._all_candidates[project_name]
|
||||
return file_candidates + page_candidates
|
||||
|
||||
def make_candidate_evaluator(
|
||||
self,
|
||||
project_name: str,
|
||||
specifier: specifiers.BaseSpecifier | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
specifier: Optional[specifiers.BaseSpecifier] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
) -> CandidateEvaluator:
|
||||
"""Create a CandidateEvaluator object to use."""
|
||||
candidate_prefs = self._candidate_prefs
|
||||
|
|
@ -895,11 +872,12 @@ class PackageFinder:
|
|||
hashes=hashes,
|
||||
)
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def find_best_candidate(
|
||||
self,
|
||||
project_name: str,
|
||||
specifier: specifiers.BaseSpecifier | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
specifier: Optional[specifiers.BaseSpecifier] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
) -> BestCandidateResult:
|
||||
"""Find matches for the given project and specifier.
|
||||
|
||||
|
|
@ -909,42 +887,32 @@ class PackageFinder:
|
|||
|
||||
:return: A `BestCandidateResult` instance.
|
||||
"""
|
||||
if (project_name, specifier, hashes) in self._best_candidates:
|
||||
return self._best_candidates[project_name, specifier, hashes]
|
||||
|
||||
candidates = self.find_all_candidates(project_name)
|
||||
candidate_evaluator = self.make_candidate_evaluator(
|
||||
project_name=project_name,
|
||||
specifier=specifier,
|
||||
hashes=hashes,
|
||||
)
|
||||
self._best_candidates[project_name, specifier, hashes] = (
|
||||
candidate_evaluator.compute_best_candidate(candidates)
|
||||
)
|
||||
|
||||
return self._best_candidates[project_name, specifier, hashes]
|
||||
return candidate_evaluator.compute_best_candidate(candidates)
|
||||
|
||||
def find_requirement(
|
||||
self, req: InstallRequirement, upgrade: bool
|
||||
) -> InstallationCandidate | None:
|
||||
) -> Optional[InstallationCandidate]:
|
||||
"""Try to find a Link matching req
|
||||
|
||||
Expects req, an InstallRequirement and upgrade, a boolean
|
||||
Returns a InstallationCandidate if found,
|
||||
Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise
|
||||
"""
|
||||
name = req.name
|
||||
assert name is not None, "find_requirement() called with no name"
|
||||
|
||||
hashes = req.hashes(trust_internet=False)
|
||||
best_candidate_result = self.find_best_candidate(
|
||||
name,
|
||||
req.name,
|
||||
specifier=req.specifier,
|
||||
hashes=hashes,
|
||||
)
|
||||
best_candidate = best_candidate_result.best_candidate
|
||||
|
||||
installed_version: _BaseVersion | None = None
|
||||
installed_version: Optional[_BaseVersion] = None
|
||||
if req.satisfied_by is not None:
|
||||
installed_version = req.satisfied_by.version
|
||||
|
||||
|
|
@ -968,14 +936,14 @@ class PackageFinder:
|
|||
"Could not find a version that satisfies the requirement %s "
|
||||
"(from versions: %s)",
|
||||
req,
|
||||
_format_versions(best_candidate_result.all_candidates),
|
||||
_format_versions(best_candidate_result.iter_all()),
|
||||
)
|
||||
|
||||
raise DistributionNotFound(f"No matching distribution found for {req}")
|
||||
|
||||
def _should_install_candidate(
|
||||
candidate: InstallationCandidate | None,
|
||||
) -> TypeGuard[InstallationCandidate]:
|
||||
candidate: Optional[InstallationCandidate],
|
||||
) -> "TypeGuard[InstallationCandidate]":
|
||||
if installed_version is None:
|
||||
return True
|
||||
if best_candidate is None:
|
||||
|
|
@ -1002,7 +970,7 @@ class PackageFinder:
|
|||
logger.debug(
|
||||
"Using version %s (newest of versions: %s)",
|
||||
best_candidate.version,
|
||||
_format_versions(best_candidate_result.applicable_candidates),
|
||||
_format_versions(best_candidate_result.iter_applicable()),
|
||||
)
|
||||
return best_candidate
|
||||
|
||||
|
|
@ -1010,7 +978,7 @@ class PackageFinder:
|
|||
logger.debug(
|
||||
"Installed version (%s) is most up-to-date (past versions: %s)",
|
||||
installed_version,
|
||||
_format_versions(best_candidate_result.applicable_candidates),
|
||||
_format_versions(best_candidate_result.iter_applicable()),
|
||||
)
|
||||
raise BestVersionAlreadyInstalled
|
||||
|
||||
|
|
@ -1041,7 +1009,7 @@ def _find_name_version_sep(fragment: str, canonical_name: str) -> int:
|
|||
raise ValueError(f"{fragment} does not match {canonical_name}")
|
||||
|
||||
|
||||
def _extract_version_from_fragment(fragment: str, canonical_name: str) -> str | None:
|
||||
def _extract_version_from_fragment(fragment: str, canonical_name: str) -> Optional[str]:
|
||||
"""Parse the version string from a <package>+<version> filename
|
||||
"fragment" (stem) or egg fragment.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from collections.abc import Iterable
|
||||
from typing import Callable
|
||||
from typing import Callable, Dict, Iterable, List, Optional, Tuple
|
||||
|
||||
from pip._vendor.packaging.utils import (
|
||||
InvalidSdistFilename,
|
||||
InvalidVersion,
|
||||
InvalidWheelFilename,
|
||||
canonicalize_name,
|
||||
parse_sdist_filename,
|
||||
|
|
@ -30,7 +28,7 @@ PageValidator = Callable[[Link], bool]
|
|||
|
||||
class LinkSource:
|
||||
@property
|
||||
def link(self) -> Link | None:
|
||||
def link(self) -> Optional[Link]:
|
||||
"""Returns the underlying link, if there's one."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -52,8 +50,8 @@ class _FlatDirectoryToUrls:
|
|||
|
||||
def __init__(self, path: str) -> None:
|
||||
self._path = path
|
||||
self._page_candidates: list[str] = []
|
||||
self._project_name_to_urls: dict[str, list[str]] = defaultdict(list)
|
||||
self._page_candidates: List[str] = []
|
||||
self._project_name_to_urls: Dict[str, List[str]] = defaultdict(list)
|
||||
self._scanned_directory = False
|
||||
|
||||
def _scan_directory(self) -> None:
|
||||
|
|
@ -70,24 +68,24 @@ class _FlatDirectoryToUrls:
|
|||
# otherwise not worth considering as a package
|
||||
try:
|
||||
project_filename = parse_wheel_filename(entry.name)[0]
|
||||
except InvalidWheelFilename:
|
||||
except (InvalidWheelFilename, InvalidVersion):
|
||||
try:
|
||||
project_filename = parse_sdist_filename(entry.name)[0]
|
||||
except InvalidSdistFilename:
|
||||
except (InvalidSdistFilename, InvalidVersion):
|
||||
continue
|
||||
|
||||
self._project_name_to_urls[project_filename].append(url)
|
||||
self._scanned_directory = True
|
||||
|
||||
@property
|
||||
def page_candidates(self) -> list[str]:
|
||||
def page_candidates(self) -> List[str]:
|
||||
if not self._scanned_directory:
|
||||
self._scan_directory()
|
||||
|
||||
return self._page_candidates
|
||||
|
||||
@property
|
||||
def project_name_to_urls(self) -> dict[str, list[str]]:
|
||||
def project_name_to_urls(self) -> Dict[str, List[str]]:
|
||||
if not self._scanned_directory:
|
||||
self._scan_directory()
|
||||
|
||||
|
|
@ -103,7 +101,7 @@ class _FlatDirectorySource(LinkSource):
|
|||
* ``file_candidates``: Archives in the directory.
|
||||
"""
|
||||
|
||||
_paths_to_urls: dict[str, _FlatDirectoryToUrls] = {}
|
||||
_paths_to_urls: Dict[str, _FlatDirectoryToUrls] = {}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -122,7 +120,7 @@ class _FlatDirectorySource(LinkSource):
|
|||
self._paths_to_urls[path] = self._path_to_urls
|
||||
|
||||
@property
|
||||
def link(self) -> Link | None:
|
||||
def link(self) -> Optional[Link]:
|
||||
return None
|
||||
|
||||
def page_candidates(self) -> FoundCandidates:
|
||||
|
|
@ -153,7 +151,7 @@ class _LocalFileSource(LinkSource):
|
|||
self._link = link
|
||||
|
||||
@property
|
||||
def link(self) -> Link | None:
|
||||
def link(self) -> Optional[Link]:
|
||||
return self._link
|
||||
|
||||
def page_candidates(self) -> FoundCandidates:
|
||||
|
|
@ -187,7 +185,7 @@ class _RemoteFileSource(LinkSource):
|
|||
self._link = link
|
||||
|
||||
@property
|
||||
def link(self) -> Link | None:
|
||||
def link(self) -> Optional[Link]:
|
||||
return self._link
|
||||
|
||||
def page_candidates(self) -> FoundCandidates:
|
||||
|
|
@ -215,7 +213,7 @@ class _IndexDirectorySource(LinkSource):
|
|||
self._link = link
|
||||
|
||||
@property
|
||||
def link(self) -> Link | None:
|
||||
def link(self) -> Optional[Link]:
|
||||
return self._link
|
||||
|
||||
def page_candidates(self) -> FoundCandidates:
|
||||
|
|
@ -233,9 +231,9 @@ def build_source(
|
|||
expand_dir: bool,
|
||||
cache_link_parsing: bool,
|
||||
project_name: str,
|
||||
) -> tuple[str | None, LinkSource | None]:
|
||||
path: str | None = None
|
||||
url: str | None = None
|
||||
) -> Tuple[Optional[str], Optional[LinkSource]]:
|
||||
path: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
if os.path.exists(location): # Is a local path.
|
||||
url = path_to_url(location)
|
||||
path = location
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import sysconfig
|
||||
from typing import Any
|
||||
from typing import Any, Dict, Generator, Optional, Tuple
|
||||
|
||||
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
|
|
@ -89,7 +87,7 @@ def _looks_like_bpo_44860() -> bool:
|
|||
return unix_user_platlib == "$usersite"
|
||||
|
||||
|
||||
def _looks_like_red_hat_patched_platlib_purelib(scheme: dict[str, str]) -> bool:
|
||||
def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool:
|
||||
platlib = scheme["platlib"]
|
||||
if "/$platlibdir/" in platlib:
|
||||
platlib = platlib.replace("/$platlibdir/", f"/{_PLATLIBDIR}/")
|
||||
|
|
@ -99,7 +97,7 @@ def _looks_like_red_hat_patched_platlib_purelib(scheme: dict[str, str]) -> bool:
|
|||
return unpatched.replace("$platbase/", "$base/") == scheme["purelib"]
|
||||
|
||||
|
||||
@functools.cache
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _looks_like_red_hat_lib() -> bool:
|
||||
"""Red Hat patches platlib in unix_prefix and unix_home, but not purelib.
|
||||
|
||||
|
|
@ -114,7 +112,7 @@ def _looks_like_red_hat_lib() -> bool:
|
|||
)
|
||||
|
||||
|
||||
@functools.cache
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _looks_like_debian_scheme() -> bool:
|
||||
"""Debian adds two additional schemes."""
|
||||
from distutils.command.install import INSTALL_SCHEMES
|
||||
|
|
@ -122,7 +120,7 @@ def _looks_like_debian_scheme() -> bool:
|
|||
return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES
|
||||
|
||||
|
||||
@functools.cache
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _looks_like_red_hat_scheme() -> bool:
|
||||
"""Red Hat patches ``sys.prefix`` and ``sys.exec_prefix``.
|
||||
|
||||
|
|
@ -142,7 +140,7 @@ def _looks_like_red_hat_scheme() -> bool:
|
|||
)
|
||||
|
||||
|
||||
@functools.cache
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _looks_like_slackware_scheme() -> bool:
|
||||
"""Slackware patches sysconfig but fails to patch distutils and site.
|
||||
|
||||
|
|
@ -158,7 +156,7 @@ def _looks_like_slackware_scheme() -> bool:
|
|||
return "/lib64/" in paths["purelib"] and "/lib64/" not in user_site
|
||||
|
||||
|
||||
@functools.cache
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _looks_like_msys2_mingw_scheme() -> bool:
|
||||
"""MSYS2 patches distutils and sysconfig to use a UNIX-like scheme.
|
||||
|
||||
|
|
@ -176,7 +174,23 @@ def _looks_like_msys2_mingw_scheme() -> bool:
|
|||
)
|
||||
|
||||
|
||||
@functools.cache
|
||||
def _fix_abiflags(parts: Tuple[str]) -> Generator[str, None, None]:
|
||||
ldversion = sysconfig.get_config_var("LDVERSION")
|
||||
abiflags = getattr(sys, "abiflags", None)
|
||||
|
||||
# LDVERSION does not end with sys.abiflags. Just return the path unchanged.
|
||||
if not ldversion or not abiflags or not ldversion.endswith(abiflags):
|
||||
yield from parts
|
||||
return
|
||||
|
||||
# Strip sys.abiflags from LDVERSION-based path components.
|
||||
for part in parts:
|
||||
if part.endswith(ldversion):
|
||||
part = part[: (0 - len(abiflags))]
|
||||
yield part
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None:
|
||||
issue_url = "https://github.com/pypa/pip/issues/10151"
|
||||
message = (
|
||||
|
|
@ -194,13 +208,13 @@ def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool
|
|||
return True
|
||||
|
||||
|
||||
@functools.cache
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _log_context(
|
||||
*,
|
||||
user: bool = False,
|
||||
home: str | None = None,
|
||||
root: str | None = None,
|
||||
prefix: str | None = None,
|
||||
home: Optional[str] = None,
|
||||
root: Optional[str] = None,
|
||||
prefix: Optional[str] = None,
|
||||
) -> None:
|
||||
parts = [
|
||||
"Additional context:",
|
||||
|
|
@ -216,10 +230,10 @@ def _log_context(
|
|||
def get_scheme(
|
||||
dist_name: str,
|
||||
user: bool = False,
|
||||
home: str | None = None,
|
||||
root: str | None = None,
|
||||
home: Optional[str] = None,
|
||||
root: Optional[str] = None,
|
||||
isolated: bool = False,
|
||||
prefix: str | None = None,
|
||||
prefix: Optional[str] = None,
|
||||
) -> Scheme:
|
||||
new = _sysconfig.get_scheme(
|
||||
dist_name,
|
||||
|
|
@ -283,13 +297,14 @@ def get_scheme(
|
|||
continue
|
||||
|
||||
# On Python 3.9+, sysconfig's posix_user scheme sets platlib against
|
||||
# sys.platlibdir, but distutils's unix_user incorrectly continues
|
||||
# sys.platlibdir, but distutils's unix_user incorrectly coninutes
|
||||
# using the same $usersite for both platlib and purelib. This creates a
|
||||
# mismatch when sys.platlibdir is not "lib".
|
||||
skip_bpo_44860 = (
|
||||
user
|
||||
and k == "platlib"
|
||||
and not WINDOWS
|
||||
and sys.version_info >= (3, 9)
|
||||
and _PLATLIBDIR != "lib"
|
||||
and _looks_like_bpo_44860()
|
||||
)
|
||||
|
|
@ -321,6 +336,17 @@ def get_scheme(
|
|||
if skip_linux_system_special_case:
|
||||
continue
|
||||
|
||||
# On Python 3.7 and earlier, sysconfig does not include sys.abiflags in
|
||||
# the "pythonX.Y" part of the path, but distutils does.
|
||||
skip_sysconfig_abiflag_bug = (
|
||||
sys.version_info < (3, 8)
|
||||
and not WINDOWS
|
||||
and k in ("headers", "platlib", "purelib")
|
||||
and tuple(_fix_abiflags(old_v.parts)) == new_v.parts
|
||||
)
|
||||
if skip_sysconfig_abiflag_bug:
|
||||
continue
|
||||
|
||||
# MSYS2 MINGW's sysconfig patch does not include the "site-packages"
|
||||
# part of the path. This is incorrect and will be fixed in MSYS.
|
||||
skip_msys2_mingw_bug = (
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -9,8 +9,6 @@
|
|||
#
|
||||
# See https://github.com/pypa/pip/issues/8761 for the original discussion and
|
||||
# rationale for why this is done within pip.
|
||||
from __future__ import annotations
|
||||
|
||||
try:
|
||||
__import__("_distutils_hack").remove_shim()
|
||||
except (ImportError, AttributeError):
|
||||
|
|
@ -23,6 +21,7 @@ from distutils.cmd import Command as DistutilsCommand
|
|||
from distutils.command.install import SCHEME_KEYS
|
||||
from distutils.command.install import install as distutils_install_command
|
||||
from distutils.sysconfig import get_python_lib
|
||||
from typing import Dict, List, Optional, Union, cast
|
||||
|
||||
from pip._internal.models.scheme import Scheme
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
|
|
@ -36,19 +35,19 @@ logger = logging.getLogger(__name__)
|
|||
def distutils_scheme(
|
||||
dist_name: str,
|
||||
user: bool = False,
|
||||
home: str | None = None,
|
||||
root: str | None = None,
|
||||
home: Optional[str] = None,
|
||||
root: Optional[str] = None,
|
||||
isolated: bool = False,
|
||||
prefix: str | None = None,
|
||||
prefix: Optional[str] = None,
|
||||
*,
|
||||
ignore_config_files: bool = False,
|
||||
) -> dict[str, str]:
|
||||
) -> Dict[str, str]:
|
||||
"""
|
||||
Return a distutils install scheme
|
||||
"""
|
||||
from distutils.dist import Distribution
|
||||
|
||||
dist_args: dict[str, str | list[str]] = {"name": dist_name}
|
||||
dist_args: Dict[str, Union[str, List[str]]] = {"name": dist_name}
|
||||
if isolated:
|
||||
dist_args["script_args"] = ["--no-user-cfg"]
|
||||
|
||||
|
|
@ -62,10 +61,10 @@ def distutils_scheme(
|
|||
"Ignore distutils configs in %s due to encoding errors.",
|
||||
", ".join(os.path.basename(p) for p in paths),
|
||||
)
|
||||
obj: DistutilsCommand | None = None
|
||||
obj: Optional[DistutilsCommand] = None
|
||||
obj = d.get_command_obj("install", create=True)
|
||||
assert obj is not None
|
||||
i: distutils_install_command = obj
|
||||
i = cast(distutils_install_command, obj)
|
||||
# NOTE: setting user or home has the side-effect of creating the home dir
|
||||
# or user base for installations during finalize_options()
|
||||
# ideally, we'd prefer a scheme class that has no side-effects.
|
||||
|
|
@ -79,7 +78,7 @@ def distutils_scheme(
|
|||
i.root = root or i.root
|
||||
i.finalize_options()
|
||||
|
||||
scheme: dict[str, str] = {}
|
||||
scheme = {}
|
||||
for key in SCHEME_KEYS:
|
||||
scheme[key] = getattr(i, "install_" + key)
|
||||
|
||||
|
|
@ -116,10 +115,10 @@ def distutils_scheme(
|
|||
def get_scheme(
|
||||
dist_name: str,
|
||||
user: bool = False,
|
||||
home: str | None = None,
|
||||
root: str | None = None,
|
||||
home: Optional[str] = None,
|
||||
root: Optional[str] = None,
|
||||
isolated: bool = False,
|
||||
prefix: str | None = None,
|
||||
prefix: Optional[str] = None,
|
||||
) -> Scheme:
|
||||
"""
|
||||
Get the "scheme" corresponding to the input parameters. The distutils
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue