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
|
@@ -10,6 +10,7 @@ from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
|
|
10
10
|
from pip._vendor.packaging.version import Version
|
|
11
11
|
|
|
12
12
|
from pip._internal.exceptions import (
|
|
13
|
+
FailedToPrepareCandidate,
|
|
13
14
|
HashError,
|
|
14
15
|
InstallationSubprocessError,
|
|
15
16
|
InvalidInstalledPackage,
|
|
@@ -68,10 +69,8 @@ def make_install_req_from_link(
|
|
|
68
69
|
line,
|
|
69
70
|
user_supplied=template.user_supplied,
|
|
70
71
|
comes_from=template.comes_from,
|
|
71
|
-
use_pep517=template.use_pep517,
|
|
72
72
|
isolated=template.isolated,
|
|
73
73
|
constraint=template.constraint,
|
|
74
|
-
global_options=template.global_options,
|
|
75
74
|
hash_options=template.hash_options,
|
|
76
75
|
config_settings=template.config_settings,
|
|
77
76
|
)
|
|
@@ -85,15 +84,17 @@ def make_install_req_from_editable(
|
|
|
85
84
|
link: Link, template: InstallRequirement
|
|
86
85
|
) -> InstallRequirement:
|
|
87
86
|
assert template.editable, "template not editable"
|
|
87
|
+
if template.name:
|
|
88
|
+
req_string = f"{template.name} @ {link.url}"
|
|
89
|
+
else:
|
|
90
|
+
req_string = link.url
|
|
88
91
|
ireq = install_req_from_editable(
|
|
89
|
-
|
|
92
|
+
req_string,
|
|
90
93
|
user_supplied=template.user_supplied,
|
|
91
94
|
comes_from=template.comes_from,
|
|
92
|
-
use_pep517=template.use_pep517,
|
|
93
95
|
isolated=template.isolated,
|
|
94
96
|
constraint=template.constraint,
|
|
95
97
|
permit_editable_wheels=template.permit_editable_wheels,
|
|
96
|
-
global_options=template.global_options,
|
|
97
98
|
hash_options=template.hash_options,
|
|
98
99
|
config_settings=template.config_settings,
|
|
99
100
|
)
|
|
@@ -114,10 +115,8 @@ def _make_install_req_from_dist(
|
|
|
114
115
|
line,
|
|
115
116
|
user_supplied=template.user_supplied,
|
|
116
117
|
comes_from=template.comes_from,
|
|
117
|
-
use_pep517=template.use_pep517,
|
|
118
118
|
isolated=template.isolated,
|
|
119
119
|
constraint=template.constraint,
|
|
120
|
-
global_options=template.global_options,
|
|
121
120
|
hash_options=template.hash_options,
|
|
122
121
|
config_settings=template.config_settings,
|
|
123
122
|
)
|
|
@@ -244,9 +243,19 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|
|
244
243
|
e.req = self._ireq
|
|
245
244
|
raise
|
|
246
245
|
except InstallationSubprocessError as exc:
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
246
|
+
if isinstance(self._ireq.comes_from, InstallRequirement):
|
|
247
|
+
request_chain = self._ireq.comes_from.from_path()
|
|
248
|
+
else:
|
|
249
|
+
request_chain = self._ireq.comes_from
|
|
250
|
+
|
|
251
|
+
if request_chain is None:
|
|
252
|
+
request_chain = "directly requested"
|
|
253
|
+
|
|
254
|
+
raise FailedToPrepareCandidate(
|
|
255
|
+
package_name=self._ireq.name or str(self._link),
|
|
256
|
+
requirement_chain=request_chain,
|
|
257
|
+
failed_step=exc.command_description,
|
|
258
|
+
)
|
|
250
259
|
|
|
251
260
|
self._check_metadata_consistency(dist)
|
|
252
261
|
return dist
|
|
@@ -283,7 +292,7 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
|
|
|
283
292
|
assert ireq.link == link
|
|
284
293
|
if ireq.link.is_wheel and not ireq.link.is_file:
|
|
285
294
|
wheel = Wheel(ireq.link.filename)
|
|
286
|
-
wheel_name =
|
|
295
|
+
wheel_name = wheel.name
|
|
287
296
|
assert name == wheel_name, f"{name!r} != {wheel_name!r} for wheel"
|
|
288
297
|
# Version may not be present for PEP 508 direct URLs
|
|
289
298
|
if version is not None:
|
|
@@ -695,9 +695,20 @@ class Factory:
|
|
|
695
695
|
"version: %s",
|
|
696
696
|
"; ".join(skipped_by_requires_python) or "none",
|
|
697
697
|
)
|
|
698
|
+
|
|
699
|
+
# Check if only final releases are allowed for this package
|
|
700
|
+
version_type = "version"
|
|
701
|
+
if self._finder.release_control is not None:
|
|
702
|
+
allows_pre = self._finder.release_control.allows_prereleases(
|
|
703
|
+
canonicalize_name(req.project_name)
|
|
704
|
+
)
|
|
705
|
+
if allows_pre is False:
|
|
706
|
+
version_type = "final version"
|
|
707
|
+
|
|
698
708
|
logger.critical(
|
|
699
|
-
"Could not find a
|
|
709
|
+
"Could not find a %s that satisfies the requirement %s "
|
|
700
710
|
"(from versions: %s)",
|
|
711
|
+
version_type,
|
|
701
712
|
req_disp,
|
|
702
713
|
", ".join(versions) or "none",
|
|
703
714
|
)
|
|
@@ -711,6 +722,21 @@ class Factory:
|
|
|
711
722
|
|
|
712
723
|
return DistributionNotFound(f"No matching distribution found for {req}")
|
|
713
724
|
|
|
725
|
+
def _has_any_candidates(self, project_name: str) -> bool:
|
|
726
|
+
"""
|
|
727
|
+
Check if there are any candidates available for the project name.
|
|
728
|
+
"""
|
|
729
|
+
return any(
|
|
730
|
+
self.find_candidates(
|
|
731
|
+
project_name,
|
|
732
|
+
requirements={project_name: []},
|
|
733
|
+
incompatibilities={},
|
|
734
|
+
constraint=Constraint.empty(),
|
|
735
|
+
prefers_installed=True,
|
|
736
|
+
is_satisfied_by=lambda r, c: True,
|
|
737
|
+
)
|
|
738
|
+
)
|
|
739
|
+
|
|
714
740
|
def get_installation_error(
|
|
715
741
|
self,
|
|
716
742
|
e: ResolutionImpossible[Requirement, Candidate],
|
|
@@ -796,6 +822,22 @@ class Factory:
|
|
|
796
822
|
spec = constraints[key].specifier
|
|
797
823
|
msg += f"\n The user requested (constraint) {key}{spec}"
|
|
798
824
|
|
|
825
|
+
# Check for causes that had no candidates
|
|
826
|
+
causes = set()
|
|
827
|
+
for req, _ in e.causes:
|
|
828
|
+
causes.add(req.name)
|
|
829
|
+
|
|
830
|
+
no_candidates = {c for c in causes if not self._has_any_candidates(c)}
|
|
831
|
+
if no_candidates:
|
|
832
|
+
msg = (
|
|
833
|
+
msg
|
|
834
|
+
+ "\n\n"
|
|
835
|
+
+ "Additionally, some packages in these conflicts have no "
|
|
836
|
+
+ "matching distributions available for your environment:"
|
|
837
|
+
+ "\n "
|
|
838
|
+
+ "\n ".join(sorted(no_candidates))
|
|
839
|
+
)
|
|
840
|
+
|
|
799
841
|
msg = (
|
|
800
842
|
msg
|
|
801
843
|
+ "\n\n"
|
|
@@ -100,6 +100,15 @@ class PipProvider(_ProviderBase):
|
|
|
100
100
|
self._upgrade_strategy = upgrade_strategy
|
|
101
101
|
self._user_requested = user_requested
|
|
102
102
|
|
|
103
|
+
@property
|
|
104
|
+
def constraints(self) -> dict[str, Constraint]:
|
|
105
|
+
"""Public view of user-specified constraints.
|
|
106
|
+
|
|
107
|
+
Exposes the provider's constraints mapping without encouraging
|
|
108
|
+
external callers to reach into private attributes.
|
|
109
|
+
"""
|
|
110
|
+
return self._constraints
|
|
111
|
+
|
|
103
112
|
def identify(self, requirement_or_candidate: Requirement | Candidate) -> str:
|
|
104
113
|
return requirement_or_candidate.name
|
|
105
114
|
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections import defaultdict
|
|
4
|
+
from collections.abc import Mapping
|
|
4
5
|
from logging import getLogger
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
from pip._vendor.resolvelib.reporters import BaseReporter
|
|
8
9
|
|
|
9
|
-
from .base import Candidate, Requirement
|
|
10
|
+
from .base import Candidate, Constraint, Requirement
|
|
10
11
|
|
|
11
12
|
logger = getLogger(__name__)
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class PipReporter(BaseReporter[Requirement, Candidate, str]):
|
|
15
|
-
def __init__(self) -> None:
|
|
16
|
+
def __init__(self, constraints: Mapping[str, Constraint] | None = None) -> None:
|
|
16
17
|
self.reject_count_by_package: defaultdict[str, int] = defaultdict(int)
|
|
18
|
+
self._constraints = constraints or {}
|
|
17
19
|
|
|
18
20
|
self._messages_at_reject_count = {
|
|
19
21
|
1: (
|
|
@@ -35,25 +37,36 @@ class PipReporter(BaseReporter[Requirement, Candidate, str]):
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None:
|
|
40
|
+
"""Report a candidate being rejected.
|
|
41
|
+
|
|
42
|
+
Logs both the rejection count message (if applicable) and details about
|
|
43
|
+
the requirements and constraints that caused the rejection.
|
|
44
|
+
"""
|
|
38
45
|
self.reject_count_by_package[candidate.name] += 1
|
|
39
46
|
|
|
40
47
|
count = self.reject_count_by_package[candidate.name]
|
|
41
|
-
if count
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
message = self._messages_at_reject_count[count]
|
|
45
|
-
logger.info("INFO: %s", message.format(package_name=candidate.name))
|
|
48
|
+
if count in self._messages_at_reject_count:
|
|
49
|
+
message = self._messages_at_reject_count[count]
|
|
50
|
+
logger.info("INFO: %s", message.format(package_name=candidate.name))
|
|
46
51
|
|
|
47
52
|
msg = "Will try a different candidate, due to conflict:"
|
|
48
53
|
for req_info in criterion.information:
|
|
49
54
|
req, parent = req_info.requirement, req_info.parent
|
|
50
|
-
# Inspired by Factory.get_installation_error
|
|
51
55
|
msg += "\n "
|
|
52
56
|
if parent:
|
|
53
57
|
msg += f"{parent.name} {parent.version} depends on "
|
|
54
58
|
else:
|
|
55
59
|
msg += "The user requested "
|
|
56
60
|
msg += req.format_for_error()
|
|
61
|
+
|
|
62
|
+
# Add any relevant constraints
|
|
63
|
+
if self._constraints:
|
|
64
|
+
name = candidate.name
|
|
65
|
+
constraint = self._constraints.get(name)
|
|
66
|
+
if constraint and constraint.specifier:
|
|
67
|
+
constraint_text = f"{name}{constraint.specifier}"
|
|
68
|
+
msg += f"\n The user requested (constraint) {constraint_text}"
|
|
69
|
+
|
|
57
70
|
logger.debug(msg)
|
|
58
71
|
|
|
59
72
|
|
|
@@ -164,6 +164,12 @@ class RequiresPythonRequirement(Requirement):
|
|
|
164
164
|
self._hash: int | None = None
|
|
165
165
|
self._candidate = match
|
|
166
166
|
|
|
167
|
+
# Pre-compute candidate lookup to avoid repeated specifier checks
|
|
168
|
+
if specifier.contains(match.version, prereleases=True):
|
|
169
|
+
self._candidate_lookup: CandidateLookup = (match, None)
|
|
170
|
+
else:
|
|
171
|
+
self._candidate_lookup = (None, None)
|
|
172
|
+
|
|
167
173
|
def __str__(self) -> str:
|
|
168
174
|
return f"Python {self.specifier}"
|
|
169
175
|
|
|
@@ -197,9 +203,7 @@ class RequiresPythonRequirement(Requirement):
|
|
|
197
203
|
return str(self)
|
|
198
204
|
|
|
199
205
|
def get_candidate_lookup(self) -> CandidateLookup:
|
|
200
|
-
|
|
201
|
-
return self._candidate, None
|
|
202
|
-
return None, None
|
|
206
|
+
return self._candidate_lookup
|
|
203
207
|
|
|
204
208
|
def is_satisfied_by(self, candidate: Candidate) -> bool:
|
|
205
209
|
assert candidate.name == self._candidate.name, "Not Python candidate"
|
|
@@ -87,7 +87,8 @@ class Resolver(BaseResolver):
|
|
|
87
87
|
if "PIP_RESOLVER_DEBUG" in os.environ:
|
|
88
88
|
reporter: BaseReporter[Requirement, Candidate, str] = PipDebuggingReporter()
|
|
89
89
|
else:
|
|
90
|
-
reporter = PipReporter()
|
|
90
|
+
reporter = PipReporter(constraints=provider.constraints)
|
|
91
|
+
|
|
91
92
|
resolver: RLResolver[Requirement, Candidate, str] = RLResolver(
|
|
92
93
|
provider,
|
|
93
94
|
reporter,
|
|
@@ -180,11 +181,6 @@ class Resolver(BaseResolver):
|
|
|
180
181
|
|
|
181
182
|
req_set.add_named_requirement(ireq)
|
|
182
183
|
|
|
183
|
-
reqs = req_set.all_requirements
|
|
184
|
-
self.factory.preparer.prepare_linked_requirements_more(reqs)
|
|
185
|
-
for req in reqs:
|
|
186
|
-
req.prepared = True
|
|
187
|
-
req.needs_more_preparation = False
|
|
188
184
|
return req_set
|
|
189
185
|
|
|
190
186
|
def get_installation_order(
|
|
@@ -9,7 +9,7 @@ import optparse
|
|
|
9
9
|
import os.path
|
|
10
10
|
import sys
|
|
11
11
|
from dataclasses import dataclass
|
|
12
|
-
from typing import
|
|
12
|
+
from typing import Callable
|
|
13
13
|
|
|
14
14
|
from pip._vendor.packaging.version import Version
|
|
15
15
|
from pip._vendor.packaging.version import parse as parse_version
|
|
@@ -20,14 +20,21 @@ from pip._vendor.rich.text import Text
|
|
|
20
20
|
from pip._internal.index.collector import LinkCollector
|
|
21
21
|
from pip._internal.index.package_finder import PackageFinder
|
|
22
22
|
from pip._internal.metadata import get_default_environment
|
|
23
|
+
from pip._internal.models.release_control import ReleaseControl
|
|
23
24
|
from pip._internal.models.selection_prefs import SelectionPreferences
|
|
24
25
|
from pip._internal.network.session import PipSession
|
|
25
26
|
from pip._internal.utils.compat import WINDOWS
|
|
27
|
+
from pip._internal.utils.datetime import parse_iso_datetime
|
|
26
28
|
from pip._internal.utils.entrypoints import (
|
|
27
29
|
get_best_invocation_for_this_pip,
|
|
28
30
|
get_best_invocation_for_this_python,
|
|
29
31
|
)
|
|
30
|
-
from pip._internal.utils.filesystem import
|
|
32
|
+
from pip._internal.utils.filesystem import (
|
|
33
|
+
adjacent_tmp_file,
|
|
34
|
+
check_path_owner,
|
|
35
|
+
copy_directory_permissions,
|
|
36
|
+
replace,
|
|
37
|
+
)
|
|
31
38
|
from pip._internal.utils.misc import (
|
|
32
39
|
ExternallyManagedEnvironment,
|
|
33
40
|
check_externally_managed,
|
|
@@ -45,18 +52,9 @@ def _get_statefile_name(key: str) -> str:
|
|
|
45
52
|
return name
|
|
46
53
|
|
|
47
54
|
|
|
48
|
-
def _convert_date(isodate: str) -> datetime.datetime:
|
|
49
|
-
"""Convert an ISO format string to a date.
|
|
50
|
-
|
|
51
|
-
Handles the format 2020-01-22T14:24:01Z (trailing Z)
|
|
52
|
-
which is not supported by older versions of fromisoformat.
|
|
53
|
-
"""
|
|
54
|
-
return datetime.datetime.fromisoformat(isodate.replace("Z", "+00:00"))
|
|
55
|
-
|
|
56
|
-
|
|
57
55
|
class SelfCheckState:
|
|
58
56
|
def __init__(self, cache_dir: str) -> None:
|
|
59
|
-
self._state: dict[str,
|
|
57
|
+
self._state: dict[str, str] = {}
|
|
60
58
|
self._statefile_path = None
|
|
61
59
|
|
|
62
60
|
# Try to load the existing state
|
|
@@ -88,7 +86,7 @@ class SelfCheckState:
|
|
|
88
86
|
return None
|
|
89
87
|
|
|
90
88
|
# Determine if we need to refresh the state
|
|
91
|
-
last_check =
|
|
89
|
+
last_check = parse_iso_datetime(self._state["last_check"])
|
|
92
90
|
time_since_last_check = current_time - last_check
|
|
93
91
|
if time_since_last_check > _WEEK:
|
|
94
92
|
return None
|
|
@@ -100,13 +98,15 @@ class SelfCheckState:
|
|
|
100
98
|
if not self._statefile_path:
|
|
101
99
|
return
|
|
102
100
|
|
|
101
|
+
statefile_directory = os.path.dirname(self._statefile_path)
|
|
102
|
+
|
|
103
103
|
# Check to make sure that we own the directory
|
|
104
|
-
if not check_path_owner(
|
|
104
|
+
if not check_path_owner(statefile_directory):
|
|
105
105
|
return
|
|
106
106
|
|
|
107
107
|
# Now that we've ensured the directory is owned by this user, we'll go
|
|
108
108
|
# ahead and make sure that all our directories are created.
|
|
109
|
-
ensure_dir(
|
|
109
|
+
ensure_dir(statefile_directory)
|
|
110
110
|
|
|
111
111
|
state = {
|
|
112
112
|
# Include the key so it's easy to tell which pip wrote the
|
|
@@ -120,6 +120,7 @@ class SelfCheckState:
|
|
|
120
120
|
|
|
121
121
|
with adjacent_tmp_file(self._statefile_path) as f:
|
|
122
122
|
f.write(text.encode())
|
|
123
|
+
copy_directory_permissions(statefile_directory, f)
|
|
123
124
|
|
|
124
125
|
try:
|
|
125
126
|
# Since we have a prefix-specific state file, we can just
|
|
@@ -179,7 +180,7 @@ def _get_current_remote_pip_version(
|
|
|
179
180
|
# yanked version.
|
|
180
181
|
selection_prefs = SelectionPreferences(
|
|
181
182
|
allow_yanked=False,
|
|
182
|
-
|
|
183
|
+
release_control=ReleaseControl(only_final={"pip"}),
|
|
183
184
|
)
|
|
184
185
|
|
|
185
186
|
finder = PackageFinder.create(
|
pip/_internal/utils/datetime.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""For when pip wants to check the date or time."""
|
|
2
2
|
|
|
3
3
|
import datetime
|
|
4
|
+
import sys
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def today_is_later_than(year: int, month: int, day: int) -> bool:
|
|
@@ -8,3 +9,20 @@ def today_is_later_than(year: int, month: int, day: int) -> bool:
|
|
|
8
9
|
given = datetime.date(year, month, day)
|
|
9
10
|
|
|
10
11
|
return today > given
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_iso_datetime(isodate: str) -> datetime.datetime:
|
|
15
|
+
"""Convert an ISO format string to a datetime.
|
|
16
|
+
|
|
17
|
+
Handles the format 2020-01-22T14:24:01Z (trailing Z)
|
|
18
|
+
which is not supported by older versions of fromisoformat.
|
|
19
|
+
"""
|
|
20
|
+
# Python 3.11+ supports Z suffix natively in fromisoformat
|
|
21
|
+
if sys.version_info >= (3, 11):
|
|
22
|
+
return datetime.datetime.fromisoformat(isodate)
|
|
23
|
+
else:
|
|
24
|
+
return datetime.datetime.fromisoformat(
|
|
25
|
+
isodate.replace("Z", "+00:00")
|
|
26
|
+
if isodate.endswith("Z") and ("T" in isodate or " " in isodate.strip())
|
|
27
|
+
else isodate
|
|
28
|
+
)
|
|
@@ -7,8 +7,9 @@ import random
|
|
|
7
7
|
import sys
|
|
8
8
|
from collections.abc import Generator
|
|
9
9
|
from contextlib import contextmanager
|
|
10
|
+
from pathlib import Path
|
|
10
11
|
from tempfile import NamedTemporaryFile
|
|
11
|
-
from typing import Any, BinaryIO, cast
|
|
12
|
+
from typing import Any, BinaryIO, Callable, cast
|
|
12
13
|
|
|
13
14
|
from pip._internal.utils.compat import get_path_uid
|
|
14
15
|
from pip._internal.utils.misc import format_size
|
|
@@ -150,3 +151,53 @@ def directory_size(path: str) -> int | float:
|
|
|
150
151
|
|
|
151
152
|
def format_directory_size(path: str) -> str:
|
|
152
153
|
return format_size(directory_size(path))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def copy_directory_permissions(directory: str, target_file: BinaryIO) -> None:
|
|
157
|
+
mode = (
|
|
158
|
+
os.stat(directory).st_mode & 0o666 # select read/write permissions of directory
|
|
159
|
+
| 0o600 # set owner read/write permissions
|
|
160
|
+
)
|
|
161
|
+
# Change permissions only if there is no risk of following a symlink.
|
|
162
|
+
if os.chmod in os.supports_fd:
|
|
163
|
+
os.chmod(target_file.fileno(), mode)
|
|
164
|
+
elif os.chmod in os.supports_follow_symlinks:
|
|
165
|
+
os.chmod(target_file.name, mode, follow_symlinks=False)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _subdirs_without_generic(
|
|
169
|
+
path: str, predicate: Callable[[str, list[str]], bool]
|
|
170
|
+
) -> Generator[Path]:
|
|
171
|
+
"""Yields every subdirectory of +path+ that has no files matching the
|
|
172
|
+
predicate under it."""
|
|
173
|
+
|
|
174
|
+
directories = []
|
|
175
|
+
excluded = set()
|
|
176
|
+
|
|
177
|
+
for root_str, _, filenames in os.walk(Path(path).resolve()):
|
|
178
|
+
root = Path(root_str)
|
|
179
|
+
if predicate(root_str, filenames):
|
|
180
|
+
# This directory should be excluded, so exclude it and all of its
|
|
181
|
+
# parent directories.
|
|
182
|
+
# The last item in root.parents is ".", so we ignore it.
|
|
183
|
+
#
|
|
184
|
+
# Wrapping this in `list()` is only needed for Python 3.9.
|
|
185
|
+
excluded.update(list(root.parents)[:-1])
|
|
186
|
+
excluded.add(root)
|
|
187
|
+
directories.append(root)
|
|
188
|
+
|
|
189
|
+
for d in sorted(directories, reverse=True):
|
|
190
|
+
if d not in excluded:
|
|
191
|
+
yield d
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def subdirs_without_files(path: str) -> Generator[Path]:
|
|
195
|
+
"""Yields every subdirectory of +path+ that has no files under it."""
|
|
196
|
+
return _subdirs_without_generic(path, lambda root, filenames: len(filenames) > 0)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def subdirs_without_wheels(path: str) -> Generator[Path]:
|
|
200
|
+
"""Yields every subdirectory of +path+ that has no .whl files under it."""
|
|
201
|
+
return _subdirs_without_generic(
|
|
202
|
+
path, lambda root, filenames: any(x.endswith(".whl") for x in filenames)
|
|
203
|
+
)
|
pip/_internal/utils/logging.py
CHANGED
|
@@ -9,7 +9,7 @@ import sys
|
|
|
9
9
|
import threading
|
|
10
10
|
from collections.abc import Generator
|
|
11
11
|
from dataclasses import dataclass
|
|
12
|
-
from io import TextIOWrapper
|
|
12
|
+
from io import StringIO, TextIOWrapper
|
|
13
13
|
from logging import Filter
|
|
14
14
|
from typing import Any, ClassVar
|
|
15
15
|
|
|
@@ -29,7 +29,7 @@ from pip._vendor.rich.style import Style
|
|
|
29
29
|
from pip._internal.utils._log import VERBOSE, getLogger
|
|
30
30
|
from pip._internal.utils.compat import WINDOWS
|
|
31
31
|
from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX
|
|
32
|
-
from pip._internal.utils.misc import ensure_dir
|
|
32
|
+
from pip._internal.utils.misc import StreamWrapper, ensure_dir
|
|
33
33
|
|
|
34
34
|
_log_state = threading.local()
|
|
35
35
|
_stdout_console = None
|
|
@@ -56,6 +56,38 @@ def _is_broken_pipe_error(exc_class: type[BaseException], exc: BaseException) ->
|
|
|
56
56
|
return isinstance(exc, OSError) and exc.errno in (errno.EINVAL, errno.EPIPE)
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
@contextlib.contextmanager
|
|
60
|
+
def capture_logging() -> Generator[StringIO, None, None]:
|
|
61
|
+
"""Capture all pip logs in a buffer temporarily."""
|
|
62
|
+
# Patching sys.std(out|err) directly is not viable as the caller
|
|
63
|
+
# may want to emit non-logging output (e.g. a rich spinner). To
|
|
64
|
+
# avoid capturing that, temporarily patch the root logging handlers
|
|
65
|
+
# to use new rich consoles that write to a StringIO.
|
|
66
|
+
handlers = {}
|
|
67
|
+
for handler in logging.getLogger().handlers:
|
|
68
|
+
if isinstance(handler, RichPipStreamHandler):
|
|
69
|
+
# Also store the handler's original console so it can be
|
|
70
|
+
# restored on context exit.
|
|
71
|
+
handlers[handler] = handler.console
|
|
72
|
+
|
|
73
|
+
fake_stream = StreamWrapper.from_stream(sys.stdout)
|
|
74
|
+
if not handlers:
|
|
75
|
+
yield fake_stream
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
# HACK: grab no_color attribute from a random handler console since
|
|
79
|
+
# it's a global option anyway.
|
|
80
|
+
no_color = next(iter(handlers.values())).no_color
|
|
81
|
+
fake_console = PipConsole(file=fake_stream, no_color=no_color, soft_wrap=True)
|
|
82
|
+
try:
|
|
83
|
+
for handler in handlers:
|
|
84
|
+
handler.console = fake_console
|
|
85
|
+
yield fake_stream
|
|
86
|
+
finally:
|
|
87
|
+
for handler, original_console in handlers.items():
|
|
88
|
+
handler.console = original_console
|
|
89
|
+
|
|
90
|
+
|
|
59
91
|
@contextlib.contextmanager
|
|
60
92
|
def indent_log(num: int = 2) -> Generator[None, None, None]:
|
|
61
93
|
"""
|
pip/_internal/utils/misc.py
CHANGED
|
@@ -182,10 +182,12 @@ def rmtree_errorhandler(
|
|
|
182
182
|
def display_path(path: str) -> str:
|
|
183
183
|
"""Gives the display value for a given path, making it relative to cwd
|
|
184
184
|
if possible."""
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
try:
|
|
186
|
+
relative = Path(path).relative_to(Path.cwd())
|
|
187
|
+
except ValueError:
|
|
188
|
+
# If the path isn't relative to the CWD, leave it alone
|
|
189
|
+
return path
|
|
190
|
+
return os.path.join(".", relative)
|
|
189
191
|
|
|
190
192
|
|
|
191
193
|
def backup_dir(dir: str, ext: str = ".bak") -> str:
|
|
@@ -541,14 +543,18 @@ class HiddenText:
|
|
|
541
543
|
def __str__(self) -> str:
|
|
542
544
|
return self.redacted
|
|
543
545
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
if type(self) is
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
546
|
+
def __eq__(self, other: object) -> bool:
|
|
547
|
+
# Equality is particularly useful for testing.
|
|
548
|
+
if type(self) is type(other):
|
|
549
|
+
# The string being used for redaction doesn't also have to match,
|
|
550
|
+
# just the raw, original string.
|
|
551
|
+
return self.secret == cast(HiddenText, other).secret
|
|
552
|
+
return NotImplemented
|
|
553
|
+
|
|
554
|
+
# Disable hashing, since we have a custom __eq__ and don't need hash-ability
|
|
555
|
+
# (yet). The only required property of hashing is that objects which compare
|
|
556
|
+
# equal have the same hash value.
|
|
557
|
+
__hash__ = None # type: ignore[assignment]
|
|
552
558
|
|
|
553
559
|
|
|
554
560
|
def hide_value(value: str) -> HiddenText:
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from pip._vendor.packaging.pylock import (
|
|
5
|
+
Package,
|
|
6
|
+
PackageArchive,
|
|
7
|
+
PackageDirectory,
|
|
8
|
+
PackageSdist,
|
|
9
|
+
PackageVcs,
|
|
10
|
+
PackageWheel,
|
|
11
|
+
Pylock,
|
|
12
|
+
)
|
|
13
|
+
from pip._vendor.packaging.version import Version
|
|
14
|
+
|
|
15
|
+
from pip._internal.models.direct_url import ArchiveInfo, DirInfo, VcsInfo
|
|
16
|
+
from pip._internal.models.link import Link
|
|
17
|
+
from pip._internal.req.req_install import InstallRequirement
|
|
18
|
+
from pip._internal.utils.urls import url_to_path
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _pylock_package_from_install_requirement(
|
|
22
|
+
ireq: InstallRequirement, base_dir: Path
|
|
23
|
+
) -> Package:
|
|
24
|
+
base_dir = base_dir.resolve()
|
|
25
|
+
dist = ireq.get_dist()
|
|
26
|
+
download_info = ireq.download_info
|
|
27
|
+
assert download_info
|
|
28
|
+
package_version = None
|
|
29
|
+
package_vcs = None
|
|
30
|
+
package_directory = None
|
|
31
|
+
package_archive = None
|
|
32
|
+
package_sdist = None
|
|
33
|
+
package_wheels = None
|
|
34
|
+
if ireq.is_direct:
|
|
35
|
+
if isinstance(download_info.info, VcsInfo):
|
|
36
|
+
package_vcs = PackageVcs(
|
|
37
|
+
type=download_info.info.vcs,
|
|
38
|
+
url=download_info.url,
|
|
39
|
+
path=None,
|
|
40
|
+
requested_revision=download_info.info.requested_revision,
|
|
41
|
+
commit_id=download_info.info.commit_id,
|
|
42
|
+
subdirectory=download_info.subdirectory,
|
|
43
|
+
)
|
|
44
|
+
elif isinstance(download_info.info, DirInfo):
|
|
45
|
+
package_directory = PackageDirectory(
|
|
46
|
+
path=(
|
|
47
|
+
Path(url_to_path(download_info.url))
|
|
48
|
+
.resolve()
|
|
49
|
+
.relative_to(base_dir)
|
|
50
|
+
.as_posix()
|
|
51
|
+
),
|
|
52
|
+
editable=(
|
|
53
|
+
download_info.info.editable if download_info.info.editable else None
|
|
54
|
+
),
|
|
55
|
+
subdirectory=download_info.subdirectory,
|
|
56
|
+
)
|
|
57
|
+
elif isinstance(download_info.info, ArchiveInfo):
|
|
58
|
+
if not download_info.info.hashes:
|
|
59
|
+
raise NotImplementedError()
|
|
60
|
+
package_archive = PackageArchive(
|
|
61
|
+
url=download_info.url,
|
|
62
|
+
path=None,
|
|
63
|
+
hashes=download_info.info.hashes,
|
|
64
|
+
subdirectory=download_info.subdirectory,
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
# should never happen
|
|
68
|
+
raise NotImplementedError()
|
|
69
|
+
else:
|
|
70
|
+
package_version = dist.version
|
|
71
|
+
if isinstance(download_info.info, ArchiveInfo):
|
|
72
|
+
if not download_info.info.hashes:
|
|
73
|
+
raise NotImplementedError()
|
|
74
|
+
link = Link(download_info.url)
|
|
75
|
+
if link.is_wheel:
|
|
76
|
+
package_wheels = [
|
|
77
|
+
PackageWheel(
|
|
78
|
+
name=link.filename,
|
|
79
|
+
url=download_info.url,
|
|
80
|
+
hashes=download_info.info.hashes,
|
|
81
|
+
)
|
|
82
|
+
]
|
|
83
|
+
else:
|
|
84
|
+
package_sdist = PackageSdist(
|
|
85
|
+
name=link.filename,
|
|
86
|
+
url=download_info.url,
|
|
87
|
+
hashes=download_info.info.hashes,
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
# should never happen
|
|
91
|
+
raise NotImplementedError()
|
|
92
|
+
return Package(
|
|
93
|
+
name=dist.canonical_name,
|
|
94
|
+
version=package_version,
|
|
95
|
+
vcs=package_vcs,
|
|
96
|
+
directory=package_directory,
|
|
97
|
+
archive=package_archive,
|
|
98
|
+
sdist=package_sdist,
|
|
99
|
+
wheels=package_wheels,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def pylock_from_install_requirements(
|
|
104
|
+
install_requirements: Iterable[InstallRequirement], base_dir: Path
|
|
105
|
+
) -> Pylock:
|
|
106
|
+
return Pylock(
|
|
107
|
+
lock_version=Version("1.0"),
|
|
108
|
+
created_by="pip",
|
|
109
|
+
packages=sorted(
|
|
110
|
+
(
|
|
111
|
+
_pylock_package_from_install_requirement(ireq, base_dir)
|
|
112
|
+
for ireq in install_requirements
|
|
113
|
+
),
|
|
114
|
+
key=lambda p: p.name,
|
|
115
|
+
),
|
|
116
|
+
)
|