Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/_log.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/compat.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/distutils_args.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/encoding.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/inject_securetransport.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/logging.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/misc.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/models.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/urls.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/encoding.py +36 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/entrypoints.py +27 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/filesystem.py +182 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/filetypes.py +27 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/glibc.py +88 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/hashes.py +144 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/inject_securetransport.py +35 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/logging.py +343 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/misc.py +653 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/models.py +39 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/packaging.py +57 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/setuptools_build.py +195 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/subprocess.py +260 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/temp_dir.py +246 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/unpacking.py +258 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/urls.py +62 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/virtualenv.py +104 -0
- venv/lib/python3.10/site-packages/pip/_internal/utils/wheel.py +136 -0
- venv/lib/python3.10/site-packages/pip/_internal/vcs/__init__.py +15 -0
- venv/lib/python3.10/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/pip/_internal/vcs/__pycache__/git.cpython-310.pyc +0 -0
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (290 Bytes). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/_log.cpython-310.pyc
ADDED
|
Binary file (1.62 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-310.pyc
ADDED
|
Binary file (1.72 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/compat.cpython-310.pyc
ADDED
|
Binary file (1.61 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-310.pyc
ADDED
|
Binary file (4.18 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-310.pyc
ADDED
|
Binary file (613 Bytes). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-310.pyc
ADDED
|
Binary file (3.41 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-310.pyc
ADDED
|
Binary file (2.18 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/distutils_args.cpython-310.pyc
ADDED
|
Binary file (1.2 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-310.pyc
ADDED
|
Binary file (2.25 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/encoding.cpython-310.pyc
ADDED
|
Binary file (1.4 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-310.pyc
ADDED
|
Binary file (1.4 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-310.pyc
ADDED
|
Binary file (5.26 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-310.pyc
ADDED
|
Binary file (1.04 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-310.pyc
ADDED
|
Binary file (1.77 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-310.pyc
ADDED
|
Binary file (5.29 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/inject_securetransport.cpython-310.pyc
ADDED
|
Binary file (1.09 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/logging.cpython-310.pyc
ADDED
|
Binary file (9.73 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/misc.cpython-310.pyc
ADDED
|
Binary file (19.5 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/models.cpython-310.pyc
ADDED
|
Binary file (2.09 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-310.pyc
ADDED
|
Binary file (2.18 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-310.pyc
ADDED
|
Binary file (4.69 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-310.pyc
ADDED
|
Binary file (5.87 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-310.pyc
ADDED
|
Binary file (7.4 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-310.pyc
ADDED
|
Binary file (6.75 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/urls.cpython-310.pyc
ADDED
|
Binary file (1.68 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-310.pyc
ADDED
|
Binary file (3.39 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-310.pyc
ADDED
|
Binary file (4.51 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/utils/encoding.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import codecs
|
| 2 |
+
import locale
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from typing import List, Tuple
|
| 6 |
+
|
| 7 |
+
BOMS: List[Tuple[bytes, str]] = [
|
| 8 |
+
(codecs.BOM_UTF8, "utf-8"),
|
| 9 |
+
(codecs.BOM_UTF16, "utf-16"),
|
| 10 |
+
(codecs.BOM_UTF16_BE, "utf-16-be"),
|
| 11 |
+
(codecs.BOM_UTF16_LE, "utf-16-le"),
|
| 12 |
+
(codecs.BOM_UTF32, "utf-32"),
|
| 13 |
+
(codecs.BOM_UTF32_BE, "utf-32-be"),
|
| 14 |
+
(codecs.BOM_UTF32_LE, "utf-32-le"),
|
| 15 |
+
]
|
| 16 |
+
|
| 17 |
+
ENCODING_RE = re.compile(br"coding[:=]\s*([-\w.]+)")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def auto_decode(data: bytes) -> str:
|
| 21 |
+
"""Check a bytes string for a BOM to correctly detect the encoding
|
| 22 |
+
|
| 23 |
+
Fallback to locale.getpreferredencoding(False) like open() on Python3"""
|
| 24 |
+
for bom, encoding in BOMS:
|
| 25 |
+
if data.startswith(bom):
|
| 26 |
+
return data[len(bom) :].decode(encoding)
|
| 27 |
+
# Lets check the first two lines as in PEP263
|
| 28 |
+
for line in data.split(b"\n")[:2]:
|
| 29 |
+
if line[0:1] == b"#" and ENCODING_RE.search(line):
|
| 30 |
+
result = ENCODING_RE.search(line)
|
| 31 |
+
assert result is not None
|
| 32 |
+
encoding = result.groups()[0].decode("ascii")
|
| 33 |
+
return data.decode(encoding)
|
| 34 |
+
return data.decode(
|
| 35 |
+
locale.getpreferredencoding(False) or sys.getdefaultencoding(),
|
| 36 |
+
)
|
venv/lib/python3.10/site-packages/pip/_internal/utils/entrypoints.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from typing import List, Optional
|
| 3 |
+
|
| 4 |
+
from pip._internal.cli.main import main
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def _wrapper(args: Optional[List[str]] = None) -> int:
|
| 8 |
+
"""Central wrapper for all old entrypoints.
|
| 9 |
+
|
| 10 |
+
Historically pip has had several entrypoints defined. Because of issues
|
| 11 |
+
arising from PATH, sys.path, multiple Pythons, their interactions, and most
|
| 12 |
+
of them having a pip installed, users suffer every time an entrypoint gets
|
| 13 |
+
moved.
|
| 14 |
+
|
| 15 |
+
To alleviate this pain, and provide a mechanism for warning users and
|
| 16 |
+
directing them to an appropriate place for help, we now define all of
|
| 17 |
+
our old entrypoints as wrappers for the current one.
|
| 18 |
+
"""
|
| 19 |
+
sys.stderr.write(
|
| 20 |
+
"WARNING: pip is being invoked by an old script wrapper. This will "
|
| 21 |
+
"fail in a future version of pip.\n"
|
| 22 |
+
"Please see https://github.com/pypa/pip/issues/5599 for advice on "
|
| 23 |
+
"fixing the underlying issue.\n"
|
| 24 |
+
"To avoid this problem you can invoke Python with '-m pip' instead of "
|
| 25 |
+
"running pip directly.\n"
|
| 26 |
+
)
|
| 27 |
+
return main(args)
|
venv/lib/python3.10/site-packages/pip/_internal/utils/filesystem.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fnmatch
|
| 2 |
+
import os
|
| 3 |
+
import os.path
|
| 4 |
+
import random
|
| 5 |
+
import shutil
|
| 6 |
+
import stat
|
| 7 |
+
import sys
|
| 8 |
+
from contextlib import contextmanager
|
| 9 |
+
from tempfile import NamedTemporaryFile
|
| 10 |
+
from typing import Any, BinaryIO, Iterator, List, Union, cast
|
| 11 |
+
|
| 12 |
+
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
|
| 13 |
+
|
| 14 |
+
from pip._internal.utils.compat import get_path_uid
|
| 15 |
+
from pip._internal.utils.misc import format_size
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def check_path_owner(path: str) -> bool:
|
| 19 |
+
# If we don't have a way to check the effective uid of this process, then
|
| 20 |
+
# we'll just assume that we own the directory.
|
| 21 |
+
if sys.platform == "win32" or not hasattr(os, "geteuid"):
|
| 22 |
+
return True
|
| 23 |
+
|
| 24 |
+
assert os.path.isabs(path)
|
| 25 |
+
|
| 26 |
+
previous = None
|
| 27 |
+
while path != previous:
|
| 28 |
+
if os.path.lexists(path):
|
| 29 |
+
# Check if path is writable by current user.
|
| 30 |
+
if os.geteuid() == 0:
|
| 31 |
+
# Special handling for root user in order to handle properly
|
| 32 |
+
# cases where users use sudo without -H flag.
|
| 33 |
+
try:
|
| 34 |
+
path_uid = get_path_uid(path)
|
| 35 |
+
except OSError:
|
| 36 |
+
return False
|
| 37 |
+
return path_uid == 0
|
| 38 |
+
else:
|
| 39 |
+
return os.access(path, os.W_OK)
|
| 40 |
+
else:
|
| 41 |
+
previous, path = path, os.path.dirname(path)
|
| 42 |
+
return False # assume we don't own the path
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def copy2_fixed(src: str, dest: str) -> None:
|
| 46 |
+
"""Wrap shutil.copy2() but map errors copying socket files to
|
| 47 |
+
SpecialFileError as expected.
|
| 48 |
+
|
| 49 |
+
See also https://bugs.python.org/issue37700.
|
| 50 |
+
"""
|
| 51 |
+
try:
|
| 52 |
+
shutil.copy2(src, dest)
|
| 53 |
+
except OSError:
|
| 54 |
+
for f in [src, dest]:
|
| 55 |
+
try:
|
| 56 |
+
is_socket_file = is_socket(f)
|
| 57 |
+
except OSError:
|
| 58 |
+
# An error has already occurred. Another error here is not
|
| 59 |
+
# a problem and we can ignore it.
|
| 60 |
+
pass
|
| 61 |
+
else:
|
| 62 |
+
if is_socket_file:
|
| 63 |
+
raise shutil.SpecialFileError(f"`{f}` is a socket")
|
| 64 |
+
|
| 65 |
+
raise
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def is_socket(path: str) -> bool:
|
| 69 |
+
return stat.S_ISSOCK(os.lstat(path).st_mode)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@contextmanager
|
| 73 |
+
def adjacent_tmp_file(path: str, **kwargs: Any) -> Iterator[BinaryIO]:
|
| 74 |
+
"""Return a file-like object pointing to a tmp file next to path.
|
| 75 |
+
|
| 76 |
+
The file is created securely and is ensured to be written to disk
|
| 77 |
+
after the context reaches its end.
|
| 78 |
+
|
| 79 |
+
kwargs will be passed to tempfile.NamedTemporaryFile to control
|
| 80 |
+
the way the temporary file will be opened.
|
| 81 |
+
"""
|
| 82 |
+
with NamedTemporaryFile(
|
| 83 |
+
delete=False,
|
| 84 |
+
dir=os.path.dirname(path),
|
| 85 |
+
prefix=os.path.basename(path),
|
| 86 |
+
suffix=".tmp",
|
| 87 |
+
**kwargs,
|
| 88 |
+
) as f:
|
| 89 |
+
result = cast(BinaryIO, f)
|
| 90 |
+
try:
|
| 91 |
+
yield result
|
| 92 |
+
finally:
|
| 93 |
+
result.flush()
|
| 94 |
+
os.fsync(result.fileno())
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
# Tenacity raises RetryError by default, explicitly raise the original exception
|
| 98 |
+
_replace_retry = retry(reraise=True, stop=stop_after_delay(1), wait=wait_fixed(0.25))
|
| 99 |
+
|
| 100 |
+
replace = _replace_retry(os.replace)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
# test_writable_dir and _test_writable_dir_win are copied from Flit,
|
| 104 |
+
# with the author's agreement to also place them under pip's license.
|
| 105 |
+
def test_writable_dir(path: str) -> bool:
|
| 106 |
+
"""Check if a directory is writable.
|
| 107 |
+
|
| 108 |
+
Uses os.access() on POSIX, tries creating files on Windows.
|
| 109 |
+
"""
|
| 110 |
+
# If the directory doesn't exist, find the closest parent that does.
|
| 111 |
+
while not os.path.isdir(path):
|
| 112 |
+
parent = os.path.dirname(path)
|
| 113 |
+
if parent == path:
|
| 114 |
+
break # Should never get here, but infinite loops are bad
|
| 115 |
+
path = parent
|
| 116 |
+
|
| 117 |
+
if os.name == "posix":
|
| 118 |
+
return os.access(path, os.W_OK)
|
| 119 |
+
|
| 120 |
+
return _test_writable_dir_win(path)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def _test_writable_dir_win(path: str) -> bool:
|
| 124 |
+
# os.access doesn't work on Windows: http://bugs.python.org/issue2528
|
| 125 |
+
# and we can't use tempfile: http://bugs.python.org/issue22107
|
| 126 |
+
basename = "accesstest_deleteme_fishfingers_custard_"
|
| 127 |
+
alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
| 128 |
+
for _ in range(10):
|
| 129 |
+
name = basename + "".join(random.choice(alphabet) for _ in range(6))
|
| 130 |
+
file = os.path.join(path, name)
|
| 131 |
+
try:
|
| 132 |
+
fd = os.open(file, os.O_RDWR | os.O_CREAT | os.O_EXCL)
|
| 133 |
+
except FileExistsError:
|
| 134 |
+
pass
|
| 135 |
+
except PermissionError:
|
| 136 |
+
# This could be because there's a directory with the same name.
|
| 137 |
+
# But it's highly unlikely there's a directory called that,
|
| 138 |
+
# so we'll assume it's because the parent dir is not writable.
|
| 139 |
+
# This could as well be because the parent dir is not readable,
|
| 140 |
+
# due to non-privileged user access.
|
| 141 |
+
return False
|
| 142 |
+
else:
|
| 143 |
+
os.close(fd)
|
| 144 |
+
os.unlink(file)
|
| 145 |
+
return True
|
| 146 |
+
|
| 147 |
+
# This should never be reached
|
| 148 |
+
raise OSError("Unexpected condition testing for writable directory")
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def find_files(path: str, pattern: str) -> List[str]:
|
| 152 |
+
"""Returns a list of absolute paths of files beneath path, recursively,
|
| 153 |
+
with filenames which match the UNIX-style shell glob pattern."""
|
| 154 |
+
result: List[str] = []
|
| 155 |
+
for root, _, files in os.walk(path):
|
| 156 |
+
matches = fnmatch.filter(files, pattern)
|
| 157 |
+
result.extend(os.path.join(root, f) for f in matches)
|
| 158 |
+
return result
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def file_size(path: str) -> Union[int, float]:
|
| 162 |
+
# If it's a symlink, return 0.
|
| 163 |
+
if os.path.islink(path):
|
| 164 |
+
return 0
|
| 165 |
+
return os.path.getsize(path)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def format_file_size(path: str) -> str:
|
| 169 |
+
return format_size(file_size(path))
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def directory_size(path: str) -> Union[int, float]:
|
| 173 |
+
size = 0.0
|
| 174 |
+
for root, _dirs, files in os.walk(path):
|
| 175 |
+
for filename in files:
|
| 176 |
+
file_path = os.path.join(root, filename)
|
| 177 |
+
size += file_size(file_path)
|
| 178 |
+
return size
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def format_directory_size(path: str) -> str:
|
| 182 |
+
return format_size(directory_size(path))
|
venv/lib/python3.10/site-packages/pip/_internal/utils/filetypes.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Filetype information.
|
| 2 |
+
"""
|
| 3 |
+
|
| 4 |
+
from typing import Tuple
|
| 5 |
+
|
| 6 |
+
from pip._internal.utils.misc import splitext
|
| 7 |
+
|
| 8 |
+
WHEEL_EXTENSION = ".whl"
|
| 9 |
+
BZ2_EXTENSIONS: Tuple[str, ...] = (".tar.bz2", ".tbz")
|
| 10 |
+
XZ_EXTENSIONS: Tuple[str, ...] = (
|
| 11 |
+
".tar.xz",
|
| 12 |
+
".txz",
|
| 13 |
+
".tlz",
|
| 14 |
+
".tar.lz",
|
| 15 |
+
".tar.lzma",
|
| 16 |
+
)
|
| 17 |
+
ZIP_EXTENSIONS: Tuple[str, ...] = (".zip", WHEEL_EXTENSION)
|
| 18 |
+
TAR_EXTENSIONS: Tuple[str, ...] = (".tar.gz", ".tgz", ".tar")
|
| 19 |
+
ARCHIVE_EXTENSIONS = ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def is_archive_file(name: str) -> bool:
|
| 23 |
+
"""Return True if `name` is a considered as an archive file."""
|
| 24 |
+
ext = splitext(name)[1].lower()
|
| 25 |
+
if ext in ARCHIVE_EXTENSIONS:
|
| 26 |
+
return True
|
| 27 |
+
return False
|
venv/lib/python3.10/site-packages/pip/_internal/utils/glibc.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# The following comment should be removed at some point in the future.
|
| 2 |
+
# mypy: strict-optional=False
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
from typing import Optional, Tuple
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def glibc_version_string() -> Optional[str]:
|
| 10 |
+
"Returns glibc version string, or None if not using glibc."
|
| 11 |
+
return glibc_version_string_confstr() or glibc_version_string_ctypes()
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def glibc_version_string_confstr() -> Optional[str]:
|
| 15 |
+
"Primary implementation of glibc_version_string using os.confstr."
|
| 16 |
+
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
|
| 17 |
+
# to be broken or missing. This strategy is used in the standard library
|
| 18 |
+
# platform module:
|
| 19 |
+
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183
|
| 20 |
+
if sys.platform == "win32":
|
| 21 |
+
return None
|
| 22 |
+
try:
|
| 23 |
+
# os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17":
|
| 24 |
+
_, version = os.confstr("CS_GNU_LIBC_VERSION").split()
|
| 25 |
+
except (AttributeError, OSError, ValueError):
|
| 26 |
+
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
|
| 27 |
+
return None
|
| 28 |
+
return version
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def glibc_version_string_ctypes() -> Optional[str]:
|
| 32 |
+
"Fallback implementation of glibc_version_string using ctypes."
|
| 33 |
+
|
| 34 |
+
try:
|
| 35 |
+
import ctypes
|
| 36 |
+
except ImportError:
|
| 37 |
+
return None
|
| 38 |
+
|
| 39 |
+
# ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
|
| 40 |
+
# manpage says, "If filename is NULL, then the returned handle is for the
|
| 41 |
+
# main program". This way we can let the linker do the work to figure out
|
| 42 |
+
# which libc our process is actually using.
|
| 43 |
+
process_namespace = ctypes.CDLL(None)
|
| 44 |
+
try:
|
| 45 |
+
gnu_get_libc_version = process_namespace.gnu_get_libc_version
|
| 46 |
+
except AttributeError:
|
| 47 |
+
# Symbol doesn't exist -> therefore, we are not linked to
|
| 48 |
+
# glibc.
|
| 49 |
+
return None
|
| 50 |
+
|
| 51 |
+
# Call gnu_get_libc_version, which returns a string like "2.5"
|
| 52 |
+
gnu_get_libc_version.restype = ctypes.c_char_p
|
| 53 |
+
version_str = gnu_get_libc_version()
|
| 54 |
+
# py2 / py3 compatibility:
|
| 55 |
+
if not isinstance(version_str, str):
|
| 56 |
+
version_str = version_str.decode("ascii")
|
| 57 |
+
|
| 58 |
+
return version_str
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# platform.libc_ver regularly returns completely nonsensical glibc
|
| 62 |
+
# versions. E.g. on my computer, platform says:
|
| 63 |
+
#
|
| 64 |
+
# ~$ python2.7 -c 'import platform; print(platform.libc_ver())'
|
| 65 |
+
# ('glibc', '2.7')
|
| 66 |
+
# ~$ python3.5 -c 'import platform; print(platform.libc_ver())'
|
| 67 |
+
# ('glibc', '2.9')
|
| 68 |
+
#
|
| 69 |
+
# But the truth is:
|
| 70 |
+
#
|
| 71 |
+
# ~$ ldd --version
|
| 72 |
+
# ldd (Debian GLIBC 2.22-11) 2.22
|
| 73 |
+
#
|
| 74 |
+
# This is unfortunate, because it means that the linehaul data on libc
|
| 75 |
+
# versions that was generated by pip 8.1.2 and earlier is useless and
|
| 76 |
+
# misleading. Solution: instead of using platform, use our code that actually
|
| 77 |
+
# works.
|
| 78 |
+
def libc_ver() -> Tuple[str, str]:
|
| 79 |
+
"""Try to determine the glibc version
|
| 80 |
+
|
| 81 |
+
Returns a tuple of strings (lib, version) which default to empty strings
|
| 82 |
+
in case the lookup fails.
|
| 83 |
+
"""
|
| 84 |
+
glibc_version = glibc_version_string()
|
| 85 |
+
if glibc_version is None:
|
| 86 |
+
return ("", "")
|
| 87 |
+
else:
|
| 88 |
+
return ("glibc", glibc_version)
|
venv/lib/python3.10/site-packages/pip/_internal/utils/hashes.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import hashlib
|
| 2 |
+
from typing import TYPE_CHECKING, BinaryIO, Dict, Iterator, List
|
| 3 |
+
|
| 4 |
+
from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError
|
| 5 |
+
from pip._internal.utils.misc import read_chunks
|
| 6 |
+
|
| 7 |
+
if TYPE_CHECKING:
|
| 8 |
+
from hashlib import _Hash
|
| 9 |
+
|
| 10 |
+
# NoReturn introduced in 3.6.2; imported only for type checking to maintain
|
| 11 |
+
# pip compatibility with older patch versions of Python 3.6
|
| 12 |
+
from typing import NoReturn
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
# The recommended hash algo of the moment. Change this whenever the state of
|
| 16 |
+
# the art changes; it won't hurt backward compatibility.
|
| 17 |
+
FAVORITE_HASH = "sha256"
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# Names of hashlib algorithms allowed by the --hash option and ``pip hash``
|
| 21 |
+
# Currently, those are the ones at least as collision-resistant as sha256.
|
| 22 |
+
STRONG_HASHES = ["sha256", "sha384", "sha512"]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class Hashes:
|
| 26 |
+
"""A wrapper that builds multiple hashes at once and checks them against
|
| 27 |
+
known-good values
|
| 28 |
+
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
def __init__(self, hashes: Dict[str, List[str]] = None) -> None:
|
| 32 |
+
"""
|
| 33 |
+
:param hashes: A dict of algorithm names pointing to lists of allowed
|
| 34 |
+
hex digests
|
| 35 |
+
"""
|
| 36 |
+
allowed = {}
|
| 37 |
+
if hashes is not None:
|
| 38 |
+
for alg, keys in hashes.items():
|
| 39 |
+
# Make sure values are always sorted (to ease equality checks)
|
| 40 |
+
allowed[alg] = sorted(keys)
|
| 41 |
+
self._allowed = allowed
|
| 42 |
+
|
| 43 |
+
def __and__(self, other: "Hashes") -> "Hashes":
|
| 44 |
+
if not isinstance(other, Hashes):
|
| 45 |
+
return NotImplemented
|
| 46 |
+
|
| 47 |
+
# If either of the Hashes object is entirely empty (i.e. no hash
|
| 48 |
+
# specified at all), all hashes from the other object are allowed.
|
| 49 |
+
if not other:
|
| 50 |
+
return self
|
| 51 |
+
if not self:
|
| 52 |
+
return other
|
| 53 |
+
|
| 54 |
+
# Otherwise only hashes that present in both objects are allowed.
|
| 55 |
+
new = {}
|
| 56 |
+
for alg, values in other._allowed.items():
|
| 57 |
+
if alg not in self._allowed:
|
| 58 |
+
continue
|
| 59 |
+
new[alg] = [v for v in values if v in self._allowed[alg]]
|
| 60 |
+
return Hashes(new)
|
| 61 |
+
|
| 62 |
+
@property
|
| 63 |
+
def digest_count(self) -> int:
|
| 64 |
+
return sum(len(digests) for digests in self._allowed.values())
|
| 65 |
+
|
| 66 |
+
def is_hash_allowed(self, hash_name: str, hex_digest: str) -> bool:
|
| 67 |
+
"""Return whether the given hex digest is allowed."""
|
| 68 |
+
return hex_digest in self._allowed.get(hash_name, [])
|
| 69 |
+
|
| 70 |
+
def check_against_chunks(self, chunks: Iterator[bytes]) -> None:
|
| 71 |
+
"""Check good hashes against ones built from iterable of chunks of
|
| 72 |
+
data.
|
| 73 |
+
|
| 74 |
+
Raise HashMismatch if none match.
|
| 75 |
+
|
| 76 |
+
"""
|
| 77 |
+
gots = {}
|
| 78 |
+
for hash_name in self._allowed.keys():
|
| 79 |
+
try:
|
| 80 |
+
gots[hash_name] = hashlib.new(hash_name)
|
| 81 |
+
except (ValueError, TypeError):
|
| 82 |
+
raise InstallationError(f"Unknown hash name: {hash_name}")
|
| 83 |
+
|
| 84 |
+
for chunk in chunks:
|
| 85 |
+
for hash in gots.values():
|
| 86 |
+
hash.update(chunk)
|
| 87 |
+
|
| 88 |
+
for hash_name, got in gots.items():
|
| 89 |
+
if got.hexdigest() in self._allowed[hash_name]:
|
| 90 |
+
return
|
| 91 |
+
self._raise(gots)
|
| 92 |
+
|
| 93 |
+
def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
|
| 94 |
+
raise HashMismatch(self._allowed, gots)
|
| 95 |
+
|
| 96 |
+
def check_against_file(self, file: BinaryIO) -> None:
|
| 97 |
+
"""Check good hashes against a file-like object
|
| 98 |
+
|
| 99 |
+
Raise HashMismatch if none match.
|
| 100 |
+
|
| 101 |
+
"""
|
| 102 |
+
return self.check_against_chunks(read_chunks(file))
|
| 103 |
+
|
| 104 |
+
def check_against_path(self, path: str) -> None:
|
| 105 |
+
with open(path, "rb") as file:
|
| 106 |
+
return self.check_against_file(file)
|
| 107 |
+
|
| 108 |
+
def __bool__(self) -> bool:
|
| 109 |
+
"""Return whether I know any known-good hashes."""
|
| 110 |
+
return bool(self._allowed)
|
| 111 |
+
|
| 112 |
+
def __eq__(self, other: object) -> bool:
|
| 113 |
+
if not isinstance(other, Hashes):
|
| 114 |
+
return NotImplemented
|
| 115 |
+
return self._allowed == other._allowed
|
| 116 |
+
|
| 117 |
+
def __hash__(self) -> int:
|
| 118 |
+
return hash(
|
| 119 |
+
",".join(
|
| 120 |
+
sorted(
|
| 121 |
+
":".join((alg, digest))
|
| 122 |
+
for alg, digest_list in self._allowed.items()
|
| 123 |
+
for digest in digest_list
|
| 124 |
+
)
|
| 125 |
+
)
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
class MissingHashes(Hashes):
|
| 130 |
+
"""A workalike for Hashes used when we're missing a hash for a requirement
|
| 131 |
+
|
| 132 |
+
It computes the actual hash of the requirement and raises a HashMissing
|
| 133 |
+
exception showing it to the user.
|
| 134 |
+
|
| 135 |
+
"""
|
| 136 |
+
|
| 137 |
+
def __init__(self) -> None:
|
| 138 |
+
"""Don't offer the ``hashes`` kwarg."""
|
| 139 |
+
# Pass our favorite hash in to generate a "gotten hash". With the
|
| 140 |
+
# empty list, it will never match, so an error will always raise.
|
| 141 |
+
super().__init__(hashes={FAVORITE_HASH: []})
|
| 142 |
+
|
| 143 |
+
def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
|
| 144 |
+
raise HashMissing(gots[FAVORITE_HASH].hexdigest())
|
venv/lib/python3.10/site-packages/pip/_internal/utils/inject_securetransport.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""A helper module that injects SecureTransport, on import.
|
| 2 |
+
|
| 3 |
+
The import should be done as early as possible, to ensure all requests and
|
| 4 |
+
sessions (or whatever) are created after injecting SecureTransport.
|
| 5 |
+
|
| 6 |
+
Note that we only do the injection on macOS, when the linked OpenSSL is too
|
| 7 |
+
old to handle TLSv1.2.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import sys
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def inject_securetransport() -> None:
|
| 14 |
+
# Only relevant on macOS
|
| 15 |
+
if sys.platform != "darwin":
|
| 16 |
+
return
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
import ssl
|
| 20 |
+
except ImportError:
|
| 21 |
+
return
|
| 22 |
+
|
| 23 |
+
# Checks for OpenSSL 1.0.1
|
| 24 |
+
if ssl.OPENSSL_VERSION_NUMBER >= 0x1000100F:
|
| 25 |
+
return
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
from pip._vendor.urllib3.contrib import securetransport
|
| 29 |
+
except (ImportError, OSError):
|
| 30 |
+
return
|
| 31 |
+
|
| 32 |
+
securetransport.inject_into_urllib3()
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
inject_securetransport()
|
venv/lib/python3.10/site-packages/pip/_internal/utils/logging.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import contextlib
|
| 2 |
+
import errno
|
| 3 |
+
import logging
|
| 4 |
+
import logging.handlers
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
import threading
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
from logging import Filter
|
| 10 |
+
from typing import IO, Any, ClassVar, Iterator, List, Optional, TextIO, Type
|
| 11 |
+
|
| 12 |
+
from pip._vendor.rich.console import (
|
| 13 |
+
Console,
|
| 14 |
+
ConsoleOptions,
|
| 15 |
+
ConsoleRenderable,
|
| 16 |
+
RenderResult,
|
| 17 |
+
)
|
| 18 |
+
from pip._vendor.rich.highlighter import NullHighlighter
|
| 19 |
+
from pip._vendor.rich.logging import RichHandler
|
| 20 |
+
from pip._vendor.rich.segment import Segment
|
| 21 |
+
from pip._vendor.rich.style import Style
|
| 22 |
+
|
| 23 |
+
from pip._internal.exceptions import DiagnosticPipError
|
| 24 |
+
from pip._internal.utils._log import VERBOSE, getLogger
|
| 25 |
+
from pip._internal.utils.compat import WINDOWS
|
| 26 |
+
from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX
|
| 27 |
+
from pip._internal.utils.misc import ensure_dir
|
| 28 |
+
|
| 29 |
+
_log_state = threading.local()
|
| 30 |
+
subprocess_logger = getLogger("pip.subprocessor")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class BrokenStdoutLoggingError(Exception):
|
| 34 |
+
"""
|
| 35 |
+
Raised if BrokenPipeError occurs for the stdout stream while logging.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _is_broken_pipe_error(exc_class: Type[BaseException], exc: BaseException) -> bool:
|
| 40 |
+
if exc_class is BrokenPipeError:
|
| 41 |
+
return True
|
| 42 |
+
|
| 43 |
+
# On Windows, a broken pipe can show up as EINVAL rather than EPIPE:
|
| 44 |
+
# https://bugs.python.org/issue19612
|
| 45 |
+
# https://bugs.python.org/issue30418
|
| 46 |
+
if not WINDOWS:
|
| 47 |
+
return False
|
| 48 |
+
|
| 49 |
+
return isinstance(exc, OSError) and exc.errno in (errno.EINVAL, errno.EPIPE)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
@contextlib.contextmanager
|
| 53 |
+
def indent_log(num: int = 2) -> Iterator[None]:
|
| 54 |
+
"""
|
| 55 |
+
A context manager which will cause the log output to be indented for any
|
| 56 |
+
log messages emitted inside it.
|
| 57 |
+
"""
|
| 58 |
+
# For thread-safety
|
| 59 |
+
_log_state.indentation = get_indentation()
|
| 60 |
+
_log_state.indentation += num
|
| 61 |
+
try:
|
| 62 |
+
yield
|
| 63 |
+
finally:
|
| 64 |
+
_log_state.indentation -= num
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def get_indentation() -> int:
|
| 68 |
+
return getattr(_log_state, "indentation", 0)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class IndentingFormatter(logging.Formatter):
|
| 72 |
+
default_time_format = "%Y-%m-%dT%H:%M:%S"
|
| 73 |
+
|
| 74 |
+
def __init__(
|
| 75 |
+
self,
|
| 76 |
+
*args: Any,
|
| 77 |
+
add_timestamp: bool = False,
|
| 78 |
+
**kwargs: Any,
|
| 79 |
+
) -> None:
|
| 80 |
+
"""
|
| 81 |
+
A logging.Formatter that obeys the indent_log() context manager.
|
| 82 |
+
|
| 83 |
+
:param add_timestamp: A bool indicating output lines should be prefixed
|
| 84 |
+
with their record's timestamp.
|
| 85 |
+
"""
|
| 86 |
+
self.add_timestamp = add_timestamp
|
| 87 |
+
super().__init__(*args, **kwargs)
|
| 88 |
+
|
| 89 |
+
def get_message_start(self, formatted: str, levelno: int) -> str:
|
| 90 |
+
"""
|
| 91 |
+
Return the start of the formatted log message (not counting the
|
| 92 |
+
prefix to add to each line).
|
| 93 |
+
"""
|
| 94 |
+
if levelno < logging.WARNING:
|
| 95 |
+
return ""
|
| 96 |
+
if formatted.startswith(DEPRECATION_MSG_PREFIX):
|
| 97 |
+
# Then the message already has a prefix. We don't want it to
|
| 98 |
+
# look like "WARNING: DEPRECATION: ...."
|
| 99 |
+
return ""
|
| 100 |
+
if levelno < logging.ERROR:
|
| 101 |
+
return "WARNING: "
|
| 102 |
+
|
| 103 |
+
return "ERROR: "
|
| 104 |
+
|
| 105 |
+
def format(self, record: logging.LogRecord) -> str:
|
| 106 |
+
"""
|
| 107 |
+
Calls the standard formatter, but will indent all of the log message
|
| 108 |
+
lines by our current indentation level.
|
| 109 |
+
"""
|
| 110 |
+
formatted = super().format(record)
|
| 111 |
+
message_start = self.get_message_start(formatted, record.levelno)
|
| 112 |
+
formatted = message_start + formatted
|
| 113 |
+
|
| 114 |
+
prefix = ""
|
| 115 |
+
if self.add_timestamp:
|
| 116 |
+
prefix = f"{self.formatTime(record)} "
|
| 117 |
+
prefix += " " * get_indentation()
|
| 118 |
+
formatted = "".join([prefix + line for line in formatted.splitlines(True)])
|
| 119 |
+
return formatted
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
@dataclass
|
| 123 |
+
class IndentedRenderable:
|
| 124 |
+
renderable: ConsoleRenderable
|
| 125 |
+
indent: int
|
| 126 |
+
|
| 127 |
+
def __rich_console__(
|
| 128 |
+
self, console: Console, options: ConsoleOptions
|
| 129 |
+
) -> RenderResult:
|
| 130 |
+
segments = console.render(self.renderable, options)
|
| 131 |
+
lines = Segment.split_lines(segments)
|
| 132 |
+
for line in lines:
|
| 133 |
+
yield Segment(" " * self.indent)
|
| 134 |
+
yield from line
|
| 135 |
+
yield Segment("\n")
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
class RichPipStreamHandler(RichHandler):
|
| 139 |
+
KEYWORDS: ClassVar[Optional[List[str]]] = []
|
| 140 |
+
|
| 141 |
+
def __init__(self, stream: Optional[TextIO], no_color: bool) -> None:
|
| 142 |
+
super().__init__(
|
| 143 |
+
console=Console(file=stream, no_color=no_color, soft_wrap=True),
|
| 144 |
+
show_time=False,
|
| 145 |
+
show_level=False,
|
| 146 |
+
show_path=False,
|
| 147 |
+
highlighter=NullHighlighter(),
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
# Our custom override on Rich's logger, to make things work as we need them to.
|
| 151 |
+
def emit(self, record: logging.LogRecord) -> None:
|
| 152 |
+
style: Optional[Style] = None
|
| 153 |
+
|
| 154 |
+
# If we are given a diagnostic error to present, present it with indentation.
|
| 155 |
+
if record.msg == "[present-diagnostic] %s" and len(record.args) == 1:
|
| 156 |
+
diagnostic_error: DiagnosticPipError = record.args[0] # type: ignore[index]
|
| 157 |
+
assert isinstance(diagnostic_error, DiagnosticPipError)
|
| 158 |
+
|
| 159 |
+
renderable: ConsoleRenderable = IndentedRenderable(
|
| 160 |
+
diagnostic_error, indent=get_indentation()
|
| 161 |
+
)
|
| 162 |
+
else:
|
| 163 |
+
message = self.format(record)
|
| 164 |
+
renderable = self.render_message(record, message)
|
| 165 |
+
if record.levelno is not None:
|
| 166 |
+
if record.levelno >= logging.ERROR:
|
| 167 |
+
style = Style(color="red")
|
| 168 |
+
elif record.levelno >= logging.WARNING:
|
| 169 |
+
style = Style(color="yellow")
|
| 170 |
+
|
| 171 |
+
try:
|
| 172 |
+
self.console.print(renderable, overflow="ignore", crop=False, style=style)
|
| 173 |
+
except Exception:
|
| 174 |
+
self.handleError(record)
|
| 175 |
+
|
| 176 |
+
def handleError(self, record: logging.LogRecord) -> None:
|
| 177 |
+
"""Called when logging is unable to log some output."""
|
| 178 |
+
|
| 179 |
+
exc_class, exc = sys.exc_info()[:2]
|
| 180 |
+
# If a broken pipe occurred while calling write() or flush() on the
|
| 181 |
+
# stdout stream in logging's Handler.emit(), then raise our special
|
| 182 |
+
# exception so we can handle it in main() instead of logging the
|
| 183 |
+
# broken pipe error and continuing.
|
| 184 |
+
if (
|
| 185 |
+
exc_class
|
| 186 |
+
and exc
|
| 187 |
+
and self.console.file is sys.stdout
|
| 188 |
+
and _is_broken_pipe_error(exc_class, exc)
|
| 189 |
+
):
|
| 190 |
+
raise BrokenStdoutLoggingError()
|
| 191 |
+
|
| 192 |
+
return super().handleError(record)
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler):
|
| 196 |
+
def _open(self) -> IO[Any]:
|
| 197 |
+
ensure_dir(os.path.dirname(self.baseFilename))
|
| 198 |
+
return super()._open()
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
class MaxLevelFilter(Filter):
|
| 202 |
+
def __init__(self, level: int) -> None:
|
| 203 |
+
self.level = level
|
| 204 |
+
|
| 205 |
+
def filter(self, record: logging.LogRecord) -> bool:
|
| 206 |
+
return record.levelno < self.level
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
class ExcludeLoggerFilter(Filter):
|
| 210 |
+
|
| 211 |
+
"""
|
| 212 |
+
A logging Filter that excludes records from a logger (or its children).
|
| 213 |
+
"""
|
| 214 |
+
|
| 215 |
+
def filter(self, record: logging.LogRecord) -> bool:
|
| 216 |
+
# The base Filter class allows only records from a logger (or its
|
| 217 |
+
# children).
|
| 218 |
+
return not super().filter(record)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str]) -> int:
|
| 222 |
+
"""Configures and sets up all of the logging
|
| 223 |
+
|
| 224 |
+
Returns the requested logging level, as its integer value.
|
| 225 |
+
"""
|
| 226 |
+
|
| 227 |
+
# Determine the level to be logging at.
|
| 228 |
+
if verbosity >= 2:
|
| 229 |
+
level_number = logging.DEBUG
|
| 230 |
+
elif verbosity == 1:
|
| 231 |
+
level_number = VERBOSE
|
| 232 |
+
elif verbosity == -1:
|
| 233 |
+
level_number = logging.WARNING
|
| 234 |
+
elif verbosity == -2:
|
| 235 |
+
level_number = logging.ERROR
|
| 236 |
+
elif verbosity <= -3:
|
| 237 |
+
level_number = logging.CRITICAL
|
| 238 |
+
else:
|
| 239 |
+
level_number = logging.INFO
|
| 240 |
+
|
| 241 |
+
level = logging.getLevelName(level_number)
|
| 242 |
+
|
| 243 |
+
# The "root" logger should match the "console" level *unless* we also need
|
| 244 |
+
# to log to a user log file.
|
| 245 |
+
include_user_log = user_log_file is not None
|
| 246 |
+
if include_user_log:
|
| 247 |
+
additional_log_file = user_log_file
|
| 248 |
+
root_level = "DEBUG"
|
| 249 |
+
else:
|
| 250 |
+
additional_log_file = "/dev/null"
|
| 251 |
+
root_level = level
|
| 252 |
+
|
| 253 |
+
# Disable any logging besides WARNING unless we have DEBUG level logging
|
| 254 |
+
# enabled for vendored libraries.
|
| 255 |
+
vendored_log_level = "WARNING" if level in ["INFO", "ERROR"] else "DEBUG"
|
| 256 |
+
|
| 257 |
+
# Shorthands for clarity
|
| 258 |
+
log_streams = {
|
| 259 |
+
"stdout": "ext://sys.stdout",
|
| 260 |
+
"stderr": "ext://sys.stderr",
|
| 261 |
+
}
|
| 262 |
+
handler_classes = {
|
| 263 |
+
"stream": "pip._internal.utils.logging.RichPipStreamHandler",
|
| 264 |
+
"file": "pip._internal.utils.logging.BetterRotatingFileHandler",
|
| 265 |
+
}
|
| 266 |
+
handlers = ["console", "console_errors", "console_subprocess"] + (
|
| 267 |
+
["user_log"] if include_user_log else []
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
logging.config.dictConfig(
|
| 271 |
+
{
|
| 272 |
+
"version": 1,
|
| 273 |
+
"disable_existing_loggers": False,
|
| 274 |
+
"filters": {
|
| 275 |
+
"exclude_warnings": {
|
| 276 |
+
"()": "pip._internal.utils.logging.MaxLevelFilter",
|
| 277 |
+
"level": logging.WARNING,
|
| 278 |
+
},
|
| 279 |
+
"restrict_to_subprocess": {
|
| 280 |
+
"()": "logging.Filter",
|
| 281 |
+
"name": subprocess_logger.name,
|
| 282 |
+
},
|
| 283 |
+
"exclude_subprocess": {
|
| 284 |
+
"()": "pip._internal.utils.logging.ExcludeLoggerFilter",
|
| 285 |
+
"name": subprocess_logger.name,
|
| 286 |
+
},
|
| 287 |
+
},
|
| 288 |
+
"formatters": {
|
| 289 |
+
"indent": {
|
| 290 |
+
"()": IndentingFormatter,
|
| 291 |
+
"format": "%(message)s",
|
| 292 |
+
},
|
| 293 |
+
"indent_with_timestamp": {
|
| 294 |
+
"()": IndentingFormatter,
|
| 295 |
+
"format": "%(message)s",
|
| 296 |
+
"add_timestamp": True,
|
| 297 |
+
},
|
| 298 |
+
},
|
| 299 |
+
"handlers": {
|
| 300 |
+
"console": {
|
| 301 |
+
"level": level,
|
| 302 |
+
"class": handler_classes["stream"],
|
| 303 |
+
"no_color": no_color,
|
| 304 |
+
"stream": log_streams["stdout"],
|
| 305 |
+
"filters": ["exclude_subprocess", "exclude_warnings"],
|
| 306 |
+
"formatter": "indent",
|
| 307 |
+
},
|
| 308 |
+
"console_errors": {
|
| 309 |
+
"level": "WARNING",
|
| 310 |
+
"class": handler_classes["stream"],
|
| 311 |
+
"no_color": no_color,
|
| 312 |
+
"stream": log_streams["stderr"],
|
| 313 |
+
"filters": ["exclude_subprocess"],
|
| 314 |
+
"formatter": "indent",
|
| 315 |
+
},
|
| 316 |
+
# A handler responsible for logging to the console messages
|
| 317 |
+
# from the "subprocessor" logger.
|
| 318 |
+
"console_subprocess": {
|
| 319 |
+
"level": level,
|
| 320 |
+
"class": handler_classes["stream"],
|
| 321 |
+
"stream": log_streams["stderr"],
|
| 322 |
+
"no_color": no_color,
|
| 323 |
+
"filters": ["restrict_to_subprocess"],
|
| 324 |
+
"formatter": "indent",
|
| 325 |
+
},
|
| 326 |
+
"user_log": {
|
| 327 |
+
"level": "DEBUG",
|
| 328 |
+
"class": handler_classes["file"],
|
| 329 |
+
"filename": additional_log_file,
|
| 330 |
+
"encoding": "utf-8",
|
| 331 |
+
"delay": True,
|
| 332 |
+
"formatter": "indent_with_timestamp",
|
| 333 |
+
},
|
| 334 |
+
},
|
| 335 |
+
"root": {
|
| 336 |
+
"level": root_level,
|
| 337 |
+
"handlers": handlers,
|
| 338 |
+
},
|
| 339 |
+
"loggers": {"pip._vendor": {"level": vendored_log_level}},
|
| 340 |
+
}
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
return level_number
|
venv/lib/python3.10/site-packages/pip/_internal/utils/misc.py
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# The following comment should be removed at some point in the future.
|
| 2 |
+
# mypy: strict-optional=False
|
| 3 |
+
|
| 4 |
+
import contextlib
|
| 5 |
+
import errno
|
| 6 |
+
import getpass
|
| 7 |
+
import hashlib
|
| 8 |
+
import io
|
| 9 |
+
import logging
|
| 10 |
+
import os
|
| 11 |
+
import posixpath
|
| 12 |
+
import shutil
|
| 13 |
+
import stat
|
| 14 |
+
import sys
|
| 15 |
+
import urllib.parse
|
| 16 |
+
from io import StringIO
|
| 17 |
+
from itertools import filterfalse, tee, zip_longest
|
| 18 |
+
from types import TracebackType
|
| 19 |
+
from typing import (
|
| 20 |
+
Any,
|
| 21 |
+
BinaryIO,
|
| 22 |
+
Callable,
|
| 23 |
+
ContextManager,
|
| 24 |
+
Iterable,
|
| 25 |
+
Iterator,
|
| 26 |
+
List,
|
| 27 |
+
Optional,
|
| 28 |
+
TextIO,
|
| 29 |
+
Tuple,
|
| 30 |
+
Type,
|
| 31 |
+
TypeVar,
|
| 32 |
+
cast,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
|
| 36 |
+
|
| 37 |
+
from pip import __version__
|
| 38 |
+
from pip._internal.exceptions import CommandError
|
| 39 |
+
from pip._internal.locations import get_major_minor_version
|
| 40 |
+
from pip._internal.utils.compat import WINDOWS
|
| 41 |
+
from pip._internal.utils.virtualenv import running_under_virtualenv
|
| 42 |
+
|
| 43 |
+
__all__ = [
|
| 44 |
+
"rmtree",
|
| 45 |
+
"display_path",
|
| 46 |
+
"backup_dir",
|
| 47 |
+
"ask",
|
| 48 |
+
"splitext",
|
| 49 |
+
"format_size",
|
| 50 |
+
"is_installable_dir",
|
| 51 |
+
"normalize_path",
|
| 52 |
+
"renames",
|
| 53 |
+
"get_prog",
|
| 54 |
+
"captured_stdout",
|
| 55 |
+
"ensure_dir",
|
| 56 |
+
"remove_auth_from_url",
|
| 57 |
+
]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
logger = logging.getLogger(__name__)
|
| 61 |
+
|
| 62 |
+
T = TypeVar("T")
|
| 63 |
+
ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType]
|
| 64 |
+
VersionInfo = Tuple[int, int, int]
|
| 65 |
+
NetlocTuple = Tuple[str, Tuple[Optional[str], Optional[str]]]
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def get_pip_version() -> str:
|
| 69 |
+
pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..")
|
| 70 |
+
pip_pkg_dir = os.path.abspath(pip_pkg_dir)
|
| 71 |
+
|
| 72 |
+
return "pip {} from {} (python {})".format(
|
| 73 |
+
__version__,
|
| 74 |
+
pip_pkg_dir,
|
| 75 |
+
get_major_minor_version(),
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def normalize_version_info(py_version_info: Tuple[int, ...]) -> Tuple[int, int, int]:
|
| 80 |
+
"""
|
| 81 |
+
Convert a tuple of ints representing a Python version to one of length
|
| 82 |
+
three.
|
| 83 |
+
|
| 84 |
+
:param py_version_info: a tuple of ints representing a Python version,
|
| 85 |
+
or None to specify no version. The tuple can have any length.
|
| 86 |
+
|
| 87 |
+
:return: a tuple of length three if `py_version_info` is non-None.
|
| 88 |
+
Otherwise, return `py_version_info` unchanged (i.e. None).
|
| 89 |
+
"""
|
| 90 |
+
if len(py_version_info) < 3:
|
| 91 |
+
py_version_info += (3 - len(py_version_info)) * (0,)
|
| 92 |
+
elif len(py_version_info) > 3:
|
| 93 |
+
py_version_info = py_version_info[:3]
|
| 94 |
+
|
| 95 |
+
return cast("VersionInfo", py_version_info)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def ensure_dir(path: str) -> None:
|
| 99 |
+
"""os.path.makedirs without EEXIST."""
|
| 100 |
+
try:
|
| 101 |
+
os.makedirs(path)
|
| 102 |
+
except OSError as e:
|
| 103 |
+
# Windows can raise spurious ENOTEMPTY errors. See #6426.
|
| 104 |
+
if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
|
| 105 |
+
raise
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def get_prog() -> str:
|
| 109 |
+
try:
|
| 110 |
+
prog = os.path.basename(sys.argv[0])
|
| 111 |
+
if prog in ("__main__.py", "-c"):
|
| 112 |
+
return f"{sys.executable} -m pip"
|
| 113 |
+
else:
|
| 114 |
+
return prog
|
| 115 |
+
except (AttributeError, TypeError, IndexError):
|
| 116 |
+
pass
|
| 117 |
+
return "pip"
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
# Retry every half second for up to 3 seconds
|
| 121 |
+
# Tenacity raises RetryError by default, explicitly raise the original exception
|
| 122 |
+
@retry(reraise=True, stop=stop_after_delay(3), wait=wait_fixed(0.5))
|
| 123 |
+
def rmtree(dir: str, ignore_errors: bool = False) -> None:
|
| 124 |
+
shutil.rmtree(dir, ignore_errors=ignore_errors, onerror=rmtree_errorhandler)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def rmtree_errorhandler(func: Callable[..., Any], path: str, exc_info: ExcInfo) -> None:
|
| 128 |
+
"""On Windows, the files in .svn are read-only, so when rmtree() tries to
|
| 129 |
+
remove them, an exception is thrown. We catch that here, remove the
|
| 130 |
+
read-only attribute, and hopefully continue without problems."""
|
| 131 |
+
try:
|
| 132 |
+
has_attr_readonly = not (os.stat(path).st_mode & stat.S_IWRITE)
|
| 133 |
+
except OSError:
|
| 134 |
+
# it's equivalent to os.path.exists
|
| 135 |
+
return
|
| 136 |
+
|
| 137 |
+
if has_attr_readonly:
|
| 138 |
+
# convert to read/write
|
| 139 |
+
os.chmod(path, stat.S_IWRITE)
|
| 140 |
+
# use the original function to repeat the operation
|
| 141 |
+
func(path)
|
| 142 |
+
return
|
| 143 |
+
else:
|
| 144 |
+
raise
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def display_path(path: str) -> str:
|
| 148 |
+
"""Gives the display value for a given path, making it relative to cwd
|
| 149 |
+
if possible."""
|
| 150 |
+
path = os.path.normcase(os.path.abspath(path))
|
| 151 |
+
if path.startswith(os.getcwd() + os.path.sep):
|
| 152 |
+
path = "." + path[len(os.getcwd()) :]
|
| 153 |
+
return path
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def backup_dir(dir: str, ext: str = ".bak") -> str:
|
| 157 |
+
"""Figure out the name of a directory to back up the given dir to
|
| 158 |
+
(adding .bak, .bak2, etc)"""
|
| 159 |
+
n = 1
|
| 160 |
+
extension = ext
|
| 161 |
+
while os.path.exists(dir + extension):
|
| 162 |
+
n += 1
|
| 163 |
+
extension = ext + str(n)
|
| 164 |
+
return dir + extension
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def ask_path_exists(message: str, options: Iterable[str]) -> str:
|
| 168 |
+
for action in os.environ.get("PIP_EXISTS_ACTION", "").split():
|
| 169 |
+
if action in options:
|
| 170 |
+
return action
|
| 171 |
+
return ask(message, options)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def _check_no_input(message: str) -> None:
|
| 175 |
+
"""Raise an error if no input is allowed."""
|
| 176 |
+
if os.environ.get("PIP_NO_INPUT"):
|
| 177 |
+
raise Exception(
|
| 178 |
+
f"No input was expected ($PIP_NO_INPUT set); question: {message}"
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def ask(message: str, options: Iterable[str]) -> str:
|
| 183 |
+
"""Ask the message interactively, with the given possible responses"""
|
| 184 |
+
while 1:
|
| 185 |
+
_check_no_input(message)
|
| 186 |
+
response = input(message)
|
| 187 |
+
response = response.strip().lower()
|
| 188 |
+
if response not in options:
|
| 189 |
+
print(
|
| 190 |
+
"Your response ({!r}) was not one of the expected responses: "
|
| 191 |
+
"{}".format(response, ", ".join(options))
|
| 192 |
+
)
|
| 193 |
+
else:
|
| 194 |
+
return response
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def ask_input(message: str) -> str:
|
| 198 |
+
"""Ask for input interactively."""
|
| 199 |
+
_check_no_input(message)
|
| 200 |
+
return input(message)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def ask_password(message: str) -> str:
|
| 204 |
+
"""Ask for a password interactively."""
|
| 205 |
+
_check_no_input(message)
|
| 206 |
+
return getpass.getpass(message)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def strtobool(val: str) -> int:
|
| 210 |
+
"""Convert a string representation of truth to true (1) or false (0).
|
| 211 |
+
|
| 212 |
+
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
|
| 213 |
+
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
|
| 214 |
+
'val' is anything else.
|
| 215 |
+
"""
|
| 216 |
+
val = val.lower()
|
| 217 |
+
if val in ("y", "yes", "t", "true", "on", "1"):
|
| 218 |
+
return 1
|
| 219 |
+
elif val in ("n", "no", "f", "false", "off", "0"):
|
| 220 |
+
return 0
|
| 221 |
+
else:
|
| 222 |
+
raise ValueError(f"invalid truth value {val!r}")
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def format_size(bytes: float) -> str:
|
| 226 |
+
if bytes > 1000 * 1000:
|
| 227 |
+
return "{:.1f} MB".format(bytes / 1000.0 / 1000)
|
| 228 |
+
elif bytes > 10 * 1000:
|
| 229 |
+
return "{} kB".format(int(bytes / 1000))
|
| 230 |
+
elif bytes > 1000:
|
| 231 |
+
return "{:.1f} kB".format(bytes / 1000.0)
|
| 232 |
+
else:
|
| 233 |
+
return "{} bytes".format(int(bytes))
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def tabulate(rows: Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]:
|
| 237 |
+
"""Return a list of formatted rows and a list of column sizes.
|
| 238 |
+
|
| 239 |
+
For example::
|
| 240 |
+
|
| 241 |
+
>>> tabulate([['foobar', 2000], [0xdeadbeef]])
|
| 242 |
+
(['foobar 2000', '3735928559'], [10, 4])
|
| 243 |
+
"""
|
| 244 |
+
rows = [tuple(map(str, row)) for row in rows]
|
| 245 |
+
sizes = [max(map(len, col)) for col in zip_longest(*rows, fillvalue="")]
|
| 246 |
+
table = [" ".join(map(str.ljust, row, sizes)).rstrip() for row in rows]
|
| 247 |
+
return table, sizes
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def is_installable_dir(path: str) -> bool:
|
| 251 |
+
"""Is path is a directory containing pyproject.toml or setup.py?
|
| 252 |
+
|
| 253 |
+
If pyproject.toml exists, this is a PEP 517 project. Otherwise we look for
|
| 254 |
+
a legacy setuptools layout by identifying setup.py. We don't check for the
|
| 255 |
+
setup.cfg because using it without setup.py is only available for PEP 517
|
| 256 |
+
projects, which are already covered by the pyproject.toml check.
|
| 257 |
+
"""
|
| 258 |
+
if not os.path.isdir(path):
|
| 259 |
+
return False
|
| 260 |
+
if os.path.isfile(os.path.join(path, "pyproject.toml")):
|
| 261 |
+
return True
|
| 262 |
+
if os.path.isfile(os.path.join(path, "setup.py")):
|
| 263 |
+
return True
|
| 264 |
+
return False
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def read_chunks(file: BinaryIO, size: int = io.DEFAULT_BUFFER_SIZE) -> Iterator[bytes]:
|
| 268 |
+
"""Yield pieces of data from a file-like object until EOF."""
|
| 269 |
+
while True:
|
| 270 |
+
chunk = file.read(size)
|
| 271 |
+
if not chunk:
|
| 272 |
+
break
|
| 273 |
+
yield chunk
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def normalize_path(path: str, resolve_symlinks: bool = True) -> str:
|
| 277 |
+
"""
|
| 278 |
+
Convert a path to its canonical, case-normalized, absolute version.
|
| 279 |
+
|
| 280 |
+
"""
|
| 281 |
+
path = os.path.expanduser(path)
|
| 282 |
+
if resolve_symlinks:
|
| 283 |
+
path = os.path.realpath(path)
|
| 284 |
+
else:
|
| 285 |
+
path = os.path.abspath(path)
|
| 286 |
+
return os.path.normcase(path)
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def splitext(path: str) -> Tuple[str, str]:
|
| 290 |
+
"""Like os.path.splitext, but take off .tar too"""
|
| 291 |
+
base, ext = posixpath.splitext(path)
|
| 292 |
+
if base.lower().endswith(".tar"):
|
| 293 |
+
ext = base[-4:] + ext
|
| 294 |
+
base = base[:-4]
|
| 295 |
+
return base, ext
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def renames(old: str, new: str) -> None:
|
| 299 |
+
"""Like os.renames(), but handles renaming across devices."""
|
| 300 |
+
# Implementation borrowed from os.renames().
|
| 301 |
+
head, tail = os.path.split(new)
|
| 302 |
+
if head and tail and not os.path.exists(head):
|
| 303 |
+
os.makedirs(head)
|
| 304 |
+
|
| 305 |
+
shutil.move(old, new)
|
| 306 |
+
|
| 307 |
+
head, tail = os.path.split(old)
|
| 308 |
+
if head and tail:
|
| 309 |
+
try:
|
| 310 |
+
os.removedirs(head)
|
| 311 |
+
except OSError:
|
| 312 |
+
pass
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def is_local(path: str) -> bool:
|
| 316 |
+
"""
|
| 317 |
+
Return True if this is a path pip is allowed to modify.
|
| 318 |
+
|
| 319 |
+
If we're in a virtualenv, sys.prefix points to the virtualenv's
|
| 320 |
+
prefix; only sys.prefix is considered local.
|
| 321 |
+
|
| 322 |
+
If we're not in a virtualenv, in general we can modify anything.
|
| 323 |
+
However, if the OS vendor has configured distutils to install
|
| 324 |
+
somewhere other than sys.prefix (which could be a subdirectory of
|
| 325 |
+
sys.prefix, e.g. /usr/local), we consider sys.prefix itself nonlocal
|
| 326 |
+
and the domain of the OS vendor. (In other words, everything _other
|
| 327 |
+
than_ sys.prefix is considered local.)
|
| 328 |
+
|
| 329 |
+
Caution: this function assumes the head of path has been normalized
|
| 330 |
+
with normalize_path.
|
| 331 |
+
"""
|
| 332 |
+
|
| 333 |
+
path = normalize_path(path)
|
| 334 |
+
# Hard-coded becouse PyPy uses a different sys.prefix on Debian
|
| 335 |
+
prefix = '/usr'
|
| 336 |
+
|
| 337 |
+
if running_under_virtualenv():
|
| 338 |
+
return path.startswith(normalize_path(sys.prefix))
|
| 339 |
+
else:
|
| 340 |
+
from pip._internal.locations import get_scheme
|
| 341 |
+
from pip._internal.models.scheme import SCHEME_KEYS
|
| 342 |
+
if path.startswith(prefix):
|
| 343 |
+
scheme = get_scheme("")
|
| 344 |
+
for key in SCHEME_KEYS:
|
| 345 |
+
local_path = getattr(scheme, key)
|
| 346 |
+
if path.startswith(normalize_path(local_path)):
|
| 347 |
+
return True
|
| 348 |
+
return False
|
| 349 |
+
else:
|
| 350 |
+
return True
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
def write_output(msg: Any, *args: Any) -> None:
|
| 354 |
+
logger.info(msg, *args)
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
class StreamWrapper(StringIO):
|
| 358 |
+
orig_stream: TextIO = None
|
| 359 |
+
|
| 360 |
+
@classmethod
|
| 361 |
+
def from_stream(cls, orig_stream: TextIO) -> "StreamWrapper":
|
| 362 |
+
cls.orig_stream = orig_stream
|
| 363 |
+
return cls()
|
| 364 |
+
|
| 365 |
+
# compileall.compile_dir() needs stdout.encoding to print to stdout
|
| 366 |
+
# https://github.com/python/mypy/issues/4125
|
| 367 |
+
@property
|
| 368 |
+
def encoding(self): # type: ignore
|
| 369 |
+
return self.orig_stream.encoding
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
@contextlib.contextmanager
|
| 373 |
+
def captured_output(stream_name: str) -> Iterator[StreamWrapper]:
|
| 374 |
+
"""Return a context manager used by captured_stdout/stdin/stderr
|
| 375 |
+
that temporarily replaces the sys stream *stream_name* with a StringIO.
|
| 376 |
+
|
| 377 |
+
Taken from Lib/support/__init__.py in the CPython repo.
|
| 378 |
+
"""
|
| 379 |
+
orig_stdout = getattr(sys, stream_name)
|
| 380 |
+
setattr(sys, stream_name, StreamWrapper.from_stream(orig_stdout))
|
| 381 |
+
try:
|
| 382 |
+
yield getattr(sys, stream_name)
|
| 383 |
+
finally:
|
| 384 |
+
setattr(sys, stream_name, orig_stdout)
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def captured_stdout() -> ContextManager[StreamWrapper]:
|
| 388 |
+
"""Capture the output of sys.stdout:
|
| 389 |
+
|
| 390 |
+
with captured_stdout() as stdout:
|
| 391 |
+
print('hello')
|
| 392 |
+
self.assertEqual(stdout.getvalue(), 'hello\n')
|
| 393 |
+
|
| 394 |
+
Taken from Lib/support/__init__.py in the CPython repo.
|
| 395 |
+
"""
|
| 396 |
+
return captured_output("stdout")
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def captured_stderr() -> ContextManager[StreamWrapper]:
|
| 400 |
+
"""
|
| 401 |
+
See captured_stdout().
|
| 402 |
+
"""
|
| 403 |
+
return captured_output("stderr")
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
# Simulates an enum
|
| 407 |
+
def enum(*sequential: Any, **named: Any) -> Type[Any]:
|
| 408 |
+
enums = dict(zip(sequential, range(len(sequential))), **named)
|
| 409 |
+
reverse = {value: key for key, value in enums.items()}
|
| 410 |
+
enums["reverse_mapping"] = reverse
|
| 411 |
+
return type("Enum", (), enums)
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
def build_netloc(host: str, port: Optional[int]) -> str:
|
| 415 |
+
"""
|
| 416 |
+
Build a netloc from a host-port pair
|
| 417 |
+
"""
|
| 418 |
+
if port is None:
|
| 419 |
+
return host
|
| 420 |
+
if ":" in host:
|
| 421 |
+
# Only wrap host with square brackets when it is IPv6
|
| 422 |
+
host = f"[{host}]"
|
| 423 |
+
return f"{host}:{port}"
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
def build_url_from_netloc(netloc: str, scheme: str = "https") -> str:
|
| 427 |
+
"""
|
| 428 |
+
Build a full URL from a netloc.
|
| 429 |
+
"""
|
| 430 |
+
if netloc.count(":") >= 2 and "@" not in netloc and "[" not in netloc:
|
| 431 |
+
# It must be a bare IPv6 address, so wrap it with brackets.
|
| 432 |
+
netloc = f"[{netloc}]"
|
| 433 |
+
return f"{scheme}://{netloc}"
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
def parse_netloc(netloc: str) -> Tuple[str, Optional[int]]:
|
| 437 |
+
"""
|
| 438 |
+
Return the host-port pair from a netloc.
|
| 439 |
+
"""
|
| 440 |
+
url = build_url_from_netloc(netloc)
|
| 441 |
+
parsed = urllib.parse.urlparse(url)
|
| 442 |
+
return parsed.hostname, parsed.port
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
def split_auth_from_netloc(netloc: str) -> NetlocTuple:
|
| 446 |
+
"""
|
| 447 |
+
Parse out and remove the auth information from a netloc.
|
| 448 |
+
|
| 449 |
+
Returns: (netloc, (username, password)).
|
| 450 |
+
"""
|
| 451 |
+
if "@" not in netloc:
|
| 452 |
+
return netloc, (None, None)
|
| 453 |
+
|
| 454 |
+
# Split from the right because that's how urllib.parse.urlsplit()
|
| 455 |
+
# behaves if more than one @ is present (which can be checked using
|
| 456 |
+
# the password attribute of urlsplit()'s return value).
|
| 457 |
+
auth, netloc = netloc.rsplit("@", 1)
|
| 458 |
+
pw: Optional[str] = None
|
| 459 |
+
if ":" in auth:
|
| 460 |
+
# Split from the left because that's how urllib.parse.urlsplit()
|
| 461 |
+
# behaves if more than one : is present (which again can be checked
|
| 462 |
+
# using the password attribute of the return value)
|
| 463 |
+
user, pw = auth.split(":", 1)
|
| 464 |
+
else:
|
| 465 |
+
user, pw = auth, None
|
| 466 |
+
|
| 467 |
+
user = urllib.parse.unquote(user)
|
| 468 |
+
if pw is not None:
|
| 469 |
+
pw = urllib.parse.unquote(pw)
|
| 470 |
+
|
| 471 |
+
return netloc, (user, pw)
|
| 472 |
+
|
| 473 |
+
|
| 474 |
+
def redact_netloc(netloc: str) -> str:
|
| 475 |
+
"""
|
| 476 |
+
Replace the sensitive data in a netloc with "****", if it exists.
|
| 477 |
+
|
| 478 |
+
For example:
|
| 479 |
+
- "user:pass@example.com" returns "user:****@example.com"
|
| 480 |
+
- "accesstoken@example.com" returns "****@example.com"
|
| 481 |
+
"""
|
| 482 |
+
netloc, (user, password) = split_auth_from_netloc(netloc)
|
| 483 |
+
if user is None:
|
| 484 |
+
return netloc
|
| 485 |
+
if password is None:
|
| 486 |
+
user = "****"
|
| 487 |
+
password = ""
|
| 488 |
+
else:
|
| 489 |
+
user = urllib.parse.quote(user)
|
| 490 |
+
password = ":****"
|
| 491 |
+
return "{user}{password}@{netloc}".format(
|
| 492 |
+
user=user, password=password, netloc=netloc
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
def _transform_url(
|
| 497 |
+
url: str, transform_netloc: Callable[[str], Tuple[Any, ...]]
|
| 498 |
+
) -> Tuple[str, NetlocTuple]:
|
| 499 |
+
"""Transform and replace netloc in a url.
|
| 500 |
+
|
| 501 |
+
transform_netloc is a function taking the netloc and returning a
|
| 502 |
+
tuple. The first element of this tuple is the new netloc. The
|
| 503 |
+
entire tuple is returned.
|
| 504 |
+
|
| 505 |
+
Returns a tuple containing the transformed url as item 0 and the
|
| 506 |
+
original tuple returned by transform_netloc as item 1.
|
| 507 |
+
"""
|
| 508 |
+
purl = urllib.parse.urlsplit(url)
|
| 509 |
+
netloc_tuple = transform_netloc(purl.netloc)
|
| 510 |
+
# stripped url
|
| 511 |
+
url_pieces = (purl.scheme, netloc_tuple[0], purl.path, purl.query, purl.fragment)
|
| 512 |
+
surl = urllib.parse.urlunsplit(url_pieces)
|
| 513 |
+
return surl, cast("NetlocTuple", netloc_tuple)
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
def _get_netloc(netloc: str) -> NetlocTuple:
|
| 517 |
+
return split_auth_from_netloc(netloc)
|
| 518 |
+
|
| 519 |
+
|
| 520 |
+
def _redact_netloc(netloc: str) -> Tuple[str]:
|
| 521 |
+
return (redact_netloc(netloc),)
|
| 522 |
+
|
| 523 |
+
|
| 524 |
+
def split_auth_netloc_from_url(url: str) -> Tuple[str, str, Tuple[str, str]]:
|
| 525 |
+
"""
|
| 526 |
+
Parse a url into separate netloc, auth, and url with no auth.
|
| 527 |
+
|
| 528 |
+
Returns: (url_without_auth, netloc, (username, password))
|
| 529 |
+
"""
|
| 530 |
+
url_without_auth, (netloc, auth) = _transform_url(url, _get_netloc)
|
| 531 |
+
return url_without_auth, netloc, auth
|
| 532 |
+
|
| 533 |
+
|
| 534 |
+
def remove_auth_from_url(url: str) -> str:
|
| 535 |
+
"""Return a copy of url with 'username:password@' removed."""
|
| 536 |
+
# username/pass params are passed to subversion through flags
|
| 537 |
+
# and are not recognized in the url.
|
| 538 |
+
return _transform_url(url, _get_netloc)[0]
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
def redact_auth_from_url(url: str) -> str:
|
| 542 |
+
"""Replace the password in a given url with ****."""
|
| 543 |
+
return _transform_url(url, _redact_netloc)[0]
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
class HiddenText:
|
| 547 |
+
def __init__(self, secret: str, redacted: str) -> None:
|
| 548 |
+
self.secret = secret
|
| 549 |
+
self.redacted = redacted
|
| 550 |
+
|
| 551 |
+
def __repr__(self) -> str:
|
| 552 |
+
return "<HiddenText {!r}>".format(str(self))
|
| 553 |
+
|
| 554 |
+
def __str__(self) -> str:
|
| 555 |
+
return self.redacted
|
| 556 |
+
|
| 557 |
+
# This is useful for testing.
|
| 558 |
+
def __eq__(self, other: Any) -> bool:
|
| 559 |
+
if type(self) != type(other):
|
| 560 |
+
return False
|
| 561 |
+
|
| 562 |
+
# The string being used for redaction doesn't also have to match,
|
| 563 |
+
# just the raw, original string.
|
| 564 |
+
return self.secret == other.secret
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
def hide_value(value: str) -> HiddenText:
|
| 568 |
+
return HiddenText(value, redacted="****")
|
| 569 |
+
|
| 570 |
+
|
| 571 |
+
def hide_url(url: str) -> HiddenText:
|
| 572 |
+
redacted = redact_auth_from_url(url)
|
| 573 |
+
return HiddenText(url, redacted=redacted)
|
| 574 |
+
|
| 575 |
+
|
| 576 |
+
def protect_pip_from_modification_on_windows(modifying_pip: bool) -> None:
|
| 577 |
+
"""Protection of pip.exe from modification on Windows
|
| 578 |
+
|
| 579 |
+
On Windows, any operation modifying pip should be run as:
|
| 580 |
+
python -m pip ...
|
| 581 |
+
"""
|
| 582 |
+
pip_names = [
|
| 583 |
+
"pip.exe",
|
| 584 |
+
"pip{}.exe".format(sys.version_info[0]),
|
| 585 |
+
"pip{}.{}.exe".format(*sys.version_info[:2]),
|
| 586 |
+
]
|
| 587 |
+
|
| 588 |
+
# See https://github.com/pypa/pip/issues/1299 for more discussion
|
| 589 |
+
should_show_use_python_msg = (
|
| 590 |
+
modifying_pip and WINDOWS and os.path.basename(sys.argv[0]) in pip_names
|
| 591 |
+
)
|
| 592 |
+
|
| 593 |
+
if should_show_use_python_msg:
|
| 594 |
+
new_command = [sys.executable, "-m", "pip"] + sys.argv[1:]
|
| 595 |
+
raise CommandError(
|
| 596 |
+
"To modify pip, please run the following command:\n{}".format(
|
| 597 |
+
" ".join(new_command)
|
| 598 |
+
)
|
| 599 |
+
)
|
| 600 |
+
|
| 601 |
+
|
| 602 |
+
def is_console_interactive() -> bool:
|
| 603 |
+
"""Is this console interactive?"""
|
| 604 |
+
return sys.stdin is not None and sys.stdin.isatty()
|
| 605 |
+
|
| 606 |
+
|
| 607 |
+
def hash_file(path: str, blocksize: int = 1 << 20) -> Tuple[Any, int]:
|
| 608 |
+
"""Return (hash, length) for path using hashlib.sha256()"""
|
| 609 |
+
|
| 610 |
+
h = hashlib.sha256()
|
| 611 |
+
length = 0
|
| 612 |
+
with open(path, "rb") as f:
|
| 613 |
+
for block in read_chunks(f, size=blocksize):
|
| 614 |
+
length += len(block)
|
| 615 |
+
h.update(block)
|
| 616 |
+
return h, length
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
def is_wheel_installed() -> bool:
|
| 620 |
+
"""
|
| 621 |
+
Return whether the wheel package is installed.
|
| 622 |
+
"""
|
| 623 |
+
try:
|
| 624 |
+
import wheel # noqa: F401
|
| 625 |
+
except ImportError:
|
| 626 |
+
return False
|
| 627 |
+
|
| 628 |
+
return True
|
| 629 |
+
|
| 630 |
+
|
| 631 |
+
def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]:
|
| 632 |
+
"""
|
| 633 |
+
Return paired elements.
|
| 634 |
+
|
| 635 |
+
For example:
|
| 636 |
+
s -> (s0, s1), (s2, s3), (s4, s5), ...
|
| 637 |
+
"""
|
| 638 |
+
iterable = iter(iterable)
|
| 639 |
+
return zip_longest(iterable, iterable)
|
| 640 |
+
|
| 641 |
+
|
| 642 |
+
def partition(
|
| 643 |
+
pred: Callable[[T], bool],
|
| 644 |
+
iterable: Iterable[T],
|
| 645 |
+
) -> Tuple[Iterable[T], Iterable[T]]:
|
| 646 |
+
"""
|
| 647 |
+
Use a predicate to partition entries into false entries and true entries,
|
| 648 |
+
like
|
| 649 |
+
|
| 650 |
+
partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9
|
| 651 |
+
"""
|
| 652 |
+
t1, t2 = tee(iterable)
|
| 653 |
+
return filterfalse(pred, t1), filter(pred, t2)
|
venv/lib/python3.10/site-packages/pip/_internal/utils/models.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Utilities for defining models
|
| 2 |
+
"""
|
| 3 |
+
|
| 4 |
+
import operator
|
| 5 |
+
from typing import Any, Callable, Type
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class KeyBasedCompareMixin:
|
| 9 |
+
"""Provides comparison capabilities that is based on a key"""
|
| 10 |
+
|
| 11 |
+
__slots__ = ["_compare_key", "_defining_class"]
|
| 12 |
+
|
| 13 |
+
def __init__(self, key: Any, defining_class: Type["KeyBasedCompareMixin"]) -> None:
|
| 14 |
+
self._compare_key = key
|
| 15 |
+
self._defining_class = defining_class
|
| 16 |
+
|
| 17 |
+
def __hash__(self) -> int:
|
| 18 |
+
return hash(self._compare_key)
|
| 19 |
+
|
| 20 |
+
def __lt__(self, other: Any) -> bool:
|
| 21 |
+
return self._compare(other, operator.__lt__)
|
| 22 |
+
|
| 23 |
+
def __le__(self, other: Any) -> bool:
|
| 24 |
+
return self._compare(other, operator.__le__)
|
| 25 |
+
|
| 26 |
+
def __gt__(self, other: Any) -> bool:
|
| 27 |
+
return self._compare(other, operator.__gt__)
|
| 28 |
+
|
| 29 |
+
def __ge__(self, other: Any) -> bool:
|
| 30 |
+
return self._compare(other, operator.__ge__)
|
| 31 |
+
|
| 32 |
+
def __eq__(self, other: Any) -> bool:
|
| 33 |
+
return self._compare(other, operator.__eq__)
|
| 34 |
+
|
| 35 |
+
def _compare(self, other: Any, method: Callable[[Any, Any], bool]) -> bool:
|
| 36 |
+
if not isinstance(other, self._defining_class):
|
| 37 |
+
return NotImplemented
|
| 38 |
+
|
| 39 |
+
return method(self._compare_key, other._compare_key)
|
venv/lib/python3.10/site-packages/pip/_internal/utils/packaging.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import functools
|
| 2 |
+
import logging
|
| 3 |
+
import re
|
| 4 |
+
from typing import NewType, Optional, Tuple, cast
|
| 5 |
+
|
| 6 |
+
from pip._vendor.packaging import specifiers, version
|
| 7 |
+
from pip._vendor.packaging.requirements import Requirement
|
| 8 |
+
|
| 9 |
+
NormalizedExtra = NewType("NormalizedExtra", str)
|
| 10 |
+
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def check_requires_python(
|
| 15 |
+
requires_python: Optional[str], version_info: Tuple[int, ...]
|
| 16 |
+
) -> bool:
|
| 17 |
+
"""
|
| 18 |
+
Check if the given Python version matches a "Requires-Python" specifier.
|
| 19 |
+
|
| 20 |
+
:param version_info: A 3-tuple of ints representing a Python
|
| 21 |
+
major-minor-micro version to check (e.g. `sys.version_info[:3]`).
|
| 22 |
+
|
| 23 |
+
:return: `True` if the given Python version satisfies the requirement.
|
| 24 |
+
Otherwise, return `False`.
|
| 25 |
+
|
| 26 |
+
:raises InvalidSpecifier: If `requires_python` has an invalid format.
|
| 27 |
+
"""
|
| 28 |
+
if requires_python is None:
|
| 29 |
+
# The package provides no information
|
| 30 |
+
return True
|
| 31 |
+
requires_python_specifier = specifiers.SpecifierSet(requires_python)
|
| 32 |
+
|
| 33 |
+
python_version = version.parse(".".join(map(str, version_info)))
|
| 34 |
+
return python_version in requires_python_specifier
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
@functools.lru_cache(maxsize=512)
|
| 38 |
+
def get_requirement(req_string: str) -> Requirement:
|
| 39 |
+
"""Construct a packaging.Requirement object with caching"""
|
| 40 |
+
# Parsing requirement strings is expensive, and is also expected to happen
|
| 41 |
+
# with a low diversity of different arguments (at least relative the number
|
| 42 |
+
# constructed). This method adds a cache to requirement object creation to
|
| 43 |
+
# minimize repeated parsing of the same string to construct equivalent
|
| 44 |
+
# Requirement objects.
|
| 45 |
+
return Requirement(req_string)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def safe_extra(extra: str) -> NormalizedExtra:
|
| 49 |
+
"""Convert an arbitrary string to a standard 'extra' name
|
| 50 |
+
|
| 51 |
+
Any runs of non-alphanumeric characters are replaced with a single '_',
|
| 52 |
+
and the result is always lowercased.
|
| 53 |
+
|
| 54 |
+
This function is duplicated from ``pkg_resources``. Note that this is not
|
| 55 |
+
the same to either ``canonicalize_name`` or ``_egg_link_name``.
|
| 56 |
+
"""
|
| 57 |
+
return cast(NormalizedExtra, re.sub("[^A-Za-z0-9.-]+", "_", extra).lower())
|
venv/lib/python3.10/site-packages/pip/_internal/utils/setuptools_build.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import textwrap
|
| 3 |
+
from typing import List, Optional, Sequence
|
| 4 |
+
|
| 5 |
+
# Shim to wrap setup.py invocation with setuptools
|
| 6 |
+
# Note that __file__ is handled via two {!r} *and* %r, to ensure that paths on
|
| 7 |
+
# Windows are correctly handled (it should be "C:\\Users" not "C:\Users").
|
| 8 |
+
_SETUPTOOLS_SHIM = textwrap.dedent(
|
| 9 |
+
"""
|
| 10 |
+
exec(compile('''
|
| 11 |
+
# This is <pip-setuptools-caller> -- a caller that pip uses to run setup.py
|
| 12 |
+
#
|
| 13 |
+
# - It imports setuptools before invoking setup.py, to enable projects that directly
|
| 14 |
+
# import from `distutils.core` to work with newer packaging standards.
|
| 15 |
+
# - It provides a clear error message when setuptools is not installed.
|
| 16 |
+
# - It sets `sys.argv[0]` to the underlying `setup.py`, when invoking `setup.py` so
|
| 17 |
+
# setuptools doesn't think the script is `-c`. This avoids the following warning:
|
| 18 |
+
# manifest_maker: standard file '-c' not found".
|
| 19 |
+
# - It generates a shim setup.py, for handling setup.cfg-only projects.
|
| 20 |
+
import os, sys, tokenize
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
import setuptools
|
| 24 |
+
except ImportError as error:
|
| 25 |
+
print(
|
| 26 |
+
"ERROR: Can not execute `setup.py` since setuptools is not available in "
|
| 27 |
+
"the build environment.",
|
| 28 |
+
file=sys.stderr,
|
| 29 |
+
)
|
| 30 |
+
sys.exit(1)
|
| 31 |
+
|
| 32 |
+
__file__ = %r
|
| 33 |
+
sys.argv[0] = __file__
|
| 34 |
+
|
| 35 |
+
if os.path.exists(__file__):
|
| 36 |
+
filename = __file__
|
| 37 |
+
with tokenize.open(__file__) as f:
|
| 38 |
+
setup_py_code = f.read()
|
| 39 |
+
else:
|
| 40 |
+
filename = "<auto-generated setuptools caller>"
|
| 41 |
+
setup_py_code = "from setuptools import setup; setup()"
|
| 42 |
+
|
| 43 |
+
exec(compile(setup_py_code, filename, "exec"))
|
| 44 |
+
''' % ({!r},), "<pip-setuptools-caller>", "exec"))
|
| 45 |
+
"""
|
| 46 |
+
).rstrip()
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def make_setuptools_shim_args(
|
| 50 |
+
setup_py_path: str,
|
| 51 |
+
global_options: Sequence[str] = None,
|
| 52 |
+
no_user_config: bool = False,
|
| 53 |
+
unbuffered_output: bool = False,
|
| 54 |
+
) -> List[str]:
|
| 55 |
+
"""
|
| 56 |
+
Get setuptools command arguments with shim wrapped setup file invocation.
|
| 57 |
+
|
| 58 |
+
:param setup_py_path: The path to setup.py to be wrapped.
|
| 59 |
+
:param global_options: Additional global options.
|
| 60 |
+
:param no_user_config: If True, disables personal user configuration.
|
| 61 |
+
:param unbuffered_output: If True, adds the unbuffered switch to the
|
| 62 |
+
argument list.
|
| 63 |
+
"""
|
| 64 |
+
args = [sys.executable]
|
| 65 |
+
if unbuffered_output:
|
| 66 |
+
args += ["-u"]
|
| 67 |
+
args += ["-c", _SETUPTOOLS_SHIM.format(setup_py_path)]
|
| 68 |
+
if global_options:
|
| 69 |
+
args += global_options
|
| 70 |
+
if no_user_config:
|
| 71 |
+
args += ["--no-user-cfg"]
|
| 72 |
+
return args
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def make_setuptools_bdist_wheel_args(
|
| 76 |
+
setup_py_path: str,
|
| 77 |
+
global_options: Sequence[str],
|
| 78 |
+
build_options: Sequence[str],
|
| 79 |
+
destination_dir: str,
|
| 80 |
+
) -> List[str]:
|
| 81 |
+
# NOTE: Eventually, we'd want to also -S to the flags here, when we're
|
| 82 |
+
# isolating. Currently, it breaks Python in virtualenvs, because it
|
| 83 |
+
# relies on site.py to find parts of the standard library outside the
|
| 84 |
+
# virtualenv.
|
| 85 |
+
args = make_setuptools_shim_args(
|
| 86 |
+
setup_py_path, global_options=global_options, unbuffered_output=True
|
| 87 |
+
)
|
| 88 |
+
args += ["bdist_wheel", "-d", destination_dir]
|
| 89 |
+
args += build_options
|
| 90 |
+
return args
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def make_setuptools_clean_args(
|
| 94 |
+
setup_py_path: str,
|
| 95 |
+
global_options: Sequence[str],
|
| 96 |
+
) -> List[str]:
|
| 97 |
+
args = make_setuptools_shim_args(
|
| 98 |
+
setup_py_path, global_options=global_options, unbuffered_output=True
|
| 99 |
+
)
|
| 100 |
+
args += ["clean", "--all"]
|
| 101 |
+
return args
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def make_setuptools_develop_args(
|
| 105 |
+
setup_py_path: str,
|
| 106 |
+
global_options: Sequence[str],
|
| 107 |
+
install_options: Sequence[str],
|
| 108 |
+
no_user_config: bool,
|
| 109 |
+
prefix: Optional[str],
|
| 110 |
+
home: Optional[str],
|
| 111 |
+
use_user_site: bool,
|
| 112 |
+
) -> List[str]:
|
| 113 |
+
assert not (use_user_site and prefix)
|
| 114 |
+
|
| 115 |
+
args = make_setuptools_shim_args(
|
| 116 |
+
setup_py_path,
|
| 117 |
+
global_options=global_options,
|
| 118 |
+
no_user_config=no_user_config,
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
args += ["develop", "--no-deps"]
|
| 122 |
+
|
| 123 |
+
args += install_options
|
| 124 |
+
|
| 125 |
+
if prefix:
|
| 126 |
+
args += ["--prefix", prefix]
|
| 127 |
+
if home is not None:
|
| 128 |
+
args += ["--install-dir", home]
|
| 129 |
+
|
| 130 |
+
if use_user_site:
|
| 131 |
+
args += ["--user", "--prefix="]
|
| 132 |
+
|
| 133 |
+
return args
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def make_setuptools_egg_info_args(
|
| 137 |
+
setup_py_path: str,
|
| 138 |
+
egg_info_dir: Optional[str],
|
| 139 |
+
no_user_config: bool,
|
| 140 |
+
) -> List[str]:
|
| 141 |
+
args = make_setuptools_shim_args(setup_py_path, no_user_config=no_user_config)
|
| 142 |
+
|
| 143 |
+
args += ["egg_info"]
|
| 144 |
+
|
| 145 |
+
if egg_info_dir:
|
| 146 |
+
args += ["--egg-base", egg_info_dir]
|
| 147 |
+
|
| 148 |
+
return args
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def make_setuptools_install_args(
|
| 152 |
+
setup_py_path: str,
|
| 153 |
+
global_options: Sequence[str],
|
| 154 |
+
install_options: Sequence[str],
|
| 155 |
+
record_filename: str,
|
| 156 |
+
root: Optional[str],
|
| 157 |
+
prefix: Optional[str],
|
| 158 |
+
header_dir: Optional[str],
|
| 159 |
+
home: Optional[str],
|
| 160 |
+
use_user_site: bool,
|
| 161 |
+
no_user_config: bool,
|
| 162 |
+
pycompile: bool,
|
| 163 |
+
) -> List[str]:
|
| 164 |
+
assert not (use_user_site and prefix)
|
| 165 |
+
assert not (use_user_site and root)
|
| 166 |
+
|
| 167 |
+
args = make_setuptools_shim_args(
|
| 168 |
+
setup_py_path,
|
| 169 |
+
global_options=global_options,
|
| 170 |
+
no_user_config=no_user_config,
|
| 171 |
+
unbuffered_output=True,
|
| 172 |
+
)
|
| 173 |
+
args += ["install", "--record", record_filename]
|
| 174 |
+
args += ["--single-version-externally-managed"]
|
| 175 |
+
|
| 176 |
+
if root is not None:
|
| 177 |
+
args += ["--root", root]
|
| 178 |
+
if prefix is not None:
|
| 179 |
+
args += ["--prefix", prefix]
|
| 180 |
+
if home is not None:
|
| 181 |
+
args += ["--home", home]
|
| 182 |
+
if use_user_site:
|
| 183 |
+
args += ["--user", "--prefix="]
|
| 184 |
+
|
| 185 |
+
if pycompile:
|
| 186 |
+
args += ["--compile"]
|
| 187 |
+
else:
|
| 188 |
+
args += ["--no-compile"]
|
| 189 |
+
|
| 190 |
+
if header_dir:
|
| 191 |
+
args += ["--install-headers", header_dir]
|
| 192 |
+
|
| 193 |
+
args += install_options
|
| 194 |
+
|
| 195 |
+
return args
|
venv/lib/python3.10/site-packages/pip/_internal/utils/subprocess.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
import shlex
|
| 4 |
+
import subprocess
|
| 5 |
+
from typing import (
|
| 6 |
+
TYPE_CHECKING,
|
| 7 |
+
Any,
|
| 8 |
+
Callable,
|
| 9 |
+
Iterable,
|
| 10 |
+
List,
|
| 11 |
+
Mapping,
|
| 12 |
+
Optional,
|
| 13 |
+
Union,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
from pip._vendor.rich.markup import escape
|
| 17 |
+
|
| 18 |
+
from pip._internal.cli.spinners import SpinnerInterface, open_spinner
|
| 19 |
+
from pip._internal.exceptions import InstallationSubprocessError
|
| 20 |
+
from pip._internal.utils.logging import VERBOSE, subprocess_logger
|
| 21 |
+
from pip._internal.utils.misc import HiddenText
|
| 22 |
+
|
| 23 |
+
if TYPE_CHECKING:
|
| 24 |
+
# Literal was introduced in Python 3.8.
|
| 25 |
+
#
|
| 26 |
+
# TODO: Remove `if TYPE_CHECKING` when dropping support for Python 3.7.
|
| 27 |
+
from typing import Literal
|
| 28 |
+
|
| 29 |
+
CommandArgs = List[Union[str, HiddenText]]
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs:
|
| 33 |
+
"""
|
| 34 |
+
Create a CommandArgs object.
|
| 35 |
+
"""
|
| 36 |
+
command_args: CommandArgs = []
|
| 37 |
+
for arg in args:
|
| 38 |
+
# Check for list instead of CommandArgs since CommandArgs is
|
| 39 |
+
# only known during type-checking.
|
| 40 |
+
if isinstance(arg, list):
|
| 41 |
+
command_args.extend(arg)
|
| 42 |
+
else:
|
| 43 |
+
# Otherwise, arg is str or HiddenText.
|
| 44 |
+
command_args.append(arg)
|
| 45 |
+
|
| 46 |
+
return command_args
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def format_command_args(args: Union[List[str], CommandArgs]) -> str:
|
| 50 |
+
"""
|
| 51 |
+
Format command arguments for display.
|
| 52 |
+
"""
|
| 53 |
+
# For HiddenText arguments, display the redacted form by calling str().
|
| 54 |
+
# Also, we don't apply str() to arguments that aren't HiddenText since
|
| 55 |
+
# this can trigger a UnicodeDecodeError in Python 2 if the argument
|
| 56 |
+
# has type unicode and includes a non-ascii character. (The type
|
| 57 |
+
# checker doesn't ensure the annotations are correct in all cases.)
|
| 58 |
+
return " ".join(
|
| 59 |
+
shlex.quote(str(arg)) if isinstance(arg, HiddenText) else shlex.quote(arg)
|
| 60 |
+
for arg in args
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]:
|
| 65 |
+
"""
|
| 66 |
+
Return the arguments in their raw, unredacted form.
|
| 67 |
+
"""
|
| 68 |
+
return [arg.secret if isinstance(arg, HiddenText) else arg for arg in args]
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def call_subprocess(
|
| 72 |
+
cmd: Union[List[str], CommandArgs],
|
| 73 |
+
show_stdout: bool = False,
|
| 74 |
+
cwd: Optional[str] = None,
|
| 75 |
+
on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise",
|
| 76 |
+
extra_ok_returncodes: Optional[Iterable[int]] = None,
|
| 77 |
+
extra_environ: Optional[Mapping[str, Any]] = None,
|
| 78 |
+
unset_environ: Optional[Iterable[str]] = None,
|
| 79 |
+
spinner: Optional[SpinnerInterface] = None,
|
| 80 |
+
log_failed_cmd: Optional[bool] = True,
|
| 81 |
+
stdout_only: Optional[bool] = False,
|
| 82 |
+
*,
|
| 83 |
+
command_desc: str,
|
| 84 |
+
) -> str:
|
| 85 |
+
"""
|
| 86 |
+
Args:
|
| 87 |
+
show_stdout: if true, use INFO to log the subprocess's stderr and
|
| 88 |
+
stdout streams. Otherwise, use DEBUG. Defaults to False.
|
| 89 |
+
extra_ok_returncodes: an iterable of integer return codes that are
|
| 90 |
+
acceptable, in addition to 0. Defaults to None, which means [].
|
| 91 |
+
unset_environ: an iterable of environment variable names to unset
|
| 92 |
+
prior to calling subprocess.Popen().
|
| 93 |
+
log_failed_cmd: if false, failed commands are not logged, only raised.
|
| 94 |
+
stdout_only: if true, return only stdout, else return both. When true,
|
| 95 |
+
logging of both stdout and stderr occurs when the subprocess has
|
| 96 |
+
terminated, else logging occurs as subprocess output is produced.
|
| 97 |
+
"""
|
| 98 |
+
if extra_ok_returncodes is None:
|
| 99 |
+
extra_ok_returncodes = []
|
| 100 |
+
if unset_environ is None:
|
| 101 |
+
unset_environ = []
|
| 102 |
+
# Most places in pip use show_stdout=False. What this means is--
|
| 103 |
+
#
|
| 104 |
+
# - We connect the child's output (combined stderr and stdout) to a
|
| 105 |
+
# single pipe, which we read.
|
| 106 |
+
# - We log this output to stderr at DEBUG level as it is received.
|
| 107 |
+
# - If DEBUG logging isn't enabled (e.g. if --verbose logging wasn't
|
| 108 |
+
# requested), then we show a spinner so the user can still see the
|
| 109 |
+
# subprocess is in progress.
|
| 110 |
+
# - If the subprocess exits with an error, we log the output to stderr
|
| 111 |
+
# at ERROR level if it hasn't already been displayed to the console
|
| 112 |
+
# (e.g. if --verbose logging wasn't enabled). This way we don't log
|
| 113 |
+
# the output to the console twice.
|
| 114 |
+
#
|
| 115 |
+
# If show_stdout=True, then the above is still done, but with DEBUG
|
| 116 |
+
# replaced by INFO.
|
| 117 |
+
if show_stdout:
|
| 118 |
+
# Then log the subprocess output at INFO level.
|
| 119 |
+
log_subprocess = subprocess_logger.info
|
| 120 |
+
used_level = logging.INFO
|
| 121 |
+
else:
|
| 122 |
+
# Then log the subprocess output using VERBOSE. This also ensures
|
| 123 |
+
# it will be logged to the log file (aka user_log), if enabled.
|
| 124 |
+
log_subprocess = subprocess_logger.verbose
|
| 125 |
+
used_level = VERBOSE
|
| 126 |
+
|
| 127 |
+
# Whether the subprocess will be visible in the console.
|
| 128 |
+
showing_subprocess = subprocess_logger.getEffectiveLevel() <= used_level
|
| 129 |
+
|
| 130 |
+
# Only use the spinner if we're not showing the subprocess output
|
| 131 |
+
# and we have a spinner.
|
| 132 |
+
use_spinner = not showing_subprocess and spinner is not None
|
| 133 |
+
|
| 134 |
+
log_subprocess("Running command %s", command_desc)
|
| 135 |
+
env = os.environ.copy()
|
| 136 |
+
if extra_environ:
|
| 137 |
+
env.update(extra_environ)
|
| 138 |
+
for name in unset_environ:
|
| 139 |
+
env.pop(name, None)
|
| 140 |
+
try:
|
| 141 |
+
proc = subprocess.Popen(
|
| 142 |
+
# Convert HiddenText objects to the underlying str.
|
| 143 |
+
reveal_command_args(cmd),
|
| 144 |
+
stdin=subprocess.PIPE,
|
| 145 |
+
stdout=subprocess.PIPE,
|
| 146 |
+
stderr=subprocess.STDOUT if not stdout_only else subprocess.PIPE,
|
| 147 |
+
cwd=cwd,
|
| 148 |
+
env=env,
|
| 149 |
+
errors="backslashreplace",
|
| 150 |
+
)
|
| 151 |
+
except Exception as exc:
|
| 152 |
+
if log_failed_cmd:
|
| 153 |
+
subprocess_logger.critical(
|
| 154 |
+
"Error %s while executing command %s",
|
| 155 |
+
exc,
|
| 156 |
+
command_desc,
|
| 157 |
+
)
|
| 158 |
+
raise
|
| 159 |
+
all_output = []
|
| 160 |
+
if not stdout_only:
|
| 161 |
+
assert proc.stdout
|
| 162 |
+
assert proc.stdin
|
| 163 |
+
proc.stdin.close()
|
| 164 |
+
# In this mode, stdout and stderr are in the same pipe.
|
| 165 |
+
while True:
|
| 166 |
+
line: str = proc.stdout.readline()
|
| 167 |
+
if not line:
|
| 168 |
+
break
|
| 169 |
+
line = line.rstrip()
|
| 170 |
+
all_output.append(line + "\n")
|
| 171 |
+
|
| 172 |
+
# Show the line immediately.
|
| 173 |
+
log_subprocess(line)
|
| 174 |
+
# Update the spinner.
|
| 175 |
+
if use_spinner:
|
| 176 |
+
assert spinner
|
| 177 |
+
spinner.spin()
|
| 178 |
+
try:
|
| 179 |
+
proc.wait()
|
| 180 |
+
finally:
|
| 181 |
+
if proc.stdout:
|
| 182 |
+
proc.stdout.close()
|
| 183 |
+
output = "".join(all_output)
|
| 184 |
+
else:
|
| 185 |
+
# In this mode, stdout and stderr are in different pipes.
|
| 186 |
+
# We must use communicate() which is the only safe way to read both.
|
| 187 |
+
out, err = proc.communicate()
|
| 188 |
+
# log line by line to preserve pip log indenting
|
| 189 |
+
for out_line in out.splitlines():
|
| 190 |
+
log_subprocess(out_line)
|
| 191 |
+
all_output.append(out)
|
| 192 |
+
for err_line in err.splitlines():
|
| 193 |
+
log_subprocess(err_line)
|
| 194 |
+
all_output.append(err)
|
| 195 |
+
output = out
|
| 196 |
+
|
| 197 |
+
proc_had_error = proc.returncode and proc.returncode not in extra_ok_returncodes
|
| 198 |
+
if use_spinner:
|
| 199 |
+
assert spinner
|
| 200 |
+
if proc_had_error:
|
| 201 |
+
spinner.finish("error")
|
| 202 |
+
else:
|
| 203 |
+
spinner.finish("done")
|
| 204 |
+
if proc_had_error:
|
| 205 |
+
if on_returncode == "raise":
|
| 206 |
+
error = InstallationSubprocessError(
|
| 207 |
+
command_description=command_desc,
|
| 208 |
+
exit_code=proc.returncode,
|
| 209 |
+
output_lines=all_output if not showing_subprocess else None,
|
| 210 |
+
)
|
| 211 |
+
if log_failed_cmd:
|
| 212 |
+
subprocess_logger.error("[present-diagnostic] %s", error)
|
| 213 |
+
subprocess_logger.verbose(
|
| 214 |
+
"[bold magenta]full command[/]: [blue]%s[/]",
|
| 215 |
+
escape(format_command_args(cmd)),
|
| 216 |
+
extra={"markup": True},
|
| 217 |
+
)
|
| 218 |
+
subprocess_logger.verbose(
|
| 219 |
+
"[bold magenta]cwd[/]: %s",
|
| 220 |
+
escape(cwd or "[inherit]"),
|
| 221 |
+
extra={"markup": True},
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
raise error
|
| 225 |
+
elif on_returncode == "warn":
|
| 226 |
+
subprocess_logger.warning(
|
| 227 |
+
'Command "%s" had error code %s in %s',
|
| 228 |
+
command_desc,
|
| 229 |
+
proc.returncode,
|
| 230 |
+
cwd,
|
| 231 |
+
)
|
| 232 |
+
elif on_returncode == "ignore":
|
| 233 |
+
pass
|
| 234 |
+
else:
|
| 235 |
+
raise ValueError(f"Invalid value: on_returncode={on_returncode!r}")
|
| 236 |
+
return output
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
def runner_with_spinner_message(message: str) -> Callable[..., None]:
|
| 240 |
+
"""Provide a subprocess_runner that shows a spinner message.
|
| 241 |
+
|
| 242 |
+
Intended for use with for pep517's Pep517HookCaller. Thus, the runner has
|
| 243 |
+
an API that matches what's expected by Pep517HookCaller.subprocess_runner.
|
| 244 |
+
"""
|
| 245 |
+
|
| 246 |
+
def runner(
|
| 247 |
+
cmd: List[str],
|
| 248 |
+
cwd: Optional[str] = None,
|
| 249 |
+
extra_environ: Optional[Mapping[str, Any]] = None,
|
| 250 |
+
) -> None:
|
| 251 |
+
with open_spinner(message) as spinner:
|
| 252 |
+
call_subprocess(
|
| 253 |
+
cmd,
|
| 254 |
+
command_desc=message,
|
| 255 |
+
cwd=cwd,
|
| 256 |
+
extra_environ=extra_environ,
|
| 257 |
+
spinner=spinner,
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
return runner
|
venv/lib/python3.10/site-packages/pip/_internal/utils/temp_dir.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import errno
|
| 2 |
+
import itertools
|
| 3 |
+
import logging
|
| 4 |
+
import os.path
|
| 5 |
+
import tempfile
|
| 6 |
+
from contextlib import ExitStack, contextmanager
|
| 7 |
+
from typing import Any, Dict, Iterator, Optional, TypeVar, Union
|
| 8 |
+
|
| 9 |
+
from pip._internal.utils.misc import enum, rmtree
|
| 10 |
+
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
_T = TypeVar("_T", bound="TempDirectory")
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# Kinds of temporary directories. Only needed for ones that are
|
| 17 |
+
# globally-managed.
|
| 18 |
+
tempdir_kinds = enum(
|
| 19 |
+
BUILD_ENV="build-env",
|
| 20 |
+
EPHEM_WHEEL_CACHE="ephem-wheel-cache",
|
| 21 |
+
REQ_BUILD="req-build",
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
_tempdir_manager: Optional[ExitStack] = None
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@contextmanager
|
| 29 |
+
def global_tempdir_manager() -> Iterator[None]:
|
| 30 |
+
global _tempdir_manager
|
| 31 |
+
with ExitStack() as stack:
|
| 32 |
+
old_tempdir_manager, _tempdir_manager = _tempdir_manager, stack
|
| 33 |
+
try:
|
| 34 |
+
yield
|
| 35 |
+
finally:
|
| 36 |
+
_tempdir_manager = old_tempdir_manager
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class TempDirectoryTypeRegistry:
|
| 40 |
+
"""Manages temp directory behavior"""
|
| 41 |
+
|
| 42 |
+
def __init__(self) -> None:
|
| 43 |
+
self._should_delete: Dict[str, bool] = {}
|
| 44 |
+
|
| 45 |
+
def set_delete(self, kind: str, value: bool) -> None:
|
| 46 |
+
"""Indicate whether a TempDirectory of the given kind should be
|
| 47 |
+
auto-deleted.
|
| 48 |
+
"""
|
| 49 |
+
self._should_delete[kind] = value
|
| 50 |
+
|
| 51 |
+
def get_delete(self, kind: str) -> bool:
|
| 52 |
+
"""Get configured auto-delete flag for a given TempDirectory type,
|
| 53 |
+
default True.
|
| 54 |
+
"""
|
| 55 |
+
return self._should_delete.get(kind, True)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
_tempdir_registry: Optional[TempDirectoryTypeRegistry] = None
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@contextmanager
|
| 62 |
+
def tempdir_registry() -> Iterator[TempDirectoryTypeRegistry]:
|
| 63 |
+
"""Provides a scoped global tempdir registry that can be used to dictate
|
| 64 |
+
whether directories should be deleted.
|
| 65 |
+
"""
|
| 66 |
+
global _tempdir_registry
|
| 67 |
+
old_tempdir_registry = _tempdir_registry
|
| 68 |
+
_tempdir_registry = TempDirectoryTypeRegistry()
|
| 69 |
+
try:
|
| 70 |
+
yield _tempdir_registry
|
| 71 |
+
finally:
|
| 72 |
+
_tempdir_registry = old_tempdir_registry
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class _Default:
|
| 76 |
+
pass
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
_default = _Default()
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class TempDirectory:
|
| 83 |
+
"""Helper class that owns and cleans up a temporary directory.
|
| 84 |
+
|
| 85 |
+
This class can be used as a context manager or as an OO representation of a
|
| 86 |
+
temporary directory.
|
| 87 |
+
|
| 88 |
+
Attributes:
|
| 89 |
+
path
|
| 90 |
+
Location to the created temporary directory
|
| 91 |
+
delete
|
| 92 |
+
Whether the directory should be deleted when exiting
|
| 93 |
+
(when used as a contextmanager)
|
| 94 |
+
|
| 95 |
+
Methods:
|
| 96 |
+
cleanup()
|
| 97 |
+
Deletes the temporary directory
|
| 98 |
+
|
| 99 |
+
When used as a context manager, if the delete attribute is True, on
|
| 100 |
+
exiting the context the temporary directory is deleted.
|
| 101 |
+
"""
|
| 102 |
+
|
| 103 |
+
def __init__(
|
| 104 |
+
self,
|
| 105 |
+
path: Optional[str] = None,
|
| 106 |
+
delete: Union[bool, None, _Default] = _default,
|
| 107 |
+
kind: str = "temp",
|
| 108 |
+
globally_managed: bool = False,
|
| 109 |
+
):
|
| 110 |
+
super().__init__()
|
| 111 |
+
|
| 112 |
+
if delete is _default:
|
| 113 |
+
if path is not None:
|
| 114 |
+
# If we were given an explicit directory, resolve delete option
|
| 115 |
+
# now.
|
| 116 |
+
delete = False
|
| 117 |
+
else:
|
| 118 |
+
# Otherwise, we wait until cleanup and see what
|
| 119 |
+
# tempdir_registry says.
|
| 120 |
+
delete = None
|
| 121 |
+
|
| 122 |
+
# The only time we specify path is in for editables where it
|
| 123 |
+
# is the value of the --src option.
|
| 124 |
+
if path is None:
|
| 125 |
+
path = self._create(kind)
|
| 126 |
+
|
| 127 |
+
self._path = path
|
| 128 |
+
self._deleted = False
|
| 129 |
+
self.delete = delete
|
| 130 |
+
self.kind = kind
|
| 131 |
+
|
| 132 |
+
if globally_managed:
|
| 133 |
+
assert _tempdir_manager is not None
|
| 134 |
+
_tempdir_manager.enter_context(self)
|
| 135 |
+
|
| 136 |
+
@property
|
| 137 |
+
def path(self) -> str:
|
| 138 |
+
assert not self._deleted, f"Attempted to access deleted path: {self._path}"
|
| 139 |
+
return self._path
|
| 140 |
+
|
| 141 |
+
def __repr__(self) -> str:
|
| 142 |
+
return f"<{self.__class__.__name__} {self.path!r}>"
|
| 143 |
+
|
| 144 |
+
def __enter__(self: _T) -> _T:
|
| 145 |
+
return self
|
| 146 |
+
|
| 147 |
+
def __exit__(self, exc: Any, value: Any, tb: Any) -> None:
|
| 148 |
+
if self.delete is not None:
|
| 149 |
+
delete = self.delete
|
| 150 |
+
elif _tempdir_registry:
|
| 151 |
+
delete = _tempdir_registry.get_delete(self.kind)
|
| 152 |
+
else:
|
| 153 |
+
delete = True
|
| 154 |
+
|
| 155 |
+
if delete:
|
| 156 |
+
self.cleanup()
|
| 157 |
+
|
| 158 |
+
def _create(self, kind: str) -> str:
|
| 159 |
+
"""Create a temporary directory and store its path in self.path"""
|
| 160 |
+
# We realpath here because some systems have their default tmpdir
|
| 161 |
+
# symlinked to another directory. This tends to confuse build
|
| 162 |
+
# scripts, so we canonicalize the path by traversing potential
|
| 163 |
+
# symlinks here.
|
| 164 |
+
path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-"))
|
| 165 |
+
logger.debug("Created temporary directory: %s", path)
|
| 166 |
+
return path
|
| 167 |
+
|
| 168 |
+
def cleanup(self) -> None:
|
| 169 |
+
"""Remove the temporary directory created and reset state"""
|
| 170 |
+
self._deleted = True
|
| 171 |
+
if not os.path.exists(self._path):
|
| 172 |
+
return
|
| 173 |
+
rmtree(self._path)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
class AdjacentTempDirectory(TempDirectory):
|
| 177 |
+
"""Helper class that creates a temporary directory adjacent to a real one.
|
| 178 |
+
|
| 179 |
+
Attributes:
|
| 180 |
+
original
|
| 181 |
+
The original directory to create a temp directory for.
|
| 182 |
+
path
|
| 183 |
+
After calling create() or entering, contains the full
|
| 184 |
+
path to the temporary directory.
|
| 185 |
+
delete
|
| 186 |
+
Whether the directory should be deleted when exiting
|
| 187 |
+
(when used as a contextmanager)
|
| 188 |
+
|
| 189 |
+
"""
|
| 190 |
+
|
| 191 |
+
# The characters that may be used to name the temp directory
|
| 192 |
+
# We always prepend a ~ and then rotate through these until
|
| 193 |
+
# a usable name is found.
|
| 194 |
+
# pkg_resources raises a different error for .dist-info folder
|
| 195 |
+
# with leading '-' and invalid metadata
|
| 196 |
+
LEADING_CHARS = "-~.=%0123456789"
|
| 197 |
+
|
| 198 |
+
def __init__(self, original: str, delete: Optional[bool] = None) -> None:
|
| 199 |
+
self.original = original.rstrip("/\\")
|
| 200 |
+
super().__init__(delete=delete)
|
| 201 |
+
|
| 202 |
+
@classmethod
|
| 203 |
+
def _generate_names(cls, name: str) -> Iterator[str]:
|
| 204 |
+
"""Generates a series of temporary names.
|
| 205 |
+
|
| 206 |
+
The algorithm replaces the leading characters in the name
|
| 207 |
+
with ones that are valid filesystem characters, but are not
|
| 208 |
+
valid package names (for both Python and pip definitions of
|
| 209 |
+
package).
|
| 210 |
+
"""
|
| 211 |
+
for i in range(1, len(name)):
|
| 212 |
+
for candidate in itertools.combinations_with_replacement(
|
| 213 |
+
cls.LEADING_CHARS, i - 1
|
| 214 |
+
):
|
| 215 |
+
new_name = "~" + "".join(candidate) + name[i:]
|
| 216 |
+
if new_name != name:
|
| 217 |
+
yield new_name
|
| 218 |
+
|
| 219 |
+
# If we make it this far, we will have to make a longer name
|
| 220 |
+
for i in range(len(cls.LEADING_CHARS)):
|
| 221 |
+
for candidate in itertools.combinations_with_replacement(
|
| 222 |
+
cls.LEADING_CHARS, i
|
| 223 |
+
):
|
| 224 |
+
new_name = "~" + "".join(candidate) + name
|
| 225 |
+
if new_name != name:
|
| 226 |
+
yield new_name
|
| 227 |
+
|
| 228 |
+
def _create(self, kind: str) -> str:
|
| 229 |
+
root, name = os.path.split(self.original)
|
| 230 |
+
for candidate in self._generate_names(name):
|
| 231 |
+
path = os.path.join(root, candidate)
|
| 232 |
+
try:
|
| 233 |
+
os.mkdir(path)
|
| 234 |
+
except OSError as ex:
|
| 235 |
+
# Continue if the name exists already
|
| 236 |
+
if ex.errno != errno.EEXIST:
|
| 237 |
+
raise
|
| 238 |
+
else:
|
| 239 |
+
path = os.path.realpath(path)
|
| 240 |
+
break
|
| 241 |
+
else:
|
| 242 |
+
# Final fallback on the default behavior.
|
| 243 |
+
path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-"))
|
| 244 |
+
|
| 245 |
+
logger.debug("Created temporary directory: %s", path)
|
| 246 |
+
return path
|
venv/lib/python3.10/site-packages/pip/_internal/utils/unpacking.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Utilities related archives.
|
| 2 |
+
"""
|
| 3 |
+
|
| 4 |
+
import logging
|
| 5 |
+
import os
|
| 6 |
+
import shutil
|
| 7 |
+
import stat
|
| 8 |
+
import tarfile
|
| 9 |
+
import zipfile
|
| 10 |
+
from typing import Iterable, List, Optional
|
| 11 |
+
from zipfile import ZipInfo
|
| 12 |
+
|
| 13 |
+
from pip._internal.exceptions import InstallationError
|
| 14 |
+
from pip._internal.utils.filetypes import (
|
| 15 |
+
BZ2_EXTENSIONS,
|
| 16 |
+
TAR_EXTENSIONS,
|
| 17 |
+
XZ_EXTENSIONS,
|
| 18 |
+
ZIP_EXTENSIONS,
|
| 19 |
+
)
|
| 20 |
+
from pip._internal.utils.misc import ensure_dir
|
| 21 |
+
|
| 22 |
+
logger = logging.getLogger(__name__)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
import bz2 # noqa
|
| 29 |
+
|
| 30 |
+
SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS
|
| 31 |
+
except ImportError:
|
| 32 |
+
logger.debug("bz2 module is not available")
|
| 33 |
+
|
| 34 |
+
try:
|
| 35 |
+
# Only for Python 3.3+
|
| 36 |
+
import lzma # noqa
|
| 37 |
+
|
| 38 |
+
SUPPORTED_EXTENSIONS += XZ_EXTENSIONS
|
| 39 |
+
except ImportError:
|
| 40 |
+
logger.debug("lzma module is not available")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def current_umask() -> int:
|
| 44 |
+
"""Get the current umask which involves having to set it temporarily."""
|
| 45 |
+
mask = os.umask(0)
|
| 46 |
+
os.umask(mask)
|
| 47 |
+
return mask
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def split_leading_dir(path: str) -> List[str]:
|
| 51 |
+
path = path.lstrip("/").lstrip("\\")
|
| 52 |
+
if "/" in path and (
|
| 53 |
+
("\\" in path and path.find("/") < path.find("\\")) or "\\" not in path
|
| 54 |
+
):
|
| 55 |
+
return path.split("/", 1)
|
| 56 |
+
elif "\\" in path:
|
| 57 |
+
return path.split("\\", 1)
|
| 58 |
+
else:
|
| 59 |
+
return [path, ""]
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def has_leading_dir(paths: Iterable[str]) -> bool:
|
| 63 |
+
"""Returns true if all the paths have the same leading path name
|
| 64 |
+
(i.e., everything is in one subdirectory in an archive)"""
|
| 65 |
+
common_prefix = None
|
| 66 |
+
for path in paths:
|
| 67 |
+
prefix, rest = split_leading_dir(path)
|
| 68 |
+
if not prefix:
|
| 69 |
+
return False
|
| 70 |
+
elif common_prefix is None:
|
| 71 |
+
common_prefix = prefix
|
| 72 |
+
elif prefix != common_prefix:
|
| 73 |
+
return False
|
| 74 |
+
return True
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def is_within_directory(directory: str, target: str) -> bool:
|
| 78 |
+
"""
|
| 79 |
+
Return true if the absolute path of target is within the directory
|
| 80 |
+
"""
|
| 81 |
+
abs_directory = os.path.abspath(directory)
|
| 82 |
+
abs_target = os.path.abspath(target)
|
| 83 |
+
|
| 84 |
+
prefix = os.path.commonprefix([abs_directory, abs_target])
|
| 85 |
+
return prefix == abs_directory
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def set_extracted_file_to_default_mode_plus_executable(path: str) -> None:
|
| 89 |
+
"""
|
| 90 |
+
Make file present at path have execute for user/group/world
|
| 91 |
+
(chmod +x) is no-op on windows per python docs
|
| 92 |
+
"""
|
| 93 |
+
os.chmod(path, (0o777 & ~current_umask() | 0o111))
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def zip_item_is_executable(info: ZipInfo) -> bool:
|
| 97 |
+
mode = info.external_attr >> 16
|
| 98 |
+
# if mode and regular file and any execute permissions for
|
| 99 |
+
# user/group/world?
|
| 100 |
+
return bool(mode and stat.S_ISREG(mode) and mode & 0o111)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def unzip_file(filename: str, location: str, flatten: bool = True) -> None:
|
| 104 |
+
"""
|
| 105 |
+
Unzip the file (with path `filename`) to the destination `location`. All
|
| 106 |
+
files are written based on system defaults and umask (i.e. permissions are
|
| 107 |
+
not preserved), except that regular file members with any execute
|
| 108 |
+
permissions (user, group, or world) have "chmod +x" applied after being
|
| 109 |
+
written. Note that for windows, any execute changes using os.chmod are
|
| 110 |
+
no-ops per the python docs.
|
| 111 |
+
"""
|
| 112 |
+
ensure_dir(location)
|
| 113 |
+
zipfp = open(filename, "rb")
|
| 114 |
+
try:
|
| 115 |
+
zip = zipfile.ZipFile(zipfp, allowZip64=True)
|
| 116 |
+
leading = has_leading_dir(zip.namelist()) and flatten
|
| 117 |
+
for info in zip.infolist():
|
| 118 |
+
name = info.filename
|
| 119 |
+
fn = name
|
| 120 |
+
if leading:
|
| 121 |
+
fn = split_leading_dir(name)[1]
|
| 122 |
+
fn = os.path.join(location, fn)
|
| 123 |
+
dir = os.path.dirname(fn)
|
| 124 |
+
if not is_within_directory(location, fn):
|
| 125 |
+
message = (
|
| 126 |
+
"The zip file ({}) has a file ({}) trying to install "
|
| 127 |
+
"outside target directory ({})"
|
| 128 |
+
)
|
| 129 |
+
raise InstallationError(message.format(filename, fn, location))
|
| 130 |
+
if fn.endswith("/") or fn.endswith("\\"):
|
| 131 |
+
# A directory
|
| 132 |
+
ensure_dir(fn)
|
| 133 |
+
else:
|
| 134 |
+
ensure_dir(dir)
|
| 135 |
+
# Don't use read() to avoid allocating an arbitrarily large
|
| 136 |
+
# chunk of memory for the file's content
|
| 137 |
+
fp = zip.open(name)
|
| 138 |
+
try:
|
| 139 |
+
with open(fn, "wb") as destfp:
|
| 140 |
+
shutil.copyfileobj(fp, destfp)
|
| 141 |
+
finally:
|
| 142 |
+
fp.close()
|
| 143 |
+
if zip_item_is_executable(info):
|
| 144 |
+
set_extracted_file_to_default_mode_plus_executable(fn)
|
| 145 |
+
finally:
|
| 146 |
+
zipfp.close()
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def untar_file(filename: str, location: str) -> None:
|
| 150 |
+
"""
|
| 151 |
+
Untar the file (with path `filename`) to the destination `location`.
|
| 152 |
+
All files are written based on system defaults and umask (i.e. permissions
|
| 153 |
+
are not preserved), except that regular file members with any execute
|
| 154 |
+
permissions (user, group, or world) have "chmod +x" applied after being
|
| 155 |
+
written. Note that for windows, any execute changes using os.chmod are
|
| 156 |
+
no-ops per the python docs.
|
| 157 |
+
"""
|
| 158 |
+
ensure_dir(location)
|
| 159 |
+
if filename.lower().endswith(".gz") or filename.lower().endswith(".tgz"):
|
| 160 |
+
mode = "r:gz"
|
| 161 |
+
elif filename.lower().endswith(BZ2_EXTENSIONS):
|
| 162 |
+
mode = "r:bz2"
|
| 163 |
+
elif filename.lower().endswith(XZ_EXTENSIONS):
|
| 164 |
+
mode = "r:xz"
|
| 165 |
+
elif filename.lower().endswith(".tar"):
|
| 166 |
+
mode = "r"
|
| 167 |
+
else:
|
| 168 |
+
logger.warning(
|
| 169 |
+
"Cannot determine compression type for file %s",
|
| 170 |
+
filename,
|
| 171 |
+
)
|
| 172 |
+
mode = "r:*"
|
| 173 |
+
tar = tarfile.open(filename, mode, encoding="utf-8")
|
| 174 |
+
try:
|
| 175 |
+
leading = has_leading_dir([member.name for member in tar.getmembers()])
|
| 176 |
+
for member in tar.getmembers():
|
| 177 |
+
fn = member.name
|
| 178 |
+
if leading:
|
| 179 |
+
fn = split_leading_dir(fn)[1]
|
| 180 |
+
path = os.path.join(location, fn)
|
| 181 |
+
if not is_within_directory(location, path):
|
| 182 |
+
message = (
|
| 183 |
+
"The tar file ({}) has a file ({}) trying to install "
|
| 184 |
+
"outside target directory ({})"
|
| 185 |
+
)
|
| 186 |
+
raise InstallationError(message.format(filename, path, location))
|
| 187 |
+
if member.isdir():
|
| 188 |
+
ensure_dir(path)
|
| 189 |
+
elif member.issym():
|
| 190 |
+
try:
|
| 191 |
+
# https://github.com/python/typeshed/issues/2673
|
| 192 |
+
tar._extract_member(member, path) # type: ignore
|
| 193 |
+
except Exception as exc:
|
| 194 |
+
# Some corrupt tar files seem to produce this
|
| 195 |
+
# (specifically bad symlinks)
|
| 196 |
+
logger.warning(
|
| 197 |
+
"In the tar file %s the member %s is invalid: %s",
|
| 198 |
+
filename,
|
| 199 |
+
member.name,
|
| 200 |
+
exc,
|
| 201 |
+
)
|
| 202 |
+
continue
|
| 203 |
+
else:
|
| 204 |
+
try:
|
| 205 |
+
fp = tar.extractfile(member)
|
| 206 |
+
except (KeyError, AttributeError) as exc:
|
| 207 |
+
# Some corrupt tar files seem to produce this
|
| 208 |
+
# (specifically bad symlinks)
|
| 209 |
+
logger.warning(
|
| 210 |
+
"In the tar file %s the member %s is invalid: %s",
|
| 211 |
+
filename,
|
| 212 |
+
member.name,
|
| 213 |
+
exc,
|
| 214 |
+
)
|
| 215 |
+
continue
|
| 216 |
+
ensure_dir(os.path.dirname(path))
|
| 217 |
+
assert fp is not None
|
| 218 |
+
with open(path, "wb") as destfp:
|
| 219 |
+
shutil.copyfileobj(fp, destfp)
|
| 220 |
+
fp.close()
|
| 221 |
+
# Update the timestamp (useful for cython compiled files)
|
| 222 |
+
tar.utime(member, path)
|
| 223 |
+
# member have any execute permissions for user/group/world?
|
| 224 |
+
if member.mode & 0o111:
|
| 225 |
+
set_extracted_file_to_default_mode_plus_executable(path)
|
| 226 |
+
finally:
|
| 227 |
+
tar.close()
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def unpack_file(
|
| 231 |
+
filename: str,
|
| 232 |
+
location: str,
|
| 233 |
+
content_type: Optional[str] = None,
|
| 234 |
+
) -> None:
|
| 235 |
+
filename = os.path.realpath(filename)
|
| 236 |
+
if (
|
| 237 |
+
content_type == "application/zip"
|
| 238 |
+
or filename.lower().endswith(ZIP_EXTENSIONS)
|
| 239 |
+
or zipfile.is_zipfile(filename)
|
| 240 |
+
):
|
| 241 |
+
unzip_file(filename, location, flatten=not filename.endswith(".whl"))
|
| 242 |
+
elif (
|
| 243 |
+
content_type == "application/x-gzip"
|
| 244 |
+
or tarfile.is_tarfile(filename)
|
| 245 |
+
or filename.lower().endswith(TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS)
|
| 246 |
+
):
|
| 247 |
+
untar_file(filename, location)
|
| 248 |
+
else:
|
| 249 |
+
# FIXME: handle?
|
| 250 |
+
# FIXME: magic signatures?
|
| 251 |
+
logger.critical(
|
| 252 |
+
"Cannot unpack file %s (downloaded from %s, content-type: %s); "
|
| 253 |
+
"cannot detect archive format",
|
| 254 |
+
filename,
|
| 255 |
+
location,
|
| 256 |
+
content_type,
|
| 257 |
+
)
|
| 258 |
+
raise InstallationError(f"Cannot determine archive format of {location}")
|
venv/lib/python3.10/site-packages/pip/_internal/utils/urls.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import string
|
| 3 |
+
import urllib.parse
|
| 4 |
+
import urllib.request
|
| 5 |
+
from typing import Optional
|
| 6 |
+
|
| 7 |
+
from .compat import WINDOWS
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def get_url_scheme(url: str) -> Optional[str]:
|
| 11 |
+
if ":" not in url:
|
| 12 |
+
return None
|
| 13 |
+
return url.split(":", 1)[0].lower()
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def path_to_url(path: str) -> str:
|
| 17 |
+
"""
|
| 18 |
+
Convert a path to a file: URL. The path will be made absolute and have
|
| 19 |
+
quoted path parts.
|
| 20 |
+
"""
|
| 21 |
+
path = os.path.normpath(os.path.abspath(path))
|
| 22 |
+
url = urllib.parse.urljoin("file:", urllib.request.pathname2url(path))
|
| 23 |
+
return url
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def url_to_path(url: str) -> str:
|
| 27 |
+
"""
|
| 28 |
+
Convert a file: URL to a path.
|
| 29 |
+
"""
|
| 30 |
+
assert url.startswith(
|
| 31 |
+
"file:"
|
| 32 |
+
), f"You can only turn file: urls into filenames (not {url!r})"
|
| 33 |
+
|
| 34 |
+
_, netloc, path, _, _ = urllib.parse.urlsplit(url)
|
| 35 |
+
|
| 36 |
+
if not netloc or netloc == "localhost":
|
| 37 |
+
# According to RFC 8089, same as empty authority.
|
| 38 |
+
netloc = ""
|
| 39 |
+
elif WINDOWS:
|
| 40 |
+
# If we have a UNC path, prepend UNC share notation.
|
| 41 |
+
netloc = "\\\\" + netloc
|
| 42 |
+
else:
|
| 43 |
+
raise ValueError(
|
| 44 |
+
f"non-local file URIs are not supported on this platform: {url!r}"
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
path = urllib.request.url2pathname(netloc + path)
|
| 48 |
+
|
| 49 |
+
# On Windows, urlsplit parses the path as something like "/C:/Users/foo".
|
| 50 |
+
# This creates issues for path-related functions like io.open(), so we try
|
| 51 |
+
# to detect and strip the leading slash.
|
| 52 |
+
if (
|
| 53 |
+
WINDOWS
|
| 54 |
+
and not netloc # Not UNC.
|
| 55 |
+
and len(path) >= 3
|
| 56 |
+
and path[0] == "/" # Leading slash to strip.
|
| 57 |
+
and path[1] in string.ascii_letters # Drive letter.
|
| 58 |
+
and path[2:4] in (":", ":/") # Colon + end of string, or colon + absolute path.
|
| 59 |
+
):
|
| 60 |
+
path = path[1:]
|
| 61 |
+
|
| 62 |
+
return path
|
venv/lib/python3.10/site-packages/pip/_internal/utils/virtualenv.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
import re
|
| 4 |
+
import site
|
| 5 |
+
import sys
|
| 6 |
+
from typing import List, Optional
|
| 7 |
+
|
| 8 |
+
logger = logging.getLogger(__name__)
|
| 9 |
+
_INCLUDE_SYSTEM_SITE_PACKAGES_REGEX = re.compile(
|
| 10 |
+
r"include-system-site-packages\s*=\s*(?P<value>true|false)"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _running_under_venv() -> bool:
|
| 15 |
+
"""Checks if sys.base_prefix and sys.prefix match.
|
| 16 |
+
|
| 17 |
+
This handles PEP 405 compliant virtual environments.
|
| 18 |
+
"""
|
| 19 |
+
return sys.prefix != getattr(sys, "base_prefix", sys.prefix)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _running_under_regular_virtualenv() -> bool:
|
| 23 |
+
"""Checks if sys.real_prefix is set.
|
| 24 |
+
|
| 25 |
+
This handles virtual environments created with pypa's virtualenv.
|
| 26 |
+
"""
|
| 27 |
+
# pypa/virtualenv case
|
| 28 |
+
return hasattr(sys, "real_prefix")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def running_under_virtualenv() -> bool:
|
| 32 |
+
"""Return True if we're running inside a virtualenv, False otherwise."""
|
| 33 |
+
return _running_under_venv() or _running_under_regular_virtualenv()
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _get_pyvenv_cfg_lines() -> Optional[List[str]]:
|
| 37 |
+
"""Reads {sys.prefix}/pyvenv.cfg and returns its contents as list of lines
|
| 38 |
+
|
| 39 |
+
Returns None, if it could not read/access the file.
|
| 40 |
+
"""
|
| 41 |
+
pyvenv_cfg_file = os.path.join(sys.prefix, "pyvenv.cfg")
|
| 42 |
+
try:
|
| 43 |
+
# Although PEP 405 does not specify, the built-in venv module always
|
| 44 |
+
# writes with UTF-8. (pypa/pip#8717)
|
| 45 |
+
with open(pyvenv_cfg_file, encoding="utf-8") as f:
|
| 46 |
+
return f.read().splitlines() # avoids trailing newlines
|
| 47 |
+
except OSError:
|
| 48 |
+
return None
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _no_global_under_venv() -> bool:
|
| 52 |
+
"""Check `{sys.prefix}/pyvenv.cfg` for system site-packages inclusion
|
| 53 |
+
|
| 54 |
+
PEP 405 specifies that when system site-packages are not supposed to be
|
| 55 |
+
visible from a virtual environment, `pyvenv.cfg` must contain the following
|
| 56 |
+
line:
|
| 57 |
+
|
| 58 |
+
include-system-site-packages = false
|
| 59 |
+
|
| 60 |
+
Additionally, log a warning if accessing the file fails.
|
| 61 |
+
"""
|
| 62 |
+
cfg_lines = _get_pyvenv_cfg_lines()
|
| 63 |
+
if cfg_lines is None:
|
| 64 |
+
# We're not in a "sane" venv, so assume there is no system
|
| 65 |
+
# site-packages access (since that's PEP 405's default state).
|
| 66 |
+
logger.warning(
|
| 67 |
+
"Could not access 'pyvenv.cfg' despite a virtual environment "
|
| 68 |
+
"being active. Assuming global site-packages is not accessible "
|
| 69 |
+
"in this environment."
|
| 70 |
+
)
|
| 71 |
+
return True
|
| 72 |
+
|
| 73 |
+
for line in cfg_lines:
|
| 74 |
+
match = _INCLUDE_SYSTEM_SITE_PACKAGES_REGEX.match(line)
|
| 75 |
+
if match is not None and match.group("value") == "false":
|
| 76 |
+
return True
|
| 77 |
+
return False
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def _no_global_under_regular_virtualenv() -> bool:
|
| 81 |
+
"""Check if "no-global-site-packages.txt" exists beside site.py
|
| 82 |
+
|
| 83 |
+
This mirrors logic in pypa/virtualenv for determining whether system
|
| 84 |
+
site-packages are visible in the virtual environment.
|
| 85 |
+
"""
|
| 86 |
+
site_mod_dir = os.path.dirname(os.path.abspath(site.__file__))
|
| 87 |
+
no_global_site_packages_file = os.path.join(
|
| 88 |
+
site_mod_dir,
|
| 89 |
+
"no-global-site-packages.txt",
|
| 90 |
+
)
|
| 91 |
+
return os.path.exists(no_global_site_packages_file)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def virtualenv_no_global() -> bool:
|
| 95 |
+
"""Returns a boolean, whether running in venv with no system site-packages."""
|
| 96 |
+
# PEP 405 compliance needs to be checked first since virtualenv >=20 would
|
| 97 |
+
# return True for both checks, but is only able to use the PEP 405 config.
|
| 98 |
+
if _running_under_venv():
|
| 99 |
+
return _no_global_under_venv()
|
| 100 |
+
|
| 101 |
+
if _running_under_regular_virtualenv():
|
| 102 |
+
return _no_global_under_regular_virtualenv()
|
| 103 |
+
|
| 104 |
+
return False
|
venv/lib/python3.10/site-packages/pip/_internal/utils/wheel.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Support functions for working with wheel files.
|
| 2 |
+
"""
|
| 3 |
+
|
| 4 |
+
import logging
|
| 5 |
+
from email.message import Message
|
| 6 |
+
from email.parser import Parser
|
| 7 |
+
from typing import Tuple
|
| 8 |
+
from zipfile import BadZipFile, ZipFile
|
| 9 |
+
|
| 10 |
+
from pip._vendor.packaging.utils import canonicalize_name
|
| 11 |
+
|
| 12 |
+
from pip._internal.exceptions import UnsupportedWheel
|
| 13 |
+
|
| 14 |
+
VERSION_COMPATIBLE = (1, 0)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
logger = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]:
|
| 21 |
+
"""Extract information from the provided wheel, ensuring it meets basic
|
| 22 |
+
standards.
|
| 23 |
+
|
| 24 |
+
Returns the name of the .dist-info directory and the parsed WHEEL metadata.
|
| 25 |
+
"""
|
| 26 |
+
try:
|
| 27 |
+
info_dir = wheel_dist_info_dir(wheel_zip, name)
|
| 28 |
+
metadata = wheel_metadata(wheel_zip, info_dir)
|
| 29 |
+
version = wheel_version(metadata)
|
| 30 |
+
except UnsupportedWheel as e:
|
| 31 |
+
raise UnsupportedWheel("{} has an invalid wheel, {}".format(name, str(e)))
|
| 32 |
+
|
| 33 |
+
check_compatibility(version, name)
|
| 34 |
+
|
| 35 |
+
return info_dir, metadata
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def wheel_dist_info_dir(source: ZipFile, name: str) -> str:
|
| 39 |
+
"""Returns the name of the contained .dist-info directory.
|
| 40 |
+
|
| 41 |
+
Raises AssertionError or UnsupportedWheel if not found, >1 found, or
|
| 42 |
+
it doesn't match the provided name.
|
| 43 |
+
"""
|
| 44 |
+
# Zip file path separators must be /
|
| 45 |
+
subdirs = {p.split("/", 1)[0] for p in source.namelist()}
|
| 46 |
+
|
| 47 |
+
info_dirs = [s for s in subdirs if s.endswith(".dist-info")]
|
| 48 |
+
|
| 49 |
+
if not info_dirs:
|
| 50 |
+
raise UnsupportedWheel(".dist-info directory not found")
|
| 51 |
+
|
| 52 |
+
if len(info_dirs) > 1:
|
| 53 |
+
raise UnsupportedWheel(
|
| 54 |
+
"multiple .dist-info directories found: {}".format(", ".join(info_dirs))
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
info_dir = info_dirs[0]
|
| 58 |
+
|
| 59 |
+
info_dir_name = canonicalize_name(info_dir)
|
| 60 |
+
canonical_name = canonicalize_name(name)
|
| 61 |
+
if not info_dir_name.startswith(canonical_name):
|
| 62 |
+
raise UnsupportedWheel(
|
| 63 |
+
".dist-info directory {!r} does not start with {!r}".format(
|
| 64 |
+
info_dir, canonical_name
|
| 65 |
+
)
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
return info_dir
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def read_wheel_metadata_file(source: ZipFile, path: str) -> bytes:
|
| 72 |
+
try:
|
| 73 |
+
return source.read(path)
|
| 74 |
+
# BadZipFile for general corruption, KeyError for missing entry,
|
| 75 |
+
# and RuntimeError for password-protected files
|
| 76 |
+
except (BadZipFile, KeyError, RuntimeError) as e:
|
| 77 |
+
raise UnsupportedWheel(f"could not read {path!r} file: {e!r}")
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message:
|
| 81 |
+
"""Return the WHEEL metadata of an extracted wheel, if possible.
|
| 82 |
+
Otherwise, raise UnsupportedWheel.
|
| 83 |
+
"""
|
| 84 |
+
path = f"{dist_info_dir}/WHEEL"
|
| 85 |
+
# Zip file path separators must be /
|
| 86 |
+
wheel_contents = read_wheel_metadata_file(source, path)
|
| 87 |
+
|
| 88 |
+
try:
|
| 89 |
+
wheel_text = wheel_contents.decode()
|
| 90 |
+
except UnicodeDecodeError as e:
|
| 91 |
+
raise UnsupportedWheel(f"error decoding {path!r}: {e!r}")
|
| 92 |
+
|
| 93 |
+
# FeedParser (used by Parser) does not raise any exceptions. The returned
|
| 94 |
+
# message may have .defects populated, but for backwards-compatibility we
|
| 95 |
+
# currently ignore them.
|
| 96 |
+
return Parser().parsestr(wheel_text)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def wheel_version(wheel_data: Message) -> Tuple[int, ...]:
|
| 100 |
+
"""Given WHEEL metadata, return the parsed Wheel-Version.
|
| 101 |
+
Otherwise, raise UnsupportedWheel.
|
| 102 |
+
"""
|
| 103 |
+
version_text = wheel_data["Wheel-Version"]
|
| 104 |
+
if version_text is None:
|
| 105 |
+
raise UnsupportedWheel("WHEEL is missing Wheel-Version")
|
| 106 |
+
|
| 107 |
+
version = version_text.strip()
|
| 108 |
+
|
| 109 |
+
try:
|
| 110 |
+
return tuple(map(int, version.split(".")))
|
| 111 |
+
except ValueError:
|
| 112 |
+
raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}")
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def check_compatibility(version: Tuple[int, ...], name: str) -> None:
|
| 116 |
+
"""Raises errors or warns if called with an incompatible Wheel-Version.
|
| 117 |
+
|
| 118 |
+
pip should refuse to install a Wheel-Version that's a major series
|
| 119 |
+
ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when
|
| 120 |
+
installing a version only minor version ahead (e.g 1.2 > 1.1).
|
| 121 |
+
|
| 122 |
+
version: a 2-tuple representing a Wheel-Version (Major, Minor)
|
| 123 |
+
name: name of wheel or package to raise exception about
|
| 124 |
+
|
| 125 |
+
:raises UnsupportedWheel: when an incompatible Wheel-Version is given
|
| 126 |
+
"""
|
| 127 |
+
if version[0] > VERSION_COMPATIBLE[0]:
|
| 128 |
+
raise UnsupportedWheel(
|
| 129 |
+
"{}'s Wheel-Version ({}) is not compatible with this version "
|
| 130 |
+
"of pip".format(name, ".".join(map(str, version)))
|
| 131 |
+
)
|
| 132 |
+
elif version > VERSION_COMPATIBLE:
|
| 133 |
+
logger.warning(
|
| 134 |
+
"Installing from a newer Wheel-Version (%s)",
|
| 135 |
+
".".join(map(str, version)),
|
| 136 |
+
)
|
venv/lib/python3.10/site-packages/pip/_internal/vcs/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Expose a limited set of classes and functions so callers outside of
|
| 2 |
+
# the vcs package don't need to import deeper than `pip._internal.vcs`.
|
| 3 |
+
# (The test directory may still need to import from a vcs sub-package.)
|
| 4 |
+
# Import all vcs modules to register each VCS in the VcsSupport object.
|
| 5 |
+
import pip._internal.vcs.bazaar
|
| 6 |
+
import pip._internal.vcs.git
|
| 7 |
+
import pip._internal.vcs.mercurial
|
| 8 |
+
import pip._internal.vcs.subversion # noqa: F401
|
| 9 |
+
from pip._internal.vcs.versioncontrol import ( # noqa: F401
|
| 10 |
+
RemoteNotFoundError,
|
| 11 |
+
RemoteNotValidError,
|
| 12 |
+
is_url,
|
| 13 |
+
make_vcs_requirement_url,
|
| 14 |
+
vcs,
|
| 15 |
+
)
|
venv/lib/python3.10/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (613 Bytes). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-310.pyc
ADDED
|
Binary file (3.44 kB). View file
|
|
|
venv/lib/python3.10/site-packages/pip/_internal/vcs/__pycache__/git.cpython-310.pyc
ADDED
|
Binary file (12.6 kB). View file
|
|
|