mirror of
https://github.com/pre-commit/pre-commit-hooks.git
synced 2026-04-15 06:54:45 +00:00
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
This commit is contained in:
parent
72ad6dc953
commit
f4cd1ba0d6
813 changed files with 66015 additions and 58839 deletions
|
|
@ -1,27 +1,25 @@
|
|||
"""Metadata generation logic for source distributions.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.exceptions import (
|
||||
InstallationSubprocessError,
|
||||
MetadataGenerationFailed,
|
||||
)
|
||||
from pip._internal.exceptions import InstallationSubprocessError
|
||||
from pip._internal.exceptions import MetadataGenerationFailed
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
|
||||
def generate_metadata(
|
||||
build_env: BuildEnvironment, backend: Pep517HookCaller, details: str
|
||||
build_env: BuildEnvironment, backend: Pep517HookCaller, details: str,
|
||||
) -> str:
|
||||
"""Generate metadata using mechanisms described in PEP 517.
|
||||
|
||||
Returns the generated metadata directory.
|
||||
"""
|
||||
metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True)
|
||||
metadata_tmpdir = TempDirectory(kind='modern-metadata', globally_managed=True)
|
||||
|
||||
metadata_dir = metadata_tmpdir.path
|
||||
|
||||
|
|
@ -29,7 +27,7 @@ def generate_metadata(
|
|||
# Note that Pep517HookCaller implements a fallback for
|
||||
# prepare_metadata_for_build_wheel, so we don't have to
|
||||
# consider the possibility that this hook doesn't exist.
|
||||
runner = runner_with_spinner_message("Preparing metadata (pyproject.toml)")
|
||||
runner = runner_with_spinner_message('Preparing metadata (pyproject.toml)')
|
||||
with backend.subprocess_runner(runner):
|
||||
try:
|
||||
distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir)
|
||||
|
|
|
|||
|
|
@ -1,27 +1,25 @@
|
|||
"""Metadata generation logic for source distributions.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.exceptions import (
|
||||
InstallationSubprocessError,
|
||||
MetadataGenerationFailed,
|
||||
)
|
||||
from pip._internal.exceptions import InstallationSubprocessError
|
||||
from pip._internal.exceptions import MetadataGenerationFailed
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
|
||||
def generate_editable_metadata(
|
||||
build_env: BuildEnvironment, backend: Pep517HookCaller, details: str
|
||||
build_env: BuildEnvironment, backend: Pep517HookCaller, details: str,
|
||||
) -> str:
|
||||
"""Generate metadata using mechanisms described in PEP 660.
|
||||
|
||||
Returns the generated metadata directory.
|
||||
"""
|
||||
metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True)
|
||||
metadata_tmpdir = TempDirectory(kind='modern-metadata', globally_managed=True)
|
||||
|
||||
metadata_dir = metadata_tmpdir.path
|
||||
|
||||
|
|
@ -30,7 +28,7 @@ def generate_editable_metadata(
|
|||
# prepare_metadata_for_build_wheel/editable, so we don't have to
|
||||
# consider the possibility that this hook doesn't exist.
|
||||
runner = runner_with_spinner_message(
|
||||
"Preparing editable metadata (pyproject.toml)"
|
||||
'Preparing editable metadata (pyproject.toml)',
|
||||
)
|
||||
with backend.subprocess_runner(runner):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
"""Metadata generation logic for legacy source distributions.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
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.exceptions import InstallationError
|
||||
from pip._internal.exceptions import InstallationSubprocessError
|
||||
from pip._internal.exceptions import 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
|
||||
|
|
@ -20,14 +19,14 @@ 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")]
|
||||
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}")
|
||||
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)
|
||||
f'More than one .egg-info directory found in {directory}',
|
||||
)
|
||||
|
||||
return os.path.join(directory, filenames[0])
|
||||
|
|
@ -45,12 +44,12 @@ def generate_metadata(
|
|||
Returns the generated metadata directory.
|
||||
"""
|
||||
logger.debug(
|
||||
"Running setup.py (path:%s) egg_info for package %s",
|
||||
'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
|
||||
egg_info_dir = TempDirectory(kind='pip-egg-info', globally_managed=True).path
|
||||
|
||||
args = make_setuptools_egg_info_args(
|
||||
setup_py_path,
|
||||
|
|
@ -59,12 +58,12 @@ def generate_metadata(
|
|||
)
|
||||
|
||||
with build_env:
|
||||
with open_spinner("Preparing metadata (setup.py)") as spinner:
|
||||
with open_spinner('Preparing metadata (setup.py)') as spinner:
|
||||
try:
|
||||
call_subprocess(
|
||||
args,
|
||||
cwd=source_dir,
|
||||
command_desc="python setup.py egg_info",
|
||||
command_desc='python setup.py egg_info',
|
||||
spinner=spinner,
|
||||
)
|
||||
except InstallationSubprocessError as error:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -14,17 +15,17 @@ def build_wheel_pep517(
|
|||
backend: Pep517HookCaller,
|
||||
metadata_directory: str,
|
||||
tempd: str,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
"""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", tempd)
|
||||
logger.debug('Destination directory: %s', tempd)
|
||||
|
||||
runner = runner_with_spinner_message(
|
||||
f"Building wheel for {name} (pyproject.toml)"
|
||||
f'Building wheel for {name} (pyproject.toml)',
|
||||
)
|
||||
with backend.subprocess_runner(runner):
|
||||
wheel_name = backend.build_wheel(
|
||||
|
|
@ -32,6 +33,6 @@ def build_wheel_pep517(
|
|||
metadata_directory=metadata_directory,
|
||||
)
|
||||
except Exception:
|
||||
logger.error("Failed building wheel for %s", name)
|
||||
logger.error('Failed building wheel for %s', name)
|
||||
return None
|
||||
return os.path.join(tempd, wheel_name)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from pip._vendor.pep517.wrappers import HookMissing, Pep517HookCaller
|
||||
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
from pip._vendor.pep517.wrappers import HookMissing
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -14,17 +16,17 @@ def build_wheel_editable(
|
|||
backend: Pep517HookCaller,
|
||||
metadata_directory: str,
|
||||
tempd: str,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
"""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", tempd)
|
||||
logger.debug('Destination directory: %s', tempd)
|
||||
|
||||
runner = runner_with_spinner_message(
|
||||
f"Building editable for {name} (pyproject.toml)"
|
||||
f'Building editable for {name} (pyproject.toml)',
|
||||
)
|
||||
with backend.subprocess_runner(runner):
|
||||
try:
|
||||
|
|
@ -34,13 +36,13 @@ def build_wheel_editable(
|
|||
)
|
||||
except HookMissing as e:
|
||||
logger.error(
|
||||
"Cannot build editable %s because the build "
|
||||
"backend does not have the %s hook",
|
||||
'Cannot build editable %s because the build '
|
||||
'backend does not have the %s hook',
|
||||
name,
|
||||
e,
|
||||
)
|
||||
return None
|
||||
except Exception:
|
||||
logger.error("Failed building editable for %s", name)
|
||||
logger.error('Failed building editable for %s', name)
|
||||
return None
|
||||
return os.path.join(tempd, wheel_name)
|
||||
|
|
|
|||
|
|
@ -1,54 +1,58 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os.path
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
from typing import 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
|
||||
from pip._internal.utils.subprocess import call_subprocess
|
||||
from pip._internal.utils.subprocess import format_command_args
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def format_command_result(
|
||||
command_args: List[str],
|
||||
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"
|
||||
text = f'Command arguments: {command_desc}\n'
|
||||
|
||||
if not command_output:
|
||||
text += "Command output: None"
|
||||
text += 'Command output: None'
|
||||
elif logger.getEffectiveLevel() > logging.DEBUG:
|
||||
text += "Command output: [use --verbose to show]"
|
||||
text += 'Command output: [use --verbose to show]'
|
||||
else:
|
||||
if not command_output.endswith("\n"):
|
||||
command_output += "\n"
|
||||
text += f"Command output:\n{command_output}"
|
||||
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],
|
||||
names: list[str],
|
||||
temp_dir: str,
|
||||
name: str,
|
||||
command_args: List[str],
|
||||
command_args: list[str],
|
||||
command_output: str,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
"""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 = ('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"
|
||||
'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)
|
||||
|
|
@ -60,10 +64,10 @@ def build_wheel_legacy(
|
|||
name: str,
|
||||
setup_py_path: str,
|
||||
source_dir: str,
|
||||
global_options: List[str],
|
||||
build_options: List[str],
|
||||
global_options: list[str],
|
||||
build_options: list[str],
|
||||
tempd: str,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
"""Build one unpacked package using the "legacy" build process.
|
||||
|
||||
Returns path to wheel if successfully built. Otherwise, returns None.
|
||||
|
|
@ -75,20 +79,20 @@ def build_wheel_legacy(
|
|||
destination_dir=tempd,
|
||||
)
|
||||
|
||||
spin_message = f"Building wheel for {name} (setup.py)"
|
||||
spin_message = f'Building wheel for {name} (setup.py)'
|
||||
with open_spinner(spin_message) as spinner:
|
||||
logger.debug("Destination directory: %s", tempd)
|
||||
logger.debug('Destination directory: %s', tempd)
|
||||
|
||||
try:
|
||||
output = call_subprocess(
|
||||
wheel_args,
|
||||
command_desc="python setup.py bdist_wheel",
|
||||
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)
|
||||
spinner.finish('error')
|
||||
logger.error('Failed building wheel for %s', name)
|
||||
return None
|
||||
|
||||
names = os.listdir(tempd)
|
||||
|
|
|
|||
|
|
@ -1,23 +1,30 @@
|
|||
"""Validation of dependencies of packages
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Callable, Dict, List, NamedTuple, Optional, Set, Tuple
|
||||
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.distributions import make_distribution_for_install_requirement
|
||||
from pip._internal.metadata import get_default_environment
|
||||
from pip._internal.metadata.base import DistributionVersion
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PackageDetails(NamedTuple):
|
||||
version: DistributionVersion
|
||||
dependencies: List[Requirement]
|
||||
dependencies: list[Requirement]
|
||||
|
||||
|
||||
# Shorthands
|
||||
|
|
@ -31,7 +38,7 @@ 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
|
||||
|
|
@ -43,13 +50,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 requirements for %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: Optional[Callable[[str], bool]] = None
|
||||
package_set: PackageSet, should_ignore: Callable[[str], bool] | None = None,
|
||||
) -> CheckResult:
|
||||
"""Check if a package set is consistent
|
||||
|
||||
|
|
@ -62,8 +69,8 @@ def check_package_set(
|
|||
|
||||
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
|
||||
|
|
@ -93,7 +100,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
|
||||
"""
|
||||
|
|
@ -108,14 +115,14 @@ def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDet
|
|||
return (
|
||||
package_set,
|
||||
check_package_set(
|
||||
package_set, should_ignore=lambda name: name not in whitelist
|
||||
package_set, should_ignore=lambda name: name not in whitelist,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
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()
|
||||
|
|
@ -133,8 +140,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:
|
||||
|
|
|
|||
|
|
@ -1,38 +1,46 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import logging
|
||||
import os
|
||||
from typing import Container, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set
|
||||
from typing import Container
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
|
||||
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
|
||||
from pip._internal.req.constructors import (
|
||||
install_req_from_editable,
|
||||
install_req_from_line,
|
||||
)
|
||||
from pip._internal.exceptions import BadCommand
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.metadata import get_environment
|
||||
from pip._internal.req.constructors import install_req_from_editable
|
||||
from pip._internal.req.constructors import install_req_from_line
|
||||
from pip._internal.req.req_file import COMMENT_RE
|
||||
from pip._internal.utils.direct_url_helpers import direct_url_as_pep440_direct_reference
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _EditableInfo(NamedTuple):
|
||||
requirement: str
|
||||
comments: List[str]
|
||||
comments: list[str]
|
||||
|
||||
|
||||
def freeze(
|
||||
requirement: Optional[List[str]] = None,
|
||||
requirement: list[str] | None = None,
|
||||
local_only: bool = False,
|
||||
user_only: bool = False,
|
||||
paths: Optional[List[str]] = None,
|
||||
paths: list[str] | None = None,
|
||||
isolated: bool = False,
|
||||
exclude_editable: bool = False,
|
||||
skip: Container[str] = (),
|
||||
) -> Iterator[str]:
|
||||
installations: Dict[str, FrozenRequirement] = {}
|
||||
installations: dict[str, FrozenRequirement] = {}
|
||||
|
||||
dists = get_environment(paths).iter_installed_distributions(
|
||||
local_only=local_only,
|
||||
|
|
@ -50,30 +58,30 @@ 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:
|
||||
if (
|
||||
not line.strip()
|
||||
or line.strip().startswith("#")
|
||||
or line.startswith(
|
||||
not line.strip() or
|
||||
line.strip().startswith('#') or
|
||||
line.startswith(
|
||||
(
|
||||
"-r",
|
||||
"--requirement",
|
||||
"-f",
|
||||
"--find-links",
|
||||
"-i",
|
||||
"--index-url",
|
||||
"--pre",
|
||||
"--trusted-host",
|
||||
"--process-dependency-links",
|
||||
"--extra-index-url",
|
||||
"--use-feature",
|
||||
)
|
||||
'-r',
|
||||
'--requirement',
|
||||
'-f',
|
||||
'--find-links',
|
||||
'-i',
|
||||
'--index-url',
|
||||
'--pre',
|
||||
'--trusted-host',
|
||||
'--process-dependency-links',
|
||||
'--extra-index-url',
|
||||
'--use-feature',
|
||||
),
|
||||
)
|
||||
):
|
||||
line = line.rstrip()
|
||||
|
|
@ -82,31 +90,31 @@ def freeze(
|
|||
yield line
|
||||
continue
|
||||
|
||||
if line.startswith("-e") or line.startswith("--editable"):
|
||||
if line.startswith("-e"):
|
||||
if line.startswith('-e') or line.startswith('--editable'):
|
||||
if line.startswith('-e'):
|
||||
line = line[2:].strip()
|
||||
else:
|
||||
line = line[len("--editable") :].strip().lstrip("=")
|
||||
line = line[len('--editable'):].strip().lstrip('=')
|
||||
line_req = install_req_from_editable(
|
||||
line,
|
||||
isolated=isolated,
|
||||
)
|
||||
else:
|
||||
line_req = install_req_from_line(
|
||||
COMMENT_RE.sub("", line).strip(),
|
||||
COMMENT_RE.sub('', line).strip(),
|
||||
isolated=isolated,
|
||||
)
|
||||
|
||||
if not line_req.name:
|
||||
logger.info(
|
||||
"Skipping line in requirement file [%s] because "
|
||||
'Skipping line in requirement file [%s] because '
|
||||
"it's not clear what it would install: %s",
|
||||
req_file_path,
|
||||
line.strip(),
|
||||
)
|
||||
logger.info(
|
||||
" (add #egg=PackageName to the URL to avoid"
|
||||
" this warning)"
|
||||
' (add #egg=PackageName to the URL to avoid'
|
||||
' this warning)',
|
||||
)
|
||||
else:
|
||||
line_req_canonical_name = canonicalize_name(line_req.name)
|
||||
|
|
@ -115,10 +123,10 @@ def freeze(
|
|||
# but has been processed already
|
||||
if not req_files[line_req.name]:
|
||||
logger.warning(
|
||||
"Requirement file [%s] contains %s, but "
|
||||
"package %r is not installed",
|
||||
'Requirement file [%s] contains %s, but '
|
||||
'package %r is not installed',
|
||||
req_file_path,
|
||||
COMMENT_RE.sub("", line).strip(),
|
||||
COMMENT_RE.sub('', line).strip(),
|
||||
line_req.name,
|
||||
)
|
||||
else:
|
||||
|
|
@ -133,12 +141,12 @@ def freeze(
|
|||
for name, files in req_files.items():
|
||||
if len(files) > 1:
|
||||
logger.warning(
|
||||
"Requirement %s included multiple times [%s]",
|
||||
'Requirement %s included multiple times [%s]',
|
||||
name,
|
||||
", ".join(sorted(set(files))),
|
||||
', '.join(sorted(set(files))),
|
||||
)
|
||||
|
||||
yield ("## The following requirements were added by pip freeze:")
|
||||
yield ('## The following requirements were added by pip freeze:')
|
||||
for installation in sorted(installations.values(), key=lambda x: x.name.lower()):
|
||||
if installation.canonical_name not in skip:
|
||||
yield str(installation).rstrip()
|
||||
|
|
@ -146,8 +154,8 @@ def freeze(
|
|||
|
||||
def _format_as_name_version(dist: BaseDistribution) -> str:
|
||||
if isinstance(dist.version, Version):
|
||||
return f"{dist.raw_name}=={dist.version}"
|
||||
return f"{dist.raw_name}==={dist.version}"
|
||||
return f'{dist.raw_name}=={dist.version}'
|
||||
return f'{dist.raw_name}==={dist.version}'
|
||||
|
||||
|
||||
def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
|
||||
|
|
@ -172,7 +180,7 @@ def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
|
|||
)
|
||||
return _EditableInfo(
|
||||
requirement=location,
|
||||
comments=[f"# Editable install with no version control ({display})"],
|
||||
comments=[f'# Editable install with no version control ({display})'],
|
||||
)
|
||||
|
||||
vcs_name = type(vcs_backend).__name__
|
||||
|
|
@ -183,36 +191,36 @@ def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
|
|||
display = _format_as_name_version(dist)
|
||||
return _EditableInfo(
|
||||
requirement=location,
|
||||
comments=[f"# Editable {vcs_name} install with no remote ({display})"],
|
||||
comments=[f'# Editable {vcs_name} install with no remote ({display})'],
|
||||
)
|
||||
except RemoteNotValidError as ex:
|
||||
display = _format_as_name_version(dist)
|
||||
return _EditableInfo(
|
||||
requirement=location,
|
||||
comments=[
|
||||
f"# Editable {vcs_name} install ({display}) with either a deleted "
|
||||
f"local remote or invalid URI:",
|
||||
f'# Editable {vcs_name} install ({display}) with either a deleted '
|
||||
f'local remote or invalid URI:',
|
||||
f"# '{ex.url}'",
|
||||
],
|
||||
)
|
||||
except BadCommand:
|
||||
logger.warning(
|
||||
"cannot determine version of editable source in %s "
|
||||
"(%s command not found in path)",
|
||||
'cannot determine version of editable source in %s '
|
||||
'(%s command not found in path)',
|
||||
location,
|
||||
vcs_backend.name,
|
||||
)
|
||||
return _EditableInfo(requirement=location, comments=[])
|
||||
except InstallationError as exc:
|
||||
logger.warning("Error when trying to get requirement for VCS system %s", exc)
|
||||
logger.warning('Error when trying to get requirement for VCS system %s', exc)
|
||||
else:
|
||||
return _EditableInfo(requirement=req, comments=[])
|
||||
|
||||
logger.warning("Could not determine repository location of %s", location)
|
||||
logger.warning('Could not determine repository location of %s', location)
|
||||
|
||||
return _EditableInfo(
|
||||
requirement=location,
|
||||
comments=["## !! Could not determine repository location"],
|
||||
comments=['## !! Could not determine repository location'],
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -231,7 +239,7 @@ class FrozenRequirement:
|
|||
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)
|
||||
|
|
@ -250,5 +258,5 @@ class FrozenRequirement:
|
|||
def __str__(self) -> str:
|
||||
req = self.req
|
||||
if self.editable:
|
||||
req = f"-e {req}"
|
||||
return "\n".join(list(self.comments) + [str(req)]) + "\n"
|
||||
req = f'-e {req}'
|
||||
return '\n'.join(list(self.comments) + [str(req)]) + '\n'
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
"""For modules related to installing packages.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
"""Legacy editable installation process, i.e. `setup.py develop`.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import List, Optional, Sequence
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.utils.logging import indent_log
|
||||
|
|
@ -12,10 +16,10 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def install_editable(
|
||||
install_options: List[str],
|
||||
install_options: list[str],
|
||||
global_options: Sequence[str],
|
||||
prefix: Optional[str],
|
||||
home: Optional[str],
|
||||
prefix: str | None,
|
||||
home: str | None,
|
||||
use_user_site: bool,
|
||||
name: str,
|
||||
setup_py_path: str,
|
||||
|
|
@ -26,7 +30,7 @@ def install_editable(
|
|||
"""Install a package in editable mode. Most arguments are pass-through
|
||||
to setuptools.
|
||||
"""
|
||||
logger.info("Running setup.py develop for %s", name)
|
||||
logger.info('Running setup.py develop for %s', name)
|
||||
|
||||
args = make_setuptools_develop_args(
|
||||
setup_py_path,
|
||||
|
|
@ -42,6 +46,6 @@ def install_editable(
|
|||
with build_env:
|
||||
call_subprocess(
|
||||
args,
|
||||
command_desc="python setup.py develop",
|
||||
command_desc='python setup.py develop',
|
||||
cwd=unpacked_source_directory,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
"""Legacy installation process, i.e. `setup.py install`.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from distutils.util import change_root
|
||||
from typing import List, Optional, Sequence
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
from distutils.util import change_root
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.exceptions import InstallationError, LegacyInstallFailure
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import LegacyInstallFailure
|
||||
from pip._internal.models.scheme import Scheme
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
from pip._internal.utils.setuptools_build import make_setuptools_install_args
|
||||
|
|
@ -18,8 +22,8 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def write_installed_files_from_setuptools_record(
|
||||
record_lines: List[str],
|
||||
root: Optional[str],
|
||||
record_lines: list[str],
|
||||
root: str | None,
|
||||
req_description: str,
|
||||
) -> None:
|
||||
def prepend_root(path: str) -> str:
|
||||
|
|
@ -30,14 +34,14 @@ def write_installed_files_from_setuptools_record(
|
|||
|
||||
for line in record_lines:
|
||||
directory = os.path.dirname(line)
|
||||
if directory.endswith(".egg-info"):
|
||||
if directory.endswith('.egg-info'):
|
||||
egg_info_dir = prepend_root(directory)
|
||||
break
|
||||
else:
|
||||
message = (
|
||||
"{} did not indicate that it installed an "
|
||||
".egg-info directory. Only setup.py projects "
|
||||
"generating .egg-info directories are supported."
|
||||
'{} did not indicate that it installed an '
|
||||
'.egg-info directory. Only setup.py projects '
|
||||
'generating .egg-info directories are supported.'
|
||||
).format(req_description)
|
||||
raise InstallationError(message)
|
||||
|
||||
|
|
@ -49,17 +53,17 @@ def write_installed_files_from_setuptools_record(
|
|||
new_lines.append(os.path.relpath(prepend_root(filename), egg_info_dir))
|
||||
new_lines.sort()
|
||||
ensure_dir(egg_info_dir)
|
||||
inst_files_path = os.path.join(egg_info_dir, "installed-files.txt")
|
||||
with open(inst_files_path, "w") as f:
|
||||
f.write("\n".join(new_lines) + "\n")
|
||||
inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt')
|
||||
with open(inst_files_path, 'w') as f:
|
||||
f.write('\n'.join(new_lines) + '\n')
|
||||
|
||||
|
||||
def install(
|
||||
install_options: List[str],
|
||||
install_options: list[str],
|
||||
global_options: Sequence[str],
|
||||
root: Optional[str],
|
||||
home: Optional[str],
|
||||
prefix: Optional[str],
|
||||
root: str | None,
|
||||
home: str | None,
|
||||
prefix: str | None,
|
||||
use_user_site: bool,
|
||||
pycompile: bool,
|
||||
scheme: Scheme,
|
||||
|
|
@ -73,9 +77,9 @@ def install(
|
|||
|
||||
header_dir = scheme.headers
|
||||
|
||||
with TempDirectory(kind="record") as temp_dir:
|
||||
with TempDirectory(kind='record') as temp_dir:
|
||||
try:
|
||||
record_filename = os.path.join(temp_dir.path, "install-record.txt")
|
||||
record_filename = os.path.join(temp_dir.path, 'install-record.txt')
|
||||
install_args = make_setuptools_install_args(
|
||||
setup_py_path,
|
||||
global_options=global_options,
|
||||
|
|
@ -91,7 +95,7 @@ def install(
|
|||
)
|
||||
|
||||
runner = runner_with_spinner_message(
|
||||
f"Running setup.py install for {req_name}"
|
||||
f'Running setup.py install for {req_name}',
|
||||
)
|
||||
with build_env:
|
||||
runner(
|
||||
|
|
@ -100,7 +104,7 @@ def install(
|
|||
)
|
||||
|
||||
if not os.path.exists(record_filename):
|
||||
logger.debug("Record file %s not found", record_filename)
|
||||
logger.debug('Record file %s not found', record_filename)
|
||||
# Signal to the caller that we didn't install the new package
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Support for installing and building the "wheel" binary package format.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import compileall
|
||||
|
|
@ -14,55 +15,57 @@ import sys
|
|||
import warnings
|
||||
from base64 import urlsafe_b64encode
|
||||
from email.message import Message
|
||||
from itertools import chain, filterfalse, starmap
|
||||
from typing import (
|
||||
IO,
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
BinaryIO,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
NewType,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
from zipfile import ZipFile, ZipInfo
|
||||
|
||||
from pip._vendor.distlib.scripts import ScriptMaker
|
||||
from pip._vendor.distlib.util import get_export_entry
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from itertools import chain
|
||||
from itertools import filterfalse
|
||||
from itertools import starmap
|
||||
from typing import Any
|
||||
from typing import BinaryIO
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import IO
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import NewType
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
from zipfile import ZipFile
|
||||
from zipfile import ZipInfo
|
||||
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.locations import get_major_minor_version
|
||||
from pip._internal.metadata import (
|
||||
BaseDistribution,
|
||||
FilesystemWheel,
|
||||
get_wheel_distribution,
|
||||
)
|
||||
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 captured_stdout, ensure_dir, hash_file, partition
|
||||
from pip._internal.utils.unpacking import (
|
||||
current_umask,
|
||||
is_within_directory,
|
||||
set_extracted_file_to_default_mode_plus_executable,
|
||||
zip_item_is_executable,
|
||||
)
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.metadata import FilesystemWheel
|
||||
from pip._internal.metadata import get_wheel_distribution
|
||||
from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME
|
||||
from pip._internal.models.direct_url import DirectUrl
|
||||
from pip._internal.models.scheme import Scheme
|
||||
from pip._internal.models.scheme import SCHEME_KEYS
|
||||
from pip._internal.utils.filesystem import adjacent_tmp_file
|
||||
from pip._internal.utils.filesystem import replace
|
||||
from pip._internal.utils.misc import captured_stdout
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
from pip._internal.utils.misc import hash_file
|
||||
from pip._internal.utils.misc import partition
|
||||
from pip._internal.utils.unpacking import current_umask
|
||||
from pip._internal.utils.unpacking import is_within_directory
|
||||
from pip._internal.utils.unpacking import set_extracted_file_to_default_mode_plus_executable
|
||||
from pip._internal.utils.unpacking import zip_item_is_executable
|
||||
from pip._internal.utils.wheel import parse_wheel
|
||||
from pip._vendor.distlib.scripts import ScriptMaker
|
||||
from pip._vendor.distlib.util import get_export_entry
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
|
||||
class File(Protocol):
|
||||
src_record_path: "RecordPath"
|
||||
src_record_path: RecordPath
|
||||
dest_path: str
|
||||
changed: bool
|
||||
|
||||
|
|
@ -72,22 +75,22 @@ if TYPE_CHECKING:
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RecordPath = NewType("RecordPath", str)
|
||||
RecordPath = NewType('RecordPath', 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("=")
|
||||
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.
|
||||
"""
|
||||
return {"mode": mode, "newline": "", "encoding": "utf-8"}
|
||||
return {'mode': mode, 'newline': '', 'encoding': 'utf-8'}
|
||||
|
||||
|
||||
def fix_script(path: str) -> bool:
|
||||
|
|
@ -97,35 +100,35 @@ def fix_script(path: str) -> bool:
|
|||
# XXX RECORD hashes will need to be updated
|
||||
assert os.path.isfile(path)
|
||||
|
||||
with open(path, "rb") as script:
|
||||
with open(path, 'rb') as script:
|
||||
firstline = script.readline()
|
||||
if not firstline.startswith(b"#!python"):
|
||||
if not firstline.startswith(b'#!python'):
|
||||
return False
|
||||
exename = sys.executable.encode(sys.getfilesystemencoding())
|
||||
firstline = b"#!" + exename + os.linesep.encode("ascii")
|
||||
firstline = b'#!' + exename + os.linesep.encode('ascii')
|
||||
rest = script.read()
|
||||
with open(path, "wb") as script:
|
||||
with open(path, 'wb') as script:
|
||||
script.write(firstline)
|
||||
script.write(rest)
|
||||
return True
|
||||
|
||||
|
||||
def wheel_root_is_purelib(metadata: Message) -> bool:
|
||||
return metadata.get("Root-Is-Purelib", "").lower() == "true"
|
||||
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():
|
||||
if entry_point.group == "console_scripts":
|
||||
if entry_point.group == 'console_scripts':
|
||||
console_scripts[entry_point.name] = entry_point.value
|
||||
elif entry_point.group == "gui_scripts":
|
||||
elif entry_point.group == 'gui_scripts':
|
||||
gui_scripts[entry_point.name] = entry_point.value
|
||||
return console_scripts, gui_scripts
|
||||
|
||||
|
||||
def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
|
||||
def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> str | None:
|
||||
"""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.
|
||||
|
|
@ -134,7 +137,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
|
|||
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,12 +146,12 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
|
|||
# We don't want to warn for directories that are on PATH.
|
||||
not_warn_dirs = [
|
||||
os.path.normcase(i).rstrip(os.sep)
|
||||
for i in os.environ.get("PATH", "").split(os.pathsep)
|
||||
for i in os.environ.get('PATH', '').split(os.pathsep)
|
||||
]
|
||||
# If an executable sits with sys.executable, we don't warn for it.
|
||||
# This covers the case of venv invocations without activating the venv.
|
||||
not_warn_dirs.append(os.path.normcase(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(parent_dir) not in not_warn_dirs
|
||||
|
|
@ -159,47 +162,47 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
|
|||
# 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 = "script {} is".format(sorted_scripts[0])
|
||||
start_text = f'script {sorted_scripts[0]} is'
|
||||
else:
|
||||
start_text = "scripts {} are".format(
|
||||
", ".join(sorted_scripts[:-1]) + " and " + sorted_scripts[-1]
|
||||
start_text = 'scripts {} are'.format(
|
||||
', '.join(sorted_scripts[:-1]) + ' and ' + sorted_scripts[-1],
|
||||
)
|
||||
|
||||
msg_lines.append(
|
||||
"The {} installed in '{}' which is not on PATH.".format(
|
||||
start_text, parent_dir
|
||||
)
|
||||
start_text, parent_dir,
|
||||
),
|
||||
)
|
||||
|
||||
last_line_fmt = (
|
||||
"Consider adding {} to PATH or, if you prefer "
|
||||
"to suppress this warning, use --no-warn-script-location."
|
||||
'Consider adding {} to PATH or, if you prefer '
|
||||
'to suppress this warning, use --no-warn-script-location.'
|
||||
)
|
||||
if len(msg_lines) == 1:
|
||||
msg_lines.append(last_line_fmt.format("this directory"))
|
||||
msg_lines.append(last_line_fmt.format('this directory'))
|
||||
else:
|
||||
msg_lines.append(last_line_fmt.format("these directories"))
|
||||
msg_lines.append(last_line_fmt.format('these directories'))
|
||||
|
||||
# Add a note if any directory starts with ~
|
||||
warn_for_tilde = any(
|
||||
i[0] == "~" for i in os.environ.get("PATH", "").split(os.pathsep) if i
|
||||
i[0] == '~' for i in os.environ.get('PATH', '').split(os.pathsep) if i
|
||||
)
|
||||
if warn_for_tilde:
|
||||
tilde_warning_msg = (
|
||||
"NOTE: The current PATH contains path(s) starting with `~`, "
|
||||
"which may not be expanded by all applications."
|
||||
'NOTE: The current PATH contains path(s) starting with `~`, '
|
||||
'which may not be expanded by all applications.'
|
||||
)
|
||||
msg_lines.append(tilde_warning_msg)
|
||||
|
||||
# Returns the formatted multiline message
|
||||
return "\n".join(msg_lines)
|
||||
return '\n'.join(msg_lines)
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -227,52 +230,52 @@ def _record_to_fs_path(record_path: RecordPath) -> str:
|
|||
return record_path
|
||||
|
||||
|
||||
def _fs_to_record_path(path: str, relative_to: Optional[str] = None) -> RecordPath:
|
||||
def _fs_to_record_path(path: str, relative_to: str | None = None) -> RecordPath:
|
||||
if relative_to is not None:
|
||||
# On Windows, do not handle relative paths if they belong to different
|
||||
# logical disks
|
||||
if (
|
||||
os.path.splitdrive(path)[0].lower()
|
||||
== os.path.splitdrive(relative_to)[0].lower()
|
||||
os.path.splitdrive(path)[0].lower() ==
|
||||
os.path.splitdrive(relative_to)[0].lower()
|
||||
):
|
||||
path = os.path.relpath(path, relative_to)
|
||||
path = path.replace(os.path.sep, "/")
|
||||
return cast("RecordPath", path)
|
||||
path = path.replace(os.path.sep, '/')
|
||||
return cast('RecordPath', path)
|
||||
|
||||
|
||||
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)
|
||||
old_record_path = cast("RecordPath", row[0])
|
||||
logger.warning('RECORD line has more than three elements: %s', row)
|
||||
old_record_path = cast('RecordPath', row[0])
|
||||
new_record_path = installed.pop(old_record_path, old_record_path)
|
||||
if new_record_path in changed:
|
||||
digest, length = rehash(_record_to_fs_path(new_record_path))
|
||||
else:
|
||||
digest = row[1] if len(row) > 1 else ""
|
||||
length = row[2] if len(row) > 2 else ""
|
||||
digest = row[1] if len(row) > 1 else ''
|
||||
length = row[2] if len(row) > 2 else ''
|
||||
installed_rows.append((new_record_path, digest, length))
|
||||
for f in generated:
|
||||
path = _fs_to_record_path(f, lib_dir)
|
||||
digest, length = rehash(f)
|
||||
installed_rows.append((path, digest, length))
|
||||
for installed_record_path in installed.values():
|
||||
installed_rows.append((installed_record_path, "", ""))
|
||||
installed_rows.append((installed_record_path, '', ''))
|
||||
return installed_rows
|
||||
|
||||
|
||||
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.
|
||||
|
|
@ -315,47 +318,47 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]:
|
|||
# DEFAULT
|
||||
# - The default behavior is to install pip, pipX, pipX.Y, easy_install
|
||||
# and easy_install-X.Y.
|
||||
pip_script = console.pop("pip", None)
|
||||
pip_script = console.pop('pip', None)
|
||||
if pip_script:
|
||||
if "ENSUREPIP_OPTIONS" not in os.environ:
|
||||
scripts_to_generate.append("pip = " + pip_script)
|
||||
if 'ENSUREPIP_OPTIONS' not in os.environ:
|
||||
scripts_to_generate.append('pip = ' + pip_script)
|
||||
|
||||
if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall":
|
||||
if os.environ.get('ENSUREPIP_OPTIONS', '') != 'altinstall':
|
||||
scripts_to_generate.append(
|
||||
"pip{} = {}".format(sys.version_info[0], pip_script)
|
||||
f'pip{sys.version_info[0]} = {pip_script}',
|
||||
)
|
||||
|
||||
scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}")
|
||||
scripts_to_generate.append(f'pip{get_major_minor_version()} = {pip_script}')
|
||||
# Delete any other versioned pip entry points
|
||||
pip_ep = [k for k in console if re.match(r"pip(\d(\.\d)?)?$", k)]
|
||||
pip_ep = [k for k in console if re.match(r'pip(\d(\.\d)?)?$', k)]
|
||||
for k in pip_ep:
|
||||
del console[k]
|
||||
easy_install_script = console.pop("easy_install", None)
|
||||
easy_install_script = console.pop('easy_install', None)
|
||||
if easy_install_script:
|
||||
if "ENSUREPIP_OPTIONS" not in os.environ:
|
||||
scripts_to_generate.append("easy_install = " + easy_install_script)
|
||||
if 'ENSUREPIP_OPTIONS' not in os.environ:
|
||||
scripts_to_generate.append('easy_install = ' + easy_install_script)
|
||||
|
||||
scripts_to_generate.append(
|
||||
"easy_install-{} = {}".format(
|
||||
get_major_minor_version(), easy_install_script
|
||||
)
|
||||
'easy_install-{} = {}'.format(
|
||||
get_major_minor_version(), easy_install_script,
|
||||
),
|
||||
)
|
||||
# Delete any other versioned easy_install entry points
|
||||
easy_install_ep = [
|
||||
k for k in console if re.match(r"easy_install(-\d\.\d)?$", k)
|
||||
k for k in console if re.match(r'easy_install(-\d\.\d)?$', k)
|
||||
]
|
||||
for k in easy_install_ep:
|
||||
del console[k]
|
||||
|
||||
# Generate the console entry points specified in the wheel
|
||||
scripts_to_generate.extend(starmap("{} = {}".format, console.items()))
|
||||
scripts_to_generate.extend(starmap('{} = {}'.format, console.items()))
|
||||
|
||||
return scripts_to_generate
|
||||
|
||||
|
||||
class ZipBackedFile:
|
||||
def __init__(
|
||||
self, src_record_path: RecordPath, dest_path: str, zip_file: ZipFile
|
||||
self, src_record_path: RecordPath, dest_path: str, zip_file: ZipFile,
|
||||
) -> None:
|
||||
self.src_record_path = src_record_path
|
||||
self.dest_path = dest_path
|
||||
|
|
@ -386,7 +389,7 @@ class ZipBackedFile:
|
|||
zipinfo = self._getinfo()
|
||||
|
||||
with self._zip_file.open(zipinfo) as f:
|
||||
with open(self.dest_path, "wb") as dest:
|
||||
with open(self.dest_path, 'wb') as dest:
|
||||
shutil.copyfileobj(f, dest)
|
||||
|
||||
if zip_item_is_executable(zipinfo):
|
||||
|
|
@ -394,7 +397,7 @@ class ZipBackedFile:
|
|||
|
||||
|
||||
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
|
||||
|
|
@ -408,10 +411,10 @@ class ScriptFile:
|
|||
class MissingCallableSuffix(InstallationError):
|
||||
def __init__(self, entry_point: str) -> None:
|
||||
super().__init__(
|
||||
"Invalid script entry point: {} - A callable "
|
||||
"suffix is required. Cf https://packaging.python.org/"
|
||||
"specifications/entry-points/#use-for-scripts for more "
|
||||
"information.".format(entry_point)
|
||||
'Invalid script entry point: {} - A callable '
|
||||
'suffix is required. Cf https://packaging.python.org/'
|
||||
'specifications/entry-points/#use-for-scripts for more '
|
||||
'information.'.format(entry_point),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -422,7 +425,7 @@ def _raise_for_invalid_entrypoint(specification: str) -> None:
|
|||
|
||||
|
||||
class PipScriptMaker(ScriptMaker):
|
||||
def make(self, specification: str, options: Dict[str, Any] = None) -> List[str]:
|
||||
def make(self, specification: str, options: dict[str, Any] = None) -> list[str]:
|
||||
_raise_for_invalid_entrypoint(specification)
|
||||
return super().make(specification, options)
|
||||
|
||||
|
|
@ -434,7 +437,7 @@ def _install_wheel(
|
|||
scheme: Scheme,
|
||||
pycompile: bool = True,
|
||||
warn_script_location: bool = True,
|
||||
direct_url: Optional[DirectUrl] = None,
|
||||
direct_url: DirectUrl | None = None,
|
||||
requested: bool = False,
|
||||
) -> None:
|
||||
"""Install a wheel.
|
||||
|
|
@ -463,12 +466,12 @@ def _install_wheel(
|
|||
# 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
|
||||
srcfile: RecordPath, destfile: str, modified: bool = False,
|
||||
) -> None:
|
||||
"""Map archive RECORD paths to installation RECORD paths."""
|
||||
newpath = _fs_to_record_path(destfile, lib_dir)
|
||||
|
|
@ -477,22 +480,22 @@ def _install_wheel(
|
|||
changed.add(_fs_to_record_path(destfile))
|
||||
|
||||
def is_dir_path(path: RecordPath) -> bool:
|
||||
return path.endswith("/")
|
||||
return path.endswith('/')
|
||||
|
||||
def assert_no_path_traversal(dest_dir_path: str, target_path: str) -> None:
|
||||
if not is_within_directory(dest_dir_path, target_path):
|
||||
message = (
|
||||
"The wheel {!r} has a file {!r} trying to install"
|
||||
" outside the target directory {!r}"
|
||||
'The wheel {!r} has a file {!r} trying to install'
|
||||
' outside the target directory {!r}'
|
||||
)
|
||||
raise InstallationError(
|
||||
message.format(wheel_path, target_path, dest_dir_path)
|
||||
message.format(wheel_path, target_path, dest_dir_path),
|
||||
)
|
||||
|
||||
def root_scheme_file_maker(
|
||||
zip_file: ZipFile, dest: str
|
||||
) -> Callable[[RecordPath], "File"]:
|
||||
def make_root_scheme_file(record_path: RecordPath) -> "File":
|
||||
zip_file: ZipFile, dest: str,
|
||||
) -> 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)
|
||||
|
|
@ -501,17 +504,17 @@ def _install_wheel(
|
|||
return make_root_scheme_file
|
||||
|
||||
def data_scheme_file_maker(
|
||||
zip_file: ZipFile, scheme: Scheme
|
||||
) -> Callable[[RecordPath], "File"]:
|
||||
zip_file: ZipFile, scheme: Scheme,
|
||||
) -> 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 = (
|
||||
"Unexpected file in {}: {!r}. .data directory contents"
|
||||
'Unexpected file in {}: {!r}. .data directory contents'
|
||||
" should be named like: '<scheme key>/<path>'."
|
||||
).format(wheel_path, record_path)
|
||||
raise InstallationError(message)
|
||||
|
|
@ -519,11 +522,11 @@ def _install_wheel(
|
|||
try:
|
||||
scheme_path = scheme_paths[scheme_key]
|
||||
except KeyError:
|
||||
valid_scheme_keys = ", ".join(sorted(scheme_paths))
|
||||
valid_scheme_keys = ', '.join(sorted(scheme_paths))
|
||||
message = (
|
||||
"Unknown scheme key used in {}: {} (for file {!r}). .data"
|
||||
" directory contents should be in subdirectories named"
|
||||
" with a valid scheme key ({})"
|
||||
'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)
|
||||
|
||||
|
|
@ -534,7 +537,7 @@ def _install_wheel(
|
|||
return make_data_scheme_file
|
||||
|
||||
def is_data_scheme_path(path: RecordPath) -> bool:
|
||||
return path.split("/", 1)[0].endswith(".data")
|
||||
return path.split('/', 1)[0].endswith('.data')
|
||||
|
||||
paths = cast(List[RecordPath], wheel_zip.namelist())
|
||||
file_paths = filterfalse(is_dir_path, paths)
|
||||
|
|
@ -544,11 +547,11 @@ def _install_wheel(
|
|||
files: Iterator[File] = map(make_root_scheme_file, root_scheme_paths)
|
||||
|
||||
def is_script_scheme_path(path: RecordPath) -> bool:
|
||||
parts = path.split("/", 2)
|
||||
return len(parts) > 2 and parts[0].endswith(".data") and parts[1] == "scripts"
|
||||
parts = path.split('/', 2)
|
||||
return len(parts) > 2 and parts[0].endswith('.data') and parts[1] == 'scripts'
|
||||
|
||||
other_scheme_paths, script_scheme_paths = partition(
|
||||
is_script_scheme_path, data_scheme_paths
|
||||
is_script_scheme_path, data_scheme_paths,
|
||||
)
|
||||
|
||||
make_data_scheme_file = data_scheme_file_maker(wheel_zip, scheme)
|
||||
|
|
@ -562,16 +565,16 @@ def _install_wheel(
|
|||
)
|
||||
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
|
||||
name = os.path.basename(path)
|
||||
if name.lower().endswith(".exe"):
|
||||
if name.lower().endswith('.exe'):
|
||||
matchname = name[:-4]
|
||||
elif name.lower().endswith("-script.py"):
|
||||
elif name.lower().endswith('-script.py'):
|
||||
matchname = name[:-10]
|
||||
elif name.lower().endswith(".pya"):
|
||||
elif name.lower().endswith('.pya'):
|
||||
matchname = name[:-4]
|
||||
else:
|
||||
matchname = name
|
||||
|
|
@ -579,7 +582,7 @@ def _install_wheel(
|
|||
return matchname in console or matchname in gui
|
||||
|
||||
script_scheme_files: Iterator[File] = map(
|
||||
make_data_scheme_file, script_scheme_paths
|
||||
make_data_scheme_file, script_scheme_paths,
|
||||
)
|
||||
script_scheme_files = filterfalse(is_entrypoint_wrapper, script_scheme_files)
|
||||
script_scheme_files = map(ScriptFile, script_scheme_files)
|
||||
|
|
@ -598,7 +601,7 @@ def _install_wheel(
|
|||
full_installed_path = os.path.join(lib_dir, installed_path)
|
||||
if not os.path.isfile(full_installed_path):
|
||||
continue
|
||||
if not full_installed_path.endswith(".py"):
|
||||
if not full_installed_path.endswith('.py'):
|
||||
continue
|
||||
yield full_installed_path
|
||||
|
||||
|
|
@ -610,14 +613,14 @@ def _install_wheel(
|
|||
if pycompile:
|
||||
with captured_stdout() as stdout:
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore")
|
||||
warnings.filterwarnings('ignore')
|
||||
for path in pyc_source_file_paths():
|
||||
success = compileall.compile_file(path, force=True, quiet=True)
|
||||
if success:
|
||||
pyc_path = pyc_output_path(path)
|
||||
assert os.path.exists(pyc_path)
|
||||
pyc_record_path = cast(
|
||||
"RecordPath", pyc_path.replace(os.path.sep, "/")
|
||||
'RecordPath', pyc_path.replace(os.path.sep, '/'),
|
||||
)
|
||||
record_installed(pyc_record_path, pyc_path)
|
||||
logger.debug(stdout.getvalue())
|
||||
|
|
@ -631,7 +634,7 @@ def _install_wheel(
|
|||
# Ensure we don't generate any variants for scripts because this is almost
|
||||
# never what somebody wants.
|
||||
# See https://bitbucket.org/pypa/distlib/issue/35/
|
||||
maker.variants = {""}
|
||||
maker.variants = {''}
|
||||
|
||||
# This is required because otherwise distlib creates scripts that are not
|
||||
# executable.
|
||||
|
|
@ -641,12 +644,12 @@ def _install_wheel(
|
|||
# Generate the console and GUI entry points specified in the wheel
|
||||
scripts_to_generate = get_console_script_specs(console)
|
||||
|
||||
gui_scripts_to_generate = list(starmap("{} = {}".format, gui.items()))
|
||||
gui_scripts_to_generate = list(starmap('{} = {}'.format, gui.items()))
|
||||
|
||||
generated_console_scripts = maker.make_multiple(scripts_to_generate)
|
||||
generated.extend(generated_console_scripts)
|
||||
|
||||
generated.extend(maker.make_multiple(gui_scripts_to_generate, {"gui": True}))
|
||||
generated.extend(maker.make_multiple(gui_scripts_to_generate, {'gui': True}))
|
||||
|
||||
if warn_script_location:
|
||||
msg = message_about_scripts_not_on_PATH(generated_console_scripts)
|
||||
|
|
@ -665,26 +668,26 @@ def _install_wheel(
|
|||
dest_info_dir = os.path.join(lib_dir, info_dir)
|
||||
|
||||
# Record pip as the installer
|
||||
installer_path = os.path.join(dest_info_dir, "INSTALLER")
|
||||
installer_path = os.path.join(dest_info_dir, 'INSTALLER')
|
||||
with _generate_file(installer_path) as installer_file:
|
||||
installer_file.write(b"pip\n")
|
||||
installer_file.write(b'pip\n')
|
||||
generated.append(installer_path)
|
||||
|
||||
# Record the PEP 610 direct URL reference
|
||||
if direct_url is not None:
|
||||
direct_url_path = os.path.join(dest_info_dir, DIRECT_URL_METADATA_NAME)
|
||||
with _generate_file(direct_url_path) as direct_url_file:
|
||||
direct_url_file.write(direct_url.to_json().encode("utf-8"))
|
||||
direct_url_file.write(direct_url.to_json().encode('utf-8'))
|
||||
generated.append(direct_url_path)
|
||||
|
||||
# Record the REQUESTED file
|
||||
if requested:
|
||||
requested_path = os.path.join(dest_info_dir, "REQUESTED")
|
||||
with open(requested_path, "wb"):
|
||||
requested_path = os.path.join(dest_info_dir, 'REQUESTED')
|
||||
with open(requested_path, 'wb'):
|
||||
pass
|
||||
generated.append(requested_path)
|
||||
|
||||
record_text = distribution.read_text("RECORD")
|
||||
record_text = distribution.read_text('RECORD')
|
||||
record_rows = list(csv.reader(record_text.splitlines()))
|
||||
|
||||
rows = get_csv_rows_for_installed(
|
||||
|
|
@ -696,12 +699,12 @@ def _install_wheel(
|
|||
)
|
||||
|
||||
# Record details of all files installed
|
||||
record_path = os.path.join(dest_info_dir, "RECORD")
|
||||
record_path = os.path.join(dest_info_dir, 'RECORD')
|
||||
|
||||
with _generate_file(record_path, **csv_io_kwargs("w")) as record_file:
|
||||
with _generate_file(record_path, **csv_io_kwargs('w')) as record_file:
|
||||
# Explicitly cast to typing.IO[str] as a workaround for the mypy error:
|
||||
# "writer" has incompatible type "BinaryIO"; expected "_Writer"
|
||||
writer = csv.writer(cast("IO[str]", record_file))
|
||||
writer = csv.writer(cast('IO[str]', record_file))
|
||||
writer.writerows(_normalized_outrows(rows))
|
||||
|
||||
|
||||
|
|
@ -710,7 +713,7 @@ def req_error_context(req_description: str) -> Iterator[None]:
|
|||
try:
|
||||
yield
|
||||
except InstallationError as e:
|
||||
message = "For req: {}. {}".format(req_description, e.args[0])
|
||||
message = f'For req: {req_description}. {e.args[0]}'
|
||||
raise InstallationError(message) from e
|
||||
|
||||
|
||||
|
|
@ -721,7 +724,7 @@ def install_wheel(
|
|||
req_description: str,
|
||||
pycompile: bool = True,
|
||||
warn_script_location: bool = True,
|
||||
direct_url: Optional[DirectUrl] = None,
|
||||
direct_url: DirectUrl | None = None,
|
||||
requested: bool = False,
|
||||
) -> None:
|
||||
with ZipFile(wheel_path, allowZip64=True) as z:
|
||||
|
|
|
|||
|
|
@ -1,47 +1,50 @@
|
|||
"""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 logging
|
||||
import mimetypes
|
||||
import os
|
||||
import shutil
|
||||
from typing import Dict, Iterable, List, Optional
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.distributions import make_distribution_for_install_requirement
|
||||
from pip._internal.distributions.installed import InstalledDistribution
|
||||
from pip._internal.exceptions import (
|
||||
DirectoryUrlHashUnsupported,
|
||||
HashMismatch,
|
||||
HashUnpinned,
|
||||
InstallationError,
|
||||
NetworkConnectionError,
|
||||
PreviousBuildDirError,
|
||||
VcsHashUnsupported,
|
||||
)
|
||||
from pip._internal.exceptions import DirectoryUrlHashUnsupported
|
||||
from pip._internal.exceptions import HashMismatch
|
||||
from pip._internal.exceptions import HashUnpinned
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import NetworkConnectionError
|
||||
from pip._internal.exceptions import PreviousBuildDirError
|
||||
from pip._internal.exceptions import VcsHashUnsupported
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.models.wheel import Wheel
|
||||
from pip._internal.network.download import BatchDownloader, Downloader
|
||||
from pip._internal.network.lazy_wheel import (
|
||||
HTTPRangeRequestUnsupported,
|
||||
dist_from_wheel_url,
|
||||
)
|
||||
from pip._internal.network.download import BatchDownloader
|
||||
from pip._internal.network.download import Downloader
|
||||
from pip._internal.network.lazy_wheel import dist_from_wheel_url
|
||||
from pip._internal.network.lazy_wheel import HTTPRangeRequestUnsupported
|
||||
from pip._internal.network.session import PipSession
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_tracker import RequirementTracker
|
||||
from pip._internal.utils.filesystem import copy2_fixed
|
||||
from pip._internal.utils.hashes import Hashes, MissingHashes
|
||||
from pip._internal.utils.hashes import Hashes
|
||||
from pip._internal.utils.hashes import MissingHashes
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import display_path, hide_url, is_installable_dir, rmtree
|
||||
from pip._internal.utils.misc import display_path
|
||||
from pip._internal.utils.misc import hide_url
|
||||
from pip._internal.utils.misc import is_installable_dir
|
||||
from pip._internal.utils.misc import rmtree
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.utils.unpacking import unpack_file
|
||||
from pip._internal.vcs import vcs
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -66,7 +69,7 @@ def unpack_vcs_link(link: Link, location: str, verbosity: int) -> None:
|
|||
|
||||
|
||||
class File:
|
||||
def __init__(self, path: str, content_type: Optional[str]) -> None:
|
||||
def __init__(self, path: str, content_type: str | None) -> None:
|
||||
self.path = path
|
||||
if content_type is None:
|
||||
self.content_type = mimetypes.guess_type(path)[0]
|
||||
|
|
@ -77,10 +80,10 @@ class File:
|
|||
def get_http_url(
|
||||
link: Link,
|
||||
download: Downloader,
|
||||
download_dir: Optional[str] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
download_dir: str | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
) -> File:
|
||||
temp_dir = TempDirectory(kind="unpack", globally_managed=True)
|
||||
temp_dir = TempDirectory(kind='unpack', globally_managed=True)
|
||||
# If a download dir is specified, is the file already downloaded there?
|
||||
already_downloaded_path = None
|
||||
if download_dir:
|
||||
|
|
@ -123,14 +126,14 @@ def _copy_source_tree(source: str, target: str) -> None:
|
|||
target_basename = os.path.basename(target_abspath)
|
||||
target_dirname = os.path.dirname(target_abspath)
|
||||
|
||||
def ignore(d: str, names: List[str]) -> List[str]:
|
||||
skipped: List[str] = []
|
||||
def ignore(d: str, names: list[str]) -> list[str]:
|
||||
skipped: list[str] = []
|
||||
if d == source:
|
||||
# Pulling in those directories can potentially be very slow,
|
||||
# exclude the following directories if they appear in the top
|
||||
# level dir (and only it).
|
||||
# See discussion at https://github.com/pypa/pip/pull/6770
|
||||
skipped += [".tox", ".nox"]
|
||||
skipped += ['.tox', '.nox']
|
||||
if os.path.abspath(d) == target_dirname:
|
||||
# Prevent an infinite recursion if the target is in source.
|
||||
# This can happen when TMPDIR is set to ${PWD}/...
|
||||
|
|
@ -148,7 +151,7 @@ def _copy_source_tree(source: str, target: str) -> None:
|
|||
|
||||
|
||||
def get_file_url(
|
||||
link: Link, download_dir: Optional[str] = None, hashes: Optional[Hashes] = None
|
||||
link: Link, download_dir: str | None = None, hashes: Hashes | None = None,
|
||||
) -> File:
|
||||
"""Get file and optionally check its hash."""
|
||||
# If a download dir is specified, is the file already there and valid?
|
||||
|
|
@ -176,9 +179,9 @@ def unpack_url(
|
|||
location: str,
|
||||
download: Downloader,
|
||||
verbosity: int,
|
||||
download_dir: Optional[str] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
) -> Optional[File]:
|
||||
download_dir: str | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
) -> File | None:
|
||||
"""Unpack link into location, downloading if required.
|
||||
|
||||
:param hashes: A Hashes object, one of whose embedded hashes must match,
|
||||
|
|
@ -227,8 +230,8 @@ def unpack_url(
|
|||
|
||||
|
||||
def _check_download_dir(
|
||||
link: Link, download_dir: str, hashes: Optional[Hashes]
|
||||
) -> Optional[str]:
|
||||
link: Link, download_dir: str, hashes: Hashes | None,
|
||||
) -> str | None:
|
||||
"""Check download_dir for previously downloaded file with correct hash
|
||||
If a correct file is found return its path else None
|
||||
"""
|
||||
|
|
@ -238,13 +241,13 @@ def _check_download_dir(
|
|||
return None
|
||||
|
||||
# If already downloaded, does its hash match?
|
||||
logger.info("File was already downloaded %s", download_path)
|
||||
logger.info('File was already downloaded %s', download_path)
|
||||
if hashes:
|
||||
try:
|
||||
hashes.check_against_path(download_path)
|
||||
except HashMismatch:
|
||||
logger.warning(
|
||||
"Previously-downloaded file %s has bad hash. Re-downloading.",
|
||||
'Previously-downloaded file %s has bad hash. Re-downloading.',
|
||||
download_path,
|
||||
)
|
||||
os.unlink(download_path)
|
||||
|
|
@ -258,7 +261,7 @@ class RequirementPreparer:
|
|||
def __init__(
|
||||
self,
|
||||
build_dir: str,
|
||||
download_dir: Optional[str],
|
||||
download_dir: str | None,
|
||||
src_dir: str,
|
||||
build_isolation: bool,
|
||||
req_tracker: RequirementTracker,
|
||||
|
|
@ -304,18 +307,18 @@ class RequirementPreparer:
|
|||
self.in_tree_build = in_tree_build
|
||||
|
||||
# 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 = ("", "")
|
||||
self._previous_requirement_header = ('', '')
|
||||
|
||||
def _log_preparing_link(self, req: InstallRequirement) -> None:
|
||||
"""Provide context for the requirement being prepared."""
|
||||
if req.link.is_file and not req.original_link_is_in_wheel_cache:
|
||||
message = "Processing %s"
|
||||
message = 'Processing %s'
|
||||
information = str(display_path(req.link.file_path))
|
||||
else:
|
||||
message = "Collecting %s"
|
||||
message = 'Collecting %s'
|
||||
information = str(req.req or req)
|
||||
|
||||
if (message, information) != self._previous_requirement_header:
|
||||
|
|
@ -324,10 +327,10 @@ class RequirementPreparer:
|
|||
|
||||
if req.original_link_is_in_wheel_cache:
|
||||
with indent_log():
|
||||
logger.info("Using cached %s", req.link.filename)
|
||||
logger.info('Using cached %s', req.link.filename)
|
||||
|
||||
def _ensure_link_req_src_dir(
|
||||
self, req: InstallRequirement, parallel_builds: bool
|
||||
self, req: InstallRequirement, parallel_builds: bool,
|
||||
) -> None:
|
||||
"""Ensure source_dir of a linked InstallRequirement."""
|
||||
# Since source_dir is only set for editable requirements.
|
||||
|
|
@ -357,10 +360,10 @@ class RequirementPreparer:
|
|||
if is_installable_dir(req.source_dir):
|
||||
raise PreviousBuildDirError(
|
||||
"pip can't proceed with requirements '{}' due to a"
|
||||
"pre-existing build directory ({}). This is likely "
|
||||
"due to a previous installation that failed . pip is "
|
||||
"being responsible and not assuming it can delete this. "
|
||||
"Please delete it and try again.".format(req, req.source_dir)
|
||||
'pre-existing build directory ({}). This is likely '
|
||||
'due to a previous installation that failed . pip is '
|
||||
'being responsible and not assuming it can delete this. '
|
||||
'Please delete it and try again.'.format(req, req.source_dir),
|
||||
)
|
||||
|
||||
def _get_linked_req_hashes(self, req: InstallRequirement) -> Hashes:
|
||||
|
|
@ -398,16 +401,16 @@ class RequirementPreparer:
|
|||
def _fetch_metadata_using_lazy_wheel(
|
||||
self,
|
||||
link: Link,
|
||||
) -> Optional[BaseDistribution]:
|
||||
) -> BaseDistribution | None:
|
||||
"""Fetch metadata using lazy wheel, if possible."""
|
||||
if not self.use_lazy_wheel:
|
||||
return None
|
||||
if self.require_hashes:
|
||||
logger.debug("Lazy wheel is not used as hash checking is required")
|
||||
logger.debug('Lazy wheel is not used as hash checking is required')
|
||||
return None
|
||||
if link.is_file or not link.is_wheel:
|
||||
logger.debug(
|
||||
"Lazy wheel is not used as %r does not points to a remote wheel",
|
||||
'Lazy wheel is not used as %r does not points to a remote wheel',
|
||||
link,
|
||||
)
|
||||
return None
|
||||
|
|
@ -415,15 +418,15 @@ class RequirementPreparer:
|
|||
wheel = Wheel(link.filename)
|
||||
name = canonicalize_name(wheel.name)
|
||||
logger.info(
|
||||
"Obtaining dependency information from %s %s",
|
||||
'Obtaining dependency information from %s %s',
|
||||
name,
|
||||
wheel.version,
|
||||
)
|
||||
url = link.url.split("#", 1)[0]
|
||||
url = link.url.split('#', 1)[0]
|
||||
try:
|
||||
return dist_from_wheel_url(name, url, self._session)
|
||||
except HTTPRangeRequestUnsupported:
|
||||
logger.debug("%s does not support range requests", url)
|
||||
logger.debug('%s does not support range requests', url)
|
||||
return None
|
||||
|
||||
def _complete_partial_requirements(
|
||||
|
|
@ -434,12 +437,12 @@ class RequirementPreparer:
|
|||
"""Download any requirements which were only fetched by metadata."""
|
||||
# Download to a temporary directory. These will be copied over as
|
||||
# needed for downstream 'download', 'wheel', and 'install' commands.
|
||||
temp_dir = TempDirectory(kind="unpack", globally_managed=True).path
|
||||
temp_dir = TempDirectory(kind='unpack', globally_managed=True).path
|
||||
|
||||
# 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
|
||||
|
|
@ -449,7 +452,7 @@ class RequirementPreparer:
|
|||
temp_dir,
|
||||
)
|
||||
for link, (filepath, _) in batch_download:
|
||||
logger.debug("Downloading link %s to %s", link, filepath)
|
||||
logger.debug('Downloading link %s to %s', link, filepath)
|
||||
req = links_to_fully_download[link]
|
||||
req.local_file_path = filepath
|
||||
|
||||
|
|
@ -459,7 +462,7 @@ class RequirementPreparer:
|
|||
self._prepare_linked_requirement(req, parallel_builds)
|
||||
|
||||
def prepare_linked_requirement(
|
||||
self, req: InstallRequirement, parallel_builds: bool = False
|
||||
self, req: InstallRequirement, parallel_builds: bool = False,
|
||||
) -> BaseDistribution:
|
||||
"""Prepare a requirement to be obtained from req.link."""
|
||||
assert req.link
|
||||
|
|
@ -487,7 +490,7 @@ class RequirementPreparer:
|
|||
return self._prepare_linked_requirement(req, parallel_builds)
|
||||
|
||||
def prepare_linked_requirements_more(
|
||||
self, reqs: Iterable[InstallRequirement], parallel_builds: bool = False
|
||||
self, reqs: Iterable[InstallRequirement], parallel_builds: bool = False,
|
||||
) -> None:
|
||||
"""Prepare linked requirements more, if needed."""
|
||||
reqs = [req for req in reqs if req.needs_more_preparation]
|
||||
|
|
@ -502,7 +505,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)
|
||||
|
|
@ -517,7 +520,7 @@ class RequirementPreparer:
|
|||
)
|
||||
|
||||
def _prepare_linked_requirement(
|
||||
self, req: InstallRequirement, parallel_builds: bool
|
||||
self, req: InstallRequirement, parallel_builds: bool,
|
||||
) -> BaseDistribution:
|
||||
assert req.link
|
||||
link = req.link
|
||||
|
|
@ -539,8 +542,8 @@ class RequirementPreparer:
|
|||
)
|
||||
except NetworkConnectionError as exc:
|
||||
raise InstallationError(
|
||||
"Could not install requirement {} because of HTTP "
|
||||
"error {} for URL {}".format(req, exc, link)
|
||||
'Could not install requirement {} because of HTTP '
|
||||
'error {} for URL {}'.format(req, exc, link),
|
||||
)
|
||||
else:
|
||||
file_path = self._downloaded[link.url]
|
||||
|
|
@ -572,8 +575,8 @@ class RequirementPreparer:
|
|||
|
||||
if link.is_existing_dir():
|
||||
logger.debug(
|
||||
"Not copying link to destination directory "
|
||||
"since it is a directory: %s",
|
||||
'Not copying link to destination directory '
|
||||
'since it is a directory: %s',
|
||||
link,
|
||||
)
|
||||
return
|
||||
|
|
@ -585,23 +588,23 @@ class RequirementPreparer:
|
|||
if not os.path.exists(download_location):
|
||||
shutil.copy(req.local_file_path, download_location)
|
||||
download_path = display_path(download_location)
|
||||
logger.info("Saved %s", download_path)
|
||||
logger.info('Saved %s', download_path)
|
||||
|
||||
def prepare_editable_requirement(
|
||||
self,
|
||||
req: InstallRequirement,
|
||||
) -> BaseDistribution:
|
||||
"""Prepare an editable requirement."""
|
||||
assert req.editable, "cannot prepare a non-editable req as editable"
|
||||
assert req.editable, 'cannot prepare a non-editable req as editable'
|
||||
|
||||
logger.info("Obtaining %s", req)
|
||||
logger.info('Obtaining %s', req)
|
||||
|
||||
with indent_log():
|
||||
if self.require_hashes:
|
||||
raise InstallationError(
|
||||
"The editable requirement {} cannot be installed when "
|
||||
"requiring hashes, because there is no single file to "
|
||||
"hash.".format(req)
|
||||
'The editable requirement {} cannot be installed when '
|
||||
'requiring hashes, because there is no single file to '
|
||||
'hash.'.format(req),
|
||||
)
|
||||
req.ensure_has_source_dir(self.src_dir)
|
||||
req.update_editable()
|
||||
|
|
@ -625,18 +628,18 @@ class RequirementPreparer:
|
|||
"""Prepare an already-installed requirement."""
|
||||
assert req.satisfied_by, "req should have been satisfied but isn't"
|
||||
assert skip_reason is not None, (
|
||||
"did not get skip reason skipped but req.satisfied_by "
|
||||
"is set to {}".format(req.satisfied_by)
|
||||
'did not get skip reason skipped but req.satisfied_by '
|
||||
'is set to {}'.format(req.satisfied_by)
|
||||
)
|
||||
logger.info(
|
||||
"Requirement %s: %s (%s)", skip_reason, req, req.satisfied_by.version
|
||||
'Requirement %s: %s (%s)', skip_reason, req, req.satisfied_by.version,
|
||||
)
|
||||
with indent_log():
|
||||
if self.require_hashes:
|
||||
logger.debug(
|
||||
"Since it is already installed, we are trusting this "
|
||||
"package without checking its hash. To ensure a "
|
||||
"completely repeatable environment, install into an "
|
||||
"empty virtualenv."
|
||||
'Since it is already installed, we are trusting this '
|
||||
'package without checking its hash. To ensure a '
|
||||
'completely repeatable environment, install into an '
|
||||
'empty virtualenv.',
|
||||
)
|
||||
return InstalledDistribution(req).get_metadata_distribution()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue