Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
version.py350 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. "57            "Use packaging.version instead.",58            DeprecationWarning,59            stacklevel=2,60        )61 62    def __repr__(self):63        return f"{self.__class__.__name__} ('{str(self)}')"64 65    def __eq__(self, other):66        c = self._cmp(other)67        if c is NotImplemented:68            return c69        return c == 070 71    def __lt__(self, other):72        c = self._cmp(other)73        if c is NotImplemented:74            return c75        return c < 076 77    def __le__(self, other):78        c = self._cmp(other)79        if c is NotImplemented:80            return c81        return c <= 082 83    def __gt__(self, other):84        c = self._cmp(other)85        if c is NotImplemented:86            return c87        return c > 088 89    def __ge__(self, other):90        c = self._cmp(other)91        if c is NotImplemented:92            return c93        return c >= 094 95 96# Interface for version-number classes -- must be implemented97# by the following classes (the concrete ones -- Version should98# be treated as an abstract class).99#    __init__ (string) - create and take same action as 'parse'100#                        (string parameter is optional)101#    parse (string)    - convert a string representation to whatever102#                        internal representation is appropriate for103#                        this style of version numbering104#    __str__ (self)    - convert back to a string; should be very similar105#                        (if not identical to) the string supplied to parse106#    __repr__ (self)   - generate Python code to recreate107#                        the instance108#    _cmp (self, other) - compare two version numbers ('other' may109#                        be an unparsed version string, or another110#                        instance of your version class)111 112 113class StrictVersion(Version):114    """Version numbering for anal retentives and software idealists.115    Implements the standard interface for version number classes as116    described above.  A version number consists of two or three117    dot-separated numeric components, with an optional "pre-release" tag118    on the end.  The pre-release tag consists of the letter 'a' or 'b'119    followed by a number.  If the numeric components of two version120    numbers are equal, then one with a pre-release tag will always121    be deemed earlier (lesser) than one without.122 123    The following are valid version numbers (shown in the order that124    would be obtained by sorting according to the supplied cmp function):125 126        0.4       0.4.0  (these two are equivalent)127        0.4.1128        0.5a1129        0.5b3130        0.5131        0.9.6132        1.0133        1.0.4a3134        1.0.4b1135        1.0.4136 137    The following are examples of invalid version numbers:138 139        1140        2.7.2.2141        1.3.a4142        1.3pl1143        1.3c4144 145    The rationale for this version numbering system will be explained146    in the distutils documentation.147    """148 149    version_re = re.compile(150        r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', re.VERBOSE | re.ASCII151    )152 153    def parse(self, vstring):154        match = self.version_re.match(vstring)155        if not match:156            raise ValueError("invalid version number '%s'" % vstring)157 158        (major, minor, patch, prerelease, prerelease_num) = match.group(1, 2, 4, 5, 6)159 160        if patch:161            self.version = tuple(map(int, [major, minor, patch]))162        else:163            self.version = tuple(map(int, [major, minor])) + (0,)164 165        if prerelease:166            self.prerelease = (prerelease[0], int(prerelease_num))167        else:168            self.prerelease = None169 170    def __str__(self):171        if self.version[2] == 0:172            vstring = '.'.join(map(str, self.version[0:2]))173        else:174            vstring = '.'.join(map(str, self.version))175 176        if self.prerelease:177            vstring = vstring + self.prerelease[0] + str(self.prerelease[1])178 179        return vstring180 181    def _cmp(self, other):182        if isinstance(other, str):183            with suppress_known_deprecation():184                other = StrictVersion(other)185        elif not isinstance(other, StrictVersion):186            return NotImplemented187 188        if self.version == other.version:189            # versions match; pre-release drives the comparison190            return self._cmp_prerelease(other)191 192        return -1 if self.version < other.version else 1193 194    def _cmp_prerelease(self, other):195        """196        case 1: self has prerelease, other doesn't; other is greater197        case 2: self doesn't have prerelease, other does: self is greater198        case 3: both or neither have prerelease: compare them!199        """200        if self.prerelease and not other.prerelease:201            return -1202        elif not self.prerelease and other.prerelease:203            return 1204 205        if self.prerelease == other.prerelease:206            return 0207        elif self.prerelease < other.prerelease:208            return -1209        else:210            return 1211 212 213# end class StrictVersion214 215 216# The rules according to Greg Stein:217# 1) a version number has 1 or more numbers separated by a period or by218#    sequences of letters. If only periods, then these are compared219#    left-to-right to determine an ordering.220# 2) sequences of letters are part of the tuple for comparison and are221#    compared lexicographically222# 3) recognize the numeric components may have leading zeroes223#224# The LooseVersion class below implements these rules: a version number225# string is split up into a tuple of integer and string components, and226# comparison is a simple tuple comparison.  This means that version227# numbers behave in a predictable and obvious way, but a way that might228# not necessarily be how people *want* version numbers to behave.  There229# wouldn't be a problem if people could stick to purely numeric version230# numbers: just split on period and compare the numbers as tuples.231# However, people insist on putting letters into their version numbers;232# the most common purpose seems to be:233#   - indicating a "pre-release" version234#     ('alpha', 'beta', 'a', 'b', 'pre', 'p')235#   - indicating a post-release patch ('p', 'pl', 'patch')236# but of course this can't cover all version number schemes, and there's237# no way to know what a programmer means without asking him.238#239# The problem is what to do with letters (and other non-numeric240# characters) in a version number.  The current implementation does the241# obvious and predictable thing: keep them as strings and compare242# lexically within a tuple comparison.  This has the desired effect if243# an appended letter sequence implies something "post-release":244# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".245#246# However, if letters in a version number imply a pre-release version,247# the "obvious" thing isn't correct.  Eg. you would expect that248# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison249# implemented here, this just isn't so.250#251# Two possible solutions come to mind.  The first is to tie the252# comparison algorithm to a particular set of semantic rules, as has253# been done in the StrictVersion class above.  This works great as long254# as everyone can go along with bondage and discipline.  Hopefully a255# (large) subset of Python module programmers will agree that the256# particular flavour of bondage and discipline provided by StrictVersion257# provides enough benefit to be worth using, and will submit their258# version numbering scheme to its domination.  The free-thinking259# anarchists in the lot will never give in, though, and something needs260# to be done to accommodate them.261#262# Perhaps a "moderately strict" version class could be implemented that263# lets almost anything slide (syntactically), and makes some heuristic264# assumptions about non-digits in version number strings.  This could265# sink into special-case-hell, though; if I was as talented and266# idiosyncratic as Larry Wall, I'd go ahead and implement a class that267# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is268# just as happy dealing with things like "2g6" and "1.13++".  I don't269# think I'm smart enough to do it right though.270#271# In any case, I've coded the test suite for this module (see272# ../test/test_version.py) specifically to fail on things like comparing273# "1.2a2" and "1.2".  That's not because the *code* is doing anything274# wrong, it's because the simple, obvious design doesn't match my275# complicated, hairy expectations for real-world version numbers.  It276# would be a snap to fix the test suite to say, "Yep, LooseVersion does277# the Right Thing" (ie. the code matches the conception).  But I'd rather278# have a conception that matches common notions about version numbers.279 280 281class LooseVersion(Version):282    """Version numbering for anarchists and software realists.283    Implements the standard interface for version number classes as284    described above.  A version number consists of a series of numbers,285    separated by either periods or strings of letters.  When comparing286    version numbers, the numeric components will be compared287    numerically, and the alphabetic components lexically.  The following288    are all valid version numbers, in no particular order:289 290        1.5.1291        1.5.2b2292        161293        3.10a294        8.02295        3.4j296        1996.07.12297        3.2.pl0298        3.1.1.6299        2g6300        11g301        0.960923302        2.2beta29303        1.13++304        5.5.kw305        2.0b1pl0306 307    In fact, there is no such thing as an invalid version number under308    this scheme; the rules for comparison are simple and predictable,309    but may not always give the results you want (for some definition310    of "want").311    """312 313    component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)314 315    def parse(self, vstring):316        # I've given up on thinking I can reconstruct the version string317        # from the parsed tuple -- so I just store the string here for318        # use by __str__319        self.vstring = vstring320        components = [x for x in self.component_re.split(vstring) if x and x != '.']321        for i, obj in enumerate(components):322            try:323                components[i] = int(obj)324            except ValueError:325                pass326 327        self.version = components328 329    def __str__(self):330        return self.vstring331 332    def __repr__(self):333        return "LooseVersion ('%s')" % str(self)334 335    def _cmp(self, other):336        if isinstance(other, str):337            other = LooseVersion(other)338        elif not isinstance(other, LooseVersion):339            return NotImplemented340 341        if self.version == other.version:342            return 0343        if self.version < other.version:344            return -1345        if self.version > other.version:346            return 1347 348 349# end class LooseVersion350 
codekingpro/portable-devtools · Team Ai