Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/version.py: 20%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

160 statements  

1# This file is dual licensed under the terms of the Apache License, Version 

2# 2.0, and the BSD License. See the LICENSE file in the root of this repository 

3# for complete details. 

4from __future__ import absolute_import, division, print_function 

5 

6import collections 

7import itertools 

8import re 

9 

10from ._structures import Infinity 

11 

12 

13__all__ = [ 

14 "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN" 

15] 

16 

17 

18_Version = collections.namedtuple( 

19 "_Version", 

20 ["epoch", "release", "dev", "pre", "post", "local"], 

21) 

22 

23 

24def parse(version): 

25 """ 

26 Parse the given version string and return either a :class:`Version` object 

27 or a :class:`LegacyVersion` object depending on if the given version is 

28 a valid PEP 440 version or a legacy version. 

29 """ 

30 try: 

31 return Version(version) 

32 except InvalidVersion: 

33 return LegacyVersion(version) 

34 

35 

36class InvalidVersion(ValueError): 

37 """ 

38 An invalid version was found, users should refer to PEP 440. 

39 """ 

40 

41 

42class _BaseVersion(object): 

43 

44 def __hash__(self): 

45 return hash(self._key) 

46 

47 def __lt__(self, other): 

48 return self._compare(other, lambda s, o: s < o) 

49 

50 def __le__(self, other): 

51 return self._compare(other, lambda s, o: s <= o) 

52 

53 def __eq__(self, other): 

54 return self._compare(other, lambda s, o: s == o) 

55 

56 def __ge__(self, other): 

57 return self._compare(other, lambda s, o: s >= o) 

58 

59 def __gt__(self, other): 

60 return self._compare(other, lambda s, o: s > o) 

61 

62 def __ne__(self, other): 

63 return self._compare(other, lambda s, o: s != o) 

64 

65 def _compare(self, other, method): 

66 if not isinstance(other, _BaseVersion): 

67 return NotImplemented 

68 

69 return method(self._key, other._key) 

70 

71 

72class LegacyVersion(_BaseVersion): 

73 

74 def __init__(self, version): 

75 self._version = str(version) 

76 self._key = _legacy_cmpkey(self._version) 

77 

78 def __str__(self): 

79 return self._version 

80 

81 def __repr__(self): 

82 return "<LegacyVersion({0})>".format(repr(str(self))) 

83 

84 @property 

85 def public(self): 

86 return self._version 

87 

88 @property 

89 def base_version(self): 

90 return self._version 

91 

92 @property 

93 def local(self): 

94 return None 

95 

96 @property 

97 def is_prerelease(self): 

98 return False 

99 

100 @property 

101 def is_postrelease(self): 

102 return False 

103 

104 

105_legacy_version_component_re = re.compile( 

106 r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE, 

107) 

108 

109_legacy_version_replacement_map = { 

110 "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@", 

111} 

112 

113 

114def _parse_version_parts(s): 

115 for part in _legacy_version_component_re.split(s): 

116 part = _legacy_version_replacement_map.get(part, part) 

117 

118 if not part or part == ".": 

119 continue 

120 

121 if part[:1] in "0123456789": 

122 # pad for numeric comparison 

123 yield part.zfill(8) 

124 else: 

125 yield "*" + part 

126 

127 # ensure that alpha/beta/candidate are before final 

128 yield "*final" 

129 

130 

131def _legacy_cmpkey(version): 

132 # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch 

133 # greater than or equal to 0. This will effectively put the LegacyVersion, 

134 # which uses the defacto standard originally implemented by setuptools, 

135 # as before all PEP 440 versions. 

136 epoch = -1 

137 

138 # This scheme is taken from pkg_resources.parse_version setuptools prior to 

139 # it's adoption of the packaging library. 

140 parts = [] 

141 for part in _parse_version_parts(version.lower()): 

142 if part.startswith("*"): 

143 # remove "-" before a prerelease tag 

144 if part < "*final": 

