Coverage for plugins/module_utils/version.py: 43%
109 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-10-30 17:06 -0400
« prev ^ index » next coverage.py v7.14.1, created at 2026-10-30 17:06 -0400
1# Vendored copy of distutils/version.py from CPython 3.9.5
2#
3# Implements multiple version numbering conventions for the
4# Python Module Distribution Utilities.
5#
6# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0)
7#
9"""Provides classes to represent module version numbers (one class for
10each style of version numbering). There are currently two such classes
11implemented: StrictVersion and LooseVersion.
13Every version number class implements the following interface:
14 * the 'parse' method takes a string and parses it to some internal
15 representation; if the string is an invalid version number,
16 'parse' raises a ValueError exception
17 * the class constructor takes an optional string argument which,
18 if supplied, is passed to 'parse'
19 * __str__ reconstructs the string that was passed to 'parse' (or
20 an equivalent string -- ie. one that will generate an equivalent
21 version number instance)
22 * __repr__ generates Python code to recreate the version number instance
23 * _cmp compares the current instance with either another instance
24 of the same class or a string (which will be parsed to an instance
25 of the same class, thus must follow the same rules)
26"""
28from __future__ import absolute_import, division, print_function
30__metaclass__ = type
32import re
34try:
35 RE_FLAGS = re.VERBOSE | re.ASCII
36except AttributeError:
37 RE_FLAGS = re.VERBOSE
40class Version:
41 """Abstract base class for version numbering classes. Just provides
42 constructor (__init__) and reproducer (__repr__), because those
43 seem to be the same for all version numbering classes; and route
44 rich comparisons to _cmp.
45 """
47 def __init__(self, vstring=None):
48 if vstring:
49 self.parse(vstring)
51 def __repr__(self):
52 return "%s ('%s')" % (self.__class__.__name__, str(self))
54 def __eq__(self, other):
55 c = self._cmp(other)
56 if c is NotImplemented:
57 return c
58 return c == 0
60 def __lt__(self, other):
61 c = self._cmp(other)
62 if c is NotImplemented:
63 return c
64 return c < 0
66 def __le__(self, other):
67 c = self._cmp(other)
68 if c is NotImplemented:
69 return c
70 return c <= 0
72 def __gt__(self, other):
73 c = self._cmp(other)
74 if c is NotImplemented:
75 return c
76 return c > 0
78 def __ge__(self, other):
79 c = self._cmp(other)
80 if c is NotImplemented:
81 return c
82 return c >= 0
85# Interface for version-number classes -- must be implemented
86# by the following classes (the concrete ones -- Version should
87# be treated as an abstract class).
88# __init__ (string) - create and take same action as 'parse'
89# (string parameter is optional)
90# parse (string) - convert a string representation to whatever
91# internal representation is appropriate for
92# this style of version numbering
93# __str__ (self) - convert back to a string; should be very similar
94# (if not identical to) the string supplied to parse
95# __repr__ (self) - generate Python code to recreate
96# the instance
97# _cmp (self, other) - compare two version numbers ('other' may
98# be an unparsed version string, or another
99# instance of your version class)
102class StrictVersion(Version):
103 """Version numbering for anal retentives and software idealists.
104 Implements the standard interface for version number classes as
105 described above. A version number consists of two or three
106 dot-separated numeric components, with an optional "pre-release" tag
107 on the end. The pre-release tag consists of the letter 'a' or 'b'
108 followed by a number. If the numeric components of two version
109 numbers are equal, then one with a pre-release tag will always
110 be deemed earlier (lesser) than one without.
112 The following are valid version numbers (shown in the order that
113 would be obtained by sorting according to the supplied cmp function):
115 0.4 0.4.0 (these two are equivalent)
116 0.4.1
117 0.5a1
118 0.5b3
119 0.5
120 0.9.6
121 1.0
122 1.0.4a3
123 1.0.4b1
124 1.0.4
126 The following are examples of invalid version numbers:
128 1
129 2.7.2.2
130 1.3.a4
131 1.3pl1
132 1.3c4
134 The rationale for this version numbering system will be explained
135 in the distutils documentation.
136 """
138 version_re = re.compile(r"^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$", RE_FLAGS)
140 def parse(self, vstring):
141 match = self.version_re.match(vstring)
142 if not match:
143 raise ValueError("invalid version number '%s'" % vstring)
145 major, minor, patch, prerelease, prerelease_num = match.group(1, 2, 4, 5, 6)
147 if patch:
148 self.version = tuple(map(int, [major, minor, patch]))
149 else:
150 self.version = tuple(map(int, [major, minor])) + (0,)
152 if prerelease:
153 self.prerelease = (prerelease[0], int(prerelease_num))
154 else:
155 self.prerelease = None
157 def __str__(self):
158 if self.version[2] == 0:
159 vstring = ".".join(map(str, self.version[0:2]))
160 else:
161 vstring = ".".join(map(str, self.version))
163 if self.prerelease:
164 vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
166 return vstring
168 def _cmp(self, other):
169 if isinstance(other, str):
170 other = StrictVersion(other)
171 elif not isinstance(other, StrictVersion):
172 return NotImplemented
174 if self.version != other.version:
175 # numeric versions don't match
176 # prerelease stuff doesn't matter
177 if self.version < other.version:
178 return -1
179 else:
180 return 1
182 # have to compare prerelease
183 # case 1: neither has prerelease; they're equal
184 # case 2: self has prerelease, other doesn't; other is greater
185 # case 3: self doesn't have prerelease, other does: self is greater
186 # case 4: both have prerelease: must compare them!
188 if not self.prerelease and not other.prerelease:
189 return 0
190 elif self.prerelease and not other.prerelease:
191 return -1
192 elif not self.prerelease and other.prerelease:
193 return 1
194 elif self.prerelease and other.prerelease:
195 if self.prerelease == other.prerelease:
196 return 0
197 elif self.prerelease < other.prerelease:
198 return -1
199 else:
200 return 1
201 else:
202 raise AssertionError("never get here")
205# end class StrictVersion
207# The rules according to Greg Stein:
208# 1) a version number has 1 or more numbers separated by a period or by
209# sequences of letters. If only periods, then these are compared
210# left-to-right to determine an ordering.
211# 2) sequences of letters are part of the tuple for comparison and are
212# compared lexicographically
213# 3) recognize the numeric components may have leading zeroes
214#
215# The LooseVersion class below implements these rules: a version number
216# string is split up into a tuple of integer and string components, and
217# comparison is a simple tuple comparison. This means that version
218# numbers behave in a predictable and obvious way, but a way that might
219# not necessarily be how people *want* version numbers to behave. There
220# wouldn't be a problem if people could stick to purely numeric version
221# numbers: just split on period and compare the numbers as tuples.
222# However, people insist on putting letters into their version numbers;
223# the most common purpose seems to be:
224# - indicating a "pre-release" version
225# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
226# - indicating a post-release patch ('p', 'pl', 'patch')
227# but of course this can't cover all version number schemes, and there's
228# no way to know what a programmer means without asking him.
229#
230# The problem is what to do with letters (and other non-numeric
231# characters) in a version number. The current implementation does the
232# obvious and predictable thing: keep them as strings and compare
233# lexically within a tuple comparison. This has the desired effect if
234# an appended letter sequence implies something "post-release":
235# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
236#
237# However, if letters in a version number imply a pre-release version,
238# the "obvious" thing isn't correct. Eg. you would expect that
239# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
240# implemented here, this just isn't so.
241#
242# Two possible solutions come to mind. The first is to tie the
243# comparison algorithm to a particular set of semantic rules, as has
244# been done in the StrictVersion class above. This works great as long
245# as everyone can go along with bondage and discipline. Hopefully a
246# (large) subset of Python module programmers will agree that the
247# particular flavour of bondage and discipline provided by StrictVersion
248# provides enough benefit to be worth using, and will submit their
249# version numbering scheme to its domination. The free-thinking
250# anarchists in the lot will never give in, though, and something needs
251# to be done to accommodate them.
252#
253# Perhaps a "moderately strict" version class could be implemented that
254# lets almost anything slide (syntactically), and makes some heuristic
255# assumptions about non-digits in version number strings. This could
256# sink into special-case-hell, though; if I was as talented and
257# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
258# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
259# just as happy dealing with things like "2g6" and "1.13++". I don't
260# think I'm smart enough to do it right though.
261#
262# In any case, I've coded the test suite for this module (see
263# ../test/test_version.py) specifically to fail on things like comparing
264# "1.2a2" and "1.2". That's not because the *code* is doing anything
265# wrong, it's because the simple, obvious design doesn't match my
266# complicated, hairy expectations for real-world version numbers. It
267# would be a snap to fix the test suite to say, "Yep, LooseVersion does
268# the Right Thing" (ie. the code matches the conception). But I'd rather
269# have a conception that matches common notions about version numbers.
272class LooseVersion(Version):
273 """Version numbering for anarchists and software realists.
274 Implements the standard interface for version number classes as
275 described above. A version number consists of a series of numbers,
276 separated by either periods or strings of letters. When comparing
277 version numbers, the numeric components will be compared
278 numerically, and the alphabetic components lexically. The following
279 are all valid version numbers, in no particular order:
281 1.5.1
282 1.5.2b2
283 161
284 3.10a
285 8.02
286 3.4j
287 1996.07.12
288 3.2.pl0
289 3.1.1.6
290 2g6
291 11g
292 0.960923
293 2.2beta29
294 1.13++
295 5.5.kw
296 2.0b1pl0
298 In fact, there is no such thing as an invalid version number under
299 this scheme; the rules for comparison are simple and predictable,
300 but may not always give the results you want (for some definition
301 of "want").
302 """
304 component_re = re.compile(r"(\d+ | [a-z]+ | \.)", re.VERBOSE)
306 def __init__(self, vstring=None):
307 if vstring:
308 self.parse(vstring)
310 def parse(self, vstring):
311 # I've given up on thinking I can reconstruct the version string
312 # from the parsed tuple -- so I just store the string here for
313 # use by __str__
314 self.vstring = vstring
315 components = [x for x in self.component_re.split(vstring) if x and x != "."]
316 for i, obj in enumerate(components):
317 try:
318 components[i] = int(obj)
319 except ValueError:
320 pass
322 self.version = components
324 def __str__(self):
325 return self.vstring
327 def __repr__(self):
328 return "LooseVersion ('%s')" % str(self)
330 def _cmp(self, other):
331 if isinstance(other, str):
332 other = LooseVersion(other)
333 elif not isinstance(other, LooseVersion):
334 return NotImplemented
336 if self.version == other.version:
337 return 0
338 if self.version < other.version:
339 return -1
340 if self.version > other.version:
341 return 1
344# end class LooseVersion