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.
Files changed (167) hide show
  1. pip/__init__.py +1 -1
  2. pip/_internal/__init__.py +0 -0
  3. pip/_internal/build_env.py +265 -8
  4. pip/_internal/cache.py +1 -1
  5. pip/_internal/cli/base_command.py +11 -0
  6. pip/_internal/cli/cmdoptions.py +200 -71
  7. pip/_internal/cli/index_command.py +20 -0
  8. pip/_internal/cli/main.py +11 -6
  9. pip/_internal/cli/main_parser.py +3 -1
  10. pip/_internal/cli/parser.py +96 -36
  11. pip/_internal/cli/progress_bars.py +4 -2
  12. pip/_internal/cli/req_command.py +126 -30
  13. pip/_internal/commands/cache.py +24 -0
  14. pip/_internal/commands/completion.py +2 -1
  15. pip/_internal/commands/download.py +12 -11
  16. pip/_internal/commands/index.py +13 -6
  17. pip/_internal/commands/install.py +55 -43
  18. pip/_internal/commands/list.py +14 -16
  19. pip/_internal/commands/lock.py +19 -14
  20. pip/_internal/commands/wheel.py +13 -23
  21. pip/_internal/configuration.py +1 -2
  22. pip/_internal/distributions/sdist.py +13 -14
  23. pip/_internal/exceptions.py +96 -6
  24. pip/_internal/index/collector.py +2 -3
  25. pip/_internal/index/package_finder.py +87 -21
  26. pip/_internal/locations/__init__.py +1 -2
  27. pip/_internal/locations/_sysconfig.py +4 -1
  28. pip/_internal/metadata/__init__.py +7 -2
  29. pip/_internal/metadata/importlib/_dists.py +8 -2
  30. pip/_internal/models/link.py +18 -14
  31. pip/_internal/models/release_control.py +92 -0
  32. pip/_internal/models/selection_prefs.py +6 -3
  33. pip/_internal/models/wheel.py +5 -66
  34. pip/_internal/network/auth.py +6 -2
  35. pip/_internal/network/cache.py +6 -11
  36. pip/_internal/network/download.py +4 -5
  37. pip/_internal/network/lazy_wheel.py +5 -3
  38. pip/_internal/network/session.py +14 -10
  39. pip/_internal/operations/build/wheel.py +4 -4
  40. pip/_internal/operations/build/wheel_editable.py +4 -4
  41. pip/_internal/operations/install/wheel.py +1 -2
  42. pip/_internal/operations/prepare.py +9 -4
  43. pip/_internal/pyproject.py +2 -61
  44. pip/_internal/req/__init__.py +1 -3
  45. pip/_internal/req/constructors.py +45 -39
  46. pip/_internal/req/pep723.py +41 -0
  47. pip/_internal/req/req_file.py +10 -2
  48. pip/_internal/req/req_install.py +32 -141
  49. pip/_internal/resolution/resolvelib/candidates.py +20 -11
  50. pip/_internal/resolution/resolvelib/factory.py +43 -1
  51. pip/_internal/resolution/resolvelib/provider.py +9 -0
  52. pip/_internal/resolution/resolvelib/reporter.py +21 -8
  53. pip/_internal/resolution/resolvelib/requirements.py +7 -3
  54. pip/_internal/resolution/resolvelib/resolver.py +2 -6
  55. pip/_internal/self_outdated_check.py +17 -16
  56. pip/_internal/utils/datetime.py +18 -0
  57. pip/_internal/utils/filesystem.py +52 -1
  58. pip/_internal/utils/logging.py +34 -2
  59. pip/_internal/utils/misc.py +18 -12
  60. pip/_internal/utils/pylock.py +116 -0
  61. pip/_internal/utils/unpacking.py +26 -1
  62. pip/_internal/vcs/versioncontrol.py +3 -1
  63. pip/_internal/wheel_builder.py +23 -96
  64. pip/_vendor/README.rst +180 -0
  65. pip/_vendor/cachecontrol/LICENSE.txt +13 -0
  66. pip/_vendor/cachecontrol/__init__.py +6 -3
  67. pip/_vendor/cachecontrol/adapter.py +0 -1
  68. pip/_vendor/cachecontrol/controller.py +1 -1
  69. pip/_vendor/cachecontrol/filewrapper.py +3 -1
  70. pip/_vendor/certifi/LICENSE +20 -0
  71. pip/_vendor/certifi/__init__.py +1 -1
  72. pip/_vendor/certifi/cacert.pem +62 -372
  73. pip/_vendor/dependency_groups/LICENSE.txt +9 -0
  74. pip/_vendor/distlib/LICENSE.txt +284 -0
  75. pip/_vendor/distro/LICENSE +202 -0
  76. pip/_vendor/idna/LICENSE.md +31 -0
  77. pip/_vendor/idna/codec.py +1 -1
  78. pip/_vendor/idna/core.py +1 -1
  79. pip/_vendor/idna/idnadata.py +72 -6
  80. pip/_vendor/idna/package_data.py +1 -1
  81. pip/_vendor/idna/uts46data.py +891 -731
  82. pip/_vendor/msgpack/COPYING +14 -0
  83. pip/_vendor/msgpack/__init__.py +2 -2
  84. pip/_vendor/packaging/LICENSE +3 -0
  85. pip/_vendor/packaging/LICENSE.APACHE +177 -0
  86. pip/_vendor/packaging/LICENSE.BSD +23 -0
  87. pip/_vendor/packaging/__init__.py +1 -1
  88. pip/_vendor/packaging/_elffile.py +0 -1
  89. pip/_vendor/packaging/_manylinux.py +36 -36
  90. pip/_vendor/packaging/_musllinux.py +1 -1
  91. pip/_vendor/packaging/_parser.py +22 -10
  92. pip/_vendor/packaging/_structures.py +8 -0
  93. pip/_vendor/packaging/_tokenizer.py +23 -25
  94. pip/_vendor/packaging/licenses/__init__.py +13 -11
  95. pip/_vendor/packaging/licenses/_spdx.py +41 -1
  96. pip/_vendor/packaging/markers.py +64 -38
  97. pip/_vendor/packaging/metadata.py +143 -27
  98. pip/_vendor/packaging/pylock.py +635 -0
  99. pip/_vendor/packaging/requirements.py +5 -10
  100. pip/_vendor/packaging/specifiers.py +219 -170
  101. pip/_vendor/packaging/tags.py +15 -20
  102. pip/_vendor/packaging/utils.py +19 -24
  103. pip/_vendor/packaging/version.py +315 -105
  104. pip/_vendor/pkg_resources/LICENSE +17 -0
  105. pip/_vendor/platformdirs/LICENSE +21 -0
  106. pip/_vendor/platformdirs/api.py +1 -1
  107. pip/_vendor/platformdirs/macos.py +10 -8
  108. pip/_vendor/platformdirs/version.py +16 -3
  109. pip/_vendor/platformdirs/windows.py +7 -1
  110. pip/_vendor/pygments/LICENSE +25 -0
  111. pip/_vendor/pyproject_hooks/LICENSE +21 -0
  112. pip/_vendor/requests/LICENSE +175 -0
  113. pip/_vendor/requests/__version__.py +2 -2
  114. pip/_vendor/requests/adapters.py +17 -40
  115. pip/_vendor/requests/sessions.py +1 -1
  116. pip/_vendor/resolvelib/LICENSE +13 -0
  117. pip/_vendor/resolvelib/__init__.py +1 -1
  118. pip/_vendor/resolvelib/resolvers/abstract.py +3 -3
  119. pip/_vendor/resolvelib/resolvers/resolution.py +5 -0
  120. pip/_vendor/rich/LICENSE +19 -0
  121. pip/_vendor/rich/style.py +7 -11
  122. pip/_vendor/tomli/LICENSE +21 -0
  123. pip/_vendor/tomli/__init__.py +1 -1
  124. pip/_vendor/tomli/_parser.py +28 -21
  125. pip/_vendor/tomli/_re.py +8 -5
  126. pip/_vendor/tomli_w/LICENSE +21 -0
  127. pip/_vendor/truststore/LICENSE +21 -0
  128. pip/_vendor/truststore/__init__.py +1 -1
  129. pip/_vendor/truststore/_api.py +14 -6
  130. pip/_vendor/truststore/_openssl.py +3 -1
  131. pip/_vendor/urllib3/LICENSE.txt +21 -0
  132. pip/_vendor/vendor.txt +11 -11
  133. {pip-25.2.dist-info → pip-26.0.dist-info}/METADATA +10 -11
  134. {pip-25.2.dist-info → pip-26.0.dist-info}/RECORD +158 -139
  135. {pip-25.2.dist-info → pip-26.0.dist-info}/WHEEL +1 -2
  136. pip-26.0.dist-info/entry_points.txt +4 -0
  137. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/AUTHORS.txt +27 -0
  138. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/idna/LICENSE.md +1 -1
  139. pip/_internal/models/pylock.py +0 -188
  140. pip/_internal/operations/build/metadata_legacy.py +0 -73
  141. pip/_internal/operations/build/wheel_legacy.py +0 -119
  142. pip/_internal/operations/install/editable_legacy.py +0 -48
  143. pip/_internal/utils/setuptools_build.py +0 -149
  144. pip-25.2.dist-info/entry_points.txt +0 -3
  145. pip-25.2.dist-info/licenses/src/pip/_vendor/tomli/LICENSE-HEADER +0 -3
  146. pip-25.2.dist-info/top_level.txt +0 -1
  147. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/LICENSE.txt +0 -0
  148. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +0 -0
  149. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/certifi/LICENSE +0 -0
  150. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/dependency_groups/LICENSE.txt +0 -0
  151. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distlib/LICENSE.txt +0 -0
  152. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distro/LICENSE +0 -0
  153. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/msgpack/COPYING +0 -0
  154. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE +0 -0
  155. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +0 -0
  156. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.BSD +0 -0
  157. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pkg_resources/LICENSE +0 -0
  158. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/platformdirs/LICENSE +0 -0
  159. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pygments/LICENSE +0 -0
  160. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +0 -0
  161. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/requests/LICENSE +0 -0
  162. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/resolvelib/LICENSE +0 -0
  163. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/rich/LICENSE +0 -0
  164. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli/LICENSE +0 -0
  165. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli_w/LICENSE +0 -0
  166. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/truststore/LICENSE +0 -0
  167. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/urllib3/LICENSE.txt +0 -0
@@ -9,12 +9,59 @@
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- import itertools
13
12
  import re
14
- from typing import Any, Callable, NamedTuple, SupportsInt, Tuple, Union
13
+ import sys
14
+ import typing
15
+ from typing import (
16
+ Any,
17
+ Callable,
18
+ Literal,
19
+ NamedTuple,
20
+ SupportsInt,
21
+ Tuple,
22
+ TypedDict,
23
+ Union,
24
+ )
15
25
 
16
26
  from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
17
27
 
28
+ if typing.TYPE_CHECKING:
29
+ from typing_extensions import Self, Unpack
30
+
31
+ if sys.version_info >= (3, 13): # pragma: no cover
32
+ from warnings import deprecated as _deprecated
33
+ elif typing.TYPE_CHECKING:
34
+ from typing_extensions import deprecated as _deprecated
35
+ else: # pragma: no cover
36
+ import functools
37
+ import warnings
38
+
39
+ def _deprecated(message: str) -> object:
40
+ def decorator(func: object) -> object:
41
+ @functools.wraps(func)
42
+ def wrapper(*args: object, **kwargs: object) -> object:
43
+ warnings.warn(
44
+ message,
45
+ category=DeprecationWarning,
46
+ stacklevel=2,
47
+ )
48
+ return func(*args, **kwargs)
49
+
50
+ return wrapper
51
+
52
+ return decorator
53
+
54
+
55
+ _LETTER_NORMALIZATION = {
56
+ "alpha": "a",
57
+ "beta": "b",
58
+ "c": "rc",
59
+ "pre": "rc",
60
+ "preview": "rc",
61
+ "rev": "post",
62
+ "r": "post",
63
+ }
64
+
18
65
  __all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"]
19
66
 
20
67
  LocalType = Tuple[Union[int, str], ...]
@@ -35,13 +82,13 @@ CmpKey = Tuple[
35
82
  VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
36
83
 
37
84
 
38
- class _Version(NamedTuple):
39
- epoch: int
40
- release: tuple[int, ...]
41
- dev: tuple[str, int] | None
42
- pre: tuple[str, int] | None
43
- post: tuple[str, int] | None
44
- local: LocalType | None
85
+ class _VersionReplace(TypedDict, total=False):
86
+ epoch: int | None
87
+ release: tuple[int, ...] | None
88
+ pre: tuple[Literal["a", "b", "rc"], int] | None
89
+ post: int | None
90
+ dev: int | None
91
+ local: str | None
45
92
 
46
93
 
47
94
  def parse(version: str) -> Version:
@@ -67,7 +114,15 @@ class InvalidVersion(ValueError):
67
114
 
68
115
 
69
116
  class _BaseVersion:
70
- _key: tuple[Any, ...]
117
+ __slots__ = ()
118
+
119
+ # This can also be a normal member (see the packaging_legacy package);
120
+ # we are just requiring it to be readable. Actually defining a property
121
+ # has runtime effect on subclasses, so it's typing only.
122
+ if typing.TYPE_CHECKING:
123
+
124
+ @property
125
+ def _key(self) -> tuple[Any, ...]: ...
71
126
 
72
127
  def __hash__(self) -> int:
73
128
  return hash(self._key)
@@ -114,38 +169,56 @@ class _BaseVersion:
114
169
 
115
170
  # Deliberately not anchored to the start and end of the string, to make it
116
171
  # easier for 3rd party code to reuse
172
+
173
+ # Note that ++ doesn't behave identically on CPython and PyPy, so not using it here
117
174
  _VERSION_PATTERN = r"""
118
- v?
175
+ v?+ # optional leading v
119
176
  (?:
120
- (?:(?P<epoch>[0-9]+)!)? # epoch
121
- (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
177
+ (?:(?P<epoch>[0-9]+)!)?+ # epoch
178
+ (?P<release>[0-9]+(?:\.[0-9]+)*+) # release segment
122
179
  (?P<pre> # pre-release
123
- [-_\.]?
180
+ [._-]?+
124
181
  (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
125
- [-_\.]?
182
+ [._-]?+
126
183
  (?P<pre_n>[0-9]+)?
127
- )?
184
+ )?+
128
185
  (?P<post> # post release
129
186
  (?:-(?P<post_n1>[0-9]+))
130
187
  |
131
188
  (?:
132
- [-_\.]?
189
+ [._-]?
133
190
  (?P<post_l>post|rev|r)
134
- [-_\.]?
191
+ [._-]?
135
192
  (?P<post_n2>[0-9]+)?
136
193
  )
137
- )?
194
+ )?+
138
195
  (?P<dev> # dev release
139
- [-_\.]?
196
+ [._-]?+
140
197
  (?P<dev_l>dev)
141
- [-_\.]?
198
+ [._-]?+
142
199
  (?P<dev_n>[0-9]+)?
143
- )?
200
+ )?+
144
201
  )
145
- (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
202
+ (?:\+
203
+ (?P<local> # local version
204
+ [a-z0-9]+
205
+ (?:[._-][a-z0-9]+)*+
206
+ )
207
+ )?+
146
208
  """
147
209
 
148
- VERSION_PATTERN = _VERSION_PATTERN
210
+ _VERSION_PATTERN_OLD = _VERSION_PATTERN.replace("*+", "*").replace("?+", "?")
211
+
212
+ # Possessive qualifiers were added in Python 3.11.
213
+ # CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795
214
+ # Older PyPy also had a bug.
215
+ VERSION_PATTERN = (
216
+ _VERSION_PATTERN_OLD
217
+ if (sys.implementation.name == "cpython" and sys.version_info < (3, 11, 5))
218
+ or (sys.implementation.name == "pypy" and sys.version_info < (3, 11, 13))
219
+ or sys.version_info < (3, 11)
220
+ else _VERSION_PATTERN
221
+ )
149
222
  """
150
223
  A string containing the regular expression used to match a valid version.
151
224
 
@@ -158,6 +231,82 @@ flags set.
158
231
  """
159
232
 
160
233
 
234
+ # Validation pattern for local version in replace()
235
+ _LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE)
236
+
237
+
238
+ def _validate_epoch(value: object, /) -> int:
239
+ epoch = value or 0
240
+ if isinstance(epoch, int) and epoch >= 0:
241
+ return epoch
242
+ msg = f"epoch must be non-negative integer, got {epoch}"
243
+ raise InvalidVersion(msg)
244
+
245
+
246
+ def _validate_release(value: object, /) -> tuple[int, ...]:
247
+ release = (0,) if value is None else value
248
+ if (
249
+ isinstance(release, tuple)
250
+ and len(release) > 0
251
+ and all(isinstance(i, int) and i >= 0 for i in release)
252
+ ):
253
+ return release
254
+ msg = f"release must be a non-empty tuple of non-negative integers, got {release}"
255
+ raise InvalidVersion(msg)
256
+
257
+
258
+ def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None:
259
+ if value is None:
260
+ return value
261
+ if (
262
+ isinstance(value, tuple)
263
+ and len(value) == 2
264
+ and value[0] in ("a", "b", "rc")
265
+ and isinstance(value[1], int)
266
+ and value[1] >= 0
267
+ ):
268
+ return value
269
+ msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}"
270
+ raise InvalidVersion(msg)
271
+
272
+
273
+ def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:
274
+ if value is None:
275
+ return value
276
+ if isinstance(value, int) and value >= 0:
277
+ return ("post", value)
278
+ msg = f"post must be non-negative integer, got {value}"
279
+ raise InvalidVersion(msg)
280
+
281
+
282
+ def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:
283
+ if value is None:
284
+ return value
285
+ if isinstance(value, int) and value >= 0:
286
+ return ("dev", value)
287
+ msg = f"dev must be non-negative integer, got {value}"
288
+ raise InvalidVersion(msg)
289
+
290
+
291
+ def _validate_local(value: object, /) -> LocalType | None:
292
+ if value is None:
293
+ return value
294
+ if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):
295
+ return _parse_local_version(value)
296
+ msg = f"local must be a valid version string, got {value!r}"
297
+ raise InvalidVersion(msg)
298
+
299
+
300
+ # Backward compatibility for internals before 26.0. Do not use.
301
+ class _Version(NamedTuple):
302
+ epoch: int
303
+ release: tuple[int, ...]
304
+ dev: tuple[str, int] | None
305
+ pre: tuple[str, int] | None
306
+ post: tuple[str, int] | None
307
+ local: LocalType | None
308
+
309
+
161
310
  class Version(_BaseVersion):
