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.
|
|
@ -1,12 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
from collections.abc import Generator
|
||||
from types import TracebackType
|
||||
from typing import Dict, Generator, Optional, Set, Type, Union
|
||||
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
|
|
@ -19,7 +18,7 @@ def update_env_context_manager(**changes: str) -> Generator[None, None, None]:
|
|||
|
||||
# Save values from the target and change them.
|
||||
non_existent_marker = object()
|
||||
saved_values: dict[str, object | str] = {}
|
||||
saved_values: Dict[str, Union[object, str]] = {}
|
||||
for name, new_value in changes.items():
|
||||
try:
|
||||
saved_values[name] = target[name]
|
||||
|
|
@ -40,7 +39,7 @@ def update_env_context_manager(**changes: str) -> Generator[None, None, None]:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def get_build_tracker() -> Generator[BuildTracker, None, None]:
|
||||
def get_build_tracker() -> Generator["BuildTracker", None, None]:
|
||||
root = os.environ.get("PIP_BUILD_TRACKER")
|
||||
with contextlib.ExitStack() as ctx:
|
||||
if root is None:
|
||||
|
|
@ -67,18 +66,18 @@ class BuildTracker:
|
|||
|
||||
def __init__(self, root: str) -> None:
|
||||
self._root = root
|
||||
self._entries: dict[TrackerId, InstallRequirement] = {}
|
||||
self._entries: Dict[TrackerId, InstallRequirement] = {}
|
||||
logger.debug("Created build tracker: %s", self._root)
|
||||
|
||||
def __enter__(self) -> BuildTracker:
|
||||
def __enter__(self) -> "BuildTracker":
|
||||
logger.debug("Entered build tracker: %s", self._root)
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
) -> None:
|
||||
self.cleanup()
|
||||
|
||||
|
|
@ -100,7 +99,7 @@ class BuildTracker:
|
|||
except FileNotFoundError:
|
||||
pass
|
||||
else:
|
||||
message = f"{req.link} is already being built: {contents}"
|
||||
message = "{} is already being built: {}".format(req.link, contents)
|
||||
raise LookupError(message)
|
||||
|
||||
# If we're here, req should really not be building already.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Metadata generation logic for source distributions."""
|
||||
"""Metadata generation logic for source distributions.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Metadata generation logic for source distributions."""
|
||||
"""Metadata generation logic for source distributions.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
|
|
@ -37,5 +38,4 @@ def generate_editable_metadata(
|
|||
except InstallationSubprocessError as error:
|
||||
raise MetadataGenerationFailed(package_details=details) from error
|
||||
|
||||
assert distinfo_dir is not None
|
||||
return os.path.join(metadata_dir, distinfo_dir)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
"""Metadata generation logic for legacy source distributions.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.cli.spinners import open_spinner
|
||||
from pip._internal.exceptions import (
|
||||
InstallationError,
|
||||
InstallationSubprocessError,
|
||||
MetadataGenerationFailed,
|
||||
)
|
||||
from pip._internal.utils.setuptools_build import make_setuptools_egg_info_args
|
||||
from pip._internal.utils.subprocess import call_subprocess
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _find_egg_info(directory: str) -> str:
|
||||
"""Find an .egg-info subdirectory in `directory`."""
|
||||
filenames = [f for f in os.listdir(directory) if f.endswith(".egg-info")]
|
||||
|
||||
if not filenames:
|
||||
raise InstallationError(f"No .egg-info directory found in {directory}")
|
||||
|
||||
if len(filenames) > 1:
|
||||
raise InstallationError(
|
||||
"More than one .egg-info directory found in {}".format(directory)
|
||||
)
|
||||
|
||||
return os.path.join(directory, filenames[0])
|
||||
|
||||
|
||||
def generate_metadata(
|
||||
build_env: BuildEnvironment,
|
||||
setup_py_path: str,
|
||||
source_dir: str,
|
||||
isolated: bool,
|
||||
details: str,
|
||||
) -> str:
|
||||
"""Generate metadata using setup.py-based defacto mechanisms.
|
||||
|
||||
Returns the generated metadata directory.
|
||||
"""
|
||||
logger.debug(
|
||||
"Running setup.py (path:%s) egg_info for package %s",
|
||||
setup_py_path,
|
||||
details,
|
||||
)
|
||||
|
||||
egg_info_dir = TempDirectory(kind="pip-egg-info", globally_managed=True).path
|
||||
|
||||
args = make_setuptools_egg_info_args(
|
||||
setup_py_path,
|
||||
egg_info_dir=egg_info_dir,
|
||||
no_user_config=isolated,
|
||||
)
|
||||
|
||||
with build_env:
|
||||
with open_spinner("Preparing metadata (setup.py)") as spinner:
|
||||
try:
|
||||
call_subprocess(
|
||||
args,
|
||||
cwd=source_dir,
|
||||
command_desc="python setup.py egg_info",
|
||||
spinner=spinner,
|
||||
)
|
||||
except InstallationSubprocessError as error:
|
||||
raise MetadataGenerationFailed(package_details=details) from error
|
||||
|
||||
# Return the .egg-info directory.
|
||||
return _find_egg_info(egg_info_dir)
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
|
||||
|
||||
|
|
@ -14,25 +13,25 @@ def build_wheel_pep517(
|
|||
name: str,
|
||||
backend: BuildBackendHookCaller,
|
||||
metadata_directory: str,
|
||||
wheel_directory: str,
|
||||
) -> str | None:
|
||||
tempd: str,
|
||||
) -> Optional[str]:
|
||||
"""Build one InstallRequirement using the PEP 517 build process.
|
||||
|
||||
Returns path to wheel if successfully built. Otherwise, returns None.
|
||||
"""
|
||||
assert metadata_directory is not None
|
||||
try:
|
||||
logger.debug("Destination directory: %s", wheel_directory)
|
||||
logger.debug("Destination directory: %s", tempd)
|
||||
|
||||
runner = runner_with_spinner_message(
|
||||
f"Building wheel for {name} (pyproject.toml)"
|
||||
)
|
||||
with backend.subprocess_runner(runner):
|
||||
wheel_name = backend.build_wheel(
|
||||
wheel_directory=wheel_directory,
|
||||
tempd,
|
||||
metadata_directory=metadata_directory,
|
||||
)
|
||||
except Exception:
|
||||
logger.error("Failed building wheel for %s", name)
|
||||
return None
|
||||
return os.path.join(wheel_directory, wheel_name)
|
||||
return os.path.join(tempd, wheel_name)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller, HookMissing
|
||||
|
||||
|
|
@ -14,15 +13,15 @@ def build_wheel_editable(
|
|||
name: str,
|
||||
backend: BuildBackendHookCaller,
|
||||
metadata_directory: str,
|
||||
wheel_directory: str,
|
||||
) -> str | None:
|
||||
tempd: str,
|
||||
) -> Optional[str]:
|
||||
"""Build one InstallRequirement using the PEP 660 build process.
|
||||
|
||||
Returns path to wheel if successfully built. Otherwise, returns None.
|
||||
"""
|
||||
assert metadata_directory is not None
|
||||
try:
|
||||
logger.debug("Destination directory: %s", wheel_directory)
|
||||
logger.debug("Destination directory: %s", tempd)
|
||||
|
||||
runner = runner_with_spinner_message(
|
||||
f"Building editable for {name} (pyproject.toml)"
|
||||
|
|
@ -30,7 +29,7 @@ def build_wheel_editable(
|
|||
with backend.subprocess_runner(runner):
|
||||
try:
|
||||
wheel_name = backend.build_editable(
|
||||
wheel_directory=wheel_directory,
|
||||
tempd,
|
||||
metadata_directory=metadata_directory,
|
||||
)
|
||||
except HookMissing as e:
|
||||
|
|
@ -44,4 +43,4 @@ def build_wheel_editable(
|
|||
except Exception:
|
||||
logger.error("Failed building editable for %s", name)
|
||||
return None
|
||||
return os.path.join(wheel_directory, wheel_name)
|
||||
return os.path.join(tempd, wheel_name)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
import logging
|
||||
import os.path
|
||||
from typing import List, Optional
|
||||
|
||||
from pip._internal.cli.spinners import open_spinner
|
||||
from pip._internal.utils.setuptools_build import make_setuptools_bdist_wheel_args
|
||||
from pip._internal.utils.subprocess import call_subprocess, format_command_args
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def format_command_result(
|
||||
command_args: List[str],
|
||||
command_output: str,
|
||||
) -> str:
|
||||
"""Format command information for logging."""
|
||||
command_desc = format_command_args(command_args)
|
||||
text = f"Command arguments: {command_desc}\n"
|
||||
|
||||
if not command_output:
|
||||
text += "Command output: None"
|
||||
elif logger.getEffectiveLevel() > logging.DEBUG:
|
||||
text += "Command output: [use --verbose to show]"
|
||||
else:
|
||||
if not command_output.endswith("\n"):
|
||||
command_output += "\n"
|
||||
text += f"Command output:\n{command_output}"
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def get_legacy_build_wheel_path(
|
||||
names: List[str],
|
||||
temp_dir: str,
|
||||
name: str,
|
||||
command_args: List[str],
|
||||
command_output: str,
|
||||
) -> Optional[str]:
|
||||
"""Return the path to the wheel in the temporary build directory."""
|
||||
# Sort for determinism.
|
||||
names = sorted(names)
|
||||
if not names:
|
||||
msg = ("Legacy build of wheel for {!r} created no files.\n").format(name)
|
||||
msg += format_command_result(command_args, command_output)
|
||||
logger.warning(msg)
|
||||
return None
|
||||
|
||||
if len(names) > 1:
|
||||
msg = (
|
||||
"Legacy build of wheel for {!r} created more than one file.\n"
|
||||
"Filenames (choosing first): {}\n"
|
||||
).format(name, names)
|
||||
msg += format_command_result(command_args, command_output)
|
||||
logger.warning(msg)
|
||||
|
||||
return os.path.join(temp_dir, names[0])
|
||||
|
||||
|
||||
def build_wheel_legacy(
|
||||
name: str,
|
||||
setup_py_path: str,
|
||||
source_dir: str,
|
||||
global_options: List[str],
|
||||
build_options: List[str],
|
||||
tempd: str,
|
||||
) -> Optional[str]:
|
||||
"""Build one unpacked package using the "legacy" build process.
|
||||
|
||||
Returns path to wheel if successfully built. Otherwise, returns None.
|
||||
"""
|
||||
wheel_args = make_setuptools_bdist_wheel_args(
|
||||
setup_py_path,
|
||||
global_options=global_options,
|
||||
build_options=build_options,
|
||||
destination_dir=tempd,
|
||||
)
|
||||
|
||||
spin_message = f"Building wheel for {name} (setup.py)"
|
||||
with open_spinner(spin_message) as spinner:
|
||||
logger.debug("Destination directory: %s", tempd)
|
||||
|
||||
try:
|
||||
output = call_subprocess(
|
||||
wheel_args,
|
||||
command_desc="python setup.py bdist_wheel",
|
||||
cwd=source_dir,
|
||||
spinner=spinner,
|
||||
)
|
||||
except Exception:
|
||||
spinner.finish("error")
|
||||
logger.error("Failed building wheel for %s", name)
|
||||
return None
|
||||
|
||||
names = os.listdir(tempd)
|
||||
wheel_path = get_legacy_build_wheel_path(
|
||||
names=names,
|
||||
temp_dir=tempd,
|
||||
name=name,
|
||||
command_args=wheel_args,
|
||||
command_output=output,
|
||||
)
|
||||
return wheel_path
|
||||
|
|
@ -1,47 +1,40 @@
|
|||
"""Validation of dependencies of packages"""
|
||||
|
||||
from __future__ import annotations
|
||||
"""Validation of dependencies of packages
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections.abc import Generator, Iterable
|
||||
from contextlib import suppress
|
||||
from email.parser import Parser
|
||||
from functools import reduce
|
||||
from typing import (
|
||||
Callable,
|
||||
NamedTuple,
|
||||
)
|
||||
from typing import Callable, Dict, List, NamedTuple, Optional, Set, Tuple
|
||||
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.tags import Tag, parse_tag
|
||||
from pip._vendor.packaging.specifiers import LegacySpecifier
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pip._vendor.packaging.version import Version
|
||||
from pip._vendor.packaging.version import LegacyVersion
|
||||
|
||||
from pip._internal.distributions import make_distribution_for_install_requirement
|
||||
from pip._internal.metadata import get_default_environment
|
||||
from pip._internal.metadata.base import BaseDistribution
|
||||
from pip._internal.metadata.base import DistributionVersion
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.deprecation import deprecated
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PackageDetails(NamedTuple):
|
||||
version: Version
|
||||
dependencies: list[Requirement]
|
||||
version: DistributionVersion
|
||||
dependencies: List[Requirement]
|
||||
|
||||
|
||||
# Shorthands
|
||||
PackageSet = dict[NormalizedName, PackageDetails]
|
||||
Missing = tuple[NormalizedName, Requirement]
|
||||
Conflicting = tuple[NormalizedName, Version, Requirement]
|
||||
PackageSet = Dict[NormalizedName, PackageDetails]
|
||||
Missing = Tuple[NormalizedName, Requirement]
|
||||
Conflicting = Tuple[NormalizedName, DistributionVersion, Requirement]
|
||||
|
||||
MissingDict = dict[NormalizedName, list[Missing]]
|
||||
ConflictingDict = dict[NormalizedName, list[Conflicting]]
|
||||
CheckResult = tuple[MissingDict, ConflictingDict]
|
||||
ConflictDetails = tuple[PackageSet, CheckResult]
|
||||
MissingDict = Dict[NormalizedName, List[Missing]]
|
||||
ConflictingDict = Dict[NormalizedName, List[Conflicting]]
|
||||
CheckResult = Tuple[MissingDict, ConflictingDict]
|
||||
ConflictDetails = Tuple[PackageSet, CheckResult]
|
||||
|
||||
|
||||
def create_package_set_from_installed() -> tuple[PackageSet, bool]:
|
||||
def create_package_set_from_installed() -> Tuple[PackageSet, bool]:
|
||||
"""Converts a list of distributions into a PackageSet."""
|
||||
package_set = {}
|
||||
problems = False
|
||||
|
|
@ -53,13 +46,13 @@ def create_package_set_from_installed() -> tuple[PackageSet, bool]:
|
|||
package_set[name] = PackageDetails(dist.version, dependencies)
|
||||
except (OSError, ValueError) as e:
|
||||
# Don't crash on unreadable or broken metadata.
|
||||
logger.warning("Error parsing dependencies of %s: %s", name, e)
|
||||
logger.warning("Error parsing requirements for %s: %s", name, e)
|
||||
problems = True
|
||||
return package_set, problems
|
||||
|
||||
|
||||
def check_package_set(
|
||||
package_set: PackageSet, should_ignore: Callable[[str], bool] | None = None
|
||||
package_set: PackageSet, should_ignore: Optional[Callable[[str], bool]] = None
|
||||
) -> CheckResult:
|
||||
"""Check if a package set is consistent
|
||||
|
||||
|
|
@ -67,13 +60,15 @@ def check_package_set(
|
|||
package name and returns a boolean.
|
||||
"""
|
||||
|
||||
warn_legacy_versions_and_specifiers(package_set)
|
||||
|
||||
missing = {}
|
||||
conflicting = {}
|
||||
|
||||
for package_name, package_detail in package_set.items():
|
||||
# Info about dependencies of package_name
|
||||
missing_deps: set[Missing] = set()
|
||||
conflicting_deps: set[Conflicting] = set()
|
||||
missing_deps: Set[Missing] = set()
|
||||
conflicting_deps: Set[Conflicting] = set()
|
||||
|
||||
if should_ignore and should_ignore(package_name):
|
||||
continue
|
||||
|
|
@ -103,7 +98,7 @@ def check_package_set(
|
|||
return missing, conflicting
|
||||
|
||||
|
||||
def check_install_conflicts(to_install: list[InstallRequirement]) -> ConflictDetails:
|
||||
def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDetails:
|
||||
"""For checking if the dependency graph would be consistent after \
|
||||
installing given requirements
|
||||
"""
|
||||
|
|
@ -123,25 +118,9 @@ def check_install_conflicts(to_install: list[InstallRequirement]) -> ConflictDet
|
|||
)
|
||||
|
||||
|
||||
def check_unsupported(
|
||||
packages: Iterable[BaseDistribution],
|
||||
supported_tags: Iterable[Tag],
|
||||
) -> Generator[BaseDistribution, None, None]:
|
||||
for p in packages:
|
||||
with suppress(FileNotFoundError):
|
||||
wheel_file = p.read_text("WHEEL")
|
||||
wheel_tags: frozenset[Tag] = reduce(
|
||||
frozenset.union,
|
||||
map(parse_tag, Parser().parsestr(wheel_file).get_all("Tag", [])),
|
||||
frozenset(),
|
||||
)
|
||||
if wheel_tags.isdisjoint(supported_tags):
|
||||
yield p
|
||||
|
||||
|
||||
def _simulate_installation_of(
|
||||
to_install: list[InstallRequirement], package_set: PackageSet
|
||||
) -> set[NormalizedName]:
|
||||
to_install: List[InstallRequirement], package_set: PackageSet
|
||||
) -> Set[NormalizedName]:
|
||||
"""Computes the version of packages after installing to_install."""
|
||||
# Keep track of packages that were installed
|
||||
installed = set()
|
||||
|
|
@ -159,8 +138,8 @@ def _simulate_installation_of(
|
|||
|
||||
|
||||
def _create_whitelist(
|
||||
would_be_installed: set[NormalizedName], package_set: PackageSet
|
||||
) -> set[NormalizedName]:
|
||||
would_be_installed: Set[NormalizedName], package_set: PackageSet
|
||||
) -> Set[NormalizedName]:
|
||||
packages_affected = set(would_be_installed)
|
||||
|
||||
for package_name in package_set:
|
||||
|
|
@ -173,3 +152,36 @@ def _create_whitelist(
|
|||
break
|
||||
|
||||
return packages_affected
|
||||
|
||||
|
||||
def warn_legacy_versions_and_specifiers(package_set: PackageSet) -> None:
|
||||
for project_name, package_details in package_set.items():
|
||||
if isinstance(package_details.version, LegacyVersion):
|
||||
deprecated(
|
||||
reason=(
|
||||
f"{project_name} {package_details.version} "
|
||||
f"has a non-standard version number."
|
||||
),
|
||||
replacement=(
|
||||
f"to upgrade to a newer version of {project_name} "
|
||||
f"or contact the author to suggest that they "
|
||||
f"release a version with a conforming version number"
|
||||
),
|
||||
issue=12063,
|
||||
gone_in="24.1",
|
||||
)
|
||||
for dep in package_details.dependencies:
|
||||
if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier):
|
||||
deprecated(
|
||||
reason=(
|
||||
f"{project_name} {package_details.version} "
|
||||
f"has a non-standard dependency specifier {dep}."
|
||||
),
|
||||
replacement=(
|
||||
f"to upgrade to a newer version of {project_name} "
|
||||
f"or contact the author to suggest that they "
|
||||
f"release a version with a conforming dependency specifiers"
|
||||
),
|
||||
issue=12063,
|
||||
gone_in="24.1",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import logging
|
||||
import os
|
||||
from collections.abc import Container, Generator, Iterable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import NamedTuple
|
||||
from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set
|
||||
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pip._vendor.packaging.version import InvalidVersion
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
from pip._internal.exceptions import BadCommand, InstallationError
|
||||
from pip._internal.metadata import BaseDistribution, get_environment
|
||||
|
|
@ -24,19 +20,19 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class _EditableInfo(NamedTuple):
|
||||
requirement: str
|
||||
comments: list[str]
|
||||
comments: List[str]
|
||||
|
||||
|
||||
def freeze(
|
||||
requirement: list[str] | None = None,
|
||||
requirement: Optional[List[str]] = None,
|
||||
local_only: bool = False,
|
||||
user_only: bool = False,
|
||||
paths: list[str] | None = None,
|
||||
paths: Optional[List[str]] = None,
|
||||
isolated: bool = False,
|
||||
exclude_editable: bool = False,
|
||||
skip: Container[str] = (),
|
||||
) -> Generator[str, None, None]:
|
||||
installations: dict[str, FrozenRequirement] = {}
|
||||
installations: Dict[str, FrozenRequirement] = {}
|
||||
|
||||
dists = get_environment(paths).iter_installed_distributions(
|
||||
local_only=local_only,
|
||||
|
|
@ -54,10 +50,10 @@ def freeze(
|
|||
# should only be emitted once, even if the same option is in multiple
|
||||
# requirements files, so we need to keep track of what has been emitted
|
||||
# so that we don't emit it again if it's seen again
|
||||
emitted_options: set[str] = set()
|
||||
emitted_options: Set[str] = set()
|
||||
# keep track of which files a requirement is in so that we can
|
||||
# give an accurate warning if a requirement appears multiple times.
|
||||
req_files: dict[str, list[str]] = collections.defaultdict(list)
|
||||
req_files: Dict[str, List[str]] = collections.defaultdict(list)
|
||||
for req_file_path in requirement:
|
||||
with open(req_file_path) as req_file:
|
||||
for line in req_file:
|
||||
|
|
@ -86,7 +82,7 @@ def freeze(
|
|||
yield line
|
||||
continue
|
||||
|
||||
if line.startswith(("-e", "--editable")):
|
||||
if line.startswith("-e") or line.startswith("--editable"):
|
||||
if line.startswith("-e"):
|
||||
line = line[2:].strip()
|
||||
else:
|
||||
|
|
@ -149,13 +145,10 @@ def freeze(
|
|||
|
||||
|
||||
def _format_as_name_version(dist: BaseDistribution) -> str:
|
||||
try:
|
||||
dist_version = dist.version
|
||||
except InvalidVersion:
|
||||
# legacy version
|
||||
return f"{dist.raw_name}==={dist.raw_version}"
|
||||
else:
|
||||
dist_version = dist.version
|
||||
if isinstance(dist_version, Version):
|
||||
return f"{dist.raw_name}=={dist_version}"
|
||||
return f"{dist.raw_name}==={dist_version}"
|
||||
|
||||
|
||||
def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
|
||||
|
|
@ -224,19 +217,22 @@ def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
|
|||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FrozenRequirement:
|
||||
name: str
|
||||
req: str
|
||||
editable: bool
|
||||
comments: Iterable[str] = field(default_factory=tuple)
|
||||
|
||||
@property
|
||||
def canonical_name(self) -> NormalizedName:
|
||||
return canonicalize_name(self.name)
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
req: str,
|
||||
editable: bool,
|
||||
comments: Iterable[str] = (),
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.canonical_name = canonicalize_name(name)
|
||||
self.req = req
|
||||
self.editable = editable
|
||||
self.comments = comments
|
||||
|
||||
@classmethod
|
||||
def from_dist(cls, dist: BaseDistribution) -> FrozenRequirement:
|
||||
def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":
|
||||
editable = dist.editable
|
||||
if editable:
|
||||
req, comments = _get_editable_info(dist)
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
"""For modules related to installing packages."""
|
||||
"""For modules related to installing packages.
|
||||
"""
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
"""Prepares a distribution for installation"""
|
||||
"""Prepares a distribution for installation
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
from __future__ import annotations
|
||||
|
||||
import mimetypes
|
||||
import os
|
||||
import shutil
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Dict, Iterable, List, Optional
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pip._internal.build_env import BuildEnvironmentInstaller
|
||||
from pip._internal.distributions import make_distribution_for_install_requirement
|
||||
from pip._internal.distributions.installed import InstalledDistribution
|
||||
from pip._internal.exceptions import (
|
||||
|
|
@ -31,7 +28,7 @@ from pip._internal.metadata import BaseDistribution, get_metadata_distribution
|
|||
from pip._internal.models.direct_url import ArchiveInfo
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.models.wheel import Wheel
|
||||
from pip._internal.network.download import Downloader
|
||||
from pip._internal.network.download import BatchDownloader, Downloader
|
||||
from pip._internal.network.lazy_wheel import (
|
||||
HTTPRangeRequestUnsupported,
|
||||
dist_from_wheel_url,
|
||||
|
|
@ -56,16 +53,13 @@ from pip._internal.utils.temp_dir import TempDirectory
|
|||
from pip._internal.utils.unpacking import unpack_file
|
||||
from pip._internal.vcs import vcs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._internal.cli.progress_bars import BarType
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
def _get_prepared_distribution(
|
||||
req: InstallRequirement,
|
||||
build_tracker: BuildTracker,
|
||||
build_env_installer: BuildEnvironmentInstaller,
|
||||
finder: PackageFinder,
|
||||
build_isolation: bool,
|
||||
check_build_deps: bool,
|
||||
) -> BaseDistribution:
|
||||
|
|
@ -75,7 +69,7 @@ def _get_prepared_distribution(
|
|||
if tracker_id is not None:
|
||||
with build_tracker.track(req, tracker_id):
|
||||
abstract_dist.prepare_distribution_metadata(
|
||||
build_env_installer, build_isolation, check_build_deps
|
||||
finder, build_isolation, check_build_deps
|
||||
)
|
||||
return abstract_dist.get_metadata_distribution()
|
||||
|
||||
|
|
@ -86,26 +80,20 @@ def unpack_vcs_link(link: Link, location: str, verbosity: int) -> None:
|
|||
vcs_backend.unpack(location, url=hide_url(link.url), verbosity=verbosity)
|
||||
|
||||
|
||||
@dataclass
|
||||
class File:
|
||||
path: str
|
||||
content_type: str | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.content_type is None:
|
||||
# Try to guess the file's MIME type. If the system MIME tables
|
||||
# can't be loaded, give up.
|
||||
try:
|
||||
self.content_type = mimetypes.guess_type(self.path)[0]
|
||||
except OSError:
|
||||
pass
|
||||
def __init__(self, path: str, content_type: Optional[str]) -> None:
|
||||
self.path = path
|
||||
if content_type is None:
|
||||
self.content_type = mimetypes.guess_type(path)[0]
|
||||
else:
|
||||
self.content_type = content_type
|
||||
|
||||
|
||||
def get_http_url(
|
||||
link: Link,
|
||||
download: Downloader,
|
||||
download_dir: str | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
download_dir: Optional[str] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
) -> File:
|
||||
temp_dir = TempDirectory(kind="unpack", globally_managed=True)
|
||||
# If a download dir is specified, is the file already downloaded there?
|
||||
|
|
@ -126,7 +114,7 @@ def get_http_url(
|
|||
|
||||
|
||||
def get_file_url(
|
||||
link: Link, download_dir: str | None = None, hashes: Hashes | None = None
|
||||
link: Link, download_dir: Optional[str] = None, hashes: Optional[Hashes] = None
|
||||
) -> File:
|
||||
"""Get file and optionally check its hash."""
|
||||
# If a download dir is specified, is the file already there and valid?
|
||||
|
|
@ -154,9 +142,9 @@ def unpack_url(
|
|||
location: str,
|
||||
download: Downloader,
|
||||
verbosity: int,
|
||||
download_dir: str | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
) -> File | None:
|
||||
download_dir: Optional[str] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
) -> Optional[File]:
|
||||
"""Unpack link into location, downloading if required.
|
||||
|
||||
:param hashes: A Hashes object, one of whose embedded hashes must match,
|
||||
|
|
@ -195,9 +183,9 @@ def unpack_url(
|
|||
def _check_download_dir(
|
||||
link: Link,
|
||||
download_dir: str,
|
||||
hashes: Hashes | None,
|
||||
hashes: Optional[Hashes],
|
||||
warn_on_hash_mismatch: bool = True,
|
||||
) -> str | None:
|
||||
) -> Optional[str]:
|
||||
"""Check download_dir for previously downloaded file with correct hash
|
||||
If a correct file is found return its path else None
|
||||
"""
|
||||
|
|
@ -225,25 +213,22 @@ def _check_download_dir(
|
|||
class RequirementPreparer:
|
||||
"""Prepares a Requirement"""
|
||||
|
||||
def __init__( # noqa: PLR0913 (too many parameters)
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
build_dir: str,
|
||||
download_dir: str | None,
|
||||
download_dir: Optional[str],
|
||||
src_dir: str,
|
||||
build_isolation: bool,
|
||||
build_isolation_installer: BuildEnvironmentInstaller,
|
||||
check_build_deps: bool,
|
||||
build_tracker: BuildTracker,
|
||||
session: PipSession,
|
||||
progress_bar: BarType,
|
||||
progress_bar: str,
|
||||
finder: PackageFinder,
|
||||
require_hashes: bool,
|
||||
use_user_site: bool,
|
||||
lazy_wheel: bool,
|
||||
verbosity: int,
|
||||
legacy_resolver: bool,
|
||||
resume_retries: int,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
|
|
@ -251,7 +236,8 @@ class RequirementPreparer:
|
|||
self.build_dir = build_dir
|
||||
self.build_tracker = build_tracker
|
||||
self._session = session
|
||||
self._download = Downloader(session, progress_bar, resume_retries)
|
||||
self._download = Downloader(session, progress_bar)
|
||||
self._batch_download = BatchDownloader(session, progress_bar)
|
||||
self.finder = finder
|
||||
|
||||
# Where still-packed archives should be written to. If None, they are
|
||||
|
|
@ -260,7 +246,6 @@ class RequirementPreparer:
|
|||
|
||||
# Is build isolation allowed?
|
||||
self.build_isolation = build_isolation
|
||||
self.build_env_installer = build_isolation_installer
|
||||
|
||||
# Should check build dependencies?
|
||||
self.check_build_deps = check_build_deps
|
||||
|
|
@ -281,7 +266,7 @@ class RequirementPreparer:
|
|||
self.legacy_resolver = legacy_resolver
|
||||
|
||||
# Memoized downloaded files, as mapping of url: path.
|
||||
self._downloaded: dict[str, str] = {}
|
||||
self._downloaded: Dict[str, str] = {}
|
||||
|
||||
# Previous "header" printed for a link-based InstallRequirement
|
||||
self._previous_requirement_header = ("", "")
|
||||
|
|
@ -299,7 +284,7 @@ class RequirementPreparer:
|
|||
# would already be included if we used req directly)
|
||||
if req.req and req.comes_from:
|
||||
if isinstance(req.comes_from, str):
|
||||
comes_from: str | None = req.comes_from
|
||||
comes_from: Optional[str] = req.comes_from
|
||||
else:
|
||||
comes_from = req.comes_from.from_path()
|
||||
if comes_from:
|
||||
|
|
@ -371,7 +356,7 @@ class RequirementPreparer:
|
|||
def _fetch_metadata_only(
|
||||
self,
|
||||
req: InstallRequirement,
|
||||
) -> BaseDistribution | None:
|
||||
) -> Optional[BaseDistribution]:
|
||||
if self.legacy_resolver:
|
||||
logger.debug(
|
||||
"Metadata-only fetching is not used in the legacy resolver",
|
||||
|
|
@ -390,7 +375,7 @@ class RequirementPreparer:
|
|||
def _fetch_metadata_using_link_data_attr(
|
||||
self,
|
||||
req: InstallRequirement,
|
||||
) -> BaseDistribution | None:
|
||||
) -> Optional[BaseDistribution]:
|
||||
"""Fetch metadata from the data-dist-info-metadata attribute, if possible."""
|
||||
# (1) Get the link to the metadata file, if provided by the backend.
|
||||
metadata_link = req.link.metadata_link()
|
||||
|
|
@ -431,7 +416,7 @@ class RequirementPreparer:
|
|||
def _fetch_metadata_using_lazy_wheel(
|
||||
self,
|
||||
link: Link,
|
||||
) -> BaseDistribution | None:
|
||||
) -> Optional[BaseDistribution]:
|
||||
"""Fetch metadata using lazy wheel, if possible."""
|
||||
# --use-feature=fast-deps must be provided.
|
||||
if not self.use_lazy_wheel:
|
||||
|
|
@ -444,7 +429,7 @@ class RequirementPreparer:
|
|||
return None
|
||||
|
||||
wheel = Wheel(link.filename)
|
||||
name = wheel.name
|
||||
name = canonicalize_name(wheel.name)
|
||||
logger.info(
|
||||
"Obtaining dependency information from %s %s",
|
||||
name,
|
||||
|
|
@ -470,12 +455,15 @@ class RequirementPreparer:
|
|||
# Map each link to the requirement that owns it. This allows us to set
|
||||
# `req.local_file_path` on the appropriate requirement after passing
|
||||
# all the links at once into BatchDownloader.
|
||||
links_to_fully_download: dict[Link, InstallRequirement] = {}
|
||||
links_to_fully_download: Dict[Link, InstallRequirement] = {}
|
||||
for req in partially_downloaded_reqs:
|
||||
assert req.link
|
||||
links_to_fully_download[req.link] = req
|
||||
|
||||
batch_download = self._download.batch(links_to_fully_download.keys(), temp_dir)
|
||||
batch_download = self._batch_download(
|
||||
links_to_fully_download.keys(),
|
||||
temp_dir,
|
||||
)
|
||||
for link, (filepath, _) in batch_download:
|
||||
logger.debug("Downloading link %s to %s", link, filepath)
|
||||
req = links_to_fully_download[link]
|
||||
|
|
@ -531,12 +519,6 @@ class RequirementPreparer:
|
|||
metadata_dist = self._fetch_metadata_only(req)
|
||||
if metadata_dist is not None:
|
||||
req.needs_more_preparation = True
|
||||
req.set_dist(metadata_dist)
|
||||
# Ensure download_info is available even in dry-run mode
|
||||
if req.download_info is None:
|
||||
req.download_info = direct_url_from_link(
|
||||
req.link, req.source_dir
|
||||
)
|
||||
return metadata_dist
|
||||
|
||||
# None of the optimizations worked, fully prepare the requirement
|
||||
|
|
@ -558,7 +540,7 @@ class RequirementPreparer:
|
|||
|
||||
# Prepare requirements we found were already downloaded for some
|
||||
# reason. The other downloads will be completed separately.
|
||||
partially_downloaded_reqs: list[InstallRequirement] = []
|
||||
partially_downloaded_reqs: List[InstallRequirement] = []
|
||||
for req in reqs:
|
||||
if req.needs_more_preparation:
|
||||
partially_downloaded_reqs.append(req)
|
||||
|
|
@ -658,7 +640,7 @@ class RequirementPreparer:
|
|||
dist = _get_prepared_distribution(
|
||||
req,
|
||||
self.build_tracker,
|
||||
self.build_env_installer,
|
||||
self.finder,
|
||||
self.build_isolation,
|
||||
self.check_build_deps,
|
||||
)
|
||||
|
|
@ -714,7 +696,7 @@ class RequirementPreparer:
|
|||
dist = _get_prepared_distribution(
|
||||
req,
|
||||
self.build_tracker,
|
||||
self.build_env_installer,
|
||||
self.finder,
|
||||
self.build_isolation,
|
||||
self.check_build_deps,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue