pip 25.2__py3-none-any.whl → 26.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/__init__.py +0 -0
- pip/_internal/build_env.py +265 -8
- pip/_internal/cache.py +1 -1
- pip/_internal/cli/base_command.py +11 -0
- pip/_internal/cli/cmdoptions.py +200 -71
- pip/_internal/cli/index_command.py +20 -0
- pip/_internal/cli/main.py +11 -6
- pip/_internal/cli/main_parser.py +3 -1
- pip/_internal/cli/parser.py +96 -36
- pip/_internal/cli/progress_bars.py +4 -2
- pip/_internal/cli/req_command.py +126 -30
- pip/_internal/commands/cache.py +24 -0
- pip/_internal/commands/completion.py +2 -1
- pip/_internal/commands/download.py +12 -11
- pip/_internal/commands/index.py +13 -6
- pip/_internal/commands/install.py +55 -43
- pip/_internal/commands/list.py +14 -16
- pip/_internal/commands/lock.py +19 -14
- pip/_internal/commands/wheel.py +13 -23
- pip/_internal/configuration.py +1 -2
- pip/_internal/distributions/sdist.py +13 -14
- pip/_internal/exceptions.py +96 -6
- pip/_internal/index/collector.py +2 -3
- pip/_internal/index/package_finder.py +87 -21
- pip/_internal/locations/__init__.py +1 -2
- pip/_internal/locations/_sysconfig.py +4 -1
- pip/_internal/metadata/__init__.py +7 -2
- pip/_internal/metadata/importlib/_dists.py +8 -2
- pip/_internal/models/link.py +18 -14
- pip/_internal/models/release_control.py +92 -0
- pip/_internal/models/selection_prefs.py +6 -3
- pip/_internal/models/wheel.py +5 -66
- pip/_internal/network/auth.py +6 -2
- pip/_internal/network/cache.py +6 -11
- pip/_internal/network/download.py +4 -5
- pip/_internal/network/lazy_wheel.py +5 -3
- pip/_internal/network/session.py +14 -10
- pip/_internal/operations/build/wheel.py +4 -4
- pip/_internal/operations/build/wheel_editable.py +4 -4
- pip/_internal/operations/install/wheel.py +1 -2
- pip/_internal/operations/prepare.py +9 -4
- pip/_internal/pyproject.py +2 -61
- pip/_internal/req/__init__.py +1 -3
- pip/_internal/req/constructors.py +45 -39
- pip/_internal/req/pep723.py +41 -0
- pip/_internal/req/req_file.py +10 -2
- pip/_internal/req/req_install.py +32 -141
- pip/_internal/resolution/resolvelib/candidates.py +20 -11
- pip/_internal/resolution/resolvelib/factory.py +43 -1
- pip/_internal/resolution/resolvelib/provider.py +9 -0
- pip/_internal/resolution/resolvelib/reporter.py +21 -8
- pip/_internal/resolution/resolvelib/requirements.py +7 -3
- pip/_internal/resolution/resolvelib/resolver.py +2 -6
- pip/_internal/self_outdated_check.py +17 -16
- pip/_internal/utils/datetime.py +18 -0
- pip/_internal/utils/filesystem.py +52 -1
- pip/_internal/utils/logging.py +34 -2
- pip/_internal/utils/misc.py +18 -12
- pip/_internal/utils/pylock.py +116 -0
- pip/_internal/utils/unpacking.py +26 -1
- pip/_internal/vcs/versioncontrol.py +3 -1
- pip/_internal/wheel_builder.py +23 -96
- pip/_vendor/README.rst +180 -0
- pip/_vendor/cachecontrol/LICENSE.txt +13 -0
- pip/_vendor/cachecontrol/__init__.py +6 -3
- pip/_vendor/cachecontrol/adapter.py +0 -1
- pip/_vendor/cachecontrol/controller.py +1 -1
- pip/_vendor/cachecontrol/filewrapper.py +3 -1
- pip/_vendor/certifi/LICENSE +20 -0
- pip/_vendor/certifi/__init__.py +1 -1
- pip/_vendor/certifi/cacert.pem +62 -372
- pip/_vendor/dependency_groups/LICENSE.txt +9 -0
- pip/_vendor/distlib/LICENSE.txt +284 -0
- pip/_vendor/distro/LICENSE +202 -0
- pip/_vendor/idna/LICENSE.md +31 -0
- pip/_vendor/idna/codec.py +1 -1
- pip/_vendor/idna/core.py +1 -1
- pip/_vendor/idna/idnadata.py +72 -6
- pip/_vendor/idna/package_data.py +1 -1
- pip/_vendor/idna/uts46data.py +891 -731
- pip/_vendor/msgpack/COPYING +14 -0
- pip/_vendor/msgpack/__init__.py +2 -2
- pip/_vendor/packaging/LICENSE +3 -0
- pip/_vendor/packaging/LICENSE.APACHE +177 -0
- pip/_vendor/packaging/LICENSE.BSD +23 -0
- pip/_vendor/packaging/__init__.py +1 -1
- pip/_vendor/packaging/_elffile.py +0 -1
- pip/_vendor/packaging/_manylinux.py +36 -36
- pip/_vendor/packaging/_musllinux.py +1 -1
- pip/_vendor/packaging/_parser.py +22 -10
- pip/_vendor/packaging/_structures.py +8 -0
- pip/_vendor/packaging/_tokenizer.py +23 -25
- pip/_vendor/packaging/licenses/__init__.py +13 -11
- pip/_vendor/packaging/licenses/_spdx.py +41 -1
- pip/_vendor/packaging/markers.py +64 -38
- pip/_vendor/packaging/metadata.py +143 -27
- pip/_vendor/packaging/pylock.py +635 -0
- pip/_vendor/packaging/requirements.py +5 -10
- pip/_vendor/packaging/specifiers.py +219 -170
- pip/_vendor/packaging/tags.py +15 -20
- pip/_vendor/packaging/utils.py +19 -24
- pip/_vendor/packaging/version.py +315 -105
- pip/_vendor/pkg_resources/LICENSE +17 -0
- pip/_vendor/platformdirs/LICENSE +21 -0
- pip/_vendor/platformdirs/api.py +1 -1
- pip/_vendor/platformdirs/macos.py +10 -8
- pip/_vendor/platformdirs/version.py +16 -3
- pip/_vendor/platformdirs/windows.py +7 -1
- pip/_vendor/pygments/LICENSE +25 -0
- pip/_vendor/pyproject_hooks/LICENSE +21 -0
- pip/_vendor/requests/LICENSE +175 -0
- pip/_vendor/requests/__version__.py +2 -2
- pip/_vendor/requests/adapters.py +17 -40
- pip/_vendor/requests/sessions.py +1 -1
- pip/_vendor/resolvelib/LICENSE +13 -0
- pip/_vendor/resolvelib/__init__.py +1 -1
- pip/_vendor/resolvelib/resolvers/abstract.py +3 -3
- pip/_vendor/resolvelib/resolvers/resolution.py +5 -0
- pip/_vendor/rich/LICENSE +19 -0
- pip/_vendor/rich/style.py +7 -11
- pip/_vendor/tomli/LICENSE +21 -0
- pip/_vendor/tomli/__init__.py +1 -1
- pip/_vendor/tomli/_parser.py +28 -21
- pip/_vendor/tomli/_re.py +8 -5
- pip/_vendor/tomli_w/LICENSE +21 -0
- pip/_vendor/truststore/LICENSE +21 -0
- pip/_vendor/truststore/__init__.py +1 -1
- pip/_vendor/truststore/_api.py +14 -6
- pip/_vendor/truststore/_openssl.py +3 -1
- pip/_vendor/urllib3/LICENSE.txt +21 -0
- pip/_vendor/vendor.txt +11 -11
- {pip-25.2.dist-info → pip-26.0.dist-info}/METADATA +10 -11
- {pip-25.2.dist-info → pip-26.0.dist-info}/RECORD +158 -139
- {pip-25.2.dist-info → pip-26.0.dist-info}/WHEEL +1 -2
- pip-26.0.dist-info/entry_points.txt +4 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/AUTHORS.txt +27 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/idna/LICENSE.md +1 -1
- pip/_internal/models/pylock.py +0 -188
- pip/_internal/operations/build/metadata_legacy.py +0 -73
- pip/_internal/operations/build/wheel_legacy.py +0 -119
- pip/_internal/operations/install/editable_legacy.py +0 -48
- pip/_internal/utils/setuptools_build.py +0 -149
- pip-25.2.dist-info/entry_points.txt +0 -3
- pip-25.2.dist-info/licenses/src/pip/_vendor/tomli/LICENSE-HEADER +0 -3
- pip-25.2.dist-info/top_level.txt +0 -1
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/LICENSE.txt +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/certifi/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/dependency_groups/LICENSE.txt +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distlib/LICENSE.txt +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distro/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/msgpack/COPYING +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.BSD +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pkg_resources/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/platformdirs/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pygments/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/requests/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/resolvelib/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/rich/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli_w/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/truststore/LICENSE +0 -0
- {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/urllib3/LICENSE.txt +0 -0
pip/_internal/exceptions.py
CHANGED
|
@@ -14,7 +14,8 @@ import logging
|
|
|
14
14
|
import pathlib
|
|
15
15
|
import re
|
|
16
16
|
import sys
|
|
17
|
-
|
|
17
|
+
import traceback
|
|
18
|
+
from collections.abc import Iterable, Iterator
|
|
18
19
|
from itertools import chain, groupby, repeat
|
|
19
20
|
from typing import TYPE_CHECKING, Literal
|
|
20
21
|
|
|
@@ -27,9 +28,10 @@ from pip._vendor.rich.text import Text
|
|
|
27
28
|
if TYPE_CHECKING:
|
|
28
29
|
from hashlib import _Hash
|
|
29
30
|
|
|
30
|
-
from pip._vendor.requests.models import Request, Response
|
|
31
|
+
from pip._vendor.requests.models import PreparedRequest, Request, Response
|
|
31
32
|
|
|
32
33
|
from pip._internal.metadata import BaseDistribution
|
|
34
|
+
from pip._internal.models.link import Link
|
|
33
35
|
from pip._internal.network.download import _FileDownload
|
|
34
36
|
from pip._internal.req.req_install import InstallRequirement
|
|
35
37
|
|
|
@@ -190,6 +192,23 @@ class InstallationError(PipError):
|
|
|
190
192
|
"""General exception during installation"""
|
|
191
193
|
|
|
192
194
|
|
|
195
|
+
class FailedToPrepareCandidate(InstallationError):
|
|
196
|
+
"""Raised when we fail to prepare a candidate (i.e. fetch and generate metadata).
|
|
197
|
+
|
|
198
|
+
This is intentionally not a diagnostic error, since the output will be presented
|
|
199
|
+
above this error, when this occurs. This should instead present information to the
|
|
200
|
+
user.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
def __init__(
|
|
204
|
+
self, *, package_name: str, requirement_chain: str, failed_step: str
|
|
205
|
+
) -> None:
|
|
206
|
+
super().__init__(f"Failed to build '{package_name}' when {failed_step.lower()}")
|
|
207
|
+
self.package_name = package_name
|
|
208
|
+
self.requirement_chain = requirement_chain
|
|
209
|
+
self.failed_step = failed_step
|
|
210
|
+
|
|
211
|
+
|
|
193
212
|
class MissingPyProjectBuildRequires(DiagnosticPipError):
|
|
194
213
|
"""Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
|
|
195
214
|
|
|
@@ -297,7 +316,7 @@ class NetworkConnectionError(PipError):
|
|
|
297
316
|
self,
|
|
298
317
|
error_msg: str,
|
|
299
318
|
response: Response | None = None,
|
|
300
|
-
request: Request | None = None,
|
|
319
|
+
request: Request | PreparedRequest | None = None,
|
|
301
320
|
) -> None:
|
|
302
321
|
"""
|
|
303
322
|
Initialize NetworkConnectionError with `request` and `response`
|
|
@@ -384,7 +403,7 @@ class InstallationSubprocessError(DiagnosticPipError, InstallationError):
|
|
|
384
403
|
output_lines: list[str] | None,
|
|
385
404
|
) -> None:
|
|
386
405
|
if output_lines is None:
|
|
387
|
-
output_prompt = Text("
|
|
406
|
+
output_prompt = Text("No available output.")
|
|
388
407
|
else:
|
|
389
408
|
output_prompt = (
|
|
390
409
|
Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
|
|
@@ -412,7 +431,7 @@ class InstallationSubprocessError(DiagnosticPipError, InstallationError):
|
|
|
412
431
|
return f"{self.command_description} exited with {self.exit_code}"
|
|
413
432
|
|
|
414
433
|
|
|
415
|
-
class MetadataGenerationFailed(
|
|
434
|
+
class MetadataGenerationFailed(DiagnosticPipError, InstallationError):
|
|
416
435
|
reference = "metadata-generation-failed"
|
|
417
436
|
|
|
418
437
|
def __init__(
|
|
@@ -420,7 +439,7 @@ class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
|
|
|
420
439
|
*,
|
|
421
440
|
package_details: str,
|
|
422
441
|
) -> None:
|
|
423
|
-
super(
|
|
442
|
+
super().__init__(
|
|
424
443
|
message="Encountered error while generating package metadata.",
|
|
425
444
|
context=escape(package_details),
|
|
426
445
|
hint_stmt="See above for details.",
|
|
@@ -879,3 +898,74 @@ class InstallWheelBuildError(DiagnosticPipError):
|
|
|
879
898
|
context=", ".join(r.name for r in failed), # type: ignore
|
|
880
899
|
hint_stmt=None,
|
|
881
900
|
)
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
class InvalidEggFragment(DiagnosticPipError):
|
|
904
|
+
reference = "invalid-egg-fragment"
|
|
905
|
+
|
|
906
|
+
def __init__(self, link: Link, fragment: str) -> None:
|
|
907
|
+
hint = ""
|
|
908
|
+
if ">" in fragment or "=" in fragment or "<" in fragment:
|
|
909
|
+
hint = (
|
|
910
|
+
"Version specifiers are silently ignored for URL references. "
|
|
911
|
+
"Remove them. "
|
|
912
|
+
)
|
|
913
|
+
if "[" in fragment and "]" in fragment:
|
|
914
|
+
hint += "Try using the Direct URL requirement syntax: 'name[extra] @ URL'"
|
|
915
|
+
|
|
916
|
+
if not hint:
|
|
917
|
+
hint = "Egg fragments can only be a valid project name."
|
|
918
|
+
|
|
919
|
+
super().__init__(
|
|
920
|
+
message=f"The '{escape(fragment)}' egg fragment is invalid",
|
|
921
|
+
context=f"from '{escape(str(link))}'",
|
|
922
|
+
hint_stmt=escape(hint),
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
class BuildDependencyInstallError(DiagnosticPipError):
|
|
927
|
+
"""Raised when build dependencies cannot be installed."""
|
|
928
|
+
|
|
929
|
+
reference = "failed-build-dependency-install"
|
|
930
|
+
|
|
931
|
+
def __init__(
|
|
932
|
+
self,
|
|
933
|
+
req: InstallRequirement | None,
|
|
934
|
+
build_reqs: Iterable[str],
|
|
935
|
+
*,
|
|
936
|
+
cause: Exception,
|
|
937
|
+
log_lines: list[str] | None,
|
|
938
|
+
) -> None:
|
|
939
|
+
if isinstance(cause, PipError):
|
|
940
|
+
note = "This is likely not a problem with pip."
|
|
941
|
+
else:
|
|
942
|
+
note = (
|
|
943
|
+
"pip crashed unexpectedly. Please file an issue on pip's issue "
|
|
944
|
+
"tracker: https://github.com/pypa/pip/issues/new"
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
if log_lines is None:
|
|
948
|
+
# No logs are available, they must have been printed earlier.
|
|
949
|
+
context = Text("See above for more details.")
|
|
950
|
+
else:
|
|
951
|
+
if isinstance(cause, PipError):
|
|
952
|
+
log_lines.append(f"ERROR: {cause}")
|
|
953
|
+
else:
|
|
954
|
+
# Split rendered error into real lines without trailing newlines.
|
|
955
|
+
log_lines.extend(
|
|
956
|
+
"".join(traceback.format_exception(cause)).splitlines()
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
context = Text.assemble(
|
|
960
|
+
f"Installing {' '.join(build_reqs)}\n",
|
|
961
|
+
(f"[{len(log_lines)} lines of output]\n", "red"),
|
|
962
|
+
"\n".join(log_lines),
|
|
963
|
+
("\n[end of output]", "red"),
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
message = Text("Cannot install build dependencies", "green")
|
|
967
|
+
if req:
|
|
968
|
+
message += Text(f" for {req}")
|
|
969
|
+
super().__init__(
|
|
970
|
+
message=message, context=context, hint_stmt=None, note_stmt=note
|
|
971
|
+
)
|
pip/_internal/index/collector.py
CHANGED
|
@@ -12,7 +12,6 @@ import json
|
|
|
12
12
|
import logging
|
|
13
13
|
import os
|
|
14
14
|
import urllib.parse
|
|
15
|
-
import urllib.request
|
|
16
15
|
from collections.abc import Iterable, MutableMapping, Sequence
|
|
17
16
|
from dataclasses import dataclass
|
|
18
17
|
from html.parser import HTMLParser
|
|
@@ -34,6 +33,7 @@ from pip._internal.network.session import PipSession
|
|
|
34
33
|
from pip._internal.network.utils import raise_for_status
|
|
35
34
|
from pip._internal.utils.filetypes import is_archive_file
|
|
36
35
|
from pip._internal.utils.misc import redact_auth_from_url
|
|
36
|
+
from pip._internal.utils.urls import url_to_path
|
|
37
37
|
from pip._internal.vcs import vcs
|
|
38
38
|
|
|
39
39
|
from .sources import CandidatesFromPage, LinkSource, build_source
|
|
@@ -330,8 +330,7 @@ def _get_index_content(link: Link, *, session: PipSession) -> IndexContent | Non
|
|
|
330
330
|
return None
|
|
331
331
|
|
|
332
332
|
# Tack index.html onto file:// URLs that point to directories
|
|
333
|
-
|
|
334
|
-
if scheme == "file" and os.path.isdir(urllib.request.url2pathname(path)):
|
|
333
|
+
if url.startswith("file:") and os.path.isdir(url_to_path(url)):
|
|
335
334
|
# add trailing slash if not present so urljoin doesn't trim
|
|
336
335
|
# final segment
|
|
337
336
|
if not url.endswith("/"):
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import datetime
|
|
5
6
|
import enum
|
|
6
7
|
import functools
|
|
7
8
|
import itertools
|
|
@@ -17,20 +18,23 @@ from typing import (
|
|
|
17
18
|
|
|
18
19
|
from pip._vendor.packaging import specifiers
|
|
19
20
|
from pip._vendor.packaging.tags import Tag
|
|
20
|
-
from pip._vendor.packaging.utils import canonicalize_name
|
|
21
|
-
from pip._vendor.packaging.version import InvalidVersion, _BaseVersion
|
|
21
|
+
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
|
22
|
+
from pip._vendor.packaging.version import InvalidVersion, Version, _BaseVersion
|
|
22
23
|
from pip._vendor.packaging.version import parse as parse_version
|
|
23
24
|
|
|
24
25
|
from pip._internal.exceptions import (
|
|
25
26
|
BestVersionAlreadyInstalled,
|
|
26
27
|
DistributionNotFound,
|
|
28
|
+
InstallationError,
|
|
27
29
|
InvalidWheelFilename,
|
|
28
30
|
UnsupportedWheel,
|
|
29
31
|
)
|
|
30
32
|
from pip._internal.index.collector import LinkCollector, parse_links
|
|
33
|
+
from pip._internal.metadata import select_backend
|
|
31
34
|
from pip._internal.models.candidate import InstallationCandidate
|
|
32
35
|
from pip._internal.models.format_control import FormatControl
|
|
33
36
|
from pip._internal.models.link import Link
|
|
37
|
+
from pip._internal.models.release_control import ReleaseControl
|
|
34
38
|
from pip._internal.models.search_scope import SearchScope
|
|
35
39
|
from pip._internal.models.selection_prefs import SelectionPreferences
|
|
36
40
|
from pip._internal.models.target_python import TargetPython
|
|
@@ -111,6 +115,8 @@ class LinkType(enum.Enum):
|
|
|
111
115
|
format_invalid = enum.auto()
|
|
112
116
|
platform_mismatch = enum.auto()
|
|
113
117
|
requires_python_mismatch = enum.auto()
|
|
118
|
+
upload_too_late = enum.auto()
|
|
119
|
+
upload_time_missing = enum.auto()
|
|
114
120
|
|
|
115
121
|
|
|
116
122
|
class LinkEvaluator:
|
|
@@ -127,11 +133,12 @@ class LinkEvaluator:
|
|
|
127
133
|
def __init__(
|
|
128
134
|
self,
|
|
129
135
|
project_name: str,
|
|
130
|
-
canonical_name:
|
|
136
|
+
canonical_name: NormalizedName,
|
|
131
137
|
formats: frozenset[str],
|
|
132
138
|
target_python: TargetPython,
|
|
133
139
|
allow_yanked: bool,
|
|
134
140
|
ignore_requires_python: bool | None = None,
|
|
141
|
+
uploaded_prior_to: datetime.datetime | None = None,
|
|
135
142
|
) -> None:
|
|
136
143
|
"""
|
|
137
144
|
:param project_name: The user supplied package name.
|
|
@@ -149,6 +156,8 @@ class LinkEvaluator:
|
|
|
149
156
|
:param ignore_requires_python: Whether to ignore incompatible
|
|
150
157
|
PEP 503 "data-requires-python" values in HTML links. Defaults
|
|
151
158
|
to False.
|
|
159
|
+
:param uploaded_prior_to: If set, only allow links uploaded prior to
|
|
160
|
+
the given datetime.
|
|
152
161
|
"""
|
|
153
162
|
if ignore_requires_python is None:
|
|
154
163
|
ignore_requires_python = False
|
|
@@ -158,6 +167,7 @@ class LinkEvaluator:
|
|
|
158
167
|
self._ignore_requires_python = ignore_requires_python
|
|
159
168
|
self._formats = formats
|
|
160
169
|
self._target_python = target_python
|
|
170
|
+
self._uploaded_prior_to = uploaded_prior_to
|
|
161
171
|
|
|
162
172
|
self.project_name = project_name
|
|
163
173
|
|
|
@@ -201,7 +211,7 @@ class LinkEvaluator:
|
|
|
201
211
|
LinkType.format_invalid,
|
|
202
212
|
"invalid wheel filename",
|
|
203
213
|
)
|
|
204
|
-
if
|
|
214
|
+
if wheel.name != self._canonical_name:
|
|
205
215
|
reason = f"wrong project name (not {self.project_name})"
|
|
206
216
|
return (LinkType.different_project, reason)
|
|
207
217
|
|
|
@@ -218,6 +228,27 @@ class LinkEvaluator:
|
|
|
218
228
|
|
|
219
229
|
version = wheel.version
|
|
220
230
|
|
|
231
|
+
# Check upload-time filter after verifying the link is a package file.
|
|
232
|
+
# Skip this check for local files, as --uploaded-prior-to only applies
|
|
233
|
+
# to packages from indexes.
|
|
234
|
+
if self._uploaded_prior_to is not None and not link.is_file:
|
|
235
|
+
if link.upload_time is None:
|
|
236
|
+
if link.comes_from:
|
|
237
|
+
index_info = f"Index {link.comes_from}"
|
|
238
|
+
else:
|
|
239
|
+
index_info = "Index"
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
LinkType.upload_time_missing,
|
|
243
|
+
f"{index_info} does not provide upload-time metadata.",
|
|
244
|
+
)
|
|
245
|
+
elif link.upload_time >= self._uploaded_prior_to:
|
|
246
|
+
return (
|
|
247
|
+
LinkType.upload_too_late,
|
|
248
|
+
f"Upload time {link.upload_time} not "
|
|
249
|
+
f"prior to {self._uploaded_prior_to}",
|
|
250
|
+
)
|
|
251
|
+
|
|
221
252
|
# This should be up by the self.ok_binary check, but see issue 2700.
|
|
222
253
|
if "source" not in self._formats and ext != WHEEL_EXTENSION:
|
|
223
254
|
reason = f"No sources permitted for {self.project_name}"
|
|
@@ -350,7 +381,7 @@ class CandidatePreferences:
|
|
|
350
381
|
"""
|
|
351
382
|
|
|
352
383
|
prefer_binary: bool = False
|
|
353
|
-
|
|
384
|
+
release_control: ReleaseControl | None = None
|
|
354
385
|
|
|
355
386
|
|
|
356
387
|
@dataclass(frozen=True)
|
|
@@ -391,7 +422,7 @@ class CandidateEvaluator:
|
|
|
391
422
|
project_name: str,
|
|
392
423
|
target_python: TargetPython | None = None,
|
|
393
424
|
prefer_binary: bool = False,
|
|
394
|
-
|
|
425
|
+
release_control: ReleaseControl | None = None,
|
|
395
426
|
specifier: specifiers.BaseSpecifier | None = None,
|
|
396
427
|
hashes: Hashes | None = None,
|
|
397
428
|
) -> CandidateEvaluator:
|
|
@@ -417,7 +448,7 @@ class CandidateEvaluator:
|
|
|
417
448
|
supported_tags=supported_tags,
|
|
418
449
|
specifier=specifier,
|
|
419
450
|
prefer_binary=prefer_binary,
|
|
420
|
-
|
|
451
|
+
release_control=release_control,
|
|
421
452
|
hashes=hashes,
|
|
422
453
|
)
|
|
423
454
|
|
|
@@ -427,14 +458,14 @@ class CandidateEvaluator:
|
|
|
427
458
|
supported_tags: list[Tag],
|
|
428
459
|
specifier: specifiers.BaseSpecifier,
|
|
429
460
|
prefer_binary: bool = False,
|
|
430
|
-
|
|
461
|
+
release_control: ReleaseControl | None = None,
|
|
431
462
|
hashes: Hashes | None = None,
|
|
432
463
|
) -> None:
|
|
433
464
|
"""
|
|
434
465
|
:param supported_tags: The PEP 425 tags supported by the target
|
|
435
466
|
Python in order of preference (most preferred first).
|
|
436
467
|
"""
|
|
437
|
-
self.
|
|
468
|
+
self._release_control = release_control
|
|
438
469
|
self._hashes = hashes
|
|
439
470
|
self._prefer_binary = prefer_binary
|
|
440
471
|
self._project_name = project_name
|
|
@@ -455,17 +486,27 @@ class CandidateEvaluator:
|
|
|
455
486
|
Return the applicable candidates from a list of candidates.
|
|
456
487
|
"""
|
|
457
488
|
# Using None infers from the specifier instead.
|
|
458
|
-
|
|
489
|
+
if self._release_control is not None:
|
|
490
|
+
allow_prereleases = self._release_control.allows_prereleases(
|
|
491
|
+
canonicalize_name(self._project_name)
|
|
492
|
+
)
|
|
493
|
+
else:
|
|
494
|
+
allow_prereleases = None
|
|
459
495
|
specifier = self._specifier
|
|
460
496
|
|
|
461
|
-
#
|
|
462
|
-
# when we're debundled but setuptools isn't,
|
|
463
|
-
# packaging.version.Version and
|
|
497
|
+
# When using the pkg_resources backend we turn the version object into
|
|
498
|
+
# a str here because otherwise when we're debundled but setuptools isn't,
|
|
499
|
+
# Python will see packaging.version.Version and
|
|
464
500
|
# pkg_resources._vendor.packaging.version.Version as different
|
|
465
501
|
# types. This way we'll use a str as a common data interchange
|
|
466
502
|
# format. If we stop using the pkg_resources provided specifier
|
|
467
503
|
# and start using our own, we can drop the cast to str().
|
|
468
|
-
|
|
504
|
+
if select_backend().NAME == "pkg_resources":
|
|
505
|
+
candidates_and_versions: list[
|
|
506
|
+
tuple[InstallationCandidate, str | Version]
|
|
507
|
+
] = [(c, str(c.version)) for c in candidates]
|
|
508
|
+
else:
|
|
509
|
+
candidates_and_versions = [(c, c.version) for c in candidates]
|
|
469
510
|
versions = set(
|
|
470
511
|
specifier.filter(
|
|
471
512
|
(v for _, v in candidates_and_versions),
|
|
@@ -593,6 +634,7 @@ class PackageFinder:
|
|
|
593
634
|
format_control: FormatControl | None = None,
|
|
594
635
|
candidate_prefs: CandidatePreferences | None = None,
|
|
595
636
|
ignore_requires_python: bool | None = None,
|
|
637
|
+
uploaded_prior_to: datetime.datetime | None = None,
|
|
596
638
|
) -> None:
|
|
597
639
|
"""
|
|
598
640
|
This constructor is primarily meant to be used by the create() class
|
|
@@ -614,6 +656,7 @@ class PackageFinder:
|
|
|
614
656
|
self._ignore_requires_python = ignore_requires_python
|
|
615
657
|
self._link_collector = link_collector
|
|
616
658
|
self._target_python = target_python
|
|
659
|
+
self._uploaded_prior_to = uploaded_prior_to
|
|
617
660
|
|
|
618
661
|
self.format_control = format_control
|
|
619
662
|
|
|
@@ -637,6 +680,7 @@ class PackageFinder:
|
|
|
637
680
|
link_collector: LinkCollector,
|
|
638
681
|
selection_prefs: SelectionPreferences,
|
|
639
682
|
target_python: TargetPython | None = None,
|
|
683
|
+
uploaded_prior_to: datetime.datetime | None = None,
|
|
640
684
|
) -> PackageFinder:
|
|
641
685
|
"""Create a PackageFinder.
|
|
642
686
|
|
|
@@ -645,13 +689,15 @@ class PackageFinder:
|
|
|
645
689
|
:param target_python: The target Python interpreter to use when
|
|
646
690
|
checking compatibility. If None (the default), a TargetPython
|
|
647
691
|
object will be constructed from the running Python.
|
|
692
|
+
:param uploaded_prior_to: If set, only find links uploaded prior
|
|
693
|
+
to the given datetime.
|
|
648
694
|
"""
|
|
649
695
|
if target_python is None:
|
|
650
696
|
target_python = TargetPython()
|
|
651
697
|
|
|
652
698
|
candidate_prefs = CandidatePreferences(
|
|
653
699
|
prefer_binary=selection_prefs.prefer_binary,
|
|
654
|
-
|
|
700
|
+
release_control=selection_prefs.release_control,
|
|
655
701
|
)
|
|
656
702
|
|
|
657
703
|
return cls(
|
|
@@ -661,6 +707,7 @@ class PackageFinder:
|
|
|
661
707
|
allow_yanked=selection_prefs.allow_yanked,
|
|
662
708
|
format_control=selection_prefs.format_control,
|
|
663
709
|
ignore_requires_python=selection_prefs.ignore_requires_python,
|
|
710
|
+
uploaded_prior_to=uploaded_prior_to,
|
|
664
711
|
)
|
|
665
712
|
|
|
666
713
|
@property
|
|
@@ -707,11 +754,11 @@ class PackageFinder:
|
|
|
707
754
|
return cert
|
|
708
755
|
|
|
709
756
|
@property
|
|
710
|
-
def
|
|
711
|
-
return self._candidate_prefs.
|
|
757
|
+
def release_control(self) -> ReleaseControl | None:
|
|
758
|
+
return self._candidate_prefs.release_control
|
|
712
759
|
|
|
713
|
-
def
|
|
714
|
-
self._candidate_prefs.
|
|
760
|
+
def set_release_control(self, release_control: ReleaseControl) -> None:
|
|
761
|
+
self._candidate_prefs.release_control = release_control
|
|
715
762
|
|
|
716
763
|
@property
|
|
717
764
|
def prefer_binary(self) -> bool:
|
|
@@ -720,6 +767,10 @@ class PackageFinder:
|
|
|
720
767
|
def set_prefer_binary(self) -> None:
|
|
721
768
|
self._candidate_prefs.prefer_binary = True
|
|
722
769
|
|
|
770
|
+
@property
|
|
771
|
+
def uploaded_prior_to(self) -> datetime.datetime | None:
|
|
772
|
+
return self._uploaded_prior_to
|
|
773
|
+
|
|
723
774
|
def requires_python_skipped_reasons(self) -> list[str]:
|
|
724
775
|
reasons = {
|
|
725
776
|
detail
|
|
@@ -739,6 +790,7 @@ class PackageFinder:
|
|
|
739
790
|
target_python=self._target_python,
|
|
740
791
|
allow_yanked=self._allow_yanked,
|
|
741
792
|
ignore_requires_python=self._ignore_requires_python,
|
|
793
|
+
uploaded_prior_to=self._uploaded_prior_to,
|
|
742
794
|
)
|
|
743
795
|
|
|
744
796
|
def _sort_links(self, links: Iterable[Link]) -> list[Link]:
|
|
@@ -773,6 +825,10 @@ class PackageFinder:
|
|
|
773
825
|
InstallationCandidate and return it. Otherwise, return None.
|
|
774
826
|
"""
|
|
775
827
|
result, detail = link_evaluator.evaluate_link(link)
|
|
828
|
+
if result == LinkType.upload_time_missing:
|
|
829
|
+
# Fail immediately if the index doesn't provide upload-time
|
|
830
|
+
# when --uploaded-prior-to is specified
|
|
831
|
+
raise InstallationError(detail)
|
|
776
832
|
if result != LinkType.candidate:
|
|
777
833
|
self._log_skipped_link(link, result, detail)
|
|
778
834
|
return None
|
|
@@ -890,7 +946,7 @@ class PackageFinder:
|
|
|
890
946
|
project_name=project_name,
|
|
891
947
|
target_python=self._target_python,
|
|
892
948
|
prefer_binary=candidate_prefs.prefer_binary,
|
|
893
|
-
|
|
949
|
+
release_control=candidate_prefs.release_control,
|
|
894
950
|
specifier=specifier,
|
|
895
951
|
hashes=hashes,
|
|
896
952
|
)
|
|
@@ -964,9 +1020,19 @@ class PackageFinder:
|
|
|
964
1020
|
)
|
|
965
1021
|
|
|
966
1022
|
if installed_version is None and best_candidate is None:
|
|
1023
|
+
# Check if only final releases are allowed for this package
|
|
1024
|
+
version_type = "version"
|
|
1025
|
+
if self.release_control is not None:
|
|
1026
|
+
allows_pre = self.release_control.allows_prereleases(
|
|
1027
|
+
canonicalize_name(name)
|
|
1028
|
+
)
|
|
1029
|
+
if allows_pre is False:
|
|
1030
|
+
version_type = "final version"
|
|
1031
|
+
|
|
967
1032
|
logger.critical(
|
|
968
|
-
"Could not find a
|
|
1033
|
+
"Could not find a %s that satisfies the requirement %s "
|
|
969
1034
|
"(from versions: %s)",
|
|
1035
|
+
version_type,
|
|
970
1036
|
req,
|
|
971
1037
|
_format_versions(best_candidate_result.all_candidates),
|
|
972
1038
|
)
|
|
@@ -6,7 +6,6 @@ import os
|
|
|
6
6
|
import pathlib
|
|
7
7
|
import sys
|
|
8
8
|
import sysconfig
|
|
9
|
-
from typing import Any
|
|
10
9
|
|
|
11
10
|
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
|
|
12
11
|
from pip._internal.utils.compat import WINDOWS
|
|
@@ -134,7 +133,7 @@ def _looks_like_red_hat_scheme() -> bool:
|
|
|
134
133
|
from distutils.command.install import install
|
|
135
134
|
from distutils.dist import Distribution
|
|
136
135
|
|
|
137
|
-
cmd
|
|
136
|
+
cmd = install(Distribution())
|
|
138
137
|
cmd.finalize_options()
|
|
139
138
|
return (
|
|
140
139
|
cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local"
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
6
|
import sysconfig
|
|
7
|
+
from typing import Callable
|
|
7
8
|
|
|
8
9
|
from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationInvalid
|
|
9
10
|
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
|
|
@@ -24,7 +25,9 @@ logger = logging.getLogger(__name__)
|
|
|
24
25
|
|
|
25
26
|
_AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names())
|
|
26
27
|
|
|
27
|
-
_PREFERRED_SCHEME_API = getattr(
|
|
28
|
+
_PREFERRED_SCHEME_API: Callable[[str], str] | None = getattr(
|
|
29
|
+
sysconfig, "get_preferred_scheme", None
|
|
30
|
+
)
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
def _should_use_osx_framework_prefix() -> bool:
|
|
@@ -4,13 +4,16 @@ import contextlib
|
|
|
4
4
|
import functools
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
|
-
from typing import Literal, Protocol, cast
|
|
7
|
+
from typing import TYPE_CHECKING, Literal, Protocol, cast
|
|
8
8
|
|
|
9
9
|
from pip._internal.utils.deprecation import deprecated
|
|
10
10
|
from pip._internal.utils.misc import strtobool
|
|
11
11
|
|
|
12
12
|
from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel
|
|
13
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from pip._vendor.packaging.utils import NormalizedName
|
|
16
|
+
|
|
14
17
|
__all__ = [
|
|
15
18
|
"BaseDistribution",
|
|
16
19
|
"BaseEnvironment",
|
|
@@ -131,7 +134,9 @@ def get_directory_distribution(directory: str) -> BaseDistribution:
|
|
|
131
134
|
return select_backend().Distribution.from_directory(directory)
|
|
132
135
|
|
|
133
136
|
|
|
134
|
-
def get_wheel_distribution(
|
|
137
|
+
def get_wheel_distribution(
|
|
138
|
+
wheel: Wheel, canonical_name: NormalizedName
|
|
139
|
+
) -> BaseDistribution:
|
|
135
140
|
"""Get the representation of the specified wheel's distribution metadata.
|
|
136
141
|
|
|
137
142
|
This returns a Distribution instance from the chosen backend based on
|
|
@@ -28,6 +28,7 @@ from pip._internal.utils.temp_dir import TempDirectory
|
|
|
28
28
|
from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
|
|
29
29
|
|
|
30
30
|
from ._compat import (
|
|
31
|
+
BadMetadata,
|
|
31
32
|
BasePath,
|
|
32
33
|
get_dist_canonical_name,
|
|
33
34
|
parse_name_and_version_from_info_directory,
|
|
@@ -165,9 +166,14 @@ class Distribution(BaseDistribution):
|
|
|
165
166
|
|
|
166
167
|
@property
|
|
167
168
|
def version(self) -> Version:
|
|
168
|
-
|
|
169
|
+
try:
|
|
170
|
+
version = (
|
|
171
|
+
parse_name_and_version_from_info_directory(self._dist)[1]
|
|
172
|
+
or self._dist.version
|
|
173
|
+
)
|
|
169
174
|
return parse_version(version)
|
|
170
|
-
|
|
175
|
+
except TypeError:
|
|
176
|
+
raise BadMetadata(self._dist, reason="invalid metadata entry `version`")
|
|
171
177
|
|
|
172
178
|
@property
|
|
173
179
|
def raw_version(self) -> str:
|