Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

View file

@ -1 +1,2 @@
"""For modules related to installing packages."""
"""For modules related to installing packages.
"""

View file

@ -0,0 +1,46 @@
"""Legacy editable installation process, i.e. `setup.py develop`.
"""
import logging
from typing import Optional, Sequence
from pip._internal.build_env import BuildEnvironment
from pip._internal.utils.logging import indent_log
from pip._internal.utils.setuptools_build import make_setuptools_develop_args
from pip._internal.utils.subprocess import call_subprocess
logger = logging.getLogger(__name__)
def install_editable(
*,
global_options: Sequence[str],
prefix: Optional[str],
home: Optional[str],
use_user_site: bool,
name: str,
setup_py_path: str,
isolated: bool,
build_env: BuildEnvironment,
unpacked_source_directory: str,
) -> None:
"""Install a package in editable mode. Most arguments are pass-through
to setuptools.
"""
logger.info("Running setup.py develop for %s", name)
args = make_setuptools_develop_args(
setup_py_path,
global_options=global_options,
no_user_config=isolated,
prefix=prefix,
home=home,
use_user_site=use_user_site,
)
with indent_log():
with build_env:
call_subprocess(
args,
command_desc="python setup.py develop",
cwd=unpacked_source_directory,
)

View file

@ -1,6 +1,5 @@
"""Support for installing and building the "wheel" binary package format."""
from __future__ import annotations
"""Support for installing and building the "wheel" binary package format.
"""
import collections
import compileall
@ -12,19 +11,26 @@ import os.path
import re
import shutil
import sys
import textwrap
import warnings
from base64 import urlsafe_b64encode
from collections.abc import Generator, Iterable, Iterator, Sequence
from email.message import Message
from itertools import chain, filterfalse, starmap
from typing import (
IO,
TYPE_CHECKING,
Any,
BinaryIO,
Callable,
Dict,
Generator,
Iterable,
Iterator,
List,
NewType,
Protocol,
Optional,
Sequence,
Set,
Tuple,
Union,
cast,
)
@ -44,7 +50,7 @@ from pip._internal.metadata import (
from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, DirectUrl
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
from pip._internal.utils.filesystem import adjacent_tmp_file, replace
from pip._internal.utils.misc import StreamWrapper, ensure_dir, hash_file, partition
from pip._internal.utils.misc import captured_stdout, ensure_dir, hash_file, partition
from pip._internal.utils.unpacking import (
current_umask,
is_within_directory,
@ -53,30 +59,32 @@ from pip._internal.utils.unpacking import (
)
from pip._internal.utils.wheel import parse_wheel
if TYPE_CHECKING:
from typing import Protocol
class File(Protocol):
src_record_path: RecordPath
dest_path: str
changed: bool
class File(Protocol):
src_record_path: "RecordPath"
dest_path: str
changed: bool
def save(self) -> None:
pass
def save(self) -> None:
pass
logger = logging.getLogger(__name__)
RecordPath = NewType("RecordPath", str)
InstalledCSVRow = tuple[RecordPath, str, Union[int, str]]
InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]]
def rehash(path: str, blocksize: int = 1 << 20) -> tuple[str, str]:
def rehash(path: str, blocksize: int = 1 << 20) -> Tuple[str, str]:
"""Return (encoded_digest, length) for path using hashlib.sha256()"""
h, length = hash_file(path, blocksize)
digest = "sha256=" + urlsafe_b64encode(h.digest()).decode("latin1").rstrip("=")
return (digest, str(length))
def csv_io_kwargs(mode: str) -> dict[str, Any]:
def csv_io_kwargs(mode: str) -> Dict[str, Any]:
"""Return keyword arguments to properly open a CSV file
in the given mode.
"""
@ -107,7 +115,7 @@ def wheel_root_is_purelib(metadata: Message) -> bool:
return metadata.get("Root-Is-Purelib", "").lower() == "true"
def get_entrypoints(dist: BaseDistribution) -> tuple[dict[str, str], dict[str, str]]:
def get_entrypoints(dist: BaseDistribution) -> Tuple[Dict[str, str], Dict[str, str]]:
console_scripts = {}
gui_scripts = {}
for entry_point in dist.iter_entry_points():
@ -118,7 +126,7 @@ def get_entrypoints(dist: BaseDistribution) -> tuple[dict[str, str], dict[str, s
return console_scripts, gui_scripts
def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> str | None:
def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
"""Determine if any scripts are not on PATH and format a warning.
Returns a warning message if one or more scripts are not on PATH,
otherwise None.
@ -127,7 +135,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> str | None:
return None
# Group scripts by the path they were installed in
grouped_by_dir: dict[str, set[str]] = collections.defaultdict(set)
grouped_by_dir: Dict[str, Set[str]] = collections.defaultdict(set)
for destfile in scripts:
parent_dir = os.path.dirname(destfile)
script_name = os.path.basename(destfile)
@ -143,7 +151,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> str | None:
not_warn_dirs.append(
os.path.normcase(os.path.normpath(os.path.dirname(sys.executable)))
)
warn_for: dict[str, set[str]] = {
warn_for: Dict[str, Set[str]] = {
parent_dir: scripts
for parent_dir, scripts in grouped_by_dir.items()
if os.path.normcase(os.path.normpath(parent_dir)) not in not_warn_dirs
@ -154,7 +162,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> str | None:
# Format a message
msg_lines = []
for parent_dir, dir_scripts in warn_for.items():
sorted_scripts: list[str] = sorted(dir_scripts)
sorted_scripts: List[str] = sorted(dir_scripts)
if len(sorted_scripts) == 1:
start_text = f"script {sorted_scripts[0]} is"
else:
@ -192,7 +200,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> str | None:
def _normalized_outrows(
outrows: Iterable[InstalledCSVRow],
) -> list[tuple[str, str, str]]:
) -> List[Tuple[str, str, str]]:
"""Normalize the given rows of a RECORD file.
Items in each row are converted into str. Rows are then sorted to make
@ -231,17 +239,17 @@ def _fs_to_record_path(path: str, lib_dir: str) -> RecordPath:
def get_csv_rows_for_installed(
old_csv_rows: list[list[str]],
installed: dict[RecordPath, RecordPath],
changed: set[RecordPath],
generated: list[str],
old_csv_rows: List[List[str]],
installed: Dict[RecordPath, RecordPath],
changed: Set[RecordPath],
generated: List[str],
lib_dir: str,
) -> list[InstalledCSVRow]:
) -> List[InstalledCSVRow]:
"""
:param installed: A map from archive RECORD path to installation RECORD
path.
"""
installed_rows: list[InstalledCSVRow] = []
installed_rows: List[InstalledCSVRow] = []
for row in old_csv_rows:
if len(row) > 3:
logger.warning("RECORD line has more than three elements: %s", row)
@ -262,7 +270,7 @@ def get_csv_rows_for_installed(
]
def get_console_script_specs(console: dict[str, str]) -> list[str]:
def get_console_script_specs(console: Dict[str, str]) -> List[str]:
"""
Given the mapping from entrypoint name to callable, return the relevant
console script specs.
@ -280,15 +288,17 @@ def get_console_script_specs(console: dict[str, str]) -> list[str]:
# the wheel metadata at build time, and so if the wheel is installed with
# a *different* version of Python the entry points will be wrong. The
# correct fix for this is to enhance the metadata to be able to describe
# such versioned entry points.
# Currently, projects using versioned entry points will either have
# such versioned entry points, but that won't happen till Metadata 2.0 is
# available.
# In the meantime, projects using versioned entry points will either have
# incorrect versioned entry points, or they will not be able to distribute
# "universal" wheels (i.e., they will need a wheel per Python version).
#
# Because setuptools and pip are bundled with _ensurepip and virtualenv,
# we need to use universal wheels. As a workaround, we
# we need to use universal wheels. So, as a stopgap until Metadata 2.0, we
# override the versioned entry points in the wheel and generate the
# correct ones.
# correct ones. This code is purely a short-term measure until Metadata 2.0
# is available.
#
# To add the level of hack in this section of code, in order to support
# ensurepip this code will look for an ``ENSUREPIP_OPTIONS`` environment
@ -350,6 +360,12 @@ class ZipBackedFile:
return self._zip_file.getinfo(self.src_record_path)
def save(self) -> None:
# directory creation is lazy and after file filtering
# to ensure we don't install empty dirs; empty dirs can't be
# uninstalled.
parent_dir = os.path.dirname(self.dest_path)
ensure_dir(parent_dir)
# When we open the output file below, any existing file is truncated
# before we start writing the new contents. This is fine in most
# cases, but can cause a segfault if pip has loaded a shared
@ -363,20 +379,16 @@ class ZipBackedFile:
zipinfo = self._getinfo()
# optimization: the file is created by open(),
# skip the decompression when there is 0 bytes to decompress.
with open(self.dest_path, "wb") as dest:
if zipinfo.file_size > 0:
with self._zip_file.open(zipinfo) as f:
blocksize = min(zipinfo.file_size, 1024 * 1024)
shutil.copyfileobj(f, dest, blocksize)
with self._zip_file.open(zipinfo) as f:
with open(self.dest_path, "wb") as dest:
shutil.copyfileobj(f, dest)
if zip_item_is_executable(zipinfo):
set_extracted_file_to_default_mode_plus_executable(self.dest_path)
class ScriptFile:
def __init__(self, file: File) -> None:
def __init__(self, file: "File") -> None:
self._file = file
self.src_record_path = self._file.src_record_path
self.dest_path = self._file.dest_path
@ -391,7 +403,7 @@ class MissingCallableSuffix(InstallationError):
def __init__(self, entry_point: str) -> None:
super().__init__(
f"Invalid script entry point: {entry_point} - A callable "
"suffix is required. See https://packaging.python.org/"
"suffix is required. Cf https://packaging.python.org/"
"specifications/entry-points/#use-for-scripts for more "
"information."
)
@ -404,34 +416,21 @@ def _raise_for_invalid_entrypoint(specification: str) -> None:
class PipScriptMaker(ScriptMaker):
# Override distlib's default script template with one that
# doesn't import `re` module, allowing scripts to load faster.
script_template = textwrap.dedent(
"""\
import sys
from %(module)s import %(import_name)s
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(%(func)s())
"""
)
def make(
self, specification: str, options: dict[str, Any] | None = None
) -> list[str]:
self, specification: str, options: Optional[Dict[str, Any]] = None
) -> List[str]:
_raise_for_invalid_entrypoint(specification)
return super().make(specification, options)
def _install_wheel( # noqa: C901, PLR0915 function is too long
def _install_wheel(
name: str,
wheel_zip: ZipFile,
wheel_path: str,
scheme: Scheme,
pycompile: bool = True,
warn_script_location: bool = True,
direct_url: DirectUrl | None = None,
direct_url: Optional[DirectUrl] = None,
requested: bool = False,
) -> None:
"""Install a wheel.
@ -460,9 +459,9 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
# installed = files copied from the wheel to the destination
# changed = files changed while installing (scripts #! line typically)
# generated = files newly generated during the install (script wrappers)
installed: dict[RecordPath, RecordPath] = {}
changed: set[RecordPath] = set()
generated: list[str] = []
installed: Dict[RecordPath, RecordPath] = {}
changed: Set[RecordPath] = set()
generated: List[str] = []
def record_installed(
srcfile: RecordPath, destfile: str, modified: bool = False
@ -488,8 +487,8 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
def root_scheme_file_maker(
zip_file: ZipFile, dest: str
) -> Callable[[RecordPath], File]:
def make_root_scheme_file(record_path: RecordPath) -> File:
) -> Callable[[RecordPath], "File"]:
def make_root_scheme_file(record_path: RecordPath) -> "File":
normed_path = os.path.normpath(record_path)
dest_path = os.path.join(dest, normed_path)
assert_no_path_traversal(dest, dest_path)
@ -499,18 +498,18 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
def data_scheme_file_maker(
zip_file: ZipFile, scheme: Scheme
) -> Callable[[RecordPath], File]:
) -> Callable[[RecordPath], "File"]:
scheme_paths = {key: getattr(scheme, key) for key in SCHEME_KEYS}
def make_data_scheme_file(record_path: RecordPath) -> File:
def make_data_scheme_file(record_path: RecordPath) -> "File":
normed_path = os.path.normpath(record_path)
try:
_, scheme_key, dest_subpath = normed_path.split(os.path.sep, 2)
except ValueError:
message = (
f"Unexpected file in {wheel_path}: {record_path!r}. .data directory"
" contents should be named like: '<scheme key>/<path>'."
)
"Unexpected file in {}: {!r}. .data directory contents"
" should be named like: '<scheme key>/<path>'."
).format(wheel_path, record_path)
raise InstallationError(message)
try:
@ -518,11 +517,10 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
except KeyError:
valid_scheme_keys = ", ".join(sorted(scheme_paths))
message = (
f"Unknown scheme key used in {wheel_path}: {scheme_key} "
f"(for file {record_path!r}). .data directory contents "
f"should be in subdirectories named with a valid scheme "
f"key ({valid_scheme_keys})"
)
"Unknown scheme key used in {}: {} (for file {!r}). .data"
" directory contents should be in subdirectories named"
" with a valid scheme key ({})"
).format(wheel_path, scheme_key, record_path, valid_scheme_keys)
raise InstallationError(message)
dest_path = os.path.join(scheme_path, dest_subpath)
@ -534,7 +532,7 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
def is_data_scheme_path(path: RecordPath) -> bool:
return path.split("/", 1)[0].endswith(".data")
paths = cast(list[RecordPath], wheel_zip.namelist())
paths = cast(List[RecordPath], wheel_zip.namelist())
file_paths = filterfalse(is_dir_path, paths)
root_scheme_paths, data_scheme_paths = partition(is_data_scheme_path, file_paths)
@ -560,7 +558,7 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
)
console, gui = get_entrypoints(distribution)
def is_entrypoint_wrapper(file: File) -> bool:
def is_entrypoint_wrapper(file: "File") -> bool:
# EP, EP.exe and EP-script.py are scripts generated for
# entry point EP by setuptools
path = file.dest_path
@ -583,15 +581,7 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
script_scheme_files = map(ScriptFile, script_scheme_files)
files = chain(files, script_scheme_files)
existing_parents = set()
for file in files:
# directory creation is lazy and after file filtering
# to ensure we don't install empty dirs; empty dirs can't be
# uninstalled.
parent_dir = os.path.dirname(file.dest_path)
if parent_dir not in existing_parents:
ensure_dir(parent_dir)
existing_parents.add(parent_dir)
file.save()
record_installed(file.src_record_path, file.dest_path, file.changed)
@ -614,9 +604,7 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
# Compile all of the pyc files for the installed files
if pycompile:
with contextlib.redirect_stdout(
StreamWrapper.from_stream(sys.stdout)
) as stdout:
with captured_stdout() as stdout:
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
for path in pyc_source_file_paths():
@ -729,7 +717,7 @@ def install_wheel(
req_description: str,
pycompile: bool = True,
warn_script_location: bool = True,
direct_url: DirectUrl | None = None,
direct_url: Optional[DirectUrl] = None,
requested: bool = False,
) -> None:
with ZipFile(wheel_path, allowZip64=True) as z: