nab-python 0.0.1__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 (71) hide show
  1. nab_python/__init__.py +1 -0
  2. nab_python/_build/__init__.py +1 -0
  3. nab_python/_build/env.py +364 -0
  4. nab_python/_build/errors.py +17 -0
  5. nab_python/_build/runner.py +254 -0
  6. nab_python/_lockfile/__init__.py +1 -0
  7. nab_python/_lockfile/builder.py +339 -0
  8. nab_python/_lockfile/disjointness.py +207 -0
  9. nab_python/_lockfile/pylock.py +323 -0
  10. nab_python/_lockfile/requirements.py +121 -0
  11. nab_python/_packaging_provider.py +98 -0
  12. nab_python/_provider/__init__.py +1 -0
  13. nab_python/_provider/build_remote.py +95 -0
  14. nab_python/_provider/extras.py +231 -0
  15. nab_python/_provider/listing.py +442 -0
  16. nab_python/_provider/lookahead.py +156 -0
  17. nab_python/_provider/metadata_resolver.py +450 -0
  18. nab_python/_provider/priority.py +174 -0
  19. nab_python/_provider/sources.py +215 -0
  20. nab_python/_testing/__init__.py +1 -0
  21. nab_python/_testing/coordinator_fake.py +240 -0
  22. nab_python/_vcs_admission.py +209 -0
  23. nab_python/_vendor/__init__.py +6 -0
  24. nab_python/_vendor/packaging/LICENSE +3 -0
  25. nab_python/_vendor/packaging/LICENSE.APACHE +177 -0
  26. nab_python/_vendor/packaging/LICENSE.BSD +23 -0
  27. nab_python/_vendor/packaging/PROVENANCE.md +73 -0
  28. nab_python/_vendor/packaging/__init__.py +15 -0
  29. nab_python/_vendor/packaging/_elffile.py +108 -0
  30. nab_python/_vendor/packaging/_manylinux.py +265 -0
  31. nab_python/_vendor/packaging/_musllinux.py +88 -0
  32. nab_python/_vendor/packaging/_parser.py +394 -0
  33. nab_python/_vendor/packaging/_structures.py +33 -0
  34. nab_python/_vendor/packaging/_tokenizer.py +196 -0
  35. nab_python/_vendor/packaging/dependency_groups.py +302 -0
  36. nab_python/_vendor/packaging/direct_url.py +325 -0
  37. nab_python/_vendor/packaging/errors.py +94 -0
  38. nab_python/_vendor/packaging/licenses/__init__.py +186 -0
  39. nab_python/_vendor/packaging/licenses/_spdx.py +799 -0
  40. nab_python/_vendor/packaging/markers.py +506 -0
  41. nab_python/_vendor/packaging/metadata.py +964 -0
  42. nab_python/_vendor/packaging/py.typed +0 -0
  43. nab_python/_vendor/packaging/pylock.py +910 -0
  44. nab_python/_vendor/packaging/ranges.py +1803 -0
  45. nab_python/_vendor/packaging/requirements.py +132 -0
  46. nab_python/_vendor/packaging/specifiers.py +1141 -0
  47. nab_python/_vendor/packaging/tags.py +929 -0
  48. nab_python/_vendor/packaging/utils.py +296 -0
  49. nab_python/_vendor/packaging/version.py +1230 -0
  50. nab_python/build_backend.py +184 -0
  51. nab_python/config.py +805 -0
  52. nab_python/download.py +170 -0
  53. nab_python/fetch.py +827 -0
  54. nab_python/lockfile.py +238 -0
  55. nab_python/metadata.py +145 -0
  56. nab_python/provider.py +1235 -0
  57. nab_python/py.typed +0 -0
  58. nab_python/requirements_file.py +180 -0
  59. nab_python/resolve.py +497 -0
  60. nab_python/universal/__init__.py +1 -0
  61. nab_python/universal/matrix.py +235 -0
  62. nab_python/universal/provider.py +214 -0
  63. nab_python/universal/reresolve.py +310 -0
  64. nab_python/universal/resolve.py +508 -0
  65. nab_python/universal/validate.py +439 -0
  66. nab_python/universal/wheel_selection.py +327 -0
  67. nab_python/workspace.py +214 -0
  68. nab_python-0.0.1.dist-info/METADATA +49 -0
  69. nab_python-0.0.1.dist-info/RECORD +71 -0
  70. nab_python-0.0.1.dist-info/WHEEL +4 -0
  71. nab_python-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1230 @@