145 while parts and parts[-1] == "*final-": 

146 parts.pop() 

147 

148 # remove trailing zeros from each series of numeric parts 

149 while parts and parts[-1] == "00000000": 

150 parts.pop() 

151 

152 parts.append(part) 

153 parts = tuple(parts) 

154 

155 return epoch, parts 

156 

157# Deliberately not anchored to the start and end of the string, to make it 

158# easier for 3rd party code to reuse 

159VERSION_PATTERN = r""" 

160 v? 

161 (?: 

162 (?:(?P<epoch>[0-9]+)!)? # epoch 

163 (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment 

164 (?P<pre> # pre-release 

165 [-_\.]? 

166 (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview)) 

167 [-_\.]? 

168 (?P<pre_n>[0-9]+)? 

169 )? 

170 (?P<post> # post release 

171 (?:-(?P<post_n1>[0-9]+)) 

172 | 

173 (?: 

174 [-_\.]? 

175 (?P<post_l>post|rev|r) 

176 [-_\.]? 

177 (?P<post_n2>[0-9]+)? 

178 ) 

179 )? 

180 (?P<dev> # dev release 

181 [-_\.]? 

182 (?P<dev_l>dev) 

183 [-_\.]? 

184 (?P<dev_n>[0-9]+)? 

185 )? 

186 ) 

187 (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version 

188""" 

189 

190 

191class Version(_BaseVersion): 

192 

193 _regex = re.compile( 

194 r"^\s*" + VERSION_PATTERN + r"\s*$", 

195 re.VERBOSE | re.IGNORECASE, 

196 ) 

197 

198 def __init__(self, version): 

199 # Validate the version and parse it into pieces 

200 match = self._regex.search(version) 

201 if not match: 

202 raise InvalidVersion("Invalid version: '{0}'".format(version)) 

203 

204 # Store the parsed out pieces of the version 

205 self._version = _Version( 

206 epoch=int(match.group("epoch")) if match.group("epoch") else 0, 

207 release=tuple(int(i) for i in match.group("release").split(".")), 

208 pre=_parse_letter_version( 

209 match.group("pre_l"), 

210 match.group("pre_n"), 

211 ), 

212 post=_parse_letter_version( 

213 match.group("post_l"), 

214 match.group("post_n1") or match.group("post_n2"), 

215 ), 

216 dev=_parse_letter_version( 

217 match.group("dev_l"), 

218 match.group("dev_n"), 

219 ), 

220 local=_parse_local_version(match.group("local")), 

221 ) 

222 

223 # Generate a key which will be used for sorting 

224 self._key = _cmpkey( 

225 self._version.epoch, 

226 self._version.release, 

227 self._version.pre, 

228 self._version.post, 

229 self._version.dev, 

230 self._version.local, 

231 ) 

232 

233 def __repr__(self): 

234 return "<Version({0})>".format(repr(str(self))) 

235 

236 def __str__(self): 

237 parts = [] 

238 

239 # Epoch 

240 if self._version.epoch != 0: 

241 parts.append("{0}!".format(self._version.epoch)) 

242 

243 # Release segment 

244 parts.append(".".join(str(x) for x in self._version.release)) 

245 

246 # Pre-release 

247 if self._version.pre is not None: 

248 parts.append("".join(str(x) for x in self._version.pre)) 

249 

250 # Post-release 

251 if self._version.post is not None: 

252 parts.append(".post{0}".format(self._version.post[1])) 

253 

254 # Development release 

255 if self._version.dev is not None: 

256 parts.append(".dev{0}".format(self._version.dev[1])) 

257 

258 # Local version segment 

259 if self._version.local is not None: 

260 parts.append( 

261 "+{0}".format(".".join(str(x) for x in self._version.local)) 

262 ) 

263 

264 return "".join(parts) 

265 

266 @property 

267 def public(self): 

268 return str(self).split("+", 1)[0] 

269 

270 @property 

