CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
version.py349 linesDownload Raw Back to _distutils
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 
Aluode/PerceptionLabPortable · CoolFace