mirror of
https://github.com/pre-commit/pre-commit-hooks.git
synced 2026-04-13 06:24:16 +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
|
|
@ -4,9 +4,11 @@ Defines custom logger class for the `logger.verbose(...)` method.
|
|||
|
||||
init_logging() must be called before any other modules that call logging.getLogger.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
|
||||
# custom log level for `--verbose` output
|
||||
# between DEBUG and INFO
|
||||
|
|
@ -35,4 +37,4 @@ def init_logging() -> None:
|
|||
i.e. in pip._internal.__init__
|
||||
"""
|
||||
logging.setLoggerClass(VerboseLogger)
|
||||
logging.addLevelName(VERBOSE, "VERBOSE")
|
||||
logging.addLevelName(VERBOSE, 'VERBOSE')
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ compatible for the current pip code base.
|
|||
The intention is to rewrite current usages gradually, keeping the tests pass,
|
||||
and eventually drop this after all usages are changed.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
|
@ -24,7 +25,7 @@ def _macos_user_config_dir(appname: str, roaming: bool = True) -> str:
|
|||
return path
|
||||
|
||||
# Use a Linux-like ~/.config/pip, by default.
|
||||
linux_like_path = "~/.config/"
|
||||
linux_like_path = '~/.config/'
|
||||
if appname:
|
||||
linux_like_path = os.path.join(linux_like_path, appname)
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ def _macos_user_config_dir(appname: str, roaming: bool = True) -> str:
|
|||
|
||||
|
||||
def user_config_dir(appname: str, roaming: bool = True) -> str:
|
||||
if sys.platform == "darwin":
|
||||
if sys.platform == 'darwin':
|
||||
return _macos_user_config_dir(appname, roaming)
|
||||
|
||||
return _appdirs.user_config_dir(appname, appauthor=False, roaming=roaming)
|
||||
|
|
@ -40,13 +41,13 @@ def user_config_dir(appname: str, roaming: bool = True) -> str:
|
|||
|
||||
# for the discussion regarding site_config_dir locations
|
||||
# see <https://github.com/pypa/pip/issues/1733>
|
||||
def site_config_dirs(appname: str) -> List[str]:
|
||||
if sys.platform == "darwin":
|
||||
def site_config_dirs(appname: str) -> list[str]:
|
||||
if sys.platform == 'darwin':
|
||||
return [_appdirs.site_data_dir(appname, appauthor=False, multipath=True)]
|
||||
|
||||
dirval = _appdirs.site_config_dir(appname, appauthor=False, multipath=True)
|
||||
if sys.platform == "win32":
|
||||
if sys.platform == 'win32':
|
||||
return [dirval]
|
||||
|
||||
# Unix-y system. Look in /etc as well.
|
||||
return dirval.split(os.pathsep) + ["/etc"]
|
||||
return dirval.split(os.pathsep) + ['/etc']
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
"""Stuff that differs in different Python versions and platform
|
||||
distributions."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
__all__ = ["get_path_uid", "stdlib_pkgs", "WINDOWS"]
|
||||
__all__ = ['get_path_uid', 'stdlib_pkgs', 'WINDOWS']
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -36,7 +37,7 @@ def get_path_uid(path: str) -> int:
|
|||
|
||||
:raises OSError: When path is a symlink or can't be read.
|
||||
"""
|
||||
if hasattr(os, "O_NOFOLLOW"):
|
||||
if hasattr(os, 'O_NOFOLLOW'):
|
||||
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
|
||||
file_uid = os.fstat(fd).st_uid
|
||||
os.close(fd)
|
||||
|
|
@ -47,7 +48,7 @@ def get_path_uid(path: str) -> int:
|
|||
file_uid = os.stat(path).st_uid
|
||||
else:
|
||||
# raise OSError for parity with os.O_NOFOLLOW above
|
||||
raise OSError(f"{path} is a symlink; Will not return uid for symlinks")
|
||||
raise OSError(f'{path} is a symlink; Will not return uid for symlinks')
|
||||
return file_uid
|
||||
|
||||
|
||||
|
|
@ -56,8 +57,8 @@ def get_path_uid(path: str) -> int:
|
|||
# dist.location (py27:`sysconfig.get_paths()['stdlib']`,
|
||||
# py26:sysconfig.get_config_vars('LIBDEST')), but fear platform variation may
|
||||
# make this ineffective, so hard-coding
|
||||
stdlib_pkgs = {"python", "wsgiref", "argparse"}
|
||||
stdlib_pkgs = {'python', 'wsgiref', 'argparse'}
|
||||
|
||||
|
||||
# windows detection, covers cpython and ironpython
|
||||
WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")
|
||||
WINDOWS = sys.platform.startswith('win') or (sys.platform == 'cli' and os.name == 'nt')
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
"""Generate and work with PEP 425 Compatibility Tags.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import List, Optional, Tuple
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._vendor.packaging.tags import (
|
||||
PythonVersion,
|
||||
Tag,
|
||||
compatible_tags,
|
||||
cpython_tags,
|
||||
generic_tags,
|
||||
interpreter_name,
|
||||
interpreter_version,
|
||||
mac_platforms,
|
||||
)
|
||||
from pip._vendor.packaging.tags import compatible_tags
|
||||
from pip._vendor.packaging.tags import cpython_tags
|
||||
from pip._vendor.packaging.tags import generic_tags
|
||||
from pip._vendor.packaging.tags import interpreter_name
|
||||
from pip._vendor.packaging.tags import interpreter_version
|
||||
from pip._vendor.packaging.tags import mac_platforms
|
||||
from pip._vendor.packaging.tags import PythonVersion
|
||||
from pip._vendor.packaging.tags import Tag
|
||||
|
||||
_osx_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)")
|
||||
_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)')
|
||||
|
||||
|
||||
def version_info_to_nodot(version_info: Tuple[int, ...]) -> str:
|
||||
def version_info_to_nodot(version_info: tuple[int, ...]) -> str:
|
||||
# Only use up to the first two numbers.
|
||||
return "".join(map(str, version_info[:2]))
|
||||
return ''.join(map(str, version_info[:2]))
|
||||
|
||||
|
||||
def _mac_platforms(arch: str) -> List[str]:
|
||||
def _mac_platforms(arch: str) -> list[str]:
|
||||
match = _osx_arch_pat.match(arch)
|
||||
if match:
|
||||
name, major, minor, actual_arch = match.groups()
|
||||
|
|
@ -34,7 +35,7 @@ def _mac_platforms(arch: str) -> List[str]:
|
|||
# actual prefix provided by the user in case they provided
|
||||
# something like "macosxcustom_". It may be good to remove
|
||||
# this as undocumented or deprecate it in the future.
|
||||
"{}_{}".format(name, arch[len("macosx_") :])
|
||||
'{}_{}'.format(name, arch[len('macosx_'):])
|
||||
for arch in mac_platforms(mac_version, actual_arch)
|
||||
]
|
||||
else:
|
||||
|
|
@ -43,39 +44,39 @@ def _mac_platforms(arch: str) -> List[str]:
|
|||
return arches
|
||||
|
||||
|
||||
def _custom_manylinux_platforms(arch: str) -> List[str]:
|
||||
def _custom_manylinux_platforms(arch: str) -> list[str]:
|
||||
arches = [arch]
|
||||
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
|
||||
if arch_prefix == "manylinux2014":
|
||||
arch_prefix, arch_sep, arch_suffix = arch.partition('_')
|
||||
if arch_prefix == 'manylinux2014':
|
||||
# manylinux1/manylinux2010 wheels run on most manylinux2014 systems
|
||||
# with the exception of wheels depending on ncurses. PEP 599 states
|
||||
# manylinux1/manylinux2010 wheels should be considered
|
||||
# manylinux2014 wheels:
|
||||
# https://www.python.org/dev/peps/pep-0599/#backwards-compatibility-with-manylinux2010-wheels
|
||||
if arch_suffix in {"i686", "x86_64"}:
|
||||
arches.append("manylinux2010" + arch_sep + arch_suffix)
|
||||
arches.append("manylinux1" + arch_sep + arch_suffix)
|
||||
elif arch_prefix == "manylinux2010":
|
||||
if arch_suffix in {'i686', 'x86_64'}:
|
||||
arches.append('manylinux2010' + arch_sep + arch_suffix)
|
||||
arches.append('manylinux1' + arch_sep + arch_suffix)
|
||||
elif arch_prefix == 'manylinux2010':
|
||||
# manylinux1 wheels run on most manylinux2010 systems with the
|
||||
# exception of wheels depending on ncurses. PEP 571 states
|
||||
# manylinux1 wheels should be considered manylinux2010 wheels:
|
||||
# https://www.python.org/dev/peps/pep-0571/#backwards-compatibility-with-manylinux1-wheels
|
||||
arches.append("manylinux1" + arch_sep + arch_suffix)
|
||||
arches.append('manylinux1' + arch_sep + arch_suffix)
|
||||
return arches
|
||||
|
||||
|
||||
def _get_custom_platforms(arch: str) -> List[str]:
|
||||
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
|
||||
if arch.startswith("macosx"):
|
||||
def _get_custom_platforms(arch: str) -> list[str]:
|
||||
arch_prefix, arch_sep, arch_suffix = arch.partition('_')
|
||||
if arch.startswith('macosx'):
|
||||
arches = _mac_platforms(arch)
|
||||
elif arch_prefix in ["manylinux2014", "manylinux2010"]:
|
||||
elif arch_prefix in ['manylinux2014', 'manylinux2010']:
|
||||
arches = _custom_manylinux_platforms(arch)
|
||||
else:
|
||||
arches = [arch]
|
||||
return arches
|
||||
|
||||
|
||||
def _expand_allowed_platforms(platforms: Optional[List[str]]) -> Optional[List[str]]:
|
||||
def _expand_allowed_platforms(platforms: list[str] | None) -> list[str] | None:
|
||||
if not platforms:
|
||||
return None
|
||||
|
||||
|
|
@ -100,21 +101,21 @@ def _get_python_version(version: str) -> PythonVersion:
|
|||
|
||||
|
||||
def _get_custom_interpreter(
|
||||
implementation: Optional[str] = None, version: Optional[str] = None
|
||||
implementation: str | None = None, version: str | None = None,
|
||||
) -> str:
|
||||
if implementation is None:
|
||||
implementation = interpreter_name()
|
||||
if version is None:
|
||||
version = interpreter_version()
|
||||
return f"{implementation}{version}"
|
||||
return f'{implementation}{version}'
|
||||
|
||||
|
||||
def get_supported(
|
||||
version: Optional[str] = None,
|
||||
platforms: Optional[List[str]] = None,
|
||||
impl: Optional[str] = None,
|
||||
abis: Optional[List[str]] = None,
|
||||
) -> List[Tag]:
|
||||
version: str | None = None,
|
||||
platforms: list[str] | None = None,
|
||||
impl: str | None = None,
|
||||
abis: list[str] | None = None,
|
||||
) -> list[Tag]:
|
||||
"""Return a list of supported tags for each version specified in
|
||||
`versions`.
|
||||
|
||||
|
|
@ -127,9 +128,9 @@ def get_supported(
|
|||
:param abis: specify a list of abis you want valid
|
||||
tags for, or None. If None, use the local interpreter abi.
|
||||
"""
|
||||
supported: List[Tag] = []
|
||||
supported: list[Tag] = []
|
||||
|
||||
python_version: Optional[PythonVersion] = None
|
||||
python_version: PythonVersion | None = None
|
||||
if version is not None:
|
||||
python_version = _get_python_version(version)
|
||||
|
||||
|
|
@ -137,14 +138,14 @@ def get_supported(
|
|||
|
||||
platforms = _expand_allowed_platforms(platforms)
|
||||
|
||||
is_cpython = (impl or interpreter_name()) == "cp"
|
||||
is_cpython = (impl or interpreter_name()) == 'cp'
|
||||
if is_cpython:
|
||||
supported.extend(
|
||||
cpython_tags(
|
||||
python_version=python_version,
|
||||
abis=abis,
|
||||
platforms=platforms,
|
||||
)
|
||||
),
|
||||
)
|
||||
else:
|
||||
supported.extend(
|
||||
|
|
@ -152,14 +153,14 @@ def get_supported(
|
|||
interpreter=interpreter,
|
||||
abis=abis,
|
||||
platforms=platforms,
|
||||
)
|
||||
),
|
||||
)
|
||||
supported.extend(
|
||||
compatible_tags(
|
||||
python_version=python_version,
|
||||
interpreter=interpreter,
|
||||
platforms=platforms,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
return supported
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""For when pip wants to check the date or time.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
"""
|
||||
A module that implements tooling to enable easy warnings about deprecations.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
from typing import Any, Optional, TextIO, Type, Union
|
||||
|
||||
from pip._vendor.packaging.version import parse
|
||||
from typing import Any
|
||||
from typing import Optional
|
||||
from typing import TextIO
|
||||
from typing import Type
|
||||
from typing import Union
|
||||
|
||||
from pip import __version__ as current_version # NOTE: tests patch this name.
|
||||
from pip._vendor.packaging.version import parse
|
||||
|
||||
DEPRECATION_MSG_PREFIX = "DEPRECATION: "
|
||||
DEPRECATION_MSG_PREFIX = 'DEPRECATION: '
|
||||
|
||||
|
||||
class PipDeprecationWarning(Warning):
|
||||
|
|
@ -22,12 +26,12 @@ _original_showwarning: Any = None
|
|||
|
||||
# Warnings <-> Logging Integration
|
||||
def _showwarning(
|
||||
message: Union[Warning, str],
|
||||
category: Type[Warning],
|
||||
message: Warning | str,
|
||||
category: type[Warning],
|
||||
filename: str,
|
||||
lineno: int,
|
||||
file: Optional[TextIO] = None,
|
||||
line: Optional[str] = None,
|
||||
file: TextIO | None = None,
|
||||
line: str | None = None,
|
||||
) -> None:
|
||||
if file is not None:
|
||||
if _original_showwarning is not None:
|
||||
|
|
@ -35,7 +39,7 @@ def _showwarning(
|
|||
elif issubclass(category, PipDeprecationWarning):
|
||||
# We use a specially named logger which will handle all of the
|
||||
# deprecation messages for pip.
|
||||
logger = logging.getLogger("pip._internal.deprecations")
|
||||
logger = logging.getLogger('pip._internal.deprecations')
|
||||
logger.warning(message)
|
||||
else:
|
||||
_original_showwarning(message, category, filename, lineno, file, line)
|
||||
|
|
@ -43,7 +47,7 @@ def _showwarning(
|
|||
|
||||
def install_warning_logger() -> None:
|
||||
# Enable our Deprecation Warnings
|
||||
warnings.simplefilter("default", PipDeprecationWarning, append=True)
|
||||
warnings.simplefilter('default', PipDeprecationWarning, append=True)
|
||||
|
||||
global _original_showwarning
|
||||
|
||||
|
|
@ -55,10 +59,10 @@ def install_warning_logger() -> None:
|
|||
def deprecated(
|
||||
*,
|
||||
reason: str,
|
||||
replacement: Optional[str],
|
||||
gone_in: Optional[str],
|
||||
feature_flag: Optional[str] = None,
|
||||
issue: Optional[int] = None,
|
||||
replacement: str | None,
|
||||
gone_in: str | None,
|
||||
feature_flag: str | None = None,
|
||||
issue: int | None = None,
|
||||
) -> None:
|
||||
"""Helper to deprecate existing functionality.
|
||||
|
||||
|
|
@ -84,30 +88,30 @@ def deprecated(
|
|||
is_gone = gone_in is not None and parse(current_version) >= parse(gone_in)
|
||||
|
||||
message_parts = [
|
||||
(reason, f"{DEPRECATION_MSG_PREFIX}{{}}"),
|
||||
(reason, f'{DEPRECATION_MSG_PREFIX}{{}}'),
|
||||
(
|
||||
gone_in,
|
||||
"pip {} will enforce this behaviour change."
|
||||
'pip {} will enforce this behaviour change.'
|
||||
if not is_gone
|
||||
else "Since pip {}, this is no longer supported.",
|
||||
else 'Since pip {}, this is no longer supported.',
|
||||
),
|
||||
(
|
||||
replacement,
|
||||
"A possible replacement is {}.",
|
||||
'A possible replacement is {}.',
|
||||
),
|
||||
(
|
||||
feature_flag,
|
||||
"You can use the flag --use-feature={} to test the upcoming behaviour."
|
||||
'You can use the flag --use-feature={} to test the upcoming behaviour.'
|
||||
if not is_gone
|
||||
else None,
|
||||
),
|
||||
(
|
||||
issue,
|
||||
"Discussion can be found at https://github.com/pypa/pip/issues/{}",
|
||||
'Discussion can be found at https://github.com/pypa/pip/issues/{}',
|
||||
),
|
||||
]
|
||||
|
||||
message = " ".join(
|
||||
message = ' '.join(
|
||||
format_str.format(value)
|
||||
for value, format_str in message_parts
|
||||
if format_str is not None and value is not None
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.models.direct_url import ArchiveInfo, DirectUrl, DirInfo, VcsInfo
|
||||
from pip._internal.models.direct_url import ArchiveInfo
|
||||
from pip._internal.models.direct_url import DirectUrl
|
||||
from pip._internal.models.direct_url import DirInfo
|
||||
from pip._internal.models.direct_url import VcsInfo
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.utils.urls import path_to_url
|
||||
from pip._internal.vcs import vcs
|
||||
|
|
@ -9,11 +14,11 @@ from pip._internal.vcs import vcs
|
|||
def direct_url_as_pep440_direct_reference(direct_url: DirectUrl, name: str) -> str:
|
||||
"""Convert a DirectUrl to a pip requirement string."""
|
||||
direct_url.validate() # if invalid, this is a pip bug
|
||||
requirement = name + " @ "
|
||||
requirement = name + ' @ '
|
||||
fragments = []
|
||||
if isinstance(direct_url.info, VcsInfo):
|
||||
requirement += "{}+{}@{}".format(
|
||||
direct_url.info.vcs, direct_url.url, direct_url.info.commit_id
|
||||
requirement += '{}+{}@{}'.format(
|
||||
direct_url.info.vcs, direct_url.url, direct_url.info.commit_id,
|
||||
)
|
||||
elif isinstance(direct_url.info, ArchiveInfo):
|
||||
requirement += direct_url.url
|
||||
|
|
@ -23,9 +28,9 @@ def direct_url_as_pep440_direct_reference(direct_url: DirectUrl, name: str) -> s
|
|||
assert isinstance(direct_url.info, DirInfo)
|
||||
requirement += direct_url.url
|
||||
if direct_url.subdirectory:
|
||||
fragments.append("subdirectory=" + direct_url.subdirectory)
|
||||
fragments.append('subdirectory=' + direct_url.subdirectory)
|
||||
if fragments:
|
||||
requirement += "#" + "&".join(fragments)
|
||||
requirement += '#' + '&'.join(fragments)
|
||||
return requirement
|
||||
|
||||
|
||||
|
|
@ -37,13 +42,13 @@ def direct_url_for_editable(source_dir: str) -> DirectUrl:
|
|||
|
||||
|
||||
def direct_url_from_link(
|
||||
link: Link, source_dir: Optional[str] = None, link_is_in_wheel_cache: bool = False
|
||||
link: Link, source_dir: str | None = None, link_is_in_wheel_cache: bool = False,
|
||||
) -> DirectUrl:
|
||||
if link.is_vcs:
|
||||
vcs_backend = vcs.get_backend_for_scheme(link.scheme)
|
||||
assert vcs_backend
|
||||
url, requested_revision, _ = vcs_backend.get_url_rev_and_auth(
|
||||
link.url_without_fragment
|
||||
link.url_without_fragment,
|
||||
)
|
||||
# For VCS links, we need to find out and add commit_id.
|
||||
if link_is_in_wheel_cache:
|
||||
|
|
@ -79,7 +84,7 @@ def direct_url_from_link(
|
|||
hash = None
|
||||
hash_name = link.hash_name
|
||||
if hash_name:
|
||||
hash = f"{hash_name}={link.hash}"
|
||||
hash = f'{hash_name}={link.hash}'
|
||||
return DirectUrl(
|
||||
url=link.url_without_fragment,
|
||||
info=ArchiveInfo(hash=hash),
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
||||
from distutils.errors import DistutilsArgError
|
||||
from distutils.fancy_getopt import FancyGetopt
|
||||
from typing import Dict, List
|
||||
|
||||
_options = [
|
||||
("exec-prefix=", None, ""),
|
||||
("home=", None, ""),
|
||||
("install-base=", None, ""),
|
||||
("install-data=", None, ""),
|
||||
("install-headers=", None, ""),
|
||||
("install-lib=", None, ""),
|
||||
("install-platlib=", None, ""),
|
||||
("install-purelib=", None, ""),
|
||||
("install-scripts=", None, ""),
|
||||
("prefix=", None, ""),
|
||||
("root=", None, ""),
|
||||
("user", None, ""),
|
||||
('exec-prefix=', None, ''),
|
||||
('home=', None, ''),
|
||||
('install-base=', None, ''),
|
||||
('install-data=', None, ''),
|
||||
('install-headers=', None, ''),
|
||||
('install-lib=', None, ''),
|
||||
('install-platlib=', None, ''),
|
||||
('install-purelib=', None, ''),
|
||||
('install-scripts=', None, ''),
|
||||
('prefix=', None, ''),
|
||||
('root=', None, ''),
|
||||
('user', None, ''),
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -22,7 +26,7 @@ _options = [
|
|||
_distutils_getopt = FancyGetopt(_options) # type: ignore
|
||||
|
||||
|
||||
def parse_distutils_args(args: List[str]) -> Dict[str, str]:
|
||||
def parse_distutils_args(args: list[str]) -> dict[str, str]:
|
||||
"""Parse provided arguments, returning an object that has the
|
||||
matched arguments.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.locations import site_packages, user_site
|
||||
from pip._internal.utils.virtualenv import (
|
||||
running_under_virtualenv,
|
||||
virtualenv_no_global,
|
||||
)
|
||||
from pip._internal.locations import site_packages
|
||||
from pip._internal.locations import user_site
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
from pip._internal.utils.virtualenv import virtualenv_no_global
|
||||
|
||||
__all__ = [
|
||||
"egg_link_path_from_sys_path",
|
||||
"egg_link_path_from_location",
|
||||
'egg_link_path_from_sys_path',
|
||||
'egg_link_path_from_location',
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -24,10 +24,10 @@ def _egg_link_name(raw_name: str) -> str:
|
|||
the same substitution as pkg_resources's safe_name function.
|
||||
Note: we cannot use canonicalize_name because it has a different logic.
|
||||
"""
|
||||
return re.sub("[^A-Za-z0-9.]+", "-", raw_name) + ".egg-link"
|
||||
return re.sub('[^A-Za-z0-9.]+', '-', raw_name) + '.egg-link'
|
||||
|
||||
|
||||
def egg_link_path_from_sys_path(raw_name: str) -> Optional[str]:
|
||||
def egg_link_path_from_sys_path(raw_name: str) -> str | None:
|
||||
"""
|
||||
Look for a .egg-link file for project name, by walking sys.path.
|
||||
"""
|
||||
|
|
@ -39,7 +39,7 @@ def egg_link_path_from_sys_path(raw_name: str) -> Optional[str]:
|
|||
return None
|
||||
|
||||
|
||||
def egg_link_path_from_location(raw_name: str) -> Optional[str]:
|
||||
def egg_link_path_from_location(raw_name: str) -> str | None:
|
||||
"""
|
||||
Return the path for the .egg-link file if it exists, otherwise, None.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import codecs
|
||||
import locale
|
||||
import re
|
||||
import sys
|
||||
from typing import List, Tuple
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
|
||||
BOMS: List[Tuple[bytes, str]] = [
|
||||
(codecs.BOM_UTF8, "utf-8"),
|
||||
(codecs.BOM_UTF16, "utf-16"),
|
||||
(codecs.BOM_UTF16_BE, "utf-16-be"),
|
||||
(codecs.BOM_UTF16_LE, "utf-16-le"),
|
||||
(codecs.BOM_UTF32, "utf-32"),
|
||||
(codecs.BOM_UTF32_BE, "utf-32-be"),
|
||||
(codecs.BOM_UTF32_LE, "utf-32-le"),
|
||||
BOMS: list[tuple[bytes, str]] = [
|
||||
(codecs.BOM_UTF8, 'utf-8'),
|
||||
(codecs.BOM_UTF16, 'utf-16'),
|
||||
(codecs.BOM_UTF16_BE, 'utf-16-be'),
|
||||
(codecs.BOM_UTF16_LE, 'utf-16-le'),
|
||||
(codecs.BOM_UTF32, 'utf-32'),
|
||||
(codecs.BOM_UTF32_BE, 'utf-32-be'),
|
||||
(codecs.BOM_UTF32_LE, 'utf-32-le'),
|
||||
]
|
||||
|
||||
ENCODING_RE = re.compile(br"coding[:=]\s*([-\w.]+)")
|
||||
ENCODING_RE = re.compile(br'coding[:=]\s*([-\w.]+)')
|
||||
|
||||
|
||||
def auto_decode(data: bytes) -> str:
|
||||
|
|
@ -23,13 +26,13 @@ def auto_decode(data: bytes) -> str:
|
|||
Fallback to locale.getpreferredencoding(False) like open() on Python3"""
|
||||
for bom, encoding in BOMS:
|
||||
if data.startswith(bom):
|
||||
return data[len(bom) :].decode(encoding)
|
||||
return data[len(bom):].decode(encoding)
|
||||
# Lets check the first two lines as in PEP263
|
||||
for line in data.split(b"\n")[:2]:
|
||||
if line[0:1] == b"#" and ENCODING_RE.search(line):
|
||||
for line in data.split(b'\n')[:2]:
|
||||
if line[0:1] == b'#' and ENCODING_RE.search(line):
|
||||
result = ENCODING_RE.search(line)
|
||||
assert result is not None
|
||||
encoding = result.groups()[0].decode("ascii")
|
||||
encoding = result.groups()[0].decode('ascii')
|
||||
return data.decode(encoding)
|
||||
return data.decode(
|
||||
locale.getpreferredencoding(False) or sys.getdefaultencoding(),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.cli.main import main
|
||||
|
||||
|
||||
def _wrapper(args: Optional[List[str]] = None) -> int:
|
||||
def _wrapper(args: list[str] | None = None) -> int:
|
||||
"""Central wrapper for all old entrypoints.
|
||||
|
||||
Historically pip has had several entrypoints defined. Because of issues
|
||||
|
|
@ -17,11 +20,11 @@ def _wrapper(args: Optional[List[str]] = None) -> int:
|
|||
our old entrypoints as wrappers for the current one.
|
||||
"""
|
||||
sys.stderr.write(
|
||||
"WARNING: pip is being invoked by an old script wrapper. This will "
|
||||
"fail in a future version of pip.\n"
|
||||
"Please see https://github.com/pypa/pip/issues/5599 for advice on "
|
||||
"fixing the underlying issue.\n"
|
||||
'WARNING: pip is being invoked by an old script wrapper. This will '
|
||||
'fail in a future version of pip.\n'
|
||||
'Please see https://github.com/pypa/pip/issues/5599 for advice on '
|
||||
'fixing the underlying issue.\n'
|
||||
"To avoid this problem you can invoke Python with '-m pip' instead of "
|
||||
"running pip directly.\n"
|
||||
'running pip directly.\n',
|
||||
)
|
||||
return main(args)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
import os
|
||||
import os.path
|
||||
import random
|
||||
import shutil
|
||||
|
|
@ -7,18 +8,24 @@ import stat
|
|||
import sys
|
||||
from contextlib import contextmanager
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Any, BinaryIO, Iterator, List, Union, cast
|
||||
|
||||
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
|
||||
from typing import Any
|
||||
from typing import BinaryIO
|
||||
from typing import cast
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Union
|
||||
|
||||
from pip._internal.utils.compat import get_path_uid
|
||||
from pip._internal.utils.misc import format_size
|
||||
from pip._vendor.tenacity import retry
|
||||
from pip._vendor.tenacity import stop_after_delay
|
||||
from pip._vendor.tenacity import wait_fixed
|
||||
|
||||
|
||||
def check_path_owner(path: str) -> bool:
|
||||
# If we don't have a way to check the effective uid of this process, then
|
||||
# we'll just assume that we own the directory.
|
||||
if sys.platform == "win32" or not hasattr(os, "geteuid"):
|
||||
if sys.platform == 'win32' or not hasattr(os, 'geteuid'):
|
||||
return True
|
||||
|
||||
assert os.path.isabs(path)
|
||||
|
|
@ -60,7 +67,7 @@ def copy2_fixed(src: str, dest: str) -> None:
|
|||
pass
|
||||
else:
|
||||
if is_socket_file:
|
||||
raise shutil.SpecialFileError(f"`{f}` is a socket")
|
||||
raise shutil.SpecialFileError(f'`{f}` is a socket')
|
||||
|
||||
raise
|
||||
|
||||
|
|
@ -83,7 +90,7 @@ def adjacent_tmp_file(path: str, **kwargs: Any) -> Iterator[BinaryIO]:
|
|||
delete=False,
|
||||
dir=os.path.dirname(path),
|
||||
prefix=os.path.basename(path),
|
||||
suffix=".tmp",
|
||||
suffix='.tmp',
|
||||
**kwargs,
|
||||
) as f:
|
||||
result = cast(BinaryIO, f)
|
||||
|
|
@ -114,7 +121,7 @@ def test_writable_dir(path: str) -> bool:
|
|||
break # Should never get here, but infinite loops are bad
|
||||
path = parent
|
||||
|
||||
if os.name == "posix":
|
||||
if os.name == 'posix':
|
||||
return os.access(path, os.W_OK)
|
||||
|
||||
return _test_writable_dir_win(path)
|
||||
|
|
@ -123,10 +130,10 @@ def test_writable_dir(path: str) -> bool:
|
|||
def _test_writable_dir_win(path: str) -> bool:
|
||||
# os.access doesn't work on Windows: http://bugs.python.org/issue2528
|
||||
# and we can't use tempfile: http://bugs.python.org/issue22107
|
||||
basename = "accesstest_deleteme_fishfingers_custard_"
|
||||
alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
basename = 'accesstest_deleteme_fishfingers_custard_'
|
||||
alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
for _ in range(10):
|
||||
name = basename + "".join(random.choice(alphabet) for _ in range(6))
|
||||
name = basename + ''.join(random.choice(alphabet) for _ in range(6))
|
||||
file = os.path.join(path, name)
|
||||
try:
|
||||
fd = os.open(file, os.O_RDWR | os.O_CREAT | os.O_EXCL)
|
||||
|
|
@ -145,20 +152,20 @@ def _test_writable_dir_win(path: str) -> bool:
|
|||
return True
|
||||
|
||||
# This should never be reached
|
||||
raise OSError("Unexpected condition testing for writable directory")
|
||||
raise OSError('Unexpected condition testing for writable directory')
|
||||
|
||||
|
||||
def find_files(path: str, pattern: str) -> List[str]:
|
||||
def find_files(path: str, pattern: str) -> list[str]:
|
||||
"""Returns a list of absolute paths of files beneath path, recursively,
|
||||
with filenames which match the UNIX-style shell glob pattern."""
|
||||
result: List[str] = []
|
||||
result: list[str] = []
|
||||
for root, _, files in os.walk(path):
|
||||
matches = fnmatch.filter(files, pattern)
|
||||
result.extend(os.path.join(root, f) for f in matches)
|
||||
return result
|
||||
|
||||
|
||||
def file_size(path: str) -> Union[int, float]:
|
||||
def file_size(path: str) -> int | float:
|
||||
# If it's a symlink, return 0.
|
||||
if os.path.islink(path):
|
||||
return 0
|
||||
|
|
@ -169,7 +176,7 @@ def format_file_size(path: str) -> str:
|
|||
return format_size(file_size(path))
|
||||
|
||||
|
||||
def directory_size(path: str) -> Union[int, float]:
|
||||
def directory_size(path: str) -> int | float:
|
||||
size = 0.0
|
||||
for root, _dirs, files in os.walk(path):
|
||||
for filename in files:
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
"""Filetype information.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.utils.misc import splitext
|
||||
|
||||
WHEEL_EXTENSION = ".whl"
|
||||
BZ2_EXTENSIONS: Tuple[str, ...] = (".tar.bz2", ".tbz")
|
||||
XZ_EXTENSIONS: Tuple[str, ...] = (
|
||||
".tar.xz",
|
||||
".txz",
|
||||
".tlz",
|
||||
".tar.lz",
|
||||
".tar.lzma",
|
||||
WHEEL_EXTENSION = '.whl'
|
||||
BZ2_EXTENSIONS: tuple[str, ...] = ('.tar.bz2', '.tbz')
|
||||
XZ_EXTENSIONS: tuple[str, ...] = (
|
||||
'.tar.xz',
|
||||
'.txz',
|
||||
'.tlz',
|
||||
'.tar.lz',
|
||||
'.tar.lzma',
|
||||
)
|
||||
ZIP_EXTENSIONS: Tuple[str, ...] = (".zip", WHEEL_EXTENSION)
|
||||
TAR_EXTENSIONS: Tuple[str, ...] = (".tar.gz", ".tgz", ".tar")
|
||||
ZIP_EXTENSIONS: tuple[str, ...] = ('.zip', WHEEL_EXTENSION)
|
||||
TAR_EXTENSIONS: tuple[str, ...] = ('.tar.gz', '.tgz', '.tar')
|
||||
ARCHIVE_EXTENSIONS = ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +1,37 @@
|
|||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional, Tuple
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
def glibc_version_string() -> Optional[str]:
|
||||
"Returns glibc version string, or None if not using glibc."
|
||||
def glibc_version_string() -> str | None:
|
||||
'Returns glibc version string, or None if not using glibc.'
|
||||
return glibc_version_string_confstr() or glibc_version_string_ctypes()
|
||||
|
||||
|
||||
def glibc_version_string_confstr() -> Optional[str]:
|
||||
"Primary implementation of glibc_version_string using os.confstr."
|
||||
def glibc_version_string_confstr() -> str | None:
|
||||
'Primary implementation of glibc_version_string using os.confstr.'
|
||||
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
|
||||
# to be broken or missing. This strategy is used in the standard library
|
||||
# platform module:
|
||||
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183
|
||||
if sys.platform == "win32":
|
||||
if sys.platform == 'win32':
|
||||
return None
|
||||
try:
|
||||
# os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17":
|
||||
_, version = os.confstr("CS_GNU_LIBC_VERSION").split()
|
||||
_, version = os.confstr('CS_GNU_LIBC_VERSION').split()
|
||||
except (AttributeError, OSError, ValueError):
|
||||
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
|
||||
return None
|
||||
return version
|
||||
|
||||
|
||||
def glibc_version_string_ctypes() -> Optional[str]:
|
||||
"Fallback implementation of glibc_version_string using ctypes."
|
||||
def glibc_version_string_ctypes() -> str | None:
|
||||
'Fallback implementation of glibc_version_string using ctypes.'
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
|
|
@ -53,7 +55,7 @@ def glibc_version_string_ctypes() -> Optional[str]:
|
|||
version_str = gnu_get_libc_version()
|
||||
# py2 / py3 compatibility:
|
||||
if not isinstance(version_str, str):
|
||||
version_str = version_str.decode("ascii")
|
||||
version_str = version_str.decode('ascii')
|
||||
|
||||
return version_str
|
||||
|
||||
|
|
@ -75,7 +77,7 @@ def glibc_version_string_ctypes() -> Optional[str]:
|
|||
# versions that was generated by pip 8.1.2 and earlier is useless and
|
||||
# misleading. Solution: instead of using platform, use our code that actually
|
||||
# works.
|
||||
def libc_ver() -> Tuple[str, str]:
|
||||
def libc_ver() -> tuple[str, str]:
|
||||
"""Try to determine the glibc version
|
||||
|
||||
Returns a tuple of strings (lib, version) which default to empty strings
|
||||
|
|
@ -83,6 +85,6 @@ def libc_ver() -> Tuple[str, str]:
|
|||
"""
|
||||
glibc_version = glibc_version_string()
|
||||
if glibc_version is None:
|
||||
return ("", "")
|
||||
return ('', '')
|
||||
else:
|
||||
return ("glibc", glibc_version)
|
||||
return ('glibc', glibc_version)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
import hashlib
|
||||
from typing import TYPE_CHECKING, BinaryIO, Dict, Iterator, List
|
||||
from __future__ import annotations
|
||||
|
||||
from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError
|
||||
import hashlib
|
||||
from typing import BinaryIO
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pip._internal.exceptions import HashMismatch
|
||||
from pip._internal.exceptions import HashMissing
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.utils.misc import read_chunks
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -14,12 +22,12 @@ if TYPE_CHECKING:
|
|||
|
||||
# The recommended hash algo of the moment. Change this whenever the state of
|
||||
# the art changes; it won't hurt backward compatibility.
|
||||
FAVORITE_HASH = "sha256"
|
||||
FAVORITE_HASH = 'sha256'
|
||||
|
||||
|
||||
# Names of hashlib algorithms allowed by the --hash option and ``pip hash``
|
||||
# Currently, those are the ones at least as collision-resistant as sha256.
|
||||
STRONG_HASHES = ["sha256", "sha384", "sha512"]
|
||||
STRONG_HASHES = ['sha256', 'sha384', 'sha512']
|
||||
|
||||
|
||||
class Hashes:
|
||||
|
|
@ -28,7 +36,7 @@ class Hashes:
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, hashes: Dict[str, List[str]] = None) -> None:
|
||||
def __init__(self, hashes: dict[str, list[str]] = None) -> None:
|
||||
"""
|
||||
:param hashes: A dict of algorithm names pointing to lists of allowed
|
||||
hex digests
|
||||
|
|
@ -40,7 +48,7 @@ class Hashes:
|
|||
allowed[alg] = sorted(keys)
|
||||
self._allowed = allowed
|
||||
|
||||
def __and__(self, other: "Hashes") -> "Hashes":
|
||||
def __and__(self, other: Hashes) -> Hashes:
|
||||
if not isinstance(other, Hashes):
|
||||
return NotImplemented
|
||||
|
||||
|
|
@ -79,7 +87,7 @@ class Hashes:
|
|||
try:
|
||||
gots[hash_name] = hashlib.new(hash_name)
|
||||
except (ValueError, TypeError):
|
||||
raise InstallationError(f"Unknown hash name: {hash_name}")
|
||||
raise InstallationError(f'Unknown hash name: {hash_name}')
|
||||
|
||||
for chunk in chunks:
|
||||
for hash in gots.values():
|
||||
|
|
@ -90,7 +98,7 @@ class Hashes:
|
|||
return
|
||||
self._raise(gots)
|
||||
|
||||
def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
|
||||
def _raise(self, gots: dict[str, _Hash]) -> NoReturn:
|
||||
raise HashMismatch(self._allowed, gots)
|
||||
|
||||
def check_against_file(self, file: BinaryIO) -> None:
|
||||
|
|
@ -102,7 +110,7 @@ class Hashes:
|
|||
return self.check_against_chunks(read_chunks(file))
|
||||
|
||||
def check_against_path(self, path: str) -> None:
|
||||
with open(path, "rb") as file:
|
||||
with open(path, 'rb') as file:
|
||||
return self.check_against_file(file)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
|
|
@ -116,13 +124,13 @@ class Hashes:
|
|||
|
||||
def __hash__(self) -> int:
|
||||
return hash(
|
||||
",".join(
|
||||
','.join(
|
||||
sorted(
|
||||
":".join((alg, digest))
|
||||
':'.join((alg, digest))
|
||||
for alg, digest_list in self._allowed.items()
|
||||
for digest in digest_list
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -140,5 +148,5 @@ class MissingHashes(Hashes):
|
|||
# empty list, it will never match, so an error will always raise.
|
||||
super().__init__(hashes={FAVORITE_HASH: []})
|
||||
|
||||
def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
|
||||
def _raise(self, gots: dict[str, _Hash]) -> NoReturn:
|
||||
raise HashMissing(gots[FAVORITE_HASH].hexdigest())
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ sessions (or whatever) are created after injecting SecureTransport.
|
|||
Note that we only do the injection on macOS, when the linked OpenSSL is too
|
||||
old to handle TLSv1.2.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def inject_securetransport() -> None:
|
||||
# Only relevant on macOS
|
||||
if sys.platform != "darwin":
|
||||
if sys.platform != 'darwin':
|
||||
return
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,33 +1,39 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import errno
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
from dataclasses import dataclass
|
||||
from logging import Filter
|
||||
from typing import IO, Any, ClassVar, Iterator, List, Optional, TextIO, Type
|
||||
from typing import Any
|
||||
from typing import ClassVar
|
||||
from typing import IO
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import TextIO
|
||||
from typing import Type
|
||||
|
||||
from pip._vendor.rich.console import (
|
||||
Console,
|
||||
ConsoleOptions,
|
||||
ConsoleRenderable,
|
||||
RenderResult,
|
||||
)
|
||||
from pip._internal.exceptions import DiagnosticPipError
|
||||
from pip._internal.utils._log import getLogger
|
||||
from pip._internal.utils._log import VERBOSE
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
from pip._vendor.rich.console import Console
|
||||
from pip._vendor.rich.console import ConsoleOptions
|
||||
from pip._vendor.rich.console import ConsoleRenderable
|
||||
from pip._vendor.rich.console import RenderResult
|
||||
from pip._vendor.rich.highlighter import NullHighlighter
|
||||
from pip._vendor.rich.logging import RichHandler
|
||||
from pip._vendor.rich.segment import Segment
|
||||
from pip._vendor.rich.style import Style
|
||||
|
||||
from pip._internal.exceptions import DiagnosticPipError
|
||||
from pip._internal.utils._log import VERBOSE, getLogger
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
|
||||
_log_state = threading.local()
|
||||
subprocess_logger = getLogger("pip.subprocessor")
|
||||
subprocess_logger = getLogger('pip.subprocessor')
|
||||
|
||||
|
||||
class BrokenStdoutLoggingError(Exception):
|
||||
|
|
@ -36,7 +42,7 @@ class BrokenStdoutLoggingError(Exception):
|
|||
"""
|
||||
|
||||
|
||||
def _is_broken_pipe_error(exc_class: Type[BaseException], exc: BaseException) -> bool:
|
||||
def _is_broken_pipe_error(exc_class: type[BaseException], exc: BaseException) -> bool:
|
||||
if exc_class is BrokenPipeError:
|
||||
return True
|
||||
|
||||
|
|
@ -65,11 +71,11 @@ def indent_log(num: int = 2) -> Iterator[None]:
|
|||
|
||||
|
||||
def get_indentation() -> int:
|
||||
return getattr(_log_state, "indentation", 0)
|
||||
return getattr(_log_state, 'indentation', 0)
|
||||
|
||||
|
||||
class IndentingFormatter(logging.Formatter):
|
||||
default_time_format = "%Y-%m-%dT%H:%M:%S"
|
||||
default_time_format = '%Y-%m-%dT%H:%M:%S'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -92,15 +98,15 @@ class IndentingFormatter(logging.Formatter):
|
|||
prefix to add to each line).
|
||||
"""
|
||||
if levelno < logging.WARNING:
|
||||
return ""
|
||||
return ''
|
||||
if formatted.startswith(DEPRECATION_MSG_PREFIX):
|
||||
# Then the message already has a prefix. We don't want it to
|
||||
# look like "WARNING: DEPRECATION: ...."
|
||||
return ""
|
||||
return ''
|
||||
if levelno < logging.ERROR:
|
||||
return "WARNING: "
|
||||
return 'WARNING: '
|
||||
|
||||
return "ERROR: "
|
||||
return 'ERROR: '
|
||||
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
"""
|
||||
|
|
@ -111,11 +117,11 @@ class IndentingFormatter(logging.Formatter):
|
|||
message_start = self.get_message_start(formatted, record.levelno)
|
||||
formatted = message_start + formatted
|
||||
|
||||
prefix = ""
|
||||
prefix = ''
|
||||
if self.add_timestamp:
|
||||
prefix = f"{self.formatTime(record)} "
|
||||
prefix += " " * get_indentation()
|
||||
formatted = "".join([prefix + line for line in formatted.splitlines(True)])
|
||||
prefix = f'{self.formatTime(record)} '
|
||||
prefix += ' ' * get_indentation()
|
||||
formatted = ''.join([prefix + line for line in formatted.splitlines(True)])
|
||||
return formatted
|
||||
|
||||
|
||||
|
|
@ -125,20 +131,20 @@ class IndentedRenderable:
|
|||
indent: int
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
self, console: Console, options: ConsoleOptions,
|
||||
) -> RenderResult:
|
||||
segments = console.render(self.renderable, options)
|
||||
lines = Segment.split_lines(segments)
|
||||
for line in lines:
|
||||
yield Segment(" " * self.indent)
|
||||
yield Segment(' ' * self.indent)
|
||||
yield from line
|
||||
yield Segment("\n")
|
||||
yield Segment('\n')
|
||||
|
||||
|
||||
class RichPipStreamHandler(RichHandler):
|
||||
KEYWORDS: ClassVar[Optional[List[str]]] = []
|
||||
KEYWORDS: ClassVar[list[str] | None] = []
|
||||
|
||||
def __init__(self, stream: Optional[TextIO], no_color: bool) -> None:
|
||||
def __init__(self, stream: TextIO | None, no_color: bool) -> None:
|
||||
super().__init__(
|
||||
console=Console(file=stream, no_color=no_color, soft_wrap=True),
|
||||
show_time=False,
|
||||
|
|
@ -149,27 +155,27 @@ class RichPipStreamHandler(RichHandler):
|
|||
|
||||
# Our custom override on Rich's logger, to make things work as we need them to.
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
style: Optional[Style] = None
|
||||
style: Style | None = None
|
||||
|
||||
# If we are given a diagnostic error to present, present it with indentation.
|
||||
if record.msg == "[present-diagnostic] %s" and len(record.args) == 1:
|
||||
if record.msg == '[present-diagnostic] %s' and len(record.args) == 1:
|
||||
diagnostic_error: DiagnosticPipError = record.args[0] # type: ignore[index]
|
||||
assert isinstance(diagnostic_error, DiagnosticPipError)
|
||||
|
||||
renderable: ConsoleRenderable = IndentedRenderable(
|
||||
diagnostic_error, indent=get_indentation()
|
||||
diagnostic_error, indent=get_indentation(),
|
||||
)
|
||||
else:
|
||||
message = self.format(record)
|
||||
renderable = self.render_message(record, message)
|
||||
if record.levelno is not None:
|
||||
if record.levelno >= logging.ERROR:
|
||||
style = Style(color="red")
|
||||
style = Style(color='red')
|
||||
elif record.levelno >= logging.WARNING:
|
||||
style = Style(color="yellow")
|
||||
style = Style(color='yellow')
|
||||
|
||||
try:
|
||||
self.console.print(renderable, overflow="ignore", crop=False, style=style)
|
||||
self.console.print(renderable, overflow='ignore', crop=False, style=style)
|
||||
except Exception:
|
||||
self.handleError(record)
|
||||
|
||||
|
|
@ -182,10 +188,10 @@ class RichPipStreamHandler(RichHandler):
|
|||
# exception so we can handle it in main() instead of logging the
|
||||
# broken pipe error and continuing.
|
||||
if (
|
||||
exc_class
|
||||
and exc
|
||||
and self.console.file is sys.stdout
|
||||
and _is_broken_pipe_error(exc_class, exc)
|
||||
exc_class and
|
||||
exc and
|
||||
self.console.file is sys.stdout and
|
||||
_is_broken_pipe_error(exc_class, exc)
|
||||
):
|
||||
raise BrokenStdoutLoggingError()
|
||||
|
||||
|
|
@ -218,7 +224,7 @@ class ExcludeLoggerFilter(Filter):
|
|||
return not super().filter(record)
|
||||
|
||||
|
||||
def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str]) -> int:
|
||||
def setup_logging(verbosity: int, no_color: bool, user_log_file: str | None) -> int:
|
||||
"""Configures and sets up all of the logging
|
||||
|
||||
Returns the requested logging level, as its integer value.
|
||||
|
|
@ -245,99 +251,99 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str])
|
|||
include_user_log = user_log_file is not None
|
||||
if include_user_log:
|
||||
additional_log_file = user_log_file
|
||||
root_level = "DEBUG"
|
||||
root_level = 'DEBUG'
|
||||
else:
|
||||
additional_log_file = "/dev/null"
|
||||
additional_log_file = '/dev/null'
|
||||
root_level = level
|
||||
|
||||
# Disable any logging besides WARNING unless we have DEBUG level logging
|
||||
# enabled for vendored libraries.
|
||||
vendored_log_level = "WARNING" if level in ["INFO", "ERROR"] else "DEBUG"
|
||||
vendored_log_level = 'WARNING' if level in ['INFO', 'ERROR'] else 'DEBUG'
|
||||
|
||||
# Shorthands for clarity
|
||||
log_streams = {
|
||||
"stdout": "ext://sys.stdout",
|
||||
"stderr": "ext://sys.stderr",
|
||||
'stdout': 'ext://sys.stdout',
|
||||
'stderr': 'ext://sys.stderr',
|
||||
}
|
||||
handler_classes = {
|
||||
"stream": "pip._internal.utils.logging.RichPipStreamHandler",
|
||||
"file": "pip._internal.utils.logging.BetterRotatingFileHandler",
|
||||
'stream': 'pip._internal.utils.logging.RichPipStreamHandler',
|
||||
'file': 'pip._internal.utils.logging.BetterRotatingFileHandler',
|
||||
}
|
||||
handlers = ["console", "console_errors", "console_subprocess"] + (
|
||||
["user_log"] if include_user_log else []
|
||||
handlers = ['console', 'console_errors', 'console_subprocess'] + (
|
||||
['user_log'] if include_user_log else []
|
||||
)
|
||||
|
||||
logging.config.dictConfig(
|
||||
{
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"filters": {
|
||||
"exclude_warnings": {
|
||||
"()": "pip._internal.utils.logging.MaxLevelFilter",
|
||||
"level": logging.WARNING,
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'filters': {
|
||||
'exclude_warnings': {
|
||||
'()': 'pip._internal.utils.logging.MaxLevelFilter',
|
||||
'level': logging.WARNING,
|
||||
},
|
||||
"restrict_to_subprocess": {
|
||||
"()": "logging.Filter",
|
||||
"name": subprocess_logger.name,
|
||||
'restrict_to_subprocess': {
|
||||
'()': 'logging.Filter',
|
||||
'name': subprocess_logger.name,
|
||||
},
|
||||
"exclude_subprocess": {
|
||||
"()": "pip._internal.utils.logging.ExcludeLoggerFilter",
|
||||
"name": subprocess_logger.name,
|
||||
'exclude_subprocess': {
|
||||
'()': 'pip._internal.utils.logging.ExcludeLoggerFilter',
|
||||
'name': subprocess_logger.name,
|
||||
},
|
||||
},
|
||||
"formatters": {
|
||||
"indent": {
|
||||
"()": IndentingFormatter,
|
||||
"format": "%(message)s",
|
||||
'formatters': {
|
||||
'indent': {
|
||||
'()': IndentingFormatter,
|
||||
'format': '%(message)s',
|
||||
},
|
||||
"indent_with_timestamp": {
|
||||
"()": IndentingFormatter,
|
||||
"format": "%(message)s",
|
||||
"add_timestamp": True,
|
||||
'indent_with_timestamp': {
|
||||
'()': IndentingFormatter,
|
||||
'format': '%(message)s',
|
||||
'add_timestamp': True,
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"console": {
|
||||
"level": level,
|
||||
"class": handler_classes["stream"],
|
||||
"no_color": no_color,
|
||||
"stream": log_streams["stdout"],
|
||||
"filters": ["exclude_subprocess", "exclude_warnings"],
|
||||
"formatter": "indent",
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': level,
|
||||
'class': handler_classes['stream'],
|
||||
'no_color': no_color,
|
||||
'stream': log_streams['stdout'],
|
||||
'filters': ['exclude_subprocess', 'exclude_warnings'],
|
||||
'formatter': 'indent',
|
||||
},
|
||||
"console_errors": {
|
||||
"level": "WARNING",
|
||||
"class": handler_classes["stream"],
|
||||
"no_color": no_color,
|
||||
"stream": log_streams["stderr"],
|
||||
"filters": ["exclude_subprocess"],
|
||||
"formatter": "indent",
|
||||
'console_errors': {
|
||||
'level': 'WARNING',
|
||||
'class': handler_classes['stream'],
|
||||
'no_color': no_color,
|
||||
'stream': log_streams['stderr'],
|
||||
'filters': ['exclude_subprocess'],
|
||||
'formatter': 'indent',
|
||||
},
|
||||
# A handler responsible for logging to the console messages
|
||||
# from the "subprocessor" logger.
|
||||
"console_subprocess": {
|
||||
"level": level,
|
||||
"class": handler_classes["stream"],
|
||||
"stream": log_streams["stderr"],
|
||||
"no_color": no_color,
|
||||
"filters": ["restrict_to_subprocess"],
|
||||
"formatter": "indent",
|
||||
'console_subprocess': {
|
||||
'level': level,
|
||||
'class': handler_classes['stream'],
|
||||
'stream': log_streams['stderr'],
|
||||
'no_color': no_color,
|
||||
'filters': ['restrict_to_subprocess'],
|
||||
'formatter': 'indent',
|
||||
},
|
||||
"user_log": {
|
||||
"level": "DEBUG",
|
||||
"class": handler_classes["file"],
|
||||
"filename": additional_log_file,
|
||||
"encoding": "utf-8",
|
||||
"delay": True,
|
||||
"formatter": "indent_with_timestamp",
|
||||
'user_log': {
|
||||
'level': 'DEBUG',
|
||||
'class': handler_classes['file'],
|
||||
'filename': additional_log_file,
|
||||
'encoding': 'utf-8',
|
||||
'delay': True,
|
||||
'formatter': 'indent_with_timestamp',
|
||||
},
|
||||
},
|
||||
"root": {
|
||||
"level": root_level,
|
||||
"handlers": handlers,
|
||||
'root': {
|
||||
'level': root_level,
|
||||
'handlers': handlers,
|
||||
},
|
||||
"loggers": {"pip._vendor": {"level": vendored_log_level}},
|
||||
}
|
||||
'loggers': {'pip._vendor': {'level': vendored_log_level}},
|
||||
},
|
||||
)
|
||||
|
||||
return level_number
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import errno
|
||||
|
|
@ -14,69 +15,70 @@ import stat
|
|||
import sys
|
||||
import urllib.parse
|
||||
from io import StringIO
|
||||
from itertools import filterfalse, tee, zip_longest
|
||||
from itertools import filterfalse
|
||||
from itertools import tee
|
||||
from itertools import zip_longest
|
||||
from types import TracebackType
|
||||
from typing import (
|
||||
Any,
|
||||
BinaryIO,
|
||||
Callable,
|
||||
ContextManager,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
TextIO,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
cast,
|
||||
)
|
||||
|
||||
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
|
||||
from typing import Any
|
||||
from typing import BinaryIO
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import ContextManager
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import TextIO
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
|
||||
from pip import __version__
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.locations import get_major_minor_version
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
from pip._vendor.tenacity import retry
|
||||
from pip._vendor.tenacity import stop_after_delay
|
||||
from pip._vendor.tenacity import wait_fixed
|
||||
|
||||
__all__ = [
|
||||
"rmtree",
|
||||
"display_path",
|
||||
"backup_dir",
|
||||
"ask",
|
||||
"splitext",
|
||||
"format_size",
|
||||
"is_installable_dir",
|
||||
"normalize_path",
|
||||
"renames",
|
||||
"get_prog",
|
||||
"captured_stdout",
|
||||
"ensure_dir",
|
||||
"remove_auth_from_url",
|
||||
'rmtree',
|
||||
'display_path',
|
||||
'backup_dir',
|
||||
'ask',
|
||||
'splitext',
|
||||
'format_size',
|
||||
'is_installable_dir',
|
||||
'normalize_path',
|
||||
'renames',
|
||||
'get_prog',
|
||||
'captured_stdout',
|
||||
'ensure_dir',
|
||||
'remove_auth_from_url',
|
||||
]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
T = TypeVar("T")
|
||||
T = TypeVar('T')
|
||||
ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType]
|
||||
VersionInfo = Tuple[int, int, int]
|
||||
NetlocTuple = Tuple[str, Tuple[Optional[str], Optional[str]]]
|
||||
|
||||
|
||||
def get_pip_version() -> str:
|
||||
pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
pip_pkg_dir = os.path.join(os.path.dirname(__file__), '..', '..')
|
||||
pip_pkg_dir = os.path.abspath(pip_pkg_dir)
|
||||
|
||||
return "pip {} from {} (python {})".format(
|
||||
return 'pip {} from {} (python {})'.format(
|
||||
__version__,
|
||||
pip_pkg_dir,
|
||||
get_major_minor_version(),
|
||||
)
|
||||
|
||||
|
||||
def normalize_version_info(py_version_info: Tuple[int, ...]) -> Tuple[int, int, int]:
|
||||
def normalize_version_info(py_version_info: tuple[int, ...]) -> tuple[int, int, int]:
|
||||
"""
|
||||
Convert a tuple of ints representing a Python version to one of length
|
||||
three.
|
||||
|
|
@ -92,7 +94,7 @@ def normalize_version_info(py_version_info: Tuple[int, ...]) -> Tuple[int, int,
|
|||
elif len(py_version_info) > 3:
|
||||
py_version_info = py_version_info[:3]
|
||||
|
||||
return cast("VersionInfo", py_version_info)
|
||||
return cast('VersionInfo', py_version_info)
|
||||
|
||||
|
||||
def ensure_dir(path: str) -> None:
|
||||
|
|
@ -108,13 +110,13 @@ def ensure_dir(path: str) -> None:
|
|||
def get_prog() -> str:
|
||||
try:
|
||||
prog = os.path.basename(sys.argv[0])
|
||||
if prog in ("__main__.py", "-c"):
|
||||
return f"{sys.executable} -m pip"
|
||||
if prog in ('__main__.py', '-c'):
|
||||
return f'{sys.executable} -m pip'
|
||||
else:
|
||||
return prog
|
||||
except (AttributeError, TypeError, IndexError):
|
||||
pass
|
||||
return "pip"
|
||||
return 'pip'
|
||||
|
||||
|
||||
# Retry every half second for up to 3 seconds
|
||||
|
|
@ -149,11 +151,11 @@ def display_path(path: str) -> str:
|
|||
if possible."""
|
||||
path = os.path.normcase(os.path.abspath(path))
|
||||
if path.startswith(os.getcwd() + os.path.sep):
|
||||
path = "." + path[len(os.getcwd()) :]
|
||||
path = '.' + path[len(os.getcwd()):]
|
||||
return path
|
||||
|
||||
|
||||
def backup_dir(dir: str, ext: str = ".bak") -> str:
|
||||
def backup_dir(dir: str, ext: str = '.bak') -> str:
|
||||
"""Figure out the name of a directory to back up the given dir to
|
||||
(adding .bak, .bak2, etc)"""
|
||||
n = 1
|
||||
|
|
@ -165,7 +167,7 @@ def backup_dir(dir: str, ext: str = ".bak") -> str:
|
|||
|
||||
|
||||
def ask_path_exists(message: str, options: Iterable[str]) -> str:
|
||||
for action in os.environ.get("PIP_EXISTS_ACTION", "").split():
|
||||
for action in os.environ.get('PIP_EXISTS_ACTION', '').split():
|
||||
if action in options:
|
||||
return action
|
||||
return ask(message, options)
|
||||
|
|
@ -173,9 +175,9 @@ def ask_path_exists(message: str, options: Iterable[str]) -> str:
|
|||
|
||||
def _check_no_input(message: str) -> None:
|
||||
"""Raise an error if no input is allowed."""
|
||||
if os.environ.get("PIP_NO_INPUT"):
|
||||
if os.environ.get('PIP_NO_INPUT'):
|
||||
raise Exception(
|
||||
f"No input was expected ($PIP_NO_INPUT set); question: {message}"
|
||||
f'No input was expected ($PIP_NO_INPUT set); question: {message}',
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -187,8 +189,8 @@ def ask(message: str, options: Iterable[str]) -> str:
|
|||
response = response.strip().lower()
|
||||
if response not in options:
|
||||
print(
|
||||
"Your response ({!r}) was not one of the expected responses: "
|
||||
"{}".format(response, ", ".join(options))
|
||||
'Your response ({!r}) was not one of the expected responses: '
|
||||
'{}'.format(response, ', '.join(options)),
|
||||
)
|
||||
else:
|
||||
return response
|
||||
|
|
@ -214,26 +216,26 @@ def strtobool(val: str) -> int:
|
|||
'val' is anything else.
|
||||
"""
|
||||
val = val.lower()
|
||||
if val in ("y", "yes", "t", "true", "on", "1"):
|
||||
if val in ('y', 'yes', 't', 'true', 'on', '1'):
|
||||
return 1
|
||||
elif val in ("n", "no", "f", "false", "off", "0"):
|
||||
elif val in ('n', 'no', 'f', 'false', 'off', '0'):
|
||||
return 0
|
||||
else:
|
||||
raise ValueError(f"invalid truth value {val!r}")
|
||||
raise ValueError(f'invalid truth value {val!r}')
|
||||
|
||||
|
||||
def format_size(bytes: float) -> str:
|
||||
if bytes > 1000 * 1000:
|
||||
return "{:.1f} MB".format(bytes / 1000.0 / 1000)
|
||||
return f'{bytes / 1000.0 / 1000:.1f} MB'
|
||||
elif bytes > 10 * 1000:
|
||||
return "{} kB".format(int(bytes / 1000))
|
||||
return f'{int(bytes / 1000)} kB'
|
||||
elif bytes > 1000:
|
||||
return "{:.1f} kB".format(bytes / 1000.0)
|
||||
return f'{bytes / 1000.0:.1f} kB'
|
||||
else:
|
||||
return "{} bytes".format(int(bytes))
|
||||
return f'{int(bytes)} bytes'
|
||||
|
||||
|
||||
def tabulate(rows: Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]:
|
||||
def tabulate(rows: Iterable[Iterable[Any]]) -> tuple[list[str], list[int]]:
|
||||
"""Return a list of formatted rows and a list of column sizes.
|
||||
|
||||
For example::
|
||||
|
|
@ -242,8 +244,8 @@ def tabulate(rows: Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]:
|
|||
(['foobar 2000', '3735928559'], [10, 4])
|
||||
"""
|
||||
rows = [tuple(map(str, row)) for row in rows]
|
||||
sizes = [max(map(len, col)) for col in zip_longest(*rows, fillvalue="")]
|
||||
table = [" ".join(map(str.ljust, row, sizes)).rstrip() for row in rows]
|
||||
sizes = [max(map(len, col)) for col in zip_longest(*rows, fillvalue='')]
|
||||
table = [' '.join(map(str.ljust, row, sizes)).rstrip() for row in rows]
|
||||
return table, sizes
|
||||
|
||||
|
||||
|
|
@ -257,9 +259,9 @@ def is_installable_dir(path: str) -> bool:
|
|||
"""
|
||||
if not os.path.isdir(path):
|
||||
return False
|
||||
if os.path.isfile(os.path.join(path, "pyproject.toml")):
|
||||
if os.path.isfile(os.path.join(path, 'pyproject.toml')):
|
||||
return True
|
||||
if os.path.isfile(os.path.join(path, "setup.py")):
|
||||
if os.path.isfile(os.path.join(path, 'setup.py')):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -286,10 +288,10 @@ def normalize_path(path: str, resolve_symlinks: bool = True) -> str:
|
|||
return os.path.normcase(path)
|
||||
|
||||
|
||||
def splitext(path: str) -> Tuple[str, str]:
|
||||
def splitext(path: str) -> tuple[str, str]:
|
||||
"""Like os.path.splitext, but take off .tar too"""
|
||||
base, ext = posixpath.splitext(path)
|
||||
if base.lower().endswith(".tar"):
|
||||
if base.lower().endswith('.tar'):
|
||||
ext = base[-4:] + ext
|
||||
base = base[:-4]
|
||||
return base, ext
|
||||
|
|
@ -334,7 +336,7 @@ class StreamWrapper(StringIO):
|
|||
orig_stream: TextIO = None
|
||||
|
||||
@classmethod
|
||||
def from_stream(cls, orig_stream: TextIO) -> "StreamWrapper":
|
||||
def from_stream(cls, orig_stream: TextIO) -> StreamWrapper:
|
||||
cls.orig_stream = orig_stream
|
||||
return cls()
|
||||
|
||||
|
|
@ -369,47 +371,47 @@ def captured_stdout() -> ContextManager[StreamWrapper]:
|
|||
|
||||
Taken from Lib/support/__init__.py in the CPython repo.
|
||||
"""
|
||||
return captured_output("stdout")
|
||||
return captured_output('stdout')
|
||||
|
||||
|
||||
def captured_stderr() -> ContextManager[StreamWrapper]:
|
||||
"""
|
||||
See captured_stdout().
|
||||
"""
|
||||
return captured_output("stderr")
|
||||
return captured_output('stderr')
|
||||
|
||||
|
||||
# Simulates an enum
|
||||
def enum(*sequential: Any, **named: Any) -> Type[Any]:
|
||||
def enum(*sequential: Any, **named: Any) -> type[Any]:
|
||||
enums = dict(zip(sequential, range(len(sequential))), **named)
|
||||
reverse = {value: key for key, value in enums.items()}
|
||||
enums["reverse_mapping"] = reverse
|
||||
return type("Enum", (), enums)
|
||||
enums['reverse_mapping'] = reverse
|
||||
return type('Enum', (), enums)
|
||||
|
||||
|
||||
def build_netloc(host: str, port: Optional[int]) -> str:
|
||||
def build_netloc(host: str, port: int | None) -> str:
|
||||
"""
|
||||
Build a netloc from a host-port pair
|
||||
"""
|
||||
if port is None:
|
||||
return host
|
||||
if ":" in host:
|
||||
if ':' in host:
|
||||
# Only wrap host with square brackets when it is IPv6
|
||||
host = f"[{host}]"
|
||||
return f"{host}:{port}"
|
||||
host = f'[{host}]'
|
||||
return f'{host}:{port}'
|
||||
|
||||
|
||||
def build_url_from_netloc(netloc: str, scheme: str = "https") -> str:
|
||||
def build_url_from_netloc(netloc: str, scheme: str = 'https') -> str:
|
||||
"""
|
||||
Build a full URL from a netloc.
|
||||
"""
|
||||
if netloc.count(":") >= 2 and "@" not in netloc and "[" not in netloc:
|
||||
if netloc.count(':') >= 2 and '@' not in netloc and '[' not in netloc:
|
||||
# It must be a bare IPv6 address, so wrap it with brackets.
|
||||
netloc = f"[{netloc}]"
|
||||
return f"{scheme}://{netloc}"
|
||||
netloc = f'[{netloc}]'
|
||||
return f'{scheme}://{netloc}'
|
||||
|
||||
|
||||
def parse_netloc(netloc: str) -> Tuple[str, Optional[int]]:
|
||||
def parse_netloc(netloc: str) -> tuple[str, int | None]:
|
||||
"""
|
||||
Return the host-port pair from a netloc.
|
||||
"""
|
||||
|
|
@ -424,19 +426,19 @@ def split_auth_from_netloc(netloc: str) -> NetlocTuple:
|
|||
|
||||
Returns: (netloc, (username, password)).
|
||||
"""
|
||||
if "@" not in netloc:
|
||||
if '@' not in netloc:
|
||||
return netloc, (None, None)
|
||||
|
||||
# Split from the right because that's how urllib.parse.urlsplit()
|
||||
# behaves if more than one @ is present (which can be checked using
|
||||
# the password attribute of urlsplit()'s return value).
|
||||
auth, netloc = netloc.rsplit("@", 1)
|
||||
pw: Optional[str] = None
|
||||
if ":" in auth:
|
||||
auth, netloc = netloc.rsplit('@', 1)
|
||||
pw: str | None = None
|
||||
if ':' in auth:
|
||||
# Split from the left because that's how urllib.parse.urlsplit()
|
||||
# behaves if more than one : is present (which again can be checked
|
||||
# using the password attribute of the return value)
|
||||
user, pw = auth.split(":", 1)
|
||||
user, pw = auth.split(':', 1)
|
||||
else:
|
||||
user, pw = auth, None
|
||||
|
||||
|
|
@ -459,19 +461,19 @@ def redact_netloc(netloc: str) -> str:
|
|||
if user is None:
|
||||
return netloc
|
||||
if password is None:
|
||||
user = "****"
|
||||
password = ""
|
||||
user = '****'
|
||||
password = ''
|
||||
else:
|
||||
user = urllib.parse.quote(user)
|
||||
password = ":****"
|
||||
return "{user}{password}@{netloc}".format(
|
||||
user=user, password=password, netloc=netloc
|
||||
password = ':****'
|
||||
return '{user}{password}@{netloc}'.format(
|
||||
user=user, password=password, netloc=netloc,
|
||||
)
|
||||
|
||||
|
||||
def _transform_url(
|
||||
url: str, transform_netloc: Callable[[str], Tuple[Any, ...]]
|
||||
) -> Tuple[str, NetlocTuple]:
|
||||
url: str, transform_netloc: Callable[[str], tuple[Any, ...]],
|
||||
) -> tuple[str, NetlocTuple]:
|
||||
"""Transform and replace netloc in a url.
|
||||
|
||||
transform_netloc is a function taking the netloc and returning a
|
||||
|
|
@ -486,18 +488,18 @@ def _transform_url(
|
|||
# stripped url
|
||||
url_pieces = (purl.scheme, netloc_tuple[0], purl.path, purl.query, purl.fragment)
|
||||
surl = urllib.parse.urlunsplit(url_pieces)
|
||||
return surl, cast("NetlocTuple", netloc_tuple)
|
||||
return surl, cast('NetlocTuple', netloc_tuple)
|
||||
|
||||
|
||||
def _get_netloc(netloc: str) -> NetlocTuple:
|
||||
return split_auth_from_netloc(netloc)
|
||||
|
||||
|
||||
def _redact_netloc(netloc: str) -> Tuple[str]:
|
||||
def _redact_netloc(netloc: str) -> tuple[str]:
|
||||
return (redact_netloc(netloc),)
|
||||
|
||||
|
||||
def split_auth_netloc_from_url(url: str) -> Tuple[str, str, Tuple[str, str]]:
|
||||
def split_auth_netloc_from_url(url: str) -> tuple[str, str, tuple[str, str]]:
|
||||
"""
|
||||
Parse a url into separate netloc, auth, and url with no auth.
|
||||
|
||||
|
|
@ -525,7 +527,7 @@ class HiddenText:
|
|||
self.redacted = redacted
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<HiddenText {!r}>".format(str(self))
|
||||
return f'<HiddenText {str(self)!r}>'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.redacted
|
||||
|
|
@ -541,7 +543,7 @@ class HiddenText:
|
|||
|
||||
|
||||
def hide_value(value: str) -> HiddenText:
|
||||
return HiddenText(value, redacted="****")
|
||||
return HiddenText(value, redacted='****')
|
||||
|
||||
|
||||
def hide_url(url: str) -> HiddenText:
|
||||
|
|
@ -556,9 +558,9 @@ def protect_pip_from_modification_on_windows(modifying_pip: bool) -> None:
|
|||
python -m pip ...
|
||||
"""
|
||||
pip_names = [
|
||||
"pip.exe",
|
||||
"pip{}.exe".format(sys.version_info[0]),
|
||||
"pip{}.{}.exe".format(*sys.version_info[:2]),
|
||||
'pip.exe',
|
||||
f'pip{sys.version_info[0]}.exe',
|
||||
'pip{}.{}.exe'.format(*sys.version_info[:2]),
|
||||
]
|
||||
|
||||
# See https://github.com/pypa/pip/issues/1299 for more discussion
|
||||
|
|
@ -567,11 +569,11 @@ def protect_pip_from_modification_on_windows(modifying_pip: bool) -> None:
|
|||
)
|
||||
|
||||
if should_show_use_python_msg:
|
||||
new_command = [sys.executable, "-m", "pip"] + sys.argv[1:]
|
||||
new_command = [sys.executable, '-m', 'pip'] + sys.argv[1:]
|
||||
raise CommandError(
|
||||
"To modify pip, please run the following command:\n{}".format(
|
||||
" ".join(new_command)
|
||||
)
|
||||
'To modify pip, please run the following command:\n{}'.format(
|
||||
' '.join(new_command),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -580,12 +582,12 @@ def is_console_interactive() -> bool:
|
|||
return sys.stdin is not None and sys.stdin.isatty()
|
||||
|
||||
|
||||
def hash_file(path: str, blocksize: int = 1 << 20) -> Tuple[Any, int]:
|
||||
def hash_file(path: str, blocksize: int = 1 << 20) -> tuple[Any, int]:
|
||||
"""Return (hash, length) for path using hashlib.sha256()"""
|
||||
|
||||
h = hashlib.sha256()
|
||||
length = 0
|
||||
with open(path, "rb") as f:
|
||||
with open(path, 'rb') as f:
|
||||
for block in read_chunks(f, size=blocksize):
|
||||
length += len(block)
|
||||
h.update(block)
|
||||
|
|
@ -604,7 +606,7 @@ def is_wheel_installed() -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]:
|
||||
def pairwise(iterable: Iterable[Any]) -> Iterator[tuple[Any, Any]]:
|
||||
"""
|
||||
Return paired elements.
|
||||
|
||||
|
|
@ -618,7 +620,7 @@ def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]:
|
|||
def partition(
|
||||
pred: Callable[[T], bool],
|
||||
iterable: Iterable[T],
|
||||
) -> Tuple[Iterable[T], Iterable[T]]:
|
||||
) -> tuple[Iterable[T], Iterable[T]]:
|
||||
"""
|
||||
Use a predicate to partition entries into false entries and true entries,
|
||||
like
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
"""Utilities for defining models
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import operator
|
||||
from typing import Any, Callable, Type
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Type
|
||||
|
||||
|
||||
class KeyBasedCompareMixin:
|
||||
"""Provides comparison capabilities that is based on a key"""
|
||||
|
||||
__slots__ = ["_compare_key", "_defining_class"]
|
||||
__slots__ = ['_compare_key', '_defining_class']
|
||||
|
||||
def __init__(self, key: Any, defining_class: Type["KeyBasedCompareMixin"]) -> None:
|
||||
def __init__(self, key: Any, defining_class: type[KeyBasedCompareMixin]) -> None:
|
||||
self._compare_key = key
|
||||
self._defining_class = defining_class
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import re
|
||||
from typing import NewType, Optional, Tuple, cast
|
||||
from typing import cast
|
||||
from typing import NewType
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._vendor.packaging import specifiers, version
|
||||
from pip._vendor.packaging import specifiers
|
||||
from pip._vendor.packaging import version
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
|
||||
NormalizedExtra = NewType("NormalizedExtra", str)
|
||||
NormalizedExtra = NewType('NormalizedExtra', str)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def check_requires_python(
|
||||
requires_python: Optional[str], version_info: Tuple[int, ...]
|
||||
requires_python: str | None, version_info: tuple[int, ...],
|
||||
) -> bool:
|
||||
"""
|
||||
Check if the given Python version matches a "Requires-Python" specifier.
|
||||
|
|
@ -30,7 +36,7 @@ def check_requires_python(
|
|||
return True
|
||||
requires_python_specifier = specifiers.SpecifierSet(requires_python)
|
||||
|
||||
python_version = version.parse(".".join(map(str, version_info)))
|
||||
python_version = version.parse('.'.join(map(str, version_info)))
|
||||
return python_version in requires_python_specifier
|
||||
|
||||
|
||||
|
|
@ -54,4 +60,4 @@ def safe_extra(extra: str) -> NormalizedExtra:
|
|||
This function is duplicated from ``pkg_resources``. Note that this is not
|
||||
the same to either ``canonicalize_name`` or ``_egg_link_name``.
|
||||
"""
|
||||
return cast(NormalizedExtra, re.sub("[^A-Za-z0-9.-]+", "_", extra).lower())
|
||||
return cast(NormalizedExtra, re.sub('[^A-Za-z0-9.-]+', '_', extra).lower())
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import textwrap
|
||||
from typing import List, Optional, Sequence
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
# Shim to wrap setup.py invocation with setuptools
|
||||
# Note that __file__ is handled via two {!r} *and* %r, to ensure that paths on
|
||||
|
|
@ -42,7 +46,7 @@ _SETUPTOOLS_SHIM = textwrap.dedent(
|
|||
|
||||
exec(compile(setup_py_code, filename, "exec"))
|
||||
''' % ({!r},), "<pip-setuptools-caller>", "exec"))
|
||||
"""
|
||||
""",
|
||||
).rstrip()
|
||||
|
||||
|
||||
|
|
@ -51,7 +55,7 @@ def make_setuptools_shim_args(
|
|||
global_options: Sequence[str] = None,
|
||||
no_user_config: bool = False,
|
||||
unbuffered_output: bool = False,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
"""
|
||||
Get setuptools command arguments with shim wrapped setup file invocation.
|
||||
|
||||
|
|
@ -63,12 +67,12 @@ def make_setuptools_shim_args(
|
|||
"""
|
||||
args = [sys.executable]
|
||||
if unbuffered_output:
|
||||
args += ["-u"]
|
||||
args += ["-c", _SETUPTOOLS_SHIM.format(setup_py_path)]
|
||||
args += ['-u']
|
||||
args += ['-c', _SETUPTOOLS_SHIM.format(setup_py_path)]
|
||||
if global_options:
|
||||
args += global_options
|
||||
if no_user_config:
|
||||
args += ["--no-user-cfg"]
|
||||
args += ['--no-user-cfg']
|
||||
return args
|
||||
|
||||
|
||||
|
|
@ -77,15 +81,15 @@ def make_setuptools_bdist_wheel_args(
|
|||
global_options: Sequence[str],
|
||||
build_options: Sequence[str],
|
||||
destination_dir: str,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
# NOTE: Eventually, we'd want to also -S to the flags here, when we're
|
||||
# isolating. Currently, it breaks Python in virtualenvs, because it
|
||||
# relies on site.py to find parts of the standard library outside the
|
||||
# virtualenv.
|
||||
args = make_setuptools_shim_args(
|
||||
setup_py_path, global_options=global_options, unbuffered_output=True
|
||||
setup_py_path, global_options=global_options, unbuffered_output=True,
|
||||
)
|
||||
args += ["bdist_wheel", "-d", destination_dir]
|
||||
args += ['bdist_wheel', '-d', destination_dir]
|
||||
args += build_options
|
||||
return args
|
||||
|
||||
|
|
@ -93,11 +97,11 @@ def make_setuptools_bdist_wheel_args(
|
|||
def make_setuptools_clean_args(
|
||||
setup_py_path: str,
|
||||
global_options: Sequence[str],
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
args = make_setuptools_shim_args(
|
||||
setup_py_path, global_options=global_options, unbuffered_output=True
|
||||
setup_py_path, global_options=global_options, unbuffered_output=True,
|
||||
)
|
||||
args += ["clean", "--all"]
|
||||
args += ['clean', '--all']
|
||||
return args
|
||||
|
||||
|
||||
|
|
@ -106,10 +110,10 @@ def make_setuptools_develop_args(
|
|||
global_options: Sequence[str],
|
||||
install_options: Sequence[str],
|
||||
no_user_config: bool,
|
||||
prefix: Optional[str],
|
||||
home: Optional[str],
|
||||
prefix: str | None,
|
||||
home: str | None,
|
||||
use_user_site: bool,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
assert not (use_user_site and prefix)
|
||||
|
||||
args = make_setuptools_shim_args(
|
||||
|
|
@ -118,32 +122,32 @@ def make_setuptools_develop_args(
|
|||
no_user_config=no_user_config,
|
||||
)
|
||||
|
||||
args += ["develop", "--no-deps"]
|
||||
args += ['develop', '--no-deps']
|
||||
|
||||
args += install_options
|
||||
|
||||
if prefix:
|
||||
args += ["--prefix", prefix]
|
||||
args += ['--prefix', prefix]
|
||||
if home is not None:
|
||||
args += ["--install-dir", home]
|
||||
args += ['--install-dir', home]
|
||||
|
||||
if use_user_site:
|
||||
args += ["--user", "--prefix="]
|
||||
args += ['--user', '--prefix=']
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def make_setuptools_egg_info_args(
|
||||
setup_py_path: str,
|
||||
egg_info_dir: Optional[str],
|
||||
egg_info_dir: str | None,
|
||||
no_user_config: bool,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
args = make_setuptools_shim_args(setup_py_path, no_user_config=no_user_config)
|
||||
|
||||
args += ["egg_info"]
|
||||
args += ['egg_info']
|
||||
|
||||
if egg_info_dir:
|
||||
args += ["--egg-base", egg_info_dir]
|
||||
args += ['--egg-base', egg_info_dir]
|
||||
|
||||
return args
|
||||
|
||||
|
|
@ -153,14 +157,14 @@ def make_setuptools_install_args(
|
|||
global_options: Sequence[str],
|
||||
install_options: Sequence[str],
|
||||
record_filename: str,
|
||||
root: Optional[str],
|
||||
prefix: Optional[str],
|
||||
header_dir: Optional[str],
|
||||
home: Optional[str],
|
||||
root: str | None,
|
||||
prefix: str | None,
|
||||
header_dir: str | None,
|
||||
home: str | None,
|
||||
use_user_site: bool,
|
||||
no_user_config: bool,
|
||||
pycompile: bool,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
assert not (use_user_site and prefix)
|
||||
assert not (use_user_site and root)
|
||||
|
||||
|
|
@ -170,25 +174,25 @@ def make_setuptools_install_args(
|
|||
no_user_config=no_user_config,
|
||||
unbuffered_output=True,
|
||||
)
|
||||
args += ["install", "--record", record_filename]
|
||||
args += ["--single-version-externally-managed"]
|
||||
args += ['install', '--record', record_filename]
|
||||
args += ['--single-version-externally-managed']
|
||||
|
||||
if root is not None:
|
||||
args += ["--root", root]
|
||||
args += ['--root', root]
|
||||
if prefix is not None:
|
||||
args += ["--prefix", prefix]
|
||||
args += ['--prefix', prefix]
|
||||
if home is not None:
|
||||
args += ["--home", home]
|
||||
args += ['--home', home]
|
||||
if use_user_site:
|
||||
args += ["--user", "--prefix="]
|
||||
args += ['--user', '--prefix=']
|
||||
|
||||
if pycompile:
|
||||
args += ["--compile"]
|
||||
args += ['--compile']
|
||||
else:
|
||||
args += ["--no-compile"]
|
||||
args += ['--no-compile']
|
||||
|
||||
if header_dir:
|
||||
args += ["--install-headers", header_dir]
|
||||
args += ['--install-headers', header_dir]
|
||||
|
||||
args += install_options
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,25 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Iterable,
|
||||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from pip._vendor.rich.markup import escape
|
||||
|
||||
from pip._internal.cli.spinners import SpinnerInterface, open_spinner
|
||||
from pip._internal.cli.spinners import open_spinner
|
||||
from pip._internal.cli.spinners import SpinnerInterface
|
||||
from pip._internal.exceptions import InstallationSubprocessError
|
||||
from pip._internal.utils.logging import VERBOSE, subprocess_logger
|
||||
from pip._internal.utils.logging import subprocess_logger
|
||||
from pip._internal.utils.logging import VERBOSE
|
||||
from pip._internal.utils.misc import HiddenText
|
||||
from pip._vendor.rich.markup import escape
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# Literal was introduced in Python 3.8.
|
||||
|
|
@ -29,7 +30,7 @@ if TYPE_CHECKING:
|
|||
CommandArgs = List[Union[str, HiddenText]]
|
||||
|
||||
|
||||
def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs:
|
||||
def make_command(*args: str | HiddenText | CommandArgs) -> CommandArgs:
|
||||
"""
|
||||
Create a CommandArgs object.
|
||||
"""
|
||||
|
|
@ -46,7 +47,7 @@ def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs:
|
|||
return command_args
|
||||
|
||||
|
||||
def format_command_args(args: Union[List[str], CommandArgs]) -> str:
|
||||
def format_command_args(args: list[str] | CommandArgs) -> str:
|
||||
"""
|
||||
Format command arguments for display.
|
||||
"""
|
||||
|
|
@ -55,13 +56,13 @@ def format_command_args(args: Union[List[str], CommandArgs]) -> str:
|
|||
# this can trigger a UnicodeDecodeError in Python 2 if the argument
|
||||
# has type unicode and includes a non-ascii character. (The type
|
||||
# checker doesn't ensure the annotations are correct in all cases.)
|
||||
return " ".join(
|
||||
return ' '.join(
|
||||
shlex.quote(str(arg)) if isinstance(arg, HiddenText) else shlex.quote(arg)
|
||||
for arg in args
|
||||
)
|
||||
|
||||
|
||||
def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]:
|
||||
def reveal_command_args(args: list[str] | CommandArgs) -> list[str]:
|
||||
"""
|
||||
Return the arguments in their raw, unredacted form.
|
||||
"""
|
||||
|
|
@ -69,16 +70,16 @@ def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]:
|
|||
|
||||
|
||||
def call_subprocess(
|
||||
cmd: Union[List[str], CommandArgs],
|
||||
cmd: list[str] | CommandArgs,
|
||||
show_stdout: bool = False,
|
||||
cwd: Optional[str] = None,
|
||||
on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise",
|
||||
extra_ok_returncodes: Optional[Iterable[int]] = None,
|
||||
extra_environ: Optional[Mapping[str, Any]] = None,
|
||||
unset_environ: Optional[Iterable[str]] = None,
|
||||
spinner: Optional[SpinnerInterface] = None,
|
||||
log_failed_cmd: Optional[bool] = True,
|
||||
stdout_only: Optional[bool] = False,
|
||||
cwd: str | None = None,
|
||||
on_returncode: Literal["raise", "warn", "ignore"] = 'raise',
|
||||
extra_ok_returncodes: Iterable[int] | None = None,
|
||||
extra_environ: Mapping[str, Any] | None = None,
|
||||
unset_environ: Iterable[str] | None = None,
|
||||
spinner: SpinnerInterface | None = None,
|
||||
log_failed_cmd: bool | None = True,
|
||||
stdout_only: bool | None = False,
|
||||
*,
|
||||
command_desc: str,
|
||||
) -> str:
|
||||
|
|
@ -131,7 +132,7 @@ def call_subprocess(
|
|||
# and we have a spinner.
|
||||
use_spinner = not showing_subprocess and spinner is not None
|
||||
|
||||
log_subprocess("Running command %s", command_desc)
|
||||
log_subprocess('Running command %s', command_desc)
|
||||
env = os.environ.copy()
|
||||
if extra_environ:
|
||||
env.update(extra_environ)
|
||||
|
|
@ -146,12 +147,12 @@ def call_subprocess(
|
|||
stderr=subprocess.STDOUT if not stdout_only else subprocess.PIPE,
|
||||
cwd=cwd,
|
||||
env=env,
|
||||
errors="backslashreplace",
|
||||
errors='backslashreplace',
|
||||
)
|
||||
except Exception as exc:
|
||||
if log_failed_cmd:
|
||||
subprocess_logger.critical(
|
||||
"Error %s while executing command %s",
|
||||
'Error %s while executing command %s',
|
||||
exc,
|
||||
command_desc,
|
||||
)
|
||||
|
|
@ -167,7 +168,7 @@ def call_subprocess(
|
|||
if not line:
|
||||
break
|
||||
line = line.rstrip()
|
||||
all_output.append(line + "\n")
|
||||
all_output.append(line + '\n')
|
||||
|
||||
# Show the line immediately.
|
||||
log_subprocess(line)
|
||||
|
|
@ -180,7 +181,7 @@ def call_subprocess(
|
|||
finally:
|
||||
if proc.stdout:
|
||||
proc.stdout.close()
|
||||
output = "".join(all_output)
|
||||
output = ''.join(all_output)
|
||||
else:
|
||||
# In this mode, stdout and stderr are in different pipes.
|
||||
# We must use communicate() which is the only safe way to read both.
|
||||
|
|
@ -198,41 +199,41 @@ def call_subprocess(
|
|||
if use_spinner:
|
||||
assert spinner
|
||||
if proc_had_error:
|
||||
spinner.finish("error")
|
||||
spinner.finish('error')
|
||||
else:
|
||||
spinner.finish("done")
|
||||
spinner.finish('done')
|
||||
if proc_had_error:
|
||||
if on_returncode == "raise":
|
||||
if on_returncode == 'raise':
|
||||
error = InstallationSubprocessError(
|
||||
command_description=command_desc,
|
||||
exit_code=proc.returncode,
|
||||
output_lines=all_output if not showing_subprocess else None,
|
||||
)
|
||||
if log_failed_cmd:
|
||||
subprocess_logger.error("[present-diagnostic] %s", error)
|
||||
subprocess_logger.error('[present-diagnostic] %s', error)
|
||||
subprocess_logger.verbose(
|
||||
"[bold magenta]full command[/]: [blue]%s[/]",
|
||||
'[bold magenta]full command[/]: [blue]%s[/]',
|
||||
escape(format_command_args(cmd)),
|
||||
extra={"markup": True},
|
||||
extra={'markup': True},
|
||||
)
|
||||
subprocess_logger.verbose(
|
||||
"[bold magenta]cwd[/]: %s",
|
||||
escape(cwd or "[inherit]"),
|
||||
extra={"markup": True},
|
||||
'[bold magenta]cwd[/]: %s',
|
||||
escape(cwd or '[inherit]'),
|
||||
extra={'markup': True},
|
||||
)
|
||||
|
||||
raise error
|
||||
elif on_returncode == "warn":
|
||||
elif on_returncode == 'warn':
|
||||
subprocess_logger.warning(
|
||||
'Command "%s" had error code %s in %s',
|
||||
command_desc,
|
||||
proc.returncode,
|
||||
cwd,
|
||||
)
|
||||
elif on_returncode == "ignore":
|
||||
elif on_returncode == 'ignore':
|
||||
pass
|
||||
else:
|
||||
raise ValueError(f"Invalid value: on_returncode={on_returncode!r}")
|
||||
raise ValueError(f'Invalid value: on_returncode={on_returncode!r}')
|
||||
return output
|
||||
|
||||
|
||||
|
|
@ -244,9 +245,9 @@ def runner_with_spinner_message(message: str) -> Callable[..., None]:
|
|||
"""
|
||||
|
||||
def runner(
|
||||
cmd: List[str],
|
||||
cwd: Optional[str] = None,
|
||||
extra_environ: Optional[Mapping[str, Any]] = None,
|
||||
cmd: list[str],
|
||||
cwd: str | None = None,
|
||||
extra_environ: Mapping[str, Any] | None = None,
|
||||
) -> None:
|
||||
with open_spinner(message) as spinner:
|
||||
call_subprocess(
|
||||
|
|
|
|||
|
|
@ -1,28 +1,37 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import errno
|
||||
import itertools
|
||||
import logging
|
||||
import os.path
|
||||
import tempfile
|
||||
from contextlib import ExitStack, contextmanager
|
||||
from typing import Any, Dict, Iterator, Optional, TypeVar, Union
|
||||
from contextlib import contextmanager
|
||||
from contextlib import ExitStack
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
from typing import Optional
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from pip._internal.utils.misc import enum, rmtree
|
||||
from pip._internal.utils.misc import enum
|
||||
from pip._internal.utils.misc import rmtree
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_T = TypeVar("_T", bound="TempDirectory")
|
||||
_T = TypeVar('_T', bound='TempDirectory')
|
||||
|
||||
|
||||
# Kinds of temporary directories. Only needed for ones that are
|
||||
# globally-managed.
|
||||
tempdir_kinds = enum(
|
||||
BUILD_ENV="build-env",
|
||||
EPHEM_WHEEL_CACHE="ephem-wheel-cache",
|
||||
REQ_BUILD="req-build",
|
||||
BUILD_ENV='build-env',
|
||||
EPHEM_WHEEL_CACHE='ephem-wheel-cache',
|
||||
REQ_BUILD='req-build',
|
||||
)
|
||||
|
||||
|
||||
_tempdir_manager: Optional[ExitStack] = None
|
||||
_tempdir_manager: ExitStack | None = None
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -40,7 +49,7 @@ class TempDirectoryTypeRegistry:
|
|||
"""Manages temp directory behavior"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._should_delete: Dict[str, bool] = {}
|
||||
self._should_delete: dict[str, bool] = {}
|
||||
|
||||
def set_delete(self, kind: str, value: bool) -> None:
|
||||
"""Indicate whether a TempDirectory of the given kind should be
|
||||
|
|
@ -55,7 +64,7 @@ class TempDirectoryTypeRegistry:
|
|||
return self._should_delete.get(kind, True)
|
||||
|
||||
|
||||
_tempdir_registry: Optional[TempDirectoryTypeRegistry] = None
|
||||
_tempdir_registry: TempDirectoryTypeRegistry | None = None
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -102,9 +111,9 @@ class TempDirectory:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
path: Optional[str] = None,
|
||||
delete: Union[bool, None, _Default] = _default,
|
||||
kind: str = "temp",
|
||||
path: str | None = None,
|
||||
delete: bool | None | _Default = _default,
|
||||
kind: str = 'temp',
|
||||
globally_managed: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
|
|
@ -135,11 +144,11 @@ class TempDirectory:
|
|||
|
||||
@property
|
||||
def path(self) -> str:
|
||||
assert not self._deleted, f"Attempted to access deleted path: {self._path}"
|
||||
assert not self._deleted, f'Attempted to access deleted path: {self._path}'
|
||||
return self._path
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.__class__.__name__} {self.path!r}>"
|
||||
return f'<{self.__class__.__name__} {self.path!r}>'
|
||||
|
||||
def __enter__(self: _T) -> _T:
|
||||
return self
|
||||
|
|
@ -161,8 +170,8 @@ class TempDirectory:
|
|||
# symlinked to another directory. This tends to confuse build
|
||||
# scripts, so we canonicalize the path by traversing potential
|
||||
# symlinks here.
|
||||
path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-"))
|
||||
logger.debug("Created temporary directory: %s", path)
|
||||
path = os.path.realpath(tempfile.mkdtemp(prefix=f'pip-{kind}-'))
|
||||
logger.debug('Created temporary directory: %s', path)
|
||||
return path
|
||||
|
||||
def cleanup(self) -> None:
|
||||
|
|
@ -193,10 +202,10 @@ class AdjacentTempDirectory(TempDirectory):
|
|||
# a usable name is found.
|
||||
# pkg_resources raises a different error for .dist-info folder
|
||||
# with leading '-' and invalid metadata
|
||||
LEADING_CHARS = "-~.=%0123456789"
|
||||
LEADING_CHARS = '-~.=%0123456789'
|
||||
|
||||
def __init__(self, original: str, delete: Optional[bool] = None) -> None:
|
||||
self.original = original.rstrip("/\\")
|
||||
def __init__(self, original: str, delete: bool | None = None) -> None:
|
||||
self.original = original.rstrip('/\\')
|
||||
super().__init__(delete=delete)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -210,18 +219,18 @@ class AdjacentTempDirectory(TempDirectory):
|
|||
"""
|
||||
for i in range(1, len(name)):
|
||||
for candidate in itertools.combinations_with_replacement(
|
||||
cls.LEADING_CHARS, i - 1
|
||||
cls.LEADING_CHARS, i - 1,
|
||||
):
|
||||
new_name = "~" + "".join(candidate) + name[i:]
|
||||
new_name = '~' + ''.join(candidate) + name[i:]
|
||||
if new_name != name:
|
||||
yield new_name
|
||||
|
||||
# If we make it this far, we will have to make a longer name
|
||||
for i in range(len(cls.LEADING_CHARS)):
|
||||
for candidate in itertools.combinations_with_replacement(
|
||||
cls.LEADING_CHARS, i
|
||||
cls.LEADING_CHARS, i,
|
||||
):
|
||||
new_name = "~" + "".join(candidate) + name
|
||||
new_name = '~' + ''.join(candidate) + name
|
||||
if new_name != name:
|
||||
yield new_name
|
||||
|
||||
|
|
@ -240,7 +249,7 @@ class AdjacentTempDirectory(TempDirectory):
|
|||
break
|
||||
else:
|
||||
# Final fallback on the default behavior.
|
||||
path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-"))
|
||||
path = os.path.realpath(tempfile.mkdtemp(prefix=f'pip-{kind}-'))
|
||||
|
||||
logger.debug("Created temporary directory: %s", path)
|
||||
logger.debug('Created temporary directory: %s', path)
|
||||
return path
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Utilities related archives.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -7,16 +8,16 @@ import shutil
|
|||
import stat
|
||||
import tarfile
|
||||
import zipfile
|
||||
from typing import Iterable, List, Optional
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from zipfile import ZipInfo
|
||||
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.utils.filetypes import (
|
||||
BZ2_EXTENSIONS,
|
||||
TAR_EXTENSIONS,
|
||||
XZ_EXTENSIONS,
|
||||
ZIP_EXTENSIONS,
|
||||
)
|
||||
from pip._internal.utils.filetypes import BZ2_EXTENSIONS
|
||||
from pip._internal.utils.filetypes import TAR_EXTENSIONS
|
||||
from pip._internal.utils.filetypes import XZ_EXTENSIONS
|
||||
from pip._internal.utils.filetypes import ZIP_EXTENSIONS
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -29,7 +30,7 @@ try:
|
|||
|
||||
SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS
|
||||
except ImportError:
|
||||
logger.debug("bz2 module is not available")
|
||||
logger.debug('bz2 module is not available')
|
||||
|
||||
try:
|
||||
# Only for Python 3.3+
|
||||
|
|
@ -37,7 +38,7 @@ try:
|
|||
|
||||
SUPPORTED_EXTENSIONS += XZ_EXTENSIONS
|
||||
except ImportError:
|
||||
logger.debug("lzma module is not available")
|
||||
logger.debug('lzma module is not available')
|
||||
|
||||
|
||||
def current_umask() -> int:
|
||||
|
|
@ -47,16 +48,16 @@ def current_umask() -> int:
|
|||
return mask
|
||||
|
||||
|
||||
def split_leading_dir(path: str) -> List[str]:
|
||||
path = path.lstrip("/").lstrip("\\")
|
||||
if "/" in path and (
|
||||
("\\" in path and path.find("/") < path.find("\\")) or "\\" not in path
|
||||
def split_leading_dir(path: str) -> list[str]:
|
||||
path = path.lstrip('/').lstrip('\\')
|
||||
if '/' in path and (
|
||||
('\\' in path and path.find('/') < path.find('\\')) or '\\' not in path
|
||||
):
|
||||
return path.split("/", 1)
|
||||
elif "\\" in path:
|
||||
return path.split("\\", 1)
|
||||
return path.split('/', 1)
|
||||
elif '\\' in path:
|
||||
return path.split('\\', 1)
|
||||
else:
|
||||
return [path, ""]
|
||||
return [path, '']
|
||||
|
||||
|
||||
def has_leading_dir(paths: Iterable[str]) -> bool:
|
||||
|
|
@ -110,7 +111,7 @@ def unzip_file(filename: str, location: str, flatten: bool = True) -> None:
|
|||
no-ops per the python docs.
|
||||
"""
|
||||
ensure_dir(location)
|
||||
zipfp = open(filename, "rb")
|
||||
zipfp = open(filename, 'rb')
|
||||
try:
|
||||
zip = zipfile.ZipFile(zipfp, allowZip64=True)
|
||||
leading = has_leading_dir(zip.namelist()) and flatten
|
||||
|
|
@ -123,11 +124,11 @@ def unzip_file(filename: str, location: str, flatten: bool = True) -> None:
|
|||
dir = os.path.dirname(fn)
|
||||
if not is_within_directory(location, fn):
|
||||
message = (
|
||||
"The zip file ({}) has a file ({}) trying to install "
|
||||
"outside target directory ({})"
|
||||
'The zip file ({}) has a file ({}) trying to install '
|
||||
'outside target directory ({})'
|
||||
)
|
||||
raise InstallationError(message.format(filename, fn, location))
|
||||
if fn.endswith("/") or fn.endswith("\\"):
|
||||
if fn.endswith('/') or fn.endswith('\\'):
|
||||
# A directory
|
||||
ensure_dir(fn)
|
||||
else:
|
||||
|
|
@ -136,7 +137,7 @@ def unzip_file(filename: str, location: str, flatten: bool = True) -> None:
|
|||
# chunk of memory for the file's content
|
||||
fp = zip.open(name)
|
||||
try:
|
||||
with open(fn, "wb") as destfp:
|
||||
with open(fn, 'wb') as destfp:
|
||||
shutil.copyfileobj(fp, destfp)
|
||||
finally:
|
||||
fp.close()
|
||||
|
|
@ -156,21 +157,21 @@ def untar_file(filename: str, location: str) -> None:
|
|||
no-ops per the python docs.
|
||||
"""
|
||||
ensure_dir(location)
|
||||
if filename.lower().endswith(".gz") or filename.lower().endswith(".tgz"):
|
||||
mode = "r:gz"
|
||||
if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):
|
||||
mode = 'r:gz'
|
||||
elif filename.lower().endswith(BZ2_EXTENSIONS):
|
||||
mode = "r:bz2"
|
||||
mode = 'r:bz2'
|
||||
elif filename.lower().endswith(XZ_EXTENSIONS):
|
||||
mode = "r:xz"
|
||||
elif filename.lower().endswith(".tar"):
|
||||
mode = "r"
|
||||
mode = 'r:xz'
|
||||
elif filename.lower().endswith('.tar'):
|
||||
mode = 'r'
|
||||
else:
|
||||
logger.warning(
|
||||
"Cannot determine compression type for file %s",
|
||||
'Cannot determine compression type for file %s',
|
||||
filename,
|
||||
)
|
||||
mode = "r:*"
|
||||
tar = tarfile.open(filename, mode, encoding="utf-8")
|
||||
mode = 'r:*'
|
||||
tar = tarfile.open(filename, mode, encoding='utf-8')
|
||||
try:
|
||||
leading = has_leading_dir([member.name for member in tar.getmembers()])
|
||||
for member in tar.getmembers():
|
||||
|
|
@ -180,8 +181,8 @@ def untar_file(filename: str, location: str) -> None:
|
|||
path = os.path.join(location, fn)
|
||||
if not is_within_directory(location, path):
|
||||
message = (
|
||||
"The tar file ({}) has a file ({}) trying to install "
|
||||
"outside target directory ({})"
|
||||
'The tar file ({}) has a file ({}) trying to install '
|
||||
'outside target directory ({})'
|
||||
)
|
||||
raise InstallationError(message.format(filename, path, location))
|
||||
if member.isdir():
|
||||
|
|
@ -194,7 +195,7 @@ def untar_file(filename: str, location: str) -> None:
|
|||
# Some corrupt tar files seem to produce this
|
||||
# (specifically bad symlinks)
|
||||
logger.warning(
|
||||
"In the tar file %s the member %s is invalid: %s",
|
||||
'In the tar file %s the member %s is invalid: %s',
|
||||
filename,
|
||||
member.name,
|
||||
exc,
|
||||
|
|
@ -207,7 +208,7 @@ def untar_file(filename: str, location: str) -> None:
|
|||
# Some corrupt tar files seem to produce this
|
||||
# (specifically bad symlinks)
|
||||
logger.warning(
|
||||
"In the tar file %s the member %s is invalid: %s",
|
||||
'In the tar file %s the member %s is invalid: %s',
|
||||
filename,
|
||||
member.name,
|
||||
exc,
|
||||
|
|
@ -215,7 +216,7 @@ def untar_file(filename: str, location: str) -> None:
|
|||
continue
|
||||
ensure_dir(os.path.dirname(path))
|
||||
assert fp is not None
|
||||
with open(path, "wb") as destfp:
|
||||
with open(path, 'wb') as destfp:
|
||||
shutil.copyfileobj(fp, destfp)
|
||||
fp.close()
|
||||
# Update the timestamp (useful for cython compiled files)
|
||||
|
|
@ -230,29 +231,29 @@ def untar_file(filename: str, location: str) -> None:
|
|||
def unpack_file(
|
||||
filename: str,
|
||||
location: str,
|
||||
content_type: Optional[str] = None,
|
||||
content_type: str | None = None,
|
||||
) -> None:
|
||||
filename = os.path.realpath(filename)
|
||||
if (
|
||||
content_type == "application/zip"
|
||||
or filename.lower().endswith(ZIP_EXTENSIONS)
|
||||
or zipfile.is_zipfile(filename)
|
||||
content_type == 'application/zip' or
|
||||
filename.lower().endswith(ZIP_EXTENSIONS) or
|
||||
zipfile.is_zipfile(filename)
|
||||
):
|
||||
unzip_file(filename, location, flatten=not filename.endswith(".whl"))
|
||||
unzip_file(filename, location, flatten=not filename.endswith('.whl'))
|
||||
elif (
|
||||
content_type == "application/x-gzip"
|
||||
or tarfile.is_tarfile(filename)
|
||||
or filename.lower().endswith(TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS)
|
||||
content_type == 'application/x-gzip' or
|
||||
tarfile.is_tarfile(filename) or
|
||||
filename.lower().endswith(TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS)
|
||||
):
|
||||
untar_file(filename, location)
|
||||
else:
|
||||
# FIXME: handle?
|
||||
# FIXME: magic signatures?
|
||||
logger.critical(
|
||||
"Cannot unpack file %s (downloaded from %s, content-type: %s); "
|
||||
"cannot detect archive format",
|
||||
'Cannot unpack file %s (downloaded from %s, content-type: %s); '
|
||||
'cannot detect archive format',
|
||||
filename,
|
||||
location,
|
||||
content_type,
|
||||
)
|
||||
raise InstallationError(f"Cannot determine archive format of {location}")
|
||||
raise InstallationError(f'Cannot determine archive format of {location}')
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import string
|
||||
import urllib.parse
|
||||
|
|
@ -7,10 +9,10 @@ from typing import Optional
|
|||
from .compat import WINDOWS
|
||||
|
||||
|
||||
def get_url_scheme(url: str) -> Optional[str]:
|
||||
if ":" not in url:
|
||||
def get_url_scheme(url: str) -> str | None:
|
||||
if ':' not in url:
|
||||
return None
|
||||
return url.split(":", 1)[0].lower()
|
||||
return url.split(':', 1)[0].lower()
|
||||
|
||||
|
||||
def path_to_url(path: str) -> str:
|
||||
|
|
@ -19,7 +21,7 @@ def path_to_url(path: str) -> str:
|
|||
quoted path parts.
|
||||
"""
|
||||
path = os.path.normpath(os.path.abspath(path))
|
||||
url = urllib.parse.urljoin("file:", urllib.request.pathname2url(path))
|
||||
url = urllib.parse.urljoin('file:', urllib.request.pathname2url(path))
|
||||
return url
|
||||
|
||||
|
||||
|
|
@ -28,20 +30,20 @@ def url_to_path(url: str) -> str:
|
|||
Convert a file: URL to a path.
|
||||
"""
|
||||
assert url.startswith(
|
||||
"file:"
|
||||
), f"You can only turn file: urls into filenames (not {url!r})"
|
||||
'file:',
|
||||
), f'You can only turn file: urls into filenames (not {url!r})'
|
||||
|
||||
_, netloc, path, _, _ = urllib.parse.urlsplit(url)
|
||||
|
||||
if not netloc or netloc == "localhost":
|
||||
if not netloc or netloc == 'localhost':
|
||||
# According to RFC 8089, same as empty authority.
|
||||
netloc = ""
|
||||
netloc = ''
|
||||
elif WINDOWS:
|
||||
# If we have a UNC path, prepend UNC share notation.
|
||||
netloc = "\\\\" + netloc
|
||||
netloc = '\\\\' + netloc
|
||||
else:
|
||||
raise ValueError(
|
||||
f"non-local file URIs are not supported on this platform: {url!r}"
|
||||
f'non-local file URIs are not supported on this platform: {url!r}',
|
||||
)
|
||||
|
||||
path = urllib.request.url2pathname(netloc + path)
|
||||
|
|
@ -50,12 +52,12 @@ def url_to_path(url: str) -> str:
|
|||
# This creates issues for path-related functions like io.open(), so we try
|
||||
# to detect and strip the leading slash.
|
||||
if (
|
||||
WINDOWS
|
||||
and not netloc # Not UNC.
|
||||
and len(path) >= 3
|
||||
and path[0] == "/" # Leading slash to strip.
|
||||
and path[1] in string.ascii_letters # Drive letter.
|
||||
and path[2:4] in (":", ":/") # Colon + end of string, or colon + absolute path.
|
||||
WINDOWS and
|
||||
not netloc and # Not UNC.
|
||||
len(path) >= 3 and
|
||||
path[0] == '/' and # Leading slash to strip.
|
||||
path[1] in string.ascii_letters and # Drive letter.
|
||||
path[2:4] in (':', ':/') # Colon + end of string, or colon + absolute path.
|
||||
):
|
||||
path = path[1:]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import site
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_INCLUDE_SYSTEM_SITE_PACKAGES_REGEX = re.compile(
|
||||
r"include-system-site-packages\s*=\s*(?P<value>true|false)"
|
||||
r'include-system-site-packages\s*=\s*(?P<value>true|false)',
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -16,7 +19,7 @@ def _running_under_venv() -> bool:
|
|||
|
||||
This handles PEP 405 compliant virtual environments.
|
||||
"""
|
||||
return sys.prefix != getattr(sys, "base_prefix", sys.prefix)
|
||||
return sys.prefix != getattr(sys, 'base_prefix', sys.prefix)
|
||||
|
||||
|
||||
def _running_under_regular_virtualenv() -> bool:
|
||||
|
|
@ -25,7 +28,7 @@ def _running_under_regular_virtualenv() -> bool:
|
|||
This handles virtual environments created with pypa's virtualenv.
|
||||
"""
|
||||
# pypa/virtualenv case
|
||||
return hasattr(sys, "real_prefix")
|
||||
return hasattr(sys, 'real_prefix')
|
||||
|
||||
|
||||
def running_under_virtualenv() -> bool:
|
||||
|
|
@ -33,16 +36,16 @@ def running_under_virtualenv() -> bool:
|
|||
return _running_under_venv() or _running_under_regular_virtualenv()
|
||||
|
||||
|
||||
def _get_pyvenv_cfg_lines() -> Optional[List[str]]:
|
||||
def _get_pyvenv_cfg_lines() -> list[str] | None:
|
||||
"""Reads {sys.prefix}/pyvenv.cfg and returns its contents as list of lines
|
||||
|
||||
Returns None, if it could not read/access the file.
|
||||
"""
|
||||
pyvenv_cfg_file = os.path.join(sys.prefix, "pyvenv.cfg")
|
||||
pyvenv_cfg_file = os.path.join(sys.prefix, 'pyvenv.cfg')
|
||||
try:
|
||||
# Although PEP 405 does not specify, the built-in venv module always
|
||||
# writes with UTF-8. (pypa/pip#8717)
|
||||
with open(pyvenv_cfg_file, encoding="utf-8") as f:
|
||||
with open(pyvenv_cfg_file, encoding='utf-8') as f:
|
||||
return f.read().splitlines() # avoids trailing newlines
|
||||
except OSError:
|
||||
return None
|
||||
|
|
@ -65,14 +68,14 @@ def _no_global_under_venv() -> bool:
|
|||
# site-packages access (since that's PEP 405's default state).
|
||||
logger.warning(
|
||||
"Could not access 'pyvenv.cfg' despite a virtual environment "
|
||||
"being active. Assuming global site-packages is not accessible "
|
||||
"in this environment."
|
||||
'being active. Assuming global site-packages is not accessible '
|
||||
'in this environment.',
|
||||
)
|
||||
return True
|
||||
|
||||
for line in cfg_lines:
|
||||
match = _INCLUDE_SYSTEM_SITE_PACKAGES_REGEX.match(line)
|
||||
if match is not None and match.group("value") == "false":
|
||||
if match is not None and match.group('value') == 'false':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -86,7 +89,7 @@ def _no_global_under_regular_virtualenv() -> bool:
|
|||
site_mod_dir = os.path.dirname(os.path.abspath(site.__file__))
|
||||
no_global_site_packages_file = os.path.join(
|
||||
site_mod_dir,
|
||||
"no-global-site-packages.txt",
|
||||
'no-global-site-packages.txt',
|
||||
)
|
||||
return os.path.exists(no_global_site_packages_file)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
"""Support functions for working with wheel files.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from email.message import Message
|
||||
from email.parser import Parser
|
||||
from typing import Tuple
|
||||
from zipfile import BadZipFile, ZipFile
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from zipfile import BadZipFile
|
||||
from zipfile import ZipFile
|
||||
|
||||
from pip._internal.exceptions import UnsupportedWheel
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
VERSION_COMPATIBLE = (1, 0)
|
||||
|
||||
|
|
@ -17,7 +18,7 @@ VERSION_COMPATIBLE = (1, 0)
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]:
|
||||
def parse_wheel(wheel_zip: ZipFile, name: str) -> tuple[str, Message]:
|
||||
"""Extract information from the provided wheel, ensuring it meets basic
|
||||
standards.
|
||||
|
||||
|
|
@ -28,7 +29,7 @@ def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]:
|
|||
metadata = wheel_metadata(wheel_zip, info_dir)
|
||||
version = wheel_version(metadata)
|
||||
except UnsupportedWheel as e:
|
||||
raise UnsupportedWheel("{} has an invalid wheel, {}".format(name, str(e)))
|
||||
raise UnsupportedWheel(f'{name} has an invalid wheel, {str(e)}')
|
||||
|
||||
check_compatibility(version, name)
|
||||
|
||||
|
|
@ -42,16 +43,16 @@ def wheel_dist_info_dir(source: ZipFile, name: str) -> str:
|
|||
it doesn't match the provided name.
|
||||
"""
|
||||
# Zip file path separators must be /
|
||||
subdirs = {p.split("/", 1)[0] for p in source.namelist()}
|
||||
subdirs = {p.split('/', 1)[0] for p in source.namelist()}
|
||||
|
||||
info_dirs = [s for s in subdirs if s.endswith(".dist-info")]
|
||||
info_dirs = [s for s in subdirs if s.endswith('.dist-info')]
|
||||
|
||||
if not info_dirs:
|
||||
raise UnsupportedWheel(".dist-info directory not found")
|
||||
raise UnsupportedWheel('.dist-info directory not found')
|
||||
|
||||
if len(info_dirs) > 1:
|
||||
raise UnsupportedWheel(
|
||||
"multiple .dist-info directories found: {}".format(", ".join(info_dirs))
|
||||
'multiple .dist-info directories found: {}'.format(', '.join(info_dirs)),
|
||||
)
|
||||
|
||||
info_dir = info_dirs[0]
|
||||
|
|
@ -60,9 +61,9 @@ def wheel_dist_info_dir(source: ZipFile, name: str) -> str:
|
|||
canonical_name = canonicalize_name(name)
|
||||
if not info_dir_name.startswith(canonical_name):
|
||||
raise UnsupportedWheel(
|
||||
".dist-info directory {!r} does not start with {!r}".format(
|
||||
info_dir, canonical_name
|
||||
)
|
||||
'.dist-info directory {!r} does not start with {!r}'.format(
|
||||
info_dir, canonical_name,
|
||||
),
|
||||
)
|
||||
|
||||
return info_dir
|
||||
|
|
@ -74,21 +75,21 @@ def read_wheel_metadata_file(source: ZipFile, path: str) -> bytes:
|
|||
# BadZipFile for general corruption, KeyError for missing entry,
|
||||
# and RuntimeError for password-protected files
|
||||
except (BadZipFile, KeyError, RuntimeError) as e:
|
||||
raise UnsupportedWheel(f"could not read {path!r} file: {e!r}")
|
||||
raise UnsupportedWheel(f'could not read {path!r} file: {e!r}')
|
||||
|
||||
|
||||
def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message:
|
||||
"""Return the WHEEL metadata of an extracted wheel, if possible.
|
||||
Otherwise, raise UnsupportedWheel.
|
||||
"""
|
||||
path = f"{dist_info_dir}/WHEEL"
|
||||
path = f'{dist_info_dir}/WHEEL'
|
||||
# Zip file path separators must be /
|
||||
wheel_contents = read_wheel_metadata_file(source, path)
|
||||
|
||||
try:
|
||||
wheel_text = wheel_contents.decode()
|
||||
except UnicodeDecodeError as e:
|
||||
raise UnsupportedWheel(f"error decoding {path!r}: {e!r}")
|
||||
raise UnsupportedWheel(f'error decoding {path!r}: {e!r}')
|
||||
|
||||
# FeedParser (used by Parser) does not raise any exceptions. The returned
|
||||
# message may have .defects populated, but for backwards-compatibility we
|
||||
|
|
@ -96,23 +97,23 @@ def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message:
|
|||
return Parser().parsestr(wheel_text)
|
||||
|
||||
|
||||
def wheel_version(wheel_data: Message) -> Tuple[int, ...]:
|
||||
def wheel_version(wheel_data: Message) -> tuple[int, ...]:
|
||||
"""Given WHEEL metadata, return the parsed Wheel-Version.
|
||||
Otherwise, raise UnsupportedWheel.
|
||||
"""
|
||||
version_text = wheel_data["Wheel-Version"]
|
||||
version_text = wheel_data['Wheel-Version']
|
||||
if version_text is None:
|
||||
raise UnsupportedWheel("WHEEL is missing Wheel-Version")
|
||||
raise UnsupportedWheel('WHEEL is missing Wheel-Version')
|
||||
|
||||
version = version_text.strip()
|
||||
|
||||
try:
|
||||
return tuple(map(int, version.split(".")))
|
||||
return tuple(map(int, version.split('.')))
|
||||
except ValueError:
|
||||
raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}")
|
||||
raise UnsupportedWheel(f'invalid Wheel-Version: {version!r}')
|
||||
|
||||
|
||||
def check_compatibility(version: Tuple[int, ...], name: str) -> None:
|
||||
def check_compatibility(version: tuple[int, ...], name: str) -> None:
|
||||
"""Raises errors or warns if called with an incompatible Wheel-Version.
|
||||
|
||||
pip should refuse to install a Wheel-Version that's a major series
|
||||
|
|
@ -127,10 +128,10 @@ def check_compatibility(version: Tuple[int, ...], name: str) -> None:
|
|||
if version[0] > VERSION_COMPATIBLE[0]:
|
||||
raise UnsupportedWheel(
|
||||
"{}'s Wheel-Version ({}) is not compatible with this version "
|
||||
"of pip".format(name, ".".join(map(str, version)))
|
||||
'of pip'.format(name, '.'.join(map(str, version))),
|
||||
)
|
||||
elif version > VERSION_COMPATIBLE:
|
||||
logger.warning(
|
||||
"Installing from a newer Wheel-Version (%s)",
|
||||
".".join(map(str, version)),
|
||||
'Installing from a newer Wheel-Version (%s)',
|
||||
'.'.join(map(str, version)),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue