Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/numpy/compat/_pep440.py: 32%
182 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""Utility to compare pep440 compatible version strings.
3The LooseVersion and StrictVersion classes that distutils provides don't
4work; they don't recognize anything like alpha/beta/rc/dev versions.
5"""
7# Copyright (c) Donald Stufft and individual contributors.
8# All rights reserved.
10# Redistribution and use in source and binary forms, with or without
11# modification, are permitted provided that the following conditions are met:
13# 1. Redistributions of source code must retain the above copyright notice,
14# this list of conditions and the following disclaimer.
16# 2. Redistributions in binary form must reproduce the above copyright
17# notice, this list of conditions and the following disclaimer in the
18# documentation and/or other materials provided with the distribution.
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30# POSSIBILITY OF SUCH DAMAGE.
32import collections
33import itertools
34import re
37__all__ = [
38 "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN",
39]
42# BEGIN packaging/_structures.py
45class Infinity:
46 def __repr__(self):
47 return "Infinity"
49 def __hash__(self):
50 return hash(repr(self))
52 def __lt__(self, other):
53 return False
55 def __le__(self, other):
56 return False
58 def __eq__(self, other):
59 return isinstance(other, self.__class__)
61 def __ne__(self, other):
62 return not isinstance(other, self.__class__)
64 def __gt__(self, other):
65 return True
67 def __ge__(self, other):
68 return True
70 def __neg__(self):
71 return NegativeInfinity
74Infinity = Infinity()
77class NegativeInfinity:
78 def __repr__(self):
79 return "-Infinity"
81 def __hash__(self):
82 return hash(repr(self))
84 def __lt__(self, other):
85 return True
87 def __le__(self, other):
88 return True
90 def __eq__(self, other):
91 return isinstance(other, self.__class__)
93 def __ne__(self, other):
94 return not isinstance(other, self.__class__)
96 def __gt__(self, other):
97 return False
99 def __ge__(self, other):
100 return False
102 def __neg__(self):
103 return Infinity
106# BEGIN packaging/version.py
109NegativeInfinity = NegativeInfinity()
111_Version = collections.namedtuple(
112 "_Version",
113 ["epoch", "release", "dev", "pre", "post", "local"],
114)
117def parse(version):
118 """
119 Parse the given version string and return either a :class:`Version` object
120 or a :class:`LegacyVersion` object depending on if the given version is
121 a valid PEP 440 version or a legacy version.
122 """
123 try:
124 return Version(version)
125 except InvalidVersion:
126 return LegacyVersion(version)
129class InvalidVersion(ValueError):
130 """
131 An invalid version was found, users should refer to PEP 440.
132 """
135class _BaseVersion:
137 def __hash__(self):
138 return hash(self._key)
140 def __lt__(self, other):
141 return self._compare(other, lambda s, o: s < o)
143 def __le__(self, other):
144 return self._compare(other, lambda s, o: s <= o)
146 def __eq__(self, other):
147 return self._compare(other, lambda s, o: s == o)
149 def __ge__(self, other):
150 return self._compare(other, lambda s, o: s >= o)
152 def __gt__(self, other):
153 return self._compare(other, lambda s, o: s > o)
155 def __ne__(self, other):
156 return self._compare(other, lambda s, o: s != o)
158 def _compare(self, other, method):
159 if not isinstance(other, _BaseVersion):
160 return NotImplemented
162 return method(self._key, other._key)
165class LegacyVersion(_BaseVersion):
167 def __init__(self, version):
168 self._version = str(version)
169 self._key = _legacy_cmpkey(self._version)
171 def __str__(self):
172 return self._version
174 def __repr__(self):
175 return "<LegacyVersion({0})>".format(repr(str(self)))
177 @property
178 def public(self):
179 return self._version
181 @property
182 def base_version(self):
183 return self._version
185 @property
186 def local(self):
187 return None
189 @property
190 def is_prerelease(self):
191 return False
193 @property
194 def is_postrelease(self):
195 return False
198_legacy_version_component_re = re.compile(
199 r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
200)
202_legacy_version_replacement_map = {
203 "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
204}
207def _parse_version_parts(s):
208 for part in _legacy_version_component_re.split(s):
209 part = _legacy_version_replacement_map.get(part, part)
211 if not part or part == ".":
212 continue
214 if part[:1] in "0123456789":
215 # pad for numeric comparison
216 yield part.zfill(8)
217 else:
218 yield "*" + part
220 # ensure that alpha/beta/candidate are before final
221 yield "*final"
224def _legacy_cmpkey(version):
225 # We hardcode an epoch of -1 here. A PEP 440 version can only have an epoch
226 # greater than or equal to 0. This will effectively put the LegacyVersion,
227 # which uses the defacto standard originally implemented by setuptools,
228 # as before all PEP 440 versions.
229 epoch = -1
231 # This scheme is taken from pkg_resources.parse_version setuptools prior to
232 # its adoption of the packaging library.
233 parts = []
234 for part in _parse_version_parts(version.lower()):
235 if part.startswith("*"):
236 # remove "-" before a prerelease tag
237 if part < "*final":
238 while parts and parts[-1] == "*final-":
239 parts.pop()
241 # remove trailing zeros from each series of numeric parts
242 while parts and parts[-1] == "00000000":
243 parts.pop()
245 parts.append(part)
246 parts = tuple(parts)
248 return epoch, parts
251# Deliberately not anchored to the start and end of the string, to make it
252# easier for 3rd party code to reuse
253VERSION_PATTERN = r"""
254 v?
255 (?:
256 (?:(?P<epoch>[0-9]+)!)? # epoch
257 (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
258 (?P<pre> # pre-release
259 [-_\.]?
260 (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
261 [-_\.]?
262 (?P<pre_n>[0-9]+)?
263 )?
264 (?P<post> # post release
265 (?:-(?P<post_n1>[0-9]+))
266 |
267 (?:
268 [-_\.]?
269 (?P<post_l>post|rev|r)
270 [-_\.]?
271 (?P<post_n2>[0-9]+)?
272 )
273 )?
274 (?P<dev> # dev release
275 [-_\.]?
276 (?P<dev_l>dev)
277 [-_\.]?
278 (?P<dev_n>[0-9]+)?
279 )?
280 )
281 (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
282"""
285class Version(_BaseVersion):
287 _regex = re.compile(
288 r"^\s*" + VERSION_PATTERN + r"\s*$",
289 re.VERBOSE | re.IGNORECASE,
290 )
292 def __init__(self, version):
293 # Validate the version and parse it into pieces
294 match = self._regex.search(version)
295 if not match:
296 raise InvalidVersion("Invalid version: '{0}'".format(version))
298 # Store the parsed out pieces of the version
299 self._version = _Version(
300 epoch=int(match.group("epoch")) if match.group("epoch") else 0,
301 release=tuple(int(i) for i in match.group("release").split(".")),
302 pre=_parse_letter_version(
303 match.group("pre_l"),
304 match.group("pre_n"),
305 ),
306 post=_parse_letter_version(
307 match.group("post_l"),
308 match.group("post_n1") or match.group("post_n2"),
309 ),
310 dev=_parse_letter_version(
311 match.group("dev_l"),
312 match.group("dev_n"),
313 ),
314 local=_parse_local_version(match.group("local")),
315 )
317 # Generate a key which will be used for sorting
318 self._key = _cmpkey(
319 self._version.epoch,
320 self._version.release,
321 self._version.pre,
322 self._version.post,
323 self._version.dev,
324 self._version.local,
325 )
327 def __repr__(self):
328 return "<Version({0})>".format(repr(str(self)))
330 def __str__(self):
331 parts = []
333 # Epoch
334 if self._version.epoch != 0:
335 parts.append("{0}!".format(self._version.epoch))
337 # Release segment
338 parts.append(".".join(str(x) for x in self._version.release))
340 # Pre-release
341 if self._version.pre is not None:
342 parts.append("".join(str(x) for x in self._version.pre))
344 # Post-release
345 if self._version.post is not None:
346 parts.append(".post{0}".format(self._version.post[1]))
348 # Development release
349 if self._version.dev is not None:
350 parts.append(".dev{0}".format(self._version.dev[1]))
352 # Local version segment
353 if self._version.local is not None:
354 parts.append(
355 "+{0}".format(".".join(str(x) for x in self._version.local))
356 )
358 return "".join(parts)
360 @property
361 def public(self):
362 return str(self).split("+", 1)[0]
364 @property
365 def base_version(self):
366 parts = []
368 # Epoch
369 if self._version.epoch != 0:
370 parts.append("{0}!".format(self._version.epoch))
372 # Release segment
373 parts.append(".".join(str(x) for x in self._version.release))
375 return "".join(parts)
377 @property
378 def local(self):
379 version_string = str(self)
380 if "+" in version_string:
381 return version_string.split("+", 1)[1]
383 @property
384 def is_prerelease(self):
385 return bool(self._version.dev or self._version.pre)
387 @property
388 def is_postrelease(self):
389 return bool(self._version.post)
392def _parse_letter_version(letter, number):
393 if letter:
394 # We assume there is an implicit 0 in a pre-release if there is
395 # no numeral associated with it.
396 if number is None:
397 number = 0
399 # We normalize any letters to their lower-case form
400 letter = letter.lower()
402 # We consider some words to be alternate spellings of other words and
403 # in those cases we want to normalize the spellings to our preferred
404 # spelling.
405 if letter == "alpha":
406 letter = "a"
407 elif letter == "beta":
408 letter = "b"
409 elif letter in ["c", "pre", "preview"]:
410 letter = "rc"
411 elif letter in ["rev", "r"]:
412 letter = "post"
414 return letter, int(number)
415 if not letter and number:
416 # We assume that if we are given a number but not given a letter,
417 # then this is using the implicit post release syntax (e.g., 1.0-1)
418 letter = "post"
420 return letter, int(number)
423_local_version_seperators = re.compile(r"[\._-]")
426def _parse_local_version(local):
427 """
428 Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
429 """
430 if local is not None:
431 return tuple(
432 part.lower() if not part.isdigit() else int(part)
433 for part in _local_version_seperators.split(local)
434 )
437def _cmpkey(epoch, release, pre, post, dev, local):
438 # When we compare a release version, we want to compare it with all of the
439 # trailing zeros removed. So we'll use a reverse the list, drop all the now
440 # leading zeros until we come to something non-zero, then take the rest,
441 # re-reverse it back into the correct order, and make it a tuple and use
442 # that for our sorting key.
443 release = tuple(
444 reversed(list(
445 itertools.dropwhile(
446 lambda x: x == 0,
447 reversed(release),
448 )
449 ))
450 )
452 # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
453 # We'll do this by abusing the pre-segment, but we _only_ want to do this
454 # if there is no pre- or a post-segment. If we have one of those, then
455 # the normal sorting rules will handle this case correctly.
456 if pre is None and post is None and dev is not None:
457 pre = -Infinity
458 # Versions without a pre-release (except as noted above) should sort after
459 # those with one.
460 elif pre is None:
461 pre = Infinity
463 # Versions without a post-segment should sort before those with one.
464 if post is None:
465 post = -Infinity
467 # Versions without a development segment should sort after those with one.
468 if dev is None:
469 dev = Infinity
471 if local is None:
472 # Versions without a local segment should sort before those with one.
473 local = -Infinity
474 else:
475 # Versions with a local segment need that segment parsed to implement
476 # the sorting rules in PEP440.
477 # - Alphanumeric segments sort before numeric segments
478 # - Alphanumeric segments sort lexicographically
479 # - Numeric segments sort numerically
480 # - Shorter versions sort before longer versions when the prefixes
481 # match exactly
482 local = tuple(
483 (i, "") if isinstance(i, int) else (-Infinity, i)
484 for i in local
485 )
487 return epoch, release, pre, post, dev, local