Voice et bot modif
This commit is contained in:
parent
189d56026b
commit
7333a22bcd
10774 changed files with 634644 additions and 933308 deletions
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.
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.
|
|
@ -88,7 +88,7 @@ def join_continuation(lines):
|
|||
['foobarbaz']
|
||||
|
||||
Not sure why, but...
|
||||
The character preceding the backslash is also elided.
|
||||
The character preceeding the backslash is also elided.
|
||||
|
||||
>>> list(join_continuation(['goo\\', 'dly']))
|
||||
['godly']
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ and eventually drop this after all usages are changed.
|
|||
|
||||
import os
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
from pip._vendor import platformdirs as _appdirs
|
||||
|
||||
|
|
@ -39,10 +40,9 @@ def user_config_dir(appname: str, roaming: bool = True) -> str:
|
|||
|
||||
# for the discussion regarding site_config_dir locations
|
||||
# see <https://github.com/pypa/pip/issues/1733>
|
||||
def site_config_dirs(appname: str) -> list[str]:
|
||||
def site_config_dirs(appname: str) -> List[str]:
|
||||
if sys.platform == "darwin":
|
||||
dirval = _appdirs.site_data_dir(appname, appauthor=False, multipath=True)
|
||||
return dirval.split(os.pathsep)
|
||||
return [_appdirs.site_data_dir(appname, appauthor=False, multipath=True)]
|
||||
|
||||
dirval = _appdirs.site_config_dir(appname, appauthor=False, multipath=True)
|
||||
if sys.platform == "win32":
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
"""Stuff that differs in different Python versions and platform
|
||||
distributions."""
|
||||
|
||||
import importlib.resources
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import IO
|
||||
|
||||
__all__ = ["get_path_uid", "stdlib_pkgs", "tomllib", "WINDOWS"]
|
||||
__all__ = ["get_path_uid", "stdlib_pkgs", "WINDOWS"]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -53,26 +51,6 @@ def get_path_uid(path: str) -> int:
|
|||
return file_uid
|
||||
|
||||
|
||||
# The importlib.resources.open_text function was deprecated in 3.11 with suggested
|
||||
# replacement we use below.
|
||||
if sys.version_info < (3, 11):
|
||||
open_text_resource = importlib.resources.open_text
|
||||
else:
|
||||
|
||||
def open_text_resource(
|
||||
package: str, resource: str, encoding: str = "utf-8", errors: str = "strict"
|
||||
) -> IO[str]:
|
||||
return (importlib.resources.files(package) / resource).open(
|
||||
"r", encoding=encoding, errors=errors
|
||||
)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
from pip._vendor import tomli as tomllib
|
||||
|
||||
|
||||
# packages in the stdlib that may have installation metadata, but should not be
|
||||
# considered 'installed'. this theoretically could be determined based on
|
||||
# dist.location (py27:`sysconfig.get_paths()['stdlib']`,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,30 @@
|
|||
"""Generate and work with PEP 425 Compatibility Tags."""
|
||||
|
||||
from __future__ import annotations
|
||||
"""Generate and work with PEP 425 Compatibility Tags.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from pip._vendor.packaging.tags import (
|
||||
PythonVersion,
|
||||
Tag,
|
||||
android_platforms,
|
||||
compatible_tags,
|
||||
cpython_tags,
|
||||
generic_tags,
|
||||
interpreter_name,
|
||||
interpreter_version,
|
||||
ios_platforms,
|
||||
mac_platforms,
|
||||
)
|
||||
|
||||
_apple_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)")
|
||||
_osx_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)")
|
||||
|
||||
|
||||
def version_info_to_nodot(version_info: tuple[int, ...]) -> str:
|
||||
def version_info_to_nodot(version_info: Tuple[int, ...]) -> str:
|
||||
# Only use up to the first two numbers.
|
||||
return "".join(map(str, version_info[:2]))
|
||||
|
||||
|
||||
def _mac_platforms(arch: str) -> list[str]:
|
||||
match = _apple_arch_pat.match(arch)
|
||||
def _mac_platforms(arch: str) -> List[str]:
|
||||
match = _osx_arch_pat.match(arch)
|
||||
if match:
|
||||
name, major, minor, actual_arch = match.groups()
|
||||
mac_version = (int(major), int(minor))
|
||||
|
|
@ -45,37 +43,7 @@ def _mac_platforms(arch: str) -> list[str]:
|
|||
return arches
|
||||
|
||||
|
||||
def _ios_platforms(arch: str) -> list[str]:
|
||||
match = _apple_arch_pat.match(arch)
|
||||
if match:
|
||||
name, major, minor, actual_multiarch = match.groups()
|
||||
ios_version = (int(major), int(minor))
|
||||
arches = [
|
||||
# Since we have always only checked that the platform starts
|
||||
# with "ios", for backwards-compatibility we extract the
|
||||
# actual prefix provided by the user in case they provided
|
||||
# something like "ioscustom_". It may be good to remove
|
||||
# this as undocumented or deprecate it in the future.
|
||||
"{}_{}".format(name, arch[len("ios_") :])
|
||||
for arch in ios_platforms(ios_version, actual_multiarch)
|
||||
]
|
||||
else:
|
||||
# arch pattern didn't match (?!)
|
||||
arches = [arch]
|
||||
return arches
|
||||
|
||||
|
||||
def _android_platforms(arch: str) -> list[str]:
|
||||
match = re.fullmatch(r"android_(\d+)_(.+)", arch)
|
||||
if match:
|
||||
api_level, abi = match.groups()
|
||||
return list(android_platforms(int(api_level), abi))
|
||||
else:
|
||||
# arch pattern didn't match (?!)
|
||||
return [arch]
|
||||
|
||||
|
||||
def _custom_manylinux_platforms(arch: str) -> list[str]:
|
||||
def _custom_manylinux_platforms(arch: str) -> List[str]:
|
||||
arches = [arch]
|
||||
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
|
||||
if arch_prefix == "manylinux2014":
|
||||
|
|
@ -96,14 +64,10 @@ def _custom_manylinux_platforms(arch: str) -> list[str]:
|
|||
return arches
|
||||
|
||||
|
||||
def _get_custom_platforms(arch: str) -> list[str]:
|
||||
def _get_custom_platforms(arch: str) -> List[str]:
|
||||
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
|
||||
if arch.startswith("macosx"):
|
||||
arches = _mac_platforms(arch)
|
||||
elif arch.startswith("ios"):
|
||||
arches = _ios_platforms(arch)
|
||||
elif arch_prefix == "android":
|
||||
arches = _android_platforms(arch)
|
||||
elif arch_prefix in ["manylinux2014", "manylinux2010"]:
|
||||
arches = _custom_manylinux_platforms(arch)
|
||||
else:
|
||||
|
|
@ -111,7 +75,7 @@ def _get_custom_platforms(arch: str) -> list[str]:
|
|||
return arches
|
||||
|
||||
|
||||
def _expand_allowed_platforms(platforms: list[str] | None) -> list[str] | None:
|
||||
def _expand_allowed_platforms(platforms: Optional[List[str]]) -> Optional[List[str]]:
|
||||
if not platforms:
|
||||
return None
|
||||
|
||||
|
|
@ -136,7 +100,7 @@ def _get_python_version(version: str) -> PythonVersion:
|
|||
|
||||
|
||||
def _get_custom_interpreter(
|
||||
implementation: str | None = None, version: str | None = None
|
||||
implementation: Optional[str] = None, version: Optional[str] = None
|
||||
) -> str:
|
||||
if implementation is None:
|
||||
implementation = interpreter_name()
|
||||
|
|
@ -146,11 +110,11 @@ def _get_custom_interpreter(
|
|||
|
||||
|
||||
def get_supported(
|
||||
version: str | None = None,
|
||||
platforms: list[str] | None = None,
|
||||
impl: str | None = None,
|
||||
abis: list[str] | None = None,
|
||||
) -> list[Tag]:
|
||||
version: Optional[str] = None,
|
||||
platforms: Optional[List[str]] = None,
|
||||
impl: Optional[str] = None,
|
||||
abis: Optional[List[str]] = None,
|
||||
) -> List[Tag]:
|
||||
"""Return a list of supported tags for each version specified in
|
||||
`versions`.
|
||||
|
||||
|
|
@ -163,9 +127,9 @@ def get_supported(
|
|||
:param abis: specify a list of abis you want valid
|
||||
tags for, or None. If None, use the local interpreter abi.
|
||||
"""
|
||||
supported: list[Tag] = []
|
||||
supported: List[Tag] = []
|
||||
|
||||
python_version: PythonVersion | None = None
|
||||
python_version: Optional[PythonVersion] = None
|
||||
if version is not None:
|
||||
python_version = _get_python_version(version)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""For when pip wants to check the date or time."""
|
||||
"""For when pip wants to check the date or time.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,9 @@
|
|||
A module that implements tooling to enable easy warnings about deprecations.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
from typing import Any, TextIO
|
||||
from typing import Any, Optional, TextIO, Type, Union
|
||||
|
||||
from pip._vendor.packaging.version import parse
|
||||
|
||||
|
|
@ -24,12 +22,12 @@ _original_showwarning: Any = None
|
|||
|
||||
# Warnings <-> Logging Integration
|
||||
def _showwarning(
|
||||
message: Warning | str,
|
||||
category: type[Warning],
|
||||
message: Union[Warning, str],
|
||||
category: Type[Warning],
|
||||
filename: str,
|
||||
lineno: int,
|
||||
file: TextIO | None = None,
|
||||
line: str | None = None,
|
||||
file: Optional[TextIO] = None,
|
||||
line: Optional[str] = None,
|
||||
) -> None:
|
||||
if file is not None:
|
||||
if _original_showwarning is not None:
|
||||
|
|
@ -57,10 +55,10 @@ def install_warning_logger() -> None:
|
|||
def deprecated(
|
||||
*,
|
||||
reason: str,
|
||||
replacement: str | None,
|
||||
gone_in: str | None,
|
||||
feature_flag: str | None = None,
|
||||
issue: int | None = None,
|
||||
replacement: Optional[str],
|
||||
gone_in: Optional[str],
|
||||
feature_flag: Optional[str] = None,
|
||||
issue: Optional[int] = None,
|
||||
) -> None:
|
||||
"""Helper to deprecate existing functionality.
|
||||
|
||||
|
|
@ -89,11 +87,9 @@ def deprecated(
|
|||
(reason, f"{DEPRECATION_MSG_PREFIX}{{}}"),
|
||||
(
|
||||
gone_in,
|
||||
(
|
||||
"pip {} will enforce this behaviour change."
|
||||
if not is_gone
|
||||
else "Since pip {}, this is no longer supported."
|
||||
),
|
||||
"pip {} will enforce this behaviour change."
|
||||
if not is_gone
|
||||
else "Since pip {}, this is no longer supported.",
|
||||
),
|
||||
(
|
||||
replacement,
|
||||
|
|
@ -101,11 +97,9 @@ def deprecated(
|
|||
),
|
||||
(
|
||||
feature_flag,
|
||||
(
|
||||
"You can use the flag --use-feature={} to test the upcoming behaviour."
|
||||
if not is_gone
|
||||
else None
|
||||
),
|
||||
"You can use the flag --use-feature={} to test the upcoming behaviour."
|
||||
if not is_gone
|
||||
else None,
|
||||
),
|
||||
(
|
||||
issue,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.models.direct_url import ArchiveInfo, DirectUrl, DirInfo, VcsInfo
|
||||
from pip._internal.models.link import Link
|
||||
|
|
@ -12,8 +12,8 @@ def direct_url_as_pep440_direct_reference(direct_url: DirectUrl, name: str) -> s
|
|||
requirement = name + " @ "
|
||||
fragments = []
|
||||
if isinstance(direct_url.info, VcsInfo):
|
||||
requirement += (
|
||||
f"{direct_url.info.vcs}+{direct_url.url}@{direct_url.info.commit_id}"
|
||||
requirement += "{}+{}@{}".format(
|
||||
direct_url.info.vcs, direct_url.url, direct_url.info.commit_id
|
||||
)
|
||||
elif isinstance(direct_url.info, ArchiveInfo):
|
||||
requirement += direct_url.url
|
||||
|
|
@ -37,7 +37,7 @@ def direct_url_for_editable(source_dir: str) -> DirectUrl:
|
|||
|
||||
|
||||
def direct_url_from_link(
|
||||
link: Link, source_dir: str | None = None, link_is_in_wheel_cache: bool = False
|
||||
link: Link, source_dir: Optional[str] = None, link_is_in_wheel_cache: bool = False
|
||||
) -> DirectUrl:
|
||||
if link.is_vcs:
|
||||
vcs_backend = vcs.get_backend_for_scheme(link.scheme)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
|
||||
from pip._internal.locations import site_packages, user_site
|
||||
from pip._internal.utils.virtualenv import (
|
||||
|
|
@ -16,7 +15,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
def _egg_link_names(raw_name: str) -> list[str]:
|
||||
def _egg_link_names(raw_name: str) -> List[str]:
|
||||
"""
|
||||
Convert a Name metadata value to a .egg-link name, by applying
|
||||
the same substitution as pkg_resources's safe_name function.
|
||||
|
|
@ -31,7 +30,7 @@ def _egg_link_names(raw_name: str) -> list[str]:
|
|||
]
|
||||
|
||||
|
||||
def egg_link_path_from_sys_path(raw_name: str) -> str | None:
|
||||
def egg_link_path_from_sys_path(raw_name: str) -> Optional[str]:
|
||||
"""
|
||||
Look for a .egg-link file for project name, by walking sys.path.
|
||||
"""
|
||||
|
|
@ -44,7 +43,7 @@ def egg_link_path_from_sys_path(raw_name: str) -> str | None:
|
|||
return None
|
||||
|
||||
|
||||
def egg_link_path_from_location(raw_name: str) -> str | None:
|
||||
def egg_link_path_from_location(raw_name: str) -> Optional[str]:
|
||||
"""
|
||||
Return the path for the .egg-link file if it exists, otherwise, None.
|
||||
|
||||
|
|
@ -62,7 +61,7 @@ def egg_link_path_from_location(raw_name: str) -> str | None:
|
|||
|
||||
This method will just return the first one found.
|
||||
"""
|
||||
sites: list[str] = []
|
||||
sites: List[str] = []
|
||||
if running_under_virtualenv():
|
||||
sites.append(site_packages)
|
||||
if not virtualenv_no_global() and user_site:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import codecs
|
||||
import locale
|
||||
import re
|
||||
import sys
|
||||
from typing import List, Tuple
|
||||
|
||||
BOMS: List[Tuple[bytes, str]] = [
|
||||
(codecs.BOM_UTF8, "utf-8"),
|
||||
(codecs.BOM_UTF16, "utf-16"),
|
||||
(codecs.BOM_UTF16_BE, "utf-16-be"),
|
||||
(codecs.BOM_UTF16_LE, "utf-16-le"),
|
||||
(codecs.BOM_UTF32, "utf-32"),
|
||||
(codecs.BOM_UTF32_BE, "utf-32-be"),
|
||||
(codecs.BOM_UTF32_LE, "utf-32-le"),
|
||||
]
|
||||
|
||||
ENCODING_RE = re.compile(rb"coding[:=]\s*([-\w.]+)")
|
||||
|
||||
|
||||
def auto_decode(data: bytes) -> str:
|
||||
"""Check a bytes string for a BOM to correctly detect the encoding
|
||||
|
||||
Fallback to locale.getpreferredencoding(False) like open() on Python3"""
|
||||
for bom, encoding in BOMS:
|
||||
if data.startswith(bom):
|
||||
return data[len(bom) :].decode(encoding)
|
||||
# Lets check the first two lines as in PEP263
|
||||
for line in data.split(b"\n")[:2]:
|
||||
if line[0:1] == b"#" and ENCODING_RE.search(line):
|
||||
result = ENCODING_RE.search(line)
|
||||
assert result is not None
|
||||
encoding = result.groups()[0].decode("ascii")
|
||||
return data.decode(encoding)
|
||||
return data.decode(
|
||||
locale.getpreferredencoding(False) or sys.getdefaultencoding(),
|
||||
)
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
|
||||
from pip._internal.cli.main import main
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
|
|
@ -21,7 +20,7 @@ if WINDOWS:
|
|||
]
|
||||
|
||||
|
||||
def _wrapper(args: list[str] | None = None) -> int:
|
||||
def _wrapper(args: Optional[List[str]] = None) -> int:
|
||||
"""Central wrapper for all old entrypoints.
|
||||
|
||||
Historically pip has had several entrypoints defined. Because of issues
|
||||
|
|
@ -78,10 +77,7 @@ def get_best_invocation_for_this_python() -> str:
|
|||
|
||||
# Try to use the basename, if it's the first executable.
|
||||
found_executable = shutil.which(exe_name)
|
||||
# Virtual environments often symlink to their parent Python binaries, but we don't
|
||||
# want to treat the Python binaries as equivalent when the environment's Python is
|
||||
# not on PATH (not activated). Thus, we don't follow symlinks.
|
||||
if found_executable and os.path.samestat(os.lstat(found_executable), os.lstat(exe)):
|
||||
if found_executable and os.path.samefile(found_executable, exe):
|
||||
return exe_name
|
||||
|
||||
# Use the full executable name, because we couldn't find something simpler.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
import os
|
||||
import os.path
|
||||
import random
|
||||
import sys
|
||||
from collections.abc import Generator
|
||||
from contextlib import contextmanager
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Any, BinaryIO, cast
|
||||
from typing import Any, BinaryIO, Generator, List, Union, cast
|
||||
|
||||
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
|
||||
|
||||
from pip._internal.utils.compat import get_path_uid
|
||||
from pip._internal.utils.misc import format_size
|
||||
from pip._internal.utils.retry import retry
|
||||
|
||||
|
||||
def check_path_owner(path: str) -> bool:
|
||||
|
|
@ -67,7 +65,10 @@ def adjacent_tmp_file(path: str, **kwargs: Any) -> Generator[BinaryIO, None, Non
|
|||
os.fsync(result.fileno())
|
||||
|
||||
|
||||
replace = retry(stop_after_delay=1, wait=0.25)(os.replace)
|
||||
# Tenacity raises RetryError by default, explicitly raise the original exception
|
||||
_replace_retry = retry(reraise=True, stop=stop_after_delay(1), wait=wait_fixed(0.25))
|
||||
|
||||
replace = _replace_retry(os.replace)
|
||||
|
||||
|
||||
# test_writable_dir and _test_writable_dir_win are copied from Flit,
|
||||
|
|
@ -118,17 +119,17 @@ def _test_writable_dir_win(path: str) -> bool:
|
|||
raise OSError("Unexpected condition testing for writable directory")
|
||||
|
||||
|
||||
def find_files(path: str, pattern: str) -> list[str]:
|
||||
def find_files(path: str, pattern: str) -> List[str]:
|
||||
"""Returns a list of absolute paths of files beneath path, recursively,
|
||||
with filenames which match the UNIX-style shell glob pattern."""
|
||||
result: list[str] = []
|
||||
result: List[str] = []
|
||||
for root, _, files in os.walk(path):
|
||||
matches = fnmatch.filter(files, pattern)
|
||||
result.extend(os.path.join(root, f) for f in matches)
|
||||
return result
|
||||
|
||||
|
||||
def file_size(path: str) -> int | float:
|
||||
def file_size(path: str) -> Union[int, float]:
|
||||
# If it's a symlink, return 0.
|
||||
if os.path.islink(path):
|
||||
return 0
|
||||
|
|
@ -139,7 +140,7 @@ def format_file_size(path: str) -> str:
|
|||
return format_size(file_size(path))
|
||||
|
||||
|
||||
def directory_size(path: str) -> int | float:
|
||||
def directory_size(path: str) -> Union[int, float]:
|
||||
size = 0.0
|
||||
for root, _dirs, files in os.walk(path):
|
||||
for filename in files:
|
||||
|
|
@ -150,15 +151,3 @@ def directory_size(path: str) -> int | float:
|
|||
|
||||
def format_directory_size(path: str) -> str:
|
||||
return format_size(directory_size(path))
|
||||
|
||||
|
||||
def copy_directory_permissions(directory: str, target_file: BinaryIO) -> None:
|
||||
mode = (
|
||||
os.stat(directory).st_mode & 0o666 # select read/write permissions of directory
|
||||
| 0o600 # set owner read/write permissions
|
||||
)
|
||||
# Change permissions only if there is no risk of following a symlink.
|
||||
if os.chmod in os.supports_fd:
|
||||
os.chmod(target_file.fileno(), mode)
|
||||
elif os.chmod in os.supports_follow_symlinks:
|
||||
os.chmod(target_file.name, mode, follow_symlinks=False)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
"""Filetype information."""
|
||||
"""Filetype information.
|
||||
"""
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.utils.misc import splitext
|
||||
|
||||
WHEEL_EXTENSION = ".whl"
|
||||
BZ2_EXTENSIONS: tuple[str, ...] = (".tar.bz2", ".tbz")
|
||||
XZ_EXTENSIONS: tuple[str, ...] = (
|
||||
BZ2_EXTENSIONS: Tuple[str, ...] = (".tar.bz2", ".tbz")
|
||||
XZ_EXTENSIONS: Tuple[str, ...] = (
|
||||
".tar.xz",
|
||||
".txz",
|
||||
".tlz",
|
||||
".tar.lz",
|
||||
".tar.lzma",
|
||||
)
|
||||
ZIP_EXTENSIONS: tuple[str, ...] = (".zip", WHEEL_EXTENSION)
|
||||
TAR_EXTENSIONS: tuple[str, ...] = (".tar.gz", ".tgz", ".tar")
|
||||
ZIP_EXTENSIONS: Tuple[str, ...] = (".zip", WHEEL_EXTENSION)
|
||||
TAR_EXTENSIONS: Tuple[str, ...] = (".tar.gz", ".tgz", ".tar")
|
||||
ARCHIVE_EXTENSIONS = ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
||||
def glibc_version_string() -> str | None:
|
||||
def glibc_version_string() -> Optional[str]:
|
||||
"Returns glibc version string, or None if not using glibc."
|
||||
return glibc_version_string_confstr() or glibc_version_string_ctypes()
|
||||
|
||||
|
||||
def glibc_version_string_confstr() -> str | None:
|
||||
def glibc_version_string_confstr() -> Optional[str]:
|
||||
"Primary implementation of glibc_version_string using os.confstr."
|
||||
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
|
||||
# to be broken or missing. This strategy is used in the standard library
|
||||
|
|
@ -29,7 +28,7 @@ def glibc_version_string_confstr() -> str | None:
|
|||
return version
|
||||
|
||||
|
||||
def glibc_version_string_ctypes() -> str | None:
|
||||
def glibc_version_string_ctypes() -> Optional[str]:
|
||||
"Fallback implementation of glibc_version_string using ctypes."
|
||||
|
||||
try:
|
||||
|
|
@ -41,20 +40,7 @@ def glibc_version_string_ctypes() -> str | None:
|
|||
# manpage says, "If filename is NULL, then the returned handle is for the
|
||||
# main program". This way we can let the linker do the work to figure out
|
||||
# which libc our process is actually using.
|
||||
#
|
||||
# We must also handle the special case where the executable is not a
|
||||
# dynamically linked executable. This can occur when using musl libc,
|
||||
# for example. In this situation, dlopen() will error, leading to an
|
||||
# OSError. Interestingly, at least in the case of musl, there is no
|
||||
# errno set on the OSError. The single string argument used to construct
|
||||
# OSError comes from libc itself and is therefore not portable to
|
||||
# hard code here. In any case, failure to call dlopen() means we
|
||||
# can't proceed, so we bail on our attempt.
|
||||
try:
|
||||
process_namespace = ctypes.CDLL(None)
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
process_namespace = ctypes.CDLL(None)
|
||||
try:
|
||||
gnu_get_libc_version = process_namespace.gnu_get_libc_version
|
||||
except AttributeError:
|
||||
|
|
@ -64,7 +50,7 @@ def glibc_version_string_ctypes() -> str | None:
|
|||
|
||||
# Call gnu_get_libc_version, which returns a string like "2.5"
|
||||
gnu_get_libc_version.restype = ctypes.c_char_p
|
||||
version_str: str = gnu_get_libc_version()
|
||||
version_str = gnu_get_libc_version()
|
||||
# py2 / py3 compatibility:
|
||||
if not isinstance(version_str, str):
|
||||
version_str = version_str.decode("ascii")
|
||||
|
|
@ -89,7 +75,7 @@ def glibc_version_string_ctypes() -> str | None:
|
|||
# versions that was generated by pip 8.1.2 and earlier is useless and
|
||||
# misleading. Solution: instead of using platform, use our code that actually
|
||||
# works.
|
||||
def libc_ver() -> tuple[str, str]:
|
||||
def libc_ver() -> Tuple[str, str]:
|
||||
"""Try to determine the glibc version
|
||||
|
||||
Returns a tuple of strings (lib, version) which default to empty strings
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING, BinaryIO, NoReturn
|
||||
from typing import TYPE_CHECKING, BinaryIO, Dict, Iterable, List, Optional
|
||||
|
||||
from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError
|
||||
from pip._internal.utils.misc import read_chunks
|
||||
|
|
@ -10,6 +7,10 @@ from pip._internal.utils.misc import read_chunks
|
|||
if TYPE_CHECKING:
|
||||
from hashlib import _Hash
|
||||
|
||||
# NoReturn introduced in 3.6.2; imported only for type checking to maintain
|
||||
# pip compatibility with older patch versions of Python 3.6
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
# The recommended hash algo of the moment. Change this whenever the state of
|
||||
# the art changes; it won't hurt backward compatibility.
|
||||
|
|
@ -27,7 +28,7 @@ class Hashes:
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, hashes: dict[str, list[str]] | None = None) -> None:
|
||||
def __init__(self, hashes: Optional[Dict[str, List[str]]] = None) -> None:
|
||||
"""
|
||||
:param hashes: A dict of algorithm names pointing to lists of allowed
|
||||
hex digests
|
||||
|
|
@ -36,10 +37,10 @@ class Hashes:
|
|||
if hashes is not None:
|
||||
for alg, keys in hashes.items():
|
||||
# Make sure values are always sorted (to ease equality checks)
|
||||
allowed[alg] = [k.lower() for k in sorted(keys)]
|
||||
allowed[alg] = sorted(keys)
|
||||
self._allowed = allowed
|
||||
|
||||
def __and__(self, other: Hashes) -> Hashes:
|
||||
def __and__(self, other: "Hashes") -> "Hashes":
|
||||
if not isinstance(other, Hashes):
|
||||
return NotImplemented
|
||||
|
||||
|
|
@ -89,7 +90,7 @@ class Hashes:
|
|||
return
|
||||
self._raise(gots)
|
||||
|
||||
def _raise(self, gots: dict[str, _Hash]) -> NoReturn:
|
||||
def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
|
||||
raise HashMismatch(self._allowed, gots)
|
||||
|
||||
def check_against_file(self, file: BinaryIO) -> None:
|
||||
|
|
@ -104,7 +105,7 @@ class Hashes:
|
|||
with open(path, "rb") as file:
|
||||
return self.check_against_file(file)
|
||||
|
||||
def has_one_of(self, hashes: dict[str, str]) -> bool:
|
||||
def has_one_of(self, hashes: Dict[str, str]) -> bool:
|
||||
"""Return whether any of the given hashes are allowed."""
|
||||
for hash_name, hex_digest in hashes.items():
|
||||
if self.is_hash_allowed(hash_name, hex_digest):
|
||||
|
|
@ -146,5 +147,5 @@ class MissingHashes(Hashes):
|
|||
# empty list, it will never match, so an error will always raise.
|
||||
super().__init__(hashes={FAVORITE_HASH: []})
|
||||
|
||||
def _raise(self, gots: dict[str, _Hash]) -> NoReturn:
|
||||
def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
|
||||
raise HashMissing(gots[FAVORITE_HASH].hexdigest())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import errno
|
||||
import logging
|
||||
|
|
@ -7,11 +5,10 @@ import logging.handlers
|
|||
import os
|
||||
import sys
|
||||
import threading
|
||||
from collections.abc import Generator
|
||||
from dataclasses import dataclass
|
||||
from io import TextIOWrapper
|
||||
from logging import Filter
|
||||
from typing import Any, ClassVar
|
||||
from typing import Any, ClassVar, Generator, List, Optional, TextIO, Type
|
||||
|
||||
from pip._vendor.rich.console import (
|
||||
Console,
|
||||
|
|
@ -32,8 +29,6 @@ from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX
|
|||
from pip._internal.utils.misc import ensure_dir
|
||||
|
||||
_log_state = threading.local()
|
||||
_stdout_console = None
|
||||
_stderr_console = None
|
||||
subprocess_logger = getLogger("pip.subprocessor")
|
||||
|
||||
|
||||
|
|
@ -43,7 +38,7 @@ class BrokenStdoutLoggingError(Exception):
|
|||
"""
|
||||
|
||||
|
||||
def _is_broken_pipe_error(exc_class: type[BaseException], exc: BaseException) -> bool:
|
||||
def _is_broken_pipe_error(exc_class: Type[BaseException], exc: BaseException) -> bool:
|
||||
if exc_class is BrokenPipeError:
|
||||
return True
|
||||
|
||||
|
|
@ -142,28 +137,12 @@ class IndentedRenderable:
|
|||
yield Segment("\n")
|
||||
|
||||
|
||||
class PipConsole(Console):
|
||||
def on_broken_pipe(self) -> None:
|
||||
# Reraise the original exception, rich 13.8.0+ exits by default
|
||||
# instead, preventing our handler from firing.
|
||||
raise BrokenPipeError() from None
|
||||
|
||||
|
||||
def get_console(*, stderr: bool = False) -> Console:
|
||||
if stderr:
|
||||
assert _stderr_console is not None, "stderr rich console is missing!"
|
||||
return _stderr_console
|
||||
else:
|
||||
assert _stdout_console is not None, "stdout rich console is missing!"
|
||||
return _stdout_console
|
||||
|
||||
|
||||
class RichPipStreamHandler(RichHandler):
|
||||
KEYWORDS: ClassVar[list[str] | None] = []
|
||||
KEYWORDS: ClassVar[Optional[List[str]]] = []
|
||||
|
||||
def __init__(self, console: Console) -> None:
|
||||
def __init__(self, stream: Optional[TextIO], no_color: bool) -> None:
|
||||
super().__init__(
|
||||
console=console,
|
||||
console=Console(file=stream, no_color=no_color, soft_wrap=True),
|
||||
show_time=False,
|
||||
show_level=False,
|
||||
show_path=False,
|
||||
|
|
@ -172,11 +151,11 @@ class RichPipStreamHandler(RichHandler):
|
|||
|
||||
# Our custom override on Rich's logger, to make things work as we need them to.
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
style: Style | None = None
|
||||
style: Optional[Style] = None
|
||||
|
||||
# If we are given a diagnostic error to present, present it with indentation.
|
||||
assert isinstance(record.args, tuple)
|
||||
if getattr(record, "rich", False):
|
||||
assert isinstance(record.args, tuple)
|
||||
(rich_renderable,) = record.args
|
||||
assert isinstance(
|
||||
rich_renderable, (ConsoleRenderable, RichCast, str)
|
||||
|
|
@ -233,6 +212,7 @@ class MaxLevelFilter(Filter):
|
|||
|
||||
|
||||
class ExcludeLoggerFilter(Filter):
|
||||
|
||||
"""
|
||||
A logging Filter that excludes records from a logger (or its children).
|
||||
"""
|
||||
|
|
@ -243,7 +223,7 @@ class ExcludeLoggerFilter(Filter):
|
|||
return not super().filter(record)
|
||||
|
||||
|
||||
def setup_logging(verbosity: int, no_color: bool, user_log_file: str | None) -> int:
|
||||
def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str]) -> int:
|
||||
"""Configures and sets up all of the logging
|
||||
|
||||
Returns the requested logging level, as its integer value.
|
||||
|
|
@ -280,6 +260,10 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: str | None) ->
|
|||
vendored_log_level = "WARNING" if level in ["INFO", "ERROR"] else "DEBUG"
|
||||
|
||||
# Shorthands for clarity
|
||||
log_streams = {
|
||||
"stdout": "ext://sys.stdout",
|
||||
"stderr": "ext://sys.stderr",
|
||||
}
|
||||
handler_classes = {
|
||||
"stream": "pip._internal.utils.logging.RichPipStreamHandler",
|
||||
"file": "pip._internal.utils.logging.BetterRotatingFileHandler",
|
||||
|
|
@ -287,9 +271,6 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: str | None) ->
|
|||
handlers = ["console", "console_errors", "console_subprocess"] + (
|
||||
["user_log"] if include_user_log else []
|
||||
)
|
||||
global _stdout_console, stderr_console
|
||||
_stdout_console = PipConsole(file=sys.stdout, no_color=no_color, soft_wrap=True)
|
||||
_stderr_console = PipConsole(file=sys.stderr, no_color=no_color, soft_wrap=True)
|
||||
|
||||
logging.config.dictConfig(
|
||||
{
|
||||
|
|
@ -324,14 +305,16 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: str | None) ->
|
|||
"console": {
|
||||
"level": level,
|
||||
"class": handler_classes["stream"],
|
||||
"console": _stdout_console,
|
||||
"no_color": no_color,
|
||||
"stream": log_streams["stdout"],
|
||||
"filters": ["exclude_subprocess", "exclude_warnings"],
|
||||
"formatter": "indent",
|
||||
},
|
||||
"console_errors": {
|
||||
"level": "WARNING",
|
||||
"class": handler_classes["stream"],
|
||||
"console": _stderr_console,
|
||||
"no_color": no_color,
|
||||
"stream": log_streams["stderr"],
|
||||
"filters": ["exclude_subprocess"],
|
||||
"formatter": "indent",
|
||||
},
|
||||
|
|
@ -340,7 +323,8 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: str | None) ->
|
|||
"console_subprocess": {
|
||||
"level": level,
|
||||
"class": handler_classes["stream"],
|
||||
"console": _stderr_console,
|
||||
"stream": log_streams["stderr"],
|
||||
"no_color": no_color,
|
||||
"filters": ["restrict_to_subprocess"],
|
||||
"formatter": "indent",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import errno
|
||||
import getpass
|
||||
import hashlib
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import posixpath
|
||||
|
|
@ -11,8 +11,6 @@ import stat
|
|||
import sys
|
||||
import sysconfig
|
||||
import urllib.parse
|
||||
from collections.abc import Generator, Iterable, Iterator, Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from functools import partial
|
||||
from io import StringIO
|
||||
from itertools import filterfalse, tee, zip_longest
|
||||
|
|
@ -22,20 +20,29 @@ from typing import (
|
|||
Any,
|
||||
BinaryIO,
|
||||
Callable,
|
||||
ContextManager,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
TextIO,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
|
||||
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
|
||||
|
||||
from pip import __version__
|
||||
from pip._internal.exceptions import CommandError, ExternallyManagedEnvironment
|
||||
from pip._internal.locations import get_major_minor_version
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.retry import retry
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
|
||||
__all__ = [
|
||||
|
|
@ -49,6 +56,7 @@ __all__ = [
|
|||
"normalize_path",
|
||||
"renames",
|
||||
"get_prog",
|
||||
"captured_stdout",
|
||||
"ensure_dir",
|
||||
"remove_auth_from_url",
|
||||
"check_externally_managed",
|
||||
|
|
@ -58,14 +66,12 @@ __all__ = [
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
T = TypeVar("T")
|
||||
ExcInfo = tuple[type[BaseException], BaseException, TracebackType]
|
||||
VersionInfo = tuple[int, int, int]
|
||||
NetlocTuple = tuple[str, tuple[Optional[str], Optional[str]]]
|
||||
ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType]
|
||||
VersionInfo = Tuple[int, int, int]
|
||||
NetlocTuple = Tuple[str, Tuple[Optional[str], Optional[str]]]
|
||||
OnExc = Callable[[FunctionType, Path, BaseException], Any]
|
||||
OnErr = Callable[[FunctionType, Path, ExcInfo], Any]
|
||||
|
||||
FILE_CHUNK_SIZE = 1024 * 1024
|
||||
|
||||
|
||||
def get_pip_version() -> str:
|
||||
pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
|
|
@ -74,7 +80,7 @@ def get_pip_version() -> str:
|
|||
return f"pip {__version__} from {pip_pkg_dir} (python {get_major_minor_version()})"
|
||||
|
||||
|
||||
def normalize_version_info(py_version_info: tuple[int, ...]) -> tuple[int, int, int]:
|
||||
def normalize_version_info(py_version_info: Tuple[int, ...]) -> Tuple[int, int, int]:
|
||||
"""
|
||||
Convert a tuple of ints representing a Python version to one of length
|
||||
three.
|
||||
|
|
@ -116,13 +122,23 @@ def get_prog() -> str:
|
|||
|
||||
|
||||
# Retry every half second for up to 3 seconds
|
||||
@retry(stop_after_delay=3, wait=0.5)
|
||||
def rmtree(dir: str, ignore_errors: bool = False, onexc: OnExc | None = None) -> None:
|
||||
# Tenacity raises RetryError by default, explicitly raise the original exception
|
||||
@retry(reraise=True, stop=stop_after_delay(3), wait=wait_fixed(0.5))
|
||||
def rmtree(
|
||||
dir: str,
|
||||
ignore_errors: bool = False,
|
||||
onexc: Optional[OnExc] = None,
|
||||
) -> None:
|
||||
if ignore_errors:
|
||||
onexc = _onerror_ignore
|
||||
if onexc is None:
|
||||
onexc = _onerror_reraise
|
||||
handler: OnErr = partial(rmtree_errorhandler, onexc=onexc)
|
||||
handler: OnErr = partial(
|
||||
# `[func, path, Union[ExcInfo, BaseException]] -> Any` is equivalent to
|
||||
# `Union[([func, path, ExcInfo] -> Any), ([func, path, BaseException] -> Any)]`.
|
||||
cast(Union[OnExc, OnErr], rmtree_errorhandler),
|
||||
onexc=onexc,
|
||||
)
|
||||
if sys.version_info >= (3, 12):
|
||||
# See https://docs.python.org/3.12/whatsnew/3.12.html#shutil.
|
||||
shutil.rmtree(dir, onexc=handler) # type: ignore
|
||||
|
|
@ -135,13 +151,13 @@ def _onerror_ignore(*_args: Any) -> None:
|
|||
|
||||
|
||||
def _onerror_reraise(*_args: Any) -> None:
|
||||
raise # noqa: PLE0704 - Bare exception used to reraise existing exception
|
||||
raise
|
||||
|
||||
|
||||
def rmtree_errorhandler(
|
||||
func: FunctionType,
|
||||
path: Path,
|
||||
exc_info: ExcInfo | BaseException,
|
||||
exc_info: Union[ExcInfo, BaseException],
|
||||
*,
|
||||
onexc: OnExc = _onerror_reraise,
|
||||
) -> None:
|
||||
|
|
@ -268,7 +284,7 @@ def format_size(bytes: float) -> str:
|
|||
return f"{int(bytes)} bytes"
|
||||
|
||||
|
||||
def tabulate(rows: Iterable[Iterable[Any]]) -> tuple[list[str], list[int]]:
|
||||
def tabulate(rows: Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]:
|
||||
"""Return a list of formatted rows and a list of column sizes.
|
||||
|
||||
For example::
|
||||
|
|
@ -300,7 +316,7 @@ def is_installable_dir(path: str) -> bool:
|
|||
|
||||
|
||||
def read_chunks(
|
||||
file: BinaryIO, size: int = FILE_CHUNK_SIZE
|
||||
file: BinaryIO, size: int = io.DEFAULT_BUFFER_SIZE
|
||||
) -> Generator[bytes, None, None]:
|
||||
"""Yield pieces of data from a file-like object until EOF."""
|
||||
while True:
|
||||
|
|
@ -323,7 +339,7 @@ def normalize_path(path: str, resolve_symlinks: bool = True) -> str:
|
|||
return os.path.normcase(path)
|
||||
|
||||
|
||||
def splitext(path: str) -> tuple[str, str]:
|
||||
def splitext(path: str) -> Tuple[str, str]:
|
||||
"""Like os.path.splitext, but take off .tar too"""
|
||||
base, ext = posixpath.splitext(path)
|
||||
if base.lower().endswith(".tar"):
|
||||
|
|
@ -371,7 +387,7 @@ class StreamWrapper(StringIO):
|
|||
orig_stream: TextIO
|
||||
|
||||
@classmethod
|
||||
def from_stream(cls, orig_stream: TextIO) -> StreamWrapper:
|
||||
def from_stream(cls, orig_stream: TextIO) -> "StreamWrapper":
|
||||
ret = cls()
|
||||
ret.orig_stream = orig_stream
|
||||
return ret
|
||||
|
|
@ -383,15 +399,49 @@ class StreamWrapper(StringIO):
|
|||
return self.orig_stream.encoding
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def captured_output(stream_name: str) -> Generator[StreamWrapper, None, None]:
|
||||
"""Return a context manager used by captured_stdout/stdin/stderr
|
||||
that temporarily replaces the sys stream *stream_name* with a StringIO.
|
||||
|
||||
Taken from Lib/support/__init__.py in the CPython repo.
|
||||
"""
|
||||
orig_stdout = getattr(sys, stream_name)
|
||||
setattr(sys, stream_name, StreamWrapper.from_stream(orig_stdout))
|
||||
try:
|
||||
yield getattr(sys, stream_name)
|
||||
finally:
|
||||
setattr(sys, stream_name, orig_stdout)
|
||||
|
||||
|
||||
def captured_stdout() -> ContextManager[StreamWrapper]:
|
||||
"""Capture the output of sys.stdout:
|
||||
|
||||
with captured_stdout() as stdout:
|
||||
print('hello')
|
||||
self.assertEqual(stdout.getvalue(), 'hello\n')
|
||||
|
||||
Taken from Lib/support/__init__.py in the CPython repo.
|
||||
"""
|
||||
return captured_output("stdout")
|
||||
|
||||
|
||||
def captured_stderr() -> ContextManager[StreamWrapper]:
|
||||
"""
|
||||
See captured_stdout().
|
||||
"""
|
||||
return captured_output("stderr")
|
||||
|
||||
|
||||
# Simulates an enum
|
||||
def enum(*sequential: Any, **named: Any) -> type[Any]:
|
||||
def enum(*sequential: Any, **named: Any) -> Type[Any]:
|
||||
enums = dict(zip(sequential, range(len(sequential))), **named)
|
||||
reverse = {value: key for key, value in enums.items()}
|
||||
enums["reverse_mapping"] = reverse
|
||||
return type("Enum", (), enums)
|
||||
|
||||
|
||||
def build_netloc(host: str, port: int | None) -> str:
|
||||
def build_netloc(host: str, port: Optional[int]) -> str:
|
||||
"""
|
||||
Build a netloc from a host-port pair
|
||||
"""
|
||||
|
|
@ -413,7 +463,7 @@ def build_url_from_netloc(netloc: str, scheme: str = "https") -> str:
|
|||
return f"{scheme}://{netloc}"
|
||||
|
||||
|
||||
def parse_netloc(netloc: str) -> tuple[str | None, int | None]:
|
||||
def parse_netloc(netloc: str) -> Tuple[Optional[str], Optional[int]]:
|
||||
"""
|
||||
Return the host-port pair from a netloc.
|
||||
"""
|
||||
|
|
@ -435,7 +485,7 @@ def split_auth_from_netloc(netloc: str) -> NetlocTuple:
|
|||
# behaves if more than one @ is present (which can be checked using
|
||||
# the password attribute of urlsplit()'s return value).
|
||||
auth, netloc = netloc.rsplit("@", 1)
|
||||
pw: str | None = None
|
||||
pw: Optional[str] = None
|
||||
if ":" in auth:
|
||||
# Split from the left because that's how urllib.parse.urlsplit()
|
||||
# behaves if more than one : is present (which again can be checked
|
||||
|
|
@ -472,8 +522,8 @@ def redact_netloc(netloc: str) -> str:
|
|||
|
||||
|
||||
def _transform_url(
|
||||
url: str, transform_netloc: Callable[[str], tuple[Any, ...]]
|
||||
) -> tuple[str, NetlocTuple]:
|
||||
url: str, transform_netloc: Callable[[str], Tuple[Any, ...]]
|
||||
) -> Tuple[str, NetlocTuple]:
|
||||
"""Transform and replace netloc in a url.
|
||||
|
||||
transform_netloc is a function taking the netloc and returning a
|
||||
|
|
@ -495,13 +545,13 @@ def _get_netloc(netloc: str) -> NetlocTuple:
|
|||
return split_auth_from_netloc(netloc)
|
||||
|
||||
|
||||
def _redact_netloc(netloc: str) -> tuple[str]:
|
||||
def _redact_netloc(netloc: str) -> Tuple[str]:
|
||||
return (redact_netloc(netloc),)
|
||||
|
||||
|
||||
def split_auth_netloc_from_url(
|
||||
url: str,
|
||||
) -> tuple[str, str, tuple[str | None, str | None]]:
|
||||
) -> Tuple[str, str, Tuple[Optional[str], Optional[str]]]:
|
||||
"""
|
||||
Parse a url into separate netloc, auth, and url with no auth.
|
||||
|
||||
|
|
@ -530,10 +580,10 @@ def redact_auth_from_requirement(req: Requirement) -> str:
|
|||
return str(req).replace(req.url, redact_auth_from_url(req.url))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class HiddenText:
|
||||
secret: str
|
||||
redacted: str
|
||||
def __init__(self, secret: str, redacted: str) -> None:
|
||||
self.secret = secret
|
||||
self.redacted = redacted
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<HiddenText {str(self)!r}>"
|
||||
|
|
@ -543,7 +593,7 @@ class HiddenText:
|
|||
|
||||
# This is useful for testing.
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if type(self) is not type(other):
|
||||
if type(self) != type(other):
|
||||
return False
|
||||
|
||||
# The string being used for redaction doesn't also have to match,
|
||||
|
|
@ -606,7 +656,7 @@ def is_console_interactive() -> bool:
|
|||
return sys.stdin is not None and sys.stdin.isatty()
|
||||
|
||||
|
||||
def hash_file(path: str, blocksize: int = 1 << 20) -> tuple[Any, int]:
|
||||
def hash_file(path: str, blocksize: int = 1 << 20) -> Tuple[Any, int]:
|
||||
"""Return (hash, length) for path using hashlib.sha256()"""
|
||||
|
||||
h = hashlib.sha256()
|
||||
|
|
@ -618,7 +668,7 @@ def hash_file(path: str, blocksize: int = 1 << 20) -> tuple[Any, int]:
|
|||
return h, length
|
||||
|
||||
|
||||
def pairwise(iterable: Iterable[Any]) -> Iterator[tuple[Any, Any]]:
|
||||
def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]:
|
||||
"""
|
||||
Return paired elements.
|
||||
|
||||
|
|
@ -630,8 +680,9 @@ def pairwise(iterable: Iterable[Any]) -> Iterator[tuple[Any, Any]]:
|
|||
|
||||
|
||||
def partition(
|
||||
pred: Callable[[T], bool], iterable: Iterable[T]
|
||||
) -> tuple[Iterable[T], Iterable[T]]:
|
||||
pred: Callable[[T], bool],
|
||||
iterable: Iterable[T],
|
||||
) -> Tuple[Iterable[T], Iterable[T]]:
|
||||
"""
|
||||
Use a predicate to partition entries into false entries and true entries,
|
||||
like
|
||||
|
|
@ -648,9 +699,9 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
|
|||
config_holder: Any,
|
||||
source_dir: str,
|
||||
build_backend: str,
|
||||
backend_path: str | None = None,
|
||||
runner: Callable[..., None] | None = None,
|
||||
python_executable: str | None = None,
|
||||
backend_path: Optional[str] = None,
|
||||
runner: Optional[Callable[..., None]] = None,
|
||||
python_executable: Optional[str] = None,
|
||||
):
|
||||
super().__init__(
|
||||
source_dir, build_backend, backend_path, runner, python_executable
|
||||
|
|
@ -660,8 +711,8 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
|
|||
def build_wheel(
|
||||
self,
|
||||
wheel_directory: str,
|
||||
config_settings: Mapping[str, Any] | None = None,
|
||||
metadata_directory: str | None = None,
|
||||
config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
|
||||
metadata_directory: Optional[str] = None,
|
||||
) -> str:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().build_wheel(
|
||||
|
|
@ -671,7 +722,7 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
|
|||
def build_sdist(
|
||||
self,
|
||||
sdist_directory: str,
|
||||
config_settings: Mapping[str, Any] | None = None,
|
||||
config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
|
||||
) -> str:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().build_sdist(sdist_directory, config_settings=cs)
|
||||
|
|
@ -679,8 +730,8 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
|
|||
def build_editable(
|
||||
self,
|
||||
wheel_directory: str,
|
||||
config_settings: Mapping[str, Any] | None = None,
|
||||
metadata_directory: str | None = None,
|
||||
config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
|
||||
metadata_directory: Optional[str] = None,
|
||||
) -> str:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().build_editable(
|
||||
|
|
@ -688,27 +739,27 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
|
|||
)
|
||||
|
||||
def get_requires_for_build_wheel(
|
||||
self, config_settings: Mapping[str, Any] | None = None
|
||||
) -> Sequence[str]:
|
||||
self, config_settings: Optional[Dict[str, Union[str, List[str]]]] = None
|
||||
) -> List[str]:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().get_requires_for_build_wheel(config_settings=cs)
|
||||
|
||||
def get_requires_for_build_sdist(
|
||||
self, config_settings: Mapping[str, Any] | None = None
|
||||
) -> Sequence[str]:
|
||||
self, config_settings: Optional[Dict[str, Union[str, List[str]]]] = None
|
||||
) -> List[str]:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().get_requires_for_build_sdist(config_settings=cs)
|
||||
|
||||
def get_requires_for_build_editable(
|
||||
self, config_settings: Mapping[str, Any] | None = None
|
||||
) -> Sequence[str]:
|
||||
self, config_settings: Optional[Dict[str, Union[str, List[str]]]] = None
|
||||
) -> List[str]:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().get_requires_for_build_editable(config_settings=cs)
|
||||
|
||||
def prepare_metadata_for_build_wheel(
|
||||
self,
|
||||
metadata_directory: str,
|
||||
config_settings: Mapping[str, Any] | None = None,
|
||||
config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
|
||||
_allow_fallback: bool = True,
|
||||
) -> str:
|
||||
cs = self.config_holder.config_settings
|
||||
|
|
@ -721,45 +772,12 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
|
|||
def prepare_metadata_for_build_editable(
|
||||
self,
|
||||
metadata_directory: str,
|
||||
config_settings: Mapping[str, Any] | None = None,
|
||||
config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
|
||||
_allow_fallback: bool = True,
|
||||
) -> str | None:
|
||||
) -> str:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().prepare_metadata_for_build_editable(
|
||||
metadata_directory=metadata_directory,
|
||||
config_settings=cs,
|
||||
_allow_fallback=_allow_fallback,
|
||||
)
|
||||
|
||||
|
||||
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, possibly "
|
||||
"rendering your system unusable. "
|
||||
"It is recommended to use a virtual environment instead: "
|
||||
"https://pip.pypa.io/warnings/venv. "
|
||||
"Use the --root-user-action option if you know what you are doing and "
|
||||
"want to suppress this warning."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
"""Utilities for defining models
|
||||
"""
|
||||
|
||||
import operator
|
||||
from typing import Any, Callable, Type
|
||||
|
||||
|
||||
class KeyBasedCompareMixin:
|
||||
"""Provides comparison capabilities that is based on a key"""
|
||||
|
||||
__slots__ = ["_compare_key", "_defining_class"]
|
||||
|
||||
def __init__(self, key: Any, defining_class: Type["KeyBasedCompareMixin"]) -> None:
|
||||
self._compare_key = key
|
||||
self._defining_class = defining_class
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._compare_key)
|
||||
|
||||
def __lt__(self, other: Any) -> bool:
|
||||
return self._compare(other, operator.__lt__)
|
||||
|
||||
def __le__(self, other: Any) -> bool:
|
||||
return self._compare(other, operator.__le__)
|
||||
|
||||
def __gt__(self, other: Any) -> bool:
|
||||
return self._compare(other, operator.__gt__)
|
||||
|
||||
def __ge__(self, other: Any) -> bool:
|
||||
return self._compare(other, operator.__ge__)
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
return self._compare(other, operator.__eq__)
|
||||
|
||||
def _compare(self, other: Any, method: Callable[[Any, Any], bool]) -> bool:
|
||||
if not isinstance(other, self._defining_class):
|
||||
return NotImplemented
|
||||
|
||||
return method(self._compare_key, other._compare_key)
|
||||
|
|
@ -1,17 +1,18 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import re
|
||||
from typing import NewType, Optional, Tuple, cast
|
||||
|
||||
from pip._vendor.packaging import specifiers, version
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
|
||||
NormalizedExtra = NewType("NormalizedExtra", str)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=32)
|
||||
def check_requires_python(
|
||||
requires_python: str | None, version_info: tuple[int, ...]
|
||||
requires_python: Optional[str], version_info: Tuple[int, ...]
|
||||
) -> bool:
|
||||
"""
|
||||
Check if the given Python version matches a "Requires-Python" specifier.
|
||||
|
|
@ -33,7 +34,7 @@ def check_requires_python(
|
|||
return python_version in requires_python_specifier
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=10000)
|
||||
@functools.lru_cache(maxsize=512)
|
||||
def get_requirement(req_string: str) -> Requirement:
|
||||
"""Construct a packaging.Requirement object with caching"""
|
||||
# Parsing requirement strings is expensive, and is also expected to happen
|
||||
|
|
@ -42,3 +43,15 @@ def get_requirement(req_string: str) -> Requirement:
|
|||
# minimize repeated parsing of the same string to construct equivalent
|
||||
# Requirement objects.
|
||||
return Requirement(req_string)
|
||||
|
||||
|
||||
def safe_extra(extra: str) -> NormalizedExtra:
|
||||
"""Convert an arbitrary string to a standard 'extra' name
|
||||
|
||||
Any runs of non-alphanumeric characters are replaced with a single '_',
|
||||
and the result is always lowercased.
|
||||
|
||||
This function is duplicated from ``pkg_resources``. Note that this is not
|
||||
the same to either ``canonicalize_name`` or ``_egg_link_name``.
|
||||
"""
|
||||
return cast(NormalizedExtra, re.sub("[^A-Za-z0-9.-]+", "_", extra).lower())
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from time import perf_counter, sleep
|
||||
from typing import TYPE_CHECKING, Callable, TypeVar
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
T = TypeVar("T")
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
def retry(
|
||||
wait: float, stop_after_delay: float
|
||||
) -> Callable[[Callable[P, T]], Callable[P, T]]:
|
||||
"""Decorator to automatically retry a function on error.
|
||||
|
||||
If the function raises, the function is recalled with the same arguments
|
||||
until it returns or the time limit is reached. When the time limit is
|
||||
surpassed, the last exception raised is reraised.
|
||||
|
||||
:param wait: The time to wait after an error before retrying, in seconds.
|
||||
:param stop_after_delay: The time limit after which retries will cease,
|
||||
in seconds.
|
||||
"""
|
||||
|
||||
def wrapper(func: Callable[P, T]) -> Callable[P, T]:
|
||||
|
||||
@functools.wraps(func)
|
||||
def retry_wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||
# The performance counter is monotonic on all platforms we care
|
||||
# about and has much better resolution than time.monotonic().
|
||||
start_time = perf_counter()
|
||||
while True:
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception:
|
||||
if perf_counter() - start_time > stop_after_delay:
|
||||
raise
|
||||
sleep(wait)
|
||||
|
||||
return retry_wrapped
|
||||
|
||||
return wrapper
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
import sys
|
||||
import textwrap
|
||||
from typing import List, Optional, Sequence
|
||||
|
||||
# Shim to wrap setup.py invocation with setuptools
|
||||
# Note that __file__ is handled via two {!r} *and* %r, to ensure that paths on
|
||||
# Windows are correctly handled (it should be "C:\\Users" not "C:\Users").
|
||||
_SETUPTOOLS_SHIM = textwrap.dedent(
|
||||
"""
|
||||
exec(compile('''
|
||||
# This is <pip-setuptools-caller> -- a caller that pip uses to run setup.py
|
||||
#
|
||||
# - It imports setuptools before invoking setup.py, to enable projects that directly
|
||||
# import from `distutils.core` to work with newer packaging standards.
|
||||
# - It provides a clear error message when setuptools is not installed.
|
||||
# - It sets `sys.argv[0]` to the underlying `setup.py`, when invoking `setup.py` so
|
||||
# setuptools doesn't think the script is `-c`. This avoids the following warning:
|
||||
# manifest_maker: standard file '-c' not found".
|
||||
# - It generates a shim setup.py, for handling setup.cfg-only projects.
|
||||
import os, sys, tokenize
|
||||
|
||||
try:
|
||||
import setuptools
|
||||
except ImportError as error:
|
||||
print(
|
||||
"ERROR: Can not execute `setup.py` since setuptools is not available in "
|
||||
"the build environment.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
__file__ = %r
|
||||
sys.argv[0] = __file__
|
||||
|
||||
if os.path.exists(__file__):
|
||||
filename = __file__
|
||||
with tokenize.open(__file__) as f:
|
||||
setup_py_code = f.read()
|
||||
else:
|
||||
filename = "<auto-generated setuptools caller>"
|
||||
setup_py_code = "from setuptools import setup; setup()"
|
||||
|
||||
exec(compile(setup_py_code, filename, "exec"))
|
||||
''' % ({!r},), "<pip-setuptools-caller>", "exec"))
|
||||
"""
|
||||
).rstrip()
|
||||
|
||||
|
||||
def make_setuptools_shim_args(
|
||||
setup_py_path: str,
|
||||
global_options: Optional[Sequence[str]] = None,
|
||||
no_user_config: bool = False,
|
||||
unbuffered_output: bool = False,
|
||||
) -> List[str]:
|
||||
"""
|
||||
Get setuptools command arguments with shim wrapped setup file invocation.
|
||||
|
||||
:param setup_py_path: The path to setup.py to be wrapped.
|
||||
:param global_options: Additional global options.
|
||||
:param no_user_config: If True, disables personal user configuration.
|
||||
:param unbuffered_output: If True, adds the unbuffered switch to the
|
||||
argument list.
|
||||
"""
|
||||
args = [sys.executable]
|
||||
if unbuffered_output:
|
||||
args += ["-u"]
|
||||
args += ["-c", _SETUPTOOLS_SHIM.format(setup_py_path)]
|
||||
if global_options:
|
||||
args += global_options
|
||||
if no_user_config:
|
||||
args += ["--no-user-cfg"]
|
||||
return args
|
||||
|
||||
|
||||
def make_setuptools_bdist_wheel_args(
|
||||
setup_py_path: str,
|
||||
global_options: Sequence[str],
|
||||
build_options: Sequence[str],
|
||||
destination_dir: str,
|
||||
) -> List[str]:
|
||||
# NOTE: Eventually, we'd want to also -S to the flags here, when we're
|
||||
# isolating. Currently, it breaks Python in virtualenvs, because it
|
||||
# relies on site.py to find parts of the standard library outside the
|
||||
# virtualenv.
|
||||
args = make_setuptools_shim_args(
|
||||
setup_py_path, global_options=global_options, unbuffered_output=True
|
||||
)
|
||||
args += ["bdist_wheel", "-d", destination_dir]
|
||||
args += build_options
|
||||
return args
|
||||
|
||||
|
||||
def make_setuptools_clean_args(
|
||||
setup_py_path: str,
|
||||
global_options: Sequence[str],
|
||||
) -> List[str]:
|
||||
args = make_setuptools_shim_args(
|
||||
setup_py_path, global_options=global_options, unbuffered_output=True
|
||||
)
|
||||
args += ["clean", "--all"]
|
||||
return args
|
||||
|
||||
|
||||
def make_setuptools_develop_args(
|
||||
setup_py_path: str,
|
||||
*,
|
||||
global_options: Sequence[str],
|
||||
no_user_config: bool,
|
||||
prefix: Optional[str],
|
||||
home: Optional[str],
|
||||
use_user_site: bool,
|
||||
) -> List[str]:
|
||||
assert not (use_user_site and prefix)
|
||||
|
||||
args = make_setuptools_shim_args(
|
||||
setup_py_path,
|
||||
global_options=global_options,
|
||||
no_user_config=no_user_config,
|
||||
)
|
||||
|
||||
args += ["develop", "--no-deps"]
|
||||
|
||||
if prefix:
|
||||
args += ["--prefix", prefix]
|
||||
if home is not None:
|
||||
args += ["--install-dir", home]
|
||||
|
||||
if use_user_site:
|
||||
args += ["--user", "--prefix="]
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def make_setuptools_egg_info_args(
|
||||
setup_py_path: str,
|
||||
egg_info_dir: Optional[str],
|
||||
no_user_config: bool,
|
||||
) -> List[str]:
|
||||
args = make_setuptools_shim_args(setup_py_path, no_user_config=no_user_config)
|
||||
|
||||
args += ["egg_info"]
|
||||
|
||||
if egg_info_dir:
|
||||
args += ["--egg-base", egg_info_dir]
|
||||
|
||||
return args
|
||||
|
|
@ -1,11 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from collections.abc import Iterable, Mapping
|
||||
from typing import Any, Callable, Literal, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Iterable,
|
||||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
|
||||
from pip._vendor.rich.markup import escape
|
||||
|
||||
|
|
@ -14,10 +20,16 @@ from pip._internal.exceptions import InstallationSubprocessError
|
|||
from pip._internal.utils.logging import VERBOSE, subprocess_logger
|
||||
from pip._internal.utils.misc import HiddenText
|
||||
|
||||
CommandArgs = list[Union[str, HiddenText]]
|
||||
if TYPE_CHECKING:
|
||||
# Literal was introduced in Python 3.8.
|
||||
#
|
||||
# TODO: Remove `if TYPE_CHECKING` when dropping support for Python 3.7.
|
||||
from typing import Literal
|
||||
|
||||
CommandArgs = List[Union[str, HiddenText]]
|
||||
|
||||
|
||||
def make_command(*args: str | HiddenText | CommandArgs) -> CommandArgs:
|
||||
def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs:
|
||||
"""
|
||||
Create a CommandArgs object.
|
||||
"""
|
||||
|
|
@ -34,7 +46,7 @@ def make_command(*args: str | HiddenText | CommandArgs) -> CommandArgs:
|
|||
return command_args
|
||||
|
||||
|
||||
def format_command_args(args: list[str] | CommandArgs) -> str:
|
||||
def format_command_args(args: Union[List[str], CommandArgs]) -> str:
|
||||
"""
|
||||
Format command arguments for display.
|
||||
"""
|
||||
|
|
@ -49,7 +61,7 @@ def format_command_args(args: list[str] | CommandArgs) -> str:
|
|||
)
|
||||
|
||||
|
||||
def reveal_command_args(args: list[str] | CommandArgs) -> list[str]:
|
||||
def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]:
|
||||
"""
|
||||
Return the arguments in their raw, unredacted form.
|
||||
"""
|
||||
|
|
@ -57,16 +69,16 @@ def reveal_command_args(args: list[str] | CommandArgs) -> list[str]:
|
|||
|
||||
|
||||
def call_subprocess(
|
||||
cmd: list[str] | CommandArgs,
|
||||
cmd: Union[List[str], CommandArgs],
|
||||
show_stdout: bool = False,
|
||||
cwd: str | None = None,
|
||||
on_returncode: Literal["raise", "warn", "ignore"] = "raise",
|
||||
extra_ok_returncodes: Iterable[int] | None = None,
|
||||
extra_environ: Mapping[str, Any] | None = None,
|
||||
unset_environ: Iterable[str] | None = None,
|
||||
spinner: SpinnerInterface | None = None,
|
||||
log_failed_cmd: bool | None = True,
|
||||
stdout_only: bool | None = False,
|
||||
cwd: Optional[str] = None,
|
||||
on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise",
|
||||
extra_ok_returncodes: Optional[Iterable[int]] = None,
|
||||
extra_environ: Optional[Mapping[str, Any]] = None,
|
||||
unset_environ: Optional[Iterable[str]] = None,
|
||||
spinner: Optional[SpinnerInterface] = None,
|
||||
log_failed_cmd: Optional[bool] = True,
|
||||
stdout_only: Optional[bool] = False,
|
||||
*,
|
||||
command_desc: str,
|
||||
) -> str:
|
||||
|
|
@ -232,9 +244,9 @@ def runner_with_spinner_message(message: str) -> Callable[..., None]:
|
|||
"""
|
||||
|
||||
def runner(
|
||||
cmd: list[str],
|
||||
cwd: str | None = None,
|
||||
extra_environ: Mapping[str, Any] | None = None,
|
||||
cmd: List[str],
|
||||
cwd: Optional[str] = None,
|
||||
extra_environ: Optional[Mapping[str, Any]] = None,
|
||||
) -> None:
|
||||
with open_spinner(message) as spinner:
|
||||
call_subprocess(
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import errno
|
||||
import itertools
|
||||
import logging
|
||||
import os.path
|
||||
import tempfile
|
||||
import traceback
|
||||
from collections.abc import Generator
|
||||
from contextlib import ExitStack, contextmanager
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Generator,
|
||||
List,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from pip._internal.utils.misc import enum, rmtree
|
||||
|
|
@ -31,7 +33,7 @@ tempdir_kinds = enum(
|
|||
)
|
||||
|
||||
|
||||
_tempdir_manager: ExitStack | None = None
|
||||
_tempdir_manager: Optional[ExitStack] = None
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -49,7 +51,7 @@ class TempDirectoryTypeRegistry:
|
|||
"""Manages temp directory behavior"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._should_delete: dict[str, bool] = {}
|
||||
self._should_delete: Dict[str, bool] = {}
|
||||
|
||||
def set_delete(self, kind: str, value: bool) -> None:
|
||||
"""Indicate whether a TempDirectory of the given kind should be
|
||||
|
|
@ -64,7 +66,7 @@ class TempDirectoryTypeRegistry:
|
|||
return self._should_delete.get(kind, True)
|
||||
|
||||
|
||||
_tempdir_registry: TempDirectoryTypeRegistry | None = None
|
||||
_tempdir_registry: Optional[TempDirectoryTypeRegistry] = None
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -111,8 +113,8 @@ class TempDirectory:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
path: str | None = None,
|
||||
delete: bool | None | _Default = _default,
|
||||
path: Optional[str] = None,
|
||||
delete: Union[bool, None, _Default] = _default,
|
||||
kind: str = "temp",
|
||||
globally_managed: bool = False,
|
||||
ignore_cleanup_errors: bool = True,
|
||||
|
|
@ -182,7 +184,7 @@ class TempDirectory:
|
|||
if not os.path.exists(self._path):
|
||||
return
|
||||
|
||||
errors: list[BaseException] = []
|
||||
errors: List[BaseException] = []
|
||||
|
||||
def onerror(
|
||||
func: Callable[..., Any],
|
||||
|
|
@ -206,7 +208,7 @@ class TempDirectory:
|
|||
|
||||
if self.ignore_cleanup_errors:
|
||||
try:
|
||||
# first try with @retry; retrying to handle ephemeral errors
|
||||
# first try with tenacity; retrying to handle ephemeral errors
|
||||
rmtree(self._path, ignore_errors=False)
|
||||
except OSError:
|
||||
# last pass ignore/log all errors
|
||||
|
|
@ -243,7 +245,7 @@ class AdjacentTempDirectory(TempDirectory):
|
|||
# with leading '-' and invalid metadata
|
||||
LEADING_CHARS = "-~.=%0123456789"
|
||||
|
||||
def __init__(self, original: str, delete: bool | None = None) -> None:
|
||||
def __init__(self, original: str, delete: Optional[bool] = None) -> None:
|
||||
self.original = original.rstrip("/\\")
|
||||
super().__init__(delete=delete)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
"""Utilities related archives."""
|
||||
|
||||
from __future__ import annotations
|
||||
"""Utilities related archives.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tarfile
|
||||
import zipfile
|
||||
from collections.abc import Iterable
|
||||
from typing import Iterable, List, Optional
|
||||
from zipfile import ZipInfo
|
||||
|
||||
from pip._internal.exceptions import InstallationError
|
||||
|
|
@ -49,7 +47,7 @@ def current_umask() -> int:
|
|||
return mask
|
||||
|
||||
|
||||
def split_leading_dir(path: str) -> list[str]:
|
||||
def split_leading_dir(path: str) -> List[str]:
|
||||
path = path.lstrip("/").lstrip("\\")
|
||||
if "/" in path and (
|
||||
("\\" in path and path.find("/") < path.find("\\")) or "\\" not in path
|
||||
|
|
@ -87,16 +85,12 @@ def is_within_directory(directory: str, target: str) -> bool:
|
|||
return prefix == abs_directory
|
||||
|
||||
|
||||
def _get_default_mode_plus_executable() -> int:
|
||||
return 0o777 & ~current_umask() | 0o111
|
||||
|
||||
|
||||
def set_extracted_file_to_default_mode_plus_executable(path: str) -> None:
|
||||
"""
|
||||
Make file present at path have execute for user/group/world
|
||||
(chmod +x) is no-op on windows per python docs
|
||||
"""
|
||||
os.chmod(path, _get_default_mode_plus_executable())
|
||||
os.chmod(path, (0o777 & ~current_umask() | 0o111))
|
||||
|
||||
|
||||
def zip_item_is_executable(info: ZipInfo) -> bool:
|
||||
|
|
@ -133,7 +127,7 @@ def unzip_file(filename: str, location: str, flatten: bool = True) -> None:
|
|||
"outside target directory ({})"
|
||||
)
|
||||
raise InstallationError(message.format(filename, fn, location))
|
||||
if fn.endswith(("/", "\\")):
|
||||
if fn.endswith("/") or fn.endswith("\\"):
|
||||
# A directory
|
||||
ensure_dir(fn)
|
||||
else:
|
||||
|
|
@ -157,8 +151,8 @@ def untar_file(filename: str, location: str) -> None:
|
|||
Untar the file (with path `filename`) to the destination `location`.
|
||||
All files are written based on system defaults and umask (i.e. permissions
|
||||
are not preserved), except that regular file members with any execute
|
||||
permissions (user, group, or world) have "chmod +x" applied on top of the
|
||||
default. Note that for windows, any execute changes using os.chmod are
|
||||
permissions (user, group, or world) have "chmod +x" applied after being
|
||||
written. Note that for windows, any execute changes using os.chmod are
|
||||
no-ops per the python docs.
|
||||
"""
|
||||
ensure_dir(location)
|
||||
|
|
@ -176,165 +170,66 @@ def untar_file(filename: str, location: str) -> None:
|
|||
filename,
|
||||
)
|
||||
mode = "r:*"
|
||||
|
||||
tar = tarfile.open(filename, mode, encoding="utf-8") # type: ignore
|
||||
tar = tarfile.open(filename, mode, encoding="utf-8")
|
||||
try:
|
||||
leading = has_leading_dir([member.name for member in tar.getmembers()])
|
||||
|
||||
# PEP 706 added `tarfile.data_filter`, and made some other changes to
|
||||
# Python's tarfile module (see below). The features were backported to
|
||||
# security releases.
|
||||
try:
|
||||
data_filter = tarfile.data_filter
|
||||
except AttributeError:
|
||||
_untar_without_filter(filename, location, tar, leading)
|
||||
else:
|
||||
default_mode_plus_executable = _get_default_mode_plus_executable()
|
||||
|
||||
for member in tar.getmembers():
|
||||
fn = member.name
|
||||
if leading:
|
||||
# Strip the leading directory from all files in the archive,
|
||||
# including hardlink targets (which are relative to the
|
||||
# unpack location).
|
||||
for member in tar.getmembers():
|
||||
name_lead, name_rest = split_leading_dir(member.name)
|
||||
member.name = name_rest
|
||||
if member.islnk():
|
||||
lnk_lead, lnk_rest = split_leading_dir(member.linkname)
|
||||
if lnk_lead == name_lead:
|
||||
member.linkname = lnk_rest
|
||||
|
||||
def pip_filter(member: tarfile.TarInfo, path: str) -> tarfile.TarInfo:
|
||||
orig_mode = member.mode
|
||||
try:
|
||||
try:
|
||||
member = data_filter(member, location)
|
||||
except tarfile.LinkOutsideDestinationError:
|
||||
if sys.version_info[:3] in {
|
||||
(3, 9, 17),
|
||||
(3, 10, 12),
|
||||
(3, 11, 4),
|
||||
}:
|
||||
# The tarfile filter in specific Python versions
|
||||
# raises LinkOutsideDestinationError on valid input
|
||||
# (https://github.com/python/cpython/issues/107845)
|
||||
# Ignore the error there, but do use the
|
||||
# more lax `tar_filter`
|
||||
member = tarfile.tar_filter(member, location)
|
||||
else:
|
||||
raise
|
||||
except tarfile.TarError as exc:
|
||||
message = "Invalid member in the tar file {}: {}"
|
||||
# Filter error messages mention the member name.
|
||||
# No need to add it here.
|
||||
raise InstallationError(
|
||||
message.format(
|
||||
filename,
|
||||
exc,
|
||||
)
|
||||
)
|
||||
if member.isfile() and orig_mode & 0o111:
|
||||
member.mode = default_mode_plus_executable
|
||||
else:
|
||||
# See PEP 706 note above.
|
||||
# The PEP changed this from `int` to `Optional[int]`,
|
||||
# where None means "use the default". Mypy doesn't
|
||||
# know this yet.
|
||||
member.mode = None # type: ignore [assignment]
|
||||
return member
|
||||
|
||||
tar.extractall(location, filter=pip_filter)
|
||||
|
||||
finally:
|
||||
tar.close()
|
||||
|
||||
|
||||
def is_symlink_target_in_tar(tar: tarfile.TarFile, tarinfo: tarfile.TarInfo) -> bool:
|
||||
"""Check if the file pointed to by the symbolic link is in the tar archive"""
|
||||
linkname = os.path.join(os.path.dirname(tarinfo.name), tarinfo.linkname)
|
||||
|
||||
linkname = os.path.normpath(linkname)
|
||||
linkname = linkname.replace("\\", "/")
|
||||
|
||||
try:
|
||||
tar.getmember(linkname)
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
|
||||
def _untar_without_filter(
|
||||
filename: str,
|
||||
location: str,
|
||||
tar: tarfile.TarFile,
|
||||
leading: bool,
|
||||
) -> None:
|
||||
"""Fallback for Python without tarfile.data_filter"""
|
||||
# NOTE: This function can be removed once pip requires CPython ≥ 3.12.
|
||||
# PEP 706 added tarfile.data_filter, made tarfile extraction operations more secure.
|
||||
# This feature is fully supported from CPython 3.12 onward.
|
||||
for member in tar.getmembers():
|
||||
fn = member.name
|
||||
if leading:
|
||||
fn = split_leading_dir(fn)[1]
|
||||
path = os.path.join(location, fn)
|
||||
if not is_within_directory(location, path):
|
||||
message = (
|
||||
"The tar file ({}) has a file ({}) trying to install "
|
||||
"outside target directory ({})"
|
||||
)
|
||||
raise InstallationError(message.format(filename, path, location))
|
||||
if member.isdir():
|
||||
ensure_dir(path)
|
||||
elif member.issym():
|
||||
if not is_symlink_target_in_tar(tar, member):
|
||||
fn = split_leading_dir(fn)[1]
|
||||
path = os.path.join(location, fn)
|
||||
if not is_within_directory(location, path):
|
||||
message = (
|
||||
"The tar file ({}) has a file ({}) trying to install "
|
||||
"outside target directory ({})"
|
||||
)
|
||||
raise InstallationError(
|
||||
message.format(filename, member.name, member.linkname)
|
||||
)
|
||||
try:
|
||||
tar._extract_member(member, path)
|
||||
except Exception as exc:
|
||||
# Some corrupt tar files seem to produce this
|
||||
# (specifically bad symlinks)
|
||||
logger.warning(
|
||||
"In the tar file %s the member %s is invalid: %s",
|
||||
filename,
|
||||
member.name,
|
||||
exc,
|
||||
)
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
fp = tar.extractfile(member)
|
||||
except (KeyError, AttributeError) as exc:
|
||||
# Some corrupt tar files seem to produce this
|
||||
# (specifically bad symlinks)
|
||||
logger.warning(
|
||||
"In the tar file %s the member %s is invalid: %s",
|
||||
filename,
|
||||
member.name,
|
||||
exc,
|
||||
)
|
||||
continue
|
||||
ensure_dir(os.path.dirname(path))
|
||||
assert fp is not None
|
||||
with open(path, "wb") as destfp:
|
||||
shutil.copyfileobj(fp, destfp)
|
||||
fp.close()
|
||||
# Update the timestamp (useful for cython compiled files)
|
||||
tar.utime(member, path)
|
||||
# member have any execute permissions for user/group/world?
|
||||
if member.mode & 0o111:
|
||||
set_extracted_file_to_default_mode_plus_executable(path)
|
||||
raise InstallationError(message.format(filename, path, location))
|
||||
if member.isdir():
|
||||
ensure_dir(path)
|
||||
elif member.issym():
|
||||
try:
|
||||
tar._extract_member(member, path)
|
||||
except Exception as exc:
|
||||
# Some corrupt tar files seem to produce this
|
||||
# (specifically bad symlinks)
|
||||
logger.warning(
|
||||
"In the tar file %s the member %s is invalid: %s",
|
||||
filename,
|
||||
member.name,
|
||||
exc,
|
||||
)
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
fp = tar.extractfile(member)
|
||||
except (KeyError, AttributeError) as exc:
|
||||
# Some corrupt tar files seem to produce this
|
||||
# (specifically bad symlinks)
|
||||
logger.warning(
|
||||
"In the tar file %s the member %s is invalid: %s",
|
||||
filename,
|
||||
member.name,
|
||||
exc,
|
||||
)
|
||||
continue
|
||||
ensure_dir(os.path.dirname(path))
|
||||
assert fp is not None
|
||||
with open(path, "wb") as destfp:
|
||||
shutil.copyfileobj(fp, destfp)
|
||||
fp.close()
|
||||
# Update the timestamp (useful for cython compiled files)
|
||||
tar.utime(member, path)
|
||||
# member have any execute permissions for user/group/world?
|
||||
if member.mode & 0o111:
|
||||
set_extracted_file_to_default_mode_plus_executable(path)
|
||||
finally:
|
||||
tar.close()
|
||||
|
||||
|
||||
def unpack_file(
|
||||
filename: str,
|
||||
location: str,
|
||||
content_type: str | None = None,
|
||||
content_type: Optional[str] = None,
|
||||
) -> None:
|
||||
filename = os.path.realpath(filename)
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -2,17 +2,24 @@ import os
|
|||
import string
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from typing import Optional
|
||||
|
||||
from .compat import WINDOWS
|
||||
|
||||
|
||||
def get_url_scheme(url: str) -> Optional[str]:
|
||||
if ":" not in url:
|
||||
return None
|
||||
return url.split(":", 1)[0].lower()
|
||||
|
||||
|
||||
def path_to_url(path: str) -> str:
|
||||
"""
|
||||
Convert a path to a file: URL. The path will be made absolute and have
|
||||
quoted path parts.
|
||||
"""
|
||||
path = os.path.normpath(os.path.abspath(path))
|
||||
url = urllib.parse.urljoin("file://", urllib.request.pathname2url(path))
|
||||
url = urllib.parse.urljoin("file:", urllib.request.pathname2url(path))
|
||||
return url
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import site
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_INCLUDE_SYSTEM_SITE_PACKAGES_REGEX = re.compile(
|
||||
|
|
@ -34,7 +33,7 @@ def running_under_virtualenv() -> bool:
|
|||
return _running_under_venv() or _running_under_legacy_virtualenv()
|
||||
|
||||
|
||||
def _get_pyvenv_cfg_lines() -> list[str] | None:
|
||||
def _get_pyvenv_cfg_lines() -> Optional[List[str]]:
|
||||
"""Reads {sys.prefix}/pyvenv.cfg and returns its contents as list of lines
|
||||
|
||||
Returns None, if it could not read/access the file.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
"""Support functions for working with wheel files."""
|
||||
"""Support functions for working with wheel files.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from email.message import Message
|
||||
from email.parser import Parser
|
||||
from typing import Tuple
|
||||
from zipfile import BadZipFile, ZipFile
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
|
@ -15,7 +17,7 @@ VERSION_COMPATIBLE = (1, 0)
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_wheel(wheel_zip: ZipFile, name: str) -> tuple[str, Message]:
|
||||
def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]:
|
||||
"""Extract information from the provided wheel, ensuring it meets basic
|
||||
standards.
|
||||
|
||||
|
|
@ -26,7 +28,7 @@ def parse_wheel(wheel_zip: ZipFile, name: str) -> tuple[str, Message]:
|
|||
metadata = wheel_metadata(wheel_zip, info_dir)
|
||||
version = wheel_version(metadata)
|
||||
except UnsupportedWheel as e:
|
||||
raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
|
||||
raise UnsupportedWheel(f"{name} has an invalid wheel, {str(e)}")
|
||||
|
||||
check_compatibility(version, name)
|
||||
|
||||
|
|
@ -92,7 +94,7 @@ def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message:
|
|||
return Parser().parsestr(wheel_text)
|
||||
|
||||
|
||||
def wheel_version(wheel_data: Message) -> tuple[int, ...]:
|
||||
def wheel_version(wheel_data: Message) -> Tuple[int, ...]:
|
||||
"""Given WHEEL metadata, return the parsed Wheel-Version.
|
||||
Otherwise, raise UnsupportedWheel.
|
||||
"""
|
||||
|
|
@ -108,7 +110,7 @@ def wheel_version(wheel_data: Message) -> tuple[int, ...]:
|
|||
raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}")
|
||||
|
||||
|
||||
def check_compatibility(version: tuple[int, ...], name: str) -> None:
|
||||
def check_compatibility(version: Tuple[int, ...], name: str) -> None:
|
||||
"""Raises errors or warns if called with an incompatible Wheel-Version.
|
||||
|
||||
pip should refuse to install a Wheel-Version that's a major series
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue