Aluode/PerceptionLabPortable
0
1#2# distutils/version.py3#4# Implements multiple version numbering conventions for the5# Python Module Distribution Utilities.6#7# $Id$8#9 10"""Provides classes to represent module version numbers (one class for11each style of version numbering). There are currently two such classes12implemented: StrictVersion and LooseVersion.13 14Every version number class implements the following interface:15 * the 'parse' method takes a string and parses it to some internal16 representation; if the string is an invalid version number,17 'parse' raises a ValueError exception18 * the class constructor takes an optional string argument which,19 if supplied, is passed to 'parse'20 * __str__ reconstructs the string that was passed to 'parse' (or21 an equivalent string -- ie. one that will generate an equivalent22 version number instance)23 * __repr__ generates Python code to recreate the version number instance24 * _cmp compares the current instance with either another instance25 of the same class or a string (which will be parsed to an instance26 of the same class, thus must follow the same rules)27"""28 29import contextlib30import re31import warnings32 33 34@contextlib.contextmanager35def suppress_known_deprecation():36 with warnings.catch_warnings(record=True) as ctx:37 warnings.filterwarnings(38 action='default',39 category=DeprecationWarning,40 message="distutils Version classes are deprecated.",41 )42 yield ctx43 44 45class Version:46 """Abstract base class for version numbering classes. Just provides47 constructor (__init__) and reproducer (__repr__), because those48 seem to be the same for all version numbering classes; and route49 rich comparisons to _cmp.50 """51 52 def __init__(self, vstring=None):53 if vstring:54 self.parse(vstring)55 warnings.warn(56 "distutils Version classes are deprecated. Use packaging.version instead.",57 DeprecationWarning,58 stacklevel=2,59 )60 61 def __repr__(self):62 return f"{self.__class__.__name__} ('{self}')"63 64 def __eq__(self, other):65 c = self._cmp(other)66 if c is NotImplemented:67 return c68 return c == 069 70 def __lt__(self, other):71 c = self._cmp(other)72 if c is NotImplemented:73 return c74 return c < 075 76 def __le__(self, other):77 c = self._cmp(other)78 if c is NotImplemented:79 return c80 return c <= 081 82 def __gt__(self, other):83 c = self._cmp(other)84 if c is NotImplemented:85 return c86 return c > 087 88 def __ge__(self, other):89 c = self._cmp(other)90 if c is NotImplemented:91 return c92 return c >= 093 94 95# Interface for version-number classes -- must be implemented96# by the following classes (the concrete ones -- Version should97# be treated as an abstract class).98# __init__ (string) - create and take same action as 'parse'99# (string parameter is optional)100# parse (string) - convert a string representation to whatever101# internal representation is appropriate for102# this style of version numbering103# __str__ (self) - convert back to a string; should be very similar104# (if not identical to) the string supplied to parse105# __repr__ (self) - generate Python code to recreate106# the instance107# _cmp (self, other) - compare two version numbers ('other' may108# be an unparsed version string, or another109# instance of your version class)110 111 112class StrictVersion(Version):113 """Version numbering for anal retentives and software idealists.114 Implements the standard interface for version number classes as115 described above. A version number consists of two or three116 dot-separated numeric components, with an optional "pre-release" tag117 on the end. The pre-release tag consists of the letter 'a' or 'b'118 followed by a number. If the numeric components of two version119 numbers are equal, then one with a pre-release tag will always120 be deemed earlier (lesser) than one without.121 122 The following are valid version numbers (shown in the order that123 would be obtained by sorting according to the supplied cmp function):124 125 0.4 0.4.0 (these two are equivalent)126 0.4.1127 0.5a1128 0.5b3129 0.5130 0.9.6131 1.0132 1.0.4a3133 1.0.4b1134 1.0.4135 136 The following are examples of invalid version numbers:137 138 1139 2.7.2.2140 1.3.a4141 1.3pl1142 1.3c4143 144 The rationale for this version numbering system will be explained145 in the distutils documentation.146 """147 148 version_re = re.compile(149 r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', re.VERBOSE | re.ASCII150 )151 152 def parse(self, vstring):153 match = self.version_re.match(vstring)154 if not match:155 raise ValueError(f"invalid version number '{vstring}'")156 157 (major, minor, patch, prerelease, prerelease_num) = match.group(1, 2, 4, 5, 6)158 159 if patch:160 self.version = tuple(map(int, [major, minor, patch]))161 else:162 self.version = tuple(map(int, [major, minor])) + (0,)163 164 if prerelease:165 self.prerelease = (prerelease[0], int(prerelease_num))166 else:167 self.prerelease = None168 169 def __str__(self):170 if self.version[2] == 0:171 vstring = '.'.join(map(str, self.version[0:2]))172 else:173 vstring = '.'.join(map(str, self.version))174 175 if self.prerelease:176 vstring = vstring + self.prerelease[0] + str(self.prerelease[1])177 178 return vstring179 180 def _cmp(self, other):181 if isinstance(other, str):182 with suppress_known_deprecation():183 other = StrictVersion(other)184 elif not isinstance(other, StrictVersion):185 return NotImplemented186 187 if self.version == other.version:188 # versions match; pre-release drives the comparison189 return self._cmp_prerelease(other)190 191 return -1 if self.version < other.version else 1192 193 def _cmp_prerelease(self, other):194 """195 case 1: self has prerelease, other doesn't; other is greater196 case 2: self doesn't have prerelease, other does: self is greater197 case 3: both or neither have prerelease: compare them!198 """199 if self.prerelease and not other.prerelease:200 return -1201 elif not self.prerelease and other.prerelease:202 return 1203 204 if self.prerelease == other.prerelease:205 return 0206 elif self.prerelease < other.prerelease:207 return -1208 else:209 return 1210 211 212# end class StrictVersion213 214 215# The rules according to Greg Stein:216# 1) a version number has 1 or more numbers separated by a period or by217# sequences of letters. If only periods, then these are compared218# left-to-right to determine an ordering.219# 2) sequences of letters are part of the tuple for comparison and are220# compared lexicographically221# 3) recognize the numeric components may have leading zeroes222#223# The LooseVersion class below implements these rules: a version number224# string is split up into a tuple of integer and string components, and225# comparison is a simple tuple comparison. This means that version226# numbers behave in a predictable and obvious way, but a way that might227# not necessarily be how people *want* version numbers to behave. There228# wouldn't be a problem if people could stick to purely numeric version229# numbers: just split on period and compare the numbers as tuples.230# However, people insist on putting letters into their version numbers;231# the most common purpose seems to be:232# - indicating a "pre-release" version233# ('alpha', 'beta', 'a', 'b', 'pre', 'p')234# - indicating a post-release patch ('p', 'pl', 'patch')235# but of course this can't cover all version number schemes, and there's236# no way to know what a programmer means without asking him.237#238# The problem is what to do with letters (and other non-numeric239# characters) in a version number. The current implementation does the240# obvious and predictable thing: keep them as strings and compare241# lexically within a tuple comparison. This has the desired effect if242# an appended letter sequence implies something "post-release":243# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".244#245# However, if letters in a version number imply a pre-release version,246# the "obvious" thing isn't correct. Eg. you would expect that247# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison248# implemented here, this just isn't so.249#250# Two possible solutions come to mind. The first is to tie the251# comparison algorithm to a particular set of semantic rules, as has252# been done in the StrictVersion class above. This works great as long253# as everyone can go along with bondage and discipline. Hopefully a254# (large) subset of Python module programmers will agree that the255# particular flavour of bondage and discipline provided by StrictVersion256# provides enough benefit to be worth using, and will submit their257# version numbering scheme to its domination. The free-thinking258# anarchists in the lot will never give in, though, and something needs259# to be done to accommodate them.260#261# Perhaps a "moderately strict" version class could be implemented that262# lets almost anything slide (syntactically), and makes some heuristic263# assumptions about non-digits in version number strings. This could264# sink into special-case-hell, though; if I was as talented and265# idiosyncratic as Larry Wall, I'd go ahead and implement a class that266# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is267# just as happy dealing with things like "2g6" and "1.13++". I don't268# think I'm smart enough to do it right though.269#270# In any case, I've coded the test suite for this module (see271# ../test/test_version.py) specifically to fail on things like comparing272# "1.2a2" and "1.2". That's not because the *code* is doing anything273# wrong, it's because the simple, obvious design doesn't match my274# complicated, hairy expectations for real-world version numbers. It275# would be a snap to fix the test suite to say, "Yep, LooseVersion does276# the Right Thing" (ie. the code matches the conception). But I'd rather277# have a conception that matches common notions about version numbers.278 279 280class LooseVersion(Version):281 """Version numbering for anarchists and software realists.282 Implements the standard interface for version number classes as283 described above. A version number consists of a series of numbers,284 separated by either periods or strings of letters. When comparing285 version numbers, the numeric components will be compared286 numerically, and the alphabetic components lexically. The following287 are all valid version numbers, in no particular order:288 289 1.5.1290 1.5.2b2291 161292 3.10a293 8.02294 3.4j295 1996.07.12296 3.2.pl0297 3.1.1.6298 2g6299 11g300 0.960923301 2.2beta29302 1.13++303 5.5.kw304 2.0b1pl0305 306 In fact, there is no such thing as an invalid version number under307 this scheme; the rules for comparison are simple and predictable,308 but may not always give the results you want (for some definition309 of "want").310 """311 312 component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)313 314 def parse(self, vstring):315 # I've given up on thinking I can reconstruct the version string316 # from the parsed tuple -- so I just store the string here for317 # use by __str__318 self.vstring = vstring319 components = [x for x in self.component_re.split(vstring) if x and x != '.']320 for i, obj in enumerate(components):321 try:322 components[i] = int(obj)323 except ValueError:324 pass325 326 self.version = components327 328 def __str__(self):329 return self.vstring330 331 def __repr__(self):332 return f"LooseVersion ('{self}')"333 334 def _cmp(self, other):335 if isinstance(other, str):336 other = LooseVersion(other)337 elif not isinstance(other, LooseVersion):338 return NotImplemented339 340 if self.version == other.version:341 return 0342 if self.version < other.version:343 return -1344 if self.version > other.version:345 return 1346 347 348# end class LooseVersion349 