pip 24.3__py3-none-any.whl → 25.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pip/__init__.py +1 -1
- pip/_internal/build_env.py +6 -2
- pip/_internal/cli/base_command.py +9 -0
- pip/_internal/cli/cmdoptions.py +2 -2
- pip/_internal/cli/index_command.py +1 -0
- pip/_internal/cli/progress_bars.py +1 -1
- pip/_internal/commands/cache.py +4 -1
- pip/_internal/commands/install.py +8 -7
- pip/_internal/commands/show.py +8 -1
- pip/_internal/configuration.py +1 -1
- pip/_internal/index/package_finder.py +41 -32
- pip/_internal/metadata/__init__.py +2 -2
- pip/_internal/metadata/_json.py +2 -0
- pip/_internal/metadata/importlib/_dists.py +7 -1
- pip/_internal/models/link.py +23 -9
- pip/_internal/network/cache.py +12 -0
- pip/_internal/network/session.py +1 -0
- pip/_internal/operations/build/metadata_editable.py +1 -0
- pip/_internal/operations/freeze.py +11 -13
- pip/_internal/pyproject.py +1 -1
- pip/_internal/req/req_file.py +106 -52
- pip/_internal/req/req_install.py +2 -2
- pip/_internal/resolution/resolvelib/factory.py +1 -1
- pip/_internal/self_outdated_check.py +9 -1
- pip/_internal/utils/logging.py +8 -1
- pip/_internal/utils/misc.py +15 -14
- pip/_internal/utils/packaging.py +1 -0
- pip/_internal/utils/unpacking.py +1 -1
- pip/_vendor/cachecontrol/__init__.py +2 -1
- pip/_vendor/cachecontrol/adapter.py +2 -2
- pip/_vendor/cachecontrol/cache.py +1 -0
- pip/_vendor/cachecontrol/caches/file_cache.py +1 -1
- pip/_vendor/cachecontrol/controller.py +1 -0
- pip/_vendor/cachecontrol/filewrapper.py +2 -2
- pip/_vendor/cachecontrol/heuristics.py +4 -1
- pip/_vendor/idna/__init__.py +2 -1
- pip/_vendor/idna/codec.py +31 -27
- pip/_vendor/idna/compat.py +6 -4
- pip/_vendor/idna/core.py +161 -119
- pip/_vendor/idna/idnadata.py +3537 -3539
- pip/_vendor/idna/intranges.py +7 -4
- pip/_vendor/idna/package_data.py +1 -2
- pip/_vendor/idna/uts46data.py +8261 -8178
- pip/_vendor/msgpack/__init__.py +8 -8
- pip/_vendor/msgpack/ext.py +5 -3
- pip/_vendor/msgpack/fallback.py +29 -51
- pip/_vendor/packaging/__init__.py +2 -2
- pip/_vendor/packaging/_elffile.py +4 -4
- pip/_vendor/packaging/_manylinux.py +1 -0
- pip/_vendor/packaging/licenses/__init__.py +145 -0
- pip/_vendor/packaging/licenses/_spdx.py +759 -0
- pip/_vendor/packaging/markers.py +15 -9
- pip/_vendor/packaging/metadata.py +83 -24
- pip/_vendor/packaging/specifiers.py +19 -8
- pip/_vendor/packaging/tags.py +15 -25
- pip/_vendor/packaging/utils.py +33 -44
- pip/_vendor/packaging/version.py +26 -7
- pip/_vendor/platformdirs/__init__.py +14 -10
- pip/_vendor/platformdirs/android.py +1 -1
- pip/_vendor/platformdirs/api.py +6 -0
- pip/_vendor/platformdirs/macos.py +14 -0
- pip/_vendor/platformdirs/unix.py +0 -6
- pip/_vendor/platformdirs/version.py +2 -2
- pip/_vendor/pyproject_hooks/__init__.py +17 -9
- pip/_vendor/pyproject_hooks/_impl.py +181 -101
- pip/_vendor/pyproject_hooks/_in_process/__init__.py +5 -2
- pip/_vendor/pyproject_hooks/_in_process/_in_process.py +113 -77
- pip/_vendor/pyproject_hooks/py.typed +0 -0
- pip/_vendor/requests/certs.py +1 -8
- pip/_vendor/rich/_inspect.py +0 -2
- pip/_vendor/rich/_null_file.py +1 -1
- pip/_vendor/rich/_win32_console.py +3 -4
- pip/_vendor/rich/align.py +1 -0
- pip/_vendor/rich/ansi.py +1 -0
- pip/_vendor/rich/cells.py +30 -23
- pip/_vendor/rich/color.py +2 -2
- pip/_vendor/rich/console.py +55 -27
- pip/_vendor/rich/default_styles.py +2 -1
- pip/_vendor/rich/filesize.py +1 -2
- pip/_vendor/rich/highlighter.py +1 -1
- pip/_vendor/rich/live.py +1 -1
- pip/_vendor/rich/logging.py +8 -0
- pip/_vendor/rich/padding.py +5 -5
- pip/_vendor/rich/panel.py +13 -7
- pip/_vendor/rich/pretty.py +46 -25
- pip/_vendor/rich/progress.py +25 -9
- pip/_vendor/rich/progress_bar.py +1 -1
- pip/_vendor/rich/prompt.py +29 -4
- pip/_vendor/rich/segment.py +33 -19
- pip/_vendor/rich/spinner.py +1 -0
- pip/_vendor/rich/style.py +1 -1
- pip/_vendor/rich/syntax.py +16 -8
- pip/_vendor/rich/table.py +15 -8
- pip/_vendor/rich/text.py +10 -6
- pip/_vendor/rich/theme.py +2 -2
- pip/_vendor/rich/traceback.py +70 -26
- pip/_vendor/rich/tree.py +16 -8
- pip/_vendor/tomli/__init__.py +1 -4
- pip/_vendor/tomli/_parser.py +158 -79
- pip/_vendor/tomli/_re.py +10 -5
- pip/_vendor/vendor.txt +8 -8
- {pip-24.3.dist-info → pip-25.0.dist-info}/AUTHORS.txt +7 -0
- {pip-24.3.dist-info → pip-25.0.dist-info}/METADATA +2 -2
- {pip-24.3.dist-info → pip-25.0.dist-info}/RECORD +108 -107
- {pip-24.3.dist-info → pip-25.0.dist-info}/WHEEL +1 -1
- pip/_internal/utils/encoding.py +0 -36
- pip/_vendor/pyproject_hooks/_compat.py +0 -8
- {pip-24.3.dist-info → pip-25.0.dist-info}/LICENSE.txt +0 -0
- {pip-24.3.dist-info → pip-25.0.dist-info}/entry_points.txt +0 -0
- {pip-24.3.dist-info → pip-25.0.dist-info}/top_level.txt +0 -0
pip/__init__.py
CHANGED
pip/_internal/build_env.py
CHANGED
|
@@ -246,6 +246,8 @@ class BuildEnvironment:
|
|
|
246
246
|
# target from config file or env var should be ignored
|
|
247
247
|
"--target",
|
|
248
248
|
"",
|
|
249
|
+
"--cert",
|
|
250
|
+
finder.custom_cert or where(),
|
|
249
251
|
]
|
|
250
252
|
if logger.getEffectiveLevel() <= logging.DEBUG:
|
|
251
253
|
args.append("-vv")
|
|
@@ -270,21 +272,23 @@ class BuildEnvironment:
|
|
|
270
272
|
for link in finder.find_links:
|
|
271
273
|
args.extend(["--find-links", link])
|
|
272
274
|
|
|
275
|
+
if finder.proxy:
|
|
276
|
+
args.extend(["--proxy", finder.proxy])
|
|
273
277
|
for host in finder.trusted_hosts:
|
|
274
278
|
args.extend(["--trusted-host", host])
|
|
279
|
+
if finder.client_cert:
|
|
280
|
+
args.extend(["--client-cert", finder.client_cert])
|
|
275
281
|
if finder.allow_all_prereleases:
|
|
276
282
|
args.append("--pre")
|
|
277
283
|
if finder.prefer_binary:
|
|
278
284
|
args.append("--prefer-binary")
|
|
279
285
|
args.append("--")
|
|
280
286
|
args.extend(requirements)
|
|
281
|
-
extra_environ = {"_PIP_STANDALONE_CERT": where()}
|
|
282
287
|
with open_spinner(f"Installing {kind}") as spinner:
|
|
283
288
|
call_subprocess(
|
|
284
289
|
args,
|
|
285
290
|
command_desc=f"pip subprocess to install {kind}",
|
|
286
291
|
spinner=spinner,
|
|
287
|
-
extra_environ=extra_environ,
|
|
288
292
|
)
|
|
289
293
|
|
|
290
294
|
|
|
@@ -29,6 +29,7 @@ from pip._internal.exceptions import (
|
|
|
29
29
|
NetworkConnectionError,
|
|
30
30
|
PreviousBuildDirError,
|
|
31
31
|
)
|
|
32
|
+
from pip._internal.utils.deprecation import deprecated
|
|
32
33
|
from pip._internal.utils.filesystem import check_path_owner
|
|
33
34
|
from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
|
|
34
35
|
from pip._internal.utils.misc import get_prog, normalize_path
|
|
@@ -228,4 +229,12 @@ class Command(CommandContextMixIn):
|
|
|
228
229
|
)
|
|
229
230
|
options.cache_dir = None
|
|
230
231
|
|
|
232
|
+
if options.no_python_version_warning:
|
|
233
|
+
deprecated(
|
|
234
|
+
reason="--no-python-version-warning is deprecated.",
|
|
235
|
+
replacement="to remove the flag as it's a no-op",
|
|
236
|
+
gone_in="25.1",
|
|
237
|
+
issue=13154,
|
|
238
|
+
)
|
|
239
|
+
|
|
231
240
|
return self._run_wrapper(level_number, options, args)
|
pip/_internal/cli/cmdoptions.py
CHANGED
|
@@ -260,8 +260,8 @@ keyring_provider: Callable[..., Option] = partial(
|
|
|
260
260
|
default="auto",
|
|
261
261
|
help=(
|
|
262
262
|
"Enable the credential lookup via the keyring library if user input is allowed."
|
|
263
|
-
" Specify which mechanism to use [disabled, import, subprocess]."
|
|
264
|
-
" (default:
|
|
263
|
+
" Specify which mechanism to use [auto, disabled, import, subprocess]."
|
|
264
|
+
" (default: %default)"
|
|
265
265
|
),
|
|
266
266
|
)
|
|
267
267
|
|
|
@@ -123,6 +123,7 @@ class SessionCommandMixin(CommandContextMixIn):
|
|
|
123
123
|
"https": options.proxy,
|
|
124
124
|
}
|
|
125
125
|
session.trust_env = False
|
|
126
|
+
session.pip_proxy = options.proxy
|
|
126
127
|
|
|
127
128
|
# Determine if we can prompt the user for authentication or not
|
|
128
129
|
session.auth.prompting = not options.no_input
|
|
@@ -63,7 +63,7 @@ def _raw_progress_bar(
|
|
|
63
63
|
size: Optional[int],
|
|
64
64
|
) -> Generator[bytes, None, None]:
|
|
65
65
|
def write_progress(current: int, total: int) -> None:
|
|
66
|
-
sys.stdout.write("Progress
|
|
66
|
+
sys.stdout.write(f"Progress {current} of {total}\n")
|
|
67
67
|
sys.stdout.flush()
|
|
68
68
|
|
|
69
69
|
current = 0
|
pip/_internal/commands/cache.py
CHANGED
|
@@ -8,6 +8,7 @@ from pip._internal.cli.status_codes import ERROR, SUCCESS
|
|
|
8
8
|
from pip._internal.exceptions import CommandError, PipError
|
|
9
9
|
from pip._internal.utils import filesystem
|
|
10
10
|
from pip._internal.utils.logging import getLogger
|
|
11
|
+
from pip._internal.utils.misc import format_size
|
|
11
12
|
|
|
12
13
|
logger = getLogger(__name__)
|
|
13
14
|
|
|
@@ -180,10 +181,12 @@ class CacheCommand(Command):
|
|
|
180
181
|
if not files:
|
|
181
182
|
logger.warning(no_matching_msg)
|
|
182
183
|
|
|
184
|
+
bytes_removed = 0
|
|
183
185
|
for filename in files:
|
|
186
|
+
bytes_removed += os.stat(filename).st_size
|
|
184
187
|
os.unlink(filename)
|
|
185
188
|
logger.verbose("Removed %s", filename)
|
|
186
|
-
logger.info("Files removed: %s", len(files))
|
|
189
|
+
logger.info("Files removed: %s (%s)", len(files), format_size(bytes_removed))
|
|
187
190
|
|
|
188
191
|
def purge_cache(self, options: Values, args: List[Any]) -> None:
|
|
189
192
|
if args:
|
|
@@ -10,6 +10,13 @@ from typing import List, Optional
|
|
|
10
10
|
from pip._vendor.packaging.utils import canonicalize_name
|
|
11
11
|
from pip._vendor.rich import print_json
|
|
12
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
|
|
13
20
|
from pip._internal.cache import WheelCache
|
|
14
21
|
from pip._internal.cli import cmdoptions
|
|
15
22
|
from pip._internal.cli.cmdoptions import make_target_python
|
|
@@ -408,12 +415,6 @@ class InstallCommand(RequirementCommand):
|
|
|
408
415
|
# If we're not replacing an already installed pip,
|
|
409
416
|
# we're not modifying it.
|
|
410
417
|
modifying_pip = pip_req.satisfied_by is None
|
|
411
|
-
if modifying_pip:
|
|
412
|
-
# Eagerly import this module to avoid crashes. Otherwise, this
|
|
413
|
-
# module would be imported *after* pip was replaced, resulting in
|
|
414
|
-
# crashes if the new self_outdated_check module was incompatible
|
|
415
|
-
# with the rest of pip that's already imported.
|
|
416
|
-
import pip._internal.self_outdated_check # noqa: F401
|
|
417
418
|
protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
|
|
418
419
|
|
|
419
420
|
reqs_to_build = [
|
|
@@ -432,7 +433,7 @@ class InstallCommand(RequirementCommand):
|
|
|
432
433
|
|
|
433
434
|
if build_failures:
|
|
434
435
|
raise InstallationError(
|
|
435
|
-
"
|
|
436
|
+
"Failed to build installable wheels for some "
|
|
436
437
|
"pyproject.toml based projects ({})".format(
|
|
437
438
|
", ".join(r.name for r in build_failures) # type: ignore
|
|
438
439
|
)
|
pip/_internal/commands/show.py
CHANGED
|
@@ -66,6 +66,7 @@ class _PackageInfo(NamedTuple):
|
|
|
66
66
|
author: str
|
|
67
67
|
author_email: str
|
|
68
68
|
license: str
|
|
69
|
+
license_expression: str
|
|
69
70
|
entry_points: List[str]
|
|
70
71
|
files: Optional[List[str]]
|
|
71
72
|
|
|
@@ -161,6 +162,7 @@ def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None
|
|
|
161
162
|
author=metadata.get("Author", ""),
|
|
162
163
|
author_email=metadata.get("Author-email", ""),
|
|
163
164
|
license=metadata.get("License", ""),
|
|
165
|
+
license_expression=metadata.get("License-Expression", ""),
|
|
164
166
|
entry_points=entry_points,
|
|
165
167
|
files=files,
|
|
166
168
|
)
|
|
@@ -180,13 +182,18 @@ def print_results(
|
|
|
180
182
|
if i > 0:
|
|
181
183
|
write_output("---")
|
|
182
184
|
|
|
185
|
+
metadata_version_tuple = tuple(map(int, dist.metadata_version.split(".")))
|
|
186
|
+
|
|
183
187
|
write_output("Name: %s", dist.name)
|
|
184
188
|
write_output("Version: %s", dist.version)
|
|
185
189
|
write_output("Summary: %s", dist.summary)
|
|
186
190
|
write_output("Home-page: %s", dist.homepage)
|
|
187
191
|
write_output("Author: %s", dist.author)
|
|
188
192
|
write_output("Author-email: %s", dist.author_email)
|
|
189
|
-
|
|
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)
|
|
190
197
|
write_output("Location: %s", dist.location)
|
|
191
198
|
if dist.editable_project_location is not None:
|
|
192
199
|
write_output(
|
pip/_internal/configuration.py
CHANGED
|
@@ -330,7 +330,7 @@ class Configuration:
|
|
|
330
330
|
This should be treated like items of a dictionary. The order
|
|
331
331
|
here doesn't affect what gets overridden. That is controlled
|
|
332
332
|
by OVERRIDE_ORDER. However this does control the order they are
|
|
333
|
-
displayed to the user. It's probably most
|
|
333
|
+
displayed to the user. It's probably most ergonomic to display
|
|
334
334
|
things in the same order as OVERRIDE_ORDER
|
|
335
335
|
"""
|
|
336
336
|
# SMELL: Move the conditions out of this function
|
|
@@ -334,44 +334,30 @@ class CandidatePreferences:
|
|
|
334
334
|
allow_all_prereleases: bool = False
|
|
335
335
|
|
|
336
336
|
|
|
337
|
+
@dataclass(frozen=True)
|
|
337
338
|
class BestCandidateResult:
|
|
338
339
|
"""A collection of candidates, returned by `PackageFinder.find_best_candidate`.
|
|
339
340
|
|
|
340
341
|
This class is only intended to be instantiated by CandidateEvaluator's
|
|
341
342
|
`compute_best_candidate()` method.
|
|
342
|
-
"""
|
|
343
|
-
|
|
344
|
-
def __init__(
|
|
345
|
-
self,
|
|
346
|
-
candidates: List[InstallationCandidate],
|
|
347
|
-
applicable_candidates: List[InstallationCandidate],
|
|
348
|
-
best_candidate: Optional[InstallationCandidate],
|
|
349
|
-
) -> None:
|
|
350
|
-
"""
|
|
351
|
-
:param candidates: A sequence of all available candidates found.
|
|
352
|
-
:param applicable_candidates: The applicable candidates.
|
|
353
|
-
:param best_candidate: The most preferred candidate found, or None
|
|
354
|
-
if no applicable candidates were found.
|
|
355
|
-
"""
|
|
356
|
-
assert set(applicable_candidates) <= set(candidates)
|
|
357
|
-
|
|
358
|
-
if best_candidate is None:
|
|
359
|
-
assert not applicable_candidates
|
|
360
|
-
else:
|
|
361
|
-
assert best_candidate in applicable_candidates
|
|
362
343
|
|
|
363
|
-
|
|
364
|
-
|
|
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
|
+
"""
|
|
365
349
|
|
|
366
|
-
|
|
350
|
+
all_candidates: List[InstallationCandidate]
|
|
351
|
+
applicable_candidates: List[InstallationCandidate]
|
|
352
|
+
best_candidate: Optional[InstallationCandidate]
|
|
367
353
|
|
|
368
|
-
def
|
|
369
|
-
|
|
370
|
-
return iter(self._candidates)
|
|
354
|
+
def __post_init__(self) -> None:
|
|
355
|
+
assert set(self.applicable_candidates) <= set(self.all_candidates)
|
|
371
356
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
357
|
+
if self.best_candidate is None:
|
|
358
|
+
assert not self.applicable_candidates
|
|
359
|
+
else:
|
|
360
|
+
assert self.best_candidate in self.applicable_candidates
|
|
375
361
|
|
|
376
362
|
|
|
377
363
|
class CandidateEvaluator:
|
|
@@ -675,11 +661,29 @@ class PackageFinder:
|
|
|
675
661
|
def index_urls(self) -> List[str]:
|
|
676
662
|
return self.search_scope.index_urls
|
|
677
663
|
|
|
664
|
+
@property
|
|
665
|
+
def proxy(self) -> Optional[str]:
|
|
666
|
+
return self._link_collector.session.pip_proxy
|
|
667
|
+
|
|
678
668
|
@property
|
|
679
669
|
def trusted_hosts(self) -> Iterable[str]:
|
|
680
670
|
for host_port in self._link_collector.session.pip_trusted_origins:
|
|
681
671
|
yield build_netloc(*host_port)
|
|
682
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
|
+
|
|
683
687
|
@property
|
|
684
688
|
def allow_all_prereleases(self) -> bool:
|
|
685
689
|
return self._candidate_prefs.allow_all_prereleases
|
|
@@ -732,6 +736,11 @@ class PackageFinder:
|
|
|
732
736
|
return no_eggs + eggs
|
|
733
737
|
|
|
734
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
|
+
|
|
735
744
|
entry = (link, result, detail)
|
|
736
745
|
if entry not in self._logged_links:
|
|
737
746
|
# Put the link at the end so the reason is more visible and because
|
|
@@ -929,7 +938,7 @@ class PackageFinder:
|
|
|
929
938
|
"Could not find a version that satisfies the requirement %s "
|
|
930
939
|
"(from versions: %s)",
|
|
931
940
|
req,
|
|
932
|
-
_format_versions(best_candidate_result.
|
|
941
|
+
_format_versions(best_candidate_result.all_candidates),
|
|
933
942
|
)
|
|
934
943
|
|
|
935
944
|
raise DistributionNotFound(f"No matching distribution found for {req}")
|
|
@@ -963,7 +972,7 @@ class PackageFinder:
|
|
|
963
972
|
logger.debug(
|
|
964
973
|
"Using version %s (newest of versions: %s)",
|
|
965
974
|
best_candidate.version,
|
|
966
|
-
_format_versions(best_candidate_result.
|
|
975
|
+
_format_versions(best_candidate_result.applicable_candidates),
|
|
967
976
|
)
|
|
968
977
|
return best_candidate
|
|
969
978
|
|
|
@@ -971,7 +980,7 @@ class PackageFinder:
|
|
|
971
980
|
logger.debug(
|
|
972
981
|
"Installed version (%s) is most up-to-date (past versions: %s)",
|
|
973
982
|
installed_version,
|
|
974
|
-
_format_versions(best_candidate_result.
|
|
983
|
+
_format_versions(best_candidate_result.applicable_candidates),
|
|
975
984
|
)
|
|
976
985
|
raise BestVersionAlreadyInstalled
|
|
977
986
|
|
|
@@ -30,7 +30,7 @@ def _should_use_importlib_metadata() -> bool:
|
|
|
30
30
|
"""Whether to use the ``importlib.metadata`` or ``pkg_resources`` backend.
|
|
31
31
|
|
|
32
32
|
By default, pip uses ``importlib.metadata`` on Python 3.11+, and
|
|
33
|
-
``
|
|
33
|
+
``pkg_resources`` otherwise. This can be overridden by a couple of ways:
|
|
34
34
|
|
|
35
35
|
* If environment variable ``_PIP_USE_IMPORTLIB_METADATA`` is set, it
|
|
36
36
|
dictates whether ``importlib.metadata`` is used, regardless of Python
|
|
@@ -71,7 +71,7 @@ def get_default_environment() -> BaseEnvironment:
|
|
|
71
71
|
|
|
72
72
|
This returns an Environment instance from the chosen backend. The default
|
|
73
73
|
Environment instance should be built from ``sys.path`` and may use caching
|
|
74
|
-
to share instance state
|
|
74
|
+
to share instance state across calls.
|
|
75
75
|
"""
|
|
76
76
|
return select_backend().Environment.default()
|
|
77
77
|
|
pip/_internal/metadata/_json.py
CHANGED
|
@@ -2,6 +2,7 @@ import email.message
|
|
|
2
2
|
import importlib.metadata
|
|
3
3
|
import pathlib
|
|
4
4
|
import zipfile
|
|
5
|
+
from os import PathLike
|
|
5
6
|
from typing import (
|
|
6
7
|
Collection,
|
|
7
8
|
Dict,
|
|
@@ -95,6 +96,11 @@ class WheelDistribution(importlib.metadata.Distribution):
|
|
|
95
96
|
raise UnsupportedWheel(error)
|
|
96
97
|
return text
|
|
97
98
|
|
|
99
|
+
def locate_file(self, path: str | PathLike[str]) -> pathlib.Path:
|
|
100
|
+
# This method doesn't make sense for our in-memory wheel, but the API
|
|
101
|
+
# requires us to define it.
|
|
102
|
+
raise NotImplementedError
|
|
103
|
+
|
|
98
104
|
|
|
99
105
|
class Distribution(BaseDistribution):
|
|
100
106
|
def __init__(
|
|
@@ -190,7 +196,7 @@ class Distribution(BaseDistribution):
|
|
|
190
196
|
return content
|
|
191
197
|
|
|
192
198
|
def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
|
|
193
|
-
# importlib.metadata's EntryPoint structure
|
|
199
|
+
# importlib.metadata's EntryPoint structure satisfies BaseEntryPoint.
|
|
194
200
|
return self._dist.entry_points
|
|
195
201
|
|
|
196
202
|
def _metadata_impl(self) -> email.message.Message:
|
pip/_internal/models/link.py
CHANGED
|
@@ -170,12 +170,23 @@ def _ensure_quoted_url(url: str) -> str:
|
|
|
170
170
|
and without double-quoting other characters.
|
|
171
171
|
"""
|
|
172
172
|
# Split the URL into parts according to the general structure
|
|
173
|
-
# `scheme://netloc/path
|
|
174
|
-
result = urllib.parse.
|
|
173
|
+
# `scheme://netloc/path?query#fragment`.
|
|
174
|
+
result = urllib.parse.urlsplit(url)
|
|
175
175
|
# If the netloc is empty, then the URL refers to a local filesystem path.
|
|
176
176
|
is_local_path = not result.netloc
|
|
177
177
|
path = _clean_url_path(result.path, is_local_path=is_local_path)
|
|
178
|
-
return urllib.parse.
|
|
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)
|
|
179
190
|
|
|
180
191
|
|
|
181
192
|
@functools.total_ordering
|
|
@@ -185,6 +196,7 @@ class Link:
|
|
|
185
196
|
__slots__ = [
|
|
186
197
|
"_parsed_url",
|
|
187
198
|
"_url",
|
|
199
|
+
"_path",
|
|
188
200
|
"_hashes",
|
|
189
201
|
"comes_from",
|
|
190
202
|
"requires_python",
|
|
@@ -241,6 +253,8 @@ class Link:
|
|
|
241
253
|
# Store the url as a private attribute to prevent accidentally
|
|
242
254
|
# trying to set a new value.
|
|
243
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)
|
|
244
258
|
|
|
245
259
|
link_hash = LinkHash.find_hash_url_fragment(url)
|
|
246
260
|
hashes_from_link = {} if link_hash is None else link_hash.as_dict()
|
|
@@ -270,7 +284,7 @@ class Link:
|
|
|
270
284
|
if file_url is None:
|
|
271
285
|
return None
|
|
272
286
|
|
|
273
|
-
url = _ensure_quoted_url(
|
|
287
|
+
url = _ensure_quoted_url(_absolute_link_url(page_url, file_url))
|
|
274
288
|
pyrequire = file_data.get("requires-python")
|
|
275
289
|
yanked_reason = file_data.get("yanked")
|
|
276
290
|
hashes = file_data.get("hashes", {})
|
|
@@ -322,7 +336,7 @@ class Link:
|
|
|
322
336
|
if not href:
|
|
323
337
|
return None
|
|
324
338
|
|
|
325
|
-
url = _ensure_quoted_url(
|
|
339
|
+
url = _ensure_quoted_url(_absolute_link_url(base_url, href))
|
|
326
340
|
pyrequire = anchor_attribs.get("data-requires-python")
|
|
327
341
|
yanked_reason = anchor_attribs.get("data-yanked")
|
|
328
342
|
|
|
@@ -421,7 +435,7 @@ class Link:
|
|
|
421
435
|
|
|
422
436
|
@property
|
|
423
437
|
def path(self) -> str:
|
|
424
|
-
return
|
|
438
|
+
return self._path
|
|
425
439
|
|
|
426
440
|
def splitext(self) -> Tuple[str, str]:
|
|
427
441
|
return splitext(posixpath.basename(self.path.rstrip("/")))
|
|
@@ -452,10 +466,10 @@ class Link:
|
|
|
452
466
|
project_name = match.group(1)
|
|
453
467
|
if not self._project_name_re.match(project_name):
|
|
454
468
|
deprecated(
|
|
455
|
-
reason=f"{self} contains an egg fragment with a non-PEP 508 name",
|
|
469
|
+
reason=f"{self} contains an egg fragment with a non-PEP 508 name.",
|
|
456
470
|
replacement="to use the req @ url syntax, and remove the egg fragment",
|
|
457
|
-
gone_in="25.
|
|
458
|
-
issue=
|
|
471
|
+
gone_in="25.1",
|
|
472
|
+
issue=13157,
|
|
459
473
|
)
|
|
460
474
|
|
|
461
475
|
return project_name
|
pip/_internal/network/cache.py
CHANGED
|
@@ -76,6 +76,18 @@ class SafeFileCache(SeparateBodyBaseCache):
|
|
|
76
76
|
|
|
77
77
|
with adjacent_tmp_file(path) as f:
|
|
78
78
|
f.write(data)
|
|
79
|
+
# Inherit the read/write permissions of the cache directory
|
|
80
|
+
# to enable multi-user cache use-cases.
|
|
81
|
+
mode = (
|
|
82
|
+
os.stat(self.directory).st_mode
|
|
83
|
+
& 0o666 # select read/write permissions of cache directory
|
|
84
|
+
| 0o600 # set owner read/write permissions
|
|
85
|
+
)
|
|
86
|
+
# Change permissions only if there is no risk of following a symlink.
|
|
87
|
+
if os.chmod in os.supports_fd:
|
|
88
|
+
os.chmod(f.fileno(), mode)
|
|
89
|
+
elif os.chmod in os.supports_follow_symlinks:
|
|
90
|
+
os.chmod(f.name, mode, follow_symlinks=False)
|
|
79
91
|
|
|
80
92
|
replace(f.name, path)
|
|
81
93
|
|
pip/_internal/network/session.py
CHANGED
|
@@ -339,6 +339,7 @@ class PipSession(requests.Session):
|
|
|
339
339
|
# Namespace the attribute with "pip_" just in case to prevent
|
|
340
340
|
# possible conflicts with the base class.
|
|
341
341
|
self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = []
|
|
342
|
+
self.pip_proxy = None
|
|
342
343
|
|
|
343
344
|
# Attach our User Agent to the request
|
|
344
345
|
self.headers["User-Agent"] = user_agent()
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import collections
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
4
5
|
from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set
|
|
5
6
|
|
|
6
|
-
from pip._vendor.packaging.utils import canonicalize_name
|
|
7
|
+
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
|
7
8
|
from pip._vendor.packaging.version import InvalidVersion
|
|
8
9
|
|
|
9
10
|
from pip._internal.exceptions import BadCommand, InstallationError
|
|
@@ -220,19 +221,16 @@ def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
|
|
|
220
221
|
)
|
|
221
222
|
|
|
222
223
|
|
|
224
|
+
@dataclass(frozen=True)
|
|
223
225
|
class FrozenRequirement:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
) ->
|
|
231
|
-
self.name
|
|
232
|
-
self.canonical_name = canonicalize_name(name)
|
|
233
|
-
self.req = req
|
|
234
|
-
self.editable = editable
|
|
235
|
-
self.comments = comments
|
|
226
|
+
name: str
|
|
227
|
+
req: str
|
|
228
|
+
editable: bool
|
|
229
|
+
comments: Iterable[str] = field(default_factory=tuple)
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def canonical_name(self) -> NormalizedName:
|
|
233
|
+
return canonicalize_name(self.name)
|
|
236
234
|
|
|
237
235
|
@classmethod
|
|
238
236
|
def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":
|
pip/_internal/pyproject.py
CHANGED
|
@@ -73,7 +73,7 @@ def load_pyproject_toml(
|
|
|
73
73
|
build_system = None
|
|
74
74
|
|
|
75
75
|
# The following cases must use PEP 517
|
|
76
|
-
# We check for use_pep517 being non-None and
|
|
76
|
+
# We check for use_pep517 being non-None and falsy because that means
|
|
77
77
|
# the user explicitly requested --no-use-pep517. The value 0 as
|
|
78
78
|
# opposed to False can occur when the value is provided via an
|
|
79
79
|
# environment variable or config file option (due to the quirk of
|