BryanW commited on
Commit
e39883f
·
verified ·
1 Parent(s): 1d230da

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. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/_multiprocess/__pycache__/__init__.cpython-312.pyc +0 -0
  2. Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/urllib3-2.0.7.dist-info/licenses/LICENSE.txt +21 -0
  3. URSA/.venv_ursa/lib/python3.12/site-packages/pip/__pycache__/__init__.cpython-312.pyc +0 -0
  4. URSA/.venv_ursa/lib/python3.12/site-packages/pip/__pycache__/__main__.cpython-312.pyc +0 -0
  5. URSA/.venv_ursa/lib/python3.12/site-packages/pip/__pycache__/__pip-runner__.cpython-312.pyc +0 -0
  6. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/__init__.py +18 -0
  7. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/build_env.py +322 -0
  8. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/cache.py +290 -0
  9. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/__init__.py +132 -0
  10. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/cache.py +228 -0
  11. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/check.py +67 -0
  12. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/completion.py +130 -0
  13. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/debug.py +201 -0
  14. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/freeze.py +109 -0
  15. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/hash.py +59 -0
  16. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/index.py +139 -0
  17. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/install.py +784 -0
  18. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/list.py +375 -0
  19. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/search.py +172 -0
  20. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/show.py +224 -0
  21. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/wheel.py +182 -0
  22. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/configuration.py +383 -0
  23. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/exceptions.py +809 -0
  24. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/index/__init__.py +2 -0
  25. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/index/collector.py +494 -0
  26. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/index/package_finder.py +1029 -0
  27. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/index/sources.py +284 -0
  28. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/locations/__init__.py +456 -0
  29. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/locations/_distutils.py +172 -0
  30. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/locations/_sysconfig.py +214 -0
  31. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/locations/base.py +81 -0
  32. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/main.py +12 -0
  33. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/metadata/__init__.py +128 -0
  34. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/metadata/_json.py +86 -0
  35. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/metadata/base.py +688 -0
  36. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/metadata/pkg_resources.py +301 -0
  37. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/__init__.py +2 -0
  38. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/candidate.py +25 -0
  39. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/direct_url.py +224 -0
  40. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/format_control.py +78 -0
  41. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/index.py +28 -0
  42. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/installation_report.py +56 -0
  43. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/link.py +604 -0
  44. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/scheme.py +25 -0
  45. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/search_scope.py +127 -0
  46. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/selection_prefs.py +53 -0
  47. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/target_python.py +121 -0
  48. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/wheel.py +118 -0
  49. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/pyproject.py +185 -0
  50. URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/resolution/__init__.py +0 -0
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/_multiprocess/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (259 Bytes). View file
 
Prism/LLaDA/LLaDA_Prism/.venv/lib/python3.12/site-packages/urllib3-2.0.7.dist-info/licenses/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2008-2020 Andrey Petrov and contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
URSA/.venv_ursa/lib/python3.12/site-packages/pip/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (706 Bytes). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/pip/__pycache__/__main__.cpython-312.pyc ADDED
Binary file (860 Bytes). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/pip/__pycache__/__pip-runner__.cpython-312.pyc ADDED
Binary file (2.22 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+
3
+ from pip._internal.utils import _log
4
+
5
+ # init_logging() must be called before any call to logging.getLogger()
6
+ # which happens at import of most modules.
7
+ _log.init_logging()
8
+
9
+
10
+ def main(args: Optional[List[str]] = None) -> int:
11
+ """This is preserved for old console scripts that may still be referencing
12
+ it.
13
+
14
+ For additional details, see https://github.com/pypa/pip/issues/7498.
15
+ """
16
+ from pip._internal.utils.entrypoints import _wrapper
17
+
18
+ return _wrapper(args)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/build_env.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Build Environment used for isolation during sdist building
2
+ """
3
+
4
+ import logging
5
+ import os
6
+ import pathlib
7
+ import site
8
+ import sys
9
+ import textwrap
10
+ from collections import OrderedDict
11
+ from types import TracebackType
12
+ from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union
13
+
14
+ from pip._vendor.packaging.version import Version
15
+
16
+ from pip import __file__ as pip_location
17
+ from pip._internal.cli.spinners import open_spinner
18
+ from pip._internal.locations import get_platlib, get_purelib, get_scheme
19
+ from pip._internal.metadata import get_default_environment, get_environment
20
+ from pip._internal.utils.logging import VERBOSE
21
+ from pip._internal.utils.packaging import get_requirement
22
+ from pip._internal.utils.subprocess import call_subprocess
23
+ from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
24
+
25
+ if TYPE_CHECKING:
26
+ from pip._internal.index.package_finder import PackageFinder
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ def _dedup(a: str, b: str) -> Union[Tuple[str], Tuple[str, str]]:
32
+ return (a, b) if a != b else (a,)
33
+
34
+
35
+ class _Prefix:
36
+ def __init__(self, path: str) -> None:
37
+ self.path = path
38
+ self.setup = False
39
+ scheme = get_scheme("", prefix=path)
40
+ self.bin_dir = scheme.scripts
41
+ self.lib_dirs = _dedup(scheme.purelib, scheme.platlib)
42
+
43
+
44
+ def get_runnable_pip() -> str:
45
+ """Get a file to pass to a Python executable, to run the currently-running pip.
46
+
47
+ This is used to run a pip subprocess, for installing requirements into the build
48
+ environment.
49
+ """
50
+ source = pathlib.Path(pip_location).resolve().parent
51
+
52
+ if not source.is_dir():
53
+ # This would happen if someone is using pip from inside a zip file. In that
54
+ # case, we can use that directly.
55
+ return str(source)
56
+
57
+ return os.fsdecode(source / "__pip-runner__.py")
58
+
59
+
60
+ def _get_system_sitepackages() -> Set[str]:
61
+ """Get system site packages
62
+
63
+ Usually from site.getsitepackages,
64
+ but fallback on `get_purelib()/get_platlib()` if unavailable
65
+ (e.g. in a virtualenv created by virtualenv<20)
66
+
67
+ Returns normalized set of strings.
68
+ """
69
+ if hasattr(site, "getsitepackages"):
70
+ system_sites = site.getsitepackages()
71
+ else:
72
+ # virtualenv < 20 overwrites site.py without getsitepackages
73
+ # fallback on get_purelib/get_platlib.
74
+ # this is known to miss things, but shouldn't in the cases
75
+ # where getsitepackages() has been removed (inside a virtualenv)
76
+ system_sites = [get_purelib(), get_platlib()]
77
+ return {os.path.normcase(path) for path in system_sites}
78
+
79
+
80
+ class BuildEnvironment:
81
+ """Creates and manages an isolated environment to install build deps"""
82
+
83
+ def __init__(self) -> None:
84
+ temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True)
85
+
86
+ self._prefixes = OrderedDict(
87
+ (name, _Prefix(os.path.join(temp_dir.path, name)))
88
+ for name in ("normal", "overlay")
89
+ )
90
+
91
+ self._bin_dirs: List[str] = []
92
+ self._lib_dirs: List[str] = []
93
+ for prefix in reversed(list(self._prefixes.values())):
94
+ self._bin_dirs.append(prefix.bin_dir)
95
+ self._lib_dirs.extend(prefix.lib_dirs)
96
+
97
+ # Customize site to:
98
+ # - ensure .pth files are honored
99
+ # - prevent access to system site packages
100
+ system_sites = _get_system_sitepackages()
101
+
102
+ self._site_dir = os.path.join(temp_dir.path, "site")
103
+ if not os.path.exists(self._site_dir):
104
+ os.mkdir(self._site_dir)
105
+ with open(
106
+ os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8"
107
+ ) as fp:
108
+ fp.write(
109
+ textwrap.dedent(
110
+ """
111
+ import os, site, sys
112
+
113
+ # First, drop system-sites related paths.
114
+ original_sys_path = sys.path[:]
115
+ known_paths = set()
116
+ for path in {system_sites!r}:
117
+ site.addsitedir(path, known_paths=known_paths)
118
+ system_paths = set(
119
+ os.path.normcase(path)
120
+ for path in sys.path[len(original_sys_path):]
121
+ )
122
+ original_sys_path = [
123
+ path for path in original_sys_path
124
+ if os.path.normcase(path) not in system_paths
125
+ ]
126
+ sys.path = original_sys_path
127
+
128
+ # Second, add lib directories.
129
+ # ensuring .pth file are processed.
130
+ for path in {lib_dirs!r}:
131
+ assert not path in sys.path
132
+ site.addsitedir(path)
133
+ """
134
+ ).format(system_sites=system_sites, lib_dirs=self._lib_dirs)
135
+ )
136
+
137
+ def __enter__(self) -> None:
138
+ self._save_env = {
139
+ name: os.environ.get(name, None)
140
+ for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH")
141
+ }
142
+
143
+ path = self._bin_dirs[:]
144
+ old_path = self._save_env["PATH"]
145
+ if old_path:
146
+ path.extend(old_path.split(os.pathsep))
147
+
148
+ pythonpath = [self._site_dir]
149
+
150
+ os.environ.update(
151
+ {
152
+ "PATH": os.pathsep.join(path),
153
+ "PYTHONNOUSERSITE": "1",
154
+ "PYTHONPATH": os.pathsep.join(pythonpath),
155
+ }
156
+ )
157
+
158
+ def __exit__(
159
+ self,
160
+ exc_type: Optional[Type[BaseException]],
161
+ exc_val: Optional[BaseException],
162
+ exc_tb: Optional[TracebackType],
163
+ ) -> None:
164
+ for varname, old_value in self._save_env.items():
165
+ if old_value is None:
166
+ os.environ.pop(varname, None)
167
+ else:
168
+ os.environ[varname] = old_value
169
+
170
+ def check_requirements(
171
+ self, reqs: Iterable[str]
172
+ ) -> Tuple[Set[Tuple[str, str]], Set[str]]:
173
+ """Return 2 sets:
174
+ - conflicting requirements: set of (installed, wanted) reqs tuples
175
+ - missing requirements: set of reqs
176
+ """
177
+ missing = set()
178
+ conflicting = set()
179
+ if reqs:
180
+ env = (
181
+ get_environment(self._lib_dirs)
182
+ if hasattr(self, "_lib_dirs")
183
+ else get_default_environment()
184
+ )
185
+ for req_str in reqs:
186
+ req = get_requirement(req_str)
187
+ # We're explicitly evaluating with an empty extra value, since build
188
+ # environments are not provided any mechanism to select specific extras.
189
+ if req.marker is not None and not req.marker.evaluate({"extra": ""}):
190
+ continue
191
+ dist = env.get_distribution(req.name)
192
+ if not dist:
193
+ missing.add(req_str)
194
+ continue
195
+ if isinstance(dist.version, Version):
196
+ installed_req_str = f"{req.name}=={dist.version}"
197
+ else:
198
+ installed_req_str = f"{req.name}==={dist.version}"
199
+ if not req.specifier.contains(dist.version, prereleases=True):
200
+ conflicting.add((installed_req_str, req_str))
201
+ # FIXME: Consider direct URL?
202
+ return conflicting, missing
203
+
204
+ def install_requirements(
205
+ self,
206
+ finder: "PackageFinder",
207
+ requirements: Iterable[str],
208
+ prefix_as_string: str,
209
+ *,
210
+ kind: str,
211
+ ) -> None:
212
+ prefix = self._prefixes[prefix_as_string]
213
+ assert not prefix.setup
214
+ prefix.setup = True
215
+ if not requirements:
216
+ return
217
+ self._install_requirements(
218
+ get_runnable_pip(),
219
+ finder,
220
+ requirements,
221
+ prefix,
222
+ kind=kind,
223
+ )
224
+
225
+ @staticmethod
226
+ def _install_requirements(
227
+ pip_runnable: str,
228
+ finder: "PackageFinder",
229
+ requirements: Iterable[str],
230
+ prefix: _Prefix,
231
+ *,
232
+ kind: str,
233
+ ) -> None:
234
+ args: List[str] = [
235
+ sys.executable,
236
+ pip_runnable,
237
+ "install",
238
+ "--ignore-installed",
239
+ "--no-user",
240
+ "--prefix",
241
+ prefix.path,
242
+ "--no-warn-script-location",
243
+ "--disable-pip-version-check",
244
+ # The prefix specified two lines above, thus
245
+ # target from config file or env var should be ignored
246
+ "--target",
247
+ "",
248
+ ]
249
+ if logger.getEffectiveLevel() <= logging.DEBUG:
250
+ args.append("-vv")
251
+ elif logger.getEffectiveLevel() <= VERBOSE:
252
+ args.append("-v")
253
+ for format_control in ("no_binary", "only_binary"):
254
+ formats = getattr(finder.format_control, format_control)
255
+ args.extend(
256
+ (
257
+ "--" + format_control.replace("_", "-"),
258
+ ",".join(sorted(formats or {":none:"})),
259
+ )
260
+ )
261
+
262
+ index_urls = finder.index_urls
263
+ if index_urls:
264
+ args.extend(["-i", index_urls[0]])
265
+ for extra_index in index_urls[1:]:
266
+ args.extend(["--extra-index-url", extra_index])
267
+ else:
268
+ args.append("--no-index")
269
+ for link in finder.find_links:
270
+ args.extend(["--find-links", link])
271
+
272
+ if finder.proxy:
273
+ args.extend(["--proxy", finder.proxy])
274
+ for host in finder.trusted_hosts:
275
+ args.extend(["--trusted-host", host])
276
+ if finder.custom_cert:
277
+ args.extend(["--cert", finder.custom_cert])
278
+ if finder.client_cert:
279
+ args.extend(["--client-cert", finder.client_cert])
280
+ if finder.allow_all_prereleases:
281
+ args.append("--pre")
282
+ if finder.prefer_binary:
283
+ args.append("--prefer-binary")
284
+ args.append("--")
285
+ args.extend(requirements)
286
+ with open_spinner(f"Installing {kind}") as spinner:
287
+ call_subprocess(
288
+ args,
289
+ command_desc=f"pip subprocess to install {kind}",
290
+ spinner=spinner,
291
+ )
292
+
293
+
294
+ class NoOpBuildEnvironment(BuildEnvironment):
295
+ """A no-op drop-in replacement for BuildEnvironment"""
296
+
297
+ def __init__(self) -> None:
298
+ pass
299
+
300
+ def __enter__(self) -> None:
301
+ pass
302
+
303
+ def __exit__(
304
+ self,
305
+ exc_type: Optional[Type[BaseException]],
306
+ exc_val: Optional[BaseException],
307
+ exc_tb: Optional[TracebackType],
308
+ ) -> None:
309
+ pass
310
+
311
+ def cleanup(self) -> None:
312
+ pass
313
+
314
+ def install_requirements(
315
+ self,
316
+ finder: "PackageFinder",
317
+ requirements: Iterable[str],
318
+ prefix_as_string: str,
319
+ *,
320
+ kind: str,
321
+ ) -> None:
322
+ raise NotImplementedError()
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/cache.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Cache Management
2
+ """
3
+
4
+ import hashlib
5
+ import json
6
+ import logging
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version
12
+ from pip._vendor.packaging.utils import canonicalize_name
13
+
14
+ from pip._internal.exceptions import InvalidWheelFilename
15
+ from pip._internal.models.direct_url import DirectUrl
16
+ from pip._internal.models.link import Link
17
+ from pip._internal.models.wheel import Wheel
18
+ from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
19
+ from pip._internal.utils.urls import path_to_url
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ ORIGIN_JSON_NAME = "origin.json"
24
+
25
+
26
+ def _hash_dict(d: Dict[str, str]) -> str:
27
+ """Return a stable sha224 of a dictionary."""
28
+ s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
29
+ return hashlib.sha224(s.encode("ascii")).hexdigest()
30
+
31
+
32
+ class Cache:
33
+ """An abstract class - provides cache directories for data from links
34
+
35
+ :param cache_dir: The root of the cache.
36
+ """
37
+
38
+ def __init__(self, cache_dir: str) -> None:
39
+ super().__init__()
40
+ assert not cache_dir or os.path.isabs(cache_dir)
41
+ self.cache_dir = cache_dir or None
42
+
43
+ def _get_cache_path_parts(self, link: Link) -> List[str]:
44
+ """Get parts of part that must be os.path.joined with cache_dir"""
45
+
46
+ # We want to generate an url to use as our cache key, we don't want to
47
+ # just reuse the URL because it might have other items in the fragment
48
+ # and we don't care about those.
49
+ key_parts = {"url": link.url_without_fragment}
50
+ if link.hash_name is not None and link.hash is not None:
51
+ key_parts[link.hash_name] = link.hash
52
+ if link.subdirectory_fragment:
53
+ key_parts["subdirectory"] = link.subdirectory_fragment
54
+
55
+ # Include interpreter name, major and minor version in cache key
56
+ # to cope with ill-behaved sdists that build a different wheel
57
+ # depending on the python version their setup.py is being run on,
58
+ # and don't encode the difference in compatibility tags.
59
+ # https://github.com/pypa/pip/issues/7296
60
+ key_parts["interpreter_name"] = interpreter_name()
61
+ key_parts["interpreter_version"] = interpreter_version()
62
+
63
+ # Encode our key url with sha224, we'll use this because it has similar
64
+ # security properties to sha256, but with a shorter total output (and
65
+ # thus less secure). However the differences don't make a lot of
66
+ # difference for our use case here.
67
+ hashed = _hash_dict(key_parts)
68
+
69
+ # We want to nest the directories some to prevent having a ton of top
70
+ # level directories where we might run out of sub directories on some
71
+ # FS.
72
+ parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
73
+
74
+ return parts
75
+
76
+ def _get_candidates(self, link: Link, canonical_package_name: str) -> List[Any]:
77
+ can_not_cache = not self.cache_dir or not canonical_package_name or not link
78
+ if can_not_cache:
79
+ return []
80
+
81
+ path = self.get_path_for_link(link)
82
+ if os.path.isdir(path):
83
+ return [(candidate, path) for candidate in os.listdir(path)]
84
+ return []
85
+
86
+ def get_path_for_link(self, link: Link) -> str:
87
+ """Return a directory to store cached items in for link."""
88
+ raise NotImplementedError()
89
+
90
+ def get(
91
+ self,
92
+ link: Link,
93
+ package_name: Optional[str],
94
+ supported_tags: List[Tag],
95
+ ) -> Link:
96
+ """Returns a link to a cached item if it exists, otherwise returns the
97
+ passed link.
98
+ """
99
+ raise NotImplementedError()
100
+
101
+
102
+ class SimpleWheelCache(Cache):
103
+ """A cache of wheels for future installs."""
104
+
105
+ def __init__(self, cache_dir: str) -> None:
106
+ super().__init__(cache_dir)
107
+
108
+ def get_path_for_link(self, link: Link) -> str:
109
+ """Return a directory to store cached wheels for link
110
+
111
+ Because there are M wheels for any one sdist, we provide a directory
112
+ to cache them in, and then consult that directory when looking up
113
+ cache hits.
114
+
115
+ We only insert things into the cache if they have plausible version
116
+ numbers, so that we don't contaminate the cache with things that were
117
+ not unique. E.g. ./package might have dozens of installs done for it
118
+ and build a version of 0.0...and if we built and cached a wheel, we'd
119
+ end up using the same wheel even if the source has been edited.
120
+
121
+ :param link: The link of the sdist for which this will cache wheels.
122
+ """
123
+ parts = self._get_cache_path_parts(link)
124
+ assert self.cache_dir
125
+ # Store wheels within the root cache_dir
126
+ return os.path.join(self.cache_dir, "wheels", *parts)
127
+
128
+ def get(
129
+ self,
130
+ link: Link,
131
+ package_name: Optional[str],
132
+ supported_tags: List[Tag],
133
+ ) -> Link:
134
+ candidates = []
135
+
136
+ if not package_name:
137
+ return link
138
+
139
+ canonical_package_name = canonicalize_name(package_name)
140
+ for wheel_name, wheel_dir in self._get_candidates(link, canonical_package_name):
141
+ try:
142
+ wheel = Wheel(wheel_name)
143
+ except InvalidWheelFilename:
144
+ continue
145
+ if canonicalize_name(wheel.name) != canonical_package_name:
146
+ logger.debug(
147
+ "Ignoring cached wheel %s for %s as it "
148
+ "does not match the expected distribution name %s.",
149
+ wheel_name,
150
+ link,
151
+ package_name,
152
+ )
153
+ continue
154
+ if not wheel.supported(supported_tags):
155
+ # Built for a different python/arch/etc
156
+ continue
157
+ candidates.append(
158
+ (
159
+ wheel.support_index_min(supported_tags),
160
+ wheel_name,
161
+ wheel_dir,
162
+ )
163
+ )
164
+
165
+ if not candidates:
166
+ return link
167
+
168
+ _, wheel_name, wheel_dir = min(candidates)
169
+ return Link(path_to_url(os.path.join(wheel_dir, wheel_name)))
170
+
171
+
172
+ class EphemWheelCache(SimpleWheelCache):
173
+ """A SimpleWheelCache that creates it's own temporary cache directory"""
174
+
175
+ def __init__(self) -> None:
176
+ self._temp_dir = TempDirectory(
177
+ kind=tempdir_kinds.EPHEM_WHEEL_CACHE,
178
+ globally_managed=True,
179
+ )
180
+
181
+ super().__init__(self._temp_dir.path)
182
+
183
+
184
+ class CacheEntry:
185
+ def __init__(
186
+ self,
187
+ link: Link,
188
+ persistent: bool,
189
+ ):
190
+ self.link = link
191
+ self.persistent = persistent
192
+ self.origin: Optional[DirectUrl] = None
193
+ origin_direct_url_path = Path(self.link.file_path).parent / ORIGIN_JSON_NAME
194
+ if origin_direct_url_path.exists():
195
+ try:
196
+ self.origin = DirectUrl.from_json(
197
+ origin_direct_url_path.read_text(encoding="utf-8")
198
+ )
199
+ except Exception as e:
200
+ logger.warning(
201
+ "Ignoring invalid cache entry origin file %s for %s (%s)",
202
+ origin_direct_url_path,
203
+ link.filename,
204
+ e,
205
+ )
206
+
207
+
208
+ class WheelCache(Cache):
209
+ """Wraps EphemWheelCache and SimpleWheelCache into a single Cache
210
+
211
+ This Cache allows for gracefully degradation, using the ephem wheel cache
212
+ when a certain link is not found in the simple wheel cache first.
213
+ """
214
+
215
+ def __init__(self, cache_dir: str) -> None:
216
+ super().__init__(cache_dir)
217
+ self._wheel_cache = SimpleWheelCache(cache_dir)
218
+ self._ephem_cache = EphemWheelCache()
219
+
220
+ def get_path_for_link(self, link: Link) -> str:
221
+ return self._wheel_cache.get_path_for_link(link)
222
+
223
+ def get_ephem_path_for_link(self, link: Link) -> str:
224
+ return self._ephem_cache.get_path_for_link(link)
225
+
226
+ def get(
227
+ self,
228
+ link: Link,
229
+ package_name: Optional[str],
230
+ supported_tags: List[Tag],
231
+ ) -> Link:
232
+ cache_entry = self.get_cache_entry(link, package_name, supported_tags)
233
+ if cache_entry is None:
234
+ return link
235
+ return cache_entry.link
236
+
237
+ def get_cache_entry(
238
+ self,
239
+ link: Link,
240
+ package_name: Optional[str],
241
+ supported_tags: List[Tag],
242
+ ) -> Optional[CacheEntry]:
243
+ """Returns a CacheEntry with a link to a cached item if it exists or
244
+ None. The cache entry indicates if the item was found in the persistent
245
+ or ephemeral cache.
246
+ """
247
+ retval = self._wheel_cache.get(
248
+ link=link,
249
+ package_name=package_name,
250
+ supported_tags=supported_tags,
251
+ )
252
+ if retval is not link:
253
+ return CacheEntry(retval, persistent=True)
254
+
255
+ retval = self._ephem_cache.get(
256
+ link=link,
257
+ package_name=package_name,
258
+ supported_tags=supported_tags,
259
+ )
260
+ if retval is not link:
261
+ return CacheEntry(retval, persistent=False)
262
+
263
+ return None
264
+
265
+ @staticmethod
266
+ def record_download_origin(cache_dir: str, download_info: DirectUrl) -> None:
267
+ origin_path = Path(cache_dir) / ORIGIN_JSON_NAME
268
+ if origin_path.exists():
269
+ try:
270
+ origin = DirectUrl.from_json(origin_path.read_text(encoding="utf-8"))
271
+ except Exception as e:
272
+ logger.warning(
273
+ "Could not read origin file %s in cache entry (%s). "
274
+ "Will attempt to overwrite it.",
275
+ origin_path,
276
+ e,
277
+ )
278
+ else:
279
+ # TODO: use DirectUrl.equivalent when
280
+ # https://github.com/pypa/pip/pull/10564 is merged.
281
+ if origin.url != download_info.url:
282
+ logger.warning(
283
+ "Origin URL %s in cache entry %s does not match download URL "
284
+ "%s. This is likely a pip bug or a cache corruption issue. "
285
+ "Will overwrite it with the new value.",
286
+ origin.url,
287
+ cache_dir,
288
+ download_info.url,
289
+ )
290
+ origin_path.write_text(download_info.to_json(), encoding="utf-8")
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/__init__.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Package containing all pip commands
3
+ """
4
+
5
+ import importlib
6
+ from collections import namedtuple
7
+ from typing import Any, Dict, Optional
8
+
9
+ from pip._internal.cli.base_command import Command
10
+
11
+ CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")
12
+
13
+ # This dictionary does a bunch of heavy lifting for help output:
14
+ # - Enables avoiding additional (costly) imports for presenting `--help`.
15
+ # - The ordering matters for help display.
16
+ #
17
+ # Even though the module path starts with the same "pip._internal.commands"
18
+ # prefix, the full path makes testing easier (specifically when modifying
19
+ # `commands_dict` in test setup / teardown).
20
+ commands_dict: Dict[str, CommandInfo] = {
21
+ "install": CommandInfo(
22
+ "pip._internal.commands.install",
23
+ "InstallCommand",
24
+ "Install packages.",
25
+ ),
26
+ "download": CommandInfo(
27
+ "pip._internal.commands.download",
28
+ "DownloadCommand",
29
+ "Download packages.",
30
+ ),
31
+ "uninstall": CommandInfo(
32
+ "pip._internal.commands.uninstall",
33
+ "UninstallCommand",
34
+ "Uninstall packages.",
35
+ ),
36
+ "freeze": CommandInfo(
37
+ "pip._internal.commands.freeze",
38
+ "FreezeCommand",
39
+ "Output installed packages in requirements format.",
40
+ ),
41
+ "inspect": CommandInfo(
42
+ "pip._internal.commands.inspect",
43
+ "InspectCommand",
44
+ "Inspect the python environment.",
45
+ ),
46
+ "list": CommandInfo(
47
+ "pip._internal.commands.list",
48
+ "ListCommand",
49
+ "List installed packages.",
50
+ ),
51
+ "show": CommandInfo(
52
+ "pip._internal.commands.show",
53
+ "ShowCommand",
54
+ "Show information about installed packages.",
55
+ ),
56
+ "check": CommandInfo(
57
+ "pip._internal.commands.check",
58
+ "CheckCommand",
59
+ "Verify installed packages have compatible dependencies.",
60
+ ),
61
+ "config": CommandInfo(
62
+ "pip._internal.commands.configuration",
63
+ "ConfigurationCommand",
64
+ "Manage local and global configuration.",
65
+ ),
66
+ "search": CommandInfo(
67
+ "pip._internal.commands.search",
68
+ "SearchCommand",
69
+ "Search PyPI for packages.",
70
+ ),
71
+ "cache": CommandInfo(
72
+ "pip._internal.commands.cache",
73
+ "CacheCommand",
74
+ "Inspect and manage pip's wheel cache.",
75
+ ),
76
+ "index": CommandInfo(
77
+ "pip._internal.commands.index",
78
+ "IndexCommand",
79
+ "Inspect information available from package indexes.",
80
+ ),
81
+ "wheel": CommandInfo(
82
+ "pip._internal.commands.wheel",
83
+ "WheelCommand",
84
+ "Build wheels from your requirements.",
85
+ ),
86
+ "hash": CommandInfo(
87
+ "pip._internal.commands.hash",
88
+ "HashCommand",
89
+ "Compute hashes of package archives.",
90
+ ),
91
+ "completion": CommandInfo(
92
+ "pip._internal.commands.completion",
93
+ "CompletionCommand",
94
+ "A helper command used for command completion.",
95
+ ),
96
+ "debug": CommandInfo(
97
+ "pip._internal.commands.debug",
98
+ "DebugCommand",
99
+ "Show information useful for debugging.",
100
+ ),
101
+ "help": CommandInfo(
102
+ "pip._internal.commands.help",
103
+ "HelpCommand",
104
+ "Show help for commands.",
105
+ ),
106
+ }
107
+
108
+
109
+ def create_command(name: str, **kwargs: Any) -> Command:
110
+ """
111
+ Create an instance of the Command class with the given name.
112
+ """
113
+ module_path, class_name, summary = commands_dict[name]
114
+ module = importlib.import_module(module_path)
115
+ command_class = getattr(module, class_name)
116
+ command = command_class(name=name, summary=summary, **kwargs)
117
+
118
+ return command
119
+
120
+
121
+ def get_similar_commands(name: str) -> Optional[str]:
122
+ """Command name auto-correct."""
123
+ from difflib import get_close_matches
124
+
125
+ name = name.lower()
126
+
127
+ close_commands = get_close_matches(name, commands_dict.keys())
128
+
129
+ if close_commands:
130
+ return close_commands[0]
131
+ else:
132
+ return None
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/cache.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import textwrap
3
+ from optparse import Values
4
+ from typing import Any, List
5
+
6
+ from pip._internal.cli.base_command import Command
7
+ from pip._internal.cli.status_codes import ERROR, SUCCESS
8
+ from pip._internal.exceptions import CommandError, PipError
9
+ from pip._internal.utils import filesystem
10
+ from pip._internal.utils.logging import getLogger
11
+ from pip._internal.utils.misc import format_size
12
+
13
+ logger = getLogger(__name__)
14
+
15
+
16
+ class CacheCommand(Command):
17
+ """
18
+ Inspect and manage pip's wheel cache.
19
+
20
+ Subcommands:
21
+
22
+ - dir: Show the cache directory.
23
+ - info: Show information about the cache.
24
+ - list: List filenames of packages stored in the cache.
25
+ - remove: Remove one or more package from the cache.
26
+ - purge: Remove all items from the cache.
27
+
28
+ ``<pattern>`` can be a glob expression or a package name.
29
+ """
30
+
31
+ ignore_require_venv = True
32
+ usage = """
33
+ %prog dir
34
+ %prog info
35
+ %prog list [<pattern>] [--format=[human, abspath]]
36
+ %prog remove <pattern>
37
+ %prog purge
38
+ """
39
+
40
+ def add_options(self) -> None:
41
+ self.cmd_opts.add_option(
42
+ "--format",
43
+ action="store",
44
+ dest="list_format",
45
+ default="human",
46
+ choices=("human", "abspath"),
47
+ help="Select the output format among: human (default) or abspath",
48
+ )
49
+
50
+ self.parser.insert_option_group(0, self.cmd_opts)
51
+
52
+ def run(self, options: Values, args: List[str]) -> int:
53
+ handlers = {
54
+ "dir": self.get_cache_dir,
55
+ "info": self.get_cache_info,
56
+ "list": self.list_cache_items,
57
+ "remove": self.remove_cache_items,
58
+ "purge": self.purge_cache,
59
+ }
60
+
61
+ if not options.cache_dir:
62
+ logger.error("pip cache commands can not function since cache is disabled.")
63
+ return ERROR
64
+
65
+ # Determine action
66
+ if not args or args[0] not in handlers:
67
+ logger.error(
68
+ "Need an action (%s) to perform.",
69
+ ", ".join(sorted(handlers)),
70
+ )
71
+ return ERROR
72
+
73
+ action = args[0]
74
+
75
+ # Error handling happens here, not in the action-handlers.
76
+ try:
77
+ handlers[action](options, args[1:])
78
+ except PipError as e:
79
+ logger.error(e.args[0])
80
+ return ERROR
81
+
82
+ return SUCCESS
83
+
84
+ def get_cache_dir(self, options: Values, args: List[Any]) -> None:
85
+ if args:
86
+ raise CommandError("Too many arguments")
87
+
88
+ logger.info(options.cache_dir)
89
+
90
+ def get_cache_info(self, options: Values, args: List[Any]) -> None:
91
+ if args:
92
+ raise CommandError("Too many arguments")
93
+
94
+ num_http_files = len(self._find_http_files(options))
95
+ num_packages = len(self._find_wheels(options, "*"))
96
+
97
+ http_cache_location = self._cache_dir(options, "http-v2")
98
+ old_http_cache_location = self._cache_dir(options, "http")
99
+ wheels_cache_location = self._cache_dir(options, "wheels")
100
+ http_cache_size = filesystem.format_size(
101
+ filesystem.directory_size(http_cache_location)
102
+ + filesystem.directory_size(old_http_cache_location)
103
+ )
104
+ wheels_cache_size = filesystem.format_directory_size(wheels_cache_location)
105
+
106
+ message = (
107
+ textwrap.dedent(
108
+ """
109
+ Package index page cache location (pip v23.3+): {http_cache_location}
110
+ Package index page cache location (older pips): {old_http_cache_location}
111
+ Package index page cache size: {http_cache_size}
112
+ Number of HTTP files: {num_http_files}
113
+ Locally built wheels location: {wheels_cache_location}
114
+ Locally built wheels size: {wheels_cache_size}
115
+ Number of locally built wheels: {package_count}
116
+ """ # noqa: E501
117
+ )
118
+ .format(
119
+ http_cache_location=http_cache_location,
120
+ old_http_cache_location=old_http_cache_location,
121
+ http_cache_size=http_cache_size,
122
+ num_http_files=num_http_files,
123
+ wheels_cache_location=wheels_cache_location,
124
+ package_count=num_packages,
125
+ wheels_cache_size=wheels_cache_size,
126
+ )
127
+ .strip()
128
+ )
129
+
130
+ logger.info(message)
131
+
132
+ def list_cache_items(self, options: Values, args: List[Any]) -> None:
133
+ if len(args) > 1:
134
+ raise CommandError("Too many arguments")
135
+
136
+ if args:
137
+ pattern = args[0]
138
+ else:
139
+ pattern = "*"
140
+
141
+ files = self._find_wheels(options, pattern)
142
+ if options.list_format == "human":
143
+ self.format_for_human(files)
144
+ else:
145
+ self.format_for_abspath(files)
146
+
147
+ def format_for_human(self, files: List[str]) -> None:
148
+ if not files:
149
+ logger.info("No locally built wheels cached.")
150
+ return
151
+
152
+ results = []
153
+ for filename in files:
154
+ wheel = os.path.basename(filename)
155
+ size = filesystem.format_file_size(filename)
156
+ results.append(f" - {wheel} ({size})")
157
+ logger.info("Cache contents:\n")
158
+ logger.info("\n".join(sorted(results)))
159
+
160
+ def format_for_abspath(self, files: List[str]) -> None:
161
+ if files:
162
+ logger.info("\n".join(sorted(files)))
163
+
164
+ def remove_cache_items(self, options: Values, args: List[Any]) -> None:
165
+ if len(args) > 1:
166
+ raise CommandError("Too many arguments")
167
+
168
+ if not args:
169
+ raise CommandError("Please provide a pattern")
170
+
171
+ files = self._find_wheels(options, args[0])
172
+
173
+ no_matching_msg = "No matching packages"
174
+ if args[0] == "*":
175
+ # Only fetch http files if no specific pattern given
176
+ files += self._find_http_files(options)
177
+ else:
178
+ # Add the pattern to the log message
179
+ no_matching_msg += f' for pattern "{args[0]}"'
180
+
181
+ if not files:
182
+ logger.warning(no_matching_msg)
183
+
184
+ bytes_removed = 0
185
+ for filename in files:
186
+ bytes_removed += os.stat(filename).st_size
187
+ os.unlink(filename)
188
+ logger.verbose("Removed %s", filename)
189
+ logger.info("Files removed: %s (%s)", len(files), format_size(bytes_removed))
190
+
191
+ def purge_cache(self, options: Values, args: List[Any]) -> None:
192
+ if args:
193
+ raise CommandError("Too many arguments")
194
+
195
+ return self.remove_cache_items(options, ["*"])
196
+
197
+ def _cache_dir(self, options: Values, subdir: str) -> str:
198
+ return os.path.join(options.cache_dir, subdir)
199
+
200
+ def _find_http_files(self, options: Values) -> List[str]:
201
+ old_http_dir = self._cache_dir(options, "http")
202
+ new_http_dir = self._cache_dir(options, "http-v2")
203
+ return filesystem.find_files(old_http_dir, "*") + filesystem.find_files(
204
+ new_http_dir, "*"
205
+ )
206
+
207
+ def _find_wheels(self, options: Values, pattern: str) -> List[str]:
208
+ wheel_dir = self._cache_dir(options, "wheels")
209
+
210
+ # The wheel filename format, as specified in PEP 427, is:
211
+ # {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl
212
+ #
213
+ # Additionally, non-alphanumeric values in the distribution are
214
+ # normalized to underscores (_), meaning hyphens can never occur
215
+ # before `-{version}`.
216
+ #
217
+ # Given that information:
218
+ # - If the pattern we're given contains a hyphen (-), the user is
219
+ # providing at least the version. Thus, we can just append `*.whl`
220
+ # to match the rest of it.
221
+ # - If the pattern we're given doesn't contain a hyphen (-), the
222
+ # user is only providing the name. Thus, we append `-*.whl` to
223
+ # match the hyphen before the version, followed by anything else.
224
+ #
225
+ # PEP 427: https://www.python.org/dev/peps/pep-0427/
226
+ pattern = pattern + ("*.whl" if "-" in pattern else "-*.whl")
227
+
228
+ return filesystem.find_files(wheel_dir, pattern)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/check.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from optparse import Values
3
+ from typing import List
4
+
5
+ from pip._internal.cli.base_command import Command
6
+ from pip._internal.cli.status_codes import ERROR, SUCCESS
7
+ from pip._internal.metadata import get_default_environment
8
+ from pip._internal.operations.check import (
9
+ check_package_set,
10
+ check_unsupported,
11
+ create_package_set_from_installed,
12
+ )
13
+ from pip._internal.utils.compatibility_tags import get_supported
14
+ from pip._internal.utils.misc import write_output
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class CheckCommand(Command):
20
+ """Verify installed packages have compatible dependencies."""
21
+
22
+ ignore_require_venv = True
23
+ usage = """
24
+ %prog [options]"""
25
+
26
+ def run(self, options: Values, args: List[str]) -> int:
27
+ package_set, parsing_probs = create_package_set_from_installed()
28
+ missing, conflicting = check_package_set(package_set)
29
+ unsupported = list(
30
+ check_unsupported(
31
+ get_default_environment().iter_installed_distributions(),
32
+ get_supported(),
33
+ )
34
+ )
35
+
36
+ for project_name in missing:
37
+ version = package_set[project_name].version
38
+ for dependency in missing[project_name]:
39
+ write_output(
40
+ "%s %s requires %s, which is not installed.",
41
+ project_name,
42
+ version,
43
+ dependency[0],
44
+ )
45
+
46
+ for project_name in conflicting:
47
+ version = package_set[project_name].version
48
+ for dep_name, dep_version, req in conflicting[project_name]:
49
+ write_output(
50
+ "%s %s has requirement %s, but you have %s %s.",
51
+ project_name,
52
+ version,
53
+ req,
54
+ dep_name,
55
+ dep_version,
56
+ )
57
+ for package in unsupported:
58
+ write_output(
59
+ "%s %s is not supported on this platform",
60
+ package.raw_name,
61
+ package.version,
62
+ )
63
+ if missing or conflicting or parsing_probs or unsupported:
64
+ return ERROR
65
+ else:
66
+ write_output("No broken requirements found.")
67
+ return SUCCESS
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/completion.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import textwrap
3
+ from optparse import Values
4
+ from typing import List
5
+
6
+ from pip._internal.cli.base_command import Command
7
+ from pip._internal.cli.status_codes import SUCCESS
8
+ from pip._internal.utils.misc import get_prog
9
+
10
+ BASE_COMPLETION = """
11
+ # pip {shell} completion start{script}# pip {shell} completion end
12
+ """
13
+
14
+ COMPLETION_SCRIPTS = {
15
+ "bash": """
16
+ _pip_completion()
17
+ {{
18
+ COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\
19
+ COMP_CWORD=$COMP_CWORD \\
20
+ PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) )
21
+ }}
22
+ complete -o default -F _pip_completion {prog}
23
+ """,
24
+ "zsh": """
25
+ #compdef -P pip[0-9.]#
26
+ __pip() {{
27
+ compadd $( COMP_WORDS="$words[*]" \\
28
+ COMP_CWORD=$((CURRENT-1)) \\
29
+ PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null )
30
+ }}
31
+ if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
32
+ # autoload from fpath, call function directly
33
+ __pip "$@"
34
+ else
35
+ # eval/source/. command, register function for later
36
+ compdef __pip -P 'pip[0-9.]#'
37
+ fi
38
+ """,
39
+ "fish": """
40
+ function __fish_complete_pip
41
+ set -lx COMP_WORDS (commandline -o) ""
42
+ set -lx COMP_CWORD ( \\
43
+ math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
44
+ )
45
+ set -lx PIP_AUTO_COMPLETE 1
46
+ string split \\ -- (eval $COMP_WORDS[1])
47
+ end
48
+ complete -fa "(__fish_complete_pip)" -c {prog}
49
+ """,
50
+ "powershell": """
51
+ if ((Test-Path Function:\\TabExpansion) -and -not `
52
+ (Test-Path Function:\\_pip_completeBackup)) {{
53
+ Rename-Item Function:\\TabExpansion _pip_completeBackup
54
+ }}
55
+ function TabExpansion($line, $lastWord) {{
56
+ $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()
57
+ if ($lastBlock.StartsWith("{prog} ")) {{
58
+ $Env:COMP_WORDS=$lastBlock
59
+ $Env:COMP_CWORD=$lastBlock.Split().Length - 1
60
+ $Env:PIP_AUTO_COMPLETE=1
61
+ (& {prog}).Split()
62
+ Remove-Item Env:COMP_WORDS
63
+ Remove-Item Env:COMP_CWORD
64
+ Remove-Item Env:PIP_AUTO_COMPLETE
65
+ }}
66
+ elseif (Test-Path Function:\\_pip_completeBackup) {{
67
+ # Fall back on existing tab expansion
68
+ _pip_completeBackup $line $lastWord
69
+ }}
70
+ }}
71
+ """,
72
+ }
73
+
74
+
75
+ class CompletionCommand(Command):
76
+ """A helper command to be used for command completion."""
77
+
78
+ ignore_require_venv = True
79
+
80
+ def add_options(self) -> None:
81
+ self.cmd_opts.add_option(
82
+ "--bash",
83
+ "-b",
84
+ action="store_const",
85
+ const="bash",
86
+ dest="shell",
87
+ help="Emit completion code for bash",
88
+ )
89
+ self.cmd_opts.add_option(
90
+ "--zsh",
91
+ "-z",
92
+ action="store_const",
93
+ const="zsh",
94
+ dest="shell",
95
+ help="Emit completion code for zsh",
96
+ )
97
+ self.cmd_opts.add_option(
98
+ "--fish",
99
+ "-f",
100
+ action="store_const",
101
+ const="fish",
102
+ dest="shell",
103
+ help="Emit completion code for fish",
104
+ )
105
+ self.cmd_opts.add_option(
106
+ "--powershell",
107
+ "-p",
108
+ action="store_const",
109
+ const="powershell",
110
+ dest="shell",
111
+ help="Emit completion code for powershell",
112
+ )
113
+
114
+ self.parser.insert_option_group(0, self.cmd_opts)
115
+
116
+ def run(self, options: Values, args: List[str]) -> int:
117
+ """Prints the completion code of the given shell"""
118
+ shells = COMPLETION_SCRIPTS.keys()
119
+ shell_options = ["--" + shell for shell in sorted(shells)]
120
+ if options.shell in shells:
121
+ script = textwrap.dedent(
122
+ COMPLETION_SCRIPTS.get(options.shell, "").format(prog=get_prog())
123
+ )
124
+ print(BASE_COMPLETION.format(script=script, shell=options.shell))
125
+ return SUCCESS
126
+ else:
127
+ sys.stderr.write(
128
+ "ERROR: You must pass {}\n".format(" or ".join(shell_options))
129
+ )
130
+ return SUCCESS
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/debug.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import locale
2
+ import logging
3
+ import os
4
+ import sys
5
+ from optparse import Values
6
+ from types import ModuleType
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ import pip._vendor
10
+ from pip._vendor.certifi import where
11
+ from pip._vendor.packaging.version import parse as parse_version
12
+
13
+ from pip._internal.cli import cmdoptions
14
+ from pip._internal.cli.base_command import Command
15
+ from pip._internal.cli.cmdoptions import make_target_python
16
+ from pip._internal.cli.status_codes import SUCCESS
17
+ from pip._internal.configuration import Configuration
18
+ from pip._internal.metadata import get_environment
19
+ from pip._internal.utils.compat import open_text_resource
20
+ from pip._internal.utils.logging import indent_log
21
+ from pip._internal.utils.misc import get_pip_version
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ def show_value(name: str, value: Any) -> None:
27
+ logger.info("%s: %s", name, value)
28
+
29
+
30
+ def show_sys_implementation() -> None:
31
+ logger.info("sys.implementation:")
32
+ implementation_name = sys.implementation.name
33
+ with indent_log():
34
+ show_value("name", implementation_name)
35
+
36
+
37
+ def create_vendor_txt_map() -> Dict[str, str]:
38
+ with open_text_resource("pip._vendor", "vendor.txt") as f:
39
+ # Purge non version specifying lines.
40
+ # Also, remove any space prefix or suffixes (including comments).
41
+ lines = [
42
+ line.strip().split(" ", 1)[0] for line in f.readlines() if "==" in line
43
+ ]
44
+
45
+ # Transform into "module" -> version dict.
46
+ return dict(line.split("==", 1) for line in lines)
47
+
48
+
49
+ def get_module_from_module_name(module_name: str) -> Optional[ModuleType]:
50
+ # Module name can be uppercase in vendor.txt for some reason...
51
+ module_name = module_name.lower().replace("-", "_")
52
+ # PATCH: setuptools is actually only pkg_resources.
53
+ if module_name == "setuptools":
54
+ module_name = "pkg_resources"
55
+
56
+ try:
57
+ __import__(f"pip._vendor.{module_name}", globals(), locals(), level=0)
58
+ return getattr(pip._vendor, module_name)
59
+ except ImportError:
60
+ # We allow 'truststore' to fail to import due
61
+ # to being unavailable on Python 3.9 and earlier.
62
+ if module_name == "truststore" and sys.version_info < (3, 10):
63
+ return None
64
+ raise
65
+
66
+
67
+ def get_vendor_version_from_module(module_name: str) -> Optional[str]:
68
+ module = get_module_from_module_name(module_name)
69
+ version = getattr(module, "__version__", None)
70
+
71
+ if module and not version:
72
+ # Try to find version in debundled module info.
73
+ assert module.__file__ is not None
74
+ env = get_environment([os.path.dirname(module.__file__)])
75
+ dist = env.get_distribution(module_name)
76
+ if dist:
77
+ version = str(dist.version)
78
+
79
+ return version
80
+
81
+
82
+ def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None:
83
+ """Log the actual version and print extra info if there is
84
+ a conflict or if the actual version could not be imported.
85
+ """
86
+ for module_name, expected_version in vendor_txt_versions.items():
87
+ extra_message = ""
88
+ actual_version = get_vendor_version_from_module(module_name)
89
+ if not actual_version:
90
+ extra_message = (
91
+ " (Unable to locate actual module version, using"
92
+ " vendor.txt specified version)"
93
+ )
94
+ actual_version = expected_version
95
+ elif parse_version(actual_version) != parse_version(expected_version):
96
+ extra_message = (
97
+ " (CONFLICT: vendor.txt suggests version should"
98
+ f" be {expected_version})"
99
+ )
100
+ logger.info("%s==%s%s", module_name, actual_version, extra_message)
101
+
102
+
103
+ def show_vendor_versions() -> None:
104
+ logger.info("vendored library versions:")
105
+
106
+ vendor_txt_versions = create_vendor_txt_map()
107
+ with indent_log():
108
+ show_actual_vendor_versions(vendor_txt_versions)
109
+
110
+
111
+ def show_tags(options: Values) -> None:
112
+ tag_limit = 10
113
+
114
+ target_python = make_target_python(options)
115
+ tags = target_python.get_sorted_tags()
116
+
117
+ # Display the target options that were explicitly provided.
118
+ formatted_target = target_python.format_given()
119
+ suffix = ""
120
+ if formatted_target:
121
+ suffix = f" (target: {formatted_target})"
122
+
123
+ msg = f"Compatible tags: {len(tags)}{suffix}"
124
+ logger.info(msg)
125
+
126
+ if options.verbose < 1 and len(tags) > tag_limit:
127
+ tags_limited = True
128
+ tags = tags[:tag_limit]
129
+ else:
130
+ tags_limited = False
131
+
132
+ with indent_log():
133
+ for tag in tags:
134
+ logger.info(str(tag))
135
+
136
+ if tags_limited:
137
+ msg = f"...\n[First {tag_limit} tags shown. Pass --verbose to show all.]"
138
+ logger.info(msg)
139
+
140
+
141
+ def ca_bundle_info(config: Configuration) -> str:
142
+ levels = {key.split(".", 1)[0] for key, _ in config.items()}
143
+ if not levels:
144
+ return "Not specified"
145
+
146
+ levels_that_override_global = ["install", "wheel", "download"]
147
+ global_overriding_level = [
148
+ level for level in levels if level in levels_that_override_global
149
+ ]
150
+ if not global_overriding_level:
151
+ return "global"
152
+
153
+ if "global" in levels:
154
+ levels.remove("global")
155
+ return ", ".join(levels)
156
+
157
+
158
+ class DebugCommand(Command):
159
+ """
160
+ Display debug information.
161
+ """
162
+
163
+ usage = """
164
+ %prog <options>"""
165
+ ignore_require_venv = True
166
+
167
+ def add_options(self) -> None:
168
+ cmdoptions.add_target_python_options(self.cmd_opts)
169
+ self.parser.insert_option_group(0, self.cmd_opts)
170
+ self.parser.config.load()
171
+
172
+ def run(self, options: Values, args: List[str]) -> int:
173
+ logger.warning(
174
+ "This command is only meant for debugging. "
175
+ "Do not use this with automation for parsing and getting these "
176
+ "details, since the output and options of this command may "
177
+ "change without notice."
178
+ )
179
+ show_value("pip version", get_pip_version())
180
+ show_value("sys.version", sys.version)
181
+ show_value("sys.executable", sys.executable)
182
+ show_value("sys.getdefaultencoding", sys.getdefaultencoding())
183
+ show_value("sys.getfilesystemencoding", sys.getfilesystemencoding())
184
+ show_value(
185
+ "locale.getpreferredencoding",
186
+ locale.getpreferredencoding(),
187
+ )
188
+ show_value("sys.platform", sys.platform)
189
+ show_sys_implementation()
190
+
191
+ show_value("'cert' config value", ca_bundle_info(self.parser.config))
192
+ show_value("REQUESTS_CA_BUNDLE", os.environ.get("REQUESTS_CA_BUNDLE"))
193
+ show_value("CURL_CA_BUNDLE", os.environ.get("CURL_CA_BUNDLE"))
194
+ show_value("pip._vendor.certifi.where()", where())
195
+ show_value("pip._vendor.DEBUNDLED", pip._vendor.DEBUNDLED)
196
+
197
+ show_vendor_versions()
198
+
199
+ show_tags(options)
200
+
201
+ return SUCCESS
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/freeze.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from optparse import Values
3
+ from typing import AbstractSet, List
4
+
5
+ from pip._internal.cli import cmdoptions
6
+ from pip._internal.cli.base_command import Command
7
+ from pip._internal.cli.status_codes import SUCCESS
8
+ from pip._internal.operations.freeze import freeze
9
+ from pip._internal.utils.compat import stdlib_pkgs
10
+
11
+
12
+ def _should_suppress_build_backends() -> bool:
13
+ return sys.version_info < (3, 12)
14
+
15
+
16
+ def _dev_pkgs() -> AbstractSet[str]:
17
+ pkgs = {"pip"}
18
+
19
+ if _should_suppress_build_backends():
20
+ pkgs |= {"setuptools", "distribute", "wheel"}
21
+
22
+ return pkgs
23
+
24
+
25
+ class FreezeCommand(Command):
26
+ """
27
+ Output installed packages in requirements format.
28
+
29
+ packages are listed in a case-insensitive sorted order.
30
+ """
31
+
32
+ ignore_require_venv = True
33
+ usage = """
34
+ %prog [options]"""
35
+ log_streams = ("ext://sys.stderr", "ext://sys.stderr")
36
+
37
+ def add_options(self) -> None:
38
+ self.cmd_opts.add_option(
39
+ "-r",
40
+ "--requirement",
41
+ dest="requirements",
42
+ action="append",
43
+ default=[],
44
+ metavar="file",
45
+ help=(
46
+ "Use the order in the given requirements file and its "
47
+ "comments when generating output. This option can be "
48
+ "used multiple times."
49
+ ),
50
+ )
51
+ self.cmd_opts.add_option(
52
+ "-l",
53
+ "--local",
54
+ dest="local",
55
+ action="store_true",
56
+ default=False,
57
+ help=(
58
+ "If in a virtualenv that has global access, do not output "
59
+ "globally-installed packages."
60
+ ),
61
+ )
62
+ self.cmd_opts.add_option(
63
+ "--user",
64
+ dest="user",
65
+ action="store_true",
66
+ default=False,
67
+ help="Only output packages installed in user-site.",
68
+ )
69
+ self.cmd_opts.add_option(cmdoptions.list_path())
70
+ self.cmd_opts.add_option(
71
+ "--all",
72
+ dest="freeze_all",
73
+ action="store_true",
74
+ help=(
75
+ "Do not skip these packages in the output:"
76
+ " {}".format(", ".join(_dev_pkgs()))
77
+ ),
78
+ )
79
+ self.cmd_opts.add_option(
80
+ "--exclude-editable",
81
+ dest="exclude_editable",
82
+ action="store_true",
83
+ help="Exclude editable package from output.",
84
+ )
85
+ self.cmd_opts.add_option(cmdoptions.list_exclude())
86
+
87
+ self.parser.insert_option_group(0, self.cmd_opts)
88
+
89
+ def run(self, options: Values, args: List[str]) -> int:
90
+ skip = set(stdlib_pkgs)
91
+ if not options.freeze_all:
92
+ skip.update(_dev_pkgs())
93
+
94
+ if options.excludes:
95
+ skip.update(options.excludes)
96
+
97
+ cmdoptions.check_list_path_option(options)
98
+
99
+ for line in freeze(
100
+ requirement=options.requirements,
101
+ local_only=options.local,
102
+ user_only=options.user,
103
+ paths=options.path,
104
+ isolated=options.isolated_mode,
105
+ skip=skip,
106
+ exclude_editable=options.exclude_editable,
107
+ ):
108
+ sys.stdout.write(line + "\n")
109
+ return SUCCESS
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/hash.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib
2
+ import logging
3
+ import sys
4
+ from optparse import Values
5
+ from typing import List
6
+
7
+ from pip._internal.cli.base_command import Command
8
+ from pip._internal.cli.status_codes import ERROR, SUCCESS
9
+ from pip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES
10
+ from pip._internal.utils.misc import read_chunks, write_output
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class HashCommand(Command):
16
+ """
17
+ Compute a hash of a local package archive.
18
+
19
+ These can be used with --hash in a requirements file to do repeatable
20
+ installs.
21
+ """
22
+
23
+ usage = "%prog [options] <file> ..."
24
+ ignore_require_venv = True
25
+
26
+ def add_options(self) -> None:
27
+ self.cmd_opts.add_option(
28
+ "-a",
29
+ "--algorithm",
30
+ dest="algorithm",
31
+ choices=STRONG_HASHES,
32
+ action="store",
33
+ default=FAVORITE_HASH,
34
+ help="The hash algorithm to use: one of {}".format(
35
+ ", ".join(STRONG_HASHES)
36
+ ),
37
+ )
38
+ self.parser.insert_option_group(0, self.cmd_opts)
39
+
40
+ def run(self, options: Values, args: List[str]) -> int:
41
+ if not args:
42
+ self.parser.print_usage(sys.stderr)
43
+ return ERROR
44
+
45
+ algorithm = options.algorithm
46
+ for path in args:
47
+ write_output(
48
+ "%s:\n--hash=%s:%s", path, algorithm, _hash_of_file(path, algorithm)
49
+ )
50
+ return SUCCESS
51
+
52
+
53
+ def _hash_of_file(path: str, algorithm: str) -> str:
54
+ """Return the hash digest of a file."""
55
+ with open(path, "rb") as archive:
56
+ hash = hashlib.new(algorithm)
57
+ for chunk in read_chunks(archive):
58
+ hash.update(chunk)
59
+ return hash.hexdigest()
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/index.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from optparse import Values
3
+ from typing import Any, Iterable, List, Optional
4
+
5
+ from pip._vendor.packaging.version import Version
6
+
7
+ from pip._internal.cli import cmdoptions
8
+ from pip._internal.cli.req_command import IndexGroupCommand
9
+ from pip._internal.cli.status_codes import ERROR, SUCCESS
10
+ from pip._internal.commands.search import print_dist_installation_info
11
+ from pip._internal.exceptions import CommandError, DistributionNotFound, PipError
12
+ from pip._internal.index.collector import LinkCollector
13
+ from pip._internal.index.package_finder import PackageFinder
14
+ from pip._internal.models.selection_prefs import SelectionPreferences
15
+ from pip._internal.models.target_python import TargetPython
16
+ from pip._internal.network.session import PipSession
17
+ from pip._internal.utils.misc import write_output
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class IndexCommand(IndexGroupCommand):
23
+ """
24
+ Inspect information available from package indexes.
25
+ """
26
+
27
+ ignore_require_venv = True
28
+ usage = """
29
+ %prog versions <package>
30
+ """
31
+
32
+ def add_options(self) -> None:
33
+ cmdoptions.add_target_python_options(self.cmd_opts)
34
+
35
+ self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
36
+ self.cmd_opts.add_option(cmdoptions.pre())
37
+ self.cmd_opts.add_option(cmdoptions.no_binary())
38
+ self.cmd_opts.add_option(cmdoptions.only_binary())
39
+
40
+ index_opts = cmdoptions.make_option_group(
41
+ cmdoptions.index_group,
42
+ self.parser,
43
+ )
44
+
45
+ self.parser.insert_option_group(0, index_opts)
46
+ self.parser.insert_option_group(0, self.cmd_opts)
47
+
48
+ def run(self, options: Values, args: List[str]) -> int:
49
+ handlers = {
50
+ "versions": self.get_available_package_versions,
51
+ }
52
+
53
+ logger.warning(
54
+ "pip index is currently an experimental command. "
55
+ "It may be removed/changed in a future release "
56
+ "without prior warning."
57
+ )
58
+
59
+ # Determine action
60
+ if not args or args[0] not in handlers:
61
+ logger.error(
62
+ "Need an action (%s) to perform.",
63
+ ", ".join(sorted(handlers)),
64
+ )
65
+ return ERROR
66
+
67
+ action = args[0]
68
+
69
+ # Error handling happens here, not in the action-handlers.
70
+ try:
71
+ handlers[action](options, args[1:])
72
+ except PipError as e:
73
+ logger.error(e.args[0])
74
+ return ERROR
75
+
76
+ return SUCCESS
77
+
78
+ def _build_package_finder(
79
+ self,
80
+ options: Values,
81
+ session: PipSession,
82
+ target_python: Optional[TargetPython] = None,
83
+ ignore_requires_python: Optional[bool] = None,
84
+ ) -> PackageFinder:
85
+ """
86
+ Create a package finder appropriate to the index command.
87
+ """
88
+ link_collector = LinkCollector.create(session, options=options)
89
+
90
+ # Pass allow_yanked=False to ignore yanked versions.
91
+ selection_prefs = SelectionPreferences(
92
+ allow_yanked=False,
93
+ allow_all_prereleases=options.pre,
94
+ ignore_requires_python=ignore_requires_python,
95
+ )
96
+
97
+ return PackageFinder.create(
98
+ link_collector=link_collector,
99
+ selection_prefs=selection_prefs,
100
+ target_python=target_python,
101
+ )
102
+
103
+ def get_available_package_versions(self, options: Values, args: List[Any]) -> None:
104
+ if len(args) != 1:
105
+ raise CommandError("You need to specify exactly one argument")
106
+
107
+ target_python = cmdoptions.make_target_python(options)
108
+ query = args[0]
109
+
110
+ with self._build_session(options) as session:
111
+ finder = self._build_package_finder(
112
+ options=options,
113
+ session=session,
114
+ target_python=target_python,
115
+ ignore_requires_python=options.ignore_requires_python,
116
+ )
117
+
118
+ versions: Iterable[Version] = (
119
+ candidate.version for candidate in finder.find_all_candidates(query)
120
+ )
121
+
122
+ if not options.pre:
123
+ # Remove prereleases
124
+ versions = (
125
+ version for version in versions if not version.is_prerelease
126
+ )
127
+ versions = set(versions)
128
+
129
+ if not versions:
130
+ raise DistributionNotFound(
131
+ f"No matching distribution found for {query}"
132
+ )
133
+
134
+ formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)]
135
+ latest = formatted_versions[0]
136
+
137
+ write_output(f"{query} ({latest})")
138
+ write_output("Available versions: {}".format(", ".join(formatted_versions)))
139
+ print_dist_installation_info(query, latest)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/install.py ADDED
@@ -0,0 +1,784 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import errno
2
+ import json
3
+ import operator
4
+ import os
5
+ import shutil
6
+ import site
7
+ from optparse import SUPPRESS_HELP, Values
8
+ from typing import List, Optional
9
+
10
+ from pip._vendor.packaging.utils import canonicalize_name
11
+ from pip._vendor.rich import print_json
12
+
13
+ # Eagerly import self_outdated_check to avoid crashes. Otherwise,
14
+ # this module would be imported *after* pip was replaced, resulting
15
+ # in crashes if the new self_outdated_check module was incompatible
16
+ # with the rest of pip that's already imported, or allowing a
17
+ # wheel to execute arbitrary code on install by replacing
18
+ # self_outdated_check.
19
+ import pip._internal.self_outdated_check # noqa: F401
20
+ from pip._internal.cache import WheelCache
21
+ from pip._internal.cli import cmdoptions
22
+ from pip._internal.cli.cmdoptions import make_target_python
23
+ from pip._internal.cli.req_command import (
24
+ RequirementCommand,
25
+ with_cleanup,
26
+ )
27
+ from pip._internal.cli.status_codes import ERROR, SUCCESS
28
+ from pip._internal.exceptions import CommandError, InstallationError
29
+ from pip._internal.locations import get_scheme
30
+ from pip._internal.metadata import get_environment
31
+ from pip._internal.models.installation_report import InstallationReport
32
+ from pip._internal.operations.build.build_tracker import get_build_tracker
33
+ from pip._internal.operations.check import ConflictDetails, check_install_conflicts
34
+ from pip._internal.req import install_given_reqs
35
+ from pip._internal.req.req_install import (
36
+ InstallRequirement,
37
+ check_legacy_setup_py_options,
38
+ )
39
+ from pip._internal.utils.compat import WINDOWS
40
+ from pip._internal.utils.filesystem import test_writable_dir
41
+ from pip._internal.utils.logging import getLogger
42
+ from pip._internal.utils.misc import (
43
+ check_externally_managed,
44
+ ensure_dir,
45
+ get_pip_version,
46
+ protect_pip_from_modification_on_windows,
47
+ warn_if_run_as_root,
48
+ write_output,
49
+ )
50
+ from pip._internal.utils.temp_dir import TempDirectory
51
+ from pip._internal.utils.virtualenv import (
52
+ running_under_virtualenv,
53
+ virtualenv_no_global,
54
+ )
55
+ from pip._internal.wheel_builder import build, should_build_for_install_command
56
+
57
+ logger = getLogger(__name__)
58
+
59
+
60
+ class InstallCommand(RequirementCommand):
61
+ """
62
+ Install packages from:
63
+
64
+ - PyPI (and other indexes) using requirement specifiers.
65
+ - VCS project urls.
66
+ - Local project directories.
67
+ - Local or remote source archives.
68
+
69
+ pip also supports installing from "requirements files", which provide
70
+ an easy way to specify a whole environment to be installed.
71
+ """
72
+
73
+ usage = """
74
+ %prog [options] <requirement specifier> [package-index-options] ...
75
+ %prog [options] -r <requirements file> [package-index-options] ...
76
+ %prog [options] [-e] <vcs project url> ...
77
+ %prog [options] [-e] <local project path> ...
78
+ %prog [options] <archive url/path> ..."""
79
+
80
+ def add_options(self) -> None:
81
+ self.cmd_opts.add_option(cmdoptions.requirements())
82
+ self.cmd_opts.add_option(cmdoptions.constraints())
83
+ self.cmd_opts.add_option(cmdoptions.no_deps())
84
+ self.cmd_opts.add_option(cmdoptions.pre())
85
+
86
+ self.cmd_opts.add_option(cmdoptions.editable())
87
+ self.cmd_opts.add_option(
88
+ "--dry-run",
89
+ action="store_true",
90
+ dest="dry_run",
91
+ default=False,
92
+ help=(
93
+ "Don't actually install anything, just print what would be. "
94
+ "Can be used in combination with --ignore-installed "
95
+ "to 'resolve' the requirements."
96
+ ),
97
+ )
98
+ self.cmd_opts.add_option(
99
+ "-t",
100
+ "--target",
101
+ dest="target_dir",
102
+ metavar="dir",
103
+ default=None,
104
+ help=(
105
+ "Install packages into <dir>. "
106
+ "By default this will not replace existing files/folders in "
107
+ "<dir>. Use --upgrade to replace existing packages in <dir> "
108
+ "with new versions."
109
+ ),
110
+ )
111
+ cmdoptions.add_target_python_options(self.cmd_opts)
112
+
113
+ self.cmd_opts.add_option(
114
+ "--user",
115
+ dest="use_user_site",
116
+ action="store_true",
117
+ help=(
118
+ "Install to the Python user install directory for your "
119
+ "platform. Typically ~/.local/, or %APPDATA%\\Python on "
120
+ "Windows. (See the Python documentation for site.USER_BASE "
121
+ "for full details.)"
122
+ ),
123
+ )
124
+ self.cmd_opts.add_option(
125
+ "--no-user",
126
+ dest="use_user_site",
127
+ action="store_false",
128
+ help=SUPPRESS_HELP,
129
+ )
130
+ self.cmd_opts.add_option(
131
+ "--root",
132
+ dest="root_path",
133
+ metavar="dir",
134
+ default=None,
135
+ help="Install everything relative to this alternate root directory.",
136
+ )
137
+ self.cmd_opts.add_option(
138
+ "--prefix",
139
+ dest="prefix_path",
140
+ metavar="dir",
141
+ default=None,
142
+ help=(
143
+ "Installation prefix where lib, bin and other top-level "
144
+ "folders are placed. Note that the resulting installation may "
145
+ "contain scripts and other resources which reference the "
146
+ "Python interpreter of pip, and not that of ``--prefix``. "
147
+ "See also the ``--python`` option if the intention is to "
148
+ "install packages into another (possibly pip-free) "
149
+ "environment."
150
+ ),
151
+ )
152
+
153
+ self.cmd_opts.add_option(cmdoptions.src())
154
+
155
+ self.cmd_opts.add_option(
156
+ "-U",
157
+ "--upgrade",
158
+ dest="upgrade",
159
+ action="store_true",
160
+ help=(
161
+ "Upgrade all specified packages to the newest available "
162
+ "version. The handling of dependencies depends on the "
163
+ "upgrade-strategy used."
164
+ ),
165
+ )
166
+
167
+ self.cmd_opts.add_option(
168
+ "--upgrade-strategy",
169
+ dest="upgrade_strategy",
170
+ default="only-if-needed",
171
+ choices=["only-if-needed", "eager"],
172
+ help=(
173
+ "Determines how dependency upgrading should be handled "
174
+ "[default: %default]. "
175
+ '"eager" - dependencies are upgraded regardless of '
176
+ "whether the currently installed version satisfies the "
177
+ "requirements of the upgraded package(s). "
178
+ '"only-if-needed" - are upgraded only when they do not '
179
+ "satisfy the requirements of the upgraded package(s)."
180
+ ),
181
+ )
182
+
183
+ self.cmd_opts.add_option(
184
+ "--force-reinstall",
185
+ dest="force_reinstall",
186
+ action="store_true",
187
+ help="Reinstall all packages even if they are already up-to-date.",
188
+ )
189
+
190
+ self.cmd_opts.add_option(
191
+ "-I",
192
+ "--ignore-installed",
193
+ dest="ignore_installed",
194
+ action="store_true",
195
+ help=(
196
+ "Ignore the installed packages, overwriting them. "
197
+ "This can break your system if the existing package "
198
+ "is of a different version or was installed "
199
+ "with a different package manager!"
200
+ ),
201
+ )
202
+
203
+ self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
204
+ self.cmd_opts.add_option(cmdoptions.no_build_isolation())
205
+ self.cmd_opts.add_option(cmdoptions.use_pep517())
206
+ self.cmd_opts.add_option(cmdoptions.no_use_pep517())
207
+ self.cmd_opts.add_option(cmdoptions.check_build_deps())
208
+ self.cmd_opts.add_option(cmdoptions.override_externally_managed())
209
+
210
+ self.cmd_opts.add_option(cmdoptions.config_settings())
211
+ self.cmd_opts.add_option(cmdoptions.global_options())
212
+
213
+ self.cmd_opts.add_option(
214
+ "--compile",
215
+ action="store_true",
216
+ dest="compile",
217
+ default=True,
218
+ help="Compile Python source files to bytecode",
219
+ )
220
+
221
+ self.cmd_opts.add_option(
222
+ "--no-compile",
223
+ action="store_false",
224
+ dest="compile",
225
+ help="Do not compile Python source files to bytecode",
226
+ )
227
+
228
+ self.cmd_opts.add_option(
229
+ "--no-warn-script-location",
230
+ action="store_false",
231
+ dest="warn_script_location",
232
+ default=True,
233
+ help="Do not warn when installing scripts outside PATH",
234
+ )
235
+ self.cmd_opts.add_option(
236
+ "--no-warn-conflicts",
237
+ action="store_false",
238
+ dest="warn_about_conflicts",
239
+ default=True,
240
+ help="Do not warn about broken dependencies",
241
+ )
242
+ self.cmd_opts.add_option(cmdoptions.no_binary())
243
+ self.cmd_opts.add_option(cmdoptions.only_binary())
244
+ self.cmd_opts.add_option(cmdoptions.prefer_binary())
245
+ self.cmd_opts.add_option(cmdoptions.require_hashes())
246
+ self.cmd_opts.add_option(cmdoptions.progress_bar())
247
+ self.cmd_opts.add_option(cmdoptions.root_user_action())
248
+
249
+ index_opts = cmdoptions.make_option_group(
250
+ cmdoptions.index_group,
251
+ self.parser,
252
+ )
253
+
254
+ self.parser.insert_option_group(0, index_opts)
255
+ self.parser.insert_option_group(0, self.cmd_opts)
256
+
257
+ self.cmd_opts.add_option(
258
+ "--report",
259
+ dest="json_report_file",
260
+ metavar="file",
261
+ default=None,
262
+ help=(
263
+ "Generate a JSON file describing what pip did to install "
264
+ "the provided requirements. "
265
+ "Can be used in combination with --dry-run and --ignore-installed "
266
+ "to 'resolve' the requirements. "
267
+ "When - is used as file name it writes to stdout. "
268
+ "When writing to stdout, please combine with the --quiet option "
269
+ "to avoid mixing pip logging output with JSON output."
270
+ ),
271
+ )
272
+
273
+ @with_cleanup
274
+ def run(self, options: Values, args: List[str]) -> int:
275
+ if options.use_user_site and options.target_dir is not None:
276
+ raise CommandError("Can not combine '--user' and '--target'")
277
+
278
+ # Check whether the environment we're installing into is externally
279
+ # managed, as specified in PEP 668. Specifying --root, --target, or
280
+ # --prefix disables the check, since there's no reliable way to locate
281
+ # the EXTERNALLY-MANAGED file for those cases. An exception is also
282
+ # made specifically for "--dry-run --report" for convenience.
283
+ installing_into_current_environment = (
284
+ not (options.dry_run and options.json_report_file)
285
+ and options.root_path is None
286
+ and options.target_dir is None
287
+ and options.prefix_path is None
288
+ )
289
+ if (
290
+ installing_into_current_environment
291
+ and not options.override_externally_managed
292
+ ):
293
+ check_externally_managed()
294
+
295
+ upgrade_strategy = "to-satisfy-only"
296
+ if options.upgrade:
297
+ upgrade_strategy = options.upgrade_strategy
298
+
299
+ cmdoptions.check_dist_restriction(options, check_target=True)
300
+
301
+ logger.verbose("Using %s", get_pip_version())
302
+ options.use_user_site = decide_user_install(
303
+ options.use_user_site,
304
+ prefix_path=options.prefix_path,
305
+ target_dir=options.target_dir,
306
+ root_path=options.root_path,
307
+ isolated_mode=options.isolated_mode,
308
+ )
309
+
310
+ target_temp_dir: Optional[TempDirectory] = None
311
+ target_temp_dir_path: Optional[str] = None
312
+ if options.target_dir:
313
+ options.ignore_installed = True
314
+ options.target_dir = os.path.abspath(options.target_dir)
315
+ if (
316
+ # fmt: off
317
+ os.path.exists(options.target_dir) and
318
+ not os.path.isdir(options.target_dir)
319
+ # fmt: on
320
+ ):
321
+ raise CommandError(
322
+ "Target path exists but is not a directory, will not continue."
323
+ )
324
+
325
+ # Create a target directory for using with the target option
326
+ target_temp_dir = TempDirectory(kind="target")
327
+ target_temp_dir_path = target_temp_dir.path
328
+ self.enter_context(target_temp_dir)
329
+
330
+ global_options = options.global_options or []
331
+
332
+ session = self.get_default_session(options)
333
+
334
+ target_python = make_target_python(options)
335
+ finder = self._build_package_finder(
336
+ options=options,
337
+ session=session,
338
+ target_python=target_python,
339
+ ignore_requires_python=options.ignore_requires_python,
340
+ )
341
+ build_tracker = self.enter_context(get_build_tracker())
342
+
343
+ directory = TempDirectory(
344
+ delete=not options.no_clean,
345
+ kind="install",
346
+ globally_managed=True,
347
+ )
348
+
349
+ try:
350
+ reqs = self.get_requirements(args, options, finder, session)
351
+ check_legacy_setup_py_options(options, reqs)
352
+
353
+ wheel_cache = WheelCache(options.cache_dir)
354
+
355
+ # Only when installing is it permitted to use PEP 660.
356
+ # In other circumstances (pip wheel, pip download) we generate
357
+ # regular (i.e. non editable) metadata and wheels.
358
+ for req in reqs:
359
+ req.permit_editable_wheels = True
360
+
361
+ preparer = self.make_requirement_preparer(
362
+ temp_build_dir=directory,
363
+ options=options,
364
+ build_tracker=build_tracker,
365
+ session=session,
366
+ finder=finder,
367
+ use_user_site=options.use_user_site,
368
+ verbosity=self.verbosity,
369
+ )
370
+ resolver = self.make_resolver(
371
+ preparer=preparer,
372
+ finder=finder,
373
+ options=options,
374
+ wheel_cache=wheel_cache,
375
+ use_user_site=options.use_user_site,
376
+ ignore_installed=options.ignore_installed,
377
+ ignore_requires_python=options.ignore_requires_python,
378
+ force_reinstall=options.force_reinstall,
379
+ upgrade_strategy=upgrade_strategy,
380
+ use_pep517=options.use_pep517,
381
+ py_version_info=options.python_version,
382
+ )
383
+
384
+ self.trace_basic_info(finder)
385
+
386
+ requirement_set = resolver.resolve(
387
+ reqs, check_supported_wheels=not options.target_dir
388
+ )
389
+
390
+ if options.json_report_file:
391
+ report = InstallationReport(requirement_set.requirements_to_install)
392
+ if options.json_report_file == "-":
393
+ print_json(data=report.to_dict())
394
+ else:
395
+ with open(options.json_report_file, "w", encoding="utf-8") as f:
396
+ json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
397
+
398
+ if options.dry_run:
399
+ would_install_items = sorted(
400
+ (r.metadata["name"], r.metadata["version"])
401
+ for r in requirement_set.requirements_to_install
402
+ )
403
+ if would_install_items:
404
+ write_output(
405
+ "Would install %s",
406
+ " ".join("-".join(item) for item in would_install_items),
407
+ )
408
+ return SUCCESS
409
+
410
+ try:
411
+ pip_req = requirement_set.get_requirement("pip")
412
+ except KeyError:
413
+ modifying_pip = False
414
+ else:
415
+ # If we're not replacing an already installed pip,
416
+ # we're not modifying it.
417
+ modifying_pip = pip_req.satisfied_by is None
418
+ protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
419
+
420
+ reqs_to_build = [
421
+ r
422
+ for r in requirement_set.requirements.values()
423
+ if should_build_for_install_command(r)
424
+ ]
425
+
426
+ _, build_failures = build(
427
+ reqs_to_build,
428
+ wheel_cache=wheel_cache,
429
+ verify=True,
430
+ build_options=[],
431
+ global_options=global_options,
432
+ )
433
+
434
+ if build_failures:
435
+ raise InstallationError(
436
+ "Failed to build installable wheels for some "
437
+ "pyproject.toml based projects ({})".format(
438
+ ", ".join(r.name for r in build_failures) # type: ignore
439
+ )
440
+ )
441
+
442
+ to_install = resolver.get_installation_order(requirement_set)
443
+
444
+ # Check for conflicts in the package set we're installing.
445
+ conflicts: Optional[ConflictDetails] = None
446
+ should_warn_about_conflicts = (
447
+ not options.ignore_dependencies and options.warn_about_conflicts
448
+ )
449
+ if should_warn_about_conflicts:
450
+ conflicts = self._determine_conflicts(to_install)
451
+
452
+ # Don't warn about script install locations if
453
+ # --target or --prefix has been specified
454
+ warn_script_location = options.warn_script_location
455
+ if options.target_dir or options.prefix_path:
456
+ warn_script_location = False
457
+
458
+ installed = install_given_reqs(
459
+ to_install,
460
+ global_options,
461
+ root=options.root_path,
462
+ home=target_temp_dir_path,
463
+ prefix=options.prefix_path,
464
+ warn_script_location=warn_script_location,
465
+ use_user_site=options.use_user_site,
466
+ pycompile=options.compile,
467
+ )
468
+
469
+ lib_locations = get_lib_location_guesses(
470
+ user=options.use_user_site,
471
+ home=target_temp_dir_path,
472
+ root=options.root_path,
473
+ prefix=options.prefix_path,
474
+ isolated=options.isolated_mode,
475
+ )
476
+ env = get_environment(lib_locations)
477
+
478
+ # Display a summary of installed packages, with extra care to
479
+ # display a package name as it was requested by the user.
480
+ installed.sort(key=operator.attrgetter("name"))
481
+ summary = []
482
+ installed_versions = {}
483
+ for distribution in env.iter_all_distributions():
484
+ installed_versions[distribution.canonical_name] = distribution.version
485
+ for package in installed:
486
+ display_name = package.name
487
+ version = installed_versions.get(canonicalize_name(display_name), None)
488
+ if version:
489
+ text = f"{display_name}-{version}"
490
+ else:
491
+ text = display_name
492
+ summary.append(text)
493
+
494
+ if conflicts is not None:
495
+ self._warn_about_conflicts(
496
+ conflicts,
497
+ resolver_variant=self.determine_resolver_variant(options),
498
+ )
499
+
500
+ installed_desc = " ".join(summary)
501
+ if installed_desc:
502
+ write_output(
503
+ "Successfully installed %s",
504
+ installed_desc,
505
+ )
506
+ except OSError as error:
507
+ show_traceback = self.verbosity >= 1
508
+
509
+ message = create_os_error_message(
510
+ error,
511
+ show_traceback,
512
+ options.use_user_site,
513
+ )
514
+ logger.error(message, exc_info=show_traceback)
515
+
516
+ return ERROR
517
+
518
+ if options.target_dir:
519
+ assert target_temp_dir
520
+ self._handle_target_dir(
521
+ options.target_dir, target_temp_dir, options.upgrade
522
+ )
523
+ if options.root_user_action == "warn":
524
+ warn_if_run_as_root()
525
+ return SUCCESS
526
+
527
+ def _handle_target_dir(
528
+ self, target_dir: str, target_temp_dir: TempDirectory, upgrade: bool
529
+ ) -> None:
530
+ ensure_dir(target_dir)
531
+
532
+ # Checking both purelib and platlib directories for installed
533
+ # packages to be moved to target directory
534
+ lib_dir_list = []
535
+
536
+ # Checking both purelib and platlib directories for installed
537
+ # packages to be moved to target directory
538
+ scheme = get_scheme("", home=target_temp_dir.path)
539
+ purelib_dir = scheme.purelib
540
+ platlib_dir = scheme.platlib
541
+ data_dir = scheme.data
542
+
543
+ if os.path.exists(purelib_dir):
544
+ lib_dir_list.append(purelib_dir)
545
+ if os.path.exists(platlib_dir) and platlib_dir != purelib_dir:
546
+ lib_dir_list.append(platlib_dir)
547
+ if os.path.exists(data_dir):
548
+ lib_dir_list.append(data_dir)
549
+
550
+ for lib_dir in lib_dir_list:
551
+ for item in os.listdir(lib_dir):
552
+ if lib_dir == data_dir:
553
+ ddir = os.path.join(data_dir, item)
554
+ if any(s.startswith(ddir) for s in lib_dir_list[:-1]):
555
+ continue
556
+ target_item_dir = os.path.join(target_dir, item)
557
+ if os.path.exists(target_item_dir):
558
+ if not upgrade:
559
+ logger.warning(
560
+ "Target directory %s already exists. Specify "
561
+ "--upgrade to force replacement.",
562
+ target_item_dir,
563
+ )
564
+ continue
565
+ if os.path.islink(target_item_dir):
566
+ logger.warning(
567
+ "Target directory %s already exists and is "
568
+ "a link. pip will not automatically replace "
569
+ "links, please remove if replacement is "
570
+ "desired.",
571
+ target_item_dir,
572
+ )
573
+ continue
574
+ if os.path.isdir(target_item_dir):
575
+ shutil.rmtree(target_item_dir)
576
+ else:
577
+ os.remove(target_item_dir)
578
+
579
+ shutil.move(os.path.join(lib_dir, item), target_item_dir)
580
+
581
+ def _determine_conflicts(
582
+ self, to_install: List[InstallRequirement]
583
+ ) -> Optional[ConflictDetails]:
584
+ try:
585
+ return check_install_conflicts(to_install)
586
+ except Exception:
587
+ logger.exception(
588
+ "Error while checking for conflicts. Please file an issue on "
589
+ "pip's issue tracker: https://github.com/pypa/pip/issues/new"
590
+ )
591
+ return None
592
+
593
+ def _warn_about_conflicts(
594
+ self, conflict_details: ConflictDetails, resolver_variant: str
595
+ ) -> None:
596
+ package_set, (missing, conflicting) = conflict_details
597
+ if not missing and not conflicting:
598
+ return
599
+
600
+ parts: List[str] = []
601
+ if resolver_variant == "legacy":
602
+ parts.append(
603
+ "pip's legacy dependency resolver does not consider dependency "
604
+ "conflicts when selecting packages. This behaviour is the "
605
+ "source of the following dependency conflicts."
606
+ )
607
+ else:
608
+ assert resolver_variant == "resolvelib"
609
+ parts.append(
610
+ "pip's dependency resolver does not currently take into account "
611
+ "all the packages that are installed. This behaviour is the "
612
+ "source of the following dependency conflicts."
613
+ )
614
+
615
+ # NOTE: There is some duplication here, with commands/check.py
616
+ for project_name in missing:
617
+ version = package_set[project_name][0]
618
+ for dependency in missing[project_name]:
619
+ message = (
620
+ f"{project_name} {version} requires {dependency[1]}, "
621
+ "which is not installed."
622
+ )
623
+ parts.append(message)
624
+
625
+ for project_name in conflicting:
626
+ version = package_set[project_name][0]
627
+ for dep_name, dep_version, req in conflicting[project_name]:
628
+ message = (
629
+ "{name} {version} requires {requirement}, but {you} have "
630
+ "{dep_name} {dep_version} which is incompatible."
631
+ ).format(
632
+ name=project_name,
633
+ version=version,
634
+ requirement=req,
635
+ dep_name=dep_name,
636
+ dep_version=dep_version,
637
+ you=("you" if resolver_variant == "resolvelib" else "you'll"),
638
+ )
639
+ parts.append(message)
640
+
641
+ logger.critical("\n".join(parts))
642
+
643
+
644
+ def get_lib_location_guesses(
645
+ user: bool = False,
646
+ home: Optional[str] = None,
647
+ root: Optional[str] = None,
648
+ isolated: bool = False,
649
+ prefix: Optional[str] = None,
650
+ ) -> List[str]:
651
+ scheme = get_scheme(
652
+ "",
653
+ user=user,
654
+ home=home,
655
+ root=root,
656
+ isolated=isolated,
657
+ prefix=prefix,
658
+ )
659
+ return [scheme.purelib, scheme.platlib]
660
+
661
+
662
+ def site_packages_writable(root: Optional[str], isolated: bool) -> bool:
663
+ return all(
664
+ test_writable_dir(d)
665
+ for d in set(get_lib_location_guesses(root=root, isolated=isolated))
666
+ )
667
+
668
+
669
+ def decide_user_install(
670
+ use_user_site: Optional[bool],
671
+ prefix_path: Optional[str] = None,
672
+ target_dir: Optional[str] = None,
673
+ root_path: Optional[str] = None,
674
+ isolated_mode: bool = False,
675
+ ) -> bool:
676
+ """Determine whether to do a user install based on the input options.
677
+
678
+ If use_user_site is False, no additional checks are done.
679
+ If use_user_site is True, it is checked for compatibility with other
680
+ options.
681
+ If use_user_site is None, the default behaviour depends on the environment,
682
+ which is provided by the other arguments.
683
+ """
684
+ # In some cases (config from tox), use_user_site can be set to an integer
685
+ # rather than a bool, which 'use_user_site is False' wouldn't catch.
686
+ if (use_user_site is not None) and (not use_user_site):
687
+ logger.debug("Non-user install by explicit request")
688
+ return False
689
+
690
+ if use_user_site:
691
+ if prefix_path:
692
+ raise CommandError(
693
+ "Can not combine '--user' and '--prefix' as they imply "
694
+ "different installation locations"
695
+ )
696
+ if virtualenv_no_global():
697
+ raise InstallationError(
698
+ "Can not perform a '--user' install. User site-packages "
699
+ "are not visible in this virtualenv."
700
+ )
701
+ logger.debug("User install by explicit request")
702
+ return True
703
+
704
+ # If we are here, user installs have not been explicitly requested/avoided
705
+ assert use_user_site is None
706
+
707
+ # user install incompatible with --prefix/--target
708
+ if prefix_path or target_dir:
709
+ logger.debug("Non-user install due to --prefix or --target option")
710
+ return False
711
+
712
+ # If user installs are not enabled, choose a non-user install
713
+ if not site.ENABLE_USER_SITE:
714
+ logger.debug("Non-user install because user site-packages disabled")
715
+ return False
716
+
717
+ # If we have permission for a non-user install, do that,
718
+ # otherwise do a user install.
719
+ if site_packages_writable(root=root_path, isolated=isolated_mode):
720
+ logger.debug("Non-user install because site-packages writeable")
721
+ return False
722
+
723
+ logger.info(
724
+ "Defaulting to user installation because normal site-packages "
725
+ "is not writeable"
726
+ )
727
+ return True
728
+
729
+
730
+ def create_os_error_message(
731
+ error: OSError, show_traceback: bool, using_user_site: bool
732
+ ) -> str:
733
+ """Format an error message for an OSError
734
+
735
+ It may occur anytime during the execution of the install command.
736
+ """
737
+ parts = []
738
+
739
+ # Mention the error if we are not going to show a traceback
740
+ parts.append("Could not install packages due to an OSError")
741
+ if not show_traceback:
742
+ parts.append(": ")
743
+ parts.append(str(error))
744
+ else:
745
+ parts.append(".")
746
+
747
+ # Spilt the error indication from a helper message (if any)
748
+ parts[-1] += "\n"
749
+
750
+ # Suggest useful actions to the user:
751
+ # (1) using user site-packages or (2) verifying the permissions
752
+ if error.errno == errno.EACCES:
753
+ user_option_part = "Consider using the `--user` option"
754
+ permissions_part = "Check the permissions"
755
+
756
+ if not running_under_virtualenv() and not using_user_site:
757
+ parts.extend(
758
+ [
759
+ user_option_part,
760
+ " or ",
761
+ permissions_part.lower(),
762
+ ]
763
+ )
764
+ else:
765
+ parts.append(permissions_part)
766
+ parts.append(".\n")
767
+
768
+ # Suggest the user to enable Long Paths if path length is
769
+ # more than 260
770
+ if (
771
+ WINDOWS
772
+ and error.errno == errno.ENOENT
773
+ and error.filename
774
+ and len(error.filename) > 260
775
+ ):
776
+ parts.append(
777
+ "HINT: This error might have occurred since "
778
+ "this system does not have Windows Long Path "
779
+ "support enabled. You can find information on "
780
+ "how to enable this at "
781
+ "https://pip.pypa.io/warnings/enable-long-paths\n"
782
+ )
783
+
784
+ return "".join(parts).strip() + "\n"
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/list.py ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import logging
3
+ from optparse import Values
4
+ from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast
5
+
6
+ from pip._vendor.packaging.utils import canonicalize_name
7
+ from pip._vendor.packaging.version import Version
8
+
9
+ from pip._internal.cli import cmdoptions
10
+ from pip._internal.cli.index_command import IndexGroupCommand
11
+ from pip._internal.cli.status_codes import SUCCESS
12
+ from pip._internal.exceptions import CommandError
13
+ from pip._internal.metadata import BaseDistribution, get_environment
14
+ from pip._internal.models.selection_prefs import SelectionPreferences
15
+ from pip._internal.utils.compat import stdlib_pkgs
16
+ from pip._internal.utils.misc import tabulate, write_output
17
+
18
+ if TYPE_CHECKING:
19
+ from pip._internal.index.package_finder import PackageFinder
20
+ from pip._internal.network.session import PipSession
21
+
22
+ class _DistWithLatestInfo(BaseDistribution):
23
+ """Give the distribution object a couple of extra fields.
24
+
25
+ These will be populated during ``get_outdated()``. This is dirty but
26
+ makes the rest of the code much cleaner.
27
+ """
28
+
29
+ latest_version: Version
30
+ latest_filetype: str
31
+
32
+ _ProcessedDists = Sequence[_DistWithLatestInfo]
33
+
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ class ListCommand(IndexGroupCommand):
39
+ """
40
+ List installed packages, including editables.
41
+
42
+ Packages are listed in a case-insensitive sorted order.
43
+ """
44
+
45
+ ignore_require_venv = True
46
+ usage = """
47
+ %prog [options]"""
48
+
49
+ def add_options(self) -> None:
50
+ self.cmd_opts.add_option(
51
+ "-o",
52
+ "--outdated",
53
+ action="store_true",
54
+ default=False,
55
+ help="List outdated packages",
56
+ )
57
+ self.cmd_opts.add_option(
58
+ "-u",
59
+ "--uptodate",
60
+ action="store_true",
61
+ default=False,
62
+ help="List uptodate packages",
63
+ )
64
+ self.cmd_opts.add_option(
65
+ "-e",
66
+ "--editable",
67
+ action="store_true",
68
+ default=False,
69
+ help="List editable projects.",
70
+ )
71
+ self.cmd_opts.add_option(
72
+ "-l",
73
+ "--local",
74
+ action="store_true",
75
+ default=False,
76
+ help=(
77
+ "If in a virtualenv that has global access, do not list "
78
+ "globally-installed packages."
79
+ ),
80
+ )
81
+ self.cmd_opts.add_option(
82
+ "--user",
83
+ dest="user",
84
+ action="store_true",
85
+ default=False,
86
+ help="Only output packages installed in user-site.",
87
+ )
88
+ self.cmd_opts.add_option(cmdoptions.list_path())
89
+ self.cmd_opts.add_option(
90
+ "--pre",
91
+ action="store_true",
92
+ default=False,
93
+ help=(
94
+ "Include pre-release and development versions. By default, "
95
+ "pip only finds stable versions."
96
+ ),
97
+ )
98
+
99
+ self.cmd_opts.add_option(
100
+ "--format",
101
+ action="store",
102
+ dest="list_format",
103
+ default="columns",
104
+ choices=("columns", "freeze", "json"),
105
+ help=(
106
+ "Select the output format among: columns (default), freeze, or json. "
107
+ "The 'freeze' format cannot be used with the --outdated option."
108
+ ),
109
+ )
110
+
111
+ self.cmd_opts.add_option(
112
+ "--not-required",
113
+ action="store_true",
114
+ dest="not_required",
115
+ help="List packages that are not dependencies of installed packages.",
116
+ )
117
+
118
+ self.cmd_opts.add_option(
119
+ "--exclude-editable",
120
+ action="store_false",
121
+ dest="include_editable",
122
+ help="Exclude editable package from output.",
123
+ )
124
+ self.cmd_opts.add_option(
125
+ "--include-editable",
126
+ action="store_true",
127
+ dest="include_editable",
128
+ help="Include editable package from output.",
129
+ default=True,
130
+ )
131
+ self.cmd_opts.add_option(cmdoptions.list_exclude())
132
+ index_opts = cmdoptions.make_option_group(cmdoptions.index_group, self.parser)
133
+
134
+ self.parser.insert_option_group(0, index_opts)
135
+ self.parser.insert_option_group(0, self.cmd_opts)
136
+
137
+ def handle_pip_version_check(self, options: Values) -> None:
138
+ if options.outdated or options.uptodate:
139
+ super().handle_pip_version_check(options)
140
+
141
+ def _build_package_finder(
142
+ self, options: Values, session: "PipSession"
143
+ ) -> "PackageFinder":
144
+ """
145
+ Create a package finder appropriate to this list command.
146
+ """
147
+ # Lazy import the heavy index modules as most list invocations won't need 'em.
148
+ from pip._internal.index.collector import LinkCollector
149
+ from pip._internal.index.package_finder import PackageFinder
150
+
151
+ link_collector = LinkCollector.create(session, options=options)
152
+
153
+ # Pass allow_yanked=False to ignore yanked versions.
154
+ selection_prefs = SelectionPreferences(
155
+ allow_yanked=False,
156
+ allow_all_prereleases=options.pre,
157
+ )
158
+
159
+ return PackageFinder.create(
160
+ link_collector=link_collector,
161
+ selection_prefs=selection_prefs,
162
+ )
163
+
164
+ def run(self, options: Values, args: List[str]) -> int:
165
+ if options.outdated and options.uptodate:
166
+ raise CommandError("Options --outdated and --uptodate cannot be combined.")
167
+
168
+ if options.outdated and options.list_format == "freeze":
169
+ raise CommandError(
170
+ "List format 'freeze' cannot be used with the --outdated option."
171
+ )
172
+
173
+ cmdoptions.check_list_path_option(options)
174
+
175
+ skip = set(stdlib_pkgs)
176
+ if options.excludes:
177
+ skip.update(canonicalize_name(n) for n in options.excludes)
178
+
179
+ packages: _ProcessedDists = [
180
+ cast("_DistWithLatestInfo", d)
181
+ for d in get_environment(options.path).iter_installed_distributions(
182
+ local_only=options.local,
183
+ user_only=options.user,
184
+ editables_only=options.editable,
185
+ include_editables=options.include_editable,
186
+ skip=skip,
187
+ )
188
+ ]
189
+
190
+ # get_not_required must be called firstly in order to find and
191
+ # filter out all dependencies correctly. Otherwise a package
192
+ # can't be identified as requirement because some parent packages
193
+ # could be filtered out before.
194
+ if options.not_required:
195
+ packages = self.get_not_required(packages, options)
196
+
197
+ if options.outdated:
198
+ packages = self.get_outdated(packages, options)
199
+ elif options.uptodate:
200
+ packages = self.get_uptodate(packages, options)
201
+
202
+ self.output_package_listing(packages, options)
203
+ return SUCCESS
204
+
205
+ def get_outdated(
206
+ self, packages: "_ProcessedDists", options: Values
207
+ ) -> "_ProcessedDists":
208
+ return [
209
+ dist
210
+ for dist in self.iter_packages_latest_infos(packages, options)
211
+ if dist.latest_version > dist.version
212
+ ]
213
+
214
+ def get_uptodate(
215
+ self, packages: "_ProcessedDists", options: Values
216
+ ) -> "_ProcessedDists":
217
+ return [
218
+ dist
219
+ for dist in self.iter_packages_latest_infos(packages, options)
220
+ if dist.latest_version == dist.version
221
+ ]
222
+
223
+ def get_not_required(
224
+ self, packages: "_ProcessedDists", options: Values
225
+ ) -> "_ProcessedDists":
226
+ dep_keys = {
227
+ canonicalize_name(dep.name)
228
+ for dist in packages
229
+ for dep in (dist.iter_dependencies() or ())
230
+ }
231
+
232
+ # Create a set to remove duplicate packages, and cast it to a list
233
+ # to keep the return type consistent with get_outdated and
234
+ # get_uptodate
235
+ return list({pkg for pkg in packages if pkg.canonical_name not in dep_keys})
236
+
237
+ def iter_packages_latest_infos(
238
+ self, packages: "_ProcessedDists", options: Values
239
+ ) -> Generator["_DistWithLatestInfo", None, None]:
240
+ with self._build_session(options) as session:
241
+ finder = self._build_package_finder(options, session)
242
+
243
+ def latest_info(
244
+ dist: "_DistWithLatestInfo",
245
+ ) -> Optional["_DistWithLatestInfo"]:
246
+ all_candidates = finder.find_all_candidates(dist.canonical_name)
247
+ if not options.pre:
248
+ # Remove prereleases
249
+ all_candidates = [
250
+ candidate
251
+ for candidate in all_candidates
252
+ if not candidate.version.is_prerelease
253
+ ]
254
+
255
+ evaluator = finder.make_candidate_evaluator(
256
+ project_name=dist.canonical_name,
257
+ )
258
+ best_candidate = evaluator.sort_best_candidate(all_candidates)
259
+ if best_candidate is None:
260
+ return None
261
+
262
+ remote_version = best_candidate.version
263
+ if best_candidate.link.is_wheel:
264
+ typ = "wheel"
265
+ else:
266
+ typ = "sdist"
267
+ dist.latest_version = remote_version
268
+ dist.latest_filetype = typ
269
+ return dist
270
+
271
+ for dist in map(latest_info, packages):
272
+ if dist is not None:
273
+ yield dist
274
+
275
+ def output_package_listing(
276
+ self, packages: "_ProcessedDists", options: Values
277
+ ) -> None:
278
+ packages = sorted(
279
+ packages,
280
+ key=lambda dist: dist.canonical_name,
281
+ )
282
+ if options.list_format == "columns" and packages:
283
+ data, header = format_for_columns(packages, options)
284
+ self.output_package_listing_columns(data, header)
285
+ elif options.list_format == "freeze":
286
+ for dist in packages:
287
+ if options.verbose >= 1:
288
+ write_output(
289
+ "%s==%s (%s)", dist.raw_name, dist.version, dist.location
290
+ )
291
+ else:
292
+ write_output("%s==%s", dist.raw_name, dist.version)
293
+ elif options.list_format == "json":
294
+ write_output(format_for_json(packages, options))
295
+
296
+ def output_package_listing_columns(
297
+ self, data: List[List[str]], header: List[str]
298
+ ) -> None:
299
+ # insert the header first: we need to know the size of column names
300
+ if len(data) > 0:
301
+ data.insert(0, header)
302
+
303
+ pkg_strings, sizes = tabulate(data)
304
+
305
+ # Create and add a separator.
306
+ if len(data) > 0:
307
+ pkg_strings.insert(1, " ".join("-" * x for x in sizes))
308
+
309
+ for val in pkg_strings:
310
+ write_output(val)
311
+
312
+
313
+ def format_for_columns(
314
+ pkgs: "_ProcessedDists", options: Values
315
+ ) -> Tuple[List[List[str]], List[str]]:
316
+ """
317
+ Convert the package data into something usable
318
+ by output_package_listing_columns.
319
+ """
320
+ header = ["Package", "Version"]
321
+
322
+ running_outdated = options.outdated
323
+ if running_outdated:
324
+ header.extend(["Latest", "Type"])
325
+
326
+ has_editables = any(x.editable for x in pkgs)
327
+ if has_editables:
328
+ header.append("Editable project location")
329
+
330
+ if options.verbose >= 1:
331
+ header.append("Location")
332
+ if options.verbose >= 1:
333
+ header.append("Installer")
334
+
335
+ data = []
336
+ for proj in pkgs:
337
+ # if we're working on the 'outdated' list, separate out the
338
+ # latest_version and type
339
+ row = [proj.raw_name, proj.raw_version]
340
+
341
+ if running_outdated:
342
+ row.append(str(proj.latest_version))
343
+ row.append(proj.latest_filetype)
344
+
345
+ if has_editables:
346
+ row.append(proj.editable_project_location or "")
347
+
348
+ if options.verbose >= 1:
349
+ row.append(proj.location or "")
350
+ if options.verbose >= 1:
351
+ row.append(proj.installer)
352
+
353
+ data.append(row)
354
+
355
+ return data, header
356
+
357
+
358
+ def format_for_json(packages: "_ProcessedDists", options: Values) -> str:
359
+ data = []
360
+ for dist in packages:
361
+ info = {
362
+ "name": dist.raw_name,
363
+ "version": str(dist.version),
364
+ }
365
+ if options.verbose >= 1:
366
+ info["location"] = dist.location or ""
367
+ info["installer"] = dist.installer
368
+ if options.outdated:
369
+ info["latest_version"] = str(dist.latest_version)
370
+ info["latest_filetype"] = dist.latest_filetype
371
+ editable_project_location = dist.editable_project_location
372
+ if editable_project_location:
373
+ info["editable_project_location"] = editable_project_location
374
+ data.append(info)
375
+ return json.dumps(data)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/search.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import shutil
3
+ import sys
4
+ import textwrap
5
+ import xmlrpc.client
6
+ from collections import OrderedDict
7
+ from optparse import Values
8
+ from typing import TYPE_CHECKING, Dict, List, Optional, TypedDict
9
+
10
+ from pip._vendor.packaging.version import parse as parse_version
11
+
12
+ from pip._internal.cli.base_command import Command
13
+ from pip._internal.cli.req_command import SessionCommandMixin
14
+ from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS
15
+ from pip._internal.exceptions import CommandError
16
+ from pip._internal.metadata import get_default_environment
17
+ from pip._internal.models.index import PyPI
18
+ from pip._internal.network.xmlrpc import PipXmlrpcTransport
19
+ from pip._internal.utils.logging import indent_log
20
+ from pip._internal.utils.misc import write_output
21
+
22
+ if TYPE_CHECKING:
23
+
24
+ class TransformedHit(TypedDict):
25
+ name: str
26
+ summary: str
27
+ versions: List[str]
28
+
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class SearchCommand(Command, SessionCommandMixin):
34
+ """Search for PyPI packages whose name or summary contains <query>."""
35
+
36
+ usage = """
37
+ %prog [options] <query>"""
38
+ ignore_require_venv = True
39
+
40
+ def add_options(self) -> None:
41
+ self.cmd_opts.add_option(
42
+ "-i",
43
+ "--index",
44
+ dest="index",
45
+ metavar="URL",
46
+ default=PyPI.pypi_url,
47
+ help="Base URL of Python Package Index (default %default)",
48
+ )
49
+
50
+ self.parser.insert_option_group(0, self.cmd_opts)
51
+
52
+ def run(self, options: Values, args: List[str]) -> int:
53
+ if not args:
54
+ raise CommandError("Missing required argument (search query).")
55
+ query = args
56
+ pypi_hits = self.search(query, options)
57
+ hits = transform_hits(pypi_hits)
58
+
59
+ terminal_width = None
60
+ if sys.stdout.isatty():
61
+ terminal_width = shutil.get_terminal_size()[0]
62
+
63
+ print_results(hits, terminal_width=terminal_width)
64
+ if pypi_hits:
65
+ return SUCCESS
66
+ return NO_MATCHES_FOUND
67
+
68
+ def search(self, query: List[str], options: Values) -> List[Dict[str, str]]:
69
+ index_url = options.index
70
+
71
+ session = self.get_default_session(options)
72
+
73
+ transport = PipXmlrpcTransport(index_url, session)
74
+ pypi = xmlrpc.client.ServerProxy(index_url, transport)
75
+ try:
76
+ hits = pypi.search({"name": query, "summary": query}, "or")
77
+ except xmlrpc.client.Fault as fault:
78
+ message = (
79
+ f"XMLRPC request failed [code: {fault.faultCode}]\n{fault.faultString}"
80
+ )
81
+ raise CommandError(message)
82
+ assert isinstance(hits, list)
83
+ return hits
84
+
85
+
86
+ def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]:
87
+ """
88
+ The list from pypi is really a list of versions. We want a list of
89
+ packages with the list of versions stored inline. This converts the
90
+ list from pypi into one we can use.
91
+ """
92
+ packages: Dict[str, TransformedHit] = OrderedDict()
93
+ for hit in hits:
94
+ name = hit["name"]
95
+ summary = hit["summary"]
96
+ version = hit["version"]
97
+
98
+ if name not in packages.keys():
99
+ packages[name] = {
100
+ "name": name,
101
+ "summary": summary,
102
+ "versions": [version],
103
+ }
104
+ else:
105
+ packages[name]["versions"].append(version)
106
+
107
+ # if this is the highest version, replace summary and score
108
+ if version == highest_version(packages[name]["versions"]):
109
+ packages[name]["summary"] = summary
110
+
111
+ return list(packages.values())
112
+
113
+
114
+ def print_dist_installation_info(name: str, latest: str) -> None:
115
+ env = get_default_environment()
116
+ dist = env.get_distribution(name)
117
+ if dist is not None:
118
+ with indent_log():
119
+ if dist.version == latest:
120
+ write_output("INSTALLED: %s (latest)", dist.version)
121
+ else:
122
+ write_output("INSTALLED: %s", dist.version)
123
+ if parse_version(latest).pre:
124
+ write_output(
125
+ "LATEST: %s (pre-release; install"
126
+ " with `pip install --pre`)",
127
+ latest,
128
+ )
129
+ else:
130
+ write_output("LATEST: %s", latest)
131
+
132
+
133
+ def print_results(
134
+ hits: List["TransformedHit"],
135
+ name_column_width: Optional[int] = None,
136
+ terminal_width: Optional[int] = None,
137
+ ) -> None:
138
+ if not hits:
139
+ return
140
+ if name_column_width is None:
141
+ name_column_width = (
142
+ max(
143
+ [
144
+ len(hit["name"]) + len(highest_version(hit.get("versions", ["-"])))
145
+ for hit in hits
146
+ ]
147
+ )
148
+ + 4
149
+ )
150
+
151
+ for hit in hits:
152
+ name = hit["name"]
153
+ summary = hit["summary"] or ""
154
+ latest = highest_version(hit.get("versions", ["-"]))
155
+ if terminal_width is not None:
156
+ target_width = terminal_width - name_column_width - 5
157
+ if target_width > 10:
158
+ # wrap and indent summary to fit terminal
159
+ summary_lines = textwrap.wrap(summary, target_width)
160
+ summary = ("\n" + " " * (name_column_width + 3)).join(summary_lines)
161
+
162
+ name_latest = f"{name} ({latest})"
163
+ line = f"{name_latest:{name_column_width}} - {summary}"
164
+ try:
165
+ write_output(line)
166
+ print_dist_installation_info(name, latest)
167
+ except UnicodeEncodeError:
168
+ pass
169
+
170
+
171
+ def highest_version(versions: List[str]) -> str:
172
+ return max(versions, key=parse_version)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/show.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from optparse import Values
3
+ from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional
4
+
5
+ from pip._vendor.packaging.requirements import InvalidRequirement
6
+ from pip._vendor.packaging.utils import canonicalize_name
7
+
8
+ from pip._internal.cli.base_command import Command
9
+ from pip._internal.cli.status_codes import ERROR, SUCCESS
10
+ from pip._internal.metadata import BaseDistribution, get_default_environment
11
+ from pip._internal.utils.misc import write_output
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class ShowCommand(Command):
17
+ """
18
+ Show information about one or more installed packages.
19
+
20
+ The output is in RFC-compliant mail header format.
21
+ """
22
+
23
+ usage = """
24
+ %prog [options] <package> ..."""
25
+ ignore_require_venv = True
26
+
27
+ def add_options(self) -> None:
28
+ self.cmd_opts.add_option(
29
+ "-f",
30
+ "--files",
31
+ dest="files",
32
+ action="store_true",
33
+ default=False,
34
+ help="Show the full list of installed files for each package.",
35
+ )
36
+
37
+ self.parser.insert_option_group(0, self.cmd_opts)
38
+
39
+ def run(self, options: Values, args: List[str]) -> int:
40
+ if not args:
41
+ logger.warning("ERROR: Please provide a package name or names.")
42
+ return ERROR
43
+ query = args
44
+
45
+ results = search_packages_info(query)
46
+ if not print_results(
47
+ results, list_files=options.files, verbose=options.verbose
48
+ ):
49
+ return ERROR
50
+ return SUCCESS
51
+
52
+
53
+ class _PackageInfo(NamedTuple):
54
+ name: str
55
+ version: str
56
+ location: str
57
+ editable_project_location: Optional[str]
58
+ requires: List[str]
59
+ required_by: List[str]
60
+ installer: str
61
+ metadata_version: str
62
+ classifiers: List[str]
63
+ summary: str
64
+ homepage: str
65
+ project_urls: List[str]
66
+ author: str
67
+ author_email: str
68
+ license: str
69
+ license_expression: str
70
+ entry_points: List[str]
71
+ files: Optional[List[str]]
72
+
73
+
74
+ def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None]:
75
+ """
76
+ Gather details from installed distributions. Print distribution name,
77
+ version, location, and installed files. Installed files requires a
78
+ pip generated 'installed-files.txt' in the distributions '.egg-info'
79
+ directory.
80
+ """
81
+ env = get_default_environment()
82
+
83
+ installed = {dist.canonical_name: dist for dist in env.iter_all_distributions()}
84
+ query_names = [canonicalize_name(name) for name in query]
85
+ missing = sorted(
86
+ [name for name, pkg in zip(query, query_names) if pkg not in installed]
87
+ )
88
+ if missing:
89
+ logger.warning("Package(s) not found: %s", ", ".join(missing))
90
+
91
+ def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[str]:
92
+ return (
93
+ dist.metadata["Name"] or "UNKNOWN"
94
+ for dist in installed.values()
95
+ if current_dist.canonical_name
96
+ in {canonicalize_name(d.name) for d in dist.iter_dependencies()}
97
+ )
98
+
99
+ for query_name in query_names:
100
+ try:
101
+ dist = installed[query_name]
102
+ except KeyError:
103
+ continue
104
+
105
+ try:
106
+ requires = sorted(
107
+ # Avoid duplicates in requirements (e.g. due to environment markers).
108
+ {req.name for req in dist.iter_dependencies()},
109
+ key=str.lower,
110
+ )
111
+ except InvalidRequirement:
112
+ requires = sorted(dist.iter_raw_dependencies(), key=str.lower)
113
+
114
+ try:
115
+ required_by = sorted(_get_requiring_packages(dist), key=str.lower)
116
+ except InvalidRequirement:
117
+ required_by = ["#N/A"]
118
+
119
+ try:
120
+ entry_points_text = dist.read_text("entry_points.txt")
121
+ entry_points = entry_points_text.splitlines(keepends=False)
122
+ except FileNotFoundError:
123
+ entry_points = []
124
+
125
+ files_iter = dist.iter_declared_entries()
126
+ if files_iter is None:
127
+ files: Optional[List[str]] = None
128
+ else:
129
+ files = sorted(files_iter)
130
+
131
+ metadata = dist.metadata
132
+
133
+ project_urls = metadata.get_all("Project-URL", [])
134
+ homepage = metadata.get("Home-page", "")
135
+ if not homepage:
136
+ # It's common that there is a "homepage" Project-URL, but Home-page
137
+ # remains unset (especially as PEP 621 doesn't surface the field).
138
+ #
139
+ # This logic was taken from PyPI's codebase.
140
+ for url in project_urls:
141
+ url_label, url = url.split(",", maxsplit=1)
142
+ normalized_label = (
143
+ url_label.casefold().replace("-", "").replace("_", "").strip()
144
+ )
145
+ if normalized_label == "homepage":
146
+ homepage = url.strip()
147
+ break
148
+
149
+ yield _PackageInfo(
150
+ name=dist.raw_name,
151
+ version=dist.raw_version,
152
+ location=dist.location or "",
153
+ editable_project_location=dist.editable_project_location,
154
+ requires=requires,
155
+ required_by=required_by,
156
+ installer=dist.installer,
157
+ metadata_version=dist.metadata_version or "",
158
+ classifiers=metadata.get_all("Classifier", []),
159
+ summary=metadata.get("Summary", ""),
160
+ homepage=homepage,
161
+ project_urls=project_urls,
162
+ author=metadata.get("Author", ""),
163
+ author_email=metadata.get("Author-email", ""),
164
+ license=metadata.get("License", ""),
165
+ license_expression=metadata.get("License-Expression", ""),
166
+ entry_points=entry_points,
167
+ files=files,
168
+ )
169
+
170
+
171
+ def print_results(
172
+ distributions: Iterable[_PackageInfo],
173
+ list_files: bool,
174
+ verbose: bool,
175
+ ) -> bool:
176
+ """
177
+ Print the information from installed distributions found.
178
+ """
179
+ results_printed = False
180
+ for i, dist in enumerate(distributions):
181
+ results_printed = True
182
+ if i > 0:
183
+ write_output("---")
184
+
185
+ metadata_version_tuple = tuple(map(int, dist.metadata_version.split(".")))
186
+
187
+ write_output("Name: %s", dist.name)
188
+ write_output("Version: %s", dist.version)
189
+ write_output("Summary: %s", dist.summary)
190
+ write_output("Home-page: %s", dist.homepage)
191
+ write_output("Author: %s", dist.author)
192
+ write_output("Author-email: %s", dist.author_email)
193
+ if metadata_version_tuple >= (2, 4) and dist.license_expression:
194
+ write_output("License-Expression: %s", dist.license_expression)
195
+ else:
196
+ write_output("License: %s", dist.license)
197
+ write_output("Location: %s", dist.location)
198
+ if dist.editable_project_location is not None:
199
+ write_output(
200
+ "Editable project location: %s", dist.editable_project_location
201
+ )
202
+ write_output("Requires: %s", ", ".join(dist.requires))
203
+ write_output("Required-by: %s", ", ".join(dist.required_by))
204
+
205
+ if verbose:
206
+ write_output("Metadata-Version: %s", dist.metadata_version)
207
+ write_output("Installer: %s", dist.installer)
208
+ write_output("Classifiers:")
209
+ for classifier in dist.classifiers:
210
+ write_output(" %s", classifier)
211
+ write_output("Entry-points:")
212
+ for entry in dist.entry_points:
213
+ write_output(" %s", entry.strip())
214
+ write_output("Project-URLs:")
215
+ for project_url in dist.project_urls:
216
+ write_output(" %s", project_url)
217
+ if list_files:
218
+ write_output("Files:")
219
+ if dist.files is None:
220
+ write_output("Cannot locate RECORD or installed-files.txt")
221
+ else:
222
+ for line in dist.files:
223
+ write_output(" %s", line.strip())
224
+ return results_printed
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/commands/wheel.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ import shutil
4
+ from optparse import Values
5
+ from typing import List
6
+
7
+ from pip._internal.cache import WheelCache
8
+ from pip._internal.cli import cmdoptions
9
+ from pip._internal.cli.req_command import RequirementCommand, with_cleanup
10
+ from pip._internal.cli.status_codes import SUCCESS
11
+ from pip._internal.exceptions import CommandError
12
+ from pip._internal.operations.build.build_tracker import get_build_tracker
13
+ from pip._internal.req.req_install import (
14
+ InstallRequirement,
15
+ check_legacy_setup_py_options,
16
+ )
17
+ from pip._internal.utils.misc import ensure_dir, normalize_path
18
+ from pip._internal.utils.temp_dir import TempDirectory
19
+ from pip._internal.wheel_builder import build, should_build_for_wheel_command
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class WheelCommand(RequirementCommand):
25
+ """
26
+ Build Wheel archives for your requirements and dependencies.
27
+
28
+ Wheel is a built-package format, and offers the advantage of not
29
+ recompiling your software during every install. For more details, see the
30
+ wheel docs: https://wheel.readthedocs.io/en/latest/
31
+
32
+ 'pip wheel' uses the build system interface as described here:
33
+ https://pip.pypa.io/en/stable/reference/build-system/
34
+
35
+ """
36
+
37
+ usage = """
38
+ %prog [options] <requirement specifier> ...
39
+ %prog [options] -r <requirements file> ...
40
+ %prog [options] [-e] <vcs project url> ...
41
+ %prog [options] [-e] <local project path> ...
42
+ %prog [options] <archive url/path> ..."""
43
+
44
+ def add_options(self) -> None:
45
+ self.cmd_opts.add_option(
46
+ "-w",
47
+ "--wheel-dir",
48
+ dest="wheel_dir",
49
+ metavar="dir",
50
+ default=os.curdir,
51
+ help=(
52
+ "Build wheels into <dir>, where the default is the "
53
+ "current working directory."
54
+ ),
55
+ )
56
+ self.cmd_opts.add_option(cmdoptions.no_binary())
57
+ self.cmd_opts.add_option(cmdoptions.only_binary())
58
+ self.cmd_opts.add_option(cmdoptions.prefer_binary())
59
+ self.cmd_opts.add_option(cmdoptions.no_build_isolation())
60
+ self.cmd_opts.add_option(cmdoptions.use_pep517())
61
+ self.cmd_opts.add_option(cmdoptions.no_use_pep517())
62
+ self.cmd_opts.add_option(cmdoptions.check_build_deps())
63
+ self.cmd_opts.add_option(cmdoptions.constraints())
64
+ self.cmd_opts.add_option(cmdoptions.editable())
65
+ self.cmd_opts.add_option(cmdoptions.requirements())
66
+ self.cmd_opts.add_option(cmdoptions.src())
67
+ self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
68
+ self.cmd_opts.add_option(cmdoptions.no_deps())
69
+ self.cmd_opts.add_option(cmdoptions.progress_bar())
70
+
71
+ self.cmd_opts.add_option(
72
+ "--no-verify",
73
+ dest="no_verify",
74
+ action="store_true",
75
+ default=False,
76
+ help="Don't verify if built wheel is valid.",
77
+ )
78
+
79
+ self.cmd_opts.add_option(cmdoptions.config_settings())
80
+ self.cmd_opts.add_option(cmdoptions.build_options())
81
+ self.cmd_opts.add_option(cmdoptions.global_options())
82
+
83
+ self.cmd_opts.add_option(
84
+ "--pre",
85
+ action="store_true",
86
+ default=False,
87
+ help=(
88
+ "Include pre-release and development versions. By default, "
89
+ "pip only finds stable versions."
90
+ ),
91
+ )
92
+
93
+ self.cmd_opts.add_option(cmdoptions.require_hashes())
94
+
95
+ index_opts = cmdoptions.make_option_group(
96
+ cmdoptions.index_group,
97
+ self.parser,
98
+ )
99
+
100
+ self.parser.insert_option_group(0, index_opts)
101
+ self.parser.insert_option_group(0, self.cmd_opts)
102
+
103
+ @with_cleanup
104
+ def run(self, options: Values, args: List[str]) -> int:
105
+ session = self.get_default_session(options)
106
+
107
+ finder = self._build_package_finder(options, session)
108
+
109
+ options.wheel_dir = normalize_path(options.wheel_dir)
110
+ ensure_dir(options.wheel_dir)
111
+
112
+ build_tracker = self.enter_context(get_build_tracker())
113
+
114
+ directory = TempDirectory(
115
+ delete=not options.no_clean,
116
+ kind="wheel",
117
+ globally_managed=True,
118
+ )
119
+
120
+ reqs = self.get_requirements(args, options, finder, session)
121
+ check_legacy_setup_py_options(options, reqs)
122
+
123
+ wheel_cache = WheelCache(options.cache_dir)
124
+
125
+ preparer = self.make_requirement_preparer(
126
+ temp_build_dir=directory,
127
+ options=options,
128
+ build_tracker=build_tracker,
129
+ session=session,
130
+ finder=finder,
131
+ download_dir=options.wheel_dir,
132
+ use_user_site=False,
133
+ verbosity=self.verbosity,
134
+ )
135
+
136
+ resolver = self.make_resolver(
137
+ preparer=preparer,
138
+ finder=finder,
139
+ options=options,
140
+ wheel_cache=wheel_cache,
141
+ ignore_requires_python=options.ignore_requires_python,
142
+ use_pep517=options.use_pep517,
143
+ )
144
+
145
+ self.trace_basic_info(finder)
146
+
147
+ requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
148
+
149
+ reqs_to_build: List[InstallRequirement] = []
150
+ for req in requirement_set.requirements.values():
151
+ if req.is_wheel:
152
+ preparer.save_linked_requirement(req)
153
+ elif should_build_for_wheel_command(req):
154
+ reqs_to_build.append(req)
155
+
156
+ preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
157
+
158
+ # build wheels
159
+ build_successes, build_failures = build(
160
+ reqs_to_build,
161
+ wheel_cache=wheel_cache,
162
+ verify=(not options.no_verify),
163
+ build_options=options.build_options or [],
164
+ global_options=options.global_options or [],
165
+ )
166
+ for req in build_successes:
167
+ assert req.link and req.link.is_wheel
168
+ assert req.local_file_path
169
+ # copy from cache to target directory
170
+ try:
171
+ shutil.copy(req.local_file_path, options.wheel_dir)
172
+ except OSError as e:
173
+ logger.warning(
174
+ "Building wheel for %s failed: %s",
175
+ req.name,
176
+ e,
177
+ )
178
+ build_failures.append(req)
179
+ if len(build_failures) != 0:
180
+ raise CommandError("Failed to build one or more wheels")
181
+
182
+ return SUCCESS
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/configuration.py ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration management setup
2
+
3
+ Some terminology:
4
+ - name
5
+ As written in config files.
6
+ - value
7
+ Value associated with a name
8
+ - key
9
+ Name combined with it's section (section.name)
10
+ - variant
11
+ A single word describing where the configuration key-value pair came from
12
+ """
13
+
14
+ import configparser
15
+ import locale
16
+ import os
17
+ import sys
18
+ from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple
19
+
20
+ from pip._internal.exceptions import (
21
+ ConfigurationError,
22
+ ConfigurationFileCouldNotBeLoaded,
23
+ )
24
+ from pip._internal.utils import appdirs
25
+ from pip._internal.utils.compat import WINDOWS
26
+ from pip._internal.utils.logging import getLogger
27
+ from pip._internal.utils.misc import ensure_dir, enum
28
+
29
+ RawConfigParser = configparser.RawConfigParser # Shorthand
30
+ Kind = NewType("Kind", str)
31
+
32
+ CONFIG_BASENAME = "pip.ini" if WINDOWS else "pip.conf"
33
+ ENV_NAMES_IGNORED = "version", "help"
34
+
35
+ # The kinds of configurations there are.
36
+ kinds = enum(
37
+ USER="user", # User Specific
38
+ GLOBAL="global", # System Wide
39
+ SITE="site", # [Virtual] Environment Specific
40
+ ENV="env", # from PIP_CONFIG_FILE
41
+ ENV_VAR="env-var", # from Environment Variables
42
+ )
43
+ OVERRIDE_ORDER = kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR
44
+ VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.SITE
45
+
46
+ logger = getLogger(__name__)
47
+
48
+
49
+ # NOTE: Maybe use the optionx attribute to normalize keynames.
50
+ def _normalize_name(name: str) -> str:
51
+ """Make a name consistent regardless of source (environment or file)"""
52
+ name = name.lower().replace("_", "-")
53
+ if name.startswith("--"):
54
+ name = name[2:] # only prefer long opts
55
+ return name
56
+
57
+
58
+ def _disassemble_key(name: str) -> List[str]:
59
+ if "." not in name:
60
+ error_message = (
61
+ "Key does not contain dot separated section and key. "
62
+ f"Perhaps you wanted to use 'global.{name}' instead?"
63
+ )
64
+ raise ConfigurationError(error_message)
65
+ return name.split(".", 1)
66
+
67
+
68
+ def get_configuration_files() -> Dict[Kind, List[str]]:
69
+ global_config_files = [
70
+ os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip")
71
+ ]
72
+
73
+ site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME)
74
+ legacy_config_file = os.path.join(
75
+ os.path.expanduser("~"),
76
+ "pip" if WINDOWS else ".pip",
77
+ CONFIG_BASENAME,
78
+ )
79
+ new_config_file = os.path.join(appdirs.user_config_dir("pip"), CONFIG_BASENAME)
80
+ return {
81
+ kinds.GLOBAL: global_config_files,
82
+ kinds.SITE: [site_config_file],
83
+ kinds.USER: [legacy_config_file, new_config_file],
84
+ }
85
+
86
+
87
+ class Configuration:
88
+ """Handles management of configuration.
89
+
90
+ Provides an interface to accessing and managing configuration files.
91
+
92
+ This class converts provides an API that takes "section.key-name" style
93
+ keys and stores the value associated with it as "key-name" under the
94
+ section "section".
95
+
96
+ This allows for a clean interface wherein the both the section and the
97
+ key-name are preserved in an easy to manage form in the configuration files
98
+ and the data stored is also nice.
99
+ """
100
+
101
+ def __init__(self, isolated: bool, load_only: Optional[Kind] = None) -> None:
102
+ super().__init__()
103
+
104
+ if load_only is not None and load_only not in VALID_LOAD_ONLY:
105
+ raise ConfigurationError(
106
+ "Got invalid value for load_only - should be one of {}".format(
107
+ ", ".join(map(repr, VALID_LOAD_ONLY))
108
+ )
109
+ )
110
+ self.isolated = isolated
111
+ self.load_only = load_only
112
+
113
+ # Because we keep track of where we got the data from
114
+ self._parsers: Dict[Kind, List[Tuple[str, RawConfigParser]]] = {
115
+ variant: [] for variant in OVERRIDE_ORDER
116
+ }
117
+ self._config: Dict[Kind, Dict[str, Any]] = {
118
+ variant: {} for variant in OVERRIDE_ORDER
119
+ }
120
+ self._modified_parsers: List[Tuple[str, RawConfigParser]] = []
121
+
122
+ def load(self) -> None:
123
+ """Loads configuration from configuration files and environment"""
124
+ self._load_config_files()
125
+ if not self.isolated:
126
+ self._load_environment_vars()
127
+
128
+ def get_file_to_edit(self) -> Optional[str]:
129
+ """Returns the file with highest priority in configuration"""
130
+ assert self.load_only is not None, "Need to be specified a file to be editing"
131
+
132
+ try:
133
+ return self._get_parser_to_modify()[0]
134
+ except IndexError:
135
+ return None
136
+
137
+ def items(self) -> Iterable[Tuple[str, Any]]:
138
+ """Returns key-value pairs like dict.items() representing the loaded
139
+ configuration
140
+ """
141
+ return self._dictionary.items()
142
+
143
+ def get_value(self, key: str) -> Any:
144
+ """Get a value from the configuration."""
145
+ orig_key = key
146
+ key = _normalize_name(key)
147
+ try:
148
+ return self._dictionary[key]
149
+ except KeyError:
150
+ # disassembling triggers a more useful error message than simply
151
+ # "No such key" in the case that the key isn't in the form command.option
152
+ _disassemble_key(key)
153
+ raise ConfigurationError(f"No such key - {orig_key}")
154
+
155
+ def set_value(self, key: str, value: Any) -> None:
156
+ """Modify a value in the configuration."""
157
+ key = _normalize_name(key)
158
+ self._ensure_have_load_only()
159
+
160
+ assert self.load_only
161
+ fname, parser = self._get_parser_to_modify()
162
+
163
+ if parser is not None:
164
+ section, name = _disassemble_key(key)
165
+
166
+ # Modify the parser and the configuration
167
+ if not parser.has_section(section):
168
+ parser.add_section(section)
169
+ parser.set(section, name, value)
170
+
171
+ self._config[self.load_only][key] = value
172
+ self._mark_as_modified(fname, parser)
173
+
174
+ def unset_value(self, key: str) -> None:
175
+ """Unset a value in the configuration."""
176
+ orig_key = key
177
+ key = _normalize_name(key)
178
+ self._ensure_have_load_only()
179
+
180
+ assert self.load_only
181
+ if key not in self._config[self.load_only]:
182
+ raise ConfigurationError(f"No such key - {orig_key}")
183
+
184
+ fname, parser = self._get_parser_to_modify()
185
+
186
+ if parser is not None:
187
+ section, name = _disassemble_key(key)
188
+ if not (
189
+ parser.has_section(section) and parser.remove_option(section, name)
190
+ ):
191
+ # The option was not removed.
192
+ raise ConfigurationError(
193
+ "Fatal Internal error [id=1]. Please report as a bug."
194
+ )
195
+
196
+ # The section may be empty after the option was removed.
197
+ if not parser.items(section):
198
+ parser.remove_section(section)
199
+ self._mark_as_modified(fname, parser)
200
+
201
+ del self._config[self.load_only][key]
202
+
203
+ def save(self) -> None:
204
+ """Save the current in-memory state."""
205
+ self._ensure_have_load_only()
206
+
207
+ for fname, parser in self._modified_parsers:
208
+ logger.info("Writing to %s", fname)
209
+
210
+ # Ensure directory exists.
211
+ ensure_dir(os.path.dirname(fname))
212
+
213
+ # Ensure directory's permission(need to be writeable)
214
+ try:
215
+ with open(fname, "w") as f:
216
+ parser.write(f)
217
+ except OSError as error:
218
+ raise ConfigurationError(
219
+ f"An error occurred while writing to the configuration file "
220
+ f"{fname}: {error}"
221
+ )
222
+
223
+ #
224
+ # Private routines
225
+ #
226
+
227
+ def _ensure_have_load_only(self) -> None:
228
+ if self.load_only is None:
229
+ raise ConfigurationError("Needed a specific file to be modifying.")
230
+ logger.debug("Will be working with %s variant only", self.load_only)
231
+
232
+ @property
233
+ def _dictionary(self) -> Dict[str, Any]:
234
+ """A dictionary representing the loaded configuration."""
235
+ # NOTE: Dictionaries are not populated if not loaded. So, conditionals
236
+ # are not needed here.
237
+ retval = {}
238
+
239
+ for variant in OVERRIDE_ORDER:
240
+ retval.update(self._config[variant])
241
+
242
+ return retval
243
+
244
+ def _load_config_files(self) -> None:
245
+ """Loads configuration from configuration files"""
246
+ config_files = dict(self.iter_config_files())
247
+ if config_files[kinds.ENV][0:1] == [os.devnull]:
248
+ logger.debug(
249
+ "Skipping loading configuration files due to "
250
+ "environment's PIP_CONFIG_FILE being os.devnull"
251
+ )
252
+ return
253
+
254
+ for variant, files in config_files.items():
255
+ for fname in files:
256
+ # If there's specific variant set in `load_only`, load only
257
+ # that variant, not the others.
258
+ if self.load_only is not None and variant != self.load_only:
259
+ logger.debug("Skipping file '%s' (variant: %s)", fname, variant)
260
+ continue
261
+
262
+ parser = self._load_file(variant, fname)
263
+
264
+ # Keeping track of the parsers used
265
+ self._parsers[variant].append((fname, parser))
266
+
267
+ def _load_file(self, variant: Kind, fname: str) -> RawConfigParser:
268
+ logger.verbose("For variant '%s', will try loading '%s'", variant, fname)
269
+ parser = self._construct_parser(fname)
270
+
271
+ for section in parser.sections():
272
+ items = parser.items(section)
273
+ self._config[variant].update(self._normalized_keys(section, items))
274
+
275
+ return parser
276
+
277
+ def _construct_parser(self, fname: str) -> RawConfigParser:
278
+ parser = configparser.RawConfigParser()
279
+ # If there is no such file, don't bother reading it but create the
280
+ # parser anyway, to hold the data.
281
+ # Doing this is useful when modifying and saving files, where we don't
282
+ # need to construct a parser.
283
+ if os.path.exists(fname):
284
+ locale_encoding = locale.getpreferredencoding(False)
285
+ try:
286
+ parser.read(fname, encoding=locale_encoding)
287
+ except UnicodeDecodeError:
288
+ # See https://github.com/pypa/pip/issues/4963
289
+ raise ConfigurationFileCouldNotBeLoaded(
290
+ reason=f"contains invalid {locale_encoding} characters",
291
+ fname=fname,
292
+ )
293
+ except configparser.Error as error:
294
+ # See https://github.com/pypa/pip/issues/4893
295
+ raise ConfigurationFileCouldNotBeLoaded(error=error)
296
+ return parser
297
+
298
+ def _load_environment_vars(self) -> None:
299
+ """Loads configuration from environment variables"""
300
+ self._config[kinds.ENV_VAR].update(
301
+ self._normalized_keys(":env:", self.get_environ_vars())
302
+ )
303
+
304
+ def _normalized_keys(
305
+ self, section: str, items: Iterable[Tuple[str, Any]]
306
+ ) -> Dict[str, Any]:
307
+ """Normalizes items to construct a dictionary with normalized keys.
308
+
309
+ This routine is where the names become keys and are made the same
310
+ regardless of source - configuration files or environment.
311
+ """
312
+ normalized = {}
313
+ for name, val in items:
314
+ key = section + "." + _normalize_name(name)
315
+ normalized[key] = val
316
+ return normalized
317
+
318
+ def get_environ_vars(self) -> Iterable[Tuple[str, str]]:
319
+ """Returns a generator with all environmental vars with prefix PIP_"""
320
+ for key, val in os.environ.items():
321
+ if key.startswith("PIP_"):
322
+ name = key[4:].lower()
323
+ if name not in ENV_NAMES_IGNORED:
324
+ yield name, val
325
+
326
+ # XXX: This is patched in the tests.
327
+ def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]:
328
+ """Yields variant and configuration files associated with it.
329
+
330
+ This should be treated like items of a dictionary. The order
331
+ here doesn't affect what gets overridden. That is controlled
332
+ by OVERRIDE_ORDER. However this does control the order they are
333
+ displayed to the user. It's probably most ergonomic to display
334
+ things in the same order as OVERRIDE_ORDER
335
+ """
336
+ # SMELL: Move the conditions out of this function
337
+
338
+ env_config_file = os.environ.get("PIP_CONFIG_FILE", None)
339
+ config_files = get_configuration_files()
340
+
341
+ yield kinds.GLOBAL, config_files[kinds.GLOBAL]
342
+
343
+ # per-user config is not loaded when env_config_file exists
344
+ should_load_user_config = not self.isolated and not (
345
+ env_config_file and os.path.exists(env_config_file)
346
+ )
347
+ if should_load_user_config:
348
+ # The legacy config file is overridden by the new config file
349
+ yield kinds.USER, config_files[kinds.USER]
350
+
351
+ # virtualenv config
352
+ yield kinds.SITE, config_files[kinds.SITE]
353
+
354
+ if env_config_file is not None:
355
+ yield kinds.ENV, [env_config_file]
356
+ else:
357
+ yield kinds.ENV, []
358
+
359
+ def get_values_in_config(self, variant: Kind) -> Dict[str, Any]:
360
+ """Get values present in a config file"""
361
+ return self._config[variant]
362
+
363
+ def _get_parser_to_modify(self) -> Tuple[str, RawConfigParser]:
364
+ # Determine which parser to modify
365
+ assert self.load_only
366
+ parsers = self._parsers[self.load_only]
367
+ if not parsers:
368
+ # This should not happen if everything works correctly.
369
+ raise ConfigurationError(
370
+ "Fatal Internal error [id=2]. Please report as a bug."
371
+ )
372
+
373
+ # Use the highest priority parser.
374
+ return parsers[-1]
375
+
376
+ # XXX: This is patched in the tests.
377
+ def _mark_as_modified(self, fname: str, parser: RawConfigParser) -> None:
378
+ file_parser_tuple = (fname, parser)
379
+ if file_parser_tuple not in self._modified_parsers:
380
+ self._modified_parsers.append(file_parser_tuple)
381
+
382
+ def __repr__(self) -> str:
383
+ return f"{self.__class__.__name__}({self._dictionary!r})"
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/exceptions.py ADDED
@@ -0,0 +1,809 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Exceptions used throughout package.
2
+
3
+ This module MUST NOT try to import from anything within `pip._internal` to
4
+ operate. This is expected to be importable from any/all files within the
5
+ subpackage and, thus, should not depend on them.
6
+ """
7
+
8
+ import configparser
9
+ import contextlib
10
+ import locale
11
+ import logging
12
+ import pathlib
13
+ import re
14
+ import sys
15
+ from itertools import chain, groupby, repeat
16
+ from typing import TYPE_CHECKING, Dict, Iterator, List, Literal, Optional, Union
17
+
18
+ from pip._vendor.packaging.requirements import InvalidRequirement
19
+ from pip._vendor.packaging.version import InvalidVersion
20
+ from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult
21
+ from pip._vendor.rich.markup import escape
22
+ from pip._vendor.rich.text import Text
23
+
24
+ if TYPE_CHECKING:
25
+ from hashlib import _Hash
26
+
27
+ from pip._vendor.requests.models import Request, Response
28
+
29
+ from pip._internal.metadata import BaseDistribution
30
+ from pip._internal.req.req_install import InstallRequirement
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ #
36
+ # Scaffolding
37
+ #
38
+ def _is_kebab_case(s: str) -> bool:
39
+ return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None
40
+
41
+
42
+ def _prefix_with_indent(
43
+ s: Union[Text, str],
44
+ console: Console,
45
+ *,
46
+ prefix: str,
47
+ indent: str,
48
+ ) -> Text:
49
+ if isinstance(s, Text):
50
+ text = s
51
+ else:
52
+ text = console.render_str(s)
53
+
54
+ return console.render_str(prefix, overflow="ignore") + console.render_str(
55
+ f"\n{indent}", overflow="ignore"
56
+ ).join(text.split(allow_blank=True))
57
+
58
+
59
+ class PipError(Exception):
60
+ """The base pip error."""
61
+
62
+
63
+ class DiagnosticPipError(PipError):
64
+ """An error, that presents diagnostic information to the user.
65
+
66
+ This contains a bunch of logic, to enable pretty presentation of our error
67
+ messages. Each error gets a unique reference. Each error can also include
68
+ additional context, a hint and/or a note -- which are presented with the
69
+ main error message in a consistent style.
70
+
71
+ This is adapted from the error output styling in `sphinx-theme-builder`.
72
+ """
73
+
74
+ reference: str
75
+
76
+ def __init__(
77
+ self,
78
+ *,
79
+ kind: 'Literal["error", "warning"]' = "error",
80
+ reference: Optional[str] = None,
81
+ message: Union[str, Text],
82
+ context: Optional[Union[str, Text]],
83
+ hint_stmt: Optional[Union[str, Text]],
84
+ note_stmt: Optional[Union[str, Text]] = None,
85
+ link: Optional[str] = None,
86
+ ) -> None:
87
+ # Ensure a proper reference is provided.
88
+ if reference is None:
89
+ assert hasattr(self, "reference"), "error reference not provided!"
90
+ reference = self.reference
91
+ assert _is_kebab_case(reference), "error reference must be kebab-case!"
92
+
93
+ self.kind = kind
94
+ self.reference = reference
95
+
96
+ self.message = message
97
+ self.context = context
98
+
99
+ self.note_stmt = note_stmt
100
+ self.hint_stmt = hint_stmt
101
+
102
+ self.link = link
103
+
104
+ super().__init__(f"<{self.__class__.__name__}: {self.reference}>")
105
+
106
+ def __repr__(self) -> str:
107
+ return (
108
+ f"<{self.__class__.__name__}("
109
+ f"reference={self.reference!r}, "
110
+ f"message={self.message!r}, "
111
+ f"context={self.context!r}, "
112
+ f"note_stmt={self.note_stmt!r}, "
113
+ f"hint_stmt={self.hint_stmt!r}"
114
+ ")>"
115
+ )
116
+
117
+ def __rich_console__(
118
+ self,
119
+ console: Console,
120
+ options: ConsoleOptions,
121
+ ) -> RenderResult:
122
+ colour = "red" if self.kind == "error" else "yellow"
123
+
124
+ yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"
125
+ yield ""
126
+
127
+ if not options.ascii_only:
128
+ # Present the main message, with relevant context indented.
129
+ if self.context is not None:
130
+ yield _prefix_with_indent(
131
+ self.message,
132
+ console,
133
+ prefix=f"[{colour}]×[/] ",
134
+ indent=f"[{colour}]│[/] ",
135
+ )
136
+ yield _prefix_with_indent(
137
+ self.context,
138
+ console,
139
+ prefix=f"[{colour}]╰─>[/] ",
140
+ indent=f"[{colour}] [/] ",
141
+ )
142
+ else:
143
+ yield _prefix_with_indent(
144
+ self.message,
145
+ console,
146
+ prefix="[red]×[/] ",
147
+ indent=" ",
148
+ )
149
+ else:
150
+ yield self.message
151
+ if self.context is not None:
152
+ yield ""
153
+ yield self.context
154
+
155
+ if self.note_stmt is not None or self.hint_stmt is not None:
156
+ yield ""
157
+
158
+ if self.note_stmt is not None:
159
+ yield _prefix_with_indent(
160
+ self.note_stmt,
161
+ console,
162
+ prefix="[magenta bold]note[/]: ",
163
+ indent=" ",
164
+ )
165
+ if self.hint_stmt is not None:
166
+ yield _prefix_with_indent(
167
+ self.hint_stmt,
168
+ console,
169
+ prefix="[cyan bold]hint[/]: ",
170
+ indent=" ",
171
+ )
172
+
173
+ if self.link is not None:
174
+ yield ""
175
+ yield f"Link: {self.link}"
176
+
177
+
178
+ #
179
+ # Actual Errors
180
+ #
181
+ class ConfigurationError(PipError):
182
+ """General exception in configuration"""
183
+
184
+
185
+ class InstallationError(PipError):
186
+ """General exception during installation"""
187
+
188
+
189
+ class MissingPyProjectBuildRequires(DiagnosticPipError):
190
+ """Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
191
+
192
+ reference = "missing-pyproject-build-system-requires"
193
+
194
+ def __init__(self, *, package: str) -> None:
195
+ super().__init__(
196
+ message=f"Can not process {escape(package)}",
197
+ context=Text(
198
+ "This package has an invalid pyproject.toml file.\n"
199
+ "The [build-system] table is missing the mandatory `requires` key."
200
+ ),
201
+ note_stmt="This is an issue with the package mentioned above, not pip.",
202
+ hint_stmt=Text("See PEP 518 for the detailed specification."),
203
+ )
204
+
205
+
206
+ class InvalidPyProjectBuildRequires(DiagnosticPipError):
207
+ """Raised when pyproject.toml an invalid `build-system.requires`."""
208
+
209
+ reference = "invalid-pyproject-build-system-requires"
210
+
211
+ def __init__(self, *, package: str, reason: str) -> None:
212
+ super().__init__(
213
+ message=f"Can not process {escape(package)}",
214
+ context=Text(
215
+ "This package has an invalid `build-system.requires` key in "
216
+ f"pyproject.toml.\n{reason}"
217
+ ),
218
+ note_stmt="This is an issue with the package mentioned above, not pip.",
219
+ hint_stmt=Text("See PEP 518 for the detailed specification."),
220
+ )
221
+
222
+
223
+ class NoneMetadataError(PipError):
224
+ """Raised when accessing a Distribution's "METADATA" or "PKG-INFO".
225
+
226
+ This signifies an inconsistency, when the Distribution claims to have
227
+ the metadata file (if not, raise ``FileNotFoundError`` instead), but is
228
+ not actually able to produce its content. This may be due to permission
229
+ errors.
230
+ """
231
+
232
+ def __init__(
233
+ self,
234
+ dist: "BaseDistribution",
235
+ metadata_name: str,
236
+ ) -> None:
237
+ """
238
+ :param dist: A Distribution object.
239
+ :param metadata_name: The name of the metadata being accessed
240
+ (can be "METADATA" or "PKG-INFO").
241
+ """
242
+ self.dist = dist
243
+ self.metadata_name = metadata_name
244
+
245
+ def __str__(self) -> str:
246
+ # Use `dist` in the error message because its stringification
247
+ # includes more information, like the version and location.
248
+ return f"None {self.metadata_name} metadata found for distribution: {self.dist}"
249
+
250
+
251
+ class UserInstallationInvalid(InstallationError):
252
+ """A --user install is requested on an environment without user site."""
253
+
254
+ def __str__(self) -> str:
255
+ return "User base directory is not specified"
256
+
257
+
258
+ class InvalidSchemeCombination(InstallationError):
259
+ def __str__(self) -> str:
260
+ before = ", ".join(str(a) for a in self.args[:-1])
261
+ return f"Cannot set {before} and {self.args[-1]} together"
262
+
263
+
264
+ class DistributionNotFound(InstallationError):
265
+ """Raised when a distribution cannot be found to satisfy a requirement"""
266
+
267
+
268
+ class RequirementsFileParseError(InstallationError):
269
+ """Raised when a general error occurs parsing a requirements file line."""
270
+
271
+
272
+ class BestVersionAlreadyInstalled(PipError):
273
+ """Raised when the most up-to-date version of a package is already
274
+ installed."""
275
+
276
+
277
+ class BadCommand(PipError):
278
+ """Raised when virtualenv or a command is not found"""
279
+
280
+
281
+ class CommandError(PipError):
282
+ """Raised when there is an error in command-line arguments"""
283
+
284
+
285
+ class PreviousBuildDirError(PipError):
286
+ """Raised when there's a previous conflicting build directory"""
287
+
288
+
289
+ class NetworkConnectionError(PipError):
290
+ """HTTP connection error"""
291
+
292
+ def __init__(
293
+ self,
294
+ error_msg: str,
295
+ response: Optional["Response"] = None,
296
+ request: Optional["Request"] = None,
297
+ ) -> None:
298
+ """
299
+ Initialize NetworkConnectionError with `request` and `response`
300
+ objects.
301
+ """
302
+ self.response = response
303
+ self.request = request
304
+ self.error_msg = error_msg
305
+ if (
306
+ self.response is not None
307
+ and not self.request
308
+ and hasattr(response, "request")
309
+ ):
310
+ self.request = self.response.request
311
+ super().__init__(error_msg, response, request)
312
+
313
+ def __str__(self) -> str:
314
+ return str(self.error_msg)
315
+
316
+
317
+ class InvalidWheelFilename(InstallationError):
318
+ """Invalid wheel filename."""
319
+
320
+
321
+ class UnsupportedWheel(InstallationError):
322
+ """Unsupported wheel."""
323
+
324
+
325
+ class InvalidWheel(InstallationError):
326
+ """Invalid (e.g. corrupt) wheel."""
327
+
328
+ def __init__(self, location: str, name: str):
329
+ self.location = location
330
+ self.name = name
331
+
332
+ def __str__(self) -> str:
333
+ return f"Wheel '{self.name}' located at {self.location} is invalid."
334
+
335
+
336
+ class MetadataInconsistent(InstallationError):
337
+ """Built metadata contains inconsistent information.
338
+
339
+ This is raised when the metadata contains values (e.g. name and version)
340
+ that do not match the information previously obtained from sdist filename,
341
+ user-supplied ``#egg=`` value, or an install requirement name.
342
+ """
343
+
344
+ def __init__(
345
+ self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str
346
+ ) -> None:
347
+ self.ireq = ireq
348
+ self.field = field
349
+ self.f_val = f_val
350
+ self.m_val = m_val
351
+
352
+ def __str__(self) -> str:
353
+ return (
354
+ f"Requested {self.ireq} has inconsistent {self.field}: "
355
+ f"expected {self.f_val!r}, but metadata has {self.m_val!r}"
356
+ )
357
+
358
+
359
+ class MetadataInvalid(InstallationError):
360
+ """Metadata is invalid."""
361
+
362
+ def __init__(self, ireq: "InstallRequirement", error: str) -> None:
363
+ self.ireq = ireq
364
+ self.error = error
365
+
366
+ def __str__(self) -> str:
367
+ return f"Requested {self.ireq} has invalid metadata: {self.error}"
368
+
369
+
370
+ class InstallationSubprocessError(DiagnosticPipError, InstallationError):
371
+ """A subprocess call failed."""
372
+
373
+ reference = "subprocess-exited-with-error"
374
+
375
+ def __init__(
376
+ self,
377
+ *,
378
+ command_description: str,
379
+ exit_code: int,
380
+ output_lines: Optional[List[str]],
381
+ ) -> None:
382
+ if output_lines is None:
383
+ output_prompt = Text("See above for output.")
384
+ else:
385
+ output_prompt = (
386
+ Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
387
+ + Text("".join(output_lines))
388
+ + Text.from_markup(R"[red]\[end of output][/]")
389
+ )
390
+
391
+ super().__init__(
392
+ message=(
393
+ f"[green]{escape(command_description)}[/] did not run successfully.\n"
394
+ f"exit code: {exit_code}"
395
+ ),
396
+ context=output_prompt,
397
+ hint_stmt=None,
398
+ note_stmt=(
399
+ "This error originates from a subprocess, and is likely not a "
400
+ "problem with pip."
401
+ ),
402
+ )
403
+
404
+ self.command_description = command_description
405
+ self.exit_code = exit_code
406
+
407
+ def __str__(self) -> str:
408
+ return f"{self.command_description} exited with {self.exit_code}"
409
+
410
+
411
+ class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
412
+ reference = "metadata-generation-failed"
413
+
414
+ def __init__(
415
+ self,
416
+ *,
417
+ package_details: str,
418
+ ) -> None:
419
+ super(InstallationSubprocessError, self).__init__(
420
+ message="Encountered error while generating package metadata.",
421
+ context=escape(package_details),
422
+ hint_stmt="See above for details.",
423
+ note_stmt="This is an issue with the package mentioned above, not pip.",
424
+ )
425
+
426
+ def __str__(self) -> str:
427
+ return "metadata generation failed"
428
+
429
+
430
+ class HashErrors(InstallationError):
431
+ """Multiple HashError instances rolled into one for reporting"""
432
+
433
+ def __init__(self) -> None:
434
+ self.errors: List[HashError] = []
435
+
436
+ def append(self, error: "HashError") -> None:
437
+ self.errors.append(error)
438
+
439
+ def __str__(self) -> str:
440
+ lines = []
441
+ self.errors.sort(key=lambda e: e.order)
442
+ for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):
443
+ lines.append(cls.head)
444
+ lines.extend(e.body() for e in errors_of_cls)
445
+ if lines:
446
+ return "\n".join(lines)
447
+ return ""
448
+
449
+ def __bool__(self) -> bool:
450
+ return bool(self.errors)
451
+
452
+
453
+ class HashError(InstallationError):
454
+ """
455
+ A failure to verify a package against known-good hashes
456
+
457
+ :cvar order: An int sorting hash exception classes by difficulty of
458
+ recovery (lower being harder), so the user doesn't bother fretting
459
+ about unpinned packages when he has deeper issues, like VCS
460
+ dependencies, to deal with. Also keeps error reports in a
461
+ deterministic order.
462
+ :cvar head: A section heading for display above potentially many
463
+ exceptions of this kind
464
+ :ivar req: The InstallRequirement that triggered this error. This is
465
+ pasted on after the exception is instantiated, because it's not
466
+ typically available earlier.
467
+
468
+ """
469
+
470
+ req: Optional["InstallRequirement"] = None
471
+ head = ""
472
+ order: int = -1
473
+
474
+ def body(self) -> str:
475
+ """Return a summary of me for display under the heading.
476
+
477
+ This default implementation simply prints a description of the
478
+ triggering requirement.
479
+
480
+ :param req: The InstallRequirement that provoked this error, with
481
+ its link already populated by the resolver's _populate_link().
482
+
483
+ """
484
+ return f" {self._requirement_name()}"
485
+
486
+ def __str__(self) -> str:
487
+ return f"{self.head}\n{self.body()}"
488
+
489
+ def _requirement_name(self) -> str:
490
+ """Return a description of the requirement that triggered me.
491
+
492
+ This default implementation returns long description of the req, with
493
+ line numbers
494
+
495
+ """
496
+ return str(self.req) if self.req else "unknown package"
497
+
498
+
499
+ class VcsHashUnsupported(HashError):
500
+ """A hash was provided for a version-control-system-based requirement, but
501
+ we don't have a method for hashing those."""
502
+
503
+ order = 0
504
+ head = (
505
+ "Can't verify hashes for these requirements because we don't "
506
+ "have a way to hash version control repositories:"
507
+ )
508
+
509
+
510
+ class DirectoryUrlHashUnsupported(HashError):
511
+ """A hash was provided for a version-control-system-based requirement, but
512
+ we don't have a method for hashing those."""
513
+
514
+ order = 1
515
+ head = (
516
+ "Can't verify hashes for these file:// requirements because they "
517
+ "point to directories:"
518
+ )
519
+
520
+
521
+ class HashMissing(HashError):
522
+ """A hash was needed for a requirement but is absent."""
523
+
524
+ order = 2
525
+ head = (
526
+ "Hashes are required in --require-hashes mode, but they are "
527
+ "missing from some requirements. Here is a list of those "
528
+ "requirements along with the hashes their downloaded archives "
529
+ "actually had. Add lines like these to your requirements files to "
530
+ "prevent tampering. (If you did not enable --require-hashes "
531
+ "manually, note that it turns on automatically when any package "
532
+ "has a hash.)"
533
+ )
534
+
535
+ def __init__(self, gotten_hash: str) -> None:
536
+ """
537
+ :param gotten_hash: The hash of the (possibly malicious) archive we
538
+ just downloaded
539
+ """
540
+ self.gotten_hash = gotten_hash
541
+
542
+ def body(self) -> str:
543
+ # Dodge circular import.
544
+ from pip._internal.utils.hashes import FAVORITE_HASH
545
+
546
+ package = None
547
+ if self.req:
548
+ # In the case of URL-based requirements, display the original URL
549
+ # seen in the requirements file rather than the package name,
550
+ # so the output can be directly copied into the requirements file.
551
+ package = (
552
+ self.req.original_link
553
+ if self.req.is_direct
554
+ # In case someone feeds something downright stupid
555
+ # to InstallRequirement's constructor.
556
+ else getattr(self.req, "req", None)
557
+ )
558
+ return " {} --hash={}:{}".format(
559
+ package or "unknown package", FAVORITE_HASH, self.gotten_hash
560
+ )
561
+
562
+
563
+ class HashUnpinned(HashError):
564
+ """A requirement had a hash specified but was not pinned to a specific
565
+ version."""
566
+
567
+ order = 3
568
+ head = (
569
+ "In --require-hashes mode, all requirements must have their "
570
+ "versions pinned with ==. These do not:"
571
+ )
572
+
573
+
574
+ class HashMismatch(HashError):
575
+ """
576
+ Distribution file hash values don't match.
577
+
578
+ :ivar package_name: The name of the package that triggered the hash
579
+ mismatch. Feel free to write to this after the exception is raise to
580
+ improve its error message.
581
+
582
+ """
583
+
584
+ order = 4
585
+ head = (
586
+ "THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS "
587
+ "FILE. If you have updated the package versions, please update "
588
+ "the hashes. Otherwise, examine the package contents carefully; "
589
+ "someone may have tampered with them."
590
+ )
591
+
592
+ def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:
593
+ """
594
+ :param allowed: A dict of algorithm names pointing to lists of allowed
595
+ hex digests
596
+ :param gots: A dict of algorithm names pointing to hashes we
597
+ actually got from the files under suspicion
598
+ """
599
+ self.allowed = allowed
600
+ self.gots = gots
601
+
602
+ def body(self) -> str:
603
+ return f" {self._requirement_name()}:\n{self._hash_comparison()}"
604
+
605
+ def _hash_comparison(self) -> str:
606
+ """
607
+ Return a comparison of actual and expected hash values.
608
+
609
+ Example::
610
+
611
+ Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
612
+ or 123451234512345123451234512345123451234512345
613
+ Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef
614
+
615
+ """
616
+
617
+ def hash_then_or(hash_name: str) -> "chain[str]":
618
+ # For now, all the decent hashes have 6-char names, so we can get
619
+ # away with hard-coding space literals.
620
+ return chain([hash_name], repeat(" or"))
621
+
622
+ lines: List[str] = []
623
+ for hash_name, expecteds in self.allowed.items():
624
+ prefix = hash_then_or(hash_name)
625
+ lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds)
626
+ lines.append(
627
+ f" Got {self.gots[hash_name].hexdigest()}\n"
628
+ )
629
+ return "\n".join(lines)
630
+
631
+
632
+ class UnsupportedPythonVersion(InstallationError):
633
+ """Unsupported python version according to Requires-Python package
634
+ metadata."""
635
+
636
+
637
+ class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
638
+ """When there are errors while loading a configuration file"""
639
+
640
+ def __init__(
641
+ self,
642
+ reason: str = "could not be loaded",
643
+ fname: Optional[str] = None,
644
+ error: Optional[configparser.Error] = None,
645
+ ) -> None:
646
+ super().__init__(error)
647
+ self.reason = reason
648
+ self.fname = fname
649
+ self.error = error
650
+
651
+ def __str__(self) -> str:
652
+ if self.fname is not None:
653
+ message_part = f" in {self.fname}."
654
+ else:
655
+ assert self.error is not None
656
+ message_part = f".\n{self.error}\n"
657
+ return f"Configuration file {self.reason}{message_part}"
658
+
659
+
660
+ _DEFAULT_EXTERNALLY_MANAGED_ERROR = f"""\
661
+ The Python environment under {sys.prefix} is managed externally, and may not be
662
+ manipulated by the user. Please use specific tooling from the distributor of
663
+ the Python installation to interact with this environment instead.
664
+ """
665
+
666
+
667
+ class ExternallyManagedEnvironment(DiagnosticPipError):
668
+ """The current environment is externally managed.
669
+
670
+ This is raised when the current environment is externally managed, as
671
+ defined by `PEP 668`_. The ``EXTERNALLY-MANAGED`` configuration is checked
672
+ and displayed when the error is bubbled up to the user.
673
+
674
+ :param error: The error message read from ``EXTERNALLY-MANAGED``.
675
+ """
676
+
677
+ reference = "externally-managed-environment"
678
+
679
+ def __init__(self, error: Optional[str]) -> None:
680
+ if error is None:
681
+ context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)
682
+ else:
683
+ context = Text(error)
684
+ super().__init__(
685
+ message="This environment is externally managed",
686
+ context=context,
687
+ note_stmt=(
688
+ "If you believe this is a mistake, please contact your "
689
+ "Python installation or OS distribution provider. "
690
+ "You can override this, at the risk of breaking your Python "
691
+ "installation or OS, by passing --break-system-packages."
692
+ ),
693
+ hint_stmt=Text("See PEP 668 for the detailed specification."),
694
+ )
695
+
696
+ @staticmethod
697
+ def _iter_externally_managed_error_keys() -> Iterator[str]:
698
+ # LC_MESSAGES is in POSIX, but not the C standard. The most common
699
+ # platform that does not implement this category is Windows, where
700
+ # using other categories for console message localization is equally
701
+ # unreliable, so we fall back to the locale-less vendor message. This
702
+ # can always be re-evaluated when a vendor proposes a new alternative.
703
+ try:
704
+ category = locale.LC_MESSAGES
705
+ except AttributeError:
706
+ lang: Optional[str] = None
707
+ else:
708
+ lang, _ = locale.getlocale(category)
709
+ if lang is not None:
710
+ yield f"Error-{lang}"
711
+ for sep in ("-", "_"):
712
+ before, found, _ = lang.partition(sep)
713
+ if not found:
714
+ continue
715
+ yield f"Error-{before}"
716
+ yield "Error"
717
+
718
+ @classmethod
719
+ def from_config(
720
+ cls,
721
+ config: Union[pathlib.Path, str],
722
+ ) -> "ExternallyManagedEnvironment":
723
+ parser = configparser.ConfigParser(interpolation=None)
724
+ try:
725
+ parser.read(config, encoding="utf-8")
726
+ section = parser["externally-managed"]
727
+ for key in cls._iter_externally_managed_error_keys():
728
+ with contextlib.suppress(KeyError):
729
+ return cls(section[key])
730
+ except KeyError:
731
+ pass
732
+ except (OSError, UnicodeDecodeError, configparser.ParsingError):
733
+ from pip._internal.utils._log import VERBOSE
734
+
735
+ exc_info = logger.isEnabledFor(VERBOSE)
736
+ logger.warning("Failed to read %s", config, exc_info=exc_info)
737
+ return cls(None)
738
+
739
+
740
+ class UninstallMissingRecord(DiagnosticPipError):
741
+ reference = "uninstall-no-record-file"
742
+
743
+ def __init__(self, *, distribution: "BaseDistribution") -> None:
744
+ installer = distribution.installer
745
+ if not installer or installer == "pip":
746
+ dep = f"{distribution.raw_name}=={distribution.version}"
747
+ hint = Text.assemble(
748
+ "You might be able to recover from this via: ",
749
+ (f"pip install --force-reinstall --no-deps {dep}", "green"),
750
+ )
751
+ else:
752
+ hint = Text(
753
+ f"The package was installed by {installer}. "
754
+ "You should check if it can uninstall the package."
755
+ )
756
+
757
+ super().__init__(
758
+ message=Text(f"Cannot uninstall {distribution}"),
759
+ context=(
760
+ "The package's contents are unknown: "
761
+ f"no RECORD file was found for {distribution.raw_name}."
762
+ ),
763
+ hint_stmt=hint,
764
+ )
765
+
766
+
767
+ class LegacyDistutilsInstall(DiagnosticPipError):
768
+ reference = "uninstall-distutils-installed-package"
769
+
770
+ def __init__(self, *, distribution: "BaseDistribution") -> None:
771
+ super().__init__(
772
+ message=Text(f"Cannot uninstall {distribution}"),
773
+ context=(
774
+ "It is a distutils installed project and thus we cannot accurately "
775
+ "determine which files belong to it which would lead to only a partial "
776
+ "uninstall."
777
+ ),
778
+ hint_stmt=None,
779
+ )
780
+
781
+
782
+ class InvalidInstalledPackage(DiagnosticPipError):
783
+ reference = "invalid-installed-package"
784
+
785
+ def __init__(
786
+ self,
787
+ *,
788
+ dist: "BaseDistribution",
789
+ invalid_exc: Union[InvalidRequirement, InvalidVersion],
790
+ ) -> None:
791
+ installed_location = dist.installed_location
792
+
793
+ if isinstance(invalid_exc, InvalidRequirement):
794
+ invalid_type = "requirement"
795
+ else:
796
+ invalid_type = "version"
797
+
798
+ super().__init__(
799
+ message=Text(
800
+ f"Cannot process installed package {dist} "
801
+ + (f"in {installed_location!r} " if installed_location else "")
802
+ + f"because it has an invalid {invalid_type}:\n{invalid_exc.args[0]}"
803
+ ),
804
+ context=(
805
+ "Starting with pip 24.1, packages with invalid "
806
+ f"{invalid_type}s can not be processed."
807
+ ),
808
+ hint_stmt="To proceed this package must be uninstalled.",
809
+ )
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/index/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ """Index interaction code
2
+ """
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/index/collector.py ADDED
@@ -0,0 +1,494 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ The main purpose of this module is to expose LinkCollector.collect_sources().
3
+ """
4
+
5
+ import collections
6
+ import email.message
7
+ import functools
8
+ import itertools
9
+ import json
10
+ import logging
11
+ import os
12
+ import urllib.parse
13
+ import urllib.request
14
+ from dataclasses import dataclass
15
+ from html.parser import HTMLParser
16
+ from optparse import Values
17
+ from typing import (
18
+ Callable,
19
+ Dict,
20
+ Iterable,
21
+ List,
22
+ MutableMapping,
23
+ NamedTuple,
24
+ Optional,
25
+ Protocol,
26
+ Sequence,
27
+ Tuple,
28
+ Union,
29
+ )
30
+
31
+ from pip._vendor import requests
32
+ from pip._vendor.requests import Response
33
+ from pip._vendor.requests.exceptions import RetryError, SSLError
34
+
35
+ from pip._internal.exceptions import NetworkConnectionError
36
+ from pip._internal.models.link import Link
37
+ from pip._internal.models.search_scope import SearchScope
38
+ from pip._internal.network.session import PipSession
39
+ from pip._internal.network.utils import raise_for_status
40
+ from pip._internal.utils.filetypes import is_archive_file
41
+ from pip._internal.utils.misc import redact_auth_from_url
42
+ from pip._internal.vcs import vcs
43
+
44
+ from .sources import CandidatesFromPage, LinkSource, build_source
45
+
46
+ logger = logging.getLogger(__name__)
47
+
48
+ ResponseHeaders = MutableMapping[str, str]
49
+
50
+
51
+ def _match_vcs_scheme(url: str) -> Optional[str]:
52
+ """Look for VCS schemes in the URL.
53
+
54
+ Returns the matched VCS scheme, or None if there's no match.
55
+ """
56
+ for scheme in vcs.schemes:
57
+ if url.lower().startswith(scheme) and url[len(scheme)] in "+:":
58
+ return scheme
59
+ return None
60
+
61
+
62
+ class _NotAPIContent(Exception):
63
+ def __init__(self, content_type: str, request_desc: str) -> None:
64
+ super().__init__(content_type, request_desc)
65
+ self.content_type = content_type
66
+ self.request_desc = request_desc
67
+
68
+
69
+ def _ensure_api_header(response: Response) -> None:
70
+ """
71
+ Check the Content-Type header to ensure the response contains a Simple
72
+ API Response.
73
+
74
+ Raises `_NotAPIContent` if the content type is not a valid content-type.
75
+ """
76
+ content_type = response.headers.get("Content-Type", "Unknown")
77
+
78
+ content_type_l = content_type.lower()
79
+ if content_type_l.startswith(
80
+ (
81
+ "text/html",
82
+ "application/vnd.pypi.simple.v1+html",
83
+ "application/vnd.pypi.simple.v1+json",
84
+ )
85
+ ):
86
+ return
87
+
88
+ raise _NotAPIContent(content_type, response.request.method)
89
+
90
+
91
+ class _NotHTTP(Exception):
92
+ pass
93
+
94
+
95
+ def _ensure_api_response(url: str, session: PipSession) -> None:
96
+ """
97
+ Send a HEAD request to the URL, and ensure the response contains a simple
98
+ API Response.
99
+
100
+ Raises `_NotHTTP` if the URL is not available for a HEAD request, or
101
+ `_NotAPIContent` if the content type is not a valid content type.
102
+ """
103
+ scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
104
+ if scheme not in {"http", "https"}:
105
+ raise _NotHTTP()
106
+
107
+ resp = session.head(url, allow_redirects=True)
108
+ raise_for_status(resp)
109
+
110
+ _ensure_api_header(resp)
111
+
112
+
113
+ def _get_simple_response(url: str, session: PipSession) -> Response:
114
+ """Access an Simple API response with GET, and return the response.
115
+
116
+ This consists of three parts:
117
+
118
+ 1. If the URL looks suspiciously like an archive, send a HEAD first to
119
+ check the Content-Type is HTML or Simple API, to avoid downloading a
120
+ large file. Raise `_NotHTTP` if the content type cannot be determined, or
121
+ `_NotAPIContent` if it is not HTML or a Simple API.
122
+ 2. Actually perform the request. Raise HTTP exceptions on network failures.
123
+ 3. Check the Content-Type header to make sure we got a Simple API response,
124
+ and raise `_NotAPIContent` otherwise.
125
+ """
126
+ if is_archive_file(Link(url).filename):
127
+ _ensure_api_response(url, session=session)
128
+
129
+ logger.debug("Getting page %s", redact_auth_from_url(url))
130
+
131
+ resp = session.get(
132
+ url,
133
+ headers={
134
+ "Accept": ", ".join(
135
+ [
136
+ "application/vnd.pypi.simple.v1+json",
137
+ "application/vnd.pypi.simple.v1+html; q=0.1",
138
+ "text/html; q=0.01",
139
+ ]
140
+ ),
141
+ # We don't want to blindly returned cached data for
142
+ # /simple/, because authors generally expecting that
143
+ # twine upload && pip install will function, but if
144
+ # they've done a pip install in the last ~10 minutes
145
+ # it won't. Thus by setting this to zero we will not
146
+ # blindly use any cached data, however the benefit of
147
+ # using max-age=0 instead of no-cache, is that we will
148
+ # still support conditional requests, so we will still
149
+ # minimize traffic sent in cases where the page hasn't
150
+ # changed at all, we will just always incur the round
151
+ # trip for the conditional GET now instead of only
152
+ # once per 10 minutes.
153
+ # For more information, please see pypa/pip#5670.
154
+ "Cache-Control": "max-age=0",
155
+ },
156
+ )
157
+ raise_for_status(resp)
158
+
159
+ # The check for archives above only works if the url ends with
160
+ # something that looks like an archive. However that is not a
161
+ # requirement of an url. Unless we issue a HEAD request on every
162
+ # url we cannot know ahead of time for sure if something is a
163
+ # Simple API response or not. However we can check after we've
164
+ # downloaded it.
165
+ _ensure_api_header(resp)
166
+
167
+ logger.debug(
168
+ "Fetched page %s as %s",
169
+ redact_auth_from_url(url),
170
+ resp.headers.get("Content-Type", "Unknown"),
171
+ )
172
+
173
+ return resp
174
+
175
+
176
+ def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]:
177
+ """Determine if we have any encoding information in our headers."""
178
+ if headers and "Content-Type" in headers:
179
+ m = email.message.Message()
180
+ m["content-type"] = headers["Content-Type"]
181
+ charset = m.get_param("charset")
182
+ if charset:
183
+ return str(charset)
184
+ return None
185
+
186
+
187
+ class CacheablePageContent:
188
+ def __init__(self, page: "IndexContent") -> None:
189
+ assert page.cache_link_parsing
190
+ self.page = page
191
+
192
+ def __eq__(self, other: object) -> bool:
193
+ return isinstance(other, type(self)) and self.page.url == other.page.url
194
+
195
+ def __hash__(self) -> int:
196
+ return hash(self.page.url)
197
+
198
+
199
+ class ParseLinks(Protocol):
200
+ def __call__(self, page: "IndexContent") -> Iterable[Link]: ...
201
+
202
+
203
+ def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
204
+ """
205
+ Given a function that parses an Iterable[Link] from an IndexContent, cache the
206
+ function's result (keyed by CacheablePageContent), unless the IndexContent
207
+ `page` has `page.cache_link_parsing == False`.
208
+ """
209
+
210
+ @functools.lru_cache(maxsize=None)
211
+ def wrapper(cacheable_page: CacheablePageContent) -> List[Link]:
212
+ return list(fn(cacheable_page.page))
213
+
214
+ @functools.wraps(fn)
215
+ def wrapper_wrapper(page: "IndexContent") -> List[Link]:
216
+ if page.cache_link_parsing:
217
+ return wrapper(CacheablePageContent(page))
218
+ return list(fn(page))
219
+
220
+ return wrapper_wrapper
221
+
222
+
223
+ @with_cached_index_content
224
+ def parse_links(page: "IndexContent") -> Iterable[Link]:
225
+ """
226
+ Parse a Simple API's Index Content, and yield its anchor elements as Link objects.
227
+ """
228
+
229
+ content_type_l = page.content_type.lower()
230
+ if content_type_l.startswith("application/vnd.pypi.simple.v1+json"):
231
+ data = json.loads(page.content)
232
+ for file in data.get("files", []):
233
+ link = Link.from_json(file, page.url)
234
+ if link is None:
235
+ continue
236
+ yield link
237
+ return
238
+
239
+ parser = HTMLLinkParser(page.url)
240
+ encoding = page.encoding or "utf-8"
241
+ parser.feed(page.content.decode(encoding))
242
+
243
+ url = page.url
244
+ base_url = parser.base_url or url
245
+ for anchor in parser.anchors:
246
+ link = Link.from_element(anchor, page_url=url, base_url=base_url)
247
+ if link is None:
248
+ continue
249
+ yield link
250
+
251
+
252
+ @dataclass(frozen=True)
253
+ class IndexContent:
254
+ """Represents one response (or page), along with its URL.
255
+
256
+ :param encoding: the encoding to decode the given content.
257
+ :param url: the URL from which the HTML was downloaded.
258
+ :param cache_link_parsing: whether links parsed from this page's url
259
+ should be cached. PyPI index urls should
260
+ have this set to False, for example.
261
+ """
262
+
263
+ content: bytes
264
+ content_type: str
265
+ encoding: Optional[str]
266
+ url: str
267
+ cache_link_parsing: bool = True
268
+
269
+ def __str__(self) -> str:
270
+ return redact_auth_from_url(self.url)
271
+
272
+
273
+ class HTMLLinkParser(HTMLParser):
274
+ """
275
+ HTMLParser that keeps the first base HREF and a list of all anchor
276
+ elements' attributes.
277
+ """
278
+
279
+ def __init__(self, url: str) -> None:
280
+ super().__init__(convert_charrefs=True)
281
+
282
+ self.url: str = url
283
+ self.base_url: Optional[str] = None
284
+ self.anchors: List[Dict[str, Optional[str]]] = []
285
+
286
+ def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None:
287
+ if tag == "base" and self.base_url is None:
288
+ href = self.get_href(attrs)
289
+ if href is not None:
290
+ self.base_url = href
291
+ elif tag == "a":
292
+ self.anchors.append(dict(attrs))
293
+
294
+ def get_href(self, attrs: List[Tuple[str, Optional[str]]]) -> Optional[str]:
295
+ for name, value in attrs:
296
+ if name == "href":
297
+ return value
298
+ return None
299
+
300
+
301
+ def _handle_get_simple_fail(
302
+ link: Link,
303
+ reason: Union[str, Exception],
304
+ meth: Optional[Callable[..., None]] = None,
305
+ ) -> None:
306
+ if meth is None:
307
+ meth = logger.debug
308
+ meth("Could not fetch URL %s: %s - skipping", link, reason)
309
+
310
+
311
+ def _make_index_content(
312
+ response: Response, cache_link_parsing: bool = True
313
+ ) -> IndexContent:
314
+ encoding = _get_encoding_from_headers(response.headers)
315
+ return IndexContent(
316
+ response.content,
317
+ response.headers["Content-Type"],
318
+ encoding=encoding,
319
+ url=response.url,
320
+ cache_link_parsing=cache_link_parsing,
321
+ )
322
+
323
+
324
+ def _get_index_content(link: Link, *, session: PipSession) -> Optional["IndexContent"]:
325
+ url = link.url.split("#", 1)[0]
326
+
327
+ # Check for VCS schemes that do not support lookup as web pages.
328
+ vcs_scheme = _match_vcs_scheme(url)
329
+ if vcs_scheme:
330
+ logger.warning(
331
+ "Cannot look at %s URL %s because it does not support lookup as web pages.",
332
+ vcs_scheme,
333
+ link,
334
+ )
335
+ return None
336
+
337
+ # Tack index.html onto file:// URLs that point to directories
338
+ scheme, _, path, _, _, _ = urllib.parse.urlparse(url)
339
+ if scheme == "file" and os.path.isdir(urllib.request.url2pathname(path)):
340
+ # add trailing slash if not present so urljoin doesn't trim
341
+ # final segment
342
+ if not url.endswith("/"):
343
+ url += "/"
344
+ # TODO: In the future, it would be nice if pip supported PEP 691
345
+ # style responses in the file:// URLs, however there's no
346
+ # standard file extension for application/vnd.pypi.simple.v1+json
347
+ # so we'll need to come up with something on our own.
348
+ url = urllib.parse.urljoin(url, "index.html")
349
+ logger.debug(" file: URL is directory, getting %s", url)
350
+
351
+ try:
352
+ resp = _get_simple_response(url, session=session)
353
+ except _NotHTTP:
354
+ logger.warning(
355
+ "Skipping page %s because it looks like an archive, and cannot "
356
+ "be checked by a HTTP HEAD request.",
357
+ link,
358
+ )
359
+ except _NotAPIContent as exc:
360
+ logger.warning(
361
+ "Skipping page %s because the %s request got Content-Type: %s. "
362
+ "The only supported Content-Types are application/vnd.pypi.simple.v1+json, "
363
+ "application/vnd.pypi.simple.v1+html, and text/html",
364
+ link,
365
+ exc.request_desc,
366
+ exc.content_type,
367
+ )
368
+ except NetworkConnectionError as exc:
369
+ _handle_get_simple_fail(link, exc)
370
+ except RetryError as exc:
371
+ _handle_get_simple_fail(link, exc)
372
+ except SSLError as exc:
373
+ reason = "There was a problem confirming the ssl certificate: "
374
+ reason += str(exc)
375
+ _handle_get_simple_fail(link, reason, meth=logger.info)
376
+ except requests.ConnectionError as exc:
377
+ _handle_get_simple_fail(link, f"connection error: {exc}")
378
+ except requests.Timeout:
379
+ _handle_get_simple_fail(link, "timed out")
380
+ else:
381
+ return _make_index_content(resp, cache_link_parsing=link.cache_link_parsing)
382
+ return None
383
+
384
+
385
+ class CollectedSources(NamedTuple):
386
+ find_links: Sequence[Optional[LinkSource]]
387
+ index_urls: Sequence[Optional[LinkSource]]
388
+
389
+
390
+ class LinkCollector:
391
+ """
392
+ Responsible for collecting Link objects from all configured locations,
393
+ making network requests as needed.
394
+
395
+ The class's main method is its collect_sources() method.
396
+ """
397
+
398
+ def __init__(
399
+ self,
400
+ session: PipSession,
401
+ search_scope: SearchScope,
402
+ ) -> None:
403
+ self.search_scope = search_scope
404
+ self.session = session
405
+
406
+ @classmethod
407
+ def create(
408
+ cls,
409
+ session: PipSession,
410
+ options: Values,
411
+ suppress_no_index: bool = False,
412
+ ) -> "LinkCollector":
413
+ """
414
+ :param session: The Session to use to make requests.
415
+ :param suppress_no_index: Whether to ignore the --no-index option
416
+ when constructing the SearchScope object.
417
+ """
418
+ index_urls = [options.index_url] + options.extra_index_urls
419
+ if options.no_index and not suppress_no_index:
420
+ logger.debug(
421
+ "Ignoring indexes: %s",
422
+ ",".join(redact_auth_from_url(url) for url in index_urls),
423
+ )
424
+ index_urls = []
425
+
426
+ # Make sure find_links is a list before passing to create().
427
+ find_links = options.find_links or []
428
+
429
+ search_scope = SearchScope.create(
430
+ find_links=find_links,
431
+ index_urls=index_urls,
432
+ no_index=options.no_index,
433
+ )
434
+ link_collector = LinkCollector(
435
+ session=session,
436
+ search_scope=search_scope,
437
+ )
438
+ return link_collector
439
+
440
+ @property
441
+ def find_links(self) -> List[str]:
442
+ return self.search_scope.find_links
443
+
444
+ def fetch_response(self, location: Link) -> Optional[IndexContent]:
445
+ """
446
+ Fetch an HTML page containing package links.
447
+ """
448
+ return _get_index_content(location, session=self.session)
449
+
450
+ def collect_sources(
451
+ self,
452
+ project_name: str,
453
+ candidates_from_page: CandidatesFromPage,
454
+ ) -> CollectedSources:
455
+ # The OrderedDict calls deduplicate sources by URL.
456
+ index_url_sources = collections.OrderedDict(
457
+ build_source(
458
+ loc,
459
+ candidates_from_page=candidates_from_page,
460
+ page_validator=self.session.is_secure_origin,
461
+ expand_dir=False,
462
+ cache_link_parsing=False,
463
+ project_name=project_name,
464
+ )
465
+ for loc in self.search_scope.get_index_urls_locations(project_name)
466
+ ).values()
467
+ find_links_sources = collections.OrderedDict(
468
+ build_source(
469
+ loc,
470
+ candidates_from_page=candidates_from_page,
471
+ page_validator=self.session.is_secure_origin,
472
+ expand_dir=True,
473
+ cache_link_parsing=True,
474
+ project_name=project_name,
475
+ )
476
+ for loc in self.find_links
477
+ ).values()
478
+
479
+ if logger.isEnabledFor(logging.DEBUG):
480
+ lines = [
481
+ f"* {s.link}"
482
+ for s in itertools.chain(find_links_sources, index_url_sources)
483
+ if s is not None and s.link is not None
484
+ ]
485
+ lines = [
486
+ f"{len(lines)} location(s) to search "
487
+ f"for versions of {project_name}:"
488
+ ] + lines
489
+ logger.debug("\n".join(lines))
490
+
491
+ return CollectedSources(
492
+ find_links=list(find_links_sources),
493
+ index_urls=list(index_url_sources),
494
+ )
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/index/package_finder.py ADDED
@@ -0,0 +1,1029 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Routines related to PyPI, indexes"""
2
+
3
+ import enum
4
+ import functools
5
+ import itertools
6
+ import logging
7
+ import re
8
+ from dataclasses import dataclass
9
+ from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Set, Tuple, Union
10
+
11
+ from pip._vendor.packaging import specifiers
12
+ from pip._vendor.packaging.tags import Tag
13
+ from pip._vendor.packaging.utils import canonicalize_name
14
+ from pip._vendor.packaging.version import InvalidVersion, _BaseVersion
15
+ from pip._vendor.packaging.version import parse as parse_version
16
+
17
+ from pip._internal.exceptions import (
18
+ BestVersionAlreadyInstalled,
19
+ DistributionNotFound,
20
+ InvalidWheelFilename,
21
+ UnsupportedWheel,
22
+ )
23
+ from pip._internal.index.collector import LinkCollector, parse_links
24
+ from pip._internal.models.candidate import InstallationCandidate
25
+ from pip._internal.models.format_control import FormatControl
26
+ from pip._internal.models.link import Link
27
+ from pip._internal.models.search_scope import SearchScope
28
+ from pip._internal.models.selection_prefs import SelectionPreferences
29
+ from pip._internal.models.target_python import TargetPython
30
+ from pip._internal.models.wheel import Wheel
31
+ from pip._internal.req import InstallRequirement
32
+ from pip._internal.utils._log import getLogger
33
+ from pip._internal.utils.filetypes import WHEEL_EXTENSION
34
+ from pip._internal.utils.hashes import Hashes
35
+ from pip._internal.utils.logging import indent_log
36
+ from pip._internal.utils.misc import build_netloc
37
+ from pip._internal.utils.packaging import check_requires_python
38
+ from pip._internal.utils.unpacking import SUPPORTED_EXTENSIONS
39
+
40
+ if TYPE_CHECKING:
41
+ from pip._vendor.typing_extensions import TypeGuard
42
+
43
+ __all__ = ["FormatControl", "BestCandidateResult", "PackageFinder"]
44
+
45
+
46
+ logger = getLogger(__name__)
47
+
48
+ BuildTag = Union[Tuple[()], Tuple[int, str]]
49
+ CandidateSortingKey = Tuple[int, int, int, _BaseVersion, Optional[int], BuildTag]
50
+
51
+
52
+ def _check_link_requires_python(
53
+ link: Link,
54
+ version_info: Tuple[int, int, int],
55
+ ignore_requires_python: bool = False,
56
+ ) -> bool:
57
+ """
58
+ Return whether the given Python version is compatible with a link's
59
+ "Requires-Python" value.
60
+
61
+ :param version_info: A 3-tuple of ints representing the Python
62
+ major-minor-micro version to check.
63
+ :param ignore_requires_python: Whether to ignore the "Requires-Python"
64
+ value if the given Python version isn't compatible.
65
+ """
66
+ try:
67
+ is_compatible = check_requires_python(
68
+ link.requires_python,
69
+ version_info=version_info,
70
+ )
71
+ except specifiers.InvalidSpecifier:
72
+ logger.debug(
73
+ "Ignoring invalid Requires-Python (%r) for link: %s",
74
+ link.requires_python,
75
+ link,
76
+ )
77
+ else:
78
+ if not is_compatible:
79
+ version = ".".join(map(str, version_info))
80
+ if not ignore_requires_python:
81
+ logger.verbose(
82
+ "Link requires a different Python (%s not in: %r): %s",
83
+ version,
84
+ link.requires_python,
85
+ link,
86
+ )
87
+ return False
88
+
89
+ logger.debug(
90
+ "Ignoring failed Requires-Python check (%s not in: %r) for link: %s",
91
+ version,
92
+ link.requires_python,
93
+ link,
94
+ )
95
+
96
+ return True
97
+
98
+
99
+ class LinkType(enum.Enum):
100
+ candidate = enum.auto()
101
+ different_project = enum.auto()
102
+ yanked = enum.auto()
103
+ format_unsupported = enum.auto()
104
+ format_invalid = enum.auto()
105
+ platform_mismatch = enum.auto()
106
+ requires_python_mismatch = enum.auto()
107
+
108
+
109
+ class LinkEvaluator:
110
+ """
111
+ Responsible for evaluating links for a particular project.
112
+ """
113
+
114
+ _py_version_re = re.compile(r"-py([123]\.?[0-9]?)$")
115
+
116
+ # Don't include an allow_yanked default value to make sure each call
117
+ # site considers whether yanked releases are allowed. This also causes
118
+ # that decision to be made explicit in the calling code, which helps
119
+ # people when reading the code.
120
+ def __init__(
121
+ self,
122
+ project_name: str,
123
+ canonical_name: str,
124
+ formats: FrozenSet[str],
125
+ target_python: TargetPython,
126
+ allow_yanked: bool,
127
+ ignore_requires_python: Optional[bool] = None,
128
+ ) -> None:
129
+ """
130
+ :param project_name: The user supplied package name.
131
+ :param canonical_name: The canonical package name.
132
+ :param formats: The formats allowed for this package. Should be a set
133
+ with 'binary' or 'source' or both in it.
134
+ :param target_python: The target Python interpreter to use when
135
+ evaluating link compatibility. This is used, for example, to
136
+ check wheel compatibility, as well as when checking the Python
137
+ version, e.g. the Python version embedded in a link filename
138
+ (or egg fragment) and against an HTML link's optional PEP 503
139
+ "data-requires-python" attribute.
140
+ :param allow_yanked: Whether files marked as yanked (in the sense
141
+ of PEP 592) are permitted to be candidates for install.
142
+ :param ignore_requires_python: Whether to ignore incompatible
143
+ PEP 503 "data-requires-python" values in HTML links. Defaults
144
+ to False.
145
+ """
146
+ if ignore_requires_python is None:
147
+ ignore_requires_python = False
148
+
149
+ self._allow_yanked = allow_yanked
150
+ self._canonical_name = canonical_name
151
+ self._ignore_requires_python = ignore_requires_python
152
+ self._formats = formats
153
+ self._target_python = target_python
154
+
155
+ self.project_name = project_name
156
+
157
+ def evaluate_link(self, link: Link) -> Tuple[LinkType, str]:
158
+ """
159
+ Determine whether a link is a candidate for installation.
160
+
161
+ :return: A tuple (result, detail), where *result* is an enum
162
+ representing whether the evaluation found a candidate, or the reason
163
+ why one is not found. If a candidate is found, *detail* will be the
164
+ candidate's version string; if one is not found, it contains the
165
+ reason the link fails to qualify.
166
+ """
167
+ version = None
168
+ if link.is_yanked and not self._allow_yanked:
169
+ reason = link.yanked_reason or "<none given>"
170
+ return (LinkType.yanked, f"yanked for reason: {reason}")
171
+
172
+ if link.egg_fragment:
173
+ egg_info = link.egg_fragment
174
+ ext = link.ext
175
+ else:
176
+ egg_info, ext = link.splitext()
177
+ if not ext:
178
+ return (LinkType.format_unsupported, "not a file")
179
+ if ext not in SUPPORTED_EXTENSIONS:
180
+ return (
181
+ LinkType.format_unsupported,
182
+ f"unsupported archive format: {ext}",
183
+ )
184
+ if "binary" not in self._formats and ext == WHEEL_EXTENSION:
185
+ reason = f"No binaries permitted for {self.project_name}"
186
+ return (LinkType.format_unsupported, reason)
187
+ if "macosx10" in link.path and ext == ".zip":
188
+ return (LinkType.format_unsupported, "macosx10 one")
189
+ if ext == WHEEL_EXTENSION:
190
+ try:
191
+ wheel = Wheel(link.filename)
192
+ except InvalidWheelFilename:
193
+ return (
194
+ LinkType.format_invalid,
195
+ "invalid wheel filename",
196
+ )
197
+ if canonicalize_name(wheel.name) != self._canonical_name:
198
+ reason = f"wrong project name (not {self.project_name})"
199
+ return (LinkType.different_project, reason)
200
+
201
+ supported_tags = self._target_python.get_unsorted_tags()
202
+ if not wheel.supported(supported_tags):
203
+ # Include the wheel's tags in the reason string to
204
+ # simplify troubleshooting compatibility issues.
205
+ file_tags = ", ".join(wheel.get_formatted_file_tags())
206
+ reason = (
207
+ f"none of the wheel's tags ({file_tags}) are compatible "
208
+ f"(run pip debug --verbose to show compatible tags)"
209
+ )
210
+ return (LinkType.platform_mismatch, reason)
211
+
212
+ version = wheel.version
213
+
214
+ # This should be up by the self.ok_binary check, but see issue 2700.
215
+ if "source" not in self._formats and ext != WHEEL_EXTENSION:
216
+ reason = f"No sources permitted for {self.project_name}"
217
+ return (LinkType.format_unsupported, reason)
218
+
219
+ if not version:
220
+ version = _extract_version_from_fragment(
221
+ egg_info,
222
+ self._canonical_name,
223
+ )
224
+ if not version:
225
+ reason = f"Missing project version for {self.project_name}"
226
+ return (LinkType.format_invalid, reason)
227
+
228
+ match = self._py_version_re.search(version)
229
+ if match:
230
+ version = version[: match.start()]
231
+ py_version = match.group(1)
232
+ if py_version != self._target_python.py_version:
233
+ return (
234
+ LinkType.platform_mismatch,
235
+ "Python version is incorrect",
236
+ )
237
+
238
+ supports_python = _check_link_requires_python(
239
+ link,
240
+ version_info=self._target_python.py_version_info,
241
+ ignore_requires_python=self._ignore_requires_python,
242
+ )
243
+ if not supports_python:
244
+ reason = f"{version} Requires-Python {link.requires_python}"
245
+ return (LinkType.requires_python_mismatch, reason)
246
+
247
+ logger.debug("Found link %s, version: %s", link, version)
248
+
249
+ return (LinkType.candidate, version)
250
+
251
+
252
+ def filter_unallowed_hashes(
253
+ candidates: List[InstallationCandidate],
254
+ hashes: Optional[Hashes],
255
+ project_name: str,
256
+ ) -> List[InstallationCandidate]:
257
+ """
258
+ Filter out candidates whose hashes aren't allowed, and return a new
259
+ list of candidates.
260
+
261
+ If at least one candidate has an allowed hash, then all candidates with
262
+ either an allowed hash or no hash specified are returned. Otherwise,
263
+ the given candidates are returned.
264
+
265
+ Including the candidates with no hash specified when there is a match
266
+ allows a warning to be logged if there is a more preferred candidate
267
+ with no hash specified. Returning all candidates in the case of no
268
+ matches lets pip report the hash of the candidate that would otherwise
269
+ have been installed (e.g. permitting the user to more easily update
270
+ their requirements file with the desired hash).
271
+ """
272
+ if not hashes:
273
+ logger.debug(
274
+ "Given no hashes to check %s links for project %r: "
275
+ "discarding no candidates",
276
+ len(candidates),
277
+ project_name,
278
+ )
279
+ # Make sure we're not returning back the given value.
280
+ return list(candidates)
281
+
282
+ matches_or_no_digest = []
283
+ # Collect the non-matches for logging purposes.
284
+ non_matches = []
285
+ match_count = 0
286
+ for candidate in candidates:
287
+ link = candidate.link
288
+ if not link.has_hash:
289
+ pass
290
+ elif link.is_hash_allowed(hashes=hashes):
291
+ match_count += 1
292
+ else:
293
+ non_matches.append(candidate)
294
+ continue
295
+
296
+ matches_or_no_digest.append(candidate)
297
+
298
+ if match_count:
299
+ filtered = matches_or_no_digest
300
+ else:
301
+ # Make sure we're not returning back the given value.
302
+ filtered = list(candidates)
303
+
304
+ if len(filtered) == len(candidates):
305
+ discard_message = "discarding no candidates"
306
+ else:
307
+ discard_message = "discarding {} non-matches:\n {}".format(
308
+ len(non_matches),
309
+ "\n ".join(str(candidate.link) for candidate in non_matches),
310
+ )
311
+
312
+ logger.debug(
313
+ "Checked %s links for project %r against %s hashes "
314
+ "(%s matches, %s no digest): %s",
315
+ len(candidates),
316
+ project_name,
317
+ hashes.digest_count,
318
+ match_count,
319
+ len(matches_or_no_digest) - match_count,
320
+ discard_message,
321
+ )
322
+
323
+ return filtered
324
+
325
+
326
+ @dataclass
327
+ class CandidatePreferences:
328
+ """
329
+ Encapsulates some of the preferences for filtering and sorting
330
+ InstallationCandidate objects.
331
+ """
332
+
333
+ prefer_binary: bool = False
334
+ allow_all_prereleases: bool = False
335
+
336
+
337
+ @dataclass(frozen=True)
338
+ class BestCandidateResult:
339
+ """A collection of candidates, returned by `PackageFinder.find_best_candidate`.
340
+
341
+ This class is only intended to be instantiated by CandidateEvaluator's
342
+ `compute_best_candidate()` method.
343
+
344
+ :param all_candidates: A sequence of all available candidates found.
345
+ :param applicable_candidates: The applicable candidates.
346
+ :param best_candidate: The most preferred candidate found, or None
347
+ if no applicable candidates were found.
348
+ """
349
+
350
+ all_candidates: List[InstallationCandidate]
351
+ applicable_candidates: List[InstallationCandidate]
352
+ best_candidate: Optional[InstallationCandidate]
353
+
354
+ def __post_init__(self) -> None:
355
+ assert set(self.applicable_candidates) <= set(self.all_candidates)
356
+
357
+ if self.best_candidate is None:
358
+ assert not self.applicable_candidates
359
+ else:
360
+ assert self.best_candidate in self.applicable_candidates
361
+
362
+
363
+ class CandidateEvaluator:
364
+ """
365
+ Responsible for filtering and sorting candidates for installation based
366
+ on what tags are valid.
367
+ """
368
+
369
+ @classmethod
370
+ def create(
371
+ cls,
372
+ project_name: str,
373
+ target_python: Optional[TargetPython] = None,
374
+ prefer_binary: bool = False,
375
+ allow_all_prereleases: bool = False,
376
+ specifier: Optional[specifiers.BaseSpecifier] = None,
377
+ hashes: Optional[Hashes] = None,
378
+ ) -> "CandidateEvaluator":
379
+ """Create a CandidateEvaluator object.
380
+
381
+ :param target_python: The target Python interpreter to use when
382
+ checking compatibility. If None (the default), a TargetPython
383
+ object will be constructed from the running Python.
384
+ :param specifier: An optional object implementing `filter`
385
+ (e.g. `packaging.specifiers.SpecifierSet`) to filter applicable
386
+ versions.
387
+ :param hashes: An optional collection of allowed hashes.
388
+ """
389
+ if target_python is None:
390
+ target_python = TargetPython()
391
+ if specifier is None:
392
+ specifier = specifiers.SpecifierSet()
393
+
394
+ supported_tags = target_python.get_sorted_tags()
395
+
396
+ return cls(
397
+ project_name=project_name,
398
+ supported_tags=supported_tags,
399
+ specifier=specifier,
400
+ prefer_binary=prefer_binary,
401
+ allow_all_prereleases=allow_all_prereleases,
402
+ hashes=hashes,
403
+ )
404
+
405
+ def __init__(
406
+ self,
407
+ project_name: str,
408
+ supported_tags: List[Tag],
409
+ specifier: specifiers.BaseSpecifier,
410
+ prefer_binary: bool = False,
411
+ allow_all_prereleases: bool = False,
412
+ hashes: Optional[Hashes] = None,
413
+ ) -> None:
414
+ """
415
+ :param supported_tags: The PEP 425 tags supported by the target
416
+ Python in order of preference (most preferred first).
417
+ """
418
+ self._allow_all_prereleases = allow_all_prereleases
419
+ self._hashes = hashes
420
+ self._prefer_binary = prefer_binary
421
+ self._project_name = project_name
422
+ self._specifier = specifier
423
+ self._supported_tags = supported_tags
424
+ # Since the index of the tag in the _supported_tags list is used
425
+ # as a priority, precompute a map from tag to index/priority to be
426
+ # used in wheel.find_most_preferred_tag.
427
+ self._wheel_tag_preferences = {
428
+ tag: idx for idx, tag in enumerate(supported_tags)
429
+ }
430
+
431
+ def get_applicable_candidates(
432
+ self,
433
+ candidates: List[InstallationCandidate],
434
+ ) -> List[InstallationCandidate]:
435
+ """
436
+ Return the applicable candidates from a list of candidates.
437
+ """
438
+ # Using None infers from the specifier instead.
439
+ allow_prereleases = self._allow_all_prereleases or None
440
+ specifier = self._specifier
441
+
442
+ # We turn the version object into a str here because otherwise
443
+ # when we're debundled but setuptools isn't, Python will see
444
+ # packaging.version.Version and
445
+ # pkg_resources._vendor.packaging.version.Version as different
446
+ # types. This way we'll use a str as a common data interchange
447
+ # format. If we stop using the pkg_resources provided specifier
448
+ # and start using our own, we can drop the cast to str().
449
+ candidates_and_versions = [(c, str(c.version)) for c in candidates]
450
+ versions = set(
451
+ specifier.filter(
452
+ (v for _, v in candidates_and_versions),
453
+ prereleases=allow_prereleases,
454
+ )
455
+ )
456
+
457
+ applicable_candidates = [c for c, v in candidates_and_versions if v in versions]
458
+ filtered_applicable_candidates = filter_unallowed_hashes(
459
+ candidates=applicable_candidates,
460
+ hashes=self._hashes,
461
+ project_name=self._project_name,
462
+ )
463
+
464
+ return sorted(filtered_applicable_candidates, key=self._sort_key)
465
+
466
+ def _sort_key(self, candidate: InstallationCandidate) -> CandidateSortingKey:
467
+ """
468
+ Function to pass as the `key` argument to a call to sorted() to sort
469
+ InstallationCandidates by preference.
470
+
471
+ Returns a tuple such that tuples sorting as greater using Python's
472
+ default comparison operator are more preferred.
473
+
474
+ The preference is as follows:
475
+
476
+ First and foremost, candidates with allowed (matching) hashes are
477
+ always preferred over candidates without matching hashes. This is
478
+ because e.g. if the only candidate with an allowed hash is yanked,
479
+ we still want to use that candidate.
480
+
481
+ Second, excepting hash considerations, candidates that have been
482
+ yanked (in the sense of PEP 592) are always less preferred than
483
+ candidates that haven't been yanked. Then:
484
+
485
+ If not finding wheels, they are sorted by version only.
486
+ If finding wheels, then the sort order is by version, then:
487
+ 1. existing installs
488
+ 2. wheels ordered via Wheel.support_index_min(self._supported_tags)
489
+ 3. source archives
490
+ If prefer_binary was set, then all wheels are sorted above sources.
491
+
492
+ Note: it was considered to embed this logic into the Link
493
+ comparison operators, but then different sdist links
494
+ with the same version, would have to be considered equal
495
+ """
496
+ valid_tags = self._supported_tags
497
+ support_num = len(valid_tags)
498
+ build_tag: BuildTag = ()
499
+ binary_preference = 0
500
+ link = candidate.link
501
+ if link.is_wheel:
502
+ # can raise InvalidWheelFilename
503
+ wheel = Wheel(link.filename)
504
+ try:
505
+ pri = -(
506
+ wheel.find_most_preferred_tag(
507
+ valid_tags, self._wheel_tag_preferences
508
+ )
509
+ )
510
+ except ValueError:
511
+ raise UnsupportedWheel(
512
+ f"{wheel.filename} is not a supported wheel for this platform. It "
513
+ "can't be sorted."
514
+ )
515
+ if self._prefer_binary:
516
+ binary_preference = 1
517
+ if wheel.build_tag is not None:
518
+ match = re.match(r"^(\d+)(.*)$", wheel.build_tag)
519
+ assert match is not None, "guaranteed by filename validation"
520
+ build_tag_groups = match.groups()
521
+ build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
522
+ else: # sdist
523
+ pri = -(support_num)
524
+ has_allowed_hash = int(link.is_hash_allowed(self._hashes))
525
+ yank_value = -1 * int(link.is_yanked) # -1 for yanked.
526
+ return (
527
+ has_allowed_hash,
528
+ yank_value,
529
+ binary_preference,
530
+ candidate.version,
531
+ pri,
532
+ build_tag,
533
+ )
534
+
535
+ def sort_best_candidate(
536
+ self,
537
+ candidates: List[InstallationCandidate],
538
+ ) -> Optional[InstallationCandidate]:
539
+ """
540
+ Return the best candidate per the instance's sort order, or None if
541
+ no candidate is acceptable.
542
+ """
543
+ if not candidates:
544
+ return None
545
+ best_candidate = max(candidates, key=self._sort_key)
546
+ return best_candidate
547
+
548
+ def compute_best_candidate(
549
+ self,
550
+ candidates: List[InstallationCandidate],
551
+ ) -> BestCandidateResult:
552
+ """
553
+ Compute and return a `BestCandidateResult` instance.
554
+ """
555
+ applicable_candidates = self.get_applicable_candidates(candidates)
556
+
557
+ best_candidate = self.sort_best_candidate(applicable_candidates)
558
+
559
+ return BestCandidateResult(
560
+ candidates,
561
+ applicable_candidates=applicable_candidates,
562
+ best_candidate=best_candidate,
563
+ )
564
+
565
+
566
+ class PackageFinder:
567
+ """This finds packages.
568
+
569
+ This is meant to match easy_install's technique for looking for
570
+ packages, by reading pages and looking for appropriate links.
571
+ """
572
+
573
+ def __init__(
574
+ self,
575
+ link_collector: LinkCollector,
576
+ target_python: TargetPython,
577
+ allow_yanked: bool,
578
+ format_control: Optional[FormatControl] = None,
579
+ candidate_prefs: Optional[CandidatePreferences] = None,
580
+ ignore_requires_python: Optional[bool] = None,
581
+ ) -> None:
582
+ """
583
+ This constructor is primarily meant to be used by the create() class
584
+ method and from tests.
585
+
586
+ :param format_control: A FormatControl object, used to control
587
+ the selection of source packages / binary packages when consulting
588
+ the index and links.
589
+ :param candidate_prefs: Options to use when creating a
590
+ CandidateEvaluator object.
591
+ """
592
+ if candidate_prefs is None:
593
+ candidate_prefs = CandidatePreferences()
594
+
595
+ format_control = format_control or FormatControl(set(), set())
596
+
597
+ self._allow_yanked = allow_yanked
598
+ self._candidate_prefs = candidate_prefs
599
+ self._ignore_requires_python = ignore_requires_python
600
+ self._link_collector = link_collector
601
+ self._target_python = target_python
602
+
603
+ self.format_control = format_control
604
+
605
+ # These are boring links that have already been logged somehow.
606
+ self._logged_links: Set[Tuple[Link, LinkType, str]] = set()
607
+
608
+ # Don't include an allow_yanked default value to make sure each call
609
+ # site considers whether yanked releases are allowed. This also causes
610
+ # that decision to be made explicit in the calling code, which helps
611
+ # people when reading the code.
612
+ @classmethod
613
+ def create(
614
+ cls,
615
+ link_collector: LinkCollector,
616
+ selection_prefs: SelectionPreferences,
617
+ target_python: Optional[TargetPython] = None,
618
+ ) -> "PackageFinder":
619
+ """Create a PackageFinder.
620
+
621
+ :param selection_prefs: The candidate selection preferences, as a
622
+ SelectionPreferences object.
623
+ :param target_python: The target Python interpreter to use when
624
+ checking compatibility. If None (the default), a TargetPython
625
+ object will be constructed from the running Python.
626
+ """
627
+ if target_python is None:
628
+ target_python = TargetPython()
629
+
630
+ candidate_prefs = CandidatePreferences(
631
+ prefer_binary=selection_prefs.prefer_binary,
632
+ allow_all_prereleases=selection_prefs.allow_all_prereleases,
633
+ )
634
+
635
+ return cls(
636
+ candidate_prefs=candidate_prefs,
637
+ link_collector=link_collector,
638
+ target_python=target_python,
639
+ allow_yanked=selection_prefs.allow_yanked,
640
+ format_control=selection_prefs.format_control,
641
+ ignore_requires_python=selection_prefs.ignore_requires_python,
642
+ )
643
+
644
+ @property
645
+ def target_python(self) -> TargetPython:
646
+ return self._target_python
647
+
648
+ @property
649
+ def search_scope(self) -> SearchScope:
650
+ return self._link_collector.search_scope
651
+
652
+ @search_scope.setter
653
+ def search_scope(self, search_scope: SearchScope) -> None:
654
+ self._link_collector.search_scope = search_scope
655
+
656
+ @property
657
+ def find_links(self) -> List[str]:
658
+ return self._link_collector.find_links
659
+
660
+ @property
661
+ def index_urls(self) -> List[str]:
662
+ return self.search_scope.index_urls
663
+
664
+ @property
665
+ def proxy(self) -> Optional[str]:
666
+ return self._link_collector.session.pip_proxy
667
+
668
+ @property
669
+ def trusted_hosts(self) -> Iterable[str]:
670
+ for host_port in self._link_collector.session.pip_trusted_origins:
671
+ yield build_netloc(*host_port)
672
+
673
+ @property
674
+ def custom_cert(self) -> Optional[str]:
675
+ # session.verify is either a boolean (use default bundle/no SSL
676
+ # verification) or a string path to a custom CA bundle to use. We only
677
+ # care about the latter.
678
+ verify = self._link_collector.session.verify
679
+ return verify if isinstance(verify, str) else None
680
+
681
+ @property
682
+ def client_cert(self) -> Optional[str]:
683
+ cert = self._link_collector.session.cert
684
+ assert not isinstance(cert, tuple), "pip only supports PEM client certs"
685
+ return cert
686
+
687
+ @property
688
+ def allow_all_prereleases(self) -> bool:
689
+ return self._candidate_prefs.allow_all_prereleases
690
+
691
+ def set_allow_all_prereleases(self) -> None:
692
+ self._candidate_prefs.allow_all_prereleases = True
693
+
694
+ @property
695
+ def prefer_binary(self) -> bool:
696
+ return self._candidate_prefs.prefer_binary
697
+
698
+ def set_prefer_binary(self) -> None:
699
+ self._candidate_prefs.prefer_binary = True
700
+
701
+ def requires_python_skipped_reasons(self) -> List[str]:
702
+ reasons = {
703
+ detail
704
+ for _, result, detail in self._logged_links
705
+ if result == LinkType.requires_python_mismatch
706
+ }
707
+ return sorted(reasons)
708
+
709
+ def make_link_evaluator(self, project_name: str) -> LinkEvaluator:
710
+ canonical_name = canonicalize_name(project_name)
711
+ formats = self.format_control.get_allowed_formats(canonical_name)
712
+
713
+ return LinkEvaluator(
714
+ project_name=project_name,
715
+ canonical_name=canonical_name,
716
+ formats=formats,
717
+ target_python=self._target_python,
718
+ allow_yanked=self._allow_yanked,
719
+ ignore_requires_python=self._ignore_requires_python,
720
+ )
721
+
722
+ def _sort_links(self, links: Iterable[Link]) -> List[Link]:
723
+ """
724
+ Returns elements of links in order, non-egg links first, egg links
725
+ second, while eliminating duplicates
726
+ """
727
+ eggs, no_eggs = [], []
728
+ seen: Set[Link] = set()
729
+ for link in links:
730
+ if link not in seen:
731
+ seen.add(link)
732
+ if link.egg_fragment:
733
+ eggs.append(link)
734
+ else:
735
+ no_eggs.append(link)
736
+ return no_eggs + eggs
737
+
738
+ def _log_skipped_link(self, link: Link, result: LinkType, detail: str) -> None:
739
+ # This is a hot method so don't waste time hashing links unless we're
740
+ # actually going to log 'em.
741
+ if not logger.isEnabledFor(logging.DEBUG):
742
+ return
743
+
744
+ entry = (link, result, detail)
745
+ if entry not in self._logged_links:
746
+ # Put the link at the end so the reason is more visible and because
747
+ # the link string is usually very long.
748
+ logger.debug("Skipping link: %s: %s", detail, link)
749
+ self._logged_links.add(entry)
750
+
751
+ def get_install_candidate(
752
+ self, link_evaluator: LinkEvaluator, link: Link
753
+ ) -> Optional[InstallationCandidate]:
754
+ """
755
+ If the link is a candidate for install, convert it to an
756
+ InstallationCandidate and return it. Otherwise, return None.
757
+ """
758
+ result, detail = link_evaluator.evaluate_link(link)
759
+ if result != LinkType.candidate:
760
+ self._log_skipped_link(link, result, detail)
761
+ return None
762
+
763
+ try:
764
+ return InstallationCandidate(
765
+ name=link_evaluator.project_name,
766
+ link=link,
767
+ version=detail,
768
+ )
769
+ except InvalidVersion:
770
+ return None
771
+
772
+ def evaluate_links(
773
+ self, link_evaluator: LinkEvaluator, links: Iterable[Link]
774
+ ) -> List[InstallationCandidate]:
775
+ """
776
+ Convert links that are candidates to InstallationCandidate objects.
777
+ """
778
+ candidates = []
779
+ for link in self._sort_links(links):
780
+ candidate = self.get_install_candidate(link_evaluator, link)
781
+ if candidate is not None:
782
+ candidates.append(candidate)
783
+
784
+ return candidates
785
+
786
+ def process_project_url(
787
+ self, project_url: Link, link_evaluator: LinkEvaluator
788
+ ) -> List[InstallationCandidate]:
789
+ logger.debug(
790
+ "Fetching project page and analyzing links: %s",
791
+ project_url,
792
+ )
793
+ index_response = self._link_collector.fetch_response(project_url)
794
+ if index_response is None:
795
+ return []
796
+
797
+ page_links = list(parse_links(index_response))
798
+
799
+ with indent_log():
800
+ package_links = self.evaluate_links(
801
+ link_evaluator,
802
+ links=page_links,
803
+ )
804
+
805
+ return package_links
806
+
807
+ @functools.lru_cache(maxsize=None)
808
+ def find_all_candidates(self, project_name: str) -> List[InstallationCandidate]:
809
+ """Find all available InstallationCandidate for project_name
810
+
811
+ This checks index_urls and find_links.
812
+ All versions found are returned as an InstallationCandidate list.
813
+
814
+ See LinkEvaluator.evaluate_link() for details on which files
815
+ are accepted.
816
+ """
817
+ link_evaluator = self.make_link_evaluator(project_name)
818
+
819
+ collected_sources = self._link_collector.collect_sources(
820
+ project_name=project_name,
821
+ candidates_from_page=functools.partial(
822
+ self.process_project_url,
823
+ link_evaluator=link_evaluator,
824
+ ),
825
+ )
826
+
827
+ page_candidates_it = itertools.chain.from_iterable(
828
+ source.page_candidates()
829
+ for sources in collected_sources
830
+ for source in sources
831
+ if source is not None
832
+ )
833
+ page_candidates = list(page_candidates_it)
834
+
835
+ file_links_it = itertools.chain.from_iterable(
836
+ source.file_links()
837
+ for sources in collected_sources
838
+ for source in sources
839
+ if source is not None
840
+ )
841
+ file_candidates = self.evaluate_links(
842
+ link_evaluator,
843
+ sorted(file_links_it, reverse=True),
844
+ )
845
+
846
+ if logger.isEnabledFor(logging.DEBUG) and file_candidates:
847
+ paths = []
848
+ for candidate in file_candidates:
849
+ assert candidate.link.url # we need to have a URL
850
+ try:
851
+ paths.append(candidate.link.file_path)
852
+ except Exception:
853
+ paths.append(candidate.link.url) # it's not a local file
854
+
855
+ logger.debug("Local files found: %s", ", ".join(paths))
856
+
857
+ # This is an intentional priority ordering
858
+ return file_candidates + page_candidates
859
+
860
+ def make_candidate_evaluator(
861
+ self,
862
+ project_name: str,
863
+ specifier: Optional[specifiers.BaseSpecifier] = None,
864
+ hashes: Optional[Hashes] = None,
865
+ ) -> CandidateEvaluator:
866
+ """Create a CandidateEvaluator object to use."""
867
+ candidate_prefs = self._candidate_prefs
868
+ return CandidateEvaluator.create(
869
+ project_name=project_name,
870
+ target_python=self._target_python,
871
+ prefer_binary=candidate_prefs.prefer_binary,
872
+ allow_all_prereleases=candidate_prefs.allow_all_prereleases,
873
+ specifier=specifier,
874
+ hashes=hashes,
875
+ )
876
+
877
+ @functools.lru_cache(maxsize=None)
878
+ def find_best_candidate(
879
+ self,
880
+ project_name: str,
881
+ specifier: Optional[specifiers.BaseSpecifier] = None,
882
+ hashes: Optional[Hashes] = None,
883
+ ) -> BestCandidateResult:
884
+ """Find matches for the given project and specifier.
885
+
886
+ :param specifier: An optional object implementing `filter`
887
+ (e.g. `packaging.specifiers.SpecifierSet`) to filter applicable
888
+ versions.
889
+
890
+ :return: A `BestCandidateResult` instance.
891
+ """
892
+ candidates = self.find_all_candidates(project_name)
893
+ candidate_evaluator = self.make_candidate_evaluator(
894
+ project_name=project_name,
895
+ specifier=specifier,
896
+ hashes=hashes,
897
+ )
898
+ return candidate_evaluator.compute_best_candidate(candidates)
899
+
900
+ def find_requirement(
901
+ self, req: InstallRequirement, upgrade: bool
902
+ ) -> Optional[InstallationCandidate]:
903
+ """Try to find a Link matching req
904
+
905
+ Expects req, an InstallRequirement and upgrade, a boolean
906
+ Returns a InstallationCandidate if found,
907
+ Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise
908
+ """
909
+ hashes = req.hashes(trust_internet=False)
910
+ best_candidate_result = self.find_best_candidate(
911
+ req.name,
912
+ specifier=req.specifier,
913
+ hashes=hashes,
914
+ )
915
+ best_candidate = best_candidate_result.best_candidate
916
+
917
+ installed_version: Optional[_BaseVersion] = None
918
+ if req.satisfied_by is not None:
919
+ installed_version = req.satisfied_by.version
920
+
921
+ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str:
922
+ # This repeated parse_version and str() conversion is needed to
923
+ # handle different vendoring sources from pip and pkg_resources.
924
+ # If we stop using the pkg_resources provided specifier and start
925
+ # using our own, we can drop the cast to str().
926
+ return (
927
+ ", ".join(
928
+ sorted(
929
+ {str(c.version) for c in cand_iter},
930
+ key=parse_version,
931
+ )
932
+ )
933
+ or "none"
934
+ )
935
+
936
+ if installed_version is None and best_candidate is None:
937
+ logger.critical(
938
+ "Could not find a version that satisfies the requirement %s "
939
+ "(from versions: %s)",
940
+ req,
941
+ _format_versions(best_candidate_result.all_candidates),
942
+ )
943
+
944
+ raise DistributionNotFound(f"No matching distribution found for {req}")
945
+
946
+ def _should_install_candidate(
947
+ candidate: Optional[InstallationCandidate],
948
+ ) -> "TypeGuard[InstallationCandidate]":
949
+ if installed_version is None:
950
+ return True
951
+ if best_candidate is None:
952
+ return False
953
+ return best_candidate.version > installed_version
954
+
955
+ if not upgrade and installed_version is not None:
956
+ if _should_install_candidate(best_candidate):
957
+ logger.debug(
958
+ "Existing installed version (%s) satisfies requirement "
959
+ "(most up-to-date version is %s)",
960
+ installed_version,
961
+ best_candidate.version,
962
+ )
963
+ else:
964
+ logger.debug(
965
+ "Existing installed version (%s) is most up-to-date and "
966
+ "satisfies requirement",
967
+ installed_version,
968
+ )
969
+ return None
970
+
971
+ if _should_install_candidate(best_candidate):
972
+ logger.debug(
973
+ "Using version %s (newest of versions: %s)",
974
+ best_candidate.version,
975
+ _format_versions(best_candidate_result.applicable_candidates),
976
+ )
977
+ return best_candidate
978
+
979
+ # We have an existing version, and its the best version
980
+ logger.debug(
981
+ "Installed version (%s) is most up-to-date (past versions: %s)",
982
+ installed_version,
983
+ _format_versions(best_candidate_result.applicable_candidates),
984
+ )
985
+ raise BestVersionAlreadyInstalled
986
+
987
+
988
+ def _find_name_version_sep(fragment: str, canonical_name: str) -> int:
989
+ """Find the separator's index based on the package's canonical name.
990
+
991
+ :param fragment: A <package>+<version> filename "fragment" (stem) or
992
+ egg fragment.
993
+ :param canonical_name: The package's canonical name.
994
+
995
+ This function is needed since the canonicalized name does not necessarily
996
+ have the same length as the egg info's name part. An example::
997
+
998
+ >>> fragment = 'foo__bar-1.0'
999
+ >>> canonical_name = 'foo-bar'
1000
+ >>> _find_name_version_sep(fragment, canonical_name)
1001
+ 8
1002
+ """
1003
+ # Project name and version must be separated by one single dash. Find all
1004
+ # occurrences of dashes; if the string in front of it matches the canonical
1005
+ # name, this is the one separating the name and version parts.
1006
+ for i, c in enumerate(fragment):
1007
+ if c != "-":
1008
+ continue
1009
+ if canonicalize_name(fragment[:i]) == canonical_name:
1010
+ return i
1011
+ raise ValueError(f"{fragment} does not match {canonical_name}")
1012
+
1013
+
1014
+ def _extract_version_from_fragment(fragment: str, canonical_name: str) -> Optional[str]:
1015
+ """Parse the version string from a <package>+<version> filename
1016
+ "fragment" (stem) or egg fragment.
1017
+
1018
+ :param fragment: The string to parse. E.g. foo-2.1
1019
+ :param canonical_name: The canonicalized name of the package this
1020
+ belongs to.
1021
+ """
1022
+ try:
1023
+ version_start = _find_name_version_sep(fragment, canonical_name) + 1
1024
+ except ValueError:
1025
+ return None
1026
+ version = fragment[version_start:]
1027
+ if not version:
1028
+ return None
1029
+ return version
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/index/sources.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import mimetypes
3
+ import os
4
+ from collections import defaultdict
5
+ from typing import Callable, Dict, Iterable, List, Optional, Tuple
6
+
7
+ from pip._vendor.packaging.utils import (
8
+ InvalidSdistFilename,
9
+ InvalidWheelFilename,
10
+ canonicalize_name,
11
+ parse_sdist_filename,
12
+ parse_wheel_filename,
13
+ )
14
+
15
+ from pip._internal.models.candidate import InstallationCandidate
16
+ from pip._internal.models.link import Link
17
+ from pip._internal.utils.urls import path_to_url, url_to_path
18
+ from pip._internal.vcs import is_url
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ FoundCandidates = Iterable[InstallationCandidate]
23
+ FoundLinks = Iterable[Link]
24
+ CandidatesFromPage = Callable[[Link], Iterable[InstallationCandidate]]
25
+ PageValidator = Callable[[Link], bool]
26
+
27
+
28
+ class LinkSource:
29
+ @property
30
+ def link(self) -> Optional[Link]:
31
+ """Returns the underlying link, if there's one."""
32
+ raise NotImplementedError()
33
+
34
+ def page_candidates(self) -> FoundCandidates:
35
+ """Candidates found by parsing an archive listing HTML file."""
36
+ raise NotImplementedError()
37
+
38
+ def file_links(self) -> FoundLinks:
39
+ """Links found by specifying archives directly."""
40
+ raise NotImplementedError()
41
+
42
+
43
+ def _is_html_file(file_url: str) -> bool:
44
+ return mimetypes.guess_type(file_url, strict=False)[0] == "text/html"
45
+
46
+
47
+ class _FlatDirectoryToUrls:
48
+ """Scans directory and caches results"""
49
+
50
+ def __init__(self, path: str) -> None:
51
+ self._path = path
52
+ self._page_candidates: List[str] = []
53
+ self._project_name_to_urls: Dict[str, List[str]] = defaultdict(list)
54
+ self._scanned_directory = False
55
+
56
+ def _scan_directory(self) -> None:
57
+ """Scans directory once and populates both page_candidates
58
+ and project_name_to_urls at the same time
59
+ """
60
+ for entry in os.scandir(self._path):
61
+ url = path_to_url(entry.path)
62
+ if _is_html_file(url):
63
+ self._page_candidates.append(url)
64
+ continue
65
+
66
+ # File must have a valid wheel or sdist name,
67
+ # otherwise not worth considering as a package
68
+ try:
69
+ project_filename = parse_wheel_filename(entry.name)[0]
70
+ except InvalidWheelFilename:
71
+ try:
72
+ project_filename = parse_sdist_filename(entry.name)[0]
73
+ except InvalidSdistFilename:
74
+ continue
75
+
76
+ self._project_name_to_urls[project_filename].append(url)
77
+ self._scanned_directory = True
78
+
79
+ @property
80
+ def page_candidates(self) -> List[str]:
81
+ if not self._scanned_directory:
82
+ self._scan_directory()
83
+
84
+ return self._page_candidates
85
+
86
+ @property
87
+ def project_name_to_urls(self) -> Dict[str, List[str]]:
88
+ if not self._scanned_directory:
89
+ self._scan_directory()
90
+
91
+ return self._project_name_to_urls
92
+
93
+
94
+ class _FlatDirectorySource(LinkSource):
95
+ """Link source specified by ``--find-links=<path-to-dir>``.
96
+
97
+ This looks the content of the directory, and returns:
98
+
99
+ * ``page_candidates``: Links listed on each HTML file in the directory.
100
+ * ``file_candidates``: Archives in the directory.
101
+ """
102
+
103
+ _paths_to_urls: Dict[str, _FlatDirectoryToUrls] = {}
104
+
105
+ def __init__(
106
+ self,
107
+ candidates_from_page: CandidatesFromPage,
108
+ path: str,
109
+ project_name: str,
110
+ ) -> None:
111
+ self._candidates_from_page = candidates_from_page
112
+ self._project_name = canonicalize_name(project_name)
113
+
114
+ # Get existing instance of _FlatDirectoryToUrls if it exists
115
+ if path in self._paths_to_urls:
116
+ self._path_to_urls = self._paths_to_urls[path]
117
+ else:
118
+ self._path_to_urls = _FlatDirectoryToUrls(path=path)
119
+ self._paths_to_urls[path] = self._path_to_urls
120
+
121
+ @property
122
+ def link(self) -> Optional[Link]:
123
+ return None
124
+
125
+ def page_candidates(self) -> FoundCandidates:
126
+ for url in self._path_to_urls.page_candidates:
127
+ yield from self._candidates_from_page(Link(url))
128
+
129
+ def file_links(self) -> FoundLinks:
130
+ for url in self._path_to_urls.project_name_to_urls[self._project_name]:
131
+ yield Link(url)
132
+
133
+
134
+ class _LocalFileSource(LinkSource):
135
+ """``--find-links=<path-or-url>`` or ``--[extra-]index-url=<path-or-url>``.
136
+
137
+ If a URL is supplied, it must be a ``file:`` URL. If a path is supplied to
138
+ the option, it is converted to a URL first. This returns:
139
+
140
+ * ``page_candidates``: Links listed on an HTML file.
141
+ * ``file_candidates``: The non-HTML file.
142
+ """
143
+
144
+ def __init__(
145
+ self,
146
+ candidates_from_page: CandidatesFromPage,
147
+ link: Link,
148
+ ) -> None:
149
+ self._candidates_from_page = candidates_from_page
150
+ self._link = link
151
+
152
+ @property
153
+ def link(self) -> Optional[Link]:
154
+ return self._link
155
+
156
+ def page_candidates(self) -> FoundCandidates:
157
+ if not _is_html_file(self._link.url):
158
+ return
159
+ yield from self._candidates_from_page(self._link)
160
+
161
+ def file_links(self) -> FoundLinks:
162
+ if _is_html_file(self._link.url):
163
+ return
164
+ yield self._link
165
+
166
+
167
+ class _RemoteFileSource(LinkSource):
168
+ """``--find-links=<url>`` or ``--[extra-]index-url=<url>``.
169
+
170
+ This returns:
171
+
172
+ * ``page_candidates``: Links listed on an HTML file.
173
+ * ``file_candidates``: The non-HTML file.
174
+ """
175
+
176
+ def __init__(
177
+ self,
178
+ candidates_from_page: CandidatesFromPage,
179
+ page_validator: PageValidator,
180
+ link: Link,
181
+ ) -> None:
182
+ self._candidates_from_page = candidates_from_page
183
+ self._page_validator = page_validator
184
+ self._link = link
185
+
186
+ @property
187
+ def link(self) -> Optional[Link]:
188
+ return self._link
189
+
190
+ def page_candidates(self) -> FoundCandidates:
191
+ if not self._page_validator(self._link):
192
+ return
193
+ yield from self._candidates_from_page(self._link)
194
+
195
+ def file_links(self) -> FoundLinks:
196
+ yield self._link
197
+
198
+
199
+ class _IndexDirectorySource(LinkSource):
200
+ """``--[extra-]index-url=<path-to-directory>``.
201
+
202
+ This is treated like a remote URL; ``candidates_from_page`` contains logic
203
+ for this by appending ``index.html`` to the link.
204
+ """
205
+
206
+ def __init__(
207
+ self,
208
+ candidates_from_page: CandidatesFromPage,
209
+ link: Link,
210
+ ) -> None:
211
+ self._candidates_from_page = candidates_from_page
212
+ self._link = link
213
+
214
+ @property
215
+ def link(self) -> Optional[Link]:
216
+ return self._link
217
+
218
+ def page_candidates(self) -> FoundCandidates:
219
+ yield from self._candidates_from_page(self._link)
220
+
221
+ def file_links(self) -> FoundLinks:
222
+ return ()
223
+
224
+
225
+ def build_source(
226
+ location: str,
227
+ *,
228
+ candidates_from_page: CandidatesFromPage,
229
+ page_validator: PageValidator,
230
+ expand_dir: bool,
231
+ cache_link_parsing: bool,
232
+ project_name: str,
233
+ ) -> Tuple[Optional[str], Optional[LinkSource]]:
234
+ path: Optional[str] = None
235
+ url: Optional[str] = None
236
+ if os.path.exists(location): # Is a local path.
237
+ url = path_to_url(location)
238
+ path = location
239
+ elif location.startswith("file:"): # A file: URL.
240
+ url = location
241
+ path = url_to_path(location)
242
+ elif is_url(location):
243
+ url = location
244
+
245
+ if url is None:
246
+ msg = (
247
+ "Location '%s' is ignored: "
248
+ "it is either a non-existing path or lacks a specific scheme."
249
+ )
250
+ logger.warning(msg, location)
251
+ return (None, None)
252
+
253
+ if path is None:
254
+ source: LinkSource = _RemoteFileSource(
255
+ candidates_from_page=candidates_from_page,
256
+ page_validator=page_validator,
257
+ link=Link(url, cache_link_parsing=cache_link_parsing),
258
+ )
259
+ return (url, source)
260
+
261
+ if os.path.isdir(path):
262
+ if expand_dir:
263
+ source = _FlatDirectorySource(
264
+ candidates_from_page=candidates_from_page,
265
+ path=path,
266
+ project_name=project_name,
267
+ )
268
+ else:
269
+ source = _IndexDirectorySource(
270
+ candidates_from_page=candidates_from_page,
271
+ link=Link(url, cache_link_parsing=cache_link_parsing),
272
+ )
273
+ return (url, source)
274
+ elif os.path.isfile(path):
275
+ source = _LocalFileSource(
276
+ candidates_from_page=candidates_from_page,
277
+ link=Link(url, cache_link_parsing=cache_link_parsing),
278
+ )
279
+ return (url, source)
280
+ logger.warning(
281
+ "Location '%s' is ignored: it is neither a file nor a directory.",
282
+ location,
283
+ )
284
+ return (url, None)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/locations/__init__.py ADDED
@@ -0,0 +1,456 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import logging
3
+ import os
4
+ import pathlib
5
+ import sys
6
+ import sysconfig
7
+ from typing import Any, Dict, Generator, Optional, Tuple
8
+
9
+ from pip._internal.models.scheme import SCHEME_KEYS, Scheme
10
+ from pip._internal.utils.compat import WINDOWS
11
+ from pip._internal.utils.deprecation import deprecated
12
+ from pip._internal.utils.virtualenv import running_under_virtualenv
13
+
14
+ from . import _sysconfig
15
+ from .base import (
16
+ USER_CACHE_DIR,
17
+ get_major_minor_version,
18
+ get_src_prefix,
19
+ is_osx_framework,
20
+ site_packages,
21
+ user_site,
22
+ )
23
+
24
+ __all__ = [
25
+ "USER_CACHE_DIR",
26
+ "get_bin_prefix",
27
+ "get_bin_user",
28
+ "get_major_minor_version",
29
+ "get_platlib",
30
+ "get_purelib",
31
+ "get_scheme",
32
+ "get_src_prefix",
33
+ "site_packages",
34
+ "user_site",
35
+ ]
36
+
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+
41
+ _PLATLIBDIR: str = getattr(sys, "platlibdir", "lib")
42
+
43
+ _USE_SYSCONFIG_DEFAULT = sys.version_info >= (3, 10)
44
+
45
+
46
+ def _should_use_sysconfig() -> bool:
47
+ """This function determines the value of _USE_SYSCONFIG.
48
+
49
+ By default, pip uses sysconfig on Python 3.10+.
50
+ But Python distributors can override this decision by setting:
51
+ sysconfig._PIP_USE_SYSCONFIG = True / False
52
+ Rationale in https://github.com/pypa/pip/issues/10647
53
+
54
+ This is a function for testability, but should be constant during any one
55
+ run.
56
+ """
57
+ return bool(getattr(sysconfig, "_PIP_USE_SYSCONFIG", _USE_SYSCONFIG_DEFAULT))
58
+
59
+
60
+ _USE_SYSCONFIG = _should_use_sysconfig()
61
+
62
+ if not _USE_SYSCONFIG:
63
+ # Import distutils lazily to avoid deprecation warnings,
64
+ # but import it soon enough that it is in memory and available during
65
+ # a pip reinstall.
66
+ from . import _distutils
67
+
68
+ # Be noisy about incompatibilities if this platforms "should" be using
69
+ # sysconfig, but is explicitly opting out and using distutils instead.
70
+ if _USE_SYSCONFIG_DEFAULT and not _USE_SYSCONFIG:
71
+ _MISMATCH_LEVEL = logging.WARNING
72
+ else:
73
+ _MISMATCH_LEVEL = logging.DEBUG
74
+
75
+
76
+ def _looks_like_bpo_44860() -> bool:
77
+ """The resolution to bpo-44860 will change this incorrect platlib.
78
+
79
+ See <https://bugs.python.org/issue44860>.
80
+ """
81
+ from distutils.command.install import INSTALL_SCHEMES
82
+
83
+ try:
84
+ unix_user_platlib = INSTALL_SCHEMES["unix_user"]["platlib"]
85
+ except KeyError:
86
+ return False
87
+ return unix_user_platlib == "$usersite"
88
+
89
+
90
+ def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool:
91
+ platlib = scheme["platlib"]
92
+ if "/$platlibdir/" in platlib:
93
+ platlib = platlib.replace("/$platlibdir/", f"/{_PLATLIBDIR}/")
94
+ if "/lib64/" not in platlib:
95
+ return False
96
+ unpatched = platlib.replace("/lib64/", "/lib/")
97
+ return unpatched.replace("$platbase/", "$base/") == scheme["purelib"]
98
+
99
+
100
+ @functools.lru_cache(maxsize=None)
101
+ def _looks_like_red_hat_lib() -> bool:
102
+ """Red Hat patches platlib in unix_prefix and unix_home, but not purelib.
103
+
104
+ This is the only way I can see to tell a Red Hat-patched Python.
105
+ """
106
+ from distutils.command.install import INSTALL_SCHEMES
107
+
108
+ return all(
109
+ k in INSTALL_SCHEMES
110
+ and _looks_like_red_hat_patched_platlib_purelib(INSTALL_SCHEMES[k])
111
+ for k in ("unix_prefix", "unix_home")
112
+ )
113
+
114
+
115
+ @functools.lru_cache(maxsize=None)
116
+ def _looks_like_debian_scheme() -> bool:
117
+ """Debian adds two additional schemes."""
118
+ from distutils.command.install import INSTALL_SCHEMES
119
+
120
+ return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES
121
+
122
+
123
+ @functools.lru_cache(maxsize=None)
124
+ def _looks_like_red_hat_scheme() -> bool:
125
+ """Red Hat patches ``sys.prefix`` and ``sys.exec_prefix``.
126
+
127
+ Red Hat's ``00251-change-user-install-location.patch`` changes the install
128
+ command's ``prefix`` and ``exec_prefix`` to append ``"/local"``. This is
129
+ (fortunately?) done quite unconditionally, so we create a default command
130
+ object without any configuration to detect this.
131
+ """
132
+ from distutils.command.install import install
133
+ from distutils.dist import Distribution
134
+
135
+ cmd: Any = install(Distribution())
136
+ cmd.finalize_options()
137
+ return (
138
+ cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local"
139
+ and cmd.prefix == f"{os.path.normpath(sys.prefix)}/local"
140
+ )
141
+
142
+
143
+ @functools.lru_cache(maxsize=None)
144
+ def _looks_like_slackware_scheme() -> bool:
145
+ """Slackware patches sysconfig but fails to patch distutils and site.
146
+
147
+ Slackware changes sysconfig's user scheme to use ``"lib64"`` for the lib
148
+ path, but does not do the same to the site module.
149
+ """
150
+ if user_site is None: # User-site not available.
151
+ return False
152
+ try:
153
+ paths = sysconfig.get_paths(scheme="posix_user", expand=False)
154
+ except KeyError: # User-site not available.
155
+ return False
156
+ return "/lib64/" in paths["purelib"] and "/lib64/" not in user_site
157
+
158
+
159
+ @functools.lru_cache(maxsize=None)
160
+ def _looks_like_msys2_mingw_scheme() -> bool:
161
+ """MSYS2 patches distutils and sysconfig to use a UNIX-like scheme.
162
+
163
+ However, MSYS2 incorrectly patches sysconfig ``nt`` scheme. The fix is
164
+ likely going to be included in their 3.10 release, so we ignore the warning.
165
+ See msys2/MINGW-packages#9319.
166
+
167
+ MSYS2 MINGW's patch uses lowercase ``"lib"`` instead of the usual uppercase,
168
+ and is missing the final ``"site-packages"``.
169
+ """
170
+ paths = sysconfig.get_paths("nt", expand=False)
171
+ return all(
172
+ "Lib" not in p and "lib" in p and not p.endswith("site-packages")
173
+ for p in (paths[key] for key in ("platlib", "purelib"))
174
+ )
175
+
176
+
177
+ def _fix_abiflags(parts: Tuple[str]) -> Generator[str, None, None]:
178
+ ldversion = sysconfig.get_config_var("LDVERSION")
179
+ abiflags = getattr(sys, "abiflags", None)
180
+
181
+ # LDVERSION does not end with sys.abiflags. Just return the path unchanged.
182
+ if not ldversion or not abiflags or not ldversion.endswith(abiflags):
183
+ yield from parts
184
+ return
185
+
186
+ # Strip sys.abiflags from LDVERSION-based path components.
187
+ for part in parts:
188
+ if part.endswith(ldversion):
189
+ part = part[: (0 - len(abiflags))]
190
+ yield part
191
+
192
+
193
+ @functools.lru_cache(maxsize=None)
194
+ def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None:
195
+ issue_url = "https://github.com/pypa/pip/issues/10151"
196
+ message = (
197
+ "Value for %s does not match. Please report this to <%s>"
198
+ "\ndistutils: %s"
199
+ "\nsysconfig: %s"
200
+ )
201
+ logger.log(_MISMATCH_LEVEL, message, key, issue_url, old, new)
202
+
203
+
204
+ def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool:
205
+ if old == new:
206
+ return False
207
+ _warn_mismatched(old, new, key=key)
208
+ return True
209
+
210
+
211
+ @functools.lru_cache(maxsize=None)
212
+ def _log_context(
213
+ *,
214
+ user: bool = False,
215
+ home: Optional[str] = None,
216
+ root: Optional[str] = None,
217
+ prefix: Optional[str] = None,
218
+ ) -> None:
219
+ parts = [
220
+ "Additional context:",
221
+ "user = %r",
222
+ "home = %r",
223
+ "root = %r",
224
+ "prefix = %r",
225
+ ]
226
+
227
+ logger.log(_MISMATCH_LEVEL, "\n".join(parts), user, home, root, prefix)
228
+
229
+
230
+ def get_scheme(
231
+ dist_name: str,
232
+ user: bool = False,
233
+ home: Optional[str] = None,
234
+ root: Optional[str] = None,
235
+ isolated: bool = False,
236
+ prefix: Optional[str] = None,
237
+ ) -> Scheme:
238
+ new = _sysconfig.get_scheme(
239
+ dist_name,
240
+ user=user,
241
+ home=home,
242
+ root=root,
243
+ isolated=isolated,
244
+ prefix=prefix,
245
+ )
246
+ if _USE_SYSCONFIG:
247
+ return new
248
+
249
+ old = _distutils.get_scheme(
250
+ dist_name,
251
+ user=user,
252
+ home=home,
253
+ root=root,
254
+ isolated=isolated,
255
+ prefix=prefix,
256
+ )
257
+
258
+ warning_contexts = []
259
+ for k in SCHEME_KEYS:
260
+ old_v = pathlib.Path(getattr(old, k))
261
+ new_v = pathlib.Path(getattr(new, k))
262
+
263
+ if old_v == new_v:
264
+ continue
265
+
266
+ # distutils incorrectly put PyPy packages under ``site-packages/python``
267
+ # in the ``posix_home`` scheme, but PyPy devs said they expect the
268
+ # directory name to be ``pypy`` instead. So we treat this as a bug fix
269
+ # and not warn about it. See bpo-43307 and python/cpython#24628.
270
+ skip_pypy_special_case = (
271
+ sys.implementation.name == "pypy"
272
+ and home is not None
273
+ and k in ("platlib", "purelib")
274
+ and old_v.parent == new_v.parent
275
+ and old_v.name.startswith("python")
276
+ and new_v.name.startswith("pypy")
277
+ )
278
+ if skip_pypy_special_case:
279
+ continue
280
+
281
+ # sysconfig's ``osx_framework_user`` does not include ``pythonX.Y`` in
282
+ # the ``include`` value, but distutils's ``headers`` does. We'll let
283
+ # CPython decide whether this is a bug or feature. See bpo-43948.
284
+ skip_osx_framework_user_special_case = (
285
+ user
286
+ and is_osx_framework()
287
+ and k == "headers"
288
+ and old_v.parent.parent == new_v.parent
289
+ and old_v.parent.name.startswith("python")
290
+ )
291
+ if skip_osx_framework_user_special_case:
292
+ continue
293
+
294
+ # On Red Hat and derived Linux distributions, distutils is patched to
295
+ # use "lib64" instead of "lib" for platlib.
296
+ if k == "platlib" and _looks_like_red_hat_lib():
297
+ continue
298
+
299
+ # On Python 3.9+, sysconfig's posix_user scheme sets platlib against
300
+ # sys.platlibdir, but distutils's unix_user incorrectly coninutes
301
+ # using the same $usersite for both platlib and purelib. This creates a
302
+ # mismatch when sys.platlibdir is not "lib".
303
+ skip_bpo_44860 = (
304
+ user
305
+ and k == "platlib"
306
+ and not WINDOWS
307
+ and sys.version_info >= (3, 9)
308
+ and _PLATLIBDIR != "lib"
309
+ and _looks_like_bpo_44860()
310
+ )
311
+ if skip_bpo_44860:
312
+ continue
313
+
314
+ # Slackware incorrectly patches posix_user to use lib64 instead of lib,
315
+ # but not usersite to match the location.
316
+ skip_slackware_user_scheme = (
317
+ user
318
+ and k in ("platlib", "purelib")
319
+ and not WINDOWS
320
+ and _looks_like_slackware_scheme()
321
+ )
322
+ if skip_slackware_user_scheme:
323
+ continue
324
+
325
+ # Both Debian and Red Hat patch Python to place the system site under
326
+ # /usr/local instead of /usr. Debian also places lib in dist-packages
327
+ # instead of site-packages, but the /usr/local check should cover it.
328
+ skip_linux_system_special_case = (
329
+ not (user or home or prefix or running_under_virtualenv())
330
+ and old_v.parts[1:3] == ("usr", "local")
331
+ and len(new_v.parts) > 1
332
+ and new_v.parts[1] == "usr"
333
+ and (len(new_v.parts) < 3 or new_v.parts[2] != "local")
334
+ and (_looks_like_red_hat_scheme() or _looks_like_debian_scheme())
335
+ )
336
+ if skip_linux_system_special_case:
337
+ continue
338
+
339
+ # MSYS2 MINGW's sysconfig patch does not include the "site-packages"
340
+ # part of the path. This is incorrect and will be fixed in MSYS.
341
+ skip_msys2_mingw_bug = (
342
+ WINDOWS and k in ("platlib", "purelib") and _looks_like_msys2_mingw_scheme()
343
+ )
344
+ if skip_msys2_mingw_bug:
345
+ continue
346
+
347
+ # CPython's POSIX install script invokes pip (via ensurepip) against the
348
+ # interpreter located in the source tree, not the install site. This
349
+ # triggers special logic in sysconfig that's not present in distutils.
350
+ # https://github.com/python/cpython/blob/8c21941ddaf/Lib/sysconfig.py#L178-L194
351
+ skip_cpython_build = (
352
+ sysconfig.is_python_build(check_home=True)
353
+ and not WINDOWS
354
+ and k in ("headers", "include", "platinclude")
355
+ )
356
+ if skip_cpython_build:
357
+ continue
358
+
359
+ warning_contexts.append((old_v, new_v, f"scheme.{k}"))
360
+
361
+ if not warning_contexts:
362
+ return old
363
+
364
+ # Check if this path mismatch is caused by distutils config files. Those
365
+ # files will no longer work once we switch to sysconfig, so this raises a
366
+ # deprecation message for them.
367
+ default_old = _distutils.distutils_scheme(
368
+ dist_name,
369
+ user,
370
+ home,
371
+ root,
372
+ isolated,
373
+ prefix,
374
+ ignore_config_files=True,
375
+ )
376
+ if any(default_old[k] != getattr(old, k) for k in SCHEME_KEYS):
377
+ deprecated(
378
+ reason=(
379
+ "Configuring installation scheme with distutils config files "
380
+ "is deprecated and will no longer work in the near future. If you "
381
+ "are using a Homebrew or Linuxbrew Python, please see discussion "
382
+ "at https://github.com/Homebrew/homebrew-core/issues/76621"
383
+ ),
384
+ replacement=None,
385
+ gone_in=None,
386
+ )
387
+ return old
388
+
389
+ # Post warnings about this mismatch so user can report them back.
390
+ for old_v, new_v, key in warning_contexts:
391
+ _warn_mismatched(old_v, new_v, key=key)
392
+ _log_context(user=user, home=home, root=root, prefix=prefix)
393
+
394
+ return old
395
+
396
+
397
+ def get_bin_prefix() -> str:
398
+ new = _sysconfig.get_bin_prefix()
399
+ if _USE_SYSCONFIG:
400
+ return new
401
+
402
+ old = _distutils.get_bin_prefix()
403
+ if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="bin_prefix"):
404
+ _log_context()
405
+ return old
406
+
407
+
408
+ def get_bin_user() -> str:
409
+ return _sysconfig.get_scheme("", user=True).scripts
410
+
411
+
412
+ def _looks_like_deb_system_dist_packages(value: str) -> bool:
413
+ """Check if the value is Debian's APT-controlled dist-packages.
414
+
415
+ Debian's ``distutils.sysconfig.get_python_lib()`` implementation returns the
416
+ default package path controlled by APT, but does not patch ``sysconfig`` to
417
+ do the same. This is similar to the bug worked around in ``get_scheme()``,
418
+ but here the default is ``deb_system`` instead of ``unix_local``. Ultimately
419
+ we can't do anything about this Debian bug, and this detection allows us to
420
+ skip the warning when needed.
421
+ """
422
+ if not _looks_like_debian_scheme():
423
+ return False
424
+ if value == "/usr/lib/python3/dist-packages":
425
+ return True
426
+ return False
427
+
428
+
429
+ def get_purelib() -> str:
430
+ """Return the default pure-Python lib location."""
431
+ new = _sysconfig.get_purelib()
432
+ if _USE_SYSCONFIG:
433
+ return new
434
+
435
+ old = _distutils.get_purelib()
436
+ if _looks_like_deb_system_dist_packages(old):
437
+ return old
438
+ if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="purelib"):
439
+ _log_context()
440
+ return old
441
+
442
+
443
+ def get_platlib() -> str:
444
+ """Return the default platform-shared lib location."""
445
+ new = _sysconfig.get_platlib()
446
+ if _USE_SYSCONFIG:
447
+ return new
448
+
449
+ from . import _distutils
450
+
451
+ old = _distutils.get_platlib()
452
+ if _looks_like_deb_system_dist_packages(old):
453
+ return old
454
+ if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="platlib"):
455
+ _log_context()
456
+ return old
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/locations/_distutils.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Locations where we look for configs, install stuff, etc"""
2
+
3
+ # The following comment should be removed at some point in the future.
4
+ # mypy: strict-optional=False
5
+
6
+ # If pip's going to use distutils, it should not be using the copy that setuptools
7
+ # might have injected into the environment. This is done by removing the injected
8
+ # shim, if it's injected.
9
+ #
10
+ # See https://github.com/pypa/pip/issues/8761 for the original discussion and
11
+ # rationale for why this is done within pip.
12
+ try:
13
+ __import__("_distutils_hack").remove_shim()
14
+ except (ImportError, AttributeError):
15
+ pass
16
+
17
+ import logging
18
+ import os
19
+ import sys
20
+ from distutils.cmd import Command as DistutilsCommand
21
+ from distutils.command.install import SCHEME_KEYS
22
+ from distutils.command.install import install as distutils_install_command
23
+ from distutils.sysconfig import get_python_lib
24
+ from typing import Dict, List, Optional, Union
25
+
26
+ from pip._internal.models.scheme import Scheme
27
+ from pip._internal.utils.compat import WINDOWS
28
+ from pip._internal.utils.virtualenv import running_under_virtualenv
29
+
30
+ from .base import get_major_minor_version
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ def distutils_scheme(
36
+ dist_name: str,
37
+ user: bool = False,
38
+ home: Optional[str] = None,
39
+ root: Optional[str] = None,
40
+ isolated: bool = False,
41
+ prefix: Optional[str] = None,
42
+ *,
43
+ ignore_config_files: bool = False,
44
+ ) -> Dict[str, str]:
45
+ """
46
+ Return a distutils install scheme
47
+ """
48
+ from distutils.dist import Distribution
49
+
50
+ dist_args: Dict[str, Union[str, List[str]]] = {"name": dist_name}
51
+ if isolated:
52
+ dist_args["script_args"] = ["--no-user-cfg"]
53
+
54
+ d = Distribution(dist_args)
55
+ if not ignore_config_files:
56
+ try:
57
+ d.parse_config_files()
58
+ except UnicodeDecodeError:
59
+ paths = d.find_config_files()
60
+ logger.warning(
61
+ "Ignore distutils configs in %s due to encoding errors.",
62
+ ", ".join(os.path.basename(p) for p in paths),
63
+ )
64
+ obj: Optional[DistutilsCommand] = None
65
+ obj = d.get_command_obj("install", create=True)
66
+ assert obj is not None
67
+ i: distutils_install_command = obj
68
+ # NOTE: setting user or home has the side-effect of creating the home dir
69
+ # or user base for installations during finalize_options()
70
+ # ideally, we'd prefer a scheme class that has no side-effects.
71
+ assert not (user and prefix), f"user={user} prefix={prefix}"
72
+ assert not (home and prefix), f"home={home} prefix={prefix}"
73
+ i.user = user or i.user
74
+ if user or home:
75
+ i.prefix = ""
76
+ i.prefix = prefix or i.prefix
77
+ i.home = home or i.home
78
+ i.root = root or i.root
79
+ i.finalize_options()
80
+
81
+ scheme: Dict[str, str] = {}
82
+ for key in SCHEME_KEYS:
83
+ scheme[key] = getattr(i, "install_" + key)
84
+
85
+ # install_lib specified in setup.cfg should install *everything*
86
+ # into there (i.e. it takes precedence over both purelib and
87
+ # platlib). Note, i.install_lib is *always* set after
88
+ # finalize_options(); we only want to override here if the user
89
+ # has explicitly requested it hence going back to the config
90
+ if "install_lib" in d.get_option_dict("install"):
91
+ scheme.update({"purelib": i.install_lib, "platlib": i.install_lib})
92
+
93
+ if running_under_virtualenv():
94
+ if home:
95
+ prefix = home
96
+ elif user:
97
+ prefix = i.install_userbase
98
+ else:
99
+ prefix = i.prefix
100
+ scheme["headers"] = os.path.join(
101
+ prefix,
102
+ "include",
103
+ "site",
104
+ f"python{get_major_minor_version()}",
105
+ dist_name,
106
+ )
107
+
108
+ if root is not None:
109
+ path_no_drive = os.path.splitdrive(os.path.abspath(scheme["headers"]))[1]
110
+ scheme["headers"] = os.path.join(root, path_no_drive[1:])
111
+
112
+ return scheme
113
+
114
+
115
+ def get_scheme(
116
+ dist_name: str,
117
+ user: bool = False,
118
+ home: Optional[str] = None,
119
+ root: Optional[str] = None,
120
+ isolated: bool = False,
121
+ prefix: Optional[str] = None,
122
+ ) -> Scheme:
123
+ """
124
+ Get the "scheme" corresponding to the input parameters. The distutils
125
+ documentation provides the context for the available schemes:
126
+ https://docs.python.org/3/install/index.html#alternate-installation
127
+
128
+ :param dist_name: the name of the package to retrieve the scheme for, used
129
+ in the headers scheme path
130
+ :param user: indicates to use the "user" scheme
131
+ :param home: indicates to use the "home" scheme and provides the base
132
+ directory for the same
133
+ :param root: root under which other directories are re-based
134
+ :param isolated: equivalent to --no-user-cfg, i.e. do not consider
135
+ ~/.pydistutils.cfg (posix) or ~/pydistutils.cfg (non-posix) for
136
+ scheme paths
137
+ :param prefix: indicates to use the "prefix" scheme and provides the
138
+ base directory for the same
139
+ """
140
+ scheme = distutils_scheme(dist_name, user, home, root, isolated, prefix)
141
+ return Scheme(
142
+ platlib=scheme["platlib"],
143
+ purelib=scheme["purelib"],
144
+ headers=scheme["headers"],
145
+ scripts=scheme["scripts"],
146
+ data=scheme["data"],
147
+ )
148
+
149
+
150
+ def get_bin_prefix() -> str:
151
+ # XXX: In old virtualenv versions, sys.prefix can contain '..' components,
152
+ # so we need to call normpath to eliminate them.
153
+ prefix = os.path.normpath(sys.prefix)
154
+ if WINDOWS:
155
+ bin_py = os.path.join(prefix, "Scripts")
156
+ # buildout uses 'bin' on Windows too?
157
+ if not os.path.exists(bin_py):
158
+ bin_py = os.path.join(prefix, "bin")
159
+ return bin_py
160
+ # Forcing to use /usr/local/bin for standard macOS framework installs
161
+ # Also log to ~/Library/Logs/ for use with the Console.app log viewer
162
+ if sys.platform[:6] == "darwin" and prefix[:16] == "/System/Library/":
163
+ return "/usr/local/bin"
164
+ return os.path.join(prefix, "bin")
165
+
166
+
167
+ def get_purelib() -> str:
168
+ return get_python_lib(plat_specific=False)
169
+
170
+
171
+ def get_platlib() -> str:
172
+ return get_python_lib(plat_specific=True)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/locations/_sysconfig.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ import sys
4
+ import sysconfig
5
+ import typing
6
+
7
+ from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationInvalid
8
+ from pip._internal.models.scheme import SCHEME_KEYS, Scheme
9
+ from pip._internal.utils.virtualenv import running_under_virtualenv
10
+
11
+ from .base import change_root, get_major_minor_version, is_osx_framework
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ # Notes on _infer_* functions.
17
+ # Unfortunately ``get_default_scheme()`` didn't exist before 3.10, so there's no
18
+ # way to ask things like "what is the '_prefix' scheme on this platform". These
19
+ # functions try to answer that with some heuristics while accounting for ad-hoc
20
+ # platforms not covered by CPython's default sysconfig implementation. If the
21
+ # ad-hoc implementation does not fully implement sysconfig, we'll fall back to
22
+ # a POSIX scheme.
23
+
24
+ _AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names())
25
+
26
+ _PREFERRED_SCHEME_API = getattr(sysconfig, "get_preferred_scheme", None)
27
+
28
+
29
+ def _should_use_osx_framework_prefix() -> bool:
30
+ """Check for Apple's ``osx_framework_library`` scheme.
31
+
32
+ Python distributed by Apple's Command Line Tools has this special scheme
33
+ that's used when:
34
+
35
+ * This is a framework build.
36
+ * We are installing into the system prefix.
37
+
38
+ This does not account for ``pip install --prefix`` (also means we're not
39
+ installing to the system prefix), which should use ``posix_prefix``, but
40
+ logic here means ``_infer_prefix()`` outputs ``osx_framework_library``. But
41
+ since ``prefix`` is not available for ``sysconfig.get_default_scheme()``,
42
+ which is the stdlib replacement for ``_infer_prefix()``, presumably Apple
43
+ wouldn't be able to magically switch between ``osx_framework_library`` and
44
+ ``posix_prefix``. ``_infer_prefix()`` returning ``osx_framework_library``
45
+ means its behavior is consistent whether we use the stdlib implementation
46
+ or our own, and we deal with this special case in ``get_scheme()`` instead.
47
+ """
48
+ return (
49
+ "osx_framework_library" in _AVAILABLE_SCHEMES
50
+ and not running_under_virtualenv()
51
+ and is_osx_framework()
52
+ )
53
+
54
+
55
+ def _infer_prefix() -> str:
56
+ """Try to find a prefix scheme for the current platform.
57
+
58
+ This tries:
59
+
60
+ * A special ``osx_framework_library`` for Python distributed by Apple's
61
+ Command Line Tools, when not running in a virtual environment.
62
+ * Implementation + OS, used by PyPy on Windows (``pypy_nt``).
63
+ * Implementation without OS, used by PyPy on POSIX (``pypy``).
64
+ * OS + "prefix", used by CPython on POSIX (``posix_prefix``).
65
+ * Just the OS name, used by CPython on Windows (``nt``).
66
+
67
+ If none of the above works, fall back to ``posix_prefix``.
68
+ """
69
+ if _PREFERRED_SCHEME_API:
70
+ return _PREFERRED_SCHEME_API("prefix")
71
+ if _should_use_osx_framework_prefix():
72
+ return "osx_framework_library"
73
+ implementation_suffixed = f"{sys.implementation.name}_{os.name}"
74
+ if implementation_suffixed in _AVAILABLE_SCHEMES:
75
+ return implementation_suffixed
76
+ if sys.implementation.name in _AVAILABLE_SCHEMES:
77
+ return sys.implementation.name
78
+ suffixed = f"{os.name}_prefix"
79
+ if suffixed in _AVAILABLE_SCHEMES:
80
+ return suffixed
81
+ if os.name in _AVAILABLE_SCHEMES: # On Windows, prefx is just called "nt".
82
+ return os.name
83
+ return "posix_prefix"
84
+
85
+
86
+ def _infer_user() -> str:
87
+ """Try to find a user scheme for the current platform."""
88
+ if _PREFERRED_SCHEME_API:
89
+ return _PREFERRED_SCHEME_API("user")
90
+ if is_osx_framework() and not running_under_virtualenv():
91
+ suffixed = "osx_framework_user"
92
+ else:
93
+ suffixed = f"{os.name}_user"
94
+ if suffixed in _AVAILABLE_SCHEMES:
95
+ return suffixed
96
+ if "posix_user" not in _AVAILABLE_SCHEMES: # User scheme unavailable.
97
+ raise UserInstallationInvalid()
98
+ return "posix_user"
99
+
100
+
101
+ def _infer_home() -> str:
102
+ """Try to find a home for the current platform."""
103
+ if _PREFERRED_SCHEME_API:
104
+ return _PREFERRED_SCHEME_API("home")
105
+ suffixed = f"{os.name}_home"
106
+ if suffixed in _AVAILABLE_SCHEMES:
107
+ return suffixed
108
+ return "posix_home"
109
+
110
+
111
+ # Update these keys if the user sets a custom home.
112
+ _HOME_KEYS = [
113
+ "installed_base",
114
+ "base",
115
+ "installed_platbase",
116
+ "platbase",
117
+ "prefix",
118
+ "exec_prefix",
119
+ ]
120
+ if sysconfig.get_config_var("userbase") is not None:
121
+ _HOME_KEYS.append("userbase")
122
+
123
+
124
+ def get_scheme(
125
+ dist_name: str,
126
+ user: bool = False,
127
+ home: typing.Optional[str] = None,
128
+ root: typing.Optional[str] = None,
129
+ isolated: bool = False,
130
+ prefix: typing.Optional[str] = None,
131
+ ) -> Scheme:
132
+ """
133
+ Get the "scheme" corresponding to the input parameters.
134
+
135
+ :param dist_name: the name of the package to retrieve the scheme for, used
136
+ in the headers scheme path
137
+ :param user: indicates to use the "user" scheme
138
+ :param home: indicates to use the "home" scheme
139
+ :param root: root under which other directories are re-based
140
+ :param isolated: ignored, but kept for distutils compatibility (where
141
+ this controls whether the user-site pydistutils.cfg is honored)
142
+ :param prefix: indicates to use the "prefix" scheme and provides the
143
+ base directory for the same
144
+ """
145
+ if user and prefix:
146
+ raise InvalidSchemeCombination("--user", "--prefix")
147
+ if home and prefix:
148
+ raise InvalidSchemeCombination("--home", "--prefix")
149
+
150
+ if home is not None:
151
+ scheme_name = _infer_home()
152
+ elif user:
153
+ scheme_name = _infer_user()
154
+ else:
155
+ scheme_name = _infer_prefix()
156
+
157
+ # Special case: When installing into a custom prefix, use posix_prefix
158
+ # instead of osx_framework_library. See _should_use_osx_framework_prefix()
159
+ # docstring for details.
160
+ if prefix is not None and scheme_name == "osx_framework_library":
161
+ scheme_name = "posix_prefix"
162
+
163
+ if home is not None:
164
+ variables = {k: home for k in _HOME_KEYS}
165
+ elif prefix is not None:
166
+ variables = {k: prefix for k in _HOME_KEYS}
167
+ else:
168
+ variables = {}
169
+
170
+ paths = sysconfig.get_paths(scheme=scheme_name, vars=variables)
171
+
172
+ # Logic here is very arbitrary, we're doing it for compatibility, don't ask.
173
+ # 1. Pip historically uses a special header path in virtual environments.
174
+ # 2. If the distribution name is not known, distutils uses 'UNKNOWN'. We
175
+ # only do the same when not running in a virtual environment because
176
+ # pip's historical header path logic (see point 1) did not do this.
177
+ if running_under_virtualenv():
178
+ if user:
179
+ base = variables.get("userbase", sys.prefix)
180
+ else:
181
+ base = variables.get("base", sys.prefix)
182
+ python_xy = f"python{get_major_minor_version()}"
183
+ paths["include"] = os.path.join(base, "include", "site", python_xy)
184
+ elif not dist_name:
185
+ dist_name = "UNKNOWN"
186
+
187
+ scheme = Scheme(
188
+ platlib=paths["platlib"],
189
+ purelib=paths["purelib"],
190
+ headers=os.path.join(paths["include"], dist_name),
191
+ scripts=paths["scripts"],
192
+ data=paths["data"],
193
+ )
194
+ if root is not None:
195
+ converted_keys = {}
196
+ for key in SCHEME_KEYS:
197
+ converted_keys[key] = change_root(root, getattr(scheme, key))
198
+ scheme = Scheme(**converted_keys)
199
+ return scheme
200
+
201
+
202
+ def get_bin_prefix() -> str:
203
+ # Forcing to use /usr/local/bin for standard macOS framework installs.
204
+ if sys.platform[:6] == "darwin" and sys.prefix[:16] == "/System/Library/":
205
+ return "/usr/local/bin"
206
+ return sysconfig.get_paths()["scripts"]
207
+
208
+
209
+ def get_purelib() -> str:
210
+ return sysconfig.get_paths()["purelib"]
211
+
212
+
213
+ def get_platlib() -> str:
214
+ return sysconfig.get_paths()["platlib"]
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/locations/base.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import os
3
+ import site
4
+ import sys
5
+ import sysconfig
6
+ import typing
7
+
8
+ from pip._internal.exceptions import InstallationError
9
+ from pip._internal.utils import appdirs
10
+ from pip._internal.utils.virtualenv import running_under_virtualenv
11
+
12
+ # Application Directories
13
+ USER_CACHE_DIR = appdirs.user_cache_dir("pip")
14
+
15
+ # FIXME doesn't account for venv linked to global site-packages
16
+ site_packages: str = sysconfig.get_path("purelib")
17
+
18
+
19
+ def get_major_minor_version() -> str:
20
+ """
21
+ Return the major-minor version of the current Python as a string, e.g.
22
+ "3.7" or "3.10".
23
+ """
24
+ return "{}.{}".format(*sys.version_info)
25
+
26
+
27
+ def change_root(new_root: str, pathname: str) -> str:
28
+ """Return 'pathname' with 'new_root' prepended.
29
+
30
+ If 'pathname' is relative, this is equivalent to os.path.join(new_root, pathname).
31
+ Otherwise, it requires making 'pathname' relative and then joining the
32
+ two, which is tricky on DOS/Windows and Mac OS.
33
+
34
+ This is borrowed from Python's standard library's distutils module.
35
+ """
36
+ if os.name == "posix":
37
+ if not os.path.isabs(pathname):
38
+ return os.path.join(new_root, pathname)
39
+ else:
40
+ return os.path.join(new_root, pathname[1:])
41
+
42
+ elif os.name == "nt":
43
+ (drive, path) = os.path.splitdrive(pathname)
44
+ if path[0] == "\\":
45
+ path = path[1:]
46
+ return os.path.join(new_root, path)
47
+
48
+ else:
49
+ raise InstallationError(
50
+ f"Unknown platform: {os.name}\n"
51
+ "Can not change root path prefix on unknown platform."
52
+ )
53
+
54
+
55
+ def get_src_prefix() -> str:
56
+ if running_under_virtualenv():
57
+ src_prefix = os.path.join(sys.prefix, "src")
58
+ else:
59
+ # FIXME: keep src in cwd for now (it is not a temporary folder)
60
+ try:
61
+ src_prefix = os.path.join(os.getcwd(), "src")
62
+ except OSError:
63
+ # In case the current working directory has been renamed or deleted
64
+ sys.exit("The folder you are executing pip from can no longer be found.")
65
+
66
+ # under macOS + virtualenv sys.prefix is not properly resolved
67
+ # it is something like /path/to/python/bin/..
68
+ return os.path.abspath(src_prefix)
69
+
70
+
71
+ try:
72
+ # Use getusersitepackages if this is present, as it ensures that the
73
+ # value is initialised properly.
74
+ user_site: typing.Optional[str] = site.getusersitepackages()
75
+ except AttributeError:
76
+ user_site = site.USER_SITE
77
+
78
+
79
+ @functools.lru_cache(maxsize=None)
80
+ def is_osx_framework() -> bool:
81
+ return bool(sysconfig.get_config_var("PYTHONFRAMEWORK"))
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/main.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+
3
+
4
+ def main(args: Optional[List[str]] = None) -> int:
5
+ """This is preserved for old console scripts that may still be referencing
6
+ it.
7
+
8
+ For additional details, see https://github.com/pypa/pip/issues/7498.
9
+ """
10
+ from pip._internal.utils.entrypoints import _wrapper
11
+
12
+ return _wrapper(args)
URSA/.venv_ursa/lib/python3.12/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_resources`` 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 across 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
+ )
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/metadata/_json.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, cast
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
+ ("License-Expression", False),
27
+ ("License-File", True),
28
+ ("Classifier", True),
29
+ ("Requires-Dist", True),
30
+ ("Requires-Python", False),
31
+ ("Requires-External", True),
32
+ ("Project-URL", True),
33
+ ("Provides-Extra", True),
34
+ ("Provides-Dist", True),
35
+ ("Obsoletes-Dist", True),
36
+ ]
37
+
38
+
39
+ def json_name(field: str) -> str:
40
+ return field.lower().replace("-", "_")
41
+
42
+
43
+ def msg_to_json(msg: Message) -> Dict[str, Any]:
44
+ """Convert a Message object into a JSON-compatible dictionary."""
45
+
46
+ def sanitise_header(h: Union[Header, str]) -> str:
47
+ if isinstance(h, Header):
48
+ chunks = []
49
+ for bytes, encoding in decode_header(h):
50
+ if encoding == "unknown-8bit":
51
+ try:
52
+ # See if UTF-8 works
53
+ bytes.decode("utf-8")
54
+ encoding = "utf-8"
55
+ except UnicodeDecodeError:
56
+ # If not, latin1 at least won't fail
57
+ encoding = "latin1"
58
+ chunks.append((bytes, encoding))
59
+ return str(make_header(chunks))
60
+ return str(h)
61
+
62
+ result = {}
63
+ for field, multi in METADATA_FIELDS:
64
+ if field not in msg:
65
+ continue
66
+ key = json_name(field)
67
+ if multi:
68
+ value: Union[str, List[str]] = [
69
+ sanitise_header(v) for v in msg.get_all(field) # type: ignore
70
+ ]
71
+ else:
72
+ value = sanitise_header(msg.get(field)) # type: ignore
73
+ if key == "keywords":
74
+ # Accept both comma-separated and space-separated
75
+ # forms, for better compatibility with old data.
76
+ if "," in value:
77
+ value = [v.strip() for v in value.split(",")]
78
+ else:
79
+ value = value.split()
80
+ result[key] = value
81
+
82
+ payload = cast(str, msg.get_payload())
83
+ if payload:
84
+ result["description"] = payload
85
+
86
+ return result
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/metadata/base.py ADDED
@@ -0,0 +1,688 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ Any,
12
+ Collection,
13
+ Container,
14
+ Dict,
15
+ Iterable,
16
+ Iterator,
17
+ List,
18
+ NamedTuple,
19
+ Optional,
20
+ Protocol,
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 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
+ InfoPath = Union[str, pathlib.PurePath]
45
+
46
+ logger = logging.getLogger(__name__)
47
+
48
+
49
+ class BaseEntryPoint(Protocol):
50
+ @property
51
+ def name(self) -> str:
52
+ raise NotImplementedError()
53
+
54
+ @property
55
+ def value(self) -> str:
56
+ raise NotImplementedError()
57
+
58
+ @property
59
+ def group(self) -> str:
60
+ raise NotImplementedError()
61
+
62
+
63
+ def _convert_installed_files_path(
64
+ entry: Tuple[str, ...],
65
+ info: Tuple[str, ...],
66
+ ) -> str:
67
+ """Convert a legacy installed-files.txt path into modern RECORD path.
68
+
69
+ The legacy format stores paths relative to the info directory, while the
70
+ modern format stores paths relative to the package root, e.g. the
71
+ site-packages directory.
72
+
73
+ :param entry: Path parts of the installed-files.txt entry.
74
+ :param info: Path parts of the egg-info directory relative to package root.
75
+ :returns: The converted entry.
76
+
77
+ For best compatibility with symlinks, this does not use ``abspath()`` or
78
+ ``Path.resolve()``, but tries to work with path parts:
79
+
80
+ 1. While ``entry`` starts with ``..``, remove the equal amounts of parts
81
+ from ``info``; if ``info`` is empty, start appending ``..`` instead.
82
+ 2. Join the two directly.
83
+ """
84
+ while entry and entry[0] == "..":
85
+ if not info or info[-1] == "..":
86
+ info += ("..",)
87
+ else:
88
+ info = info[:-1]
89
+ entry = entry[1:]
90
+ return str(pathlib.Path(*info, *entry))
91
+
92
+
93
+ class RequiresEntry(NamedTuple):
94
+ requirement: str
95
+ extra: str
96
+ marker: str
97
+
98
+
99
+ class BaseDistribution(Protocol):
100
+ @classmethod
101
+ def from_directory(cls, directory: str) -> "BaseDistribution":
102
+ """Load the distribution from a metadata directory.
103
+
104
+ :param directory: Path to a metadata directory, e.g. ``.dist-info``.
105
+ """
106
+ raise NotImplementedError()
107
+
108
+ @classmethod
109
+ def from_metadata_file_contents(
110
+ cls,
111
+ metadata_contents: bytes,
112
+ filename: str,
113
+ project_name: str,
114
+ ) -> "BaseDistribution":
115
+ """Load the distribution from the contents of a METADATA file.
116
+
117
+ This is used to implement PEP 658 by generating a "shallow" dist object that can
118
+ be used for resolution without downloading or building the actual dist yet.
119
+
120
+ :param metadata_contents: The contents of a METADATA file.
121
+ :param filename: File name for the dist with this metadata.
122
+ :param project_name: Name of the project this dist represents.
123
+ """
124
+ raise NotImplementedError()
125
+
126
+ @classmethod
127
+ def from_wheel(cls, wheel: "Wheel", name: str) -> "BaseDistribution":
128
+ """Load the distribution from a given wheel.
129
+
130
+ :param wheel: A concrete wheel definition.
131
+ :param name: File name of the wheel.
132
+
133
+ :raises InvalidWheel: Whenever loading of the wheel causes a
134
+ :py:exc:`zipfile.BadZipFile` exception to be thrown.
135
+ :raises UnsupportedWheel: If the wheel is a valid zip, but malformed
136
+ internally.
137
+ """
138
+ raise NotImplementedError()
139
+
140
+ def __repr__(self) -> str:
141
+ return f"{self.raw_name} {self.raw_version} ({self.location})"
142
+
143
+ def __str__(self) -> str:
144
+ return f"{self.raw_name} {self.raw_version}"
145
+
146
+ @property
147
+ def location(self) -> Optional[str]:
148
+ """Where the distribution is loaded from.
149
+
150
+ A string value is not necessarily a filesystem path, since distributions
151
+ can be loaded from other sources, e.g. arbitrary zip archives. ``None``
152
+ means the distribution is created in-memory.
153
+
154
+ Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If
155
+ this is a symbolic link, we want to preserve the relative path between
156
+ it and files in the distribution.
157
+ """
158
+ raise NotImplementedError()
159
+
160
+ @property
161
+ def editable_project_location(self) -> Optional[str]:
162
+ """The project location for editable distributions.
163
+
164
+ This is the directory where pyproject.toml or setup.py is located.
165
+ None if the distribution is not installed in editable mode.
166
+ """
167
+ # TODO: this property is relatively costly to compute, memoize it ?
168
+ direct_url = self.direct_url
169
+ if direct_url:
170
+ if direct_url.is_local_editable():
171
+ return url_to_path(direct_url.url)
172
+ else:
173
+ # Search for an .egg-link file by walking sys.path, as it was
174
+ # done before by dist_is_editable().
175
+ egg_link_path = egg_link_path_from_sys_path(self.raw_name)
176
+ if egg_link_path:
177
+ # TODO: get project location from second line of egg_link file
178
+ # (https://github.com/pypa/pip/issues/10243)
179
+ return self.location
180
+ return None
181
+
182
+ @property
183
+ def installed_location(self) -> Optional[str]:
184
+ """The distribution's "installed" location.
185
+
186
+ This should generally be a ``site-packages`` directory. This is
187
+ usually ``dist.location``, except for legacy develop-installed packages,
188
+ where ``dist.location`` is the source code location, and this is where
189
+ the ``.egg-link`` file is.
190
+
191
+ The returned location is normalized (in particular, with symlinks removed).
192
+ """
193
+ raise NotImplementedError()
194
+
195
+ @property
196
+ def info_location(self) -> Optional[str]:
197
+ """Location of the .[egg|dist]-info directory or file.
198
+
199
+ Similarly to ``location``, a string value is not necessarily a
200
+ filesystem path. ``None`` means the distribution is created in-memory.
201
+
202
+ For a modern .dist-info installation on disk, this should be something
203
+ like ``{location}/{raw_name}-{version}.dist-info``.
204
+
205
+ Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If
206
+ this is a symbolic link, we want to preserve the relative path between
207
+ it and other files in the distribution.
208
+ """
209
+ raise NotImplementedError()
210
+
211
+ @property
212
+ def installed_by_distutils(self) -> bool:
213
+ """Whether this distribution is installed with legacy distutils format.
214
+
215
+ A distribution installed with "raw" distutils not patched by setuptools
216
+ uses one single file at ``info_location`` to store metadata. We need to
217
+ treat this specially on uninstallation.
218
+ """
219
+ info_location = self.info_location
220
+ if not info_location:
221
+ return False
222
+ return pathlib.Path(info_location).is_file()
223
+
224
+ @property
225
+ def installed_as_egg(self) -> bool:
226
+ """Whether this distribution is installed as an egg.
227
+
228
+ This usually indicates the distribution was installed by (older versions
229
+ of) easy_install.
230
+ """
231
+ location = self.location
232
+ if not location:
233
+ return False
234
+ return location.endswith(".egg")
235
+
236
+ @property
237
+ def installed_with_setuptools_egg_info(self) -> bool:
238
+ """Whether this distribution is installed with the ``.egg-info`` format.
239
+
240
+ This usually indicates the distribution was installed with setuptools
241
+ with an old pip version or with ``single-version-externally-managed``.
242
+
243
+ Note that this ensure the metadata store is a directory. distutils can
244
+ also installs an ``.egg-info``, but as a file, not a directory. This
245
+ property is *False* for that case. Also see ``installed_by_distutils``.
246
+ """
247
+ info_location = self.info_location
248
+ if not info_location:
249
+ return False
250
+ if not info_location.endswith(".egg-info"):
251
+ return False
252
+ return pathlib.Path(info_location).is_dir()
253
+
254
+ @property
255
+ def installed_with_dist_info(self) -> bool:
256
+ """Whether this distribution is installed with the "modern format".
257
+
258
+ This indicates a "modern" installation, e.g. storing metadata in the
259
+ ``.dist-info`` directory. This applies to installations made by
260
+ setuptools (but through pip, not directly), or anything using the
261
+ standardized build backend interface (PEP 517).
262
+ """
263
+ info_location = self.info_location
264
+ if not info_location:
265
+ return False
266
+ if not info_location.endswith(".dist-info"):
267
+ return False
268
+ return pathlib.Path(info_location).is_dir()
269
+
270
+ @property
271
+ def canonical_name(self) -> NormalizedName:
272
+ raise NotImplementedError()
273
+
274
+ @property
275
+ def version(self) -> Version:
276
+ raise NotImplementedError()
277
+
278
+ @property
279
+ def raw_version(self) -> str:
280
+ raise NotImplementedError()
281
+
282
+ @property
283
+ def setuptools_filename(self) -> str:
284
+ """Convert a project name to its setuptools-compatible filename.
285
+
286
+ This is a copy of ``pkg_resources.to_filename()`` for compatibility.
287
+ """
288
+ return self.raw_name.replace("-", "_")
289
+
290
+ @property
291
+ def direct_url(self) -> Optional[DirectUrl]:
292
+ """Obtain a DirectUrl from this distribution.
293
+
294
+ Returns None if the distribution has no `direct_url.json` metadata,
295
+ or if `direct_url.json` is invalid.
296
+ """
297
+ try:
298
+ content = self.read_text(DIRECT_URL_METADATA_NAME)
299
+ except FileNotFoundError:
300
+ return None
301
+ try:
302
+ return DirectUrl.from_json(content)
303
+ except (
304
+ UnicodeDecodeError,
305
+ json.JSONDecodeError,
306
+ DirectUrlValidationError,
307
+ ) as e:
308
+ logger.warning(
309
+ "Error parsing %s for %s: %s",
310
+ DIRECT_URL_METADATA_NAME,
311
+ self.canonical_name,
312
+ e,
313
+ )
314
+ return None
315
+
316
+ @property
317
+ def installer(self) -> str:
318
+ try:
319
+ installer_text = self.read_text("INSTALLER")
320
+ except (OSError, ValueError, NoneMetadataError):
321
+ return "" # Fail silently if the installer file cannot be read.
322
+ for line in installer_text.splitlines():
323
+ cleaned_line = line.strip()
324
+ if cleaned_line:
325
+ return cleaned_line
326
+ return ""
327
+
328
+ @property
329
+ def requested(self) -> bool:
330
+ return self.is_file("REQUESTED")
331
+
332
+ @property
333
+ def editable(self) -> bool:
334
+ return bool(self.editable_project_location)
335
+
336
+ @property
337
+ def local(self) -> bool:
338
+ """If distribution is installed in the current virtual environment.
339
+
340
+ Always True if we're not in a virtualenv.
341
+ """
342
+ if self.installed_location is None:
343
+ return False
344
+ return is_local(self.installed_location)
345
+
346
+ @property
347
+ def in_usersite(self) -> bool:
348
+ if self.installed_location is None or user_site is None:
349
+ return False
350
+ return self.installed_location.startswith(normalize_path(user_site))
351
+
352
+ @property
353
+ def in_site_packages(self) -> bool:
354
+ if self.installed_location is None or site_packages is None:
355
+ return False
356
+ return self.installed_location.startswith(normalize_path(site_packages))
357
+
358
+ def is_file(self, path: InfoPath) -> bool:
359
+ """Check whether an entry in the info directory is a file."""
360
+ raise NotImplementedError()
361
+
362
+ def iter_distutils_script_names(self) -> Iterator[str]:
363
+ """Find distutils 'scripts' entries metadata.
364
+
365
+ If 'scripts' is supplied in ``setup.py``, distutils records those in the
366
+ installed distribution's ``scripts`` directory, a file for each script.
367
+ """
368
+ raise NotImplementedError()
369
+
370
+ def read_text(self, path: InfoPath) -> str:
371
+ """Read a file in the info directory.
372
+
373
+ :raise FileNotFoundError: If ``path`` does not exist in the directory.
374
+ :raise NoneMetadataError: If ``path`` exists in the info directory, but
375
+ cannot be read.
376
+ """
377
+ raise NotImplementedError()
378
+
379
+ def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
380
+ raise NotImplementedError()
381
+
382
+ def _metadata_impl(self) -> email.message.Message:
383
+ raise NotImplementedError()
384
+
385
+ @functools.cached_property
386
+ def metadata(self) -> email.message.Message:
387
+ """Metadata of distribution parsed from e.g. METADATA or PKG-INFO.
388
+
389
+ This should return an empty message if the metadata file is unavailable.
390
+
391
+ :raises NoneMetadataError: If the metadata file is available, but does
392
+ not contain valid metadata.
393
+ """
394
+ metadata = self._metadata_impl()
395
+ self._add_egg_info_requires(metadata)
396
+ return metadata
397
+
398
+ @property
399
+ def metadata_dict(self) -> Dict[str, Any]:
400
+ """PEP 566 compliant JSON-serializable representation of METADATA or PKG-INFO.
401
+
402
+ This should return an empty dict if the metadata file is unavailable.
403
+
404
+ :raises NoneMetadataError: If the metadata file is available, but does
405
+ not contain valid metadata.
406
+ """
407
+ return msg_to_json(self.metadata)
408
+
409
+ @property
410
+ def metadata_version(self) -> Optional[str]:
411
+ """Value of "Metadata-Version:" in distribution metadata, if available."""
412
+ return self.metadata.get("Metadata-Version")
413
+
414
+ @property
415
+ def raw_name(self) -> str:
416
+ """Value of "Name:" in distribution metadata."""
417
+ # The metadata should NEVER be missing the Name: key, but if it somehow
418
+ # does, fall back to the known canonical name.
419
+ return self.metadata.get("Name", self.canonical_name)
420
+
421
+ @property
422
+ def requires_python(self) -> SpecifierSet:
423
+ """Value of "Requires-Python:" in distribution metadata.
424
+
425
+ If the key does not exist or contains an invalid value, an empty
426
+ SpecifierSet should be returned.
427
+ """
428
+ value = self.metadata.get("Requires-Python")
429
+ if value is None:
430
+ return SpecifierSet()
431
+ try:
432
+ # Convert to str to satisfy the type checker; this can be a Header object.
433
+ spec = SpecifierSet(str(value))
434
+ except InvalidSpecifier as e:
435
+ message = "Package %r has an invalid Requires-Python: %s"
436
+ logger.warning(message, self.raw_name, e)
437
+ return SpecifierSet()
438
+ return spec
439
+
440
+ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
441
+ """Dependencies of this distribution.
442
+
443
+ For modern .dist-info distributions, this is the collection of
444
+ "Requires-Dist:" entries in distribution metadata.
445
+ """
446
+ raise NotImplementedError()
447
+
448
+ def iter_raw_dependencies(self) -> Iterable[str]:
449
+ """Raw Requires-Dist metadata."""
450
+ return self.metadata.get_all("Requires-Dist", [])
451
+
452
+ def iter_provided_extras(self) -> Iterable[NormalizedName]:
453
+ """Extras provided by this distribution.
454
+
455
+ For modern .dist-info distributions, this is the collection of
456
+ "Provides-Extra:" entries in distribution metadata.
457
+
458
+ The return value of this function is expected to be normalised names,
459
+ per PEP 685, with the returned value being handled appropriately by
460
+ `iter_dependencies`.
461
+ """
462
+ raise NotImplementedError()
463
+
464
+ def _iter_declared_entries_from_record(self) -> Optional[Iterator[str]]:
465
+ try:
466
+ text = self.read_text("RECORD")
467
+ except FileNotFoundError:
468
+ return None
469
+ # This extra Path-str cast normalizes entries.
470
+ return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines()))
471
+
472
+ def _iter_declared_entries_from_legacy(self) -> Optional[Iterator[str]]:
473
+ try:
474
+ text = self.read_text("installed-files.txt")
475
+ except FileNotFoundError:
476
+ return None
477
+ paths = (p for p in text.splitlines(keepends=False) if p)
478
+ root = self.location
479
+ info = self.info_location
480
+ if root is None or info is None:
481
+ return paths
482
+ try:
483
+ info_rel = pathlib.Path(info).relative_to(root)
484
+ except ValueError: # info is not relative to root.
485
+ return paths
486
+ if not info_rel.parts: # info *is* root.
487
+ return paths
488
+ return (
489
+ _convert_installed_files_path(pathlib.Path(p).parts, info_rel.parts)
490
+ for p in paths
491
+ )
492
+
493
+ def iter_declared_entries(self) -> Optional[Iterator[str]]:
494
+ """Iterate through file entries declared in this distribution.
495
+
496
+ For modern .dist-info distributions, this is the files listed in the
497
+ ``RECORD`` metadata file. For legacy setuptools distributions, this
498
+ comes from ``installed-files.txt``, with entries normalized to be
499
+ compatible with the format used by ``RECORD``.
500
+
501
+ :return: An iterator for listed entries, or None if the distribution
502
+ contains neither ``RECORD`` nor ``installed-files.txt``.
503
+ """
504
+ return (
505
+ self._iter_declared_entries_from_record()
506
+ or self._iter_declared_entries_from_legacy()
507
+ )
508
+
509
+ def _iter_requires_txt_entries(self) -> Iterator[RequiresEntry]:
510
+ """Parse a ``requires.txt`` in an egg-info directory.
511
+
512
+ This is an INI-ish format where an egg-info stores dependencies. A
513
+ section name describes extra other environment markers, while each entry
514
+ is an arbitrary string (not a key-value pair) representing a dependency
515
+ as a requirement string (no markers).
516
+
517
+ There is a construct in ``importlib.metadata`` called ``Sectioned`` that
518
+ does mostly the same, but the format is currently considered private.
519
+ """
520
+ try:
521
+ content = self.read_text("requires.txt")
522
+ except FileNotFoundError:
523
+ return
524
+ extra = marker = "" # Section-less entries don't have markers.
525
+ for line in content.splitlines():
526
+ line = line.strip()
527
+ if not line or line.startswith("#"): # Comment; ignored.
528
+ continue
529
+ if line.startswith("[") and line.endswith("]"): # A section header.
530
+ extra, _, marker = line.strip("[]").partition(":")
531
+ continue
532
+ yield RequiresEntry(requirement=line, extra=extra, marker=marker)
533
+
534
+ def _iter_egg_info_extras(self) -> Iterable[str]:
535
+ """Get extras from the egg-info directory."""
536
+ known_extras = {""}
537
+ for entry in self._iter_requires_txt_entries():
538
+ extra = canonicalize_name(entry.extra)
539
+ if extra in known_extras:
540
+ continue
541
+ known_extras.add(extra)
542
+ yield extra
543
+
544
+ def _iter_egg_info_dependencies(self) -> Iterable[str]:
545
+ """Get distribution dependencies from the egg-info directory.
546
+
547
+ To ease parsing, this converts a legacy dependency entry into a PEP 508
548
+ requirement string. Like ``_iter_requires_txt_entries()``, there is code
549
+ in ``importlib.metadata`` that does mostly the same, but not do exactly
550
+ what we need.
551
+
552
+ Namely, ``importlib.metadata`` does not normalize the extra name before
553
+ putting it into the requirement string, which causes marker comparison
554
+ to fail because the dist-info format do normalize. This is consistent in
555
+ all currently available PEP 517 backends, although not standardized.
556
+ """
557
+ for entry in self._iter_requires_txt_entries():
558
+ extra = canonicalize_name(entry.extra)
559
+ if extra and entry.marker:
560
+ marker = f'({entry.marker}) and extra == "{extra}"'
561
+ elif extra:
562
+ marker = f'extra == "{extra}"'
563
+ elif entry.marker:
564
+ marker = entry.marker
565
+ else:
566
+ marker = ""
567
+ if marker:
568
+ yield f"{entry.requirement} ; {marker}"
569
+ else:
570
+ yield entry.requirement
571
+
572
+ def _add_egg_info_requires(self, metadata: email.message.Message) -> None:
573
+ """Add egg-info requires.txt information to the metadata."""
574
+ if not metadata.get_all("Requires-Dist"):
575
+ for dep in self._iter_egg_info_dependencies():
576
+ metadata["Requires-Dist"] = dep
577
+ if not metadata.get_all("Provides-Extra"):
578
+ for extra in self._iter_egg_info_extras():
579
+ metadata["Provides-Extra"] = extra
580
+
581
+
582
+ class BaseEnvironment:
583
+ """An environment containing distributions to introspect."""
584
+
585
+ @classmethod
586
+ def default(cls) -> "BaseEnvironment":
587
+ raise NotImplementedError()
588
+
589
+ @classmethod
590
+ def from_paths(cls, paths: Optional[List[str]]) -> "BaseEnvironment":
591
+ raise NotImplementedError()
592
+
593
+ def get_distribution(self, name: str) -> Optional["BaseDistribution"]:
594
+ """Given a requirement name, return the installed distributions.
595
+
596
+ The name may not be normalized. The implementation must canonicalize
597
+ it for lookup.
598
+ """
599
+ raise NotImplementedError()
600
+
601
+ def _iter_distributions(self) -> Iterator["BaseDistribution"]:
602
+ """Iterate through installed distributions.
603
+
604
+ This function should be implemented by subclass, but never called
605
+ directly. Use the public ``iter_distribution()`` instead, which
606
+ implements additional logic to make sure the distributions are valid.
607
+ """
608
+ raise NotImplementedError()
609
+
610
+ def iter_all_distributions(self) -> Iterator[BaseDistribution]:
611
+ """Iterate through all installed distributions without any filtering."""
612
+ for dist in self._iter_distributions():
613
+ # Make sure the distribution actually comes from a valid Python
614
+ # packaging distribution. Pip's AdjacentTempDirectory leaves folders
615
+ # e.g. ``~atplotlib.dist-info`` if cleanup was interrupted. The
616
+ # valid project name pattern is taken from PEP 508.
617
+ project_name_valid = re.match(
618
+ r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$",
619
+ dist.canonical_name,
620
+ flags=re.IGNORECASE,
621
+ )
622
+ if not project_name_valid:
623
+ logger.warning(
624
+ "Ignoring invalid distribution %s (%s)",
625
+ dist.canonical_name,
626
+ dist.location,
627
+ )
628
+ continue
629
+ yield dist
630
+
631
+ def iter_installed_distributions(
632
+ self,
633
+ local_only: bool = True,
634
+ skip: Container[str] = stdlib_pkgs,
635
+ include_editables: bool = True,
636
+ editables_only: bool = False,
637
+ user_only: bool = False,
638
+ ) -> Iterator[BaseDistribution]:
639
+ """Return a list of installed distributions.
640
+
641
+ This is based on ``iter_all_distributions()`` with additional filtering
642
+ options. Note that ``iter_installed_distributions()`` without arguments
643
+ is *not* equal to ``iter_all_distributions()``, since some of the
644
+ configurations exclude packages by default.
645
+
646
+ :param local_only: If True (default), only return installations
647
+ local to the current virtualenv, if in a virtualenv.
648
+ :param skip: An iterable of canonicalized project names to ignore;
649
+ defaults to ``stdlib_pkgs``.
650
+ :param include_editables: If False, don't report editables.
651
+ :param editables_only: If True, only report editables.
652
+ :param user_only: If True, only report installations in the user
653
+ site directory.
654
+ """
655
+ it = self.iter_all_distributions()
656
+ if local_only:
657
+ it = (d for d in it if d.local)
658
+ if not include_editables:
659
+ it = (d for d in it if not d.editable)
660
+ if editables_only:
661
+ it = (d for d in it if d.editable)
662
+ if user_only:
663
+ it = (d for d in it if d.in_usersite)
664
+ return (d for d in it if d.canonical_name not in skip)
665
+
666
+
667
+ class Wheel(Protocol):
668
+ location: str
669
+
670
+ def as_zipfile(self) -> zipfile.ZipFile:
671
+ raise NotImplementedError()
672
+
673
+
674
+ class FilesystemWheel(Wheel):
675
+ def __init__(self, location: str) -> None:
676
+ self.location = location
677
+
678
+ def as_zipfile(self) -> zipfile.ZipFile:
679
+ return zipfile.ZipFile(self.location, allowZip64=True)
680
+
681
+
682
+ class MemoryWheel(Wheel):
683
+ def __init__(self, location: str, stream: IO[bytes]) -> None:
684
+ self.location = location
685
+ self.stream = stream
686
+
687
+ def as_zipfile(self) -> zipfile.ZipFile:
688
+ return zipfile.ZipFile(self.stream, allowZip64=True)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/metadata/pkg_resources.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import email.message
2
+ import email.parser
3
+ import logging
4
+ import os
5
+ import zipfile
6
+ from typing import (
7
+ Collection,
8
+ Iterable,
9
+ Iterator,
10
+ List,
11
+ Mapping,
12
+ NamedTuple,
13
+ Optional,
14
+ )
15
+
16
+ from pip._vendor import pkg_resources
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 Version
20
+ from pip._vendor.packaging.version import parse as parse_version
21
+
22
+ from pip._internal.exceptions import InvalidWheel, NoneMetadataError, UnsupportedWheel
23
+ from pip._internal.utils.egg_link import egg_link_path_from_location
24
+ from pip._internal.utils.misc import display_path, normalize_path
25
+ from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
26
+
27
+ from .base import (
28
+ BaseDistribution,
29
+ BaseEntryPoint,
30
+ BaseEnvironment,
31
+ InfoPath,
32
+ Wheel,
33
+ )
34
+
35
+ __all__ = ["NAME", "Distribution", "Environment"]
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+ NAME = "pkg_resources"
40
+
41
+
42
+ class EntryPoint(NamedTuple):
43
+ name: str
44
+ value: str
45
+ group: str
46
+
47
+
48
+ class InMemoryMetadata:
49
+ """IMetadataProvider that reads metadata files from a dictionary.
50
+
51
+ This also maps metadata decoding exceptions to our internal exception type.
52
+ """
53
+
54
+ def __init__(self, metadata: Mapping[str, bytes], wheel_name: str) -> None:
55
+ self._metadata = metadata
56
+ self._wheel_name = wheel_name
57
+
58
+ def has_metadata(self, name: str) -> bool:
59
+ return name in self._metadata
60
+
61
+ def get_metadata(self, name: str) -> str:
62
+ try:
63
+ return self._metadata[name].decode()
64
+ except UnicodeDecodeError as e:
65
+ # Augment the default error with the origin of the file.
66
+ raise UnsupportedWheel(
67
+ f"Error decoding metadata for {self._wheel_name}: {e} in {name} file"
68
+ )
69
+
70
+ def get_metadata_lines(self, name: str) -> Iterable[str]:
71
+ return pkg_resources.yield_lines(self.get_metadata(name))
72
+
73
+ def metadata_isdir(self, name: str) -> bool:
74
+ return False
75
+
76
+ def metadata_listdir(self, name: str) -> List[str]:
77
+ return []
78
+
79
+ def run_script(self, script_name: str, namespace: str) -> None:
80
+ pass
81
+
82
+
83
+ class Distribution(BaseDistribution):
84
+ def __init__(self, dist: pkg_resources.Distribution) -> None:
85
+ self._dist = dist
86
+ # This is populated lazily, to avoid loading metadata for all possible
87
+ # distributions eagerly.
88
+ self.__extra_mapping: Optional[Mapping[NormalizedName, str]] = None
89
+
90
+ @property
91
+ def _extra_mapping(self) -> Mapping[NormalizedName, str]:
92
+ if self.__extra_mapping is None:
93
+ self.__extra_mapping = {
94
+ canonicalize_name(extra): extra for extra in self._dist.extras
95
+ }
96
+
97
+ return self.__extra_mapping
98
+
99
+ @classmethod
100
+ def from_directory(cls, directory: str) -> BaseDistribution:
101
+ dist_dir = directory.rstrip(os.sep)
102
+
103
+ # Build a PathMetadata object, from path to metadata. :wink:
104
+ base_dir, dist_dir_name = os.path.split(dist_dir)
105
+ metadata = pkg_resources.PathMetadata(base_dir, dist_dir)
106
+
107
+ # Determine the correct Distribution object type.
108
+ if dist_dir.endswith(".egg-info"):
109
+ dist_cls = pkg_resources.Distribution
110
+ dist_name = os.path.splitext(dist_dir_name)[0]
111
+ else:
112
+ assert dist_dir.endswith(".dist-info")
113
+ dist_cls = pkg_resources.DistInfoDistribution
114
+ dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0]
115
+
116
+ dist = dist_cls(base_dir, project_name=dist_name, metadata=metadata)
117
+ return cls(dist)
118
+
119
+ @classmethod
120
+ def from_metadata_file_contents(
121
+ cls,
122
+ metadata_contents: bytes,
123
+ filename: str,
124
+ project_name: str,
125
+ ) -> BaseDistribution:
126
+ metadata_dict = {
127
+ "METADATA": metadata_contents,
128
+ }
129
+ dist = pkg_resources.DistInfoDistribution(
130
+ location=filename,
131
+ metadata=InMemoryMetadata(metadata_dict, filename),
132
+ project_name=project_name,
133
+ )
134
+ return cls(dist)
135
+
136
+ @classmethod
137
+ def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
138
+ try:
139
+ with wheel.as_zipfile() as zf:
140
+ info_dir, _ = parse_wheel(zf, name)
141
+ metadata_dict = {
142
+ path.split("/", 1)[-1]: read_wheel_metadata_file(zf, path)
143
+ for path in zf.namelist()
144
+ if path.startswith(f"{info_dir}/")
145
+ }
146
+ except zipfile.BadZipFile as e:
147
+ raise InvalidWheel(wheel.location, name) from e
148
+ except UnsupportedWheel as e:
149
+ raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
150
+ dist = pkg_resources.DistInfoDistribution(
151
+ location=wheel.location,
152
+ metadata=InMemoryMetadata(metadata_dict, wheel.location),
153
+ project_name=name,
154
+ )
155
+ return cls(dist)
156
+
157
+ @property
158
+ def location(self) -> Optional[str]:
159
+ return self._dist.location
160
+
161
+ @property
162
+ def installed_location(self) -> Optional[str]:
163
+ egg_link = egg_link_path_from_location(self.raw_name)
164
+ if egg_link:
165
+ location = egg_link
166
+ elif self.location:
167
+ location = self.location
168
+ else:
169
+ return None
170
+ return normalize_path(location)
171
+
172
+ @property
173
+ def info_location(self) -> Optional[str]:
174
+ return self._dist.egg_info
175
+
176
+ @property
177
+ def installed_by_distutils(self) -> bool:
178
+ # A distutils-installed distribution is provided by FileMetadata. This
179
+ # provider has a "path" attribute not present anywhere else. Not the
180
+ # best introspection logic, but pip has been doing this for a long time.
181
+ try:
182
+ return bool(self._dist._provider.path)
183
+ except AttributeError:
184
+ return False
185
+
186
+ @property
187
+ def canonical_name(self) -> NormalizedName:
188
+ return canonicalize_name(self._dist.project_name)
189
+
190
+ @property
191
+ def version(self) -> Version:
192
+ return parse_version(self._dist.version)
193
+
194
+ @property
195
+ def raw_version(self) -> str:
196
+ return self._dist.version
197
+
198
+ def is_file(self, path: InfoPath) -> bool:
199
+ return self._dist.has_metadata(str(path))
200
+
201
+ def iter_distutils_script_names(self) -> Iterator[str]:
202
+ yield from self._dist.metadata_listdir("scripts")
203
+
204
+ def read_text(self, path: InfoPath) -> str:
205
+ name = str(path)
206
+ if not self._dist.has_metadata(name):
207
+ raise FileNotFoundError(name)
208
+ content = self._dist.get_metadata(name)
209
+ if content is None:
210
+ raise NoneMetadataError(self, name)
211
+ return content
212
+
213
+ def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
214
+ for group, entries in self._dist.get_entry_map().items():
215
+ for name, entry_point in entries.items():
216
+ name, _, value = str(entry_point).partition("=")
217
+ yield EntryPoint(name=name.strip(), value=value.strip(), group=group)
218
+
219
+ def _metadata_impl(self) -> email.message.Message:
220
+ """
221
+ :raises NoneMetadataError: if the distribution reports `has_metadata()`
222
+ True but `get_metadata()` returns None.
223
+ """
224
+ if isinstance(self._dist, pkg_resources.DistInfoDistribution):
225
+ metadata_name = "METADATA"
226
+ else:
227
+ metadata_name = "PKG-INFO"
228
+ try:
229
+ metadata = self.read_text(metadata_name)
230
+ except FileNotFoundError:
231
+ if self.location:
232
+ displaying_path = display_path(self.location)
233
+ else:
234
+ displaying_path = repr(self.location)
235
+ logger.warning("No metadata found in %s", displaying_path)
236
+ metadata = ""
237
+ feed_parser = email.parser.FeedParser()
238
+ feed_parser.feed(metadata)
239
+ return feed_parser.close()
240
+
241
+ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
242
+ if extras:
243
+ relevant_extras = set(self._extra_mapping) & set(
244
+ map(canonicalize_name, extras)
245
+ )
246
+ extras = [self._extra_mapping[extra] for extra in relevant_extras]
247
+ return self._dist.requires(extras)
248
+
249
+ def iter_provided_extras(self) -> Iterable[NormalizedName]:
250
+ return self._extra_mapping.keys()
251
+
252
+
253
+ class Environment(BaseEnvironment):
254
+ def __init__(self, ws: pkg_resources.WorkingSet) -> None:
255
+ self._ws = ws
256
+
257
+ @classmethod
258
+ def default(cls) -> BaseEnvironment:
259
+ return cls(pkg_resources.working_set)
260
+
261
+ @classmethod
262
+ def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
263
+ return cls(pkg_resources.WorkingSet(paths))
264
+
265
+ def _iter_distributions(self) -> Iterator[BaseDistribution]:
266
+ for dist in self._ws:
267
+ yield Distribution(dist)
268
+
269
+ def _search_distribution(self, name: str) -> Optional[BaseDistribution]:
270
+ """Find a distribution matching the ``name`` in the environment.
271
+
272
+ This searches from *all* distributions available in the environment, to
273
+ match the behavior of ``pkg_resources.get_distribution()``.
274
+ """
275
+ canonical_name = canonicalize_name(name)
276
+ for dist in self.iter_all_distributions():
277
+ if dist.canonical_name == canonical_name:
278
+ return dist
279
+ return None
280
+
281
+ def get_distribution(self, name: str) -> Optional[BaseDistribution]:
282
+ # Search the distribution by looking through the working set.
283
+ dist = self._search_distribution(name)
284
+ if dist:
285
+ return dist
286
+
287
+ # If distribution could not be found, call working_set.require to
288
+ # update the working set, and try to find the distribution again.
289
+ # This might happen for e.g. when you install a package twice, once
290
+ # using setup.py develop and again using setup.py install. Now when
291
+ # running pip uninstall twice, the package gets removed from the
292
+ # working set in the first uninstall, so we have to populate the
293
+ # working set again so that pip knows about it and the packages gets
294
+ # picked up and is successfully uninstalled the second time too.
295
+ try:
296
+ # We didn't pass in any version specifiers, so this can never
297
+ # raise pkg_resources.VersionConflict.
298
+ self._ws.require(name)
299
+ except pkg_resources.DistributionNotFound:
300
+ return None
301
+ return self._search_distribution(name)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ """A package that contains models that represent entities.
2
+ """
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/candidate.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+
3
+ from pip._vendor.packaging.version import Version
4
+ from pip._vendor.packaging.version import parse as parse_version
5
+
6
+ from pip._internal.models.link import Link
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class InstallationCandidate:
11
+ """Represents a potential "candidate" for installation."""
12
+
13
+ __slots__ = ["name", "version", "link"]
14
+
15
+ name: str
16
+ version: Version
17
+ link: Link
18
+
19
+ def __init__(self, name: str, version: str, link: Link) -> None:
20
+ object.__setattr__(self, "name", name)
21
+ object.__setattr__(self, "version", parse_version(version))
22
+ object.__setattr__(self, "link", link)
23
+
24
+ def __str__(self) -> str:
25
+ return f"{self.name!r} candidate (version {self.version} at {self.link})"
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/direct_url.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ PEP 610 """
2
+
3
+ import json
4
+ import re
5
+ import urllib.parse
6
+ from dataclasses import dataclass
7
+ from typing import Any, ClassVar, Dict, Iterable, Optional, Type, TypeVar, Union
8
+
9
+ __all__ = [
10
+ "DirectUrl",
11
+ "DirectUrlValidationError",
12
+ "DirInfo",
13
+ "ArchiveInfo",
14
+ "VcsInfo",
15
+ ]
16
+
17
+ T = TypeVar("T")
18
+
19
+ DIRECT_URL_METADATA_NAME = "direct_url.json"
20
+ ENV_VAR_RE = re.compile(r"^\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?$")
21
+
22
+
23
+ class DirectUrlValidationError(Exception):
24
+ pass
25
+
26
+
27
+ def _get(
28
+ d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
29
+ ) -> Optional[T]:
30
+ """Get value from dictionary and verify expected type."""
31
+ if key not in d:
32
+ return default
33
+ value = d[key]
34
+ if not isinstance(value, expected_type):
35
+ raise DirectUrlValidationError(
36
+ f"{value!r} has unexpected type for {key} (expected {expected_type})"
37
+ )
38
+ return value
39
+
40
+
41
+ def _get_required(
42
+ d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
43
+ ) -> T:
44
+ value = _get(d, expected_type, key, default)
45
+ if value is None:
46
+ raise DirectUrlValidationError(f"{key} must have a value")
47
+ return value
48
+
49
+
50
+ def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType":
51
+ infos = [info for info in infos if info is not None]
52
+ if not infos:
53
+ raise DirectUrlValidationError(
54
+ "missing one of archive_info, dir_info, vcs_info"
55
+ )
56
+ if len(infos) > 1:
57
+ raise DirectUrlValidationError(
58
+ "more than one of archive_info, dir_info, vcs_info"
59
+ )
60
+ assert infos[0] is not None
61
+ return infos[0]
62
+
63
+
64
+ def _filter_none(**kwargs: Any) -> Dict[str, Any]:
65
+ """Make dict excluding None values."""
66
+ return {k: v for k, v in kwargs.items() if v is not None}
67
+
68
+
69
+ @dataclass
70
+ class VcsInfo:
71
+ name: ClassVar = "vcs_info"
72
+
73
+ vcs: str
74
+ commit_id: str
75
+ requested_revision: Optional[str] = None
76
+
77
+ @classmethod
78
+ def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]:
79
+ if d is None:
80
+ return None
81
+ return cls(
82
+ vcs=_get_required(d, str, "vcs"),
83
+ commit_id=_get_required(d, str, "commit_id"),
84
+ requested_revision=_get(d, str, "requested_revision"),
85
+ )
86
+
87
+ def _to_dict(self) -> Dict[str, Any]:
88
+ return _filter_none(
89
+ vcs=self.vcs,
90
+ requested_revision=self.requested_revision,
91
+ commit_id=self.commit_id,
92
+ )
93
+
94
+
95
+ class ArchiveInfo:
96
+ name = "archive_info"
97
+
98
+ def __init__(
99
+ self,
100
+ hash: Optional[str] = None,
101
+ hashes: Optional[Dict[str, str]] = None,
102
+ ) -> None:
103
+ # set hashes before hash, since the hash setter will further populate hashes
104
+ self.hashes = hashes
105
+ self.hash = hash
106
+
107
+ @property
108
+ def hash(self) -> Optional[str]:
109
+ return self._hash
110
+
111
+ @hash.setter
112
+ def hash(self, value: Optional[str]) -> None:
113
+ if value is not None:
114
+ # Auto-populate the hashes key to upgrade to the new format automatically.
115
+ # We don't back-populate the legacy hash key from hashes.
116
+ try:
117
+ hash_name, hash_value = value.split("=", 1)
118
+ except ValueError:
119
+ raise DirectUrlValidationError(
120
+ f"invalid archive_info.hash format: {value!r}"
121
+ )
122
+ if self.hashes is None:
123
+ self.hashes = {hash_name: hash_value}
124
+ elif hash_name not in self.hashes:
125
+ self.hashes = self.hashes.copy()
126
+ self.hashes[hash_name] = hash_value
127
+ self._hash = value
128
+
129
+ @classmethod
130
+ def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]:
131
+ if d is None:
132
+ return None
133
+ return cls(hash=_get(d, str, "hash"), hashes=_get(d, dict, "hashes"))
134
+
135
+ def _to_dict(self) -> Dict[str, Any]:
136
+ return _filter_none(hash=self.hash, hashes=self.hashes)
137
+
138
+
139
+ @dataclass
140
+ class DirInfo:
141
+ name: ClassVar = "dir_info"
142
+
143
+ editable: bool = False
144
+
145
+ @classmethod
146
+ def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["DirInfo"]:
147
+ if d is None:
148
+ return None
149
+ return cls(editable=_get_required(d, bool, "editable", default=False))
150
+
151
+ def _to_dict(self) -> Dict[str, Any]:
152
+ return _filter_none(editable=self.editable or None)
153
+
154
+
155
+ InfoType = Union[ArchiveInfo, DirInfo, VcsInfo]
156
+
157
+
158
+ @dataclass
159
+ class DirectUrl:
160
+ url: str
161
+ info: InfoType
162
+ subdirectory: Optional[str] = None
163
+
164
+ def _remove_auth_from_netloc(self, netloc: str) -> str:
165
+ if "@" not in netloc:
166
+ return netloc
167
+ user_pass, netloc_no_user_pass = netloc.split("@", 1)
168
+ if (
169
+ isinstance(self.info, VcsInfo)
170
+ and self.info.vcs == "git"
171
+ and user_pass == "git"
172
+ ):
173
+ return netloc
174
+ if ENV_VAR_RE.match(user_pass):
175
+ return netloc
176
+ return netloc_no_user_pass
177
+
178
+ @property
179
+ def redacted_url(self) -> str:
180
+ """url with user:password part removed unless it is formed with
181
+ environment variables as specified in PEP 610, or it is ``git``
182
+ in the case of a git URL.
183
+ """
184
+ purl = urllib.parse.urlsplit(self.url)
185
+ netloc = self._remove_auth_from_netloc(purl.netloc)
186
+ surl = urllib.parse.urlunsplit(
187
+ (purl.scheme, netloc, purl.path, purl.query, purl.fragment)
188
+ )
189
+ return surl
190
+
191
+ def validate(self) -> None:
192
+ self.from_dict(self.to_dict())
193
+
194
+ @classmethod
195
+ def from_dict(cls, d: Dict[str, Any]) -> "DirectUrl":
196
+ return DirectUrl(
197
+ url=_get_required(d, str, "url"),
198
+ subdirectory=_get(d, str, "subdirectory"),
199
+ info=_exactly_one_of(
200
+ [
201
+ ArchiveInfo._from_dict(_get(d, dict, "archive_info")),
202
+ DirInfo._from_dict(_get(d, dict, "dir_info")),
203
+ VcsInfo._from_dict(_get(d, dict, "vcs_info")),
204
+ ]
205
+ ),
206
+ )
207
+
208
+ def to_dict(self) -> Dict[str, Any]:
209
+ res = _filter_none(
210
+ url=self.redacted_url,
211
+ subdirectory=self.subdirectory,
212
+ )
213
+ res[self.info.name] = self.info._to_dict()
214
+ return res
215
+
216
+ @classmethod
217
+ def from_json(cls, s: str) -> "DirectUrl":
218
+ return cls.from_dict(json.loads(s))
219
+
220
+ def to_json(self) -> str:
221
+ return json.dumps(self.to_dict(), sort_keys=True)
222
+
223
+ def is_local_editable(self) -> bool:
224
+ return isinstance(self.info, DirInfo) and self.info.editable
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/format_control.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import FrozenSet, Optional, Set
2
+
3
+ from pip._vendor.packaging.utils import canonicalize_name
4
+
5
+ from pip._internal.exceptions import CommandError
6
+
7
+
8
+ class FormatControl:
9
+ """Helper for managing formats from which a package can be installed."""
10
+
11
+ __slots__ = ["no_binary", "only_binary"]
12
+
13
+ def __init__(
14
+ self,
15
+ no_binary: Optional[Set[str]] = None,
16
+ only_binary: Optional[Set[str]] = None,
17
+ ) -> None:
18
+ if no_binary is None:
19
+ no_binary = set()
20
+ if only_binary is None:
21
+ only_binary = set()
22
+
23
+ self.no_binary = no_binary
24
+ self.only_binary = only_binary
25
+
26
+ def __eq__(self, other: object) -> bool:
27
+ if not isinstance(other, self.__class__):
28
+ return NotImplemented
29
+
30
+ if self.__slots__ != other.__slots__:
31
+ return False
32
+
33
+ return all(getattr(self, k) == getattr(other, k) for k in self.__slots__)
34
+
35
+ def __repr__(self) -> str:
36
+ return f"{self.__class__.__name__}({self.no_binary}, {self.only_binary})"
37
+
38
+ @staticmethod
39
+ def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None:
40
+ if value.startswith("-"):
41
+ raise CommandError(
42
+ "--no-binary / --only-binary option requires 1 argument."
43
+ )
44
+ new = value.split(",")
45
+ while ":all:" in new:
46
+ other.clear()
47
+ target.clear()
48
+ target.add(":all:")
49
+ del new[: new.index(":all:") + 1]
50
+ # Without a none, we want to discard everything as :all: covers it
51
+ if ":none:" not in new:
52
+ return
53
+ for name in new:
54
+ if name == ":none:":
55
+ target.clear()
56
+ continue
57
+ name = canonicalize_name(name)
58
+ other.discard(name)
59
+ target.add(name)
60
+
61
+ def get_allowed_formats(self, canonical_name: str) -> FrozenSet[str]:
62
+ result = {"binary", "source"}
63
+ if canonical_name in self.only_binary:
64
+ result.discard("source")
65
+ elif canonical_name in self.no_binary:
66
+ result.discard("binary")
67
+ elif ":all:" in self.only_binary:
68
+ result.discard("source")
69
+ elif ":all:" in self.no_binary:
70
+ result.discard("binary")
71
+ return frozenset(result)
72
+
73
+ def disallow_binaries(self) -> None:
74
+ self.handle_mutual_excludes(
75
+ ":all:",
76
+ self.no_binary,
77
+ self.only_binary,
78
+ )
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/index.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import urllib.parse
2
+
3
+
4
+ class PackageIndex:
5
+ """Represents a Package Index and provides easier access to endpoints"""
6
+
7
+ __slots__ = ["url", "netloc", "simple_url", "pypi_url", "file_storage_domain"]
8
+
9
+ def __init__(self, url: str, file_storage_domain: str) -> None:
10
+ super().__init__()
11
+ self.url = url
12
+ self.netloc = urllib.parse.urlsplit(url).netloc
13
+ self.simple_url = self._url_for_path("simple")
14
+ self.pypi_url = self._url_for_path("pypi")
15
+
16
+ # This is part of a temporary hack used to block installs of PyPI
17
+ # packages which depend on external urls only necessary until PyPI can
18
+ # block such packages themselves
19
+ self.file_storage_domain = file_storage_domain
20
+
21
+ def _url_for_path(self, path: str) -> str:
22
+ return urllib.parse.urljoin(self.url, path)
23
+
24
+
25
+ PyPI = PackageIndex("https://pypi.org/", file_storage_domain="files.pythonhosted.org")
26
+ TestPyPI = PackageIndex(
27
+ "https://test.pypi.org/", file_storage_domain="test-files.pythonhosted.org"
28
+ )
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/installation_report.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict, Sequence
2
+
3
+ from pip._vendor.packaging.markers import default_environment
4
+
5
+ from pip import __version__
6
+ from pip._internal.req.req_install import InstallRequirement
7
+
8
+
9
+ class InstallationReport:
10
+ def __init__(self, install_requirements: Sequence[InstallRequirement]):
11
+ self._install_requirements = install_requirements
12
+
13
+ @classmethod
14
+ def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]:
15
+ assert ireq.download_info, f"No download_info for {ireq}"
16
+ res = {
17
+ # PEP 610 json for the download URL. download_info.archive_info.hashes may
18
+ # be absent when the requirement was installed from the wheel cache
19
+ # and the cache entry was populated by an older pip version that did not
20
+ # record origin.json.
21
+ "download_info": ireq.download_info.to_dict(),
22
+ # is_direct is true if the requirement was a direct URL reference (which
23
+ # includes editable requirements), and false if the requirement was
24
+ # downloaded from a PEP 503 index or --find-links.
25
+ "is_direct": ireq.is_direct,
26
+ # is_yanked is true if the requirement was yanked from the index, but
27
+ # was still selected by pip to conform to PEP 592.
28
+ "is_yanked": ireq.link.is_yanked if ireq.link else False,
29
+ # requested is true if the requirement was specified by the user (aka
30
+ # top level requirement), and false if it was installed as a dependency of a
31
+ # requirement. https://peps.python.org/pep-0376/#requested
32
+ "requested": ireq.user_supplied,
33
+ # PEP 566 json encoding for metadata
34
+ # https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata
35
+ "metadata": ireq.get_dist().metadata_dict,
36
+ }
37
+ if ireq.user_supplied and ireq.extras:
38
+ # For top level requirements, the list of requested extras, if any.
39
+ res["requested_extras"] = sorted(ireq.extras)
40
+ return res
41
+
42
+ def to_dict(self) -> Dict[str, Any]:
43
+ return {
44
+ "version": "1",
45
+ "pip_version": __version__,
46
+ "install": [
47
+ self._install_req_to_dict(ireq) for ireq in self._install_requirements
48
+ ],
49
+ # https://peps.python.org/pep-0508/#environment-markers
50
+ # TODO: currently, the resolver uses the default environment to evaluate
51
+ # environment markers, so that is what we report here. In the future, it
52
+ # should also take into account options such as --python-version or
53
+ # --platform, perhaps under the form of an environment_override field?
54
+ # https://github.com/pypa/pip/issues/11198
55
+ "environment": default_environment(),
56
+ }
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/link.py ADDED
@@ -0,0 +1,604 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import itertools
3
+ import logging
4
+ import os
5
+ import posixpath
6
+ import re
7
+ import urllib.parse
8
+ from dataclasses import dataclass
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ Any,
12
+ Dict,
13
+ List,
14
+ Mapping,
15
+ NamedTuple,
16
+ Optional,
17
+ Tuple,
18
+ Union,
19
+ )
20
+
21
+ from pip._internal.utils.deprecation import deprecated
22
+ from pip._internal.utils.filetypes import WHEEL_EXTENSION
23
+ from pip._internal.utils.hashes import Hashes
24
+ from pip._internal.utils.misc import (
25
+ pairwise,
26
+ redact_auth_from_url,
27
+ split_auth_from_netloc,
28
+ splitext,
29
+ )
30
+ from pip._internal.utils.urls import path_to_url, url_to_path
31
+
32
+ if TYPE_CHECKING:
33
+ from pip._internal.index.collector import IndexContent
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ # Order matters, earlier hashes have a precedence over later hashes for what
39
+ # we will pick to use.
40
+ _SUPPORTED_HASHES = ("sha512", "sha384", "sha256", "sha224", "sha1", "md5")
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class LinkHash:
45
+ """Links to content may have embedded hash values. This class parses those.
46
+
47
+ `name` must be any member of `_SUPPORTED_HASHES`.
48
+
49
+ This class can be converted to and from `ArchiveInfo`. While ArchiveInfo intends to
50
+ be JSON-serializable to conform to PEP 610, this class contains the logic for
51
+ parsing a hash name and value for correctness, and then checking whether that hash
52
+ conforms to a schema with `.is_hash_allowed()`."""
53
+
54
+ name: str
55
+ value: str
56
+
57
+ _hash_url_fragment_re = re.compile(
58
+ # NB: we do not validate that the second group (.*) is a valid hex
59
+ # digest. Instead, we simply keep that string in this class, and then check it
60
+ # against Hashes when hash-checking is needed. This is easier to debug than
61
+ # proactively discarding an invalid hex digest, as we handle incorrect hashes
62
+ # and malformed hashes in the same place.
63
+ r"[#&]({choices})=([^&]*)".format(
64
+ choices="|".join(re.escape(hash_name) for hash_name in _SUPPORTED_HASHES)
65
+ ),
66
+ )
67
+
68
+ def __post_init__(self) -> None:
69
+ assert self.name in _SUPPORTED_HASHES
70
+
71
+ @classmethod
72
+ @functools.lru_cache(maxsize=None)
73
+ def find_hash_url_fragment(cls, url: str) -> Optional["LinkHash"]:
74
+ """Search a string for a checksum algorithm name and encoded output value."""
75
+ match = cls._hash_url_fragment_re.search(url)
76
+ if match is None:
77
+ return None
78
+ name, value = match.groups()
79
+ return cls(name=name, value=value)
80
+
81
+ def as_dict(self) -> Dict[str, str]:
82
+ return {self.name: self.value}
83
+
84
+ def as_hashes(self) -> Hashes:
85
+ """Return a Hashes instance which checks only for the current hash."""
86
+ return Hashes({self.name: [self.value]})
87
+
88
+ def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
89
+ """
90
+ Return True if the current hash is allowed by `hashes`.
91
+ """
92
+ if hashes is None:
93
+ return False
94
+ return hashes.is_hash_allowed(self.name, hex_digest=self.value)
95
+
96
+
97
+ @dataclass(frozen=True)
98
+ class MetadataFile:
99
+ """Information about a core metadata file associated with a distribution."""
100
+
101
+ hashes: Optional[Dict[str, str]]
102
+
103
+ def __post_init__(self) -> None:
104
+ if self.hashes is not None:
105
+ assert all(name in _SUPPORTED_HASHES for name in self.hashes)
106
+
107
+
108
+ def supported_hashes(hashes: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
109
+ # Remove any unsupported hash types from the mapping. If this leaves no
110
+ # supported hashes, return None
111
+ if hashes is None:
112
+ return None
113
+ hashes = {n: v for n, v in hashes.items() if n in _SUPPORTED_HASHES}
114
+ if not hashes:
115
+ return None
116
+ return hashes
117
+
118
+
119
+ def _clean_url_path_part(part: str) -> str:
120
+ """
121
+ Clean a "part" of a URL path (i.e. after splitting on "@" characters).
122
+ """
123
+ # We unquote prior to quoting to make sure nothing is double quoted.
124
+ return urllib.parse.quote(urllib.parse.unquote(part))
125
+
126
+
127
+ def _clean_file_url_path(part: str) -> str:
128
+ """
129
+ Clean the first part of a URL path that corresponds to a local
130
+ filesystem path (i.e. the first part after splitting on "@" characters).
131
+ """
132
+ # We unquote prior to quoting to make sure nothing is double quoted.
133
+ # Also, on Windows the path part might contain a drive letter which
134
+ # should not be quoted. On Linux where drive letters do not
135
+ # exist, the colon should be quoted. We rely on urllib.request
136
+ # to do the right thing here.
137
+ return urllib.request.pathname2url(urllib.request.url2pathname(part))
138
+
139
+
140
+ # percent-encoded: /
141
+ _reserved_chars_re = re.compile("(@|%2F)", re.IGNORECASE)
142
+
143
+
144
+ def _clean_url_path(path: str, is_local_path: bool) -> str:
145
+ """
146
+ Clean the path portion of a URL.
147
+ """
148
+ if is_local_path:
149
+ clean_func = _clean_file_url_path
150
+ else:
151
+ clean_func = _clean_url_path_part
152
+
153
+ # Split on the reserved characters prior to cleaning so that
154
+ # revision strings in VCS URLs are properly preserved.
155
+ parts = _reserved_chars_re.split(path)
156
+
157
+ cleaned_parts = []
158
+ for to_clean, reserved in pairwise(itertools.chain(parts, [""])):
159
+ cleaned_parts.append(clean_func(to_clean))
160
+ # Normalize %xx escapes (e.g. %2f -> %2F)
161
+ cleaned_parts.append(reserved.upper())
162
+
163
+ return "".join(cleaned_parts)
164
+
165
+
166
+ def _ensure_quoted_url(url: str) -> str:
167
+ """
168
+ Make sure a link is fully quoted.
169
+ For example, if ' ' occurs in the URL, it will be replaced with "%20",
170
+ and without double-quoting other characters.
171
+ """
172
+ # Split the URL into parts according to the general structure
173
+ # `scheme://netloc/path?query#fragment`.
174
+ result = urllib.parse.urlsplit(url)
175
+ # If the netloc is empty, then the URL refers to a local filesystem path.
176
+ is_local_path = not result.netloc
177
+ path = _clean_url_path(result.path, is_local_path=is_local_path)
178
+ return urllib.parse.urlunsplit(result._replace(path=path))
179
+
180
+
181
+ def _absolute_link_url(base_url: str, url: str) -> str:
182
+ """
183
+ A faster implementation of urllib.parse.urljoin with a shortcut
184
+ for absolute http/https URLs.
185
+ """
186
+ if url.startswith(("https://", "http://")):
187
+ return url
188
+ else:
189
+ return urllib.parse.urljoin(base_url, url)
190
+
191
+
192
+ @functools.total_ordering
193
+ class Link:
194
+ """Represents a parsed link from a Package Index's simple URL"""
195
+
196
+ __slots__ = [
197
+ "_parsed_url",
198
+ "_url",
199
+ "_path",
200
+ "_hashes",
201
+ "comes_from",
202
+ "requires_python",
203
+ "yanked_reason",
204
+ "metadata_file_data",
205
+ "cache_link_parsing",
206
+ "egg_fragment",
207
+ ]
208
+
209
+ def __init__(
210
+ self,
211
+ url: str,
212
+ comes_from: Optional[Union[str, "IndexContent"]] = None,
213
+ requires_python: Optional[str] = None,
214
+ yanked_reason: Optional[str] = None,
215
+ metadata_file_data: Optional[MetadataFile] = None,
216
+ cache_link_parsing: bool = True,
217
+ hashes: Optional[Mapping[str, str]] = None,
218
+ ) -> None:
219
+ """
220
+ :param url: url of the resource pointed to (href of the link)
221
+ :param comes_from: instance of IndexContent where the link was found,
222
+ or string.
223
+ :param requires_python: String containing the `Requires-Python`
224
+ metadata field, specified in PEP 345. This may be specified by
225
+ a data-requires-python attribute in the HTML link tag, as
226
+ described in PEP 503.
227
+ :param yanked_reason: the reason the file has been yanked, if the
228
+ file has been yanked, or None if the file hasn't been yanked.
229
+ This is the value of the "data-yanked" attribute, if present, in
230
+ a simple repository HTML link. If the file has been yanked but
231
+ no reason was provided, this should be the empty string. See
232
+ PEP 592 for more information and the specification.
233
+ :param metadata_file_data: the metadata attached to the file, or None if
234
+ no such metadata is provided. This argument, if not None, indicates
235
+ that a separate metadata file exists, and also optionally supplies
236
+ hashes for that file.
237
+ :param cache_link_parsing: A flag that is used elsewhere to determine
238
+ whether resources retrieved from this link should be cached. PyPI
239
+ URLs should generally have this set to False, for example.
240
+ :param hashes: A mapping of hash names to digests to allow us to
241
+ determine the validity of a download.
242
+ """
243
+
244
+ # The comes_from, requires_python, and metadata_file_data arguments are
245
+ # only used by classmethods of this class, and are not used in client
246
+ # code directly.
247
+
248
+ # url can be a UNC windows share
249
+ if url.startswith("\\\\"):
250
+ url = path_to_url(url)
251
+
252
+ self._parsed_url = urllib.parse.urlsplit(url)
253
+ # Store the url as a private attribute to prevent accidentally
254
+ # trying to set a new value.
255
+ self._url = url
256
+ # The .path property is hot, so calculate its value ahead of time.
257
+ self._path = urllib.parse.unquote(self._parsed_url.path)
258
+
259
+ link_hash = LinkHash.find_hash_url_fragment(url)
260
+ hashes_from_link = {} if link_hash is None else link_hash.as_dict()
261
+ if hashes is None:
262
+ self._hashes = hashes_from_link
263
+ else:
264
+ self._hashes = {**hashes, **hashes_from_link}
265
+
266
+ self.comes_from = comes_from
267
+ self.requires_python = requires_python if requires_python else None
268
+ self.yanked_reason = yanked_reason
269
+ self.metadata_file_data = metadata_file_data
270
+
271
+ self.cache_link_parsing = cache_link_parsing
272
+ self.egg_fragment = self._egg_fragment()
273
+
274
+ @classmethod
275
+ def from_json(
276
+ cls,
277
+ file_data: Dict[str, Any],
278
+ page_url: str,
279
+ ) -> Optional["Link"]:
280
+ """
281
+ Convert an pypi json document from a simple repository page into a Link.
282
+ """
283
+ file_url = file_data.get("url")
284
+ if file_url is None:
285
+ return None
286
+
287
+ url = _ensure_quoted_url(_absolute_link_url(page_url, file_url))
288
+ pyrequire = file_data.get("requires-python")
289
+ yanked_reason = file_data.get("yanked")
290
+ hashes = file_data.get("hashes", {})
291
+
292
+ # PEP 714: Indexes must use the name core-metadata, but
293
+ # clients should support the old name as a fallback for compatibility.
294
+ metadata_info = file_data.get("core-metadata")
295
+ if metadata_info is None:
296
+ metadata_info = file_data.get("dist-info-metadata")
297
+
298
+ # The metadata info value may be a boolean, or a dict of hashes.
299
+ if isinstance(metadata_info, dict):
300
+ # The file exists, and hashes have been supplied
301
+ metadata_file_data = MetadataFile(supported_hashes(metadata_info))
302
+ elif metadata_info:
303
+ # The file exists, but there are no hashes
304
+ metadata_file_data = MetadataFile(None)
305
+ else:
306
+ # False or not present: the file does not exist
307
+ metadata_file_data = None
308
+
309
+ # The Link.yanked_reason expects an empty string instead of a boolean.
310
+ if yanked_reason and not isinstance(yanked_reason, str):
311
+ yanked_reason = ""
312
+ # The Link.yanked_reason expects None instead of False.
313
+ elif not yanked_reason:
314
+ yanked_reason = None
315
+
316
+ return cls(
317
+ url,
318
+ comes_from=page_url,
319
+ requires_python=pyrequire,
320
+ yanked_reason=yanked_reason,
321
+ hashes=hashes,
322
+ metadata_file_data=metadata_file_data,
323
+ )
324
+
325
+ @classmethod
326
+ def from_element(
327
+ cls,
328
+ anchor_attribs: Dict[str, Optional[str]],
329
+ page_url: str,
330
+ base_url: str,
331
+ ) -> Optional["Link"]:
332
+ """
333
+ Convert an anchor element's attributes in a simple repository page to a Link.
334
+ """
335
+ href = anchor_attribs.get("href")
336
+ if not href:
337
+ return None
338
+
339
+ url = _ensure_quoted_url(_absolute_link_url(base_url, href))
340
+ pyrequire = anchor_attribs.get("data-requires-python")
341
+ yanked_reason = anchor_attribs.get("data-yanked")
342
+
343
+ # PEP 714: Indexes must use the name data-core-metadata, but
344
+ # clients should support the old name as a fallback for compatibility.
345
+ metadata_info = anchor_attribs.get("data-core-metadata")
346
+ if metadata_info is None:
347
+ metadata_info = anchor_attribs.get("data-dist-info-metadata")
348
+ # The metadata info value may be the string "true", or a string of
349
+ # the form "hashname=hashval"
350
+ if metadata_info == "true":
351
+ # The file exists, but there are no hashes
352
+ metadata_file_data = MetadataFile(None)
353
+ elif metadata_info is None:
354
+ # The file does not exist
355
+ metadata_file_data = None
356
+ else:
357
+ # The file exists, and hashes have been supplied
358
+ hashname, sep, hashval = metadata_info.partition("=")
359
+ if sep == "=":
360
+ metadata_file_data = MetadataFile(supported_hashes({hashname: hashval}))
361
+ else:
362
+ # Error - data is wrong. Treat as no hashes supplied.
363
+ logger.debug(
364
+ "Index returned invalid data-dist-info-metadata value: %s",
365
+ metadata_info,
366
+ )
367
+ metadata_file_data = MetadataFile(None)
368
+
369
+ return cls(
370
+ url,
371
+ comes_from=page_url,
372
+ requires_python=pyrequire,
373
+ yanked_reason=yanked_reason,
374
+ metadata_file_data=metadata_file_data,
375
+ )
376
+
377
+ def __str__(self) -> str:
378
+ if self.requires_python:
379
+ rp = f" (requires-python:{self.requires_python})"
380
+ else:
381
+ rp = ""
382
+ if self.comes_from:
383
+ return f"{redact_auth_from_url(self._url)} (from {self.comes_from}){rp}"
384
+ else:
385
+ return redact_auth_from_url(str(self._url))
386
+
387
+ def __repr__(self) -> str:
388
+ return f"<Link {self}>"
389
+
390
+ def __hash__(self) -> int:
391
+ return hash(self.url)
392
+
393
+ def __eq__(self, other: Any) -> bool:
394
+ if not isinstance(other, Link):
395
+ return NotImplemented
396
+ return self.url == other.url
397
+
398
+ def __lt__(self, other: Any) -> bool:
399
+ if not isinstance(other, Link):
400
+ return NotImplemented
401
+ return self.url < other.url
402
+
403
+ @property
404
+ def url(self) -> str:
405
+ return self._url
406
+
407
+ @property
408
+ def filename(self) -> str:
409
+ path = self.path.rstrip("/")
410
+ name = posixpath.basename(path)
411
+ if not name:
412
+ # Make sure we don't leak auth information if the netloc
413
+ # includes a username and password.
414
+ netloc, user_pass = split_auth_from_netloc(self.netloc)
415
+ return netloc
416
+
417
+ name = urllib.parse.unquote(name)
418
+ assert name, f"URL {self._url!r} produced no filename"
419
+ return name
420
+
421
+ @property
422
+ def file_path(self) -> str:
423
+ return url_to_path(self.url)
424
+
425
+ @property
426
+ def scheme(self) -> str:
427
+ return self._parsed_url.scheme
428
+
429
+ @property
430
+ def netloc(self) -> str:
431
+ """
432
+ This can contain auth information.
433
+ """
434
+ return self._parsed_url.netloc
435
+
436
+ @property
437
+ def path(self) -> str:
438
+ return self._path
439
+
440
+ def splitext(self) -> Tuple[str, str]:
441
+ return splitext(posixpath.basename(self.path.rstrip("/")))
442
+
443
+ @property
444
+ def ext(self) -> str:
445
+ return self.splitext()[1]
446
+
447
+ @property
448
+ def url_without_fragment(self) -> str:
449
+ scheme, netloc, path, query, fragment = self._parsed_url
450
+ return urllib.parse.urlunsplit((scheme, netloc, path, query, ""))
451
+
452
+ _egg_fragment_re = re.compile(r"[#&]egg=([^&]*)")
453
+
454
+ # Per PEP 508.
455
+ _project_name_re = re.compile(
456
+ r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
457
+ )
458
+
459
+ def _egg_fragment(self) -> Optional[str]:
460
+ match = self._egg_fragment_re.search(self._url)
461
+ if not match:
462
+ return None
463
+
464
+ # An egg fragment looks like a PEP 508 project name, along with
465
+ # an optional extras specifier. Anything else is invalid.
466
+ project_name = match.group(1)
467
+ if not self._project_name_re.match(project_name):
468
+ deprecated(
469
+ reason=f"{self} contains an egg fragment with a non-PEP 508 name.",
470
+ replacement="to use the req @ url syntax, and remove the egg fragment",
471
+ gone_in="25.1",
472
+ issue=13157,
473
+ )
474
+
475
+ return project_name
476
+
477
+ _subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
478
+
479
+ @property
480
+ def subdirectory_fragment(self) -> Optional[str]:
481
+ match = self._subdirectory_fragment_re.search(self._url)
482
+ if not match:
483
+ return None
484
+ return match.group(1)
485
+
486
+ def metadata_link(self) -> Optional["Link"]:
487
+ """Return a link to the associated core metadata file (if any)."""
488
+ if self.metadata_file_data is None:
489
+ return None
490
+ metadata_url = f"{self.url_without_fragment}.metadata"
491
+ if self.metadata_file_data.hashes is None:
492
+ return Link(metadata_url)
493
+ return Link(metadata_url, hashes=self.metadata_file_data.hashes)
494
+
495
+ def as_hashes(self) -> Hashes:
496
+ return Hashes({k: [v] for k, v in self._hashes.items()})
497
+
498
+ @property
499
+ def hash(self) -> Optional[str]:
500
+ return next(iter(self._hashes.values()), None)
501
+
502
+ @property
503
+ def hash_name(self) -> Optional[str]:
504
+ return next(iter(self._hashes), None)
505
+
506
+ @property
507
+ def show_url(self) -> str:
508
+ return posixpath.basename(self._url.split("#", 1)[0].split("?", 1)[0])
509
+
510
+ @property
511
+ def is_file(self) -> bool:
512
+ return self.scheme == "file"
513
+
514
+ def is_existing_dir(self) -> bool:
515
+ return self.is_file and os.path.isdir(self.file_path)
516
+
517
+ @property
518
+ def is_wheel(self) -> bool:
519
+ return self.ext == WHEEL_EXTENSION
520
+
521
+ @property
522
+ def is_vcs(self) -> bool:
523
+ from pip._internal.vcs import vcs
524
+
525
+ return self.scheme in vcs.all_schemes
526
+
527
+ @property
528
+ def is_yanked(self) -> bool:
529
+ return self.yanked_reason is not None
530
+
531
+ @property
532
+ def has_hash(self) -> bool:
533
+ return bool(self._hashes)
534
+
535
+ def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
536
+ """
537
+ Return True if the link has a hash and it is allowed by `hashes`.
538
+ """
539
+ if hashes is None:
540
+ return False
541
+ return any(hashes.is_hash_allowed(k, v) for k, v in self._hashes.items())
542
+
543
+
544
+ class _CleanResult(NamedTuple):
545
+ """Convert link for equivalency check.
546
+
547
+ This is used in the resolver to check whether two URL-specified requirements
548
+ likely point to the same distribution and can be considered equivalent. This
549
+ equivalency logic avoids comparing URLs literally, which can be too strict
550
+ (e.g. "a=1&b=2" vs "b=2&a=1") and produce conflicts unexpecting to users.
551
+
552
+ Currently this does three things:
553
+
554
+ 1. Drop the basic auth part. This is technically wrong since a server can
555
+ serve different content based on auth, but if it does that, it is even
556
+ impossible to guarantee two URLs without auth are equivalent, since
557
+ the user can input different auth information when prompted. So the
558
+ practical solution is to assume the auth doesn't affect the response.
559
+ 2. Parse the query to avoid the ordering issue. Note that ordering under the
560
+ same key in the query are NOT cleaned; i.e. "a=1&a=2" and "a=2&a=1" are
561
+ still considered different.
562
+ 3. Explicitly drop most of the fragment part, except ``subdirectory=`` and
563
+ hash values, since it should have no impact the downloaded content. Note
564
+ that this drops the "egg=" part historically used to denote the requested
565
+ project (and extras), which is wrong in the strictest sense, but too many
566
+ people are supplying it inconsistently to cause superfluous resolution
567
+ conflicts, so we choose to also ignore them.
568
+ """
569
+
570
+ parsed: urllib.parse.SplitResult
571
+ query: Dict[str, List[str]]
572
+ subdirectory: str
573
+ hashes: Dict[str, str]
574
+
575
+
576
+ def _clean_link(link: Link) -> _CleanResult:
577
+ parsed = link._parsed_url
578
+ netloc = parsed.netloc.rsplit("@", 1)[-1]
579
+ # According to RFC 8089, an empty host in file: means localhost.
580
+ if parsed.scheme == "file" and not netloc:
581
+ netloc = "localhost"
582
+ fragment = urllib.parse.parse_qs(parsed.fragment)
583
+ if "egg" in fragment:
584
+ logger.debug("Ignoring egg= fragment in %s", link)
585
+ try:
586
+ # If there are multiple subdirectory values, use the first one.
587
+ # This matches the behavior of Link.subdirectory_fragment.
588
+ subdirectory = fragment["subdirectory"][0]
589
+ except (IndexError, KeyError):
590
+ subdirectory = ""
591
+ # If there are multiple hash values under the same algorithm, use the
592
+ # first one. This matches the behavior of Link.hash_value.
593
+ hashes = {k: fragment[k][0] for k in _SUPPORTED_HASHES if k in fragment}
594
+ return _CleanResult(
595
+ parsed=parsed._replace(netloc=netloc, query="", fragment=""),
596
+ query=urllib.parse.parse_qs(parsed.query),
597
+ subdirectory=subdirectory,
598
+ hashes=hashes,
599
+ )
600
+
601
+
602
+ @functools.lru_cache(maxsize=None)
603
+ def links_equivalent(link1: Link, link2: Link) -> bool:
604
+ return _clean_link(link1) == _clean_link(link2)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/scheme.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ For types associated with installation schemes.
3
+
4
+ For a general overview of available schemes and their context, see
5
+ https://docs.python.org/3/install/index.html#alternate-installation.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+
10
+ SCHEME_KEYS = ["platlib", "purelib", "headers", "scripts", "data"]
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class Scheme:
15
+ """A Scheme holds paths which are used as the base directories for
16
+ artifacts associated with a Python package.
17
+ """
18
+
19
+ __slots__ = SCHEME_KEYS
20
+
21
+ platlib: str
22
+ purelib: str
23
+ headers: str
24
+ scripts: str
25
+ data: str
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/search_scope.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import itertools
2
+ import logging
3
+ import os
4
+ import posixpath
5
+ import urllib.parse
6
+ from dataclasses import dataclass
7
+ from typing import List
8
+
9
+ from pip._vendor.packaging.utils import canonicalize_name
10
+
11
+ from pip._internal.models.index import PyPI
12
+ from pip._internal.utils.compat import has_tls
13
+ from pip._internal.utils.misc import normalize_path, redact_auth_from_url
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class SearchScope:
20
+ """
21
+ Encapsulates the locations that pip is configured to search.
22
+ """
23
+
24
+ __slots__ = ["find_links", "index_urls", "no_index"]
25
+
26
+ find_links: List[str]
27
+ index_urls: List[str]
28
+ no_index: bool
29
+
30
+ @classmethod
31
+ def create(
32
+ cls,
33
+ find_links: List[str],
34
+ index_urls: List[str],
35
+ no_index: bool,
36
+ ) -> "SearchScope":
37
+ """
38
+ Create a SearchScope object after normalizing the `find_links`.
39
+ """
40
+ # Build find_links. If an argument starts with ~, it may be
41
+ # a local file relative to a home directory. So try normalizing
42
+ # it and if it exists, use the normalized version.
43
+ # This is deliberately conservative - it might be fine just to
44
+ # blindly normalize anything starting with a ~...
45
+ built_find_links: List[str] = []
46
+ for link in find_links:
47
+ if link.startswith("~"):
48
+ new_link = normalize_path(link)
49
+ if os.path.exists(new_link):
50
+ link = new_link
51
+ built_find_links.append(link)
52
+
53
+ # If we don't have TLS enabled, then WARN if anyplace we're looking
54
+ # relies on TLS.
55
+ if not has_tls():
56
+ for link in itertools.chain(index_urls, built_find_links):
57
+ parsed = urllib.parse.urlparse(link)
58
+ if parsed.scheme == "https":
59
+ logger.warning(
60
+ "pip is configured with locations that require "
61
+ "TLS/SSL, however the ssl module in Python is not "
62
+ "available."
63
+ )
64
+ break
65
+
66
+ return cls(
67
+ find_links=built_find_links,
68
+ index_urls=index_urls,
69
+ no_index=no_index,
70
+ )
71
+
72
+ def get_formatted_locations(self) -> str:
73
+ lines = []
74
+ redacted_index_urls = []
75
+ if self.index_urls and self.index_urls != [PyPI.simple_url]:
76
+ for url in self.index_urls:
77
+ redacted_index_url = redact_auth_from_url(url)
78
+
79
+ # Parse the URL
80
+ purl = urllib.parse.urlsplit(redacted_index_url)
81
+
82
+ # URL is generally invalid if scheme and netloc is missing
83
+ # there are issues with Python and URL parsing, so this test
84
+ # is a bit crude. See bpo-20271, bpo-23505. Python doesn't
85
+ # always parse invalid URLs correctly - it should raise
86
+ # exceptions for malformed URLs
87
+ if not purl.scheme and not purl.netloc:
88
+ logger.warning(
89
+ 'The index url "%s" seems invalid, please provide a scheme.',
90
+ redacted_index_url,
91
+ )
92
+
93
+ redacted_index_urls.append(redacted_index_url)
94
+
95
+ lines.append(
96
+ "Looking in indexes: {}".format(", ".join(redacted_index_urls))
97
+ )
98
+
99
+ if self.find_links:
100
+ lines.append(
101
+ "Looking in links: {}".format(
102
+ ", ".join(redact_auth_from_url(url) for url in self.find_links)
103
+ )
104
+ )
105
+ return "\n".join(lines)
106
+
107
+ def get_index_urls_locations(self, project_name: str) -> List[str]:
108
+ """Returns the locations found via self.index_urls
109
+
110
+ Checks the url_name on the main (first in the list) index and
111
+ use this url_name to produce all locations
112
+ """
113
+
114
+ def mkurl_pypi_url(url: str) -> str:
115
+ loc = posixpath.join(
116
+ url, urllib.parse.quote(canonicalize_name(project_name))
117
+ )
118
+ # For maximum compatibility with easy_install, ensure the path
119
+ # ends in a trailing slash. Although this isn't in the spec
120
+ # (and PyPI can handle it without the slash) some other index
121
+ # implementations might break if they relied on easy_install's
122
+ # behavior.
123
+ if not loc.endswith("/"):
124
+ loc = loc + "/"
125
+ return loc
126
+
127
+ return [mkurl_pypi_url(url) for url in self.index_urls]
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/selection_prefs.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ from pip._internal.models.format_control import FormatControl
4
+
5
+
6
+ # TODO: This needs Python 3.10's improved slots support for dataclasses
7
+ # to be converted into a dataclass.
8
+ class SelectionPreferences:
9
+ """
10
+ Encapsulates the candidate selection preferences for downloading
11
+ and installing files.
12
+ """
13
+
14
+ __slots__ = [
15
+ "allow_yanked",
16
+ "allow_all_prereleases",
17
+ "format_control",
18
+ "prefer_binary",
19
+ "ignore_requires_python",
20
+ ]
21
+
22
+ # Don't include an allow_yanked default value to make sure each call
23
+ # site considers whether yanked releases are allowed. This also causes
24
+ # that decision to be made explicit in the calling code, which helps
25
+ # people when reading the code.
26
+ def __init__(
27
+ self,
28
+ allow_yanked: bool,
29
+ allow_all_prereleases: bool = False,
30
+ format_control: Optional[FormatControl] = None,
31
+ prefer_binary: bool = False,
32
+ ignore_requires_python: Optional[bool] = None,
33
+ ) -> None:
34
+ """Create a SelectionPreferences object.
35
+
36
+ :param allow_yanked: Whether files marked as yanked (in the sense
37
+ of PEP 592) are permitted to be candidates for install.
38
+ :param format_control: A FormatControl object or None. Used to control
39
+ the selection of source packages / binary packages when consulting
40
+ the index and links.
41
+ :param prefer_binary: Whether to prefer an old, but valid, binary
42
+ dist over a new source dist.
43
+ :param ignore_requires_python: Whether to ignore incompatible
44
+ "Requires-Python" values in links. Defaults to False.
45
+ """
46
+ if ignore_requires_python is None:
47
+ ignore_requires_python = False
48
+
49
+ self.allow_yanked = allow_yanked
50
+ self.allow_all_prereleases = allow_all_prereleases
51
+ self.format_control = format_control
52
+ self.prefer_binary = prefer_binary
53
+ self.ignore_requires_python = ignore_requires_python
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/target_python.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from typing import List, Optional, Set, Tuple
3
+
4
+ from pip._vendor.packaging.tags import Tag
5
+
6
+ from pip._internal.utils.compatibility_tags import get_supported, version_info_to_nodot
7
+ from pip._internal.utils.misc import normalize_version_info
8
+
9
+
10
+ class TargetPython:
11
+ """
12
+ Encapsulates the properties of a Python interpreter one is targeting
13
+ for a package install, download, etc.
14
+ """
15
+
16
+ __slots__ = [
17
+ "_given_py_version_info",
18
+ "abis",
19
+ "implementation",
20
+ "platforms",
21
+ "py_version",
22
+ "py_version_info",
23
+ "_valid_tags",
24
+ "_valid_tags_set",
25
+ ]
26
+
27
+ def __init__(
28
+ self,
29
+ platforms: Optional[List[str]] = None,
30
+ py_version_info: Optional[Tuple[int, ...]] = None,
31
+ abis: Optional[List[str]] = None,
32
+ implementation: Optional[str] = None,
33
+ ) -> None:
34
+ """
35
+ :param platforms: A list of strings or None. If None, searches for
36
+ packages that are supported by the current system. Otherwise, will
37
+ find packages that can be built on the platforms passed in. These
38
+ packages will only be downloaded for distribution: they will
39
+ not be built locally.
40
+ :param py_version_info: An optional tuple of ints representing the
41
+ Python version information to use (e.g. `sys.version_info[:3]`).
42
+ This can have length 1, 2, or 3 when provided.
43
+ :param abis: A list of strings or None. This is passed to
44
+ compatibility_tags.py's get_supported() function as is.
45
+ :param implementation: A string or None. This is passed to
46
+ compatibility_tags.py's get_supported() function as is.
47
+ """
48
+ # Store the given py_version_info for when we call get_supported().
49
+ self._given_py_version_info = py_version_info
50
+
51
+ if py_version_info is None:
52
+ py_version_info = sys.version_info[:3]
53
+ else:
54
+ py_version_info = normalize_version_info(py_version_info)
55
+
56
+ py_version = ".".join(map(str, py_version_info[:2]))
57
+
58
+ self.abis = abis
59
+ self.implementation = implementation
60
+ self.platforms = platforms
61
+ self.py_version = py_version
62
+ self.py_version_info = py_version_info
63
+
64
+ # This is used to cache the return value of get_(un)sorted_tags.
65
+ self._valid_tags: Optional[List[Tag]] = None
66
+ self._valid_tags_set: Optional[Set[Tag]] = None
67
+
68
+ def format_given(self) -> str:
69
+ """
70
+ Format the given, non-None attributes for display.
71
+ """
72
+ display_version = None
73
+ if self._given_py_version_info is not None:
74
+ display_version = ".".join(
75
+ str(part) for part in self._given_py_version_info
76
+ )
77
+
78
+ key_values = [
79
+ ("platforms", self.platforms),
80
+ ("version_info", display_version),
81
+ ("abis", self.abis),
82
+ ("implementation", self.implementation),
83
+ ]
84
+ return " ".join(
85
+ f"{key}={value!r}" for key, value in key_values if value is not None
86
+ )
87
+
88
+ def get_sorted_tags(self) -> List[Tag]:
89
+ """
90
+ Return the supported PEP 425 tags to check wheel candidates against.
91
+
92
+ The tags are returned in order of preference (most preferred first).
93
+ """
94
+ if self._valid_tags is None:
95
+ # Pass versions=None if no py_version_info was given since
96
+ # versions=None uses special default logic.
97
+ py_version_info = self._given_py_version_info
98
+ if py_version_info is None:
99
+ version = None
100
+ else:
101
+ version = version_info_to_nodot(py_version_info)
102
+
103
+ tags = get_supported(
104
+ version=version,
105
+ platforms=self.platforms,
106
+ abis=self.abis,
107
+ impl=self.implementation,
108
+ )
109
+ self._valid_tags = tags
110
+
111
+ return self._valid_tags
112
+
113
+ def get_unsorted_tags(self) -> Set[Tag]:
114
+ """Exactly the same as get_sorted_tags, but returns a set.
115
+
116
+ This is important for performance.
117
+ """
118
+ if self._valid_tags_set is None:
119
+ self._valid_tags_set = set(self.get_sorted_tags())
120
+
121
+ return self._valid_tags_set
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/models/wheel.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Represents a wheel file and provides access to the various parts of the
2
+ name that have meaning.
3
+ """
4
+
5
+ import re
6
+ from typing import Dict, Iterable, List
7
+
8
+ from pip._vendor.packaging.tags import Tag
9
+ from pip._vendor.packaging.utils import (
10
+ InvalidWheelFilename as PackagingInvalidWheelName,
11
+ )
12
+ from pip._vendor.packaging.utils import parse_wheel_filename
13
+
14
+ from pip._internal.exceptions import InvalidWheelFilename
15
+ from pip._internal.utils.deprecation import deprecated
16
+
17
+
18
+ class Wheel:
19
+ """A wheel file"""
20
+
21
+ wheel_file_re = re.compile(
22
+ r"""^(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]*?))
23
+ ((-(?P<build>\d[^-]*?))?-(?P<pyver>[^\s-]+?)-(?P<abi>[^\s-]+?)-(?P<plat>[^\s-]+?)
24
+ \.whl|\.dist-info)$""",
25
+ re.VERBOSE,
26
+ )
27
+
28
+ def __init__(self, filename: str) -> None:
29
+ """
30
+ :raises InvalidWheelFilename: when the filename is invalid for a wheel
31
+ """
32
+ wheel_info = self.wheel_file_re.match(filename)
33
+ if not wheel_info:
34
+ raise InvalidWheelFilename(f"{filename} is not a valid wheel filename.")
35
+ self.filename = filename
36
+ self.name = wheel_info.group("name").replace("_", "-")
37
+ _version = wheel_info.group("ver")
38
+ if "_" in _version:
39
+ try:
40
+ parse_wheel_filename(filename)
41
+ except PackagingInvalidWheelName as e:
42
+ deprecated(
43
+ reason=(
44
+ f"Wheel filename {filename!r} is not correctly normalised. "
45
+ "Future versions of pip will raise the following error:\n"
46
+ f"{e.args[0]}\n\n"
47
+ ),
48
+ replacement=(
49
+ "to rename the wheel to use a correctly normalised "
50
+ "name (this may require updating the version in "
51
+ "the project metadata)"
52
+ ),
53
+ gone_in="25.1",
54
+ issue=12938,
55
+ )
56
+
57
+ _version = _version.replace("_", "-")
58
+
59
+ self.version = _version
60
+ self.build_tag = wheel_info.group("build")
61
+ self.pyversions = wheel_info.group("pyver").split(".")
62
+ self.abis = wheel_info.group("abi").split(".")
63
+ self.plats = wheel_info.group("plat").split(".")
64
+
65
+ # All the tag combinations from this file
66
+ self.file_tags = {
67
+ Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats
68
+ }
69
+
70
+ def get_formatted_file_tags(self) -> List[str]:
71
+ """Return the wheel's tags as a sorted list of strings."""
72
+ return sorted(str(tag) for tag in self.file_tags)
73
+
74
+ def support_index_min(self, tags: List[Tag]) -> int:
75
+ """Return the lowest index that one of the wheel's file_tag combinations
76
+ achieves in the given list of supported tags.
77
+
78
+ For example, if there are 8 supported tags and one of the file tags
79
+ is first in the list, then return 0.
80
+
81
+ :param tags: the PEP 425 tags to check the wheel against, in order
82
+ with most preferred first.
83
+
84
+ :raises ValueError: If none of the wheel's file tags match one of
85
+ the supported tags.
86
+ """
87
+ try:
88
+ return next(i for i, t in enumerate(tags) if t in self.file_tags)
89
+ except StopIteration:
90
+ raise ValueError()
91
+
92
+ def find_most_preferred_tag(
93
+ self, tags: List[Tag], tag_to_priority: Dict[Tag, int]
94
+ ) -> int:
95
+ """Return the priority of the most preferred tag that one of the wheel's file
96
+ tag combinations achieves in the given list of supported tags using the given
97
+ tag_to_priority mapping, where lower priorities are more-preferred.
98
+
99
+ This is used in place of support_index_min in some cases in order to avoid
100
+ an expensive linear scan of a large list of tags.
101
+
102
+ :param tags: the PEP 425 tags to check the wheel against.
103
+ :param tag_to_priority: a mapping from tag to priority of that tag, where
104
+ lower is more preferred.
105
+
106
+ :raises ValueError: If none of the wheel's file tags match one of
107
+ the supported tags.
108
+ """
109
+ return min(
110
+ tag_to_priority[tag] for tag in self.file_tags if tag in tag_to_priority
111
+ )
112
+
113
+ def supported(self, tags: Iterable[Tag]) -> bool:
114
+ """Return whether the wheel is compatible with one of the given tags.
115
+
116
+ :param tags: the PEP 425 tags to check the wheel against.
117
+ """
118
+ return not self.file_tags.isdisjoint(tags)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/pyproject.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib.util
2
+ import os
3
+ import sys
4
+ from collections import namedtuple
5
+ from typing import Any, List, Optional
6
+
7
+ if sys.version_info >= (3, 11):
8
+ import tomllib
9
+ else:
10
+ from pip._vendor import tomli as tomllib
11
+
12
+ from pip._vendor.packaging.requirements import InvalidRequirement
13
+
14
+ from pip._internal.exceptions import (
15
+ InstallationError,
16
+ InvalidPyProjectBuildRequires,
17
+ MissingPyProjectBuildRequires,
18
+ )
19
+ from pip._internal.utils.packaging import get_requirement
20
+
21
+
22
+ def _is_list_of_str(obj: Any) -> bool:
23
+ return isinstance(obj, list) and all(isinstance(item, str) for item in obj)
24
+
25
+
26
+ def make_pyproject_path(unpacked_source_directory: str) -> str:
27
+ return os.path.join(unpacked_source_directory, "pyproject.toml")
28
+
29
+
30
+ BuildSystemDetails = namedtuple(
31
+ "BuildSystemDetails", ["requires", "backend", "check", "backend_path"]
32
+ )
33
+
34
+
35
+ def load_pyproject_toml(
36
+ use_pep517: Optional[bool], pyproject_toml: str, setup_py: str, req_name: str
37
+ ) -> Optional[BuildSystemDetails]:
38
+ """Load the pyproject.toml file.
39
+
40
+ Parameters:
41
+ use_pep517 - Has the user requested PEP 517 processing? None
42
+ means the user hasn't explicitly specified.
43
+ pyproject_toml - Location of the project's pyproject.toml file
44
+ setup_py - Location of the project's setup.py file
45
+ req_name - The name of the requirement we're processing (for
46
+ error reporting)
47
+
48
+ Returns:
49
+ None if we should use the legacy code path, otherwise a tuple
50
+ (
51
+ requirements from pyproject.toml,
52
+ name of PEP 517 backend,
53
+ requirements we should check are installed after setting
54
+ up the build environment
55
+ directory paths to import the backend from (backend-path),
56
+ relative to the project root.
57
+ )
58
+ """
59
+ has_pyproject = os.path.isfile(pyproject_toml)
60
+ has_setup = os.path.isfile(setup_py)
61
+
62
+ if not has_pyproject and not has_setup:
63
+ raise InstallationError(
64
+ f"{req_name} does not appear to be a Python project: "
65
+ f"neither 'setup.py' nor 'pyproject.toml' found."
66
+ )
67
+
68
+ if has_pyproject:
69
+ with open(pyproject_toml, encoding="utf-8") as f:
70
+ pp_toml = tomllib.loads(f.read())
71
+ build_system = pp_toml.get("build-system")
72
+ else:
73
+ build_system = None
74
+
75
+ # The following cases must use PEP 517
76
+ # We check for use_pep517 being non-None and falsy because that means
77
+ # the user explicitly requested --no-use-pep517. The value 0 as
78
+ # opposed to False can occur when the value is provided via an
79
+ # environment variable or config file option (due to the quirk of
80
+ # strtobool() returning an integer in pip's configuration code).
81
+ if has_pyproject and not has_setup:
82
+ if use_pep517 is not None and not use_pep517:
83
+ raise InstallationError(
84
+ "Disabling PEP 517 processing is invalid: "
85
+ "project does not have a setup.py"
86
+ )
87
+ use_pep517 = True
88
+ elif build_system and "build-backend" in build_system:
89
+ if use_pep517 is not None and not use_pep517:
90
+ raise InstallationError(
91
+ "Disabling PEP 517 processing is invalid: "
92
+ "project specifies a build backend of {} "
93
+ "in pyproject.toml".format(build_system["build-backend"])
94
+ )
95
+ use_pep517 = True
96
+
97
+ # If we haven't worked out whether to use PEP 517 yet,
98
+ # and the user hasn't explicitly stated a preference,
99
+ # we do so if the project has a pyproject.toml file
100
+ # or if we cannot import setuptools or wheels.
101
+
102
+ # We fallback to PEP 517 when without setuptools or without the wheel package,
103
+ # so setuptools can be installed as a default build backend.
104
+ # For more info see:
105
+ # https://discuss.python.org/t/pip-without-setuptools-could-the-experience-be-improved/11810/9
106
+ # https://github.com/pypa/pip/issues/8559
107
+ elif use_pep517 is None:
108
+ use_pep517 = (
109
+ has_pyproject
110
+ or not importlib.util.find_spec("setuptools")
111
+ or not importlib.util.find_spec("wheel")
112
+ )
113
+
114
+ # At this point, we know whether we're going to use PEP 517.
115
+ assert use_pep517 is not None
116
+
117
+ # If we're using the legacy code path, there is nothing further
118
+ # for us to do here.
119
+ if not use_pep517:
120
+ return None
121
+
122
+ if build_system is None:
123
+ # Either the user has a pyproject.toml with no build-system
124
+ # section, or the user has no pyproject.toml, but has opted in
125
+ # explicitly via --use-pep517.
126
+ # In the absence of any explicit backend specification, we
127
+ # assume the setuptools backend that most closely emulates the
128
+ # traditional direct setup.py execution, and require wheel and
129
+ # a version of setuptools that supports that backend.
130
+
131
+ build_system = {
132
+ "requires": ["setuptools>=40.8.0"],
133
+ "build-backend": "setuptools.build_meta:__legacy__",
134
+ }
135
+
136
+ # If we're using PEP 517, we have build system information (either
137
+ # from pyproject.toml, or defaulted by the code above).
138
+ # Note that at this point, we do not know if the user has actually
139
+ # specified a backend, though.
140
+ assert build_system is not None
141
+
142
+ # Ensure that the build-system section in pyproject.toml conforms
143
+ # to PEP 518.
144
+
145
+ # Specifying the build-system table but not the requires key is invalid
146
+ if "requires" not in build_system:
147
+ raise MissingPyProjectBuildRequires(package=req_name)
148
+
149
+ # Error out if requires is not a list of strings
150
+ requires = build_system["requires"]
151
+ if not _is_list_of_str(requires):
152
+ raise InvalidPyProjectBuildRequires(
153
+ package=req_name,
154
+ reason="It is not a list of strings.",
155
+ )
156
+
157
+ # Each requirement must be valid as per PEP 508
158
+ for requirement in requires:
159
+ try:
160
+ get_requirement(requirement)
161
+ except InvalidRequirement as error:
162
+ raise InvalidPyProjectBuildRequires(
163
+ package=req_name,
164
+ reason=f"It contains an invalid requirement: {requirement!r}",
165
+ ) from error
166
+
167
+ backend = build_system.get("build-backend")
168
+ backend_path = build_system.get("backend-path", [])
169
+ check: List[str] = []
170
+ if backend is None:
171
+ # If the user didn't specify a backend, we assume they want to use
172
+ # the setuptools backend. But we can't be sure they have included
173
+ # a version of setuptools which supplies the backend. So we
174
+ # make a note to check that this requirement is present once
175
+ # we have set up the environment.
176
+ # This is quite a lot of work to check for a very specific case. But
177
+ # the problem is, that case is potentially quite common - projects that
178
+ # adopted PEP 518 early for the ability to specify requirements to
179
+ # execute setup.py, but never considered needing to mention the build
180
+ # tools themselves. The original PEP 518 code had a similar check (but
181
+ # implemented in a different way).
182
+ backend = "setuptools.build_meta:__legacy__"
183
+ check = ["setuptools>=40.8.0"]
184
+
185
+ return BuildSystemDetails(requires, backend, check, backend_path)
URSA/.venv_ursa/lib/python3.12/site-packages/pip/_internal/resolution/__init__.py ADDED
File without changes