comparison env/lib/python3.9/site-packages/packaging/version.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
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 from __future__ import absolute_import, division, print_function
5
6 import collections
7 import itertools
8 import re
9 import warnings
10
11 from ._structures import Infinity, NegativeInfinity
12 from ._typing import TYPE_CHECKING
13
14 if TYPE_CHECKING: # pragma: no cover
15 from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union
16
17 from ._structures import InfinityType, NegativeInfinityType
18
19 InfiniteTypes = Union[InfinityType, NegativeInfinityType]
20 PrePostDevType = Union[InfiniteTypes, Tuple[str, int]]
21 SubLocalType = Union[InfiniteTypes, int, str]
22 LocalType = Union[
23 NegativeInfinityType,
24 Tuple[
25 Union[
26 SubLocalType,
27 Tuple[SubLocalType, str],
28 Tuple[NegativeInfinityType, SubLocalType],
29 ],
30 ...,
31 ],
32 ]
33 CmpKey = Tuple[
34 int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType
35 ]
36 LegacyCmpKey = Tuple[int, Tuple[str, ...]]
37 VersionComparisonMethod = Callable[
38 [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool
39 ]
40
41 __all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"]
42
43
44 _Version = collections.namedtuple(
45 "_Version", ["epoch", "release", "dev", "pre", "post", "local"]
46 )
47
48
49 def parse(version):
50 # type: (str) -> Union[LegacyVersion, Version]
51 """
52 Parse the given version string and return either a :class:`Version` object
53 or a :class:`LegacyVersion` object depending on if the given version is
54 a valid PEP 440 version or a legacy version.
55 """
56 try:
57 return Version(version)
58 except InvalidVersion:
59 return LegacyVersion(version)
60
61
62 class InvalidVersion(ValueError):
63 """
64 An invalid version was found, users should refer to PEP 440.
65 """
66
67
68 class _BaseVersion(object):
69 _key = None # type: Union[CmpKey, LegacyCmpKey]
70
71 def __hash__(self):
72 # type: () -> int
73 return hash(self._key)
74
75 # Please keep the duplicated `isinstance` check
76 # in the six comparisons hereunder
77 # unless you find a way to avoid adding overhead function calls.
78 def __lt__(self, other):
79 # type: (_BaseVersion) -> bool
80 if not isinstance(other, _BaseVersion):
81 return NotImplemented
82
83 return self._key < other._key
84
85 def __le__(self, other):
86 # type: (_BaseVersion) -> bool
87 if not isinstance(other, _BaseVersion):
88 return NotImplemented
89
90 return self._key <= other._key
91
92 def __eq__(self, other):
93 # type: (object) -> bool
94 if not isinstance(other, _BaseVersion):
95 return NotImplemented
96
97 return self._key == other._key
98
99 def __ge__(self, other):
100 # type: (_BaseVersion) -> bool
101 if not isinstance(other, _BaseVersion):
102 return NotImplemented
103
104 return self._key >= other._key
105
106 def __gt__(self, other):
107 # type: (_BaseVersion) -> bool
108 if not isinstance(other, _BaseVersion):
109 return NotImplemented
110
111 return self._key > other._key
112
113 def __ne__(self, other):
114 # type: (object) -> bool
115 if not isinstance(other, _BaseVersion):
116 return NotImplemented
117
118 return self._key != other._key
119
120
121 class LegacyVersion(_BaseVersion):
122 def __init__(self, version):
123 # type: (str) -> None
124 self._version = str(version)
125 self._key = _legacy_cmpkey(self._version)
126
127 warnings.warn(
128 "Creating a LegacyVersion has been deprecated and will be "
129 "removed in the next major release",
130 DeprecationWarning,
131 )
132
133 def __str__(self):
134 # type: () -> str
135 return self._version
136
137 def __repr__(self):
138 # type: () -> str
139 return "<LegacyVersion({0})>".format(repr(str(self)))
140
141 @property
142 def public(self):
143 # type: () -> str
144 return self._version
145
146 @property
147 def base_version(self):
148 # type: () -> str
149 return self._version
150
151 @property
152 def epoch(self):
153 # type: () -> int
154 return -1
155
156 @property
157 def release(self):
158 # type: () -> None
159 return None
160
161 @property
162 def pre(self):
163 # type: () -> None
164 return None
165
166 @property
167 def post(self):
168 # type: () -> None
169 return None
170
171 @property
172 def dev(self):
173 # type: () -> None
174 return None
175
176 @property
177 def local(self):
178 # type: () -> None
179 return None
180
181 @property
182 def is_prerelease(self):
183 # type: () -> bool
184 return False
185
186 @property
187 def is_postrelease(self):
188 # type: () -> bool
189 return False
190
191 @property
192 def is_devrelease(self):
193 # type: () -> bool
194 return False
195
196
197 _legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
198
199 _legacy_version_replacement_map = {
200 "pre": "c",
201 "preview": "c",
202 "-": "final-",
203 "rc": "c",
204 "dev": "@",
205 }
206
207
208 def _parse_version_parts(s):
209 # type: (str) -> Iterator[str]
210 for part in _legacy_version_component_re.split(s):
211 part = _legacy_version_replacement_map.get(part, part)
212
213 if not part or part == ".":
214 continue
215
216 if part[:1] in "0123456789":
217 # pad for numeric comparison
218 yield part.zfill(8)
219 else:
220 yield "*" + part
221
222 # ensure that alpha/beta/candidate are before final
223 yield "*final"
224
225
226 def _legacy_cmpkey(version):
227 # type: (str) -> LegacyCmpKey
228
229 # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
230 # greater than or equal to 0. This will effectively put the LegacyVersion,
231 # which uses the defacto standard originally implemented by setuptools,
232 # as before all PEP 440 versions.
233 epoch = -1
234
235 # This scheme is taken from pkg_resources.parse_version setuptools prior to
236 # it's adoption of the packaging library.
237 parts = [] # type: List[str]
238 for part in _parse_version_parts(version.lower()):
239 if part.startswith("*"):
240 # remove "-" before a prerelease tag
241 if part < "*final":
242 while parts and parts[-1] == "*final-":
243 parts.pop()
244
245 # remove trailing zeros from each series of numeric parts
246 while parts and parts[-1] == "00000000":
247 parts.pop()
248
249 parts.append(part)
250
251 return epoch, tuple(parts)
252
253
254 # Deliberately not anchored to the start and end of the string, to make it
255 # easier for 3rd party code to reuse
256 VERSION_PATTERN = r"""
257 v?
258 (?:
259 (?:(?P<epoch>[0-9]+)!)? # epoch
260 (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
261 (?P<pre> # pre-release
262 [-_\.]?
263 (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
264 [-_\.]?
265 (?P<pre_n>[0-9]+)?
266 )?
267 (?P<post> # post release
268 (?:-(?P<post_n1>[0-9]+))
269 |
270 (?:
271 [-_\.]?
272 (?P<post_l>post|rev|r)
273 [-_\.]?
274 (?P<post_n2>[0-9]+)?
275 )
276 )?
277 (?P<dev> # dev release
278 [-_\.]?
279 (?P<dev_l>dev)
280 [-_\.]?
281 (?P<dev_n>[0-9]+)?
282 )?
283 )
284 (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
285 """
286
287
288 class Version(_BaseVersion):
289
290 _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
291
292 def __init__(self, version):
293 # type: (str) -> None
294
295 # Validate the version and parse it into pieces
296 match = self._regex.search(version)
297 if not match:
298 raise InvalidVersion("Invalid version: '{0}'".format(version))
299
300 # Store the parsed out pieces of the version
301 self._version = _Version(
302 epoch=int(match.group("epoch")) if match.group("epoch") else 0,
303 release=tuple(int(i) for i in match.group("release").split(".")),
304 pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
305 post=_parse_letter_version(
306 match.group("post_l"), match.group("post_n1") or match.group("post_n2")
307 ),
308 dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
309 local=_parse_local_version(match.group("local")),
310 )
311
312 # Generate a key which will be used for sorting
313 self._key = _cmpkey(
314 self._version.epoch,
315 self._version.release,
316 self._version.pre,
317 self._version.post,
318 self._version.dev,
319 self._version.local,
320 )
321
322 def __repr__(self):
323 # type: () -> str
324 return "<Version({0})>".format(repr(str(self)))
325
326 def __str__(self):
327 # type: () -> str
328 parts = []
329
330 # Epoch
331 if self.epoch != 0:
332 parts.append("{0}!".format(self.epoch))
333
334 # Release segment
335 parts.append(".".join(str(x) for x in self.release))
336
337 # Pre-release
338 if self.pre is not None:
339 parts.append("".join(str(x) for x in self.pre))
340
341 # Post-release
342 if self.post is not None:
343 parts.append(".post{0}".format(self.post))
344
345 # Development release
346 if self.dev is not None:
347 parts.append(".dev{0}".format(self.dev))
348
349 # Local version segment
350 if self.local is not None:
351 parts.append("+{0}".format(self.local))
352
353 return "".join(parts)
354
355 @property
356 def epoch(self):
357 # type: () -> int
358 _epoch = self._version.epoch # type: int
359 return _epoch
360
361 @property
362 def release(self):
363 # type: () -> Tuple[int, ...]
364 _release = self._version.release # type: Tuple[int, ...]
365 return _release
366
367 @property
368 def pre(self):
369 # type: () -> Optional[Tuple[str, int]]
370 _pre = self._version.pre # type: Optional[Tuple[str, int]]
371 return _pre
372
373 @property
374 def post(self):
375 # type: () -> Optional[Tuple[str, int]]
376 return self._version.post[1] if self._version.post else None
377
378 @property
379 def dev(self):
380 # type: () -> Optional[Tuple[str, int]]
381 return self._version.dev[1] if self._version.dev else None
382
383 @property
384 def local(self):
385 # type: () -> Optional[str]
386 if self._version.local:
387 return ".".join(str(x) for x in self._version.local)
388 else:
389 return None
390
391 @property
392 def public(self):
393 # type: () -> str
394 return str(self).split("+", 1)[0]
395
396 @property
397 def base_version(self):
398 # type: () -> str
399 parts = []
400
401 # Epoch
402 if self.epoch != 0:
403 parts.append("{0}!".format(self.epoch))
404
405 # Release segment
406 parts.append(".".join(str(x) for x in self.release))
407
408 return "".join(parts)
409
410 @property
411 def is_prerelease(self):
412 # type: () -> bool
413 return self.dev is not None or self.pre is not None
414
415 @property
416 def is_postrelease(self):
417 # type: () -> bool
418 return self.post is not None
419
420 @property
421 def is_devrelease(self):
422 # type: () -> bool
423 return self.dev is not None
424
425 @property
426 def major(self):
427 # type: () -> int
428 return self.release[0] if len(self.release) >= 1 else 0
429
430 @property
431 def minor(self):
432 # type: () -> int
433 return self.release[1] if len(self.release) >= 2 else 0
434
435 @property
436 def micro(self):
437 # type: () -> int
438 return self.release[2] if len(self.release) >= 3 else 0
439
440
441 def _parse_letter_version(
442 letter, # type: str
443 number, # type: Union[str, bytes, SupportsInt]
444 ):
445 # type: (...) -> Optional[Tuple[str, int]]
446
447 if letter:
448 # We consider there to be an implicit 0 in a pre-release if there is
449 # not a numeral associated with it.
450 if number is None:
451 number = 0
452
453 # We normalize any letters to their lower case form
454 letter = letter.lower()
455
456 # We consider some words to be alternate spellings of other words and
457 # in those cases we want to normalize the spellings to our preferred
458 # spelling.
459 if letter == "alpha":
460 letter = "a"
461 elif letter == "beta":
462 letter = "b"
463 elif letter in ["c", "pre", "preview"]:
464 letter = "rc"
465 elif letter in ["rev", "r"]:
466 letter = "post"
467
468 return letter, int(number)
469 if not letter and number:
470 # We assume if we are given a number, but we are not given a letter
471 # then this is using the implicit post release syntax (e.g. 1.0-1)
472 letter = "post"
473
474 return letter, int(number)
475
476 return None
477
478
479 _local_version_separators = re.compile(r"[\._-]")
480
481
482 def _parse_local_version(local):
483 # type: (str) -> Optional[LocalType]
484 """
485 Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
486 """
487 if local is not None:
488 return tuple(
489 part.lower() if not part.isdigit() else int(part)
490 for part in _local_version_separators.split(local)
491 )
492 return None
493
494
495 def _cmpkey(
496 epoch, # type: int
497 release, # type: Tuple[int, ...]
498 pre, # type: Optional[Tuple[str, int]]
499 post, # type: Optional[Tuple[str, int]]
500 dev, # type: Optional[Tuple[str, int]]
501 local, # type: Optional[Tuple[SubLocalType]]
502 ):
503 # type: (...) -> CmpKey
504
505 # When we compare a release version, we want to compare it with all of the
506 # trailing zeros removed. So we'll use a reverse the list, drop all the now
507 # leading zeros until we come to something non zero, then take the rest
508 # re-reverse it back into the correct order and make it a tuple and use
509 # that for our sorting key.
510 _release = tuple(
511 reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
512 )
513
514 # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
515 # We'll do this by abusing the pre segment, but we _only_ want to do this
516 # if there is not a pre or a post segment. If we have one of those then
517 # the normal sorting rules will handle this case correctly.
518 if pre is None and post is None and dev is not None:
519 _pre = NegativeInfinity # type: PrePostDevType
520 # Versions without a pre-release (except as noted above) should sort after
521 # those with one.
522 elif pre is None:
523 _pre = Infinity
524 else:
525 _pre = pre
526
527 # Versions without a post segment should sort before those with one.
528 if post is None:
529 _post = NegativeInfinity # type: PrePostDevType
530
531 else:
532 _post = post
533
534 # Versions without a development segment should sort after those with one.
535 if dev is None:
536 _dev = Infinity # type: PrePostDevType
537
538 else:
539 _dev = dev
540
541 if local is None:
542 # Versions without a local segment should sort before those with one.
543 _local = NegativeInfinity # type: LocalType
544 else:
545 # Versions with a local segment need that segment parsed to implement
546 # the sorting rules in PEP440.
547 # - Alpha numeric segments sort before numeric segments
548 # - Alpha numeric segments sort lexicographically
549 # - Numeric segments sort numerically
550 # - Shorter versions sort before longer versions when the prefixes
551 # match exactly
552 _local = tuple(
553 (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
554 )
555
556 return epoch, _release, _pre, _post, _dev, _local