[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
This commit is contained in:
pre-commit-ci[bot] 2024-04-13 00:00:18 +00:00
parent 72ad6dc953
commit f4cd1ba0d6
813 changed files with 66015 additions and 58839 deletions

View file

@ -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')

View file

@ -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']

View file

@ -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')

View file

@ -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

View file

@ -1,5 +1,6 @@
"""For when pip wants to check the date or time.
"""
from __future__ import annotations
import datetime

View file

@ -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

View file

@ -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),

View file

@ -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.

View file

@ -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.

View file

@ -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(),

View file

@ -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)

View file

@ -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:

View file

@ -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

View file

@ -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)

View file

@ -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())

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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())

View file

@ -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

View file

@ -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(

View file

@ -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

View file

@ -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}')

View file

@ -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:]

View file

@ -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)

View 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)),
)