koichi12 commited on
Commit
1adfd7e
·
verified ·
1 Parent(s): 6764f79

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .venv/lib/python3.11/site-packages/pip/_internal/metadata/__init__.py +128 -0
  2. .venv/lib/python3.11/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-311.pyc +0 -0
  3. .venv/lib/python3.11/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-311.pyc +0 -0
  4. .venv/lib/python3.11/site-packages/pip/_internal/metadata/__pycache__/base.cpython-311.pyc +0 -0
  5. .venv/lib/python3.11/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-311.pyc +0 -0
  6. .venv/lib/python3.11/site-packages/pip/_internal/metadata/_json.py +84 -0
  7. .venv/lib/python3.11/site-packages/pip/_internal/metadata/base.py +702 -0
  8. .venv/lib/python3.11/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-311.pyc +0 -0
  9. .venv/lib/python3.11/site-packages/pip/_internal/metadata/importlib/_compat.py +55 -0
  10. .venv/lib/python3.11/site-packages/pip/_internal/metadata/importlib/_dists.py +227 -0
  11. .venv/lib/python3.11/site-packages/pip/_internal/metadata/importlib/_envs.py +189 -0
  12. .venv/lib/python3.11/site-packages/pip/_internal/metadata/pkg_resources.py +278 -0
  13. .venv/lib/python3.11/site-packages/pip/_internal/network/__init__.py +2 -0
  14. .venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/__init__.cpython-311.pyc +0 -0
  15. .venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/auth.cpython-311.pyc +0 -0
  16. .venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/cache.cpython-311.pyc +0 -0
  17. .venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/download.cpython-311.pyc +0 -0
  18. .venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-311.pyc +0 -0
  19. .venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/session.cpython-311.pyc +0 -0
  20. .venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/utils.cpython-311.pyc +0 -0
  21. .venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-311.pyc +0 -0
  22. .venv/lib/python3.11/site-packages/pip/_internal/network/auth.py +561 -0
  23. .venv/lib/python3.11/site-packages/pip/_internal/network/cache.py +106 -0
  24. .venv/lib/python3.11/site-packages/pip/_internal/network/download.py +186 -0
  25. .venv/lib/python3.11/site-packages/pip/_internal/network/lazy_wheel.py +210 -0
  26. .venv/lib/python3.11/site-packages/pip/_internal/network/session.py +520 -0
  27. .venv/lib/python3.11/site-packages/pip/_internal/network/utils.py +96 -0
  28. .venv/lib/python3.11/site-packages/pip/_internal/network/xmlrpc.py +62 -0
  29. .venv/lib/python3.11/site-packages/pip/_internal/utils/__init__.py +0 -0
  30. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-311.pyc +0 -0
  31. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/_jaraco_text.cpython-311.pyc +0 -0
  32. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/_log.cpython-311.pyc +0 -0
  33. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-311.pyc +0 -0
  34. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/compat.cpython-311.pyc +0 -0
  35. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-311.pyc +0 -0
  36. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-311.pyc +0 -0
  37. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-311.pyc +0 -0
  38. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-311.pyc +0 -0
  39. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-311.pyc +0 -0
  40. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/encoding.cpython-311.pyc +0 -0
  41. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-311.pyc +0 -0
  42. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-311.pyc +0 -0
  43. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-311.pyc +0 -0
  44. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-311.pyc +0 -0
  45. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-311.pyc +0 -0
  46. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/logging.cpython-311.pyc +0 -0
  47. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/misc.cpython-311.pyc +0 -0
  48. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/models.cpython-311.pyc +0 -0
  49. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-311.pyc +0 -0
  50. .venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-311.pyc +0 -0
