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