1
+ # This file is dual licensed under the terms of the Apache License, Version
2
+ # 2.0, and the BSD License. See the LICENSE file in the root of this repository
3
+ # for complete details.
4
+ """
5
+ .. testsetup::
6
+
7
+ from packaging.version import parse, normalize_pre, Version, _cmpkey
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ import sys
14
+ import typing
15
+ from typing import (
16
+ Any,
17
+ Callable,
18
+ Literal,
19
+ NamedTuple,
20
+ SupportsInt,
21
+ TypedDict,
22
+ Union,
23
+ )
24
+
25
+ if typing.TYPE_CHECKING:
26
+ from typing_extensions import Self, Unpack
27
+
28
+ if sys.version_info >= (3, 13): # pragma: no cover
29
+ from warnings import deprecated as _deprecated
30
+ elif typing.TYPE_CHECKING:
31
+ from typing_extensions import deprecated as _deprecated
32
+ else: # pragma: no cover
33
+ import functools
34
+ import warnings
35
+
36
+ def _deprecated(message: str) -> object:
37
+ def decorator(func: Callable[[...], object]) -> object:
38
+ @functools.wraps(func)
39
+ def wrapper(*args: object, **kwargs: object) -> object:
40
+ warnings.warn(
41
+ message,
42
+ category=DeprecationWarning,
43
+ stacklevel=2,
44
+ )
45
+ return func(*args, **kwargs)
46
+
47
+ return wrapper
48
+
49
+ return decorator
50
+
51
+
52
+ _LETTER_NORMALIZATION = {
53
+ "alpha": "a",
54
+ "beta": "b",
55
+ "c": "rc",
56
+ "pre": "rc",
57
+ "preview": "rc",
58
+ "rev": "post",
59
+ "r": "post",
60
+ }
61
+
62
+ __all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "normalize_pre", "parse"]
63
+
64
+
65
+ def __dir__() -> list[str]:
66
+ return __all__
67
+
68
+
69
+ LocalType = tuple[Union[int, str], ...]
70
+
71
+ CmpLocalType = tuple[tuple[int, str], ...]
72
+ CmpSuffix = tuple[int, int, int, int, int, int]
73
+ CmpKey = Union[
74
+ tuple[int, tuple[int, ...], CmpSuffix],
75
+ tuple[int, tuple[int, ...], CmpSuffix, CmpLocalType],
76
+ ]
77
+ VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
78
+
79
+
80
+ class _VersionReplace(TypedDict, total=False):
81
+ epoch: int | None
82
+ release: tuple[int, ...] | None
83
+ pre: tuple[str, int] | None
84
+ post: int | None
85
+ dev: int | None
86
+ local: str | None
87
+
88
+
89
+ def normalize_pre(letter: str, /) -> str:
90
+ """Normalize the pre-release segment of a version string.
91
+
92
+ Returns a lowercase version of the string if not a known pre-release
93
+ identifier.
94
+
95
+ >>> normalize_pre('alpha')
96
+ 'a'
97
+ >>> normalize_pre('BETA')
98
+ 'b'
99
+ >>> normalize_pre('rc')
100
+ 'rc'
101
+
102
+ :param letter:
103
+
104
+ .. versionadded:: 26.1
105
+ """
106
+ letter = letter.lower()
107
+ return _LETTER_NORMALIZATION.get(letter, letter)
108
+
109
+
110
+ def parse(version: str) -> Version:
111
+ """Parse the given version string.
112
+
113
+ This is identical to the :class:`Version` constructor.
114
+
115
+ >>> parse('1.0.dev1')
116
+ <Version('1.0.dev1')>
117
+
118
+ :param version: The version string to parse.
119
+ :raises InvalidVersion: When the version string is not a valid version.
120
+ """
121
+ return Version(version)
122
+
123
+
124
+ class InvalidVersion(ValueError):
125
+ """Raised when a version string is not a valid version.
126
+
127
+ >>> Version("invalid")
128
+ Traceback (most recent call last):
129
+ ...
130
+ packaging.version.InvalidVersion: Invalid version: 'invalid'
131
+ """
132
+
133
+
134
+ class _BaseVersion:
135
+ __slots__ = ()
136
+
137
+ # This can also be a normal member (see the packaging_legacy package);
138
+ # we are just requiring it to be readable. Actually defining a property
139
+ # has runtime effect on subclasses, so it's typing only.
140
+ if typing.TYPE_CHECKING:
141
+
142
+ @property
143
+ def _key(self) -> tuple[Any, ...]: ...
144
+
145
+ def __hash__(self) -> int:
146
+ return hash(self._key)
147
+
148
+ # Please keep the duplicated `isinstance` check
149
+ # in the six comparisons hereunder
150
+ # unless you find a way to avoid adding overhead function calls.
151
+ def __lt__(self, other: _BaseVersion) -> bool:
152
+ if not isinstance(other, _BaseVersion):
153
+ return NotImplemented
154
+
155
+ return self._key < other._key
156
+
157
+ def __le__(self, other: _BaseVersion) -> bool:
158
+ if not isinstance(other, _BaseVersion):
159
+ return NotImplemented
160
+
161
+ return self._key <= other._key
162
+
163
+ def __eq__(self, other: object) -> bool:
164
+ if not isinstance(other, _BaseVersion):
165
+ return NotImplemented
166
+
167
+ return self._key == other._key
168
+
169
+ def __ge__(self, other: _BaseVersion) -> bool:
170
+ if not isinstance(other, _BaseVersion):
171
+ return NotImplemented
172
+
173
+ return self._key >= other._key
174
+
175
+ def __gt__(self, other: _BaseVersion) -> bool:
176
+ if not isinstance(other, _BaseVersion):
177
+ return NotImplemented
178
+
179
+ return self._key > other._key
180
+
181
+ def __ne__(self, other: object) -> bool:
182
+ if not isinstance(other, _BaseVersion):
183
+ return NotImplemented
184
+
185
+ return self._key != other._key
186
+
187
+
188
+ # Deliberately not anchored to the start and end of the string, to make it
189
+ # easier for 3rd party code to reuse
190
+
191
+ # Note that ++ doesn't behave identically on CPython and PyPy, so not using it here
192
+ _VERSION_PATTERN = r"""
193
+ v?+ # optional leading v
194
+ (?a:
195
+ (?:(?P<epoch>[0-9]+)!)?+ # epoch
196
+ (?P<release>[0-9]+(?:\.[0-9]+)*+) # release segment
197
+ (?P<pre> # pre-release
198
+ [._-]?+
199
+ (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
200
+ [._-]?+
201
+ (?P<pre_n>[0-9]+)?
202
+ )?+
203
+ (?P<post> # post release
204
+ (?:-(?P<post_n1>[0-9]+))
205
+ |
206
+ (?:
207
+ [._-]?
208
+ (?P<post_l>post|rev|r)
209
+ [._-]?
210
+ (?P<post_n2>[0-9]+)?
211
+ )
212
+ )?+
213
+ (?P<dev> # dev release
214
+ [._-]?+
215
+ (?P<dev_l>dev)
216
+ [._-]?+
217
+ (?P<dev_n>[0-9]+)?
218
+ )?+
219
+ )
220
+ (?a:\+
221
+ (?P<local> # local version
222
+ [a-z0-9]+
223
+ (?:[._-][a-z0-9]+)*+
224
+ )
225
+ )?+
226
+ """
227
+
228
+ _VERSION_PATTERN_OLD = _VERSION_PATTERN.replace("*+", "*").replace("?+", "?")
229
+
230
+ # Possessive qualifiers were added in Python 3.11.
231
+ # CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795
232
+ # Older PyPy also had a bug.
233
+ VERSION_PATTERN = (
234
+ _VERSION_PATTERN_OLD
235
+ if (sys.implementation.name == "cpython" and sys.version_info < (3, 11, 5))
236
+ or (sys.implementation.name == "pypy" and sys.version_info < (3, 11, 13))
237
+ or sys.version_info < (3, 11)
238
+ else _VERSION_PATTERN
239
+ )
240
+ """
241
+ A string containing the regular expression used to match a valid version.
242
+
243
+ The pattern is not anchored at either end, and is intended for embedding in larger
244
+ expressions (for example, matching a version number as part of a file name). The
245
+ regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
246
+ flags set.
247
+
248
+ .. versionchanged:: 26.0
249
+
250
+ The regex now uses possessive qualifiers on Python 3.11 if they are
251
+ supported (CPython 3.11.5+, PyPy 3.11.13+).
252
+
253
+ :meta hide-value:
254
+ """
255
+
256
+
257
+ # Validation pattern for local version in replace()
258
+ _LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE | re.ASCII)
259
+
260
+ # Fast path: If a version has only digits and dots then we
261
+ # can skip the regex and parse it as a release segment
262
+ _SIMPLE_VERSION_INDICATORS = frozenset(".0123456789")
263
+
264
+
265
+ def _validate_epoch(value: object, /) -> int:
266
+ epoch = value or 0
267
+ if isinstance(epoch, int) and epoch >= 0:
268
+ return epoch
269
+ msg = f"epoch must be non-negative integer, got {epoch}"
270
+ raise InvalidVersion(msg)
271
+
272
+
273
+ def _validate_release(value: object, /) -> tuple[int, ...]:
274
+ release = (0,) if value is None else value
275
+ if (
276
+ isinstance(release, tuple)
277
+ and len(release) > 0
278
+ and all(isinstance(i, int) and i >= 0 for i in release)
279
+ ):
280
+ return release
281
+ msg = f"release must be a non-empty tuple of non-negative integers, got {release}"
282
+ raise InvalidVersion(msg)
283
+
284
+
285
+ def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None:
286
+ if value is None:
287
+ return value
288
+ if isinstance(value, tuple) and len(value) == 2:
289
+ letter, number = value
290
+ letter = normalize_pre(letter)
291
+ if letter in {"a", "b", "rc"} and isinstance(number, int) and number >= 0:
292
+ # type checkers can't infer the Literal type here on letter
293
+ return (letter, number) # type: ignore[return-value]
294
+ msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}"
295
+ raise InvalidVersion(msg)
296
+
297
+
298
+ def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:
299
+ if value is None:
300
+ return value
301
+ if isinstance(value, int) and value >= 0:
302
+ return ("post", value)
303
+ msg = f"post must be non-negative integer, got {value}"
304
+ raise InvalidVersion(msg)
305
+
306
+
307
+ def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:
308
+ if value is None:
309
+ return value
310
+ if isinstance(value, int) and value >= 0:
311
+ return ("dev", value)
312
+ msg = f"dev must be non-negative integer, got {value}"
313
+ raise InvalidVersion(msg)
314
+
315
+
316
+ def _validate_local(value: object, /) -> LocalType | None:
317
+ if value is None:
318
+ return value
319
+ if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):
320
+ return _parse_local_version(value)
321
+ msg = f"local must be a valid version string, got {value!r}"
322
+ raise InvalidVersion(msg)
323
+
324
+
325
+ # Backward compatibility for internals before 26.0. Do not use.
326
+ class _Version(NamedTuple):
327
+ epoch: int
328
+ release: tuple[int, ...]
329
+ dev: tuple[Literal["dev"], int] | None
330
+ pre: tuple[Literal["a", "b", "rc"], int] | None
331
+ post: tuple[Literal["post"], int] | None
332
+ local: LocalType | None
333
+
334
+
335
+ class Version(_BaseVersion):
336
+ """This class abstracts handling of a project's versions.
337
+
338
+ A :class:`Version` instance is comparison aware and can be compared and
339
+ sorted using the standard Python interfaces.
340
+
341
+ >>> v1 = Version("1.0a5")
342
+ >>> v2 = Version("1.0")
343
+ >>> v1
344
+ <Version('1.0a5')>
345
+ >>> v2
346
+ <Version('1.0')>
347
+ >>> v1 < v2
348
+ True
349
+ >>> v1 == v2
350
+ False
351
+ >>> v1 > v2
352
+ False
353
+ >>> v1 >= v2
354
+ False
355
+ >>> v1 <= v2
356
+ True
357
+
358
+ :class:`Version` is immutable; use :meth:`__replace__` to change
359
+ part of a version.
360
+
361
+ Instances are safe to serialize with :mod:`pickle`. They use a stable
362
+ format so the same pickle can be loaded in future packaging releases.
363
+
364
+ .. versionchanged:: 26.2
365
+
366
+ Added a stable pickle format. Pickles created with packaging 26.2+ can
367
+ be unpickled with future releases. Backward compatibility with pickles
368
+ from packaging < 26.2 is supported but may be removed in a future
369
+ release.
370
+ """
371
+
372
+ __slots__ = (
373
+ "_dev",
374
+ "_epoch",
375
+ "_hash_cache",
376
+ "_key_cache",
377
+ "_local",
378
+ "_post",
379
+ "_pre",
380
+ "_release",
381
+ )
382
+ __match_args__ = ("_str",)
383
+ """
384
+ Pattern matching is supported on Python 3.10+.
385
+
386
+ .. versionadded:: 26.0
387
+
388
+ :meta hide-value:
389
+ """
390
+
391
+ _regex = re.compile(r"\s*" + VERSION_PATTERN + r"\s*", re.VERBOSE | re.IGNORECASE)
392
+
393
+ _epoch: int
394
+ _release: tuple[int, ...]
395
+ _dev: tuple[Literal["dev"], int] | None
396
+ _pre: tuple[Literal["a", "b", "rc"], int] | None
397
+ _post: tuple[Literal["post"], int] | None
398
+ _local: LocalType | None
399
+
400
+ _hash_cache: int | None
401
+ _key_cache: CmpKey | None
402
+
403
+ def __init__(self, version: str) -> None:
404
+ """Initialize a Version object.
405
+
406
+ :param version:
407
+ The string representation of a version which will be parsed and normalized
408
+ before use.
409
+ :raises InvalidVersion:
410
+ If the ``version`` does not conform to PEP 440 in any way then this
411
+ exception will be raised.
412
+ """
413
+ if _SIMPLE_VERSION_INDICATORS.issuperset(version):
414
+ try:
415
+ self._release = tuple(map(int, version.split(".")))
416
+ except ValueError:
417
+ # Empty parts (from "1..2", ".1", etc.) are invalid versions.
418
+ # Any other ValueError (e.g. int str-digits limit) should
419
+ # propagate to the caller.
420
+ if "" in version.split("."):
421
+ raise InvalidVersion(f"Invalid version: {version!r}") from None
422
+ # TODO: remove "no cover" when Python 3.9 is dropped.
423
+ raise # pragma: no cover
424
+
425
+ self._epoch = 0
426
+ self._pre = None
427
+ self._post = None
428
+ self._dev = None
429
+ self._local = None
430
+ self._key_cache = None
431
+ self._hash_cache = None
432
+ return
433
+
434
+ # Validate the version and parse it into pieces
435
+ match = self._regex.fullmatch(version)
436
+ if not match:
437
+ raise InvalidVersion(f"Invalid version: {version!r}")
438
+ self._epoch = int(match.group("epoch")) if match.group("epoch") else 0
439
+ self._release = tuple(map(int, match.group("release").split(".")))
440
+ # We can type ignore the assignments below because the regex guarantees
441
+ # the correct strings
442
+ self._pre = _parse_letter_version(match.group("pre_l"), match.group("pre_n")) # type: ignore[assignment]
443
+ self._post = _parse_letter_version( # type: ignore[assignment]
444
+ match.group("post_l"), match.group("post_n1") or match.group("post_n2")
445
+ )
446
+ self._dev = _parse_letter_version(match.group("dev_l"), match.group("dev_n")) # type: ignore[assignment]
447
+ self._local = _parse_local_version(match.group("local"))
448
+
449
+ # Key which will be used for sorting
450
+ self._key_cache = None
451
+ self._hash_cache = None
452
+
453
+ @classmethod
454
+ def from_parts(
455
+ cls,
456
+ *,
457
+ epoch: int = 0,
458
+ release: tuple[int, ...],
459
+ pre: tuple[str, int] | None = None,
460
+ post: int | None = None,
461
+ dev: int | None = None,
462
+ local: str | None = None,
463
+ ) -> Self:
464
+ """
465
+ Return a new version composed of the various parts.
466
+
467
+ This allows you to build a version without going though a string and
468
+ running a regular expression. It normalizes pre-release strings. The
469
+ ``release=`` keyword argument is required.
470
+
471
+ >>> Version.from_parts(release=(1,2,3))
472
+ <Version('1.2.3')>
473
+ >>> Version.from_parts(release=(0,1,0), pre=("b", 1))
474
+ <Version('0.1.0b1')>
475
+
476
+ :param epoch:
477
+ :param release: This version tuple is required
478
+
479
+ .. versionadded:: 26.1
480
+ """
481
+ _epoch = _validate_epoch(epoch)
482
+ _release = _validate_release(release)
483
+ _pre = _validate_pre(pre) if pre is not None else None
484
+ _post = _validate_post(post) if post is not None else None
485
+ _dev = _validate_dev(dev) if dev is not None else None
486
+ _local = _validate_local(local) if local is not None else None
487
+
488
+ new_version = cls.__new__(cls)
489
+ new_version._key_cache = None
490
+ new_version._hash_cache = None
491
+ new_version._epoch = _epoch
492
+ new_version._release = _release
493
+ new_version._pre = _pre
494
+ new_version._post = _post
495
+ new_version._dev = _dev
496
+ new_version._local = _local
497
+
498
+ return new_version
499
+
500
+ def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:
501
+ """
502
+ __replace__(*, epoch=..., release=..., pre=..., post=..., dev=..., local=...)
503
+
504
+ Return a new version with parts replaced.
505
+
506
+ This returns a new version (unless no parts were changed). The
507
+ pre-release is normalized. Setting a value to ``None`` clears it.
508
+
509
+ >>> v = Version("1.2.3")
510
+ >>> v.__replace__(pre=("a", 1))
511
+ <Version('1.2.3a1')>
512
+
513
+ :param int | None epoch:
514
+ :param tuple[int, ...] | None release:
515
+ :param tuple[str, int] | None pre:
516
+ :param int | None post:
517
+ :param int | None dev:
518
+ :param str | None local:
519
+
520
+ .. versionadded:: 26.0
521
+ .. versionchanged:: 26.1
522
+
523
+ The pre-release portion is now normalized.
524
+ """
525
+ epoch = _validate_epoch(kwargs["epoch"]) if "epoch" in kwargs else self._epoch
526
+ release = (
527
+ _validate_release(kwargs["release"])
528
+ if "release" in kwargs
529
+ else self._release
530
+ )
531
+ pre = _validate_pre(kwargs["pre"]) if "pre" in kwargs else self._pre
532
+ post = _validate_post(kwargs["post"]) if "post" in kwargs else self._post
533
+ dev = _validate_dev(kwargs["dev"]) if "dev" in kwargs else self._dev
534
+ local = _validate_local(kwargs["local"]) if "local" in kwargs else self._local
535
+
536
+ if (
537
+ epoch == self._epoch
538
+ and release == self._release
539
+ and pre == self._pre
540
+ and post == self._post
541
+ and dev == self._dev
542
+ and local == self._local
543
+ ):
544
+ return self
545
+
546
+ new_version = self.__class__.__new__(self.__class__)
547
+ new_version._key_cache = None
548
+ new_version._hash_cache = None
549
+ new_version._epoch = epoch
550
+ new_version._release = release
551
+ new_version._pre = pre
552
+ new_version._post = post
553
+ new_version._dev = dev
554
+ new_version._local = local
555
+
556
+ return new_version
557
+
558
+ @property
559
+ def _key(self) -> CmpKey:
560
+ if self._key_cache is None:
561
+ self._key_cache = _cmpkey(
562
+ self._epoch,
563
+ self._release,
564
+ self._pre,
565
+ self._post,
566
+ self._dev,
567
+ self._local,
568
+ )
569
+ return self._key_cache
570
+
571
+ # __hash__ must be defined when __eq__ is overridden,
572
+ # otherwise Python sets __hash__ to None.
573
+ def __hash__(self) -> int:
574
+ if (cached_hash := self._hash_cache) is not None:
575
+ return cached_hash
576
+
577
+ if (key := self._key_cache) is None:
578
+ self._key_cache = key = _cmpkey(
579
+ self._epoch,
580
+ self._release,
581
+ self._pre,
582
+ self._post,
583
+ self._dev,
584
+ self._local,
585
+ )
586
+ self._hash_cache = cached_hash = hash(key)
587
+ return cached_hash
588
+
589
+ # Override comparison methods to use direct _key_cache access
590
+ # This is faster than property access, especially before Python 3.12
591
+ def __lt__(self, other: _BaseVersion) -> bool:
592
+ if isinstance(other, Version):
593
+ if self._key_cache is None:
594
+ self._key_cache = _cmpkey(
595
+ self._epoch,
596
+ self._release,
597
+ self._pre,
598
+ self._post,
599
+ self._dev,
600
+ self._local,
601
+ )
602
+ if other._key_cache is None:
603
+ other._key_cache = _cmpkey(
604
+ other._epoch,
605
+ other._release,
606
+ other._pre,
607
+ other._post,
608
+ other._dev,
609
+ other._local,
610
+ )
611
+ return self._key_cache < other._key_cache
612
+
613
+ if not isinstance(other, _BaseVersion):
614
+ return NotImplemented
615
+
616
+ return super().__lt__(other)
617
+
618
+ def __le__(self, other: _BaseVersion) -> bool:
619
+ if isinstance(other, Version):
620
+ if self._key_cache is None:
621
+ self._key_cache = _cmpkey(
622
+ self._epoch,
623
+ self._release,
624
+ self._pre,
625
+ self._post,
626
+ self._dev,
627
+ self._local,
628
+ )
629
+ if other._key_cache is None:
630
+ other._key_cache = _cmpkey(
631
+ other._epoch,
632
+ other._release,
633
+ other._pre,
634
+ other._post,
635
+ other._dev,
636
+ other._local,
637
+ )
638
+ return self._key_cache <= other._key_cache
639
+
640
+ if not isinstance(other, _BaseVersion):
641
+ return NotImplemented
642
+
643
+ return super().__le__(other)
644
+
645
+ def __eq__(self, other: object) -> bool:
646
+ if isinstance(other, Version):
647
+ if self._key_cache is None:
648
+ self._key_cache = _cmpkey(
649
+ self._epoch,
650
+ self._release,
651
+ self._pre,
652
+ self._post,
653
+ self._dev,
654
+ self._local,
655
+ )
656
+ if other._key_cache is None:
657
+ other._key_cache = _cmpkey(
658
+ other._epoch,
659
+ other._release,
660
+ other._pre,
661
+ other._post,
662
+ other._dev,
663
+ other._local,
664
+ )
665
+ return self._key_cache == other._key_cache
666
+
667
+ if not isinstance(other, _BaseVersion):
668
+ return NotImplemented
669
+
670
+ return super().__eq__(other)
671
+
672
+ def __ge__(self, other: _BaseVersion) -> bool:
673
+ if isinstance(other, Version):
674
+ if self._key_cache is None:
675
+ self._key_cache = _cmpkey(
676
+ self._epoch,
677
+ self._release,
678
+ self._pre,
679
+ self._post,
680
+ self._dev,
681
+ self._local,
682
+ )
683
+ if other._key_cache is None:
684
+ other._key_cache = _cmpkey(
685
+ other._epoch,
686
+ other._release,
687
+ other._pre,
688
+ other._post,
689
+ other._dev,
690
+ other._local,
691
+ )
692
+ return self._key_cache >= other._key_cache
693
+
694
+ if not isinstance(other, _BaseVersion):
695
+ return NotImplemented
696
+
697
+ return super().__ge__(other)
698
+
699
+ def __gt__(self, other: _BaseVersion) -> bool:
700
+ if isinstance(other, Version):
701
+ if self._key_cache is None:
702
+ self._key_cache = _cmpkey(
703
+ self._epoch,
704
+ self._release,
705
+ self._pre,
706
+ self._post,
707
+ self._dev,
708
+ self._local,
709
+ )
710
+ if other._key_cache is None:
711
+ other._key_cache = _cmpkey(
712
+ other._epoch,
713
+ other._release,
714
+ other._pre,
715
+ other._post,
716
+ other._dev,
717
+ other._local,
718
+ )
719
+ return self._key_cache > other._key_cache
720
+
721
+ if not isinstance(other, _BaseVersion):
722
+ return NotImplemented
723
+
724
+ return super().__gt__(other)
725
+
726
+ def __ne__(self, other: object) -> bool:
727
+ if isinstance(other, Version):
728
+ if self._key_cache is None:
729
+ self._key_cache = _cmpkey(
730
+ self._epoch,
731
+ self._release,
732
+ self._pre,
733
+ self._post,
734
+ self._dev,
735
+ self._local,
736
+ )
737
+ if other._key_cache is None:
738
+ other._key_cache = _cmpkey(
739
+ other._epoch,
740
+ other._release,
741
+ other._pre,
742
+ other._post,
743
+ other._dev,
744
+ other._local,
745
+ )
746
+ return self._key_cache != other._key_cache
747
+
748
+ if not isinstance(other, _BaseVersion):
749
+ return NotImplemented
750
+
751
+ return super().__ne__(other)
752
+
753
+ def __getstate__(
754
+ self,
755
+ ) -> tuple[
756
+ int,
757
+ tuple[int, ...],
758
+ tuple[str, int] | None,
759
+ tuple[str, int] | None,
760
+ tuple[str, int] | None,
761
+ LocalType | None,
762
+ ]:
763
+ # Return state as a 6-item tuple for compactness:
764
+ # (epoch, release, pre, post, dev, local)
765
+ # Cache members are excluded and will be recomputed on demand
766
+ return (
767
+ self._epoch,
768
+ self._release,
769
+ self._pre,
770
+ self._post,
771
+ self._dev,
772
+ self._local,
773
+ )
774
+
775
+ def __setstate__(self, state: object) -> None:
776
+ # Always discard cached values — they may contain stale references
777
+ # (e.g. packaging._structures.InfinityType from pre-26.1 pickles)
778
+ # and will be recomputed on demand from the core fields above.
779
+ self._key_cache = None
780
+ self._hash_cache = None
781
+
782
+ if isinstance(state, tuple):
783
+ if len(state) == 6:
784
+ # New format (26.2+): (epoch, release, pre, post, dev, local)
785
+ (
786
+ self._epoch,
787
+ self._release,
788
+ self._pre,
789
+ self._post,
790
+ self._dev,
791
+ self._local,
792
+ ) = state
793
+ return
794
+ if len(state) == 2:
795
+ # Format (packaging 26.0-26.1): (None, {slot: value}).
796
+ _, slot_dict = state
797
+ if isinstance(slot_dict, dict):
798
+ self._epoch = slot_dict["_epoch"]
799
+ self._release = slot_dict["_release"]
800
+ self._pre = slot_dict.get("_pre")
801
+ self._post = slot_dict.get("_post")
802
+ self._dev = slot_dict.get("_dev")
803
+ self._local = slot_dict.get("_local")
804
+ return
805
+ if isinstance(state, dict):
806
+ # Old format (packaging <= 25.x, no __slots__): state is a plain
807
+ # dict with "_version" (_Version NamedTuple) and "_key" entries.
808
+ version_nt = state.get("_version")
809
+ if version_nt is not None:
810
+ self._epoch = version_nt.epoch
811
+ self._release = version_nt.release
812
+ self._pre = version_nt.pre
813
+ self._post = version_nt.post
814
+ self._dev = version_nt.dev
815
+ self._local = version_nt.local
816
+ return
817
+
818
+ raise TypeError(f"Cannot restore Version from {state!r}")
819
+
820
+ @property
821
+ @_deprecated("Version._version is private and will be removed soon")
822
+ def _version(self) -> _Version:
823
+ return _Version(
824
+ self._epoch, self._release, self._dev, self._pre, self._post, self._local
825
+ )
826
+
827
+ @_version.setter
828
+ @_deprecated("Version._version is private and will be removed soon")
829
+ def _version(self, value: _Version) -> None:
830
+ self._epoch = value.epoch
831
+ self._release = value.release
832
+ self._dev = value.dev
833
+ self._pre = value.pre
834
+ self._post = value.post
835
+ self._local = value.local
836
+ self._key_cache = None
837
+ self._hash_cache = None
838
+
839
+ def __repr__(self) -> str:
840
+ """A representation of the Version that shows all internal state.
841
+
842
+ >>> Version('1.0.0')
843
+ <Version('1.0.0')>
844
+ """
845
+ return f"<{self.__class__.__name__}({str(self)!r})>"
846
+
847
+ def __str__(self) -> str:
848
+ """A string representation of the version that can be round-tripped.
849
+
850
+ >>> str(Version("1.0a5"))
851
+ '1.0a5'
852
+ """
853
+ # This is a hot function, so not calling self.base_version
854
+ version = ".".join(map(str, self.release))
855
+
856
+ # Epoch
857
+ if self.epoch:
858
+ version = f"{self.epoch}!{version}"
859
+
860
+ # Pre-release
861
+ if self.pre is not None:
862
+ version += "".join(map(str, self.pre))
863
+
864
+ # Post-release
865
+ if self.post is not None:
866
+ version += f".post{self.post}"
867
+
868
+ # Development release
869
+ if self.dev is not None:
870
+ version += f".dev{self.dev}"
871
+
872
+ # Local version segment
873
+ if self.local is not None:
874
+ version += f"+{self.local}"
875
+
876
+ return version
877
+
878
+ @property
879
+ def _str(self) -> str:
880
+ """Internal property for match_args"""
881
+ return str(self)
882
+
883
+ @property
884
+ def epoch(self) -> int:
885
+ """The epoch of the version.
886
+
887
+ >>> Version("2.0.0").epoch
888
+ 0
889
+ >>> Version("1!2.0.0").epoch
890
+ 1
891
+ """
892
+ return self._epoch
893
+
894
+ @property
895
+ def release(self) -> tuple[int, ...]:
896
+ """The components of the "release" segment of the version.
897
+
898
+ >>> Version("1.2.3").release
899
+ (1, 2, 3)
900
+ >>> Version("2.0.0").release
901
+ (2, 0, 0)
902
+ >>> Version("1!2.0.0.post0").release
903
+ (2, 0, 0)
904
+
905
+ Includes trailing zeroes but not the epoch or any pre-release / development /
906
+ post-release suffixes.
907
+ """
908
+ return self._release
909
+
910
+ @property
911
+ def pre(self) -> tuple[Literal["a", "b", "rc"], int] | None:
912
+ """The pre-release segment of the version.
913
+
914
+ >>> print(Version("1.2.3").pre)
915
+ None
916
+ >>> Version("1.2.3a1").pre
917
+ ('a', 1)
918
+ >>> Version("1.2.3b1").pre
919
+ ('b', 1)
920
+ >>> Version("1.2.3rc1").pre
921
+ ('rc', 1)
922
+ """
923
+ return self._pre
924
+
925
+ @property
926
+ def post(self) -> int | None:
927
+ """The post-release number of the version.
928
+
929
+ >>> print(Version("1.2.3").post)
930
+ None
931
+ >>> Version("1.2.3.post1").post
932
+ 1
933
+ """
934
+ return self._post[1] if self._post else None
935
+
936
+ @property
937
+ def dev(self) -> int | None:
938
+ """The development number of the version.
939
+
940
+ >>> print(Version("1.2.3").dev)
941
+ None
942
+ >>> Version("1.2.3.dev1").dev
943
+ 1
944
+ """
945
+ return self._dev[1] if self._dev else None
946
+
947
+ @property
948
+ def local(self) -> str | None:
949
+ """The local version segment of the version.
950
+
951
+ >>> print(Version("1.2.3").local)
952
+ None
953
+ >>> Version("1.2.3+abc").local
954
+ 'abc'
955
+ """
956
+ if self._local:
957
+ return ".".join(str(x) for x in self._local)
958
+ else:
959
+ return None
960
+
961
+ @property
962
+ def public(self) -> str:
963
+ """The public portion of the version.
964
+
965
+ This returns a string. If you want a :class:`Version` again and care
966
+ about performance, use ``v.__replace__(local=None)`` instead.
967
+
968
+ >>> Version("1.2.3").public
969
+ '1.2.3'
970
+ >>> Version("1.2.3+abc").public
971
+ '1.2.3'
972
+ >>> Version("1!1.2.3dev1+abc").public
973
+ '1!1.2.3.dev1'
974
+ """
975
+ return str(self).split("+", 1)[0]
976
+
977
+ @property
978
+ def base_version(self) -> str:
979
+ """The "base version" of the version.
980
+
981
+ This returns a string. If you want a :class:`Version` again and care
982
+ about performance, use
983
+ ``v.__replace__(pre=None, post=None, dev=None, local=None)`` instead.
984
+
985
+ >>> Version("1.2.3").base_version
986
+ '1.2.3'
987
+ >>> Version("1.2.3+abc").base_version
988
+ '1.2.3'
989
+ >>> Version("1!1.2.3dev1+abc").base_version
990
+ '1!1.2.3'
991
+
992
+ The "base version" is the public version of the project without any pre or post
993
+ release markers.
994
+ """
995
+ release_segment = ".".join(map(str, self.release))
996
+ return f"{self.epoch}!{release_segment}" if self.epoch else release_segment
997
+
998
+ @property
999
+ def is_prerelease(self) -> bool:
1000
+ """Whether this version is a pre-release.
1001
+
1002
+ >>> Version("1.2.3").is_prerelease
1003
+ False
1004
+ >>> Version("1.2.3a1").is_prerelease
1005
+ True
1006
+ >>> Version("1.2.3b1").is_prerelease
1007
+ True
1008
+ >>> Version("1.2.3rc1").is_prerelease
1009
+ True
1010
+ >>> Version("1.2.3dev1").is_prerelease
1011
+ True
1012
+ """
1013
+ return self.dev is not None or self.pre is not None
1014
+
1015
+ @property
1016
+ def is_postrelease(self) -> bool:
1017
+ """Whether this version is a post-release.
1018
+
1019
+ >>> Version("1.2.3").is_postrelease
1020
+ False
1021
+ >>> Version("1.2.3.post1").is_postrelease
1022
+ True
1023
+ """
1024
+ return self.post is not None
1025
+
1026
+ @property
1027
+ def is_devrelease(self) -> bool:
1028
+ """Whether this version is a development release.
1029
+
1030
+ >>> Version("1.2.3").is_devrelease
1031
+ False
1032
+ >>> Version("1.2.3.dev1").is_devrelease
1033
+ True
1034
+ """
1035
+ return self.dev is not None
1036
+
1037
+ @property
1038
+ def major(self) -> int:
1039
+ """The first item of :attr:`release` or ``0`` if unavailable.
1040
+
1041
+ >>> Version("1.2.3").major
1042
+ 1
1043
+ """
1044
+ return self.release[0] if len(self.release) >= 1 else 0
1045
+
1046
+ @property
1047
+ def minor(self) -> int:
1048
+ """The second item of :attr:`release` or ``0`` if unavailable.
1049
+
1050
+ >>> Version("1.2.3").minor
1051
+ 2
1052
+ >>> Version("1").minor
1053
+ 0
1054
+ """
1055
+ return self.release[1] if len(self.release) >= 2 else 0
1056
+
1057
+ @property
1058
+ def micro(self) -> int:
1059
+ """The third item of :attr:`release` or ``0`` if unavailable.
1060
+
1061
+ >>> Version("1.2.3").micro
1062
+ 3
1063
+ >>> Version("1").micro
1064
+ 0
1065
+ """
1066
+ return self.release[2] if len(self.release) >= 3 else 0
1067
+
1068
+
1069
+ class _TrimmedRelease(Version):
1070
+ __slots__ = ()
1071
+
1072
+ def __init__(self, version: str | Version) -> None:
1073
+ if isinstance(version, Version):
1074
+ self._epoch = version._epoch
1075
+ self._release = version._release
1076
+ self._dev = version._dev
1077
+ self._pre = version._pre
1078
+ self._post = version._post
1079
+ self._local = version._local
1080
+ self._key_cache = version._key_cache
1081
+ return
1082
+ super().__init__(version) # pragma: no cover
1083
+
1084
+ @property
1085
+ def release(self) -> tuple[int, ...]:
1086
+ """
1087
+ Release segment without any trailing zeros.
1088
+
1089
+ >>> _TrimmedRelease('1.0.0').release
1090
+ (1,)
1091
+ >>> _TrimmedRelease('0.0').release
1092
+ (0,)
1093
+ """
1094
+ # This leaves one 0.
1095
+ rel = super().release
1096
+ len_release = len(rel)
1097
+ i = len_release
1098
+ while i > 1 and rel[i - 1] == 0:
1099
+ i -= 1
1100
+ return rel if i == len_release else rel[:i]
1101
+
1102
+
1103
+ def _parse_letter_version(
1104
+ letter: str | None, number: str | bytes | SupportsInt | None
1105
+ ) -> tuple[str, int] | None:
1106
+ if letter:
1107
+ # We normalize any letters to their lower case form
1108
+ letter = letter.lower()
1109
+
1110
+ # We consider some words to be alternate spellings of other words and
1111
+ # in those cases we want to normalize the spellings to our preferred
1112
+ # spelling.
1113
+ letter = _LETTER_NORMALIZATION.get(letter, letter)
1114
+
1115
+ # We consider there to be an implicit 0 in a pre-release if there is
1116
+ # not a numeral associated with it.
1117
+ return letter, int(number or 0)
1118
+
1119
+ if number:
1120
+ # We assume if we are given a number, but we are not given a letter
1121
+ # then this is using the implicit post release syntax (e.g. 1.0-1)
1122
+ return "post", int(number)
1123
+
1124
+ return None
1125
+
1126
+
1127
+ _local_version_separators = re.compile(r"[\._-]")
1128
+
1129
+
1130
+ def _parse_local_version(local: str | None) -> LocalType | None:
1131
+ """
1132
+ Takes a string like ``"abc.1.twelve"`` and turns it into
1133
+ ``("abc", 1, "twelve")``.
1134
+ """
1135
+ if local is not None:
1136
+ return tuple(
1137
+ part.lower() if not part.isdigit() else int(part)
1138
+ for part in _local_version_separators.split(local)
1139
+ )
1140
+ return None
1141
+
1142
+
1143
+ # Sort ranks for pre-release: dev-only < a < b < rc < stable (no pre-release).
1144
+ _PRE_RANK = {"a": 0, "b": 1, "rc": 2}
1145
+ _PRE_RANK_DEV_ONLY = -1 # sorts before a(0)
1146
+ _PRE_RANK_STABLE = 3 # sorts after rc(2)
1147
+
1148
+ # In local version segments, strings sort before ints per PEP 440.
1149
+ _LOCAL_STR_RANK = -1 # sorts before all non-negative ints
1150
+
1151
+ # Pre-computed suffix for stable releases (no pre, post, or dev segments).
1152
+ # See _cmpkey() for the suffix layout.
1153
+ _STABLE_SUFFIX = (_PRE_RANK_STABLE, 0, 0, 0, 1, 0)
1154
+
1155
+
1156
+ def _cmpkey(
1157
+ epoch: int,
1158
+ release: tuple[int, ...],
1159
+ pre: tuple[str, int] | None,
1160
+ post: tuple[str, int] | None,
1161
+ dev: tuple[str, int] | None,
1162
+ local: LocalType | None,
1163
+ ) -> CmpKey:
1164
+ """Build a comparison key for PEP 440 ordering.
1165
+
1166
+ Returns ``(epoch, release, suffix)`` or
1167
+ ``(epoch, release, suffix, local)`` so that plain tuple
1168
+ comparison gives the correct order.
1169
+
1170
+ Trailing zeros are stripped from the release so that ``1.0.0 == 1``.
1171
+
1172
+ The suffix is a flat 6-int tuple that encodes pre/post/dev:
1173
+ ``(pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)``
1174
+
1175
+ pre_rank: dev-only=-1, a=0, b=1, rc=2, no-pre=3
1176
+ Dev-only releases (no pre or post) get -1 so they sort before
1177
+ any alpha/beta/rc. Releases without a pre-release tag get 3
1178
+ so they sort after rc.
1179
+ post_rank: no-post=0, post=1
1180
+ Releases without a post segment sort before those with one.
1181
+ dev_rank: dev=0, no-dev=1
1182
+ Releases without a dev segment sort after those with one.
1183
+
1184
+ Local segments use ``(n, "")`` for ints and ``(-1, s)`` for strings,
1185
+ following PEP 440: strings sort before ints, strings compare
1186
+ lexicographically, ints compare numerically, and shorter segments
1187
+ sort before longer when prefixes match. Versions without a local
1188
+ segment sort before those with one (3-tuple < 4-tuple).
1189
+
1190
+ >>> _cmpkey(0, (1, 0, 0), None, None, None, None)
1191
+ (0, (1,), (3, 0, 0, 0, 1, 0))
1192
+ >>> _cmpkey(0, (1,), ("a", 1), None, None, None)
1193
+ (0, (1,), (0, 1, 0, 0, 1, 0))
1194
+ >>> _cmpkey(0, (1,), None, None, None, ("ubuntu", 1))
1195
+ (0, (1,), (3, 0, 0, 0, 1, 0), ((-1, 'ubuntu'), (1, '')))
1196
+ """
1197
+ # Strip trailing zeros: 1.0.0 compares equal to 1.
1198
+ len_release = len(release)
1199
+ i = len_release
1200
+ while i and release[i - 1] == 0:
1201
+ i -= 1
1202
+ trimmed = release if i == len_release else release[:i]
1203
+
1204
+ # Fast path: stable release with no local segment.
1205
+ if pre is None and post is None and dev is None and local is None:
1206
+ return epoch, trimmed, _STABLE_SUFFIX
1207
+
1208
+ if pre is None and post is None and dev is not None:
1209
+ # dev-only (e.g. 1.0.dev1) sorts before all pre-releases.
1210
+ pre_rank, pre_n = _PRE_RANK_DEV_ONLY, 0
1211
+ elif pre is None:
1212
+ pre_rank, pre_n = _PRE_RANK_STABLE, 0
1213
+ else:
1214
+ pre_rank, pre_n = _PRE_RANK[pre[0]], pre[1]
1215
+
1216
+ post_rank = 0 if post is None else 1
1217
+ post_n = 0 if post is None else post[1]
1218
+
1219
+ dev_rank = 1 if dev is None else 0
1220
+ dev_n = 0 if dev is None else dev[1]
1221
+
1222
+ suffix = (pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)
1223
+
1224
+ if local is None:
1225
+ return epoch, trimmed, suffix
1226
+
1227
+ cmp_local: CmpLocalType = tuple(
1228
+ (seg, "") if isinstance(seg, int) else (_LOCAL_STR_RANK, seg) for seg in local
1229
+ )
1230
+ return epoch, trimmed, suffix, cmp_local