.venv/lib/python3.11/site-packages/pip/_internal/metadata/__init__.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import contextlib
2
+ import functools
3
+ import os
4
+ import sys
5
+ from typing import TYPE_CHECKING, List, Optional, Type, cast
6
+
7
+ from pip._internal.utils.misc import strtobool
8
+
9
+ from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel
10
+
11
+ if TYPE_CHECKING:
12
+ from typing import Literal, Protocol
13
+ else:
14
+ Protocol = object
15
+
16
+ __all__ = [
17
+ "BaseDistribution",
18
+ "BaseEnvironment",
19
+ "FilesystemWheel",
20
+ "MemoryWheel",
21
+ "Wheel",
22
+ "get_default_environment",
23
+ "get_environment",
24
+ "get_wheel_distribution",
25
+ "select_backend",
26
+ ]
27
+
28
+
29
+ def _should_use_importlib_metadata() -> bool:
30
+ """Whether to use the ``importlib.metadata`` or ``pkg_resources`` backend.
31
+
32
+ By default, pip uses ``importlib.metadata`` on Python 3.11+, and
33
+ ``pkg_resourcess`` otherwise. This can be overridden by a couple of ways:
34
+
35
+ * If environment variable ``_PIP_USE_IMPORTLIB_METADATA`` is set, it
36
+ dictates whether ``importlib.metadata`` is used, regardless of Python
37
+ version.
38
+ * On Python 3.11+, Python distributors can patch ``importlib.metadata``
39
+ to add a global constant ``_PIP_USE_IMPORTLIB_METADATA = False``. This
40
+ makes pip use ``pkg_resources`` (unless the user set the aforementioned
41
+ environment variable to *True*).
42
+ """
43
+ with contextlib.suppress(KeyError, ValueError):
44
+ return bool(strtobool(os.environ["_PIP_USE_IMPORTLIB_METADATA"]))
45
+ if sys.version_info < (3, 11):
46
+ return False
47
+ import importlib.metadata
48
+
49
+ return bool(getattr(importlib.metadata, "_PIP_USE_IMPORTLIB_METADATA", True))
50
+
51
+
52
+ class Backend(Protocol):
53
+ NAME: 'Literal["importlib", "pkg_resources"]'
54
+ Distribution: Type[BaseDistribution]
55
+ Environment: Type[BaseEnvironment]
56
+
57
+
58
+ @functools.lru_cache(maxsize=None)
59
+ def select_backend() -> Backend:
60
+ if _should_use_importlib_metadata():
61
+ from . import importlib
62
+
63
+ return cast(Backend, importlib)
64
+ from . import pkg_resources
65
+
66
+ return cast(Backend, pkg_resources)
67
+
68
+
69
+ def get_default_environment() -> BaseEnvironment:
70
+ """Get the default representation for the current environment.
71
+
72
+ This returns an Environment instance from the chosen backend. The default
73
+ Environment instance should be built from ``sys.path`` and may use caching
74
+ to share instance state accorss calls.
75
+ """
76
+ return select_backend().Environment.default()
77
+
78
+
79
+ def get_environment(paths: Optional[List[str]]) -> BaseEnvironment:
80
+ """Get a representation of the environment specified by ``paths``.
81
+
82
+ This returns an Environment instance from the chosen backend based on the
83
+ given import paths. The backend must build a fresh instance representing
84
+ the state of installed distributions when this function is called.
85
+ """
86
+ return select_backend().Environment.from_paths(paths)
87
+
88
+
89
+ def get_directory_distribution(directory: str) -> BaseDistribution:
90
+ """Get the distribution metadata representation in the specified directory.
91
+
92
+ This returns a Distribution instance from the chosen backend based on
93
+ the given on-disk ``.dist-info`` directory.
94
+ """
95
+ return select_backend().Distribution.from_directory(directory)
96
+
97
+
98
+ def get_wheel_distribution(wheel: Wheel, canonical_name: str) -> BaseDistribution:
99
+ """Get the representation of the specified wheel's distribution metadata.
100
+
101
+ This returns a Distribution instance from the chosen backend based on
102
+ the given wheel's ``.dist-info`` directory.
103
+
104
+ :param canonical_name: Normalized project name of the given wheel.
105
+ """
106
+ return select_backend().Distribution.from_wheel(wheel, canonical_name)
107
+
108
+
109
+ def get_metadata_distribution(
110
+ metadata_contents: bytes,
111
+ filename: str,
112
+ canonical_name: str,
113
+ ) -> BaseDistribution:
114
+ """Get the dist representation of the specified METADATA file contents.
115
+
116
+ This returns a Distribution instance from the chosen backend sourced from the data
117
+ in `metadata_contents`.
118
+
119
+ :param metadata_contents: Contents of a METADATA file within a dist, or one served
120
+ via PEP 658.
121
+ :param filename: Filename for the dist this metadata represents.
122
+ :param canonical_name: Normalized project name of the given dist.
123
+ """
124
+ return select_backend().Distribution.from_metadata_file_contents(
125
+ metadata_contents,
126
+ filename,
127
+ canonical_name,
128
+ )
.venv/lib/python3.11/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (6.5 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-311.pyc ADDED
Binary file (3.56 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/metadata/__pycache__/base.cpython-311.pyc ADDED
Binary file (38.7 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-311.pyc ADDED
Binary file (17.5 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/metadata/_json.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Extracted from https://github.com/pfmoore/pkg_metadata
2
+
3
+ from email.header import Header, decode_header, make_header
4
+ from email.message import Message
5
+ from typing import Any, Dict, List, Union
6
+
7
+ METADATA_FIELDS = [
8
+ # Name, Multiple-Use
9
+ ("Metadata-Version", False),
10
+ ("Name", False),
11
+ ("Version", False),
12
+ ("Dynamic", True),
13
+ ("Platform", True),
14
+ ("Supported-Platform", True),
15
+ ("Summary", False),
16
+ ("Description", False),
17
+ ("Description-Content-Type", False),
18
+ ("Keywords", False),
19
+ ("Home-page", False),
20
+ ("Download-URL", False),
21
+ ("Author", False),
22
+ ("Author-email", False),
23
+ ("Maintainer", False),
24
+ ("Maintainer-email", False),
25
+ ("License", False),
26
+ ("Classifier", True),
27
+ ("Requires-Dist", True),
28
+ ("Requires-Python", False),
29
+ ("Requires-External", True),
30
+ ("Project-URL", True),
31
+ ("Provides-Extra", True),
32
+ ("Provides-Dist", True),
33
+ ("Obsoletes-Dist", True),
34
+ ]
35
+
36
+
37
+ def json_name(field: str) -> str:
38
+ return field.lower().replace("-", "_")
39
+
40
+
41
+ def msg_to_json(msg: Message) -> Dict[str, Any]:
42
+ """Convert a Message object into a JSON-compatible dictionary."""
43
+
44
+ def sanitise_header(h: Union[Header, str]) -> str:
45
+ if isinstance(h, Header):
46
+ chunks = []
47
+ for bytes, encoding in decode_header(h):
48
+ if encoding == "unknown-8bit":
49
+ try:
50
+ # See if UTF-8 works
51
+ bytes.decode("utf-8")
52
+ encoding = "utf-8"
53
+ except UnicodeDecodeError:
54
+ # If not, latin1 at least won't fail
55
+ encoding = "latin1"
56
+ chunks.append((bytes, encoding))
57
+ return str(make_header(chunks))
58
+ return str(h)
59
+
60
+ result = {}
61
+ for field, multi in METADATA_FIELDS:
62
+ if field not in msg:
63
+ continue
64
+ key = json_name(field)
65
+ if multi:
66
+ value: Union[str, List[str]] = [
67
+ sanitise_header(v) for v in msg.get_all(field) # type: ignore
68
+ ]
69
+ else:
70
+ value = sanitise_header(msg.get(field)) # type: ignore
71
+ if key == "keywords":
72
+ # Accept both comma-separated and space-separated
73
+ # forms, for better compatibility with old data.
74
+ if "," in value:
75
+ value = [v.strip() for v in value.split(",")]
76
+ else:
77
+ value = value.split()
78
+ result[key] = value
79
+
80
+ payload = msg.get_payload()
81
+ if payload:
82
+ result["description"] = payload
83
+
84
+ return result
.venv/lib/python3.11/site-packages/pip/_internal/metadata/base.py ADDED
@@ -0,0 +1,702 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import email.message
3
+ import functools
4
+ import json
5
+ import logging
6
+ import pathlib
7
+ import re
8
+ import zipfile
9
+ from typing import (
10
+ IO,
11
+ TYPE_CHECKING,
12
+ Any,
13
+ Collection,
14
+ Container,
15
+ Dict,
16
+ Iterable,
17
+ Iterator,
18
+ List,
19
+ NamedTuple,
20
+ Optional,
21
+ Tuple,
22
+ Union,
23
+ )
24
+
25
+ from pip._vendor.packaging.requirements import Requirement
26
+ from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
27
+ from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
28
+ from pip._vendor.packaging.version import LegacyVersion, Version
29
+
30
+ from pip._internal.exceptions import NoneMetadataError
31
+ from pip._internal.locations import site_packages, user_site
32
+ from pip._internal.models.direct_url import (
33
+ DIRECT_URL_METADATA_NAME,
34
+ DirectUrl,
35
+ DirectUrlValidationError,
36
+ )
37
+ from pip._internal.utils.compat import stdlib_pkgs # TODO: Move definition here.
38
+ from pip._internal.utils.egg_link import egg_link_path_from_sys_path
39
+ from pip._internal.utils.misc import is_local, normalize_path
40
+ from pip._internal.utils.urls import url_to_path
41
+
42
+ from ._json import msg_to_json
43
+
44
+ if TYPE_CHECKING:
45
+ from typing import Protocol
46
+ else:
47
+ Protocol = object
48
+
49
+ DistributionVersion = Union[LegacyVersion, Version]
50
+
51
+ InfoPath = Union[str, pathlib.PurePath]
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+
56
+ class BaseEntryPoint(Protocol):
57
+ @property
58
+ def name(self) -> str:
59
+ raise NotImplementedError()
60
+
61
+ @property
62
+ def value(self) -> str:
63
+ raise NotImplementedError()
64
+
65
+ @property
66
+ def group(self) -> str:
67
+ raise NotImplementedError()
68
+
69
+
70
+ def _convert_installed_files_path(
71
+ entry: Tuple[str, ...],
72
+ info: Tuple[str, ...],
73
+ ) -> str:
74
+ """Convert a legacy installed-files.txt path into modern RECORD path.
75
+
76
+ The legacy format stores paths relative to the info directory, while the
77
+ modern format stores paths relative to the package root, e.g. the
78
+ site-packages directory.
79
+
80
+ :param entry: Path parts of the installed-files.txt entry.
81
+ :param info: Path parts of the egg-info directory relative to package root.
82
+ :returns: The converted entry.
83
+
84
+ For best compatibility with symlinks, this does not use ``abspath()`` or
85
+ ``Path.resolve()``, but tries to work with path parts:
86
+
87
+ 1. While ``entry`` starts with ``..``, remove the equal amounts of parts
88
+ from ``info``; if ``info`` is empty, start appending ``..`` instead.
89
+ 2. Join the two directly.
90
+ """
91
+ while entry and entry[0] == "..":
92
+ if not info or info[-1] == "..":
93
+ info += ("..",)
94
+ else:
95
+ info = info[:-1]
96
+ entry = entry[1:]
97
+ return str(pathlib.Path(*info, *entry))
98
+
99
+
100
+ class RequiresEntry(NamedTuple):
101
+ requirement: str
102
+ extra: str
103
+ marker: str
104
+
105
+
106
+ class BaseDistribution(Protocol):
107
+ @classmethod
108
+ def from_directory(cls, directory: str) -> "BaseDistribution":
109
+ """Load the distribution from a metadata directory.
110
+
111
+ :param directory: Path to a metadata directory, e.g. ``.dist-info``.
112
+ """
113
+ raise NotImplementedError()
114
+
115
+ @classmethod
116
+ def from_metadata_file_contents(
117
+ cls,
118
+ metadata_contents: bytes,
119
+ filename: str,
120
+ project_name: str,
121
+ ) -> "BaseDistribution":
122
+ """Load the distribution from the contents of a METADATA file.
123
+
124
+ This is used to implement PEP 658 by generating a "shallow" dist object that can
125
+ be used for resolution without downloading or building the actual dist yet.
126
+
127
+ :param metadata_contents: The contents of a METADATA file.
128
+ :param filename: File name for the dist with this metadata.
129
+ :param project_name: Name of the project this dist represents.
130
+ """
131
+ raise NotImplementedError()
132
+
133
+ @classmethod
134
+ def from_wheel(cls, wheel: "Wheel", name: str) -> "BaseDistribution":
135
+ """Load the distribution from a given wheel.
136
+
137
+ :param wheel: A concrete wheel definition.
138
+ :param name: File name of the wheel.
139
+
140
+ :raises InvalidWheel: Whenever loading of the wheel causes a
141
+ :py:exc:`zipfile.BadZipFile` exception to be thrown.
142
+ :raises UnsupportedWheel: If the wheel is a valid zip, but malformed
143
+ internally.
144
+ """
145
+ raise NotImplementedError()
146
+
147
+ def __repr__(self) -> str:
148
+ return f"{self.raw_name} {self.version} ({self.location})"
149
+
150
+ def __str__(self) -> str:
151
+ return f"{self.raw_name} {self.version}"
152
+
153
+ @property
154
+ def location(self) -> Optional[str]:
155
+ """Where the distribution is loaded from.
156
+
157
+ A string value is not necessarily a filesystem path, since distributions
158
+ can be loaded from other sources, e.g. arbitrary zip archives. ``None``
159
+ means the distribution is created in-memory.
160
+
161
+ Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If
162
+ this is a symbolic link, we want to preserve the relative path between
163
+ it and files in the distribution.
164
+ """
165
+ raise NotImplementedError()
166
+
167
+ @property
168
+ def editable_project_location(self) -> Optional[str]:
169
+ """The project location for editable distributions.
170
+
171
+ This is the directory where pyproject.toml or setup.py is located.
172
+ None if the distribution is not installed in editable mode.
173
+ """
174
+ # TODO: this property is relatively costly to compute, memoize it ?
175
+ direct_url = self.direct_url
176
+ if direct_url:
177
+ if direct_url.is_local_editable():
178
+ return url_to_path(direct_url.url)
179
+ else:
180
+ # Search for an .egg-link file by walking sys.path, as it was
181
+ # done before by dist_is_editable().
182
+ egg_link_path = egg_link_path_from_sys_path(self.raw_name)
183
+ if egg_link_path:
184
+ # TODO: get project location from second line of egg_link file
185
+ # (https://github.com/pypa/pip/issues/10243)
186
+ return self.location
187
+ return None
188
+
189
+ @property
190
+ def installed_location(self) -> Optional[str]:
191
+ """The distribution's "installed" location.
192
+
193
+ This should generally be a ``site-packages`` directory. This is
194
+ usually ``dist.location``, except for legacy develop-installed packages,
195
+ where ``dist.location`` is the source code location, and this is where
196
+ the ``.egg-link`` file is.
197
+
198
+ The returned location is normalized (in particular, with symlinks removed).
199
+ """
200
+ raise NotImplementedError()
201
+
202
+ @property
203
+ def info_location(self) -> Optional[str]:
204
+ """Location of the .[egg|dist]-info directory or file.
205
+
206
+ Similarly to ``location``, a string value is not necessarily a
207
+ filesystem path. ``None`` means the distribution is created in-memory.
208
+
209
+ For a modern .dist-info installation on disk, this should be something
210
+ like ``{location}/{raw_name}-{version}.dist-info``.
211
+
212
+ Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If
213
+ this is a symbolic link, we want to preserve the relative path between
214
+ it and other files in the distribution.
215
+ """
216
+ raise NotImplementedError()
217
+
218
+ @property
219
+ def installed_by_distutils(self) -> bool:
220
+ """Whether this distribution is installed with legacy distutils format.
221
+
222
+ A distribution installed with "raw" distutils not patched by setuptools
223
+ uses one single file at ``info_location`` to store metadata. We need to
224
+ treat this specially on uninstallation.
225
+ """
226
+ info_location = self.info_location
227
+ if not info_location:
228
+ return False
229
+ return pathlib.Path(info_location).is_file()
230
+
231
+ @property
232
+ def installed_as_egg(self) -> bool:
233
+ """Whether this distribution is installed as an egg.
234
+
235
+ This usually indicates the distribution was installed by (older versions
236
+ of) easy_install.
237
+ """
238
+ location = self.location
239
+ if not location:
240
+ return False
241
+ return location.endswith(".egg")
242
+
243
+ @property
244
+ def installed_with_setuptools_egg_info(self) -> bool:
245
+ """Whether this distribution is installed with the ``.egg-info`` format.
246
+
247
+ This usually indicates the distribution was installed with setuptools
248
+ with an old pip version or with ``single-version-externally-managed``.
249
+
250
+ Note that this ensure the metadata store is a directory. distutils can
251
+ also installs an ``.egg-info``, but as a file, not a directory. This
252
+ property is *False* for that case. Also see ``installed_by_distutils``.
253
+ """
254
+ info_location = self.info_location
255
+ if not info_location:
256
+ return False
257
+ if not info_location.endswith(".egg-info"):
258
+ return False
259
+ return pathlib.Path(info_location).is_dir()
260
+
261
+ @property
262
+ def installed_with_dist_info(self) -> bool:
263
+ """Whether this distribution is installed with the "modern format".
264
+
265
+ This indicates a "modern" installation, e.g. storing metadata in the
266
+ ``.dist-info`` directory. This applies to installations made by
267
+ setuptools (but through pip, not directly), or anything using the
268
+ standardized build backend interface (PEP 517).
269
+ """
270
+ info_location = self.info_location
271
+ if not info_location:
272
+ return False
273
+ if not info_location.endswith(".dist-info"):
274
+ return False
275
+ return pathlib.Path(info_location).is_dir()
276
+
277
+ @property
278
+ def canonical_name(self) -> NormalizedName:
279
+ raise NotImplementedError()
280
+
281
+ @property
282
+ def version(self) -> DistributionVersion:
283
+ raise NotImplementedError()
284
+
285
+ @property
286
+ def setuptools_filename(self) -> str:
287
+ """Convert a project name to its setuptools-compatible filename.
288
+
289
+ This is a copy of ``pkg_resources.to_filename()`` for compatibility.
290
+ """
291
+ return self.raw_name.replace("-", "_")
292
+
293
+ @property
294
+ def direct_url(self) -> Optional[DirectUrl]:
295
+ """Obtain a DirectUrl from this distribution.
296
+
297
+ Returns None if the distribution has no `direct_url.json` metadata,
298
+ or if `direct_url.json` is invalid.
299
+ """
300
+ try:
301
+ content = self.read_text(DIRECT_URL_METADATA_NAME)
302
+ except FileNotFoundError:
303
+ return None
304
+ try:
305
+ return DirectUrl.from_json(content)
306
+ except (
307
+ UnicodeDecodeError,
308
+ json.JSONDecodeError,
309
+ DirectUrlValidationError,
310
+ ) as e:
311
+ logger.warning(
312
+ "Error parsing %s for %s: %s",
313
+ DIRECT_URL_METADATA_NAME,
314
+ self.canonical_name,
315
+ e,
316
+ )
317
+ return None
318
+
319
+ @property
320
+ def installer(self) -> str:
321
+ try:
322
+ installer_text = self.read_text("INSTALLER")
323
+ except (OSError, ValueError, NoneMetadataError):
324
+ return "" # Fail silently if the installer file cannot be read.
325
+ for line in installer_text.splitlines():
326
+ cleaned_line = line.strip()
327
+ if cleaned_line:
328
+ return cleaned_line
329
+ return ""
330
+
331
+ @property
332
+ def requested(self) -> bool:
333
+ return self.is_file("REQUESTED")
334
+
335
+ @property
336
+ def editable(self) -> bool:
337
+ return bool(self.editable_project_location)
338
+
339
+ @property
340
+ def local(self) -> bool:
341
+ """If distribution is installed in the current virtual environment.
342
+
343
+ Always True if we're not in a virtualenv.
344
+ """
345
+ if self.installed_location is None:
346
+ return False
347
+ return is_local(self.installed_location)
348
+
349
+ @property
350
+ def in_usersite(self) -> bool:
351
+ if self.installed_location is None or user_site is None:
352
+ return False
353
+ return self.installed_location.startswith(normalize_path(user_site))
354
+
355
+ @property
356
+ def in_site_packages(self) -> bool:
357
+ if self.installed_location is None or site_packages is None:
358
+ return False
359
+ return self.installed_location.startswith(normalize_path(site_packages))
360
+
361
+ def is_file(self, path: InfoPath) -> bool:
362
+ """Check whether an entry in the info directory is a file."""
363
+ raise NotImplementedError()
364
+
365
+ def iter_distutils_script_names(self) -> Iterator[str]:
366
+ """Find distutils 'scripts' entries metadata.
367
+
368
+ If 'scripts' is supplied in ``setup.py``, distutils records those in the
369
+ installed distribution's ``scripts`` directory, a file for each script.
370
+ """
371
+ raise NotImplementedError()
372
+
373
+ def read_text(self, path: InfoPath) -> str:
374
+ """Read a file in the info directory.
375
+
376
+ :raise FileNotFoundError: If ``path`` does not exist in the directory.
377
+ :raise NoneMetadataError: If ``path`` exists in the info directory, but
378
+ cannot be read.
379
+ """
380
+ raise NotImplementedError()
381
+
382
+ def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
383
+ raise NotImplementedError()
384
+
385
+ def _metadata_impl(self) -> email.message.Message:
386
+ raise NotImplementedError()
387
+
388
+ @functools.lru_cache(maxsize=1)
389
+ def _metadata_cached(self) -> email.message.Message:
390
+ # When we drop python 3.7 support, move this to the metadata property and use
391
+ # functools.cached_property instead of lru_cache.
392
+ metadata = self._metadata_impl()
393
+ self._add_egg_info_requires(metadata)
394
+ return metadata
395
+
396
+ @property
397
+ def metadata(self) -> email.message.Message:
398
+ """Metadata of distribution parsed from e.g. METADATA or PKG-INFO.
399
+
400
+ This should return an empty message if the metadata file is unavailable.
401
+
402
+ :raises NoneMetadataError: If the metadata file is available, but does
403
+ not contain valid metadata.
404
+ """
405
+ return self._metadata_cached()
406
+
407
+ @property
408
+ def metadata_dict(self) -> Dict[str, Any]:
409
+ """PEP 566 compliant JSON-serializable representation of METADATA or PKG-INFO.
410
+
411
+ This should return an empty dict if the metadata file is unavailable.
412
+
413
+ :raises NoneMetadataError: If the metadata file is available, but does
414
+ not contain valid metadata.
415
+ """
416
+ return msg_to_json(self.metadata)
417
+
418
+ @property
419
+ def metadata_version(self) -> Optional[str]:
420
+ """Value of "Metadata-Version:" in distribution metadata, if available."""
421
+ return self.metadata.get("Metadata-Version")
422
+
423
+ @property
424
+ def raw_name(self) -> str:
425
+ """Value of "Name:" in distribution metadata."""
426
+ # The metadata should NEVER be missing the Name: key, but if it somehow
427
+ # does, fall back to the known canonical name.
428
+ return self.metadata.get("Name", self.canonical_name)
429
+
430
+ @property
431
+ def requires_python(self) -> SpecifierSet:
432
+ """Value of "Requires-Python:" in distribution metadata.
433
+
434
+ If the key does not exist or contains an invalid value, an empty
435
+ SpecifierSet should be returned.
436
+ """
437
+ value = self.metadata.get("Requires-Python")
438
+ if value is None:
439
+ return SpecifierSet()
440
+ try:
441
+ # Convert to str to satisfy the type checker; this can be a Header object.
442
+ spec = SpecifierSet(str(value))
443
+ except InvalidSpecifier as e:
444
+ message = "Package %r has an invalid Requires-Python: %s"
445
+ logger.warning(message, self.raw_name, e)
446
+ return SpecifierSet()
447
+ return spec
448
+
449
+ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
450
+ """Dependencies of this distribution.
451
+
452
+ For modern .dist-info distributions, this is the collection of
453
+ "Requires-Dist:" entries in distribution metadata.
454
+ """
455
+ raise NotImplementedError()
456
+
457
+ def iter_provided_extras(self) -> Iterable[str]:
458
+ """Extras provided by this distribution.
459
+
460
+ For modern .dist-info distributions, this is the collection of
461
+ "Provides-Extra:" entries in distribution metadata.
462
+
463
+ The return value of this function is not particularly useful other than
464
+ display purposes due to backward compatibility issues and the extra
465
+ names being poorly normalized prior to PEP 685. If you want to perform
466
+ logic operations on extras, use :func:`is_extra_provided` instead.
467
+ """
468
+ raise NotImplementedError()
469
+
470
+ def is_extra_provided(self, extra: str) -> bool:
471
+ """Check whether an extra is provided by this distribution.
472
+
473
+ This is needed mostly for compatibility issues with pkg_resources not
474
+ following the extra normalization rules defined in PEP 685.
475
+ """
476
+ raise NotImplementedError()
477
+
478
+ def _iter_declared_entries_from_record(self) -> Optional[Iterator[str]]:
479
+ try:
480
+ text = self.read_text("RECORD")
481
+ except FileNotFoundError:
482
+ return None
483
+ # This extra Path-str cast normalizes entries.
484
+ return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines()))
485
+
486
+ def _iter_declared_entries_from_legacy(self) -> Optional[Iterator[str]]:
487
+ try:
488
+ text = self.read_text("installed-files.txt")
489
+ except FileNotFoundError:
490
+ return None
491
+ paths = (p for p in text.splitlines(keepends=False) if p)
492
+ root = self.location
493
+ info = self.info_location
494
+ if root is None or info is None:
495
+ return paths
496
+ try:
497
+ info_rel = pathlib.Path(info).relative_to(root)
498
+ except ValueError: # info is not relative to root.
499
+ return paths
500
+ if not info_rel.parts: # info *is* root.
501
+ return paths
502
+ return (
503
+ _convert_installed_files_path(pathlib.Path(p).parts, info_rel.parts)
504
+ for p in paths
505
+ )
506
+
507
+ def iter_declared_entries(self) -> Optional[Iterator[str]]:
508
+ """Iterate through file entries declared in this distribution.
509
+
510
+ For modern .dist-info distributions, this is the files listed in the
511
+ ``RECORD`` metadata file. For legacy setuptools distributions, this
512
+ comes from ``installed-files.txt``, with entries normalized to be
513
+ compatible with the format used by ``RECORD``.
514
+
515
+ :return: An iterator for listed entries, or None if the distribution
516
+ contains neither ``RECORD`` nor ``installed-files.txt``.
517
+ """
518
+ return (
519
+ self._iter_declared_entries_from_record()
520
+ or self._iter_declared_entries_from_legacy()
521
+ )
522
+
523
+ def _iter_requires_txt_entries(self) -> Iterator[RequiresEntry]:
524
+ """Parse a ``requires.txt`` in an egg-info directory.
525
+
526
+ This is an INI-ish format where an egg-info stores dependencies. A
527
+ section name describes extra other environment markers, while each entry
528
+ is an arbitrary string (not a key-value pair) representing a dependency
529
+ as a requirement string (no markers).
530
+
531
+ There is a construct in ``importlib.metadata`` called ``Sectioned`` that
532
+ does mostly the same, but the format is currently considered private.
533
+ """
534
+ try:
535
+ content = self.read_text("requires.txt")
536
+ except FileNotFoundError:
537
+ return
538
+ extra = marker = "" # Section-less entries don't have markers.
539
+ for line in content.splitlines():
540
+ line = line.strip()
541
+ if not line or line.startswith("#"): # Comment; ignored.
542
+ continue
543
+ if line.startswith("[") and line.endswith("]"): # A section header.
544
+ extra, _, marker = line.strip("[]").partition(":")
545
+ continue
546
+ yield RequiresEntry(requirement=line, extra=extra, marker=marker)
547
+
548
+ def _iter_egg_info_extras(self) -> Iterable[str]:
549
+ """Get extras from the egg-info directory."""
550
+ known_extras = {""}
551
+ for entry in self._iter_requires_txt_entries():
552
+ extra = canonicalize_name(entry.extra)
553
+ if extra in known_extras:
554
+ continue
555
+ known_extras.add(extra)
556
+ yield extra
557
+
558
+ def _iter_egg_info_dependencies(self) -> Iterable[str]:
559
+ """Get distribution dependencies from the egg-info directory.
560
+
561
+ To ease parsing, this converts a legacy dependency entry into a PEP 508
562
+ requirement string. Like ``_iter_requires_txt_entries()``, there is code
563
+ in ``importlib.metadata`` that does mostly the same, but not do exactly
564
+ what we need.
565
+
566
+ Namely, ``importlib.metadata`` does not normalize the extra name before
567
+ putting it into the requirement string, which causes marker comparison
568
+ to fail because the dist-info format do normalize. This is consistent in
569
+ all currently available PEP 517 backends, although not standardized.
570
+ """
571
+ for entry in self._iter_requires_txt_entries():
572
+ extra = canonicalize_name(entry.extra)
573
+ if extra and entry.marker:
574
+ marker = f'({entry.marker}) and extra == "{extra}"'
575
+ elif extra:
576
+ marker = f'extra == "{extra}"'
577
+ elif entry.marker:
578
+ marker = entry.marker
579
+ else:
580
+ marker = ""
581
+ if marker:
582
+ yield f"{entry.requirement} ; {marker}"
583
+ else:
584
+ yield entry.requirement
585
+
586
+ def _add_egg_info_requires(self, metadata: email.message.Message) -> None:
587
+ """Add egg-info requires.txt information to the metadata."""
588
+ if not metadata.get_all("Requires-Dist"):
589
+ for dep in self._iter_egg_info_dependencies():
590
+ metadata["Requires-Dist"] = dep
591
+ if not metadata.get_all("Provides-Extra"):
592
+ for extra in self._iter_egg_info_extras():
593
+ metadata["Provides-Extra"] = extra
594
+
595
+
596
+ class BaseEnvironment:
597
+ """An environment containing distributions to introspect."""
598
+
599
+ @classmethod
600
+ def default(cls) -> "BaseEnvironment":
601
+ raise NotImplementedError()
602
+
603
+ @classmethod
604
+ def from_paths(cls, paths: Optional[List[str]]) -> "BaseEnvironment":
605
+ raise NotImplementedError()
606
+
607
+ def get_distribution(self, name: str) -> Optional["BaseDistribution"]:
608
+ """Given a requirement name, return the installed distributions.
609
+
610
+ The name may not be normalized. The implementation must canonicalize
611
+ it for lookup.
612
+ """
613
+ raise NotImplementedError()
614
+
615
+ def _iter_distributions(self) -> Iterator["BaseDistribution"]:
616
+ """Iterate through installed distributions.
617
+
618
+ This function should be implemented by subclass, but never called
619
+ directly. Use the public ``iter_distribution()`` instead, which
620
+ implements additional logic to make sure the distributions are valid.
621
+ """
622
+ raise NotImplementedError()
623
+
624
+ def iter_all_distributions(self) -> Iterator[BaseDistribution]:
625
+ """Iterate through all installed distributions without any filtering."""
626
+ for dist in self._iter_distributions():
627
+ # Make sure the distribution actually comes from a valid Python
628
+ # packaging distribution. Pip's AdjacentTempDirectory leaves folders
629
+ # e.g. ``~atplotlib.dist-info`` if cleanup was interrupted. The
630
+ # valid project name pattern is taken from PEP 508.
631
+ project_name_valid = re.match(
632
+ r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$",
633
+ dist.canonical_name,
634
+ flags=re.IGNORECASE,
635
+ )
636
+ if not project_name_valid:
637
+ logger.warning(
638
+ "Ignoring invalid distribution %s (%s)",
639
+ dist.canonical_name,
640
+ dist.location,
641
+ )
642
+ continue
643
+ yield dist
644
+
645
+ def iter_installed_distributions(
646
+ self,
647
+ local_only: bool = True,
648
+ skip: Container[str] = stdlib_pkgs,
649
+ include_editables: bool = True,
650
+ editables_only: bool = False,
651
+ user_only: bool = False,
652
+ ) -> Iterator[BaseDistribution]:
653
+ """Return a list of installed distributions.
654
+
655
+ This is based on ``iter_all_distributions()`` with additional filtering
656
+ options. Note that ``iter_installed_distributions()`` without arguments
657
+ is *not* equal to ``iter_all_distributions()``, since some of the
658
+ configurations exclude packages by default.
659
+
660
+ :param local_only: If True (default), only return installations
661
+ local to the current virtualenv, if in a virtualenv.
662
+ :param skip: An iterable of canonicalized project names to ignore;
663
+ defaults to ``stdlib_pkgs``.
664
+ :param include_editables: If False, don't report editables.
665
+ :param editables_only: If True, only report editables.
666
+ :param user_only: If True, only report installations in the user
667
+ site directory.
668
+ """
669
+ it = self.iter_all_distributions()
670
+ if local_only:
671
+ it = (d for d in it if d.local)
672
+ if not include_editables:
673
+ it = (d for d in it if not d.editable)
674
+ if editables_only:
675
+ it = (d for d in it if d.editable)
676
+ if user_only:
677
+ it = (d for d in it if d.in_usersite)
678
+ return (d for d in it if d.canonical_name not in skip)
679
+
680
+
681
+ class Wheel(Protocol):
682
+ location: str
683
+
684
+ def as_zipfile(self) -> zipfile.ZipFile:
685
+ raise NotImplementedError()
686
+
687
+
688
+ class FilesystemWheel(Wheel):
689
+ def __init__(self, location: str) -> None:
690
+ self.location = location
691
+
692
+ def as_zipfile(self) -> zipfile.ZipFile:
693
+ return zipfile.ZipFile(self.location, allowZip64=True)
694
+
695
+
696
+ class MemoryWheel(Wheel):
697
+ def __init__(self, location: str, stream: IO[bytes]) -> None:
698
+ self.location = location
699
+ self.stream = stream
700
+
701
+ def as_zipfile(self) -> zipfile.ZipFile:
702
+ return zipfile.ZipFile(self.stream, allowZip64=True)
.venv/lib/python3.11/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-311.pyc ADDED
Binary file (14.9 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/metadata/importlib/_compat.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib.metadata
2
+ from typing import Any, Optional, Protocol, cast
3
+
4
+
5
+ class BadMetadata(ValueError):
6
+ def __init__(self, dist: importlib.metadata.Distribution, *, reason: str) -> None:
7
+ self.dist = dist
8
+ self.reason = reason
9
+
10
+ def __str__(self) -> str:
11
+ return f"Bad metadata in {self.dist} ({self.reason})"
12
+
13
+
14
+ class BasePath(Protocol):
15
+ """A protocol that various path objects conform.
16
+
17
+ This exists because importlib.metadata uses both ``pathlib.Path`` and
18
+ ``zipfile.Path``, and we need a common base for type hints (Union does not
19
+ work well since ``zipfile.Path`` is too new for our linter setup).
20
+
21
+ This does not mean to be exhaustive, but only contains things that present
22
+ in both classes *that we need*.
23
+ """
24
+
25
+ @property
26
+ def name(self) -> str:
27
+ raise NotImplementedError()
28
+
29
+ @property
30
+ def parent(self) -> "BasePath":
31
+ raise NotImplementedError()
32
+
33
+
34
+ def get_info_location(d: importlib.metadata.Distribution) -> Optional[BasePath]:
35
+ """Find the path to the distribution's metadata directory.
36
+
37
+ HACK: This relies on importlib.metadata's private ``_path`` attribute. Not
38
+ all distributions exist on disk, so importlib.metadata is correct to not
39
+ expose the attribute as public. But pip's code base is old and not as clean,
40
+ so we do this to avoid having to rewrite too many things. Hopefully we can
41
+ eliminate this some day.
42
+ """
43
+ return getattr(d, "_path", None)
44
+
45
+
46
+ def get_dist_name(dist: importlib.metadata.Distribution) -> str:
47
+ """Get the distribution's project name.
48
+
49
+ The ``name`` attribute is only available in Python 3.10 or later. We are
50
+ targeting exactly that, but Mypy does not know this.
51
+ """
52
+ name = cast(Any, dist).name
53
+ if not isinstance(name, str):
54
+ raise BadMetadata(dist, reason="invalid metadata entry 'name'")
55
+ return name
.venv/lib/python3.11/site-packages/pip/_internal/metadata/importlib/_dists.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import email.message
2
+ import importlib.metadata
3
+ import os
4
+ import pathlib
5
+ import zipfile
6
+ from typing import (
7
+ Collection,
8
+ Dict,
9
+ Iterable,
10
+ Iterator,
11
+ Mapping,
12
+ Optional,
13
+ Sequence,
14
+ cast,
15
+ )
16
+
17
+ from pip._vendor.packaging.requirements import Requirement
18
+ from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
19
+ from pip._vendor.packaging.version import parse as parse_version
20
+
21
+ from pip._internal.exceptions import InvalidWheel, UnsupportedWheel
22
+ from pip._internal.metadata.base import (
23
+ BaseDistribution,
24
+ BaseEntryPoint,
25
+ DistributionVersion,
26
+ InfoPath,
27
+ Wheel,
28
+ )
29
+ from pip._internal.utils.misc import normalize_path
30
+ from pip._internal.utils.temp_dir import TempDirectory
31
+ from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
32
+
33
+ from ._compat import BasePath, get_dist_name
34
+
35
+
36
+ class WheelDistribution(importlib.metadata.Distribution):
37
+ """An ``importlib.metadata.Distribution`` read from a wheel.
38
+
39
+ Although ``importlib.metadata.PathDistribution`` accepts ``zipfile.Path``,
40
+ its implementation is too "lazy" for pip's needs (we can't keep the ZipFile
41
+ handle open for the entire lifetime of the distribution object).
42
+
43
+ This implementation eagerly reads the entire metadata directory into the
44
+ memory instead, and operates from that.
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ files: Mapping[pathlib.PurePosixPath, bytes],
50
+ info_location: pathlib.PurePosixPath,
51
+ ) -> None:
52
+ self._files = files
53
+ self.info_location = info_location
54
+
55
+ @classmethod
56
+ def from_zipfile(
57
+ cls,
58
+ zf: zipfile.ZipFile,
59
+ name: str,
60
+ location: str,
61
+ ) -> "WheelDistribution":
62
+ info_dir, _ = parse_wheel(zf, name)
63
+ paths = (
64
+ (name, pathlib.PurePosixPath(name.split("/", 1)[-1]))
65
+ for name in zf.namelist()
66
+ if name.startswith(f"{info_dir}/")
67
+ )
68
+ files = {
69
+ relpath: read_wheel_metadata_file(zf, fullpath)
70
+ for fullpath, relpath in paths
71
+ }
72
+ info_location = pathlib.PurePosixPath(location, info_dir)
73
+ return cls(files, info_location)
74
+
75
+ def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]:
76
+ # Only allow iterating through the metadata directory.
77
+ if pathlib.PurePosixPath(str(path)) in self._files:
78
+ return iter(self._files)
79
+ raise FileNotFoundError(path)
80
+
81
+ def read_text(self, filename: str) -> Optional[str]:
82
+ try:
83
+ data = self._files[pathlib.PurePosixPath(filename)]
84
+ except KeyError:
85
+ return None
86
+ try:
87
+ text = data.decode("utf-8")
88
+ except UnicodeDecodeError as e:
89
+ wheel = self.info_location.parent
90
+ error = f"Error decoding metadata for {wheel}: {e} in {filename} file"
91
+ raise UnsupportedWheel(error)
92
+ return text
93
+
94
+
95
+ class Distribution(BaseDistribution):
96
+ def __init__(
97
+ self,
98
+ dist: importlib.metadata.Distribution,
99
+ info_location: Optional[BasePath],
100
+ installed_location: Optional[BasePath],
101
+ ) -> None:
102
+ self._dist = dist
103
+ self._info_location = info_location
104
+ self._installed_location = installed_location
105
+
106
+ @classmethod
107
+ def from_directory(cls, directory: str) -> BaseDistribution:
108
+ info_location = pathlib.Path(directory)
109
+ dist = importlib.metadata.Distribution.at(info_location)
110
+ return cls(dist, info_location, info_location.parent)
111
+
112
+ @classmethod
113
+ def from_metadata_file_contents(
114
+ cls,
115
+ metadata_contents: bytes,
116
+ filename: str,
117
+ project_name: str,
118
+ ) -> BaseDistribution:
119
+ # Generate temp dir to contain the metadata file, and write the file contents.
120
+ temp_dir = pathlib.Path(
121
+ TempDirectory(kind="metadata", globally_managed=True).path
122
+ )
123
+ metadata_path = temp_dir / "METADATA"
124
+ metadata_path.write_bytes(metadata_contents)
125
+ # Construct dist pointing to the newly created directory.
126
+ dist = importlib.metadata.Distribution.at(metadata_path.parent)
127
+ return cls(dist, metadata_path.parent, None)
128
+
129
+ @classmethod
130
+ def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
131
+ try:
132
+ with wheel.as_zipfile() as zf:
133
+ dist = WheelDistribution.from_zipfile(zf, name, wheel.location)
134
+ except zipfile.BadZipFile as e:
135
+ raise InvalidWheel(wheel.location, name) from e
136
+ except UnsupportedWheel as e:
137
+ raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
138
+ return cls(dist, dist.info_location, pathlib.PurePosixPath(wheel.location))
139
+
140
+ @property
141
+ def location(self) -> Optional[str]:
142
+ if self._info_location is None:
143
+ return None
144
+ return str(self._info_location.parent)
145
+
146
+ @property
147
+ def info_location(self) -> Optional[str]:
148
+ if self._info_location is None:
149
+ return None
150
+ return str(self._info_location)
151
+
152
+ @property
153
+ def installed_location(self) -> Optional[str]:
154
+ if self._installed_location is None:
155
+ return None
156
+ return normalize_path(str(self._installed_location))
157
+
158
+ def _get_dist_name_from_location(self) -> Optional[str]:
159
+ """Try to get the name from the metadata directory name.
160
+
161
+ This is much faster than reading metadata.
162
+ """
163
+ if self._info_location is None:
164
+ return None
165
+ stem, suffix = os.path.splitext(self._info_location.name)
166
+ if suffix not in (".dist-info", ".egg-info"):
167
+ return None
168
+ return stem.split("-", 1)[0]
169
+
170
+ @property
171
+ def canonical_name(self) -> NormalizedName:
172
+ name = self._get_dist_name_from_location() or get_dist_name(self._dist)
173
+ return canonicalize_name(name)
174
+
175
+ @property
176
+ def version(self) -> DistributionVersion:
177
+ return parse_version(self._dist.version)
178
+
179
+ def is_file(self, path: InfoPath) -> bool:
180
+ return self._dist.read_text(str(path)) is not None
181
+
182
+ def iter_distutils_script_names(self) -> Iterator[str]:
183
+ # A distutils installation is always "flat" (not in e.g. egg form), so
184
+ # if this distribution's info location is NOT a pathlib.Path (but e.g.
185
+ # zipfile.Path), it can never contain any distutils scripts.
186
+ if not isinstance(self._info_location, pathlib.Path):
187
+ return
188
+ for child in self._info_location.joinpath("scripts").iterdir():
189
+ yield child.name
190
+
191
+ def read_text(self, path: InfoPath) -> str:
192
+ content = self._dist.read_text(str(path))
193
+ if content is None:
194
+ raise FileNotFoundError(path)
195
+ return content
196
+
197
+ def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
198
+ # importlib.metadata's EntryPoint structure sasitfies BaseEntryPoint.
199
+ return self._dist.entry_points
200
+
201
+ def _metadata_impl(self) -> email.message.Message:
202
+ # From Python 3.10+, importlib.metadata declares PackageMetadata as the
203
+ # return type. This protocol is unfortunately a disaster now and misses
204
+ # a ton of fields that we need, including get() and get_payload(). We
205
+ # rely on the implementation that the object is actually a Message now,
206
+ # until upstream can improve the protocol. (python/cpython#94952)
207
+ return cast(email.message.Message, self._dist.metadata)
208
+
209
+ def iter_provided_extras(self) -> Iterable[str]:
210
+ return self.metadata.get_all("Provides-Extra", [])
211
+
212
+ def is_extra_provided(self, extra: str) -> bool:
213
+ return any(
214
+ canonicalize_name(provided_extra) == canonicalize_name(extra)
215
+ for provided_extra in self.metadata.get_all("Provides-Extra", [])
216
+ )
217
+
218
+ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
219
+ contexts: Sequence[Dict[str, str]] = [{"extra": e} for e in extras]
220
+ for req_string in self.metadata.get_all("Requires-Dist", []):
221
+ req = Requirement(req_string)
222
+ if not req.marker:
223
+ yield req
224
+ elif not extras and req.marker.evaluate({"extra": ""}):
225
+ yield req
226
+ elif any(req.marker.evaluate(context) for context in contexts):
227
+ yield req
.venv/lib/python3.11/site-packages/pip/_internal/metadata/importlib/_envs.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import importlib.metadata
3
+ import logging
4
+ import os
5
+ import pathlib
6
+ import sys
7
+ import zipfile
8
+ import zipimport
9
+ from typing import Iterator, List, Optional, Sequence, Set, Tuple
10
+
11
+ from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
12
+
13
+ from pip._internal.metadata.base import BaseDistribution, BaseEnvironment
14
+ from pip._internal.models.wheel import Wheel
15
+ from pip._internal.utils.deprecation import deprecated
16
+ from pip._internal.utils.filetypes import WHEEL_EXTENSION
17
+
18
+ from ._compat import BadMetadata, BasePath, get_dist_name, get_info_location
19
+ from ._dists import Distribution
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def _looks_like_wheel(location: str) -> bool:
25
+ if not location.endswith(WHEEL_EXTENSION):
26
+ return False
27
+ if not os.path.isfile(location):
28
+ return False
29
+ if not Wheel.wheel_file_re.match(os.path.basename(location)):
30
+ return False
31
+ return zipfile.is_zipfile(location)
32
+
33
+
34
+ class _DistributionFinder:
35
+ """Finder to locate distributions.
36
+
37
+ The main purpose of this class is to memoize found distributions' names, so
38
+ only one distribution is returned for each package name. At lot of pip code
39
+ assumes this (because it is setuptools's behavior), and not doing the same
40
+ can potentially cause a distribution in lower precedence path to override a
41
+ higher precedence one if the caller is not careful.
42
+
43
+ Eventually we probably want to make it possible to see lower precedence
44
+ installations as well. It's useful feature, after all.
45
+ """
46
+
47
+ FoundResult = Tuple[importlib.metadata.Distribution, Optional[BasePath]]
48
+
49
+ def __init__(self) -> None:
50
+ self._found_names: Set[NormalizedName] = set()
51
+
52
+ def _find_impl(self, location: str) -> Iterator[FoundResult]:
53
+ """Find distributions in a location."""
54
+ # Skip looking inside a wheel. Since a package inside a wheel is not
55
+ # always valid (due to .data directories etc.), its .dist-info entry
56
+ # should not be considered an installed distribution.
57
+ if _looks_like_wheel(location):
58
+ return
59
+ # To know exactly where we find a distribution, we have to feed in the
60
+ # paths one by one, instead of dumping the list to importlib.metadata.
61
+ for dist in importlib.metadata.distributions(path=[location]):
62
+ info_location = get_info_location(dist)
63
+ try:
64
+ raw_name = get_dist_name(dist)
65
+ except BadMetadata as e:
66
+ logger.warning("Skipping %s due to %s", info_location, e.reason)
67
+ continue
68
+ normalized_name = canonicalize_name(raw_name)
69
+ if normalized_name in self._found_names:
70
+ continue
71
+ self._found_names.add(normalized_name)
72
+ yield dist, info_location
73
+
74
+ def find(self, location: str) -> Iterator[BaseDistribution]:
75
+ """Find distributions in a location.
76
+
77
+ The path can be either a directory, or a ZIP archive.
78
+ """
79
+ for dist, info_location in self._find_impl(location):
80
+ if info_location is None:
81
+ installed_location: Optional[BasePath] = None
82
+ else:
83
+ installed_location = info_location.parent
84
+ yield Distribution(dist, info_location, installed_location)
85
+
86
+ def find_linked(self, location: str) -> Iterator[BaseDistribution]:
87
+ """Read location in egg-link files and return distributions in there.
88
+
89
+ The path should be a directory; otherwise this returns nothing. This
90
+ follows how setuptools does this for compatibility. The first non-empty
91
+ line in the egg-link is read as a path (resolved against the egg-link's
92
+ containing directory if relative). Distributions found at that linked
93
+ location are returned.
94
+ """
95
+ path = pathlib.Path(location)
96
+ if not path.is_dir():
97
+ return
98
+ for child in path.iterdir():
99
+ if child.suffix != ".egg-link":
100
+ continue
101
+ with child.open() as f:
102
+ lines = (line.strip() for line in f)
103
+ target_rel = next((line for line in lines if line), "")
104
+ if not target_rel:
105
+ continue
106
+ target_location = str(path.joinpath(target_rel))
107
+ for dist, info_location in self._find_impl(target_location):
108
+ yield Distribution(dist, info_location, path)
109
+
110
+ def _find_eggs_in_dir(self, location: str) -> Iterator[BaseDistribution]:
111
+ from pip._vendor.pkg_resources import find_distributions
112
+
113
+ from pip._internal.metadata import pkg_resources as legacy
114
+
115
+ with os.scandir(location) as it:
116
+ for entry in it:
117
+ if not entry.name.endswith(".egg"):
118
+ continue
119
+ for dist in find_distributions(entry.path):
120
+ yield legacy.Distribution(dist)
121
+
122
+ def _find_eggs_in_zip(self, location: str) -> Iterator[BaseDistribution]:
123
+ from pip._vendor.pkg_resources import find_eggs_in_zip
124
+
125
+ from pip._internal.metadata import pkg_resources as legacy
126
+
127
+ try:
128
+ importer = zipimport.zipimporter(location)
129
+ except zipimport.ZipImportError:
130
+ return
131
+ for dist in find_eggs_in_zip(importer, location):
132
+ yield legacy.Distribution(dist)
133
+
134
+ def find_eggs(self, location: str) -> Iterator[BaseDistribution]:
135
+ """Find eggs in a location.
136
+
137
+ This actually uses the old *pkg_resources* backend. We likely want to
138
+ deprecate this so we can eventually remove the *pkg_resources*
139
+ dependency entirely. Before that, this should first emit a deprecation
140
+ warning for some versions when using the fallback since importing
141
+ *pkg_resources* is slow for those who don't need it.
142
+ """
143
+ if os.path.isdir(location):
144
+ yield from self._find_eggs_in_dir(location)
145
+ if zipfile.is_zipfile(location):
146
+ yield from self._find_eggs_in_zip(location)
147
+
148
+
149
+ @functools.lru_cache(maxsize=None) # Warn a distribution exactly once.
150
+ def _emit_egg_deprecation(location: Optional[str]) -> None:
151
+ deprecated(
152
+ reason=f"Loading egg at {location} is deprecated.",
153
+ replacement="to use pip for package installation.",
154
+ gone_in="24.3",
155
+ issue=12330,
156
+ )
157
+
158
+
159
+ class Environment(BaseEnvironment):
160
+ def __init__(self, paths: Sequence[str]) -> None:
161
+ self._paths = paths
162
+
163
+ @classmethod
164
+ def default(cls) -> BaseEnvironment:
165
+ return cls(sys.path)
166
+
167
+ @classmethod
168
+ def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
169
+ if paths is None:
170
+ return cls(sys.path)
171
+ return cls(paths)
172
+
173
+ def _iter_distributions(self) -> Iterator[BaseDistribution]:
174
+ finder = _DistributionFinder()
175
+ for location in self._paths:
176
+ yield from finder.find(location)
177
+ for dist in finder.find_eggs(location):
178
+ _emit_egg_deprecation(dist.location)
179
+ yield dist
180
+ # This must go last because that's how pkg_resources tie-breaks.
181
+ yield from finder.find_linked(location)
182
+
183
+ def get_distribution(self, name: str) -> Optional[BaseDistribution]:
184
+ matches = (
185
+ distribution
186
+ for distribution in self.iter_all_distributions()
187
+ if distribution.canonical_name == canonicalize_name(name)
188
+ )
189
+ return next(matches, None)
.venv/lib/python3.11/site-packages/pip/_internal/metadata/pkg_resources.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import email.message
2
+ import email.parser
3
+ import logging
4
+ import os
5
+ import zipfile
6
+ from typing import Collection, Iterable, Iterator, List, Mapping, NamedTuple, Optional
7
+
8
+ from pip._vendor import pkg_resources
9
+ from pip._vendor.packaging.requirements import Requirement
10
+ from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
11
+ from pip._vendor.packaging.version import parse as parse_version
12
+
13
+ from pip._internal.exceptions import InvalidWheel, NoneMetadataError, UnsupportedWheel
14
+ from pip._internal.utils.egg_link import egg_link_path_from_location
15
+ from pip._internal.utils.misc import display_path, normalize_path
16
+ from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
17
+
18
+ from .base import (
19
+ BaseDistribution,
20
+ BaseEntryPoint,
21
+ BaseEnvironment,
22
+ DistributionVersion,
23
+ InfoPath,
24
+ Wheel,
25
+ )
26
+
27
+ __all__ = ["NAME", "Distribution", "Environment"]
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ NAME = "pkg_resources"
32
+
33
+
34
+ class EntryPoint(NamedTuple):
35
+ name: str
36
+ value: str
37
+ group: str
38
+
39
+
40
+ class InMemoryMetadata:
41
+ """IMetadataProvider that reads metadata files from a dictionary.
42
+
43
+ This also maps metadata decoding exceptions to our internal exception type.
44
+ """
45
+
46
+ def __init__(self, metadata: Mapping[str, bytes], wheel_name: str) -> None:
47
+ self._metadata = metadata
48
+ self._wheel_name = wheel_name
49
+
50
+ def has_metadata(self, name: str) -> bool:
51
+ return name in self._metadata
52
+
53
+ def get_metadata(self, name: str) -> str:
54
+ try:
55
+ return self._metadata[name].decode()
56
+ except UnicodeDecodeError as e:
57
+ # Augment the default error with the origin of the file.
58
+ raise UnsupportedWheel(
59
+ f"Error decoding metadata for {self._wheel_name}: {e} in {name} file"
60
+ )
61
+
62
+ def get_metadata_lines(self, name: str) -> Iterable[str]:
63
+ return pkg_resources.yield_lines(self.get_metadata(name))
64
+
65
+ def metadata_isdir(self, name: str) -> bool:
66
+ return False
67
+
68
+ def metadata_listdir(self, name: str) -> List[str]:
69
+ return []
70
+
71
+ def run_script(self, script_name: str, namespace: str) -> None:
72
+ pass
73
+
74
+
75
+ class Distribution(BaseDistribution):
76
+ def __init__(self, dist: pkg_resources.Distribution) -> None:
77
+ self._dist = dist
78
+
79
+ @classmethod
80
+ def from_directory(cls, directory: str) -> BaseDistribution:
81
+ dist_dir = directory.rstrip(os.sep)
82
+
83
+ # Build a PathMetadata object, from path to metadata. :wink:
84
+ base_dir, dist_dir_name = os.path.split(dist_dir)
85
+ metadata = pkg_resources.PathMetadata(base_dir, dist_dir)
86
+
87
+ # Determine the correct Distribution object type.
88
+ if dist_dir.endswith(".egg-info"):
89
+ dist_cls = pkg_resources.Distribution
90
+ dist_name = os.path.splitext(dist_dir_name)[0]
91
+ else:
92
+ assert dist_dir.endswith(".dist-info")
93
+ dist_cls = pkg_resources.DistInfoDistribution
94
+ dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0]
95
+
96
+ dist = dist_cls(base_dir, project_name=dist_name, metadata=metadata)
97
+ return cls(dist)
98
+
99
+ @classmethod
100
+ def from_metadata_file_contents(
101
+ cls,
102
+ metadata_contents: bytes,
103
+ filename: str,
104
+ project_name: str,
105
+ ) -> BaseDistribution:
106
+ metadata_dict = {
107
+ "METADATA": metadata_contents,
108
+ }
109
+ dist = pkg_resources.DistInfoDistribution(
110
+ location=filename,
111
+ metadata=InMemoryMetadata(metadata_dict, filename),
112
+ project_name=project_name,
113
+ )
114
+ return cls(dist)
115
+
116
+ @classmethod
117
+ def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
118
+ try:
119
+ with wheel.as_zipfile() as zf:
120
+ info_dir, _ = parse_wheel(zf, name)
121
+ metadata_dict = {
122
+ path.split("/", 1)[-1]: read_wheel_metadata_file(zf, path)
123
+ for path in zf.namelist()
124
+ if path.startswith(f"{info_dir}/")
125
+ }
126
+ except zipfile.BadZipFile as e:
127
+ raise InvalidWheel(wheel.location, name) from e
128
+ except UnsupportedWheel as e:
129
+ raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
130
+ dist = pkg_resources.DistInfoDistribution(
131
+ location=wheel.location,
132
+ metadata=InMemoryMetadata(metadata_dict, wheel.location),
133
+ project_name=name,
134
+ )
135
+ return cls(dist)
136
+
137
+ @property
138
+ def location(self) -> Optional[str]:
139
+ return self._dist.location
140
+
141
+ @property
142
+ def installed_location(self) -> Optional[str]:
143
+ egg_link = egg_link_path_from_location(self.raw_name)
144
+ if egg_link:
145
+ location = egg_link
146
+ elif self.location:
147
+ location = self.location
148
+ else:
149
+ return None
150
+ return normalize_path(location)
151
+
152
+ @property
153
+ def info_location(self) -> Optional[str]:
154
+ return self._dist.egg_info
155
+
156
+ @property
157
+ def installed_by_distutils(self) -> bool:
158
+ # A distutils-installed distribution is provided by FileMetadata. This
159
+ # provider has a "path" attribute not present anywhere else. Not the
160
+ # best introspection logic, but pip has been doing this for a long time.
161
+ try:
162
+ return bool(self._dist._provider.path)
163
+ except AttributeError:
164
+ return False
165
+
166
+ @property
167
+ def canonical_name(self) -> NormalizedName:
168
+ return canonicalize_name(self._dist.project_name)
169
+
170
+ @property
171
+ def version(self) -> DistributionVersion:
172
+ return parse_version(self._dist.version)
173
+
174
+ def is_file(self, path: InfoPath) -> bool:
175
+ return self._dist.has_metadata(str(path))
176
+
177
+ def iter_distutils_script_names(self) -> Iterator[str]:
178
+ yield from self._dist.metadata_listdir("scripts")
179
+
180
+ def read_text(self, path: InfoPath) -> str:
181
+ name = str(path)
182
+ if not self._dist.has_metadata(name):
183
+ raise FileNotFoundError(name)
184
+ content = self._dist.get_metadata(name)
185
+ if content is None:
186
+ raise NoneMetadataError(self, name)
187
+ return content
188
+
189
+ def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
190
+ for group, entries in self._dist.get_entry_map().items():
191
+ for name, entry_point in entries.items():
192
+ name, _, value = str(entry_point).partition("=")
193
+ yield EntryPoint(name=name.strip(), value=value.strip(), group=group)
194
+
195
+ def _metadata_impl(self) -> email.message.Message:
196
+ """
197
+ :raises NoneMetadataError: if the distribution reports `has_metadata()`
198
+ True but `get_metadata()` returns None.
199
+ """
200
+ if isinstance(self._dist, pkg_resources.DistInfoDistribution):
201
+ metadata_name = "METADATA"
202
+ else:
203
+ metadata_name = "PKG-INFO"
204
+ try:
205
+ metadata = self.read_text(metadata_name)
206
+ except FileNotFoundError:
207
+ if self.location:
208
+ displaying_path = display_path(self.location)
209
+ else:
210
+ displaying_path = repr(self.location)
211
+ logger.warning("No metadata found in %s", displaying_path)
212
+ metadata = ""
213
+ feed_parser = email.parser.FeedParser()
214
+ feed_parser.feed(metadata)
215
+ return feed_parser.close()
216
+
217
+ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
218
+ if extras: # pkg_resources raises on invalid extras, so we sanitize.
219
+ extras = frozenset(pkg_resources.safe_extra(e) for e in extras)
220
+ extras = extras.intersection(self._dist.extras)
221
+ return self._dist.requires(extras)
222
+
223
+ def iter_provided_extras(self) -> Iterable[str]:
224
+ return self._dist.extras
225
+
226
+ def is_extra_provided(self, extra: str) -> bool:
227
+ return pkg_resources.safe_extra(extra) in self._dist.extras
228
+
229
+
230
+ class Environment(BaseEnvironment):
231
+ def __init__(self, ws: pkg_resources.WorkingSet) -> None:
232
+ self._ws = ws
233
+
234
+ @classmethod
235
+ def default(cls) -> BaseEnvironment:
236
+ return cls(pkg_resources.working_set)
237
+
238
+ @classmethod
239
+ def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
240
+ return cls(pkg_resources.WorkingSet(paths))
241
+
242
+ def _iter_distributions(self) -> Iterator[BaseDistribution]:
243
+ for dist in self._ws:
244
+ yield Distribution(dist)
245
+
246
+ def _search_distribution(self, name: str) -> Optional[BaseDistribution]:
247
+ """Find a distribution matching the ``name`` in the environment.
248
+
249
+ This searches from *all* distributions available in the environment, to
250
+ match the behavior of ``pkg_resources.get_distribution()``.
251
+ """
252
+ canonical_name = canonicalize_name(name)
253
+ for dist in self.iter_all_distributions():
254
+ if dist.canonical_name == canonical_name:
255
+ return dist
256
+ return None
257
+
258
+ def get_distribution(self, name: str) -> Optional[BaseDistribution]:
259
+ # Search the distribution by looking through the working set.
260
+ dist = self._search_distribution(name)
261
+ if dist:
262
+ return dist
263
+
264
+ # If distribution could not be found, call working_set.require to
265
+ # update the working set, and try to find the distribution again.
266
+ # This might happen for e.g. when you install a package twice, once
267
+ # using setup.py develop and again using setup.py install. Now when
268
+ # running pip uninstall twice, the package gets removed from the
269
+ # working set in the first uninstall, so we have to populate the
270
+ # working set again so that pip knows about it and the packages gets
271
+ # picked up and is successfully uninstalled the second time too.
272
+ try:
273
+ # We didn't pass in any version specifiers, so this can never
274
+ # raise pkg_resources.VersionConflict.
275
+ self._ws.require(name)
276
+ except pkg_resources.DistributionNotFound:
277
+ return None
278
+ return self._search_distribution(name)
.venv/lib/python3.11/site-packages/pip/_internal/network/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ """Contains purely network-related utilities.
2
+ """
.venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (259 Bytes). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/auth.cpython-311.pyc ADDED
Binary file (24 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/cache.cpython-311.pyc ADDED
Binary file (7.93 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/download.cpython-311.pyc ADDED
Binary file (9.54 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-311.pyc ADDED
Binary file (13 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/session.cpython-311.pyc ADDED
Binary file (21.4 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/utils.cpython-311.pyc ADDED
Binary file (2.41 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-311.pyc ADDED
Binary file (3.25 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/network/auth.py ADDED
@@ -0,0 +1,561 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Network Authentication Helpers
2
+
3
+ Contains interface (MultiDomainBasicAuth) and associated glue code for
4
+ providing credentials in the context of network requests.
5
+ """
6
+ import logging
7
+ import os
8
+ import shutil
9
+ import subprocess
10
+ import sysconfig
11
+ import typing
12
+ import urllib.parse
13
+ from abc import ABC, abstractmethod
14
+ from functools import lru_cache
15
+ from os.path import commonprefix
16
+ from pathlib import Path
17
+ from typing import Any, Dict, List, NamedTuple, Optional, Tuple
18
+
19
+ from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
20
+ from pip._vendor.requests.models import Request, Response
21
+ from pip._vendor.requests.utils import get_netrc_auth
22
+
23
+ from pip._internal.utils.logging import getLogger
24
+ from pip._internal.utils.misc import (
25
+ ask,
26
+ ask_input,
27
+ ask_password,
28
+ remove_auth_from_url,
29
+ split_auth_netloc_from_url,
30
+ )
31
+ from pip._internal.vcs.versioncontrol import AuthInfo
32
+
33
+ logger = getLogger(__name__)
34
+
35
+ KEYRING_DISABLED = False
36
+
37
+
38
+ class Credentials(NamedTuple):
39
+ url: str
40
+ username: str
41
+ password: str
42
+
43
+
44
+ class KeyRingBaseProvider(ABC):
45
+ """Keyring base provider interface"""
46
+
47
+ has_keyring: bool
48
+
49
+ @abstractmethod
50
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
51
+ ...
52
+
53
+ @abstractmethod
54
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
55
+ ...
56
+
57
+
58
+ class KeyRingNullProvider(KeyRingBaseProvider):
59
+ """Keyring null provider"""
60
+
61
+ has_keyring = False
62
+
63
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
64
+ return None
65
+
66
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
67
+ return None
68
+
69
+
70
+ class KeyRingPythonProvider(KeyRingBaseProvider):
71
+ """Keyring interface which uses locally imported `keyring`"""
72
+
73
+ has_keyring = True
74
+
75
+ def __init__(self) -> None:
76
+ import keyring
77
+
78
+ self.keyring = keyring
79
+
80
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
81
+ # Support keyring's get_credential interface which supports getting
82
+ # credentials without a username. This is only available for
83
+ # keyring>=15.2.0.
84
+ if hasattr(self.keyring, "get_credential"):
85
+ logger.debug("Getting credentials from keyring for %s", url)
86
+ cred = self.keyring.get_credential(url, username)
87
+ if cred is not None:
88
+ return cred.username, cred.password
89
+ return None
90
+
91
+ if username is not None:
92
+ logger.debug("Getting password from keyring for %s", url)
93
+ password = self.keyring.get_password(url, username)
94
+ if password:
95
+ return username, password
96
+ return None
97
+
98
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
99
+ self.keyring.set_password(url, username, password)
100
+
101
+
102
+ class KeyRingCliProvider(KeyRingBaseProvider):
103
+ """Provider which uses `keyring` cli
104
+
105
+ Instead of calling the keyring package installed alongside pip
106
+ we call keyring on the command line which will enable pip to
107
+ use which ever installation of keyring is available first in
108
+ PATH.
109
+ """
110
+
111
+ has_keyring = True
112
+
113
+ def __init__(self, cmd: str) -> None:
114
+ self.keyring = cmd
115
+
116
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
117
+ # This is the default implementation of keyring.get_credential
118
+ # https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139
119
+ if username is not None:
120
+ password = self._get_password(url, username)
121
+ if password is not None:
122
+ return username, password
123
+ return None
124
+
125
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
126
+ return self._set_password(url, username, password)
127
+
128
+ def _get_password(self, service_name: str, username: str) -> Optional[str]:
129
+ """Mirror the implementation of keyring.get_password using cli"""
130
+ if self.keyring is None:
131
+ return None
132
+
133
+ cmd = [self.keyring, "get", service_name, username]
134
+ env = os.environ.copy()
135
+ env["PYTHONIOENCODING"] = "utf-8"
136
+ res = subprocess.run(
137
+ cmd,
138
+ stdin=subprocess.DEVNULL,
139
+ stdout=subprocess.PIPE,
140
+ env=env,
141
+ )
142
+ if res.returncode:
143
+ return None
144
+ return res.stdout.decode("utf-8").strip(os.linesep)
145
+
146
+ def _set_password(self, service_name: str, username: str, password: str) -> None:
147
+ """Mirror the implementation of keyring.set_password using cli"""
148
+ if self.keyring is None:
149
+ return None
150
+ env = os.environ.copy()
151
+ env["PYTHONIOENCODING"] = "utf-8"
152
+ subprocess.run(
153
+ [self.keyring, "set", service_name, username],
154
+ input=f"{password}{os.linesep}".encode("utf-8"),
155
+ env=env,
156
+ check=True,
157
+ )
158
+ return None
159
+
160
+
161
+ @lru_cache(maxsize=None)
162
+ def get_keyring_provider(provider: str) -> KeyRingBaseProvider:
163
+ logger.verbose("Keyring provider requested: %s", provider)
164
+
165
+ # keyring has previously failed and been disabled
166
+ if KEYRING_DISABLED:
167
+ provider = "disabled"
168
+ if provider in ["import", "auto"]:
169
+ try:
170
+ impl = KeyRingPythonProvider()
171
+ logger.verbose("Keyring provider set: import")
172
+ return impl
173
+ except ImportError:
174
+ pass
175
+ except Exception as exc:
176
+ # In the event of an unexpected exception
177
+ # we should warn the user
178
+ msg = "Installed copy of keyring fails with exception %s"
179
+ if provider == "auto":
180
+ msg = msg + ", trying to find a keyring executable as a fallback"
181
+ logger.warning(msg, exc, exc_info=logger.isEnabledFor(logging.DEBUG))
182
+ if provider in ["subprocess", "auto"]:
183
+ cli = shutil.which("keyring")
184
+ if cli and cli.startswith(sysconfig.get_path("scripts")):
185
+ # all code within this function is stolen from shutil.which implementation
186
+ @typing.no_type_check
187
+ def PATH_as_shutil_which_determines_it() -> str:
188
+ path = os.environ.get("PATH", None)
189
+ if path is None:
190
+ try:
191
+ path = os.confstr("CS_PATH")
192
+ except (AttributeError, ValueError):
193
+ # os.confstr() or CS_PATH is not available
194
+ path = os.defpath
195
+ # bpo-35755: Don't use os.defpath if the PATH environment variable is
196
+ # set to an empty string
197
+
198
+ return path
199
+
200
+ scripts = Path(sysconfig.get_path("scripts"))
201
+
202
+ paths = []
203
+ for path in PATH_as_shutil_which_determines_it().split(os.pathsep):
204
+ p = Path(path)
205
+ try:
206
+ if not p.samefile(scripts):
207
+ paths.append(path)
208
+ except FileNotFoundError:
209
+ pass
210
+
211
+ path = os.pathsep.join(paths)
212
+
213
+ cli = shutil.which("keyring", path=path)
214
+
215
+ if cli:
216
+ logger.verbose("Keyring provider set: subprocess with executable %s", cli)
217
+ return KeyRingCliProvider(cli)
218
+
219
+ logger.verbose("Keyring provider set: disabled")
220
+ return KeyRingNullProvider()
221
+
222
+
223
+ class MultiDomainBasicAuth(AuthBase):
224
+ def __init__(
225
+ self,
226
+ prompting: bool = True,
227
+ index_urls: Optional[List[str]] = None,
228
+ keyring_provider: str = "auto",
229
+ ) -> None:
230
+ self.prompting = prompting
231
+ self.index_urls = index_urls
232
+ self.keyring_provider = keyring_provider # type: ignore[assignment]
233
+ self.passwords: Dict[str, AuthInfo] = {}
234
+ # When the user is prompted to enter credentials and keyring is
235
+ # available, we will offer to save them. If the user accepts,
236
+ # this value is set to the credentials they entered. After the
237
+ # request authenticates, the caller should call
238
+ # ``save_credentials`` to save these.
239
+ self._credentials_to_save: Optional[Credentials] = None
240
+
241
+ @property
242
+ def keyring_provider(self) -> KeyRingBaseProvider:
243
+ return get_keyring_provider(self._keyring_provider)
244
+
245
+ @keyring_provider.setter
246
+ def keyring_provider(self, provider: str) -> None:
247
+ # The free function get_keyring_provider has been decorated with
248
+ # functools.cache. If an exception occurs in get_keyring_auth that
249
+ # cache will be cleared and keyring disabled, take that into account
250
+ # if you want to remove this indirection.
251
+ self._keyring_provider = provider
252
+
253
+ @property
254
+ def use_keyring(self) -> bool:
255
+ # We won't use keyring when --no-input is passed unless
256
+ # a specific provider is requested because it might require
257
+ # user interaction
258
+ return self.prompting or self._keyring_provider not in ["auto", "disabled"]
259
+
260
+ def _get_keyring_auth(
261
+ self,
262
+ url: Optional[str],
263
+ username: Optional[str],
264
+ ) -> Optional[AuthInfo]:
265
+ """Return the tuple auth for a given url from keyring."""
266
+ # Do nothing if no url was provided
267
+ if not url:
268
+ return None
269
+
270
+ try:
271
+ return self.keyring_provider.get_auth_info(url, username)
272
+ except Exception as exc:
273
+ logger.warning(
274
+ "Keyring is skipped due to an exception: %s",
275
+ str(exc),
276
+ )
277
+ global KEYRING_DISABLED
278
+ KEYRING_DISABLED = True
279
+ get_keyring_provider.cache_clear()
280
+ return None
281
+
282
+ def _get_index_url(self, url: str) -> Optional[str]:
283
+ """Return the original index URL matching the requested URL.
284
+
285
+ Cached or dynamically generated credentials may work against
286
+ the original index URL rather than just the netloc.
287
+
288
+ The provided url should have had its username and password
289
+ removed already. If the original index url had credentials then
290
+ they will be included in the return value.
291
+
292
+ Returns None if no matching index was found, or if --no-index
293
+ was specified by the user.
294
+ """
295
+ if not url or not self.index_urls:
296
+ return None
297
+
298
+ url = remove_auth_from_url(url).rstrip("/") + "/"
299
+ parsed_url = urllib.parse.urlsplit(url)
300
+
301
+ candidates = []
302
+
303
+ for index in self.index_urls:
304
+ index = index.rstrip("/") + "/"
305
+ parsed_index = urllib.parse.urlsplit(remove_auth_from_url(index))
306
+ if parsed_url == parsed_index:
307
+ return index
308
+
309
+ if parsed_url.netloc != parsed_index.netloc:
310
+ continue
311
+
312
+ candidate = urllib.parse.urlsplit(index)
313
+ candidates.append(candidate)
314
+
315
+ if not candidates:
316
+ return None
317
+
318
+ candidates.sort(
319
+ reverse=True,
320
+ key=lambda candidate: commonprefix(
321
+ [
322
+ parsed_url.path,
323
+ candidate.path,
324
+ ]
325
+ ).rfind("/"),
326
+ )
327
+
328
+ return urllib.parse.urlunsplit(candidates[0])
329
+
330
+ def _get_new_credentials(
331
+ self,
332
+ original_url: str,
333
+ *,
334
+ allow_netrc: bool = True,
335
+ allow_keyring: bool = False,
336
+ ) -> AuthInfo:
337
+ """Find and return credentials for the specified URL."""
338
+ # Split the credentials and netloc from the url.
339
+ url, netloc, url_user_password = split_auth_netloc_from_url(
340
+ original_url,
341
+ )
342
+
343
+ # Start with the credentials embedded in the url
344
+ username, password = url_user_password
345
+ if username is not None and password is not None:
346
+ logger.debug("Found credentials in url for %s", netloc)
347
+ return url_user_password
348
+
349
+ # Find a matching index url for this request
350
+ index_url = self._get_index_url(url)
351
+ if index_url:
352
+ # Split the credentials from the url.
353
+ index_info = split_auth_netloc_from_url(index_url)
354
+ if index_info:
355
+ index_url, _, index_url_user_password = index_info
356
+ logger.debug("Found index url %s", index_url)
357
+
358
+ # If an index URL was found, try its embedded credentials
359
+ if index_url and index_url_user_password[0] is not None:
360
+ username, password = index_url_user_password
361
+ if username is not None and password is not None:
362
+ logger.debug("Found credentials in index url for %s", netloc)
363
+ return index_url_user_password
364
+
365
+ # Get creds from netrc if we still don't have them
366
+ if allow_netrc:
367
+ netrc_auth = get_netrc_auth(original_url)
368
+ if netrc_auth:
369
+ logger.debug("Found credentials in netrc for %s", netloc)
370
+ return netrc_auth
371
+
372
+ # If we don't have a password and keyring is available, use it.
373
+ if allow_keyring:
374
+ # The index url is more specific than the netloc, so try it first
375
+ # fmt: off
376
+ kr_auth = (
377
+ self._get_keyring_auth(index_url, username) or
378
+ self._get_keyring_auth(netloc, username)
379
+ )
380
+ # fmt: on
381
+ if kr_auth:
382
+ logger.debug("Found credentials in keyring for %s", netloc)
383
+ return kr_auth
384
+
385
+ return username, password
386
+
387
+ def _get_url_and_credentials(
388
+ self, original_url: str
389
+ ) -> Tuple[str, Optional[str], Optional[str]]:
390
+ """Return the credentials to use for the provided URL.
391
+
392
+ If allowed, netrc and keyring may be used to obtain the
393
+ correct credentials.
394
+
395
+ Returns (url_without_credentials, username, password). Note
396
+ that even if the original URL contains credentials, this
397
+ function may return a different username and password.
398
+ """
399
+ url, netloc, _ = split_auth_netloc_from_url(original_url)
400
+
401
+ # Try to get credentials from original url
402
+ username, password = self._get_new_credentials(original_url)
403
+
404
+ # If credentials not found, use any stored credentials for this netloc.
405
+ # Do this if either the username or the password is missing.
406
+ # This accounts for the situation in which the user has specified
407
+ # the username in the index url, but the password comes from keyring.
408
+ if (username is None or password is None) and netloc in self.passwords:
409
+ un, pw = self.passwords[netloc]
410
+ # It is possible that the cached credentials are for a different username,
411
+ # in which case the cache should be ignored.
412
+ if username is None or username == un:
413
+ username, password = un, pw
414
+
415
+ if username is not None or password is not None:
416
+ # Convert the username and password if they're None, so that
417
+ # this netloc will show up as "cached" in the conditional above.
418
+ # Further, HTTPBasicAuth doesn't accept None, so it makes sense to
419
+ # cache the value that is going to be used.
420
+ username = username or ""
421
+ password = password or ""
422
+
423
+ # Store any acquired credentials.
424
+ self.passwords[netloc] = (username, password)
425
+
426
+ assert (
427
+ # Credentials were found
428
+ (username is not None and password is not None)
429
+ # Credentials were not found
430
+ or (username is None and password is None)
431
+ ), f"Could not load credentials from url: {original_url}"
432
+
433
+ return url, username, password
434
+
435
+ def __call__(self, req: Request) -> Request:
436
+ # Get credentials for this request
437
+ url, username, password = self._get_url_and_credentials(req.url)
438
+
439
+ # Set the url of the request to the url without any credentials
440
+ req.url = url
441
+
442
+ if username is not None and password is not None:
443
+ # Send the basic auth with this request
444
+ req = HTTPBasicAuth(username, password)(req)
445
+
446
+ # Attach a hook to handle 401 responses
447
+ req.register_hook("response", self.handle_401)
448
+
449
+ return req
450
+
451
+ # Factored out to allow for easy patching in tests
452
+ def _prompt_for_password(
453
+ self, netloc: str
454
+ ) -> Tuple[Optional[str], Optional[str], bool]:
455
+ username = ask_input(f"User for {netloc}: ") if self.prompting else None
456
+ if not username:
457
+ return None, None, False
458
+ if self.use_keyring:
459
+ auth = self._get_keyring_auth(netloc, username)
460
+ if auth and auth[0] is not None and auth[1] is not None:
461
+ return auth[0], auth[1], False
462
+ password = ask_password("Password: ")
463
+ return username, password, True
464
+
465
+ # Factored out to allow for easy patching in tests
466
+ def _should_save_password_to_keyring(self) -> bool:
467
+ if (
468
+ not self.prompting
469
+ or not self.use_keyring
470
+ or not self.keyring_provider.has_keyring
471
+ ):
472
+ return False
473
+ return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
474
+
475
+ def handle_401(self, resp: Response, **kwargs: Any) -> Response:
476
+ # We only care about 401 responses, anything else we want to just
477
+ # pass through the actual response
478
+ if resp.status_code != 401:
479
+ return resp
480
+
481
+ username, password = None, None
482
+
483
+ # Query the keyring for credentials:
484
+ if self.use_keyring:
485
+ username, password = self._get_new_credentials(
486
+ resp.url,
487
+ allow_netrc=False,
488
+ allow_keyring=True,
489
+ )
490
+
491
+ # We are not able to prompt the user so simply return the response
492
+ if not self.prompting and not username and not password:
493
+ return resp
494
+
495
+ parsed = urllib.parse.urlparse(resp.url)
496
+
497
+ # Prompt the user for a new username and password
498
+ save = False
499
+ if not username and not password:
500
+ username, password, save = self._prompt_for_password(parsed.netloc)
501
+
502
+ # Store the new username and password to use for future requests
503
+ self._credentials_to_save = None
504
+ if username is not None and password is not None:
505
+ self.passwords[parsed.netloc] = (username, password)
506
+
507
+ # Prompt to save the password to keyring
508
+ if save and self._should_save_password_to_keyring():
509
+ self._credentials_to_save = Credentials(
510
+ url=parsed.netloc,
511
+ username=username,
512
+ password=password,
513
+ )
514
+
515
+ # Consume content and release the original connection to allow our new
516
+ # request to reuse the same one.
517
+ # The result of the assignment isn't used, it's just needed to consume
518
+ # the content.
519
+ _ = resp.content
520
+ resp.raw.release_conn()
521
+
522
+ # Add our new username and password to the request
523
+ req = HTTPBasicAuth(username or "", password or "")(resp.request)
524
+ req.register_hook("response", self.warn_on_401)
525
+
526
+ # On successful request, save the credentials that were used to
527
+ # keyring. (Note that if the user responded "no" above, this member
528
+ # is not set and nothing will be saved.)
529
+ if self._credentials_to_save:
530
+ req.register_hook("response", self.save_credentials)
531
+
532
+ # Send our new request
533
+ new_resp = resp.connection.send(req, **kwargs)
534
+ new_resp.history.append(resp)
535
+
536
+ return new_resp
537
+
538
+ def warn_on_401(self, resp: Response, **kwargs: Any) -> None:
539
+ """Response callback to warn about incorrect credentials."""
540
+ if resp.status_code == 401:
541
+ logger.warning(
542
+ "401 Error, Credentials not correct for %s",
543
+ resp.request.url,
544
+ )
545
+
546
+ def save_credentials(self, resp: Response, **kwargs: Any) -> None:
547
+ """Response callback to save credentials on success."""
548
+ assert (
549
+ self.keyring_provider.has_keyring
550
+ ), "should never reach here without keyring"
551
+
552
+ creds = self._credentials_to_save
553
+ self._credentials_to_save = None
554
+ if creds and resp.status_code < 400:
555
+ try:
556
+ logger.info("Saving credentials to keyring")
557
+ self.keyring_provider.save_auth_info(
558
+ creds.url, creds.username, creds.password
559
+ )
560
+ except Exception:
561
+ logger.exception("Failed to save credentials")
.venv/lib/python3.11/site-packages/pip/_internal/network/cache.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HTTP cache implementation.
2
+ """
3
+
4
+ import os
5
+ from contextlib import contextmanager
6
+ from datetime import datetime
7
+ from typing import BinaryIO, Generator, Optional, Union
8
+
9
+ from pip._vendor.cachecontrol.cache import SeparateBodyBaseCache
10
+ from pip._vendor.cachecontrol.caches import SeparateBodyFileCache
11
+ from pip._vendor.requests.models import Response
12
+
13
+ from pip._internal.utils.filesystem import adjacent_tmp_file, replace
14
+ from pip._internal.utils.misc import ensure_dir
15
+
16
+
17
+ def is_from_cache(response: Response) -> bool:
18
+ return getattr(response, "from_cache", False)
19
+
20
+
21
+ @contextmanager
22
+ def suppressed_cache_errors() -> Generator[None, None, None]:
23
+ """If we can't access the cache then we can just skip caching and process
24
+ requests as if caching wasn't enabled.
25
+ """
26
+ try:
27
+ yield
28
+ except OSError:
29
+ pass
30
+
31
+
32
+ class SafeFileCache(SeparateBodyBaseCache):
33
+ """
34
+ A file based cache which is safe to use even when the target directory may
35
+ not be accessible or writable.
36
+
37
+ There is a race condition when two processes try to write and/or read the
38
+ same entry at the same time, since each entry consists of two separate
39
+ files (https://github.com/psf/cachecontrol/issues/324). We therefore have
40
+ additional logic that makes sure that both files to be present before
41
+ returning an entry; this fixes the read side of the race condition.
42
+
43
+ For the write side, we assume that the server will only ever return the
44
+ same data for the same URL, which ought to be the case for files pip is
45
+ downloading. PyPI does not have a mechanism to swap out a wheel for
46
+ another wheel, for example. If this assumption is not true, the
47
+ CacheControl issue will need to be fixed.
48
+ """
49
+
50
+ def __init__(self, directory: str) -> None:
51
+ assert directory is not None, "Cache directory must not be None."
52
+ super().__init__()
53
+ self.directory = directory
54
+
55
+ def _get_cache_path(self, name: str) -> str:
56
+ # From cachecontrol.caches.file_cache.FileCache._fn, brought into our
57
+ # class for backwards-compatibility and to avoid using a non-public
58
+ # method.
59
+ hashed = SeparateBodyFileCache.encode(name)
60
+ parts = list(hashed[:5]) + [hashed]
61
+ return os.path.join(self.directory, *parts)
62
+
63
+ def get(self, key: str) -> Optional[bytes]:
64
+ # The cache entry is only valid if both metadata and body exist.
65
+ metadata_path = self._get_cache_path(key)
66
+ body_path = metadata_path + ".body"
67
+ if not (os.path.exists(metadata_path) and os.path.exists(body_path)):
68
+ return None
69
+ with suppressed_cache_errors():
70
+ with open(metadata_path, "rb") as f:
71
+ return f.read()
72
+
73
+ def _write(self, path: str, data: bytes) -> None:
74
+ with suppressed_cache_errors():
75
+ ensure_dir(os.path.dirname(path))
76
+
77
+ with adjacent_tmp_file(path) as f:
78
+ f.write(data)
79
+
80
+ replace(f.name, path)
81
+
82
+ def set(
83
+ self, key: str, value: bytes, expires: Union[int, datetime, None] = None
84
+ ) -> None:
85
+ path = self._get_cache_path(key)
86
+ self._write(path, value)
87
+
88
+ def delete(self, key: str) -> None:
89
+ path = self._get_cache_path(key)
90
+ with suppressed_cache_errors():
91
+ os.remove(path)
92
+ with suppressed_cache_errors():
93
+ os.remove(path + ".body")
94
+
95
+ def get_body(self, key: str) -> Optional[BinaryIO]:
96
+ # The cache entry is only valid if both metadata and body exist.
97
+ metadata_path = self._get_cache_path(key)
98
+ body_path = metadata_path + ".body"
99
+ if not (os.path.exists(metadata_path) and os.path.exists(body_path)):
100
+ return None
101
+ with suppressed_cache_errors():
102
+ return open(body_path, "rb")
103
+
104
+ def set_body(self, key: str, body: bytes) -> None:
105
+ path = self._get_cache_path(key) + ".body"
106
+ self._write(path, body)
.venv/lib/python3.11/site-packages/pip/_internal/network/download.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Download files with progress indicators.
2
+ """
3
+ import email.message
4
+ import logging
5
+ import mimetypes
6
+ import os
7
+ from typing import Iterable, Optional, Tuple
8
+
9
+ from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
10
+
11
+ from pip._internal.cli.progress_bars import get_download_progress_renderer
12
+ from pip._internal.exceptions import NetworkConnectionError
13
+ from pip._internal.models.index import PyPI
14
+ from pip._internal.models.link import Link
15
+ from pip._internal.network.cache import is_from_cache
16
+ from pip._internal.network.session import PipSession
17
+ from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
18
+ from pip._internal.utils.misc import format_size, redact_auth_from_url, splitext
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def _get_http_response_size(resp: Response) -> Optional[int]:
24
+ try:
25
+ return int(resp.headers["content-length"])
26
+ except (ValueError, KeyError, TypeError):
27
+ return None
28
+
29
+
30
+ def _prepare_download(
31
+ resp: Response,
32
+ link: Link,
33
+ progress_bar: str,
34
+ ) -> Iterable[bytes]:
35
+ total_length = _get_http_response_size(resp)
36
+
37
+ if link.netloc == PyPI.file_storage_domain:
38
+ url = link.show_url
39
+ else:
40
+ url = link.url_without_fragment
41
+
42
+ logged_url = redact_auth_from_url(url)
43
+
44
+ if total_length:
45
+ logged_url = f"{logged_url} ({format_size(total_length)})"
46
+
47
+ if is_from_cache(resp):
48
+ logger.info("Using cached %s", logged_url)
49
+ else:
50
+ logger.info("Downloading %s", logged_url)
51
+
52
+ if logger.getEffectiveLevel() > logging.INFO:
53
+ show_progress = False
54
+ elif is_from_cache(resp):
55
+ show_progress = False
56
+ elif not total_length:
57
+ show_progress = True
58
+ elif total_length > (40 * 1000):
59
+ show_progress = True
60
+ else:
61
+ show_progress = False
62
+
63
+ chunks = response_chunks(resp, CONTENT_CHUNK_SIZE)
64
+
65
+ if not show_progress:
66
+ return chunks
67
+
68
+ renderer = get_download_progress_renderer(bar_type=progress_bar, size=total_length)
69
+ return renderer(chunks)
70
+
71
+
72
+ def sanitize_content_filename(filename: str) -> str:
73
+ """
74
+ Sanitize the "filename" value from a Content-Disposition header.
75
+ """
76
+ return os.path.basename(filename)
77
+
78
+
79
+ def parse_content_disposition(content_disposition: str, default_filename: str) -> str:
80
+ """
81
+ Parse the "filename" value from a Content-Disposition header, and
82
+ return the default filename if the result is empty.
83
+ """
84
+ m = email.message.Message()
85
+ m["content-type"] = content_disposition
86
+ filename = m.get_param("filename")
87
+ if filename:
88
+ # We need to sanitize the filename to prevent directory traversal
89
+ # in case the filename contains ".." path parts.
90
+ filename = sanitize_content_filename(str(filename))
91
+ return filename or default_filename
92
+
93
+
94
+ def _get_http_response_filename(resp: Response, link: Link) -> str:
95
+ """Get an ideal filename from the given HTTP response, falling back to
96
+ the link filename if not provided.
97
+ """
98
+ filename = link.filename # fallback
99
+ # Have a look at the Content-Disposition header for a better guess
100
+ content_disposition = resp.headers.get("content-disposition")
101
+ if content_disposition:
102
+ filename = parse_content_disposition(content_disposition, filename)
103
+ ext: Optional[str] = splitext(filename)[1]
104
+ if not ext:
105
+ ext = mimetypes.guess_extension(resp.headers.get("content-type", ""))
106
+ if ext:
107
+ filename += ext
108
+ if not ext and link.url != resp.url:
109
+ ext = os.path.splitext(resp.url)[1]
110
+ if ext:
111
+ filename += ext
112
+ return filename
113
+
114
+
115
+ def _http_get_download(session: PipSession, link: Link) -> Response:
116
+ target_url = link.url.split("#", 1)[0]
117
+ resp = session.get(target_url, headers=HEADERS, stream=True)
118
+ raise_for_status(resp)
119
+ return resp
120
+
121
+
122
+ class Downloader:
123
+ def __init__(
124
+ self,
125
+ session: PipSession,
126
+ progress_bar: str,
127
+ ) -> None:
128
+ self._session = session
129
+ self._progress_bar = progress_bar
130
+
131
+ def __call__(self, link: Link, location: str) -> Tuple[str, str]:
132
+ """Download the file given by link into location."""
133
+ try:
134
+ resp = _http_get_download(self._session, link)
135
+ except NetworkConnectionError as e:
136
+ assert e.response is not None
137
+ logger.critical(
138
+ "HTTP error %s while getting %s", e.response.status_code, link
139
+ )
140
+ raise
141
+
142
+ filename = _get_http_response_filename(resp, link)
143
+ filepath = os.path.join(location, filename)
144
+
145
+ chunks = _prepare_download(resp, link, self._progress_bar)
146
+ with open(filepath, "wb") as content_file:
147
+ for chunk in chunks:
148
+ content_file.write(chunk)
149
+ content_type = resp.headers.get("Content-Type", "")
150
+ return filepath, content_type
151
+
152
+
153
+ class BatchDownloader:
154
+ def __init__(
155
+ self,
156
+ session: PipSession,
157
+ progress_bar: str,
158
+ ) -> None:
159
+ self._session = session
160
+ self._progress_bar = progress_bar
161
+
162
+ def __call__(
163
+ self, links: Iterable[Link], location: str
164
+ ) -> Iterable[Tuple[Link, Tuple[str, str]]]:
165
+ """Download the files given by links into location."""
166
+ for link in links:
167
+ try:
168
+ resp = _http_get_download(self._session, link)
169
+ except NetworkConnectionError as e:
170
+ assert e.response is not None
171
+ logger.critical(
172
+ "HTTP error %s while getting %s",
173
+ e.response.status_code,
174
+ link,
175
+ )
176
+ raise
177
+
178
+ filename = _get_http_response_filename(resp, link)
179
+ filepath = os.path.join(location, filename)
180
+
181
+ chunks = _prepare_download(resp, link, self._progress_bar)
182
+ with open(filepath, "wb") as content_file:
183
+ for chunk in chunks:
184
+ content_file.write(chunk)
185
+ content_type = resp.headers.get("Content-Type", "")
186
+ yield link, (filepath, content_type)
.venv/lib/python3.11/site-packages/pip/_internal/network/lazy_wheel.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Lazy ZIP over HTTP"""
2
+
3
+ __all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"]
4
+
5
+ from bisect import bisect_left, bisect_right
6
+ from contextlib import contextmanager
7
+ from tempfile import NamedTemporaryFile
8
+ from typing import Any, Dict, Generator, List, Optional, Tuple
9
+ from zipfile import BadZipFile, ZipFile
10
+
11
+ from pip._vendor.packaging.utils import canonicalize_name
12
+ from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
13
+
14
+ from pip._internal.metadata import BaseDistribution, MemoryWheel, get_wheel_distribution
15
+ from pip._internal.network.session import PipSession
16
+ from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
17
+
18
+
19
+ class HTTPRangeRequestUnsupported(Exception):
20
+ pass
21
+
22
+
23
+ def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistribution:
24
+ """Return a distribution object from the given wheel URL.
25
+
26
+ This uses HTTP range requests to only fetch the portion of the wheel
27
+ containing metadata, just enough for the object to be constructed.
28
+ If such requests are not supported, HTTPRangeRequestUnsupported
29
+ is raised.
30
+ """
31
+ with LazyZipOverHTTP(url, session) as zf:
32
+ # For read-only ZIP files, ZipFile only needs methods read,
33
+ # seek, seekable and tell, not the whole IO protocol.
34
+ wheel = MemoryWheel(zf.name, zf) # type: ignore
35
+ # After context manager exit, wheel.name
36
+ # is an invalid file by intention.
37
+ return get_wheel_distribution(wheel, canonicalize_name(name))
38
+
39
+
40
+ class LazyZipOverHTTP:
41
+ """File-like object mapped to a ZIP file over HTTP.
42
+
43
+ This uses HTTP range requests to lazily fetch the file's content,
44
+ which is supposed to be fed to ZipFile. If such requests are not
45
+ supported by the server, raise HTTPRangeRequestUnsupported
46
+ during initialization.
47
+ """
48
+
49
+ def __init__(
50
+ self, url: str, session: PipSession, chunk_size: int = CONTENT_CHUNK_SIZE
51
+ ) -> None:
52
+ head = session.head(url, headers=HEADERS)
53
+ raise_for_status(head)
54
+ assert head.status_code == 200
55
+ self._session, self._url, self._chunk_size = session, url, chunk_size
56
+ self._length = int(head.headers["Content-Length"])
57
+ self._file = NamedTemporaryFile()
58
+ self.truncate(self._length)
59
+ self._left: List[int] = []
60
+ self._right: List[int] = []
61
+ if "bytes" not in head.headers.get("Accept-Ranges", "none"):
62
+ raise HTTPRangeRequestUnsupported("range request is not supported")
63
+ self._check_zip()
64
+
65
+ @property
66
+ def mode(self) -> str:
67
+ """Opening mode, which is always rb."""
68
+ return "rb"
69
+
70
+ @property
71
+ def name(self) -> str:
72
+ """Path to the underlying file."""
73
+ return self._file.name
74
+
75
+ def seekable(self) -> bool:
76
+ """Return whether random access is supported, which is True."""
77
+ return True
78
+
79
+ def close(self) -> None:
80
+ """Close the file."""
81
+ self._file.close()
82
+
83
+ @property
84
+ def closed(self) -> bool:
85
+ """Whether the file is closed."""
86
+ return self._file.closed
87
+
88
+ def read(self, size: int = -1) -> bytes:
89
+ """Read up to size bytes from the object and return them.
90
+
91
+ As a convenience, if size is unspecified or -1,
92
+ all bytes until EOF are returned. Fewer than
93
+ size bytes may be returned if EOF is reached.
94
+ """
95
+ download_size = max(size, self._chunk_size)
96
+ start, length = self.tell(), self._length
97
+ stop = length if size < 0 else min(start + download_size, length)
98
+ start = max(0, stop - download_size)
99
+ self._download(start, stop - 1)
100
+ return self._file.read(size)
101
+
102
+ def readable(self) -> bool:
103
+ """Return whether the file is readable, which is True."""
104
+ return True
105
+
106
+ def seek(self, offset: int, whence: int = 0) -> int:
107
+ """Change stream position and return the new absolute position.
108
+
109
+ Seek to offset relative position indicated by whence:
110
+ * 0: Start of stream (the default). pos should be >= 0;
111
+ * 1: Current position - pos may be negative;
112
+ * 2: End of stream - pos usually negative.
113
+ """
114
+ return self._file.seek(offset, whence)
115
+
116
+ def tell(self) -> int:
117
+ """Return the current position."""
118
+ return self._file.tell()
119
+
120
+ def truncate(self, size: Optional[int] = None) -> int:
121
+ """Resize the stream to the given size in bytes.
122
+
123
+ If size is unspecified resize to the current position.
124
+ The current stream position isn't changed.
125
+
126
+ Return the new file size.
127
+ """
128
+ return self._file.truncate(size)
129
+
130
+ def writable(self) -> bool:
131
+ """Return False."""
132
+ return False
133
+
134
+ def __enter__(self) -> "LazyZipOverHTTP":
135
+ self._file.__enter__()
136
+ return self
137
+
138
+ def __exit__(self, *exc: Any) -> None:
139
+ self._file.__exit__(*exc)
140
+
141
+ @contextmanager
142
+ def _stay(self) -> Generator[None, None, None]:
143
+ """Return a context manager keeping the position.
144
+
145
+ At the end of the block, seek back to original position.
146
+ """
147
+ pos = self.tell()
148
+ try:
149
+ yield
150
+ finally:
151
+ self.seek(pos)
152
+
153
+ def _check_zip(self) -> None:
154
+ """Check and download until the file is a valid ZIP."""
155
+ end = self._length - 1
156
+ for start in reversed(range(0, end, self._chunk_size)):
157
+ self._download(start, end)
158
+ with self._stay():
159
+ try:
160
+ # For read-only ZIP files, ZipFile only needs
161
+ # methods read, seek, seekable and tell.
162
+ ZipFile(self) # type: ignore
163
+ except BadZipFile:
164
+ pass
165
+ else:
166
+ break
167
+
168
+ def _stream_response(
169
+ self, start: int, end: int, base_headers: Dict[str, str] = HEADERS
170
+ ) -> Response:
171
+ """Return HTTP response to a range request from start to end."""
172
+ headers = base_headers.copy()
173
+ headers["Range"] = f"bytes={start}-{end}"
174
+ # TODO: Get range requests to be correctly cached
175
+ headers["Cache-Control"] = "no-cache"
176
+ return self._session.get(self._url, headers=headers, stream=True)
177
+
178
+ def _merge(
179
+ self, start: int, end: int, left: int, right: int
180
+ ) -> Generator[Tuple[int, int], None, None]:
181
+ """Return a generator of intervals to be fetched.
182
+
183
+ Args:
184
+ start (int): Start of needed interval
185
+ end (int): End of needed interval
186
+ left (int): Index of first overlapping downloaded data
187
+ right (int): Index after last overlapping downloaded data
188
+ """
189
+ lslice, rslice = self._left[left:right], self._right[left:right]
190
+ i = start = min([start] + lslice[:1])
191
+ end = max([end] + rslice[-1:])
192
+ for j, k in zip(lslice, rslice):
193
+ if j > i:
194
+ yield i, j - 1
195
+ i = k + 1
196
+ if i <= end:
197
+ yield i, end
198
+ self._left[left:right], self._right[left:right] = [start], [end]
199
+
200
+ def _download(self, start: int, end: int) -> None:
201
+ """Download bytes from start to end inclusively."""
202
+ with self._stay():
203
+ left = bisect_left(self._right, start)
204
+ right = bisect_right(self._left, end)
205
+ for start, end in self._merge(start, end, left, right):
206
+ response = self._stream_response(start, end)
207
+ response.raise_for_status()
208
+ self.seek(start)
209
+ for chunk in response_chunks(response, self._chunk_size):
210
+ self._file.write(chunk)
.venv/lib/python3.11/site-packages/pip/_internal/network/session.py ADDED
@@ -0,0 +1,520 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """PipSession and supporting code, containing all pip-specific
2
+ network request configuration and behavior.
3
+ """
4
+
5
+ import email.utils
6
+ import io
7
+ import ipaddress
8
+ import json
9
+ import logging
10
+ import mimetypes
11
+ import os
12
+ import platform
13
+ import shutil
14
+ import subprocess
15
+ import sys
16
+ import urllib.parse
17
+ import warnings
18
+ from typing import (
19
+ TYPE_CHECKING,
20
+ Any,
21
+ Dict,
22
+ Generator,
23
+ List,
24
+ Mapping,
25
+ Optional,
26
+ Sequence,
27
+ Tuple,
28
+ Union,
29
+ )
30
+
31
+ from pip._vendor import requests, urllib3
32
+ from pip._vendor.cachecontrol import CacheControlAdapter as _BaseCacheControlAdapter
33
+ from pip._vendor.requests.adapters import DEFAULT_POOLBLOCK, BaseAdapter
34
+ from pip._vendor.requests.adapters import HTTPAdapter as _BaseHTTPAdapter
35
+ from pip._vendor.requests.models import PreparedRequest, Response
36
+ from pip._vendor.requests.structures import CaseInsensitiveDict
37
+ from pip._vendor.urllib3.connectionpool import ConnectionPool
38
+ from pip._vendor.urllib3.exceptions import InsecureRequestWarning
39
+
40
+ from pip import __version__
41
+ from pip._internal.metadata import get_default_environment
42
+ from pip._internal.models.link import Link
43
+ from pip._internal.network.auth import MultiDomainBasicAuth
44
+ from pip._internal.network.cache import SafeFileCache
45
+
46
+ # Import ssl from compat so the initial import occurs in only one place.
47
+ from pip._internal.utils.compat import has_tls
48
+ from pip._internal.utils.glibc import libc_ver
49
+ from pip._internal.utils.misc import build_url_from_netloc, parse_netloc
50
+ from pip._internal.utils.urls import url_to_path
51
+
52
+ if TYPE_CHECKING:
53
+ from ssl import SSLContext
54
+
55
+ from pip._vendor.urllib3.poolmanager import PoolManager
56
+
57
+
58
+ logger = logging.getLogger(__name__)
59
+
60
+ SecureOrigin = Tuple[str, str, Optional[Union[int, str]]]
61
+
62
+
63
+ # Ignore warning raised when using --trusted-host.
64
+ warnings.filterwarnings("ignore", category=InsecureRequestWarning)
65
+
66
+
67
+ SECURE_ORIGINS: List[SecureOrigin] = [
68
+ # protocol, hostname, port
69
+ # Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC)
70
+ ("https", "*", "*"),
71
+ ("*", "localhost", "*"),
72
+ ("*", "127.0.0.0/8", "*"),
73
+ ("*", "::1/128", "*"),
74
+ ("file", "*", None),
75
+ # ssh is always secure.
76
+ ("ssh", "*", "*"),
77
+ ]
78
+
79
+
80
+ # These are environment variables present when running under various
81
+ # CI systems. For each variable, some CI systems that use the variable
82
+ # are indicated. The collection was chosen so that for each of a number
83
+ # of popular systems, at least one of the environment variables is used.
84
+ # This list is used to provide some indication of and lower bound for
85
+ # CI traffic to PyPI. Thus, it is okay if the list is not comprehensive.
86
+ # For more background, see: https://github.com/pypa/pip/issues/5499
87
+ CI_ENVIRONMENT_VARIABLES = (
88
+ # Azure Pipelines
89
+ "BUILD_BUILDID",
90
+ # Jenkins
91
+ "BUILD_ID",
92
+ # AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI
93
+ "CI",
94
+ # Explicit environment variable.
95
+ "PIP_IS_CI",
96
+ )
97
+
98
+
99
+ def looks_like_ci() -> bool:
100
+ """
101
+ Return whether it looks like pip is running under CI.
102
+ """
103
+ # We don't use the method of checking for a tty (e.g. using isatty())
104
+ # because some CI systems mimic a tty (e.g. Travis CI). Thus that
105
+ # method doesn't provide definitive information in either direction.
106
+ return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES)
107
+
108
+
109
+ def user_agent() -> str:
110
+ """
111
+ Return a string representing the user agent.
112
+ """
113
+ data: Dict[str, Any] = {
114
+ "installer": {"name": "pip", "version": __version__},
115
+ "python": platform.python_version(),
116
+ "implementation": {
117
+ "name": platform.python_implementation(),
118
+ },
119
+ }
120
+
121
+ if data["implementation"]["name"] == "CPython":
122
+ data["implementation"]["version"] = platform.python_version()
123
+ elif data["implementation"]["name"] == "PyPy":
124
+ pypy_version_info = sys.pypy_version_info # type: ignore
125
+ if pypy_version_info.releaselevel == "final":
126
+ pypy_version_info = pypy_version_info[:3]
127
+ data["implementation"]["version"] = ".".join(
128
+ [str(x) for x in pypy_version_info]
129
+ )
130
+ elif data["implementation"]["name"] == "Jython":
131
+ # Complete Guess
132
+ data["implementation"]["version"] = platform.python_version()
133
+ elif data["implementation"]["name"] == "IronPython":
134
+ # Complete Guess
135
+ data["implementation"]["version"] = platform.python_version()
136
+
137
+ if sys.platform.startswith("linux"):
138
+ from pip._vendor import distro
139
+
140
+ linux_distribution = distro.name(), distro.version(), distro.codename()
141
+ distro_infos: Dict[str, Any] = dict(
142
+ filter(
143
+ lambda x: x[1],
144
+ zip(["name", "version", "id"], linux_distribution),
145
+ )
146
+ )
147
+ libc = dict(
148
+ filter(
149
+ lambda x: x[1],
150
+ zip(["lib", "version"], libc_ver()),
151
+ )
152
+ )
153
+ if libc:
154
+ distro_infos["libc"] = libc
155
+ if distro_infos:
156
+ data["distro"] = distro_infos
157
+
158
+ if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
159
+ data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
160
+
161
+ if platform.system():
162
+ data.setdefault("system", {})["name"] = platform.system()
163
+
164
+ if platform.release():
165
+ data.setdefault("system", {})["release"] = platform.release()
166
+
167
+ if platform.machine():
168
+ data["cpu"] = platform.machine()
169
+
170
+ if has_tls():
171
+ import _ssl as ssl
172
+
173
+ data["openssl_version"] = ssl.OPENSSL_VERSION
174
+
175
+ setuptools_dist = get_default_environment().get_distribution("setuptools")
176
+ if setuptools_dist is not None:
177
+ data["setuptools_version"] = str(setuptools_dist.version)
178
+
179
+ if shutil.which("rustc") is not None:
180
+ # If for any reason `rustc --version` fails, silently ignore it
181
+ try:
182
+ rustc_output = subprocess.check_output(
183
+ ["rustc", "--version"], stderr=subprocess.STDOUT, timeout=0.5
184
+ )
185
+ except Exception:
186
+ pass
187
+ else:
188
+ if rustc_output.startswith(b"rustc "):
189
+ # The format of `rustc --version` is:
190
+ # `b'rustc 1.52.1 (9bc8c42bb 2021-05-09)\n'`
191
+ # We extract just the middle (1.52.1) part
192
+ data["rustc_version"] = rustc_output.split(b" ")[1].decode()
193
+
194
+ # Use None rather than False so as not to give the impression that
195
+ # pip knows it is not being run under CI. Rather, it is a null or
196
+ # inconclusive result. Also, we include some value rather than no
197
+ # value to make it easier to know that the check has been run.
198
+ data["ci"] = True if looks_like_ci() else None
199
+
200
+ user_data = os.environ.get("PIP_USER_AGENT_USER_DATA")
201
+ if user_data is not None:
202
+ data["user_data"] = user_data
203
+
204
+ return "{data[installer][name]}/{data[installer][version]} {json}".format(
205
+ data=data,
206
+ json=json.dumps(data, separators=(",", ":"), sort_keys=True),
207
+ )
208
+
209
+
210
+ class LocalFSAdapter(BaseAdapter):
211
+ def send(
212
+ self,
213
+ request: PreparedRequest,
214
+ stream: bool = False,
215
+ timeout: Optional[Union[float, Tuple[float, float]]] = None,
216
+ verify: Union[bool, str] = True,
217
+ cert: Optional[Union[str, Tuple[str, str]]] = None,
218
+ proxies: Optional[Mapping[str, str]] = None,
219
+ ) -> Response:
220
+ pathname = url_to_path(request.url)
221
+
222
+ resp = Response()
223
+ resp.status_code = 200
224
+ resp.url = request.url
225
+
226
+ try:
227
+ stats = os.stat(pathname)
228
+ except OSError as exc:
229
+ # format the exception raised as a io.BytesIO object,
230
+ # to return a better error message:
231
+ resp.status_code = 404
232
+ resp.reason = type(exc).__name__
233
+ resp.raw = io.BytesIO(f"{resp.reason}: {exc}".encode("utf8"))
234
+ else:
235
+ modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
236
+ content_type = mimetypes.guess_type(pathname)[0] or "text/plain"
237
+ resp.headers = CaseInsensitiveDict(
238
+ {
239
+ "Content-Type": content_type,
240
+ "Content-Length": stats.st_size,
241
+ "Last-Modified": modified,
242
+ }
243
+ )
244
+
245
+ resp.raw = open(pathname, "rb")
246
+ resp.close = resp.raw.close
247
+
248
+ return resp
249
+
250
+ def close(self) -> None:
251
+ pass
252
+
253
+
254
+ class _SSLContextAdapterMixin:
255
+ """Mixin to add the ``ssl_context`` constructor argument to HTTP adapters.
256
+
257
+ The additional argument is forwarded directly to the pool manager. This allows us
258
+ to dynamically decide what SSL store to use at runtime, which is used to implement
259
+ the optional ``truststore`` backend.
260
+ """
261
+
262
+ def __init__(
263
+ self,
264
+ *,
265
+ ssl_context: Optional["SSLContext"] = None,
266
+ **kwargs: Any,
267
+ ) -> None:
268
+ self._ssl_context = ssl_context
269
+ super().__init__(**kwargs)
270
+
271
+ def init_poolmanager(
272
+ self,
273
+ connections: int,
274
+ maxsize: int,
275
+ block: bool = DEFAULT_POOLBLOCK,
276
+ **pool_kwargs: Any,
277
+ ) -> "PoolManager":
278
+ if self._ssl_context is not None:
279
+ pool_kwargs.setdefault("ssl_context", self._ssl_context)
280
+ return super().init_poolmanager( # type: ignore[misc]
281
+ connections=connections,
282
+ maxsize=maxsize,
283
+ block=block,
284
+ **pool_kwargs,
285
+ )
286
+
287
+
288
+ class HTTPAdapter(_SSLContextAdapterMixin, _BaseHTTPAdapter):
289
+ pass
290
+
291
+
292
+ class CacheControlAdapter(_SSLContextAdapterMixin, _BaseCacheControlAdapter):
293
+ pass
294
+
295
+
296
+ class InsecureHTTPAdapter(HTTPAdapter):
297
+ def cert_verify(
298
+ self,
299
+ conn: ConnectionPool,
300
+ url: str,
301
+ verify: Union[bool, str],
302
+ cert: Optional[Union[str, Tuple[str, str]]],
303
+ ) -> None:
304
+ super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
305
+
306
+
307
+ class InsecureCacheControlAdapter(CacheControlAdapter):
308
+ def cert_verify(
309
+ self,
310
+ conn: ConnectionPool,
311
+ url: str,
312
+ verify: Union[bool, str],
313
+ cert: Optional[Union[str, Tuple[str, str]]],
314
+ ) -> None:
315
+ super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
316
+
317
+
318
+ class PipSession(requests.Session):
319
+ timeout: Optional[int] = None
320
+
321
+ def __init__(
322
+ self,
323
+ *args: Any,
324
+ retries: int = 0,
325
+ cache: Optional[str] = None,
326
+ trusted_hosts: Sequence[str] = (),
327
+ index_urls: Optional[List[str]] = None,
328
+ ssl_context: Optional["SSLContext"] = None,
329
+ **kwargs: Any,
330
+ ) -> None:
331
+ """
332
+ :param trusted_hosts: Domains not to emit warnings for when not using
333
+ HTTPS.
334
+ """
335
+ super().__init__(*args, **kwargs)
336
+
337
+ # Namespace the attribute with "pip_" just in case to prevent
338
+ # possible conflicts with the base class.
339
+ self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = []
340
+
341
+ # Attach our User Agent to the request
342
+ self.headers["User-Agent"] = user_agent()
343
+
344
+ # Attach our Authentication handler to the session
345
+ self.auth = MultiDomainBasicAuth(index_urls=index_urls)
346
+
347
+ # Create our urllib3.Retry instance which will allow us to customize
348
+ # how we handle retries.
349
+ retries = urllib3.Retry(
350
+ # Set the total number of retries that a particular request can
351
+ # have.
352
+ total=retries,
353
+ # A 503 error from PyPI typically means that the Fastly -> Origin
354
+ # connection got interrupted in some way. A 503 error in general
355
+ # is typically considered a transient error so we'll go ahead and
356
+ # retry it.
357
+ # A 500 may indicate transient error in Amazon S3
358
+ # A 502 may be a transient error from a CDN like CloudFlare or CloudFront
359
+ # A 520 or 527 - may indicate transient error in CloudFlare
360
+ status_forcelist=[500, 502, 503, 520, 527],
361
+ # Add a small amount of back off between failed requests in
362
+ # order to prevent hammering the service.
363
+ backoff_factor=0.25,
364
+ ) # type: ignore
365
+
366
+ # Our Insecure HTTPAdapter disables HTTPS validation. It does not
367
+ # support caching so we'll use it for all http:// URLs.
368
+ # If caching is disabled, we will also use it for
369
+ # https:// hosts that we've marked as ignoring
370
+ # TLS errors for (trusted-hosts).
371
+ insecure_adapter = InsecureHTTPAdapter(max_retries=retries)
372
+
373
+ # We want to _only_ cache responses on securely fetched origins or when
374
+ # the host is specified as trusted. We do this because
375
+ # we can't validate the response of an insecurely/untrusted fetched
376
+ # origin, and we don't want someone to be able to poison the cache and
377
+ # require manual eviction from the cache to fix it.
378
+ if cache:
379
+ secure_adapter = CacheControlAdapter(
380
+ cache=SafeFileCache(cache),
381
+ max_retries=retries,
382
+ ssl_context=ssl_context,
383
+ )
384
+ self._trusted_host_adapter = InsecureCacheControlAdapter(
385
+ cache=SafeFileCache(cache),
386
+ max_retries=retries,
387
+ )
388
+ else:
389
+ secure_adapter = HTTPAdapter(max_retries=retries, ssl_context=ssl_context)
390
+ self._trusted_host_adapter = insecure_adapter
391
+
392
+ self.mount("https://", secure_adapter)
393
+ self.mount("http://", insecure_adapter)
394
+
395
+ # Enable file:// urls
396
+ self.mount("file://", LocalFSAdapter())
397
+
398
+ for host in trusted_hosts:
399
+ self.add_trusted_host(host, suppress_logging=True)
400
+
401
+ def update_index_urls(self, new_index_urls: List[str]) -> None:
402
+ """
403
+ :param new_index_urls: New index urls to update the authentication
404
+ handler with.
405
+ """
406
+ self.auth.index_urls = new_index_urls
407
+
408
+ def add_trusted_host(
409
+ self, host: str, source: Optional[str] = None, suppress_logging: bool = False
410
+ ) -> None:
411
+ """
412
+ :param host: It is okay to provide a host that has previously been
413
+ added.
414
+ :param source: An optional source string, for logging where the host
415
+ string came from.
416
+ """
417
+ if not suppress_logging:
418
+ msg = f"adding trusted host: {host!r}"
419
+ if source is not None:
420
+ msg += f" (from {source})"
421
+ logger.info(msg)
422
+
423
+ parsed_host, parsed_port = parse_netloc(host)
424
+ if parsed_host is None:
425
+ raise ValueError(f"Trusted host URL must include a host part: {host!r}")
426
+ if (parsed_host, parsed_port) not in self.pip_trusted_origins:
427
+ self.pip_trusted_origins.append((parsed_host, parsed_port))
428
+
429
+ self.mount(
430
+ build_url_from_netloc(host, scheme="http") + "/", self._trusted_host_adapter
431
+ )
432
+ self.mount(build_url_from_netloc(host) + "/", self._trusted_host_adapter)
433
+ if not parsed_port:
434
+ self.mount(
435
+ build_url_from_netloc(host, scheme="http") + ":",
436
+ self._trusted_host_adapter,
437
+ )
438
+ # Mount wildcard ports for the same host.
439
+ self.mount(build_url_from_netloc(host) + ":", self._trusted_host_adapter)
440
+
441
+ def iter_secure_origins(self) -> Generator[SecureOrigin, None, None]:
442
+ yield from SECURE_ORIGINS
443
+ for host, port in self.pip_trusted_origins:
444
+ yield ("*", host, "*" if port is None else port)
445
+
446
+ def is_secure_origin(self, location: Link) -> bool:
447
+ # Determine if this url used a secure transport mechanism
448
+ parsed = urllib.parse.urlparse(str(location))
449
+ origin_protocol, origin_host, origin_port = (
450
+ parsed.scheme,
451
+ parsed.hostname,
452
+ parsed.port,
453
+ )
454
+
455
+ # The protocol to use to see if the protocol matches.
456
+ # Don't count the repository type as part of the protocol: in
457
+ # cases such as "git+ssh", only use "ssh". (I.e., Only verify against
458
+ # the last scheme.)
459
+ origin_protocol = origin_protocol.rsplit("+", 1)[-1]
460
+
461
+ # Determine if our origin is a secure origin by looking through our
462
+ # hardcoded list of secure origins, as well as any additional ones
463
+ # configured on this PackageFinder instance.
464
+ for secure_origin in self.iter_secure_origins():
465
+ secure_protocol, secure_host, secure_port = secure_origin
466
+ if origin_protocol != secure_protocol and secure_protocol != "*":
467
+ continue
468
+
469
+ try:
470
+ addr = ipaddress.ip_address(origin_host or "")
471
+ network = ipaddress.ip_network(secure_host)
472
+ except ValueError:
473
+ # We don't have both a valid address or a valid network, so
474
+ # we'll check this origin against hostnames.
475
+ if (
476
+ origin_host
477
+ and origin_host.lower() != secure_host.lower()
478
+ and secure_host != "*"
479
+ ):
480
+ continue
481
+ else:
482
+ # We have a valid address and network, so see if the address
483
+ # is contained within the network.
484
+ if addr not in network:
485
+ continue
486
+
487
+ # Check to see if the port matches.
488
+ if (
489
+ origin_port != secure_port
490
+ and secure_port != "*"
491
+ and secure_port is not None
492
+ ):
493
+ continue
494
+
495
+ # If we've gotten here, then this origin matches the current
496
+ # secure origin and we should return True
497
+ return True
498
+
499
+ # If we've gotten to this point, then the origin isn't secure and we
500
+ # will not accept it as a valid location to search. We will however
501
+ # log a warning that we are ignoring it.
502
+ logger.warning(
503
+ "The repository located at %s is not a trusted or secure host and "
504
+ "is being ignored. If this repository is available via HTTPS we "
505
+ "recommend you use HTTPS instead, otherwise you may silence "
506
+ "this warning and allow it anyway with '--trusted-host %s'.",
507
+ origin_host,
508
+ origin_host,
509
+ )
510
+
511
+ return False
512
+
513
+ def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response:
514
+ # Allow setting a default timeout on a session
515
+ kwargs.setdefault("timeout", self.timeout)
516
+ # Allow setting a default proxies on a session
517
+ kwargs.setdefault("proxies", self.proxies)
518
+
519
+ # Dispatch the actual request
520
+ return super().request(method, url, *args, **kwargs)
.venv/lib/python3.11/site-packages/pip/_internal/network/utils.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Generator
2
+
3
+ from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
4
+
5
+ from pip._internal.exceptions import NetworkConnectionError
6
+
7
+ # The following comments and HTTP headers were originally added by
8
+ # Donald Stufft in git commit 22c562429a61bb77172039e480873fb239dd8c03.
9
+ #
10
+ # We use Accept-Encoding: identity here because requests defaults to
11
+ # accepting compressed responses. This breaks in a variety of ways
12
+ # depending on how the server is configured.
13
+ # - Some servers will notice that the file isn't a compressible file
14
+ # and will leave the file alone and with an empty Content-Encoding
15
+ # - Some servers will notice that the file is already compressed and
16
+ # will leave the file alone, adding a Content-Encoding: gzip header
17
+ # - Some servers won't notice anything at all and will take a file
18
+ # that's already been compressed and compress it again, and set
19
+ # the Content-Encoding: gzip header
20
+ # By setting this to request only the identity encoding we're hoping
21
+ # to eliminate the third case. Hopefully there does not exist a server
22
+ # which when given a file will notice it is already compressed and that
23
+ # you're not asking for a compressed file and will then decompress it
24
+ # before sending because if that's the case I don't think it'll ever be
25
+ # possible to make this work.
26
+ HEADERS: Dict[str, str] = {"Accept-Encoding": "identity"}
27
+
28
+
29
+ def raise_for_status(resp: Response) -> None:
30
+ http_error_msg = ""
31
+ if isinstance(resp.reason, bytes):
32
+ # We attempt to decode utf-8 first because some servers
33
+ # choose to localize their reason strings. If the string
34
+ # isn't utf-8, we fall back to iso-8859-1 for all other
35
+ # encodings.
36
+ try:
37
+ reason = resp.reason.decode("utf-8")
38
+ except UnicodeDecodeError:
39
+ reason = resp.reason.decode("iso-8859-1")
40
+ else:
41
+ reason = resp.reason
42
+
43
+ if 400 <= resp.status_code < 500:
44
+ http_error_msg = (
45
+ f"{resp.status_code} Client Error: {reason} for url: {resp.url}"
46
+ )
47
+
48
+ elif 500 <= resp.status_code < 600:
49
+ http_error_msg = (
50
+ f"{resp.status_code} Server Error: {reason} for url: {resp.url}"
51
+ )
52
+
53
+ if http_error_msg:
54
+ raise NetworkConnectionError(http_error_msg, response=resp)
55
+
56
+
57
+ def response_chunks(
58
+ response: Response, chunk_size: int = CONTENT_CHUNK_SIZE
59
+ ) -> Generator[bytes, None, None]:
60
+ """Given a requests Response, provide the data chunks."""
61
+ try:
62
+ # Special case for urllib3.
63
+ for chunk in response.raw.stream(
64
+ chunk_size,
65
+ # We use decode_content=False here because we don't
66
+ # want urllib3 to mess with the raw bytes we get
67
+ # from the server. If we decompress inside of
68
+ # urllib3 then we cannot verify the checksum
69
+ # because the checksum will be of the compressed
70
+ # file. This breakage will only occur if the
71
+ # server adds a Content-Encoding header, which
72
+ # depends on how the server was configured:
73
+ # - Some servers will notice that the file isn't a
74
+ # compressible file and will leave the file alone
75
+ # and with an empty Content-Encoding
76
+ # - Some servers will notice that the file is
77
+ # already compressed and will leave the file
78
+ # alone and will add a Content-Encoding: gzip
79
+ # header
80
+ # - Some servers won't notice anything at all and
81
+ # will take a file that's already been compressed
82
+ # and compress it again and set the
83
+ # Content-Encoding: gzip header
84
+ #
85
+ # By setting this not to decode automatically we
86
+ # hope to eliminate problems with the second case.
87
+ decode_content=False,
88
+ ):
89
+ yield chunk
90
+ except AttributeError:
91
+ # Standard file-like object.
92
+ while True:
93
+ chunk = response.raw.read(chunk_size)
94
+ if not chunk:
95
+ break
96
+ yield chunk
.venv/lib/python3.11/site-packages/pip/_internal/network/xmlrpc.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """xmlrpclib.Transport implementation
2
+ """
3
+
4
+ import logging
5
+ import urllib.parse
6
+ import xmlrpc.client
7
+ from typing import TYPE_CHECKING, Tuple
8
+
9
+ from pip._internal.exceptions import NetworkConnectionError
10
+ from pip._internal.network.session import PipSession
11
+ from pip._internal.network.utils import raise_for_status
12
+
13
+ if TYPE_CHECKING:
14
+ from xmlrpc.client import _HostType, _Marshallable
15
+
16
+ from _typeshed import SizedBuffer
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class PipXmlrpcTransport(xmlrpc.client.Transport):
22
+ """Provide a `xmlrpclib.Transport` implementation via a `PipSession`
23
+ object.
24
+ """
25
+
26
+ def __init__(
27
+ self, index_url: str, session: PipSession, use_datetime: bool = False
28
+ ) -> None:
29
+ super().__init__(use_datetime)
30
+ index_parts = urllib.parse.urlparse(index_url)
31
+ self._scheme = index_parts.scheme
32
+ self._session = session
33
+
34
+ def request(
35
+ self,
36
+ host: "_HostType",
37
+ handler: str,
38
+ request_body: "SizedBuffer",
39
+ verbose: bool = False,
40
+ ) -> Tuple["_Marshallable", ...]:
41
+ assert isinstance(host, str)
42
+ parts = (self._scheme, host, handler, None, None, None)
43
+ url = urllib.parse.urlunparse(parts)
44
+ try:
45
+ headers = {"Content-Type": "text/xml"}
46
+ response = self._session.post(
47
+ url,
48
+ data=request_body,
49
+ headers=headers,
50
+ stream=True,
51
+ )
52
+ raise_for_status(response)
53
+ self.verbose = verbose
54
+ return self.parse_response(response.raw)
55
+ except NetworkConnectionError as exc:
56
+ assert exc.response
57
+ logger.critical(
58
+ "HTTP error %s while getting %s",
59
+ exc.response.status_code,
60
+ url,
61
+ )
62
+ raise
.venv/lib/python3.11/site-packages/pip/_internal/utils/__init__.py ADDED
File without changes
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (192 Bytes). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/_jaraco_text.cpython-311.pyc ADDED
Binary file (4.76 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/_log.cpython-311.pyc ADDED
Binary file (2.01 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-311.pyc ADDED
Binary file (2.55 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/compat.cpython-311.pyc ADDED
Binary file (2.26 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-311.pyc ADDED
Binary file (6.75 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-311.pyc ADDED
Binary file (709 Bytes). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-311.pyc ADDED
Binary file (4.68 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-311.pyc ADDED
Binary file (3.71 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-311.pyc ADDED
Binary file (3.55 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/encoding.cpython-311.pyc ADDED
Binary file (2.32 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-311.pyc ADDED
Binary file (4.24 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-311.pyc ADDED
Binary file (8.22 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-311.pyc ADDED
Binary file (1.31 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-311.pyc ADDED
Binary file (2.6 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-311.pyc ADDED
Binary file (8.76 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/logging.cpython-311.pyc ADDED
Binary file (15.4 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/misc.cpython-311.pyc ADDED
Binary file (38.6 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/models.cpython-311.pyc ADDED
Binary file (2.93 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-311.pyc ADDED
Binary file (2.8 kB). View file
 
.venv/lib/python3.11/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-311.pyc ADDED
Binary file (4.86 kB). View file