1 2"""Describe and handle version numbers for applications, modules and packages""" 3 4import re 5 6 7__all__ = 'Version', 8 9 10class Version(str): 11 """A major.minor.micro[extraversion] version string that is comparable""" 12 13 # noinspection PyArgumentList 14 def __new__(cls, major, minor, micro, extraversion=None): 15 if major is minor is micro is extraversion is None: 16 instance = str.__new__(cls, 'undefined') 17 instance._version_info = (None, None, None, None, None) 18 return instance 19 try: 20 major, minor, micro = int(major), int(minor), int(micro) 21 except (TypeError, ValueError): 22 raise TypeError('major, minor and micro must be integer numbers') 23 if extraversion is None: 24 instance = str.__new__(cls, '%d.%d.%d' % (major, minor, micro)) 25 weight = 0 26 elif isinstance(extraversion, (int, long)): 27 instance = str.__new__(cls, '%d.%d.%d-%d' % (major, minor, micro, extraversion)) 28 weight = 0 29 elif isinstance(extraversion, basestring): 30 instance = str.__new__(cls, '%d.%d.%d%s' % (major, minor, micro, extraversion)) 31 match = re.match(r'^[-.]?(?P<name>(pre|rc|alpha|beta|))(?P<number>\d+)$', extraversion) 32 if match: 33 weight_map = {'alpha': -40, 'beta': -30, 'pre': -20, 'rc': -10, '': 0} 34 weight = weight_map[match.group('name')] 35 extraversion = int(match.group('number')) 36 else: 37 weight = 0 38 extraversion = extraversion or None 39 else: 40 raise TypeError('extraversion must be a string, integer, long or None') 41 instance._version_info = (major, minor, micro, weight, extraversion) 42 return instance 43 44 def __init__(self, *args, **kw): 45 super(Version, self).__init__() 46 47 @classmethod 48 def parse(cls, value): 49 if isinstance(value, Version): 50 return value 51 elif not isinstance(value, basestring): 52 raise TypeError('value should be a string') 53 if value == 'undefined': 54 return cls(None, None, None) 55 match = re.match(r'^(?P<major>\d+)(\.(?P<minor>\d+))?(\.(?P<micro>\d+))?(?P<extraversion>.*)$', value) 56 if not match: 57 raise ValueError('not a recognized version string') 58 return cls(**match.groupdict(0)) 59 60 @property 61 def major(self): 62 return self._version_info[0] 63 64 @property 65 def minor(self): 66 return self._version_info[1] 67 68 @property 69 def micro(self): 70 return self._version_info[2] 71 72 @property 73 def extraversion(self): 74 return self._version_info[4] 75 76 def __repr__(self): 77 major, minor, micro, weight, extraversion = self._version_info 78 if weight is not None and weight < 0: 79 weight_map = {-10: 'rc', -20: 'pre', -30: 'beta', -40: 'alpha'} 80 extraversion = '%s%d' % (weight_map[weight], extraversion) 81 return '%s(major=%r, minor=%r, micro=%r, extraversion=%r)' % (self.__class__.__name__, major, minor, micro, extraversion) 82 83 def __cmp__(self, other): 84 if isinstance(other, Version): 85 return cmp(self._version_info, other._version_info) 86 elif isinstance(other, basestring): 87 return cmp(str(self), other) 88 else: 89 return NotImplemented 90 91 def __le__(self, other): 92 return self.__cmp__(other) <= 0 93 94 def __lt__(self, other): 95 return self.__cmp__(other) < 0 96 97 def __ge__(self, other): 98 return self.__cmp__(other) >= 0 99 100 def __gt__(self, other): 101 return self.__cmp__(other) > 0 102 103 def __eq__(self, other): 104 return self.__cmp__(other) == 0 105 106 def __ne__(self, other): 107 return self.__cmp__(other) != 0 108 109