162
311
  """This class abstracts handling of a project's versions.
163
312
 
@@ -182,8 +331,19 @@ class Version(_BaseVersion):
182
331
  True
183
332
  """
184
333
 
185
- _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
186
- _key: CmpKey
334
+ __slots__ = ("_dev", "_epoch", "_key_cache", "_local", "_post", "_pre", "_release")
335
+ __match_args__ = ("_str",)
336
+
337
+ _regex = re.compile(r"\s*" + VERSION_PATTERN + r"\s*", re.VERBOSE | re.IGNORECASE)
338
+
339
+ _epoch: int
340
+ _release: tuple[int, ...]
341
+ _dev: tuple[str, int] | None
342
+ _pre: tuple[str, int] | None
343
+ _post: tuple[str, int] | None
344
+ _local: LocalType | None
345
+
346
+ _key_cache: CmpKey | None
187
347
 
188
348
  def __init__(self, version: str) -> None:
189
349
  """Initialize a Version object.
@@ -195,34 +355,86 @@ class Version(_BaseVersion):
195
355
  If the ``version`` does not conform to PEP 440 in any way then this
196
356
  exception will be raised.
197
357
  """
198
-
199
358
  # Validate the version and parse it into pieces
200
- match = self._regex.search(version)
359
+ match = self._regex.fullmatch(version)
201
360
  if not match:
