pip 25.3__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/build_env.py +194 -5
- pip/_internal/cli/base_command.py +11 -0
- pip/_internal/cli/cmdoptions.py +157 -0
- 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 +93 -33
- pip/_internal/cli/progress_bars.py +4 -2
- pip/_internal/cli/req_command.py +99 -23
- pip/_internal/commands/cache.py +24 -0
- pip/_internal/commands/completion.py +2 -1
- pip/_internal/commands/download.py +8 -4
- pip/_internal/commands/index.py +13 -6
- pip/_internal/commands/install.py +36 -29
- pip/_internal/commands/list.py +14 -16
- pip/_internal/commands/lock.py +16 -8
- pip/_internal/commands/wheel.py +8 -13
- pip/_internal/exceptions.py +76 -3
- pip/_internal/index/collector.py +2 -3
- pip/_internal/index/package_finder.py +84 -18
- pip/_internal/locations/__init__.py +1 -2
- pip/_internal/locations/_sysconfig.py +4 -1
- 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/network/auth.py +6 -2
- pip/_internal/network/download.py +4 -5
- pip/_internal/network/session.py +14 -10
- pip/_internal/operations/install/wheel.py +1 -2
- pip/_internal/operations/prepare.py +2 -3
- pip/_internal/req/constructors.py +3 -1
- pip/_internal/req/pep723.py +41 -0
- pip/_internal/req/req_file.py +10 -1
- pip/_internal/resolution/resolvelib/factory.py +12 -1
- pip/_internal/resolution/resolvelib/requirements.py +7 -3
- pip/_internal/self_outdated_check.py +6 -13
- pip/_internal/utils/datetime.py +18 -0
- pip/_internal/utils/filesystem.py +40 -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 +1 -1
- pip/_internal/vcs/versioncontrol.py +3 -1
- 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/__init__.py +1 -1
- pip/_vendor/certifi/cacert.pem +0 -332
- pip/_vendor/idna/LICENSE.md +1 -1
- 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/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/platformdirs/version.py +2 -2
- pip/_vendor/platformdirs/windows.py +7 -1
- pip/_vendor/vendor.txt +5 -5
- {pip-25.3.dist-info → pip-26.0.dist-info}/METADATA +2 -2
- {pip-25.3.dist-info → pip-26.0.dist-info}/RECORD +103 -100
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/AUTHORS.txt +18 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/idna/LICENSE.md +1 -1
- pip/_internal/models/pylock.py +0 -188
- {pip-25.3.dist-info → pip-26.0.dist-info}/WHEEL +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/entry_points.txt +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/LICENSE.txt +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/certifi/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/dependency_groups/LICENSE.txt +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distlib/LICENSE.txt +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distro/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/msgpack/COPYING +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.BSD +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pkg_resources/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/platformdirs/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pygments/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/requests/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/resolvelib/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/rich/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli_w/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/truststore/LICENSE +0 -0
- {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/urllib3/LICENSE.txt +0 -0
pip/_internal/network/session.py
CHANGED
|
@@ -50,8 +50,8 @@ from pip._internal.utils.urls import url_to_path
|
|
|
50
50
|
if TYPE_CHECKING:
|
|
51
51
|
from ssl import SSLContext
|
|
52
52
|
|
|
53
|
+
from pip._vendor.urllib3 import ProxyManager
|
|
53
54
|
from pip._vendor.urllib3.poolmanager import PoolManager
|
|
54
|
-
from pip._vendor.urllib3.proxymanager import ProxyManager
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
logger = logging.getLogger(__name__)
|
|
@@ -212,11 +212,12 @@ class LocalFSAdapter(BaseAdapter):
|
|
|
212
212
|
self,
|
|
213
213
|
request: PreparedRequest,
|
|
214
214
|
stream: bool = False,
|
|
215
|
-
timeout: float | tuple[float, float] | None = None,
|
|
215
|
+
timeout: float | tuple[float, float] | tuple[float, None] | None = None,
|
|
216
216
|
verify: bool | str = True,
|
|
217
|
-
cert: str | tuple[str, str] | None = None,
|
|
217
|
+
cert: bytes | str | tuple[bytes | str, bytes | str] | None = None,
|
|
218
218
|
proxies: Mapping[str, str] | None = None,
|
|
219
219
|
) -> Response:
|
|
220
|
+
assert request.url is not None
|
|
220
221
|
pathname = url_to_path(request.url)
|
|
221
222
|
|
|
222
223
|
resp = Response()
|
|
@@ -237,13 +238,13 @@ class LocalFSAdapter(BaseAdapter):
|
|
|
237
238
|
resp.headers = CaseInsensitiveDict(
|
|
238
239
|
{
|
|
239
240
|
"Content-Type": content_type,
|
|
240
|
-
"Content-Length": stats.st_size,
|
|
241
|
+
"Content-Length": str(stats.st_size),
|
|
241
242
|
"Last-Modified": modified,
|
|
242
243
|
}
|
|
243
244
|
)
|
|
244
245
|
|
|
245
246
|
resp.raw = open(pathname, "rb")
|
|
246
|
-
resp.close = resp.raw.close
|
|
247
|
+
resp.close = resp.raw.close # type: ignore[method-assign]
|
|
247
248
|
|
|
248
249
|
return resp
|
|
249
250
|
|
|
@@ -277,7 +278,7 @@ class _SSLContextAdapterMixin:
|
|
|
277
278
|
) -> PoolManager:
|
|
278
279
|
if self._ssl_context is not None:
|
|
279
280
|
pool_kwargs.setdefault("ssl_context", self._ssl_context)
|
|
280
|
-
return super().init_poolmanager( # type: ignore[misc]
|
|
281
|
+
return super().init_poolmanager( # type: ignore[misc, no-any-return]
|
|
281
282
|
connections=connections,
|
|
282
283
|
maxsize=maxsize,
|
|
283
284
|
block=block,
|
|
@@ -289,7 +290,7 @@ class _SSLContextAdapterMixin:
|
|
|
289
290
|
# context here too. https://github.com/pypa/pip/issues/13288
|
|
290
291
|
if self._ssl_context is not None:
|
|
291
292
|
proxy_kwargs.setdefault("ssl_context", self._ssl_context)
|
|
292
|
-
return super().proxy_manager_for(proxy, **proxy_kwargs) # type: ignore[misc]
|
|
293
|
+
return super().proxy_manager_for(proxy, **proxy_kwargs) # type: ignore[misc, no-any-return]
|
|
293
294
|
|
|
294
295
|
|
|
295
296
|
class HTTPAdapter(_SSLContextAdapterMixin, _BaseHTTPAdapter):
|
|
@@ -329,6 +330,7 @@ class PipSession(requests.Session):
|
|
|
329
330
|
self,
|
|
330
331
|
*args: Any,
|
|
331
332
|
retries: int = 0,
|
|
333
|
+
resume_retries: int = 0,
|
|
332
334
|
cache: str | None = None,
|
|
333
335
|
trusted_hosts: Sequence[str] = (),
|
|
334
336
|
index_urls: list[str] | None = None,
|
|
@@ -350,7 +352,7 @@ class PipSession(requests.Session):
|
|
|
350
352
|
self.headers["User-Agent"] = user_agent()
|
|
351
353
|
|
|
352
354
|
# Attach our Authentication handler to the session
|
|
353
|
-
self.auth = MultiDomainBasicAuth(index_urls=index_urls)
|
|
355
|
+
self.auth: MultiDomainBasicAuth = MultiDomainBasicAuth(index_urls=index_urls)
|
|
354
356
|
|
|
355
357
|
# Create our urllib3.Retry instance which will allow us to customize
|
|
356
358
|
# how we handle retries.
|
|
@@ -370,6 +372,7 @@ class PipSession(requests.Session):
|
|
|
370
372
|
# order to prevent hammering the service.
|
|
371
373
|
backoff_factor=0.25,
|
|
372
374
|
) # type: ignore
|
|
375
|
+
self.resume_retries = resume_retries
|
|
373
376
|
|
|
374
377
|
# Our Insecure HTTPAdapter disables HTTPS validation. It does not
|
|
375
378
|
# support caching so we'll use it for all http:// URLs.
|
|
@@ -383,8 +386,9 @@ class PipSession(requests.Session):
|
|
|
383
386
|
# we can't validate the response of an insecurely/untrusted fetched
|
|
384
387
|
# origin, and we don't want someone to be able to poison the cache and
|
|
385
388
|
# require manual eviction from the cache to fix it.
|
|
389
|
+
self._trusted_host_adapter: InsecureCacheControlAdapter | InsecureHTTPAdapter
|
|
386
390
|
if cache:
|
|
387
|
-
secure_adapter = CacheControlAdapter(
|
|
391
|
+
secure_adapter: _BaseHTTPAdapter = CacheControlAdapter(
|
|
388
392
|
cache=SafeFileCache(cache),
|
|
389
393
|
max_retries=retries,
|
|
390
394
|
ssl_context=ssl_context,
|
|
@@ -518,7 +522,7 @@ class PipSession(requests.Session):
|
|
|
518
522
|
|
|
519
523
|
return False
|
|
520
524
|
|
|
521
|
-
def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response:
|
|
525
|
+
def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response: # type: ignore[override]
|
|
522
526
|
# Allow setting a default timeout on a session
|
|
523
527
|
kwargs.setdefault("timeout", self.timeout)
|
|
524
528
|
# Allow setting a default proxies on a session
|
|
@@ -411,8 +411,7 @@ class PipScriptMaker(ScriptMaker):
|
|
|
411
411
|
import sys
|
|
412
412
|
from %(module)s import %(import_name)s
|
|
413
413
|
if __name__ == '__main__':
|
|
414
|
-
|
|
415
|
-
sys.argv[0] = sys.argv[0][:-4]
|
|
414
|
+
sys.argv[0] = sys.argv[0].removesuffix('.exe')
|
|
416
415
|
sys.exit(%(func)s())
|
|
417
416
|
"""
|
|
418
417
|
)
|
|
@@ -225,7 +225,7 @@ def _check_download_dir(
|
|
|
225
225
|
class RequirementPreparer:
|
|
226
226
|
"""Prepares a Requirement"""
|
|
227
227
|
|
|
228
|
-
def __init__(
|
|
228
|
+
def __init__(
|
|
229
229
|
self,
|
|
230
230
|
*,
|
|
231
231
|
build_dir: str,
|
|
@@ -243,7 +243,6 @@ class RequirementPreparer:
|
|
|
243
243
|
lazy_wheel: bool,
|
|
244
244
|
verbosity: int,
|
|
245
245
|
legacy_resolver: bool,
|
|
246
|
-
resume_retries: int,
|
|
247
246
|
) -> None:
|
|
248
247
|
super().__init__()
|
|
249
248
|
|
|
@@ -251,7 +250,7 @@ class RequirementPreparer:
|
|
|
251
250
|
self.build_dir = build_dir
|
|
252
251
|
self.build_tracker = build_tracker
|
|
253
252
|
self._session = session
|
|
254
|
-
self._download = Downloader(session, progress_bar
|
|
253
|
+
self._download = Downloader(session, progress_bar)
|
|
255
254
|
self.finder = finder
|
|
256
255
|
|
|
257
256
|
# Where still-packed archives should be written to. If None, they are
|
|
@@ -258,8 +258,10 @@ def install_req_from_editable(
|
|
|
258
258
|
permit_editable_wheels: bool = False,
|
|
259
259
|
config_settings: dict[str, str | list[str]] | None = None,
|
|
260
260
|
) -> InstallRequirement:
|
|
261
|
-
|
|
261
|
+
if constraint:
|
|
262
|
+
raise InstallationError("Editable requirements are not allowed as constraints")
|
|
262
263
|
|
|
264
|
+
parts = parse_req_from_editable(editable_req)
|
|
263
265
|
return InstallRequirement(
|
|
264
266
|
parts.requirement,
|
|
265
267
|
comes_from=comes_from,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pip._internal.utils.compat import tomllib
|
|
5
|
+
|
|
6
|
+
REGEX = r"(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PEP723Exception(ValueError):
|
|
10
|
+
"""Raised to indicate a problem when parsing PEP 723 metadata from a script"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, msg: str) -> None:
|
|
13
|
+
self.msg = msg
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def pep723_metadata(scriptfile: str) -> dict[str, Any]:
|
|
17
|
+
with open(scriptfile) as f:
|
|
18
|
+
script = f.read()
|
|
19
|
+
|
|
20
|
+
name = "script"
|
|
21
|
+
matches = list(
|
|
22
|
+
filter(lambda m: m.group("type") == name, re.finditer(REGEX, script))
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if len(matches) > 1:
|
|
26
|
+
raise PEP723Exception(f"Multiple {name!r} blocks found in {scriptfile!r}")
|
|
27
|
+
elif len(matches) == 1:
|
|
28
|
+
content = "".join(
|
|
29
|
+
line[2:] if line.startswith("# ") else line[1:]
|
|
30
|
+
for line in matches[0].group("content").splitlines(keepends=True)
|
|
31
|
+
)
|
|
32
|
+
try:
|
|
33
|
+
metadata = tomllib.loads(content)
|
|
34
|
+
except Exception as exc:
|
|
35
|
+
raise PEP723Exception(f"Failed to parse TOML in {scriptfile!r}") from exc
|
|
36
|
+
else:
|
|
37
|
+
raise PEP723Exception(
|
|
38
|
+
f"File does not contain {name!r} metadata: {scriptfile!r}"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return metadata
|
pip/_internal/req/req_file.py
CHANGED
|
@@ -25,6 +25,7 @@ from typing import (
|
|
|
25
25
|
|
|
26
26
|
from pip._internal.cli import cmdoptions
|
|
27
27
|
from pip._internal.exceptions import InstallationError, RequirementsFileParseError
|
|
28
|
+
from pip._internal.models.release_control import ReleaseControl
|
|
28
29
|
from pip._internal.models.search_scope import SearchScope
|
|
29
30
|
|
|
30
31
|
if TYPE_CHECKING:
|
|
@@ -59,6 +60,8 @@ SUPPORTED_OPTIONS: list[Callable[..., optparse.Option]] = [
|
|
|
59
60
|
cmdoptions.prefer_binary,
|
|
60
61
|
cmdoptions.require_hashes,
|
|
61
62
|
cmdoptions.pre,
|
|
63
|
+
cmdoptions.all_releases,
|
|
64
|
+
cmdoptions.only_final,
|
|
62
65
|
cmdoptions.trusted_host,
|
|
63
66
|
cmdoptions.use_new_feature,
|
|
64
67
|
]
|
|
@@ -273,8 +276,14 @@ def handle_option_line(
|
|
|
273
276
|
)
|
|
274
277
|
finder.search_scope = search_scope
|
|
275
278
|
|
|
279
|
+
# Transform --pre into --all-releases :all:
|
|
276
280
|
if opts.pre:
|
|
277
|
-
|
|
281
|
+
if not opts.release_control:
|
|
282
|
+
opts.release_control = ReleaseControl()
|
|
283
|
+
opts.release_control.all_releases.add(":all:")
|
|
284
|
+
|
|
285
|
+
if opts.release_control:
|
|
286
|
+
finder.set_release_control(opts.release_control)
|
|
278
287
|
|
|
279
288
|
if opts.prefer_binary:
|
|
280
289
|
finder.set_prefer_binary()
|
|
@@ -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
|
)
|
|
@@ -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"
|
|
@@ -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,9 +20,11 @@ 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,
|
|
@@ -50,18 +52,9 @@ def _get_statefile_name(key: str) -> str:
|
|
|
50
52
|
return name
|
|
51
53
|
|
|
52
54
|
|
|
53
|
-
def _convert_date(isodate: str) -> datetime.datetime:
|
|
54
|
-
"""Convert an ISO format string to a date.
|
|
55
|
-
|
|
56
|
-
Handles the format 2020-01-22T14:24:01Z (trailing Z)
|
|
57
|
-
which is not supported by older versions of fromisoformat.
|
|
58
|
-
"""
|
|
59
|
-
return datetime.datetime.fromisoformat(isodate.replace("Z", "+00:00"))
|
|
60
|
-
|
|
61
|
-
|
|
62
55
|
class SelfCheckState:
|
|
63
56
|
def __init__(self, cache_dir: str) -> None:
|
|
64
|
-
self._state: dict[str,
|
|
57
|
+
self._state: dict[str, str] = {}
|
|
65
58
|
self._statefile_path = None
|
|
66
59
|
|
|
67
60
|
# Try to load the existing state
|
|
@@ -93,7 +86,7 @@ class SelfCheckState:
|
|
|
93
86
|
return None
|
|
94
87
|
|
|
95
88
|
# Determine if we need to refresh the state
|
|
96
|
-
last_check =
|
|
89
|
+
last_check = parse_iso_datetime(self._state["last_check"])
|
|
97
90
|
time_since_last_check = current_time - last_check
|
|
98
91
|
if time_since_last_check > _WEEK:
|
|
99
92
|
return None
|
|
@@ -187,7 +180,7 @@ def _get_current_remote_pip_version(
|
|
|
187
180
|
# yanked version.
|
|
188
181
|
selection_prefs = SelectionPreferences(
|
|
189
182
|
allow_yanked=False,
|
|
190
|
-
|
|
183
|
+
release_control=ReleaseControl(only_final={"pip"}),
|
|
191
184
|
)
|
|
192
185
|
|
|
193
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
|
|
@@ -162,3 +163,41 @@ def copy_directory_permissions(directory: str, target_file: BinaryIO) -> None:
|
|
|
162
163
|
os.chmod(target_file.fileno(), mode)
|
|
163
164
|
elif os.chmod in os.supports_follow_symlinks:
|
|
164
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
|
+
)
|
pip/_internal/utils/unpacking.py
CHANGED
|
@@ -83,7 +83,7 @@ def is_within_directory(directory: str, target: str) -> bool:
|
|
|
83
83
|
abs_directory = os.path.abspath(directory)
|
|
84
84
|
abs_target = os.path.abspath(target)
|
|
85
85
|
|
|
86
|
-
prefix = os.path.
|
|
86
|
+
prefix = os.path.commonpath([abs_directory, abs_target])
|
|
87
87
|
return prefix == abs_directory
|
|
88
88
|
|
|
89
89
|
|
|
@@ -62,8 +62,9 @@ def make_vcs_requirement_url(
|
|
|
62
62
|
repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+").
|
|
63
63
|
project_name: the (unescaped) project name.
|
|
64
64
|
"""
|
|
65
|
+
quoted_rev = urllib.parse.quote(rev, "/")
|
|
65
66
|
egg_project_name = project_name.replace("-", "_")
|
|
66
|
-
req = f"{repo_url}@{
|
|
67
|
+
req = f"{repo_url}@{quoted_rev}#egg={egg_project_name}"
|
|
67
68
|
if subdir:
|
|
68
69
|
req += f"&subdirectory={subdir}"
|
|
69
70
|
|
|
@@ -397,6 +398,7 @@ class VersionControl:
|
|
|
397
398
|
"which is not supported. Include a revision after @ "
|
|
398
399
|
"or remove @ from the URL."
|
|
399
400
|
)
|
|
401
|
+
rev = urllib.parse.unquote(rev)
|
|
400
402
|
url = urllib.parse.urlunsplit((scheme, netloc, path, query, ""))
|
|
401
403
|
return url, rev, user_pass
|
|
402
404
|
|