1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements. See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 Verion string utilities.
27 _DIGITS_RE = re.compile(r'^\d+$')
37 class VersionString(unicode):
39 Version string that can be compared, sorted, made unique in a set, and used as a unique dict
42 The primary part of the string is one or more dot-separated natural numbers. Trailing zeroes
43 are treated as redundant, e.g. "1.0.0" == "1.0" == "1".
45 An optional qualifier can be added after a "-". The qualifier can be a natural number or a
46 specially treated prefixed natural number, e.g. "1.1-beta1" > "1.1-alpha2". The case of the
49 Numeric qualifiers will always be greater than prefixed integer qualifiers, e.g. "1.1-1" >
52 Versions without a qualifier will always be greater than their equivalents with a qualifier,
53 e.g. e.g. "1.1" > "1.1-1".
55 Any value that does not conform to this format will be treated as a zero version, which would
56 be lesser than any non-zero version.
58 For efficient list sorts use the ``key`` property, e.g.::
60 sorted(versions, key=lambda x: x.key)
63 NULL = None # initialized below
65 def __init__(self, value=None):
67 super(VersionString, self).__init__(value)
68 self.key = parse_version_string(self)
70 def __eq__(self, version):
71 if not isinstance(version, VersionString):
72 version = VersionString(version)
73 return self.key == version.key
75 def __lt__(self, version):
76 if not isinstance(version, VersionString):
77 version = VersionString(version)
78 return self.key < version.key
81 return self.key.__hash__()
84 def parse_version_string(version): # pylint: disable=too-many-branches
86 Parses a version string.
88 :param version: version string
89 :returns: primary tuple and qualifier float
90 :rtype: ((:obj:`int`), :obj:`float`)
95 version = unicode(version)
97 # Split to primary and qualifier on '-'
98 split = version.split('-', 1)
100 primary, qualifier = split
106 split = primary.split('.')
108 for element in split:
109 if _DIGITS_RE.match(element) is None:
110 # Invalid version string
113 element = int(element)
115 # Invalid version string
117 primary.append(element)
119 # Remove redundant zeros
120 for element in reversed(primary):
125 primary = tuple(primary)
128 if qualifier is not None:
129 if _DIGITS_RE.match(qualifier) is not None:
132 qualifier = float(int(qualifier))
134 # Invalid version string
137 # Prefixed integer qualifier
139 qualifier = qualifier.lower()
140 for prefix, factor in _PREFIXES.iteritems():
141 if qualifier.startswith(prefix):
142 value = qualifier[len(prefix):]
143 if _DIGITS_RE.match(value) is None:
144 # Invalid version string
147 value = float(int(value)) * factor
149 # Invalid version string
153 # Invalid version string
157 # Version strings with no qualifiers are higher
160 return primary, qualifier
163 VersionString.NULL = VersionString()