202
361
  raise InvalidVersion(f"Invalid version: {version!r}")
203
-
204
- # Store the parsed out pieces of the version
205
- self._version = _Version(
206
- epoch=int(match.group("epoch")) if match.group("epoch") else 0,
207
- release=tuple(int(i) for i in match.group("release").split(".")),
208
- pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
209
- post=_parse_letter_version(
210
- match.group("post_l"), match.group("post_n1") or match.group("post_n2")
211
- ),
212
- dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
213
- local=_parse_local_version(match.group("local")),
362
+ self._epoch = int(match.group("epoch")) if match.group("epoch") else 0
363
+ self._release = tuple(map(int, match.group("release").split(".")))
364
+ self._pre = _parse_letter_version(match.group("pre_l"), match.group("pre_n"))
365
+ self._post = _parse_letter_version(
366
+ match.group("post_l"), match.group("post_n1") or match.group("post_n2")
367
+ )
368
+ self._dev = _parse_letter_version(match.group("dev_l"), match.group("dev_n"))
369
+ self._local = _parse_local_version(match.group("local"))
370
+
371
+ # Key which will be used for sorting
372
+ self._key_cache = None
373
+
374
+ def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:
375
+ epoch = _validate_epoch(kwargs["epoch"]) if "epoch" in kwargs else self._epoch
376
+ release = (
377
+ _validate_release(kwargs["release"])
378
+ if "release" in kwargs
379
+ else self._release
214
380
  )
381
+ pre = _validate_pre(kwargs["pre"]) if "pre" in kwargs else self._pre
382
+ post = _validate_post(kwargs["post"]) if "post" in kwargs else self._post
383
+ dev = _validate_dev(kwargs["dev"]) if "dev" in kwargs else self._dev
384
+ local = _validate_local(kwargs["local"]) if "local" in kwargs else self._local
385
+
386
+ if (
387
+ epoch == self._epoch
388
+ and release == self._release
389
+ and pre == self._pre
390
+ and post == self._post
391
+ and dev == self._dev
392
+ and local == self._local
393
+ ):
394
+ return self
395
+
396
+ new_version = self.__class__.__new__(self.__class__)
397
+ new_version._key_cache = None
398
+ new_version._epoch = epoch
399
+ new_version._release = release
400
+ new_version._pre = pre
401
+ new_version._post = post
402
+ new_version._dev = dev
403
+ new_version._local = local
404
+
405
+ return new_version
406
+
407
+ @property
408
+ def _key(self) -> CmpKey:
409
+ if self._key_cache is None:
410
+ self._key_cache = _cmpkey(
411
+ self._epoch,
412
+ self._release,
413
+ self._pre,
414
+ self._post,
415
+ self._dev,
416
+ self._local,
417
+ )
418
+ return self._key_cache
215
419
 
216
- # Generate a key which will be used for sorting
217
- self._key = _cmpkey(
218
- self._version.epoch,
219
- self._version.release,
220
- self._version.pre,
221
- self._version.post,
222
- self._version.dev,
223
- self._version.local,
420
+ @property
421
+ @_deprecated("Version._version is private and will be removed soon")
422
+ def _version(self) -> _Version:
423
+ return _Version(
424
+ self._epoch, self._release, self._dev, self._pre, self._post, self._local
224
425
  )
225
426
 
427
+ @_version.setter
428
+ @_deprecated("Version._version is private and will be removed soon")
429
+ def _version(self, value: _Version) -> None:
430
+ self._epoch = value.epoch
431
+ self._release = value.release
432
+ self._dev = value.dev
433
+ self._pre = value.pre
434
+ self._post = value.post
435
+ self._local = value.local
436
+ self._key_cache = None
437
+
226
438
  def __repr__(self) -> str:
227
439
  """A representation of the Version that shows all internal state.
228
440
 
@@ -237,32 +449,35 @@ class Version(_BaseVersion):
237
449
  >>> str(Version("1.0a5"))
238
450
  '1.0a5'
239
451
  """
240
- parts = []
452
+ # This is a hot function, so not calling self.base_version
453
+ version = ".".join(map(str, self.release))
241
454
 
242
455
  # Epoch
243
- if self.epoch != 0:
244
- parts.append(f"{self.epoch}!")
245
-
246
- # Release segment
247
- parts.append(".".join(str(x) for x in self.release))
456
+ if self.epoch:
457
+ version = f"{self.epoch}!{version}"
248
458
 
249
459
  # Pre-release
250
460
  if self.pre is not None:
251
- parts.append("".join(str(x) for x in self.pre))
461
+ version += "".join(map(str, self.pre))
252
462
 
253
463
  # Post-release
254
464
  if self.post is not None:
255
- parts.append(f".post{self.post}")
465
+ version += f".post{self.post}"
256
466
 
257
467
  # Development release
258
468
  if self.dev is not None:
259
- parts.append(f".dev{self.dev}")
469
+ version += f".dev{self.dev}"
260
470
 
261
471
  # Local version segment
262
472
  if self.local is not None:
263
- parts.append(f"+{self.local}")
473
+ version += f"+{self.local}"
474
+
475
+ return version
264
476
 
265
- return "".join(parts)
477
+ @property
478
+ def _str(self) -> str:
479
+ """Internal property for match_args"""
480
+ return str(self)
266
481
 
267
482
  @property
268
483
  def epoch(self) -> int:
@@ -273,7 +488,7 @@ class Version(_BaseVersion):
273
488
  >>> Version("1!2.0.0").epoch
274
489
  1
275
490
  """
276
- return self._version.epoch
491
+ return self._epoch
277
492
 
278
493
  @property
279
494
  def release(self) -> tuple[int, ...]:
@@ -289,7 +504,7 @@ class Version(_BaseVersion):
289
504
  Includes trailing zeroes but not the epoch or any pre-release / development /
290
505
  post-release suffixes.
291
506
  """
292
- return self._version.release
507
+ return self._release
293
508
 
294
509
  @property
295
510
  def pre(self) -> tuple[str, int] | None:
@@ -304,7 +519,7 @@ class Version(_BaseVersion):
304
519
  >>> Version("1.2.3rc1").pre
305
520
  ('rc', 1)
306
521
  """
307
- return self._version.pre
522
+ return self._pre
308
523
 
309
524
  @property
310
525
  def post(self) -> int | None:
@@ -315,7 +530,7 @@ class Version(_BaseVersion):
315
530
  >>> Version("1.2.3.post1").post
316
531
  1
317
532
  """
318
- return self._version.post[1] if self._version.post else None
533
+ return self._post[1] if self._post else None
319
534
 
320
535
  @property
321
536
  def dev(self) -> int | None:
@@ -326,7 +541,7 @@ class Version(_BaseVersion):
326
541
  >>> Version("1.2.3.dev1").dev
327
542
  1
328
543
  """
329
- return self._version.dev[1] if self._version.dev else None
544
+ return self._dev[1] if self._dev else None
330
545
 
331
546
  @property
332
547
  def local(self) -> str | None:
@@ -337,8 +552,8 @@ class Version(_BaseVersion):
337
552
  >>> Version("1.2.3+abc").local
338
553
  'abc'
339
554
  """
340
- if self._version.local:
341
- return ".".join(str(x) for x in self._version.local)
555
+ if self._local:
556
+ return ".".join(str(x) for x in self._local)
342
557
  else:
343
558
  return None
344
559
 
@@ -369,16 +584,8 @@ class Version(_BaseVersion):
369
584
  The "base version" is the public version of the project without any pre or post
370
585
  release markers.
371
586
  """
372
- parts = []
373
-
374
- # Epoch
375
- if self.epoch != 0:
376
- parts.append(f"{self.epoch}!")
377
-
378
- # Release segment
379
- parts.append(".".join(str(x) for x in self.release))
380
-
381
- return "".join(parts)
587
+ release_segment = ".".join(map(str, self.release))
588
+ return f"{self.epoch}!{release_segment}" if self.epoch else release_segment
382
589
 
383
590
  @property
384
591
  def is_prerelease(self) -> bool:
@@ -452,6 +659,20 @@ class Version(_BaseVersion):
452
659
 
453
660
 
454
661
  class _TrimmedRelease(Version):
662
+ __slots__ = ()
663
+
664
+ def __init__(self, version: str | Version) -> None:
665
+ if isinstance(version, Version):
666
+ self._epoch = version._epoch
667
+ self._release = version._release
668
+ self._dev = version._dev
669
+ self._pre = version._pre
670
+ self._post = version._post
671
+ self._local = version._local
672
+ self._key_cache = version._key_cache
673
+ return
674
+ super().__init__(version) # pragma: no cover
675
+
455
676
  @property
456
677
  def release(self) -> tuple[int, ...]:
457
678
  """
@@ -462,45 +683,35 @@ class _TrimmedRelease(Version):
462
683
  >>> _TrimmedRelease('0.0').release
463
684
  (0,)
464
685
  """
686
+ # This leaves one 0.
465
687
  rel = super().release
466
- nonzeros = (index for index, val in enumerate(rel) if val)
467
- last_nonzero = max(nonzeros, default=0)
468
- return rel[: last_nonzero + 1]
688
+ len_release = len(rel)
689
+ i = len_release
690
+ while i > 1 and rel[i - 1] == 0:
691
+ i -= 1
692
+ return rel if i == len_release else rel[:i]
469
693
 
470
694
 
471
695
  def _parse_letter_version(
472
696
  letter: str | None, number: str | bytes | SupportsInt | None
473
697
  ) -> tuple[str, int] | None:
474
698
  if letter:
475
- # We consider there to be an implicit 0 in a pre-release if there is
476
- # not a numeral associated with it.
477
- if number is None:
478
- number = 0
479
-
480
699
  # We normalize any letters to their lower case form
481
700
  letter = letter.lower()
482
701
 
483
702
  # We consider some words to be alternate spellings of other words and
484
703
  # in those cases we want to normalize the spellings to our preferred
485
704
  # spelling.
486
- if letter == "alpha":
487
- letter = "a"
488
- elif letter == "beta":
489
- letter = "b"
490
- elif letter in ["c", "pre", "preview"]:
491
- letter = "rc"
492
- elif letter in ["rev", "r"]:
493
- letter = "post"
494
-
495
- return letter, int(number)
496
-
497
- assert not letter
705
+ letter = _LETTER_NORMALIZATION.get(letter, letter)
706
+
707
+ # We consider there to be an implicit 0 in a pre-release if there is
708
+ # not a numeral associated with it.
709
+ return letter, int(number or 0)
710
+
498
711
  if number:
499
712
  # We assume if we are given a number, but we are not given a letter
500
713
  # then this is using the implicit post release syntax (e.g. 1.0-1)
501
- letter = "post"
502
-
503
- return letter, int(number)
714
+ return "post", int(number)
504
715
 
505
716
  return None
506
717
 
@@ -529,13 +740,12 @@ def _cmpkey(
529
740
  local: LocalType | None,
530
741
  ) -> CmpKey:
531
742
  # When we compare a release version, we want to compare it with all of the
532
- # trailing zeros removed. So we'll use a reverse the list, drop all the now
533
- # leading zeros until we come to something non zero, then take the rest
534
- # re-reverse it back into the correct order and make it a tuple and use
535
- # that for our sorting key.
536
- _release = tuple(
537
- reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
538
- )
743
+ # trailing zeros removed. We will use this for our sorting key.
744
+ len_release = len(release)
745
+ i = len_release
746
+ while i and release[i - 1] == 0:
747
+ i -= 1
748
+ _release = release if i == len_release else release[:i]
539
749
 
540
750
  # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
541
751
  # We'll do this by abusing the pre segment, but we _only_ want to do this
@@ -0,0 +1,17 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining a copy
2
+ of this software and associated documentation files (the "Software"), to
3
+ deal in the Software without restriction, including without limitation the
4
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
5
+ sell copies of the Software, and to permit persons to whom the Software is
6
+ furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in
9
+ all copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
16
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
17
+ IN THE SOFTWARE.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2010-202x The platformdirs developers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -95,7 +95,7 @@ class PlatformDirsABC(ABC): # noqa: PLR0904
95
95
  def _first_item_as_path_if_multipath(self, directory: str) -> Path:
96
96
  if self.multipath:
97
97
  # If multipath is True, the first path is returned.
98
- directory = directory.split(os.pathsep)[0]
98
+ directory = directory.partition(os.pathsep)[0]
99
99
  return Path(directory)
100
100
 
101
101
  @property