271 def base_version(self): 

272 parts = [] 

273 

274 # Epoch 

275 if self._version.epoch != 0: 

276 parts.append("{0}!".format(self._version.epoch)) 

277 

278 # Release segment 

279 parts.append(".".join(str(x) for x in self._version.release)) 

280 

281 return "".join(parts) 

282 

283 @property 

284 def local(self): 

285 version_string = str(self) 

286 if "+" in version_string: 

287 return version_string.split("+", 1)[1] 

288 

289 @property 

290 def is_prerelease(self): 

291 return bool(self._version.dev or self._version.pre) 

292 

293 @property 

294 def is_postrelease(self): 

295 return bool(self._version.post) 

296 

297 

298def _parse_letter_version(letter, number): 

299 if letter: 

300 # We consider there to be an implicit 0 in a pre-release if there is 

301 # not a numeral associated with it. 

302 if number is None: 

303 number = 0 

304 

305 # We normalize any letters to their lower case form 

306 letter = letter.lower() 

307 

308 # We consider some words to be alternate spellings of other words and 

309 # in those cases we want to normalize the spellings to our preferred 

310 # spelling. 

311 if letter == "alpha": 

312 letter = "a" 

313 elif letter == "beta": 

314 letter = "b" 

315 elif letter in ["c", "pre", "preview"]: 

316 letter = "rc" 

317 elif letter in ["rev", "r"]: 

318 letter = "post" 

319 

320 return letter, int(number) 

321 if not letter and number: 

322 # We assume if we are given a number, but we are not given a letter 

323 # then this is using the implicit post release syntax (e.g. 1.0-1) 

324 letter = "post" 

325 

326 return letter, int(number) 

327 

328 

329_local_version_seperators = re.compile(r"[\._-]") 

330 

331 

332def _parse_local_version(local): 

333 """ 

334 Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve"). 

335 """ 

336 if local is not None: 

337 return tuple( 

338 part.lower() if not part.isdigit() else int(part) 

339 for part in _local_version_seperators.split(local) 

340 ) 

341 

342 

343def _cmpkey(epoch, release, pre, post, dev, local): 

344 # When we compare a release version, we want to compare it with all of the 

345 # trailing zeros removed. So we'll use a reverse the list, drop all the now 

346 # leading zeros until we come to something non zero, then take the rest 

347 # re-reverse it back into the correct order and make it a tuple and use 

348 # that for our sorting key. 

349 release = tuple( 

350 reversed(list( 

351 itertools.dropwhile( 

352 lambda x: x == 0, 

353 reversed(release), 

354 ) 

355 )) 

356 ) 

357 

358 # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0. 

359 # We'll do this by abusing the pre segment, but we _only_ want to do this 

360 # if there is not a pre or a post segment. If we have one of those then 

361 # the normal sorting rules will handle this case correctly. 

362 if pre is None and post is None and dev is not None: 

363 pre = -Infinity 

364 # Versions without a pre-release (except as noted above) should sort after 

365 # those with one. 

366 elif pre is None: 

367 pre = Infinity 

368 

369 # Versions without a post segment should sort before those with one. 

370 if post is None: 

371 post = -Infinity 

372 

373 # Versions without a development segment should sort after those with one. 

374 if dev is None: 

375 dev = Infinity 

376 

377 if local is None: 

378 # Versions without a local segment should sort before those with one. 

379 local = -Infinity 

380 else: 

381 # Versions with a local segment need that segment parsed to implement 

382 # the sorting rules in PEP440. 

383 # - Alpha numeric segments sort before numeric segments 

384 # - Alpha numeric segments sort lexicographically 

385 # - Numeric segments sort numerically 

386 # - Shorter versions sort before longer versions when the prefixes 

387 # match exactly 

388 local = tuple( 

389 (i, "") if isinstance(i, int) else (-Infinity, i) 

390 for i in local 

391 ) 

392 

393 return epoch, release, pre, post, dev, local