|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| """Provides classes to represent module version numbers (one class for
|
| each style of version numbering). There are currently two such classes
|
| implemented: StrictVersion and LooseVersion.
|
|
|
| Every version number class implements the following interface:
|
| * the 'parse' method takes a string and parses it to some internal
|
| representation; if the string is an invalid version number,
|
| 'parse' raises a ValueError exception
|
| * the class constructor takes an optional string argument which,
|
| if supplied, is passed to 'parse'
|
| * __str__ reconstructs the string that was passed to 'parse' (or
|
| an equivalent string -- ie. one that will generate an equivalent
|
| version number instance)
|
| * __repr__ generates Python code to recreate the version number instance
|
| * _cmp compares the current instance with either another instance
|
| of the same class or a string (which will be parsed to an instance
|
| of the same class, thus must follow the same rules)
|
| """
|
|
|
| import re
|
| import warnings
|
| import contextlib
|
|
|
|
|
| @contextlib.contextmanager
|
| def suppress_known_deprecation():
|
| with warnings.catch_warnings(record=True) as ctx:
|
| warnings.filterwarnings(
|
| action='default',
|
| category=DeprecationWarning,
|
| message="distutils Version classes are deprecated.",
|
| )
|
| yield ctx
|
|
|
|
|
| class Version:
|
| """Abstract base class for version numbering classes. Just provides
|
| constructor (__init__) and reproducer (__repr__), because those
|
| seem to be the same for all version numbering classes; and route
|
| rich comparisons to _cmp.
|
| """
|
|
|
| def __init__(self, vstring=None):
|
| if vstring:
|
| self.parse(vstring)
|
| warnings.warn(
|
| "distutils Version classes are deprecated. "
|
| "Use packaging.version instead.",
|
| DeprecationWarning,
|
| stacklevel=2,
|
| )
|
|
|
| def __repr__(self):
|
| return "{} ('{}')".format(self.__class__.__name__, str(self))
|
|
|
| def __eq__(self, other):
|
| c = self._cmp(other)
|
| if c is NotImplemented:
|
| return c
|
| return c == 0
|
|
|
| def __lt__(self, other):
|
| c = self._cmp(other)
|
| if c is NotImplemented:
|
| return c
|
| return c < 0
|
|
|
| def __le__(self, other):
|
| c = self._cmp(other)
|
| if c is NotImplemented:
|
| return c
|
| return c <= 0
|
|
|
| def __gt__(self, other):
|
| c = self._cmp(other)
|
| if c is NotImplemented:
|
| return c
|
| return c > 0
|
|
|
| def __ge__(self, other):
|
| c = self._cmp(other)
|
| if c is NotImplemented:
|
| return c
|
| return c >= 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| class StrictVersion(Version):
|
|
|
| """Version numbering for anal retentives and software idealists.
|
| Implements the standard interface for version number classes as
|
| described above. A version number consists of two or three
|
| dot-separated numeric components, with an optional "pre-release" tag
|
| on the end. The pre-release tag consists of the letter 'a' or 'b'
|
| followed by a number. If the numeric components of two version
|
| numbers are equal, then one with a pre-release tag will always
|
| be deemed earlier (lesser) than one without.
|
|
|
| The following are valid version numbers (shown in the order that
|
| would be obtained by sorting according to the supplied cmp function):
|
|
|
| 0.4 0.4.0 (these two are equivalent)
|
| 0.4.1
|
| 0.5a1
|
| 0.5b3
|
| 0.5
|
| 0.9.6
|
| 1.0
|
| 1.0.4a3
|
| 1.0.4b1
|
| 1.0.4
|
|
|
| The following are examples of invalid version numbers:
|
|
|
| 1
|
| 2.7.2.2
|
| 1.3.a4
|
| 1.3pl1
|
| 1.3c4
|
|
|
| The rationale for this version numbering system will be explained
|
| in the distutils documentation.
|
| """
|
|
|
| version_re = re.compile(
|
| r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', re.VERBOSE | re.ASCII
|
| )
|
|
|
| def parse(self, vstring):
|
| match = self.version_re.match(vstring)
|
| if not match:
|
| raise ValueError("invalid version number '%s'" % vstring)
|
|
|
| (major, minor, patch, prerelease, prerelease_num) = match.group(1, 2, 4, 5, 6)
|
|
|
| if patch:
|
| self.version = tuple(map(int, [major, minor, patch]))
|
| else:
|
| self.version = tuple(map(int, [major, minor])) + (0,)
|
|
|
| if prerelease:
|
| self.prerelease = (prerelease[0], int(prerelease_num))
|
| else:
|
| self.prerelease = None
|
|
|
| def __str__(self):
|
|
|
| if self.version[2] == 0:
|
| vstring = '.'.join(map(str, self.version[0:2]))
|
| else:
|
| vstring = '.'.join(map(str, self.version))
|
|
|
| if self.prerelease:
|
| vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
|
|
|
| return vstring
|
|
|
| def _cmp(self, other):
|
| if isinstance(other, str):
|
| with suppress_known_deprecation():
|
| other = StrictVersion(other)
|
| elif not isinstance(other, StrictVersion):
|
| return NotImplemented
|
|
|
| if self.version != other.version:
|
|
|
|
|
| if self.version < other.version:
|
| return -1
|
| else:
|
| return 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| if not self.prerelease and not other.prerelease:
|
| return 0
|
| elif self.prerelease and not other.prerelease:
|
| return -1
|
| elif not self.prerelease and other.prerelease:
|
| return 1
|
| elif self.prerelease and other.prerelease:
|
| if self.prerelease == other.prerelease:
|
| return 0
|
| elif self.prerelease < other.prerelease:
|
| return -1
|
| else:
|
| return 1
|
| else:
|
| assert False, "never get here"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| class LooseVersion(Version):
|
|
|
| """Version numbering for anarchists and software realists.
|
| Implements the standard interface for version number classes as
|
| described above. A version number consists of a series of numbers,
|
| separated by either periods or strings of letters. When comparing
|
| version numbers, the numeric components will be compared
|
| numerically, and the alphabetic components lexically. The following
|
| are all valid version numbers, in no particular order:
|
|
|
| 1.5.1
|
| 1.5.2b2
|
| 161
|
| 3.10a
|
| 8.02
|
| 3.4j
|
| 1996.07.12
|
| 3.2.pl0
|
| 3.1.1.6
|
| 2g6
|
| 11g
|
| 0.960923
|
| 2.2beta29
|
| 1.13++
|
| 5.5.kw
|
| 2.0b1pl0
|
|
|
| In fact, there is no such thing as an invalid version number under
|
| this scheme; the rules for comparison are simple and predictable,
|
| but may not always give the results you want (for some definition
|
| of "want").
|
| """
|
|
|
| component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
|
|
|
| def parse(self, vstring):
|
|
|
|
|
|
|
| self.vstring = vstring
|
| components = [x for x in self.component_re.split(vstring) if x and x != '.']
|
| for i, obj in enumerate(components):
|
| try:
|
| components[i] = int(obj)
|
| except ValueError:
|
| pass
|
|
|
| self.version = components
|
|
|
| def __str__(self):
|
| return self.vstring
|
|
|
| def __repr__(self):
|
| return "LooseVersion ('%s')" % str(self)
|
|
|
| def _cmp(self, other):
|
| if isinstance(other, str):
|
| other = LooseVersion(other)
|
| elif not isinstance(other, LooseVersion):
|
| return NotImplemented
|
|
|
| if self.version == other.version:
|
| return 0
|
| if self.version < other.version:
|
| return -1
|
| if self.version > other.version:
|
| return 1
|
|
|
|
|
|
|
|
|