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.
- nab_python/__init__.py +1 -0
- nab_python/_build/__init__.py +1 -0
- nab_python/_build/env.py +364 -0
- nab_python/_build/errors.py +17 -0
- nab_python/_build/runner.py +254 -0
- nab_python/_lockfile/__init__.py +1 -0
- nab_python/_lockfile/builder.py +339 -0
- nab_python/_lockfile/disjointness.py +207 -0
- nab_python/_lockfile/pylock.py +323 -0
- nab_python/_lockfile/requirements.py +121 -0
- nab_python/_packaging_provider.py +98 -0
- nab_python/_provider/__init__.py +1 -0
- nab_python/_provider/build_remote.py +95 -0
- nab_python/_provider/extras.py +231 -0
- nab_python/_provider/listing.py +442 -0
- nab_python/_provider/lookahead.py +156 -0
- nab_python/_provider/metadata_resolver.py +450 -0
- nab_python/_provider/priority.py +174 -0
- nab_python/_provider/sources.py +215 -0
- nab_python/_testing/__init__.py +1 -0
- nab_python/_testing/coordinator_fake.py +240 -0
- nab_python/_vcs_admission.py +209 -0
- nab_python/_vendor/__init__.py +6 -0
- nab_python/_vendor/packaging/LICENSE +3 -0
- nab_python/_vendor/packaging/LICENSE.APACHE +177 -0
- nab_python/_vendor/packaging/LICENSE.BSD +23 -0
- nab_python/_vendor/packaging/PROVENANCE.md +73 -0
- nab_python/_vendor/packaging/__init__.py +15 -0
- nab_python/_vendor/packaging/_elffile.py +108 -0
- nab_python/_vendor/packaging/_manylinux.py +265 -0
- nab_python/_vendor/packaging/_musllinux.py +88 -0
- nab_python/_vendor/packaging/_parser.py +394 -0
- nab_python/_vendor/packaging/_structures.py +33 -0
- nab_python/_vendor/packaging/_tokenizer.py +196 -0
- nab_python/_vendor/packaging/dependency_groups.py +302 -0
- nab_python/_vendor/packaging/direct_url.py +325 -0
- nab_python/_vendor/packaging/errors.py +94 -0
- nab_python/_vendor/packaging/licenses/__init__.py +186 -0
- nab_python/_vendor/packaging/licenses/_spdx.py +799 -0
- nab_python/_vendor/packaging/markers.py +506 -0
- nab_python/_vendor/packaging/metadata.py +964 -0
- nab_python/_vendor/packaging/py.typed +0 -0
- nab_python/_vendor/packaging/pylock.py +910 -0
- nab_python/_vendor/packaging/ranges.py +1803 -0
- nab_python/_vendor/packaging/requirements.py +132 -0
- nab_python/_vendor/packaging/specifiers.py +1141 -0
- nab_python/_vendor/packaging/tags.py +929 -0
- nab_python/_vendor/packaging/utils.py +296 -0
- nab_python/_vendor/packaging/version.py +1230 -0
- nab_python/build_backend.py +184 -0
- nab_python/config.py +805 -0
- nab_python/download.py +170 -0
- nab_python/fetch.py +827 -0
- nab_python/lockfile.py +238 -0
- nab_python/metadata.py +145 -0
- nab_python/provider.py +1235 -0
- nab_python/py.typed +0 -0
- nab_python/requirements_file.py +180 -0
- nab_python/resolve.py +497 -0
- nab_python/universal/__init__.py +1 -0
- nab_python/universal/matrix.py +235 -0
- nab_python/universal/provider.py +214 -0
- nab_python/universal/reresolve.py +310 -0
- nab_python/universal/resolve.py +508 -0
- nab_python/universal/validate.py +439 -0
- nab_python/universal/wheel_selection.py +327 -0
- nab_python/workspace.py +214 -0
- nab_python-0.0.1.dist-info/METADATA +49 -0
- nab_python-0.0.1.dist-info/RECORD +71 -0
- nab_python-0.0.1.dist-info/WHEEL +4 -0
- 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
|