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

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

196 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__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] 

14 

15 

16_Version = collections.namedtuple( 

17 "_Version", ["epoch", "release", "dev", "pre", "post", "local"] 

18) 

19 

20 

21def parse(version): 

22 """ 

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

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

25 a valid PEP 440 version or a legacy version. 

26 """ 

27 try: 

28 return Version(version) 

29 except InvalidVersion: 

30 return LegacyVersion(version) 

31 

32 

33class InvalidVersion(ValueError): 

34 """ 

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

36 """ 

37 

38 

39class _BaseVersion(object): 

40 def __hash__(self): 

41 return hash(self._key) 

42 

43 def __lt__(self, other): 

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

45 

46 def __le__(self, other): 

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

48 

49 def __eq__(self, other): 

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

51 

52 def __ge__(self, other): 

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

54 

55 def __gt__(self, other): 

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

57 

58 def __ne__(self, other): 

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

60 

61 def _compare(self, other, method): 

62 if not isinstance(other, _BaseVersion): 

63 return NotImplemented 

64 

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

66 

67 

68class LegacyVersion(_BaseVersion): 

69 def __init__(self, version): 

70 self._version = str(version) 

71 self._key = _legacy_cmpkey(self._version) 

72 

73 def __str__(self): 

74 return self._version 

75 

76 def __repr__(self): 

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

78 

79 @property 

80 def public(self): 

81 return self._version 

82 

83 @property 

84 def base_version(self): 

85 return self._version 

86 

87 @property 

88 def epoch(self): 

89 return -1 

90 

91 @property 

92 def release(self): 

93 return None 

94 

95 @property 

96 def pre(self): 

97 return None 

98 

99 @property 

100 def post(self): 

101 return None 

102 

103 @property 

104 def dev(self): 

105 return None 

106 

107 @property 

108 def local(self): 

109 return None 

110 

111 @property 

112 def is_prerelease(self): 

113 return False 

114 

115 @property 

116 def is_postrelease(self): 

117 return False 

118 

119 @property 

120 def is_devrelease(self): 

121 return False 

122 

123 

124_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) 

125 

126_legacy_version_replacement_map = { 

127 "pre": "c", 

128 "preview": "c", 

129 "-": "final-", 

130 "rc": "c", 

131 "dev": "@", 

132} 

133 

134 

135def _parse_version_parts(s): 

136 for part in _legacy_version_component_re.split(s): 

137 part = _legacy_version_replacement_map.get(part, part) 

138 

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

140 continue 

141 

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

143 # pad for numeric comparison 

144 yield part.zfill(8) 

145 else: 

146 yield "*" + part 

147 

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

149 yield "*final" 

150 

151 

152def _legacy_cmpkey(version): 

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

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

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

156 # as before all PEP 440 versions. 

157 epoch = -1 

158 

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

160 # it's adoption of the packaging library. 

161 parts = [] 

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

163 if part.startswith("*"): 

164 # remove "-" before a prerelease tag 

165 if part < "*final": 

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

167 parts.pop() 

168 

169 # remove trailing zeros from each series of numeric parts 

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

171 parts.pop() 

172 

173 parts.append(part) 

174 parts = tuple(parts) 

175 

176 return epoch, parts 

177 

178 

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

180# easier for 3rd party code to reuse 

181VERSION_PATTERN = r""" 

182 v? 

183 (?: 

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

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

186 (?P<pre> # pre-release 

187 [-_\.]? 

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

189 [-_\.]? 

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

191 )? 

192 (?P<post> # post release 

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

194 | 

195 (?: 

196 [-_\.]? 

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

198 [-_\.]? 

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

200 ) 

201 )? 

202 (?P<dev> # dev release 

203 [-_\.]? 

204 (?P<dev_l>dev) 

205 [-_\.]? 

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

207 )? 

208 ) 

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

210""" 

211 

212 

213class Version(_BaseVersion): 

214 

215 _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) 

216 

217 def __init__(self, version): 

218 # Validate the version and parse it into pieces 

219 match = self._regex.search(version) 

220 if not match: 

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

222 

223 # Store the parsed out pieces of the version 

224 self._version = _Version( 

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

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

227 pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")), 

228 post=_parse_letter_version( 

229 match.group("post_l"), match.group("post_n1") or match.group("post_n2") 

230 ), 

231 dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")), 

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

233 ) 

234 

235 # Generate a key which will be used for sorting 

236 self._key = _cmpkey( 

237 self._version.epoch, 

238 self._version.release, 

239 self._version.pre, 

240 self._version.post, 

241 self._version.dev, 

242 self._version.local, 

243 ) 

244 

245 def __repr__(self): 

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

247 

248 def __str__(self): 

249 parts = [] 

250 

251 # Epoch 

252 if self.epoch != 0: 

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

254 

255 # Release segment 

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

257 

258 # Pre-release 

259 if self.pre is not None: 

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

261 

262 # Post-release 

263 if self.post is not None: 

264 parts.append(".post{0}".format(self.post)) 

265 

266 # Development release 

267 if self.dev is not None: 

268 parts.append(".dev{0}".format(self.dev)) 

269 

270 # Local version segment 

271 if self.local is not None: 

272 parts.append("+{0}".format(self.local)) 

273 

274 return "".join(parts) 

275 

276 @property 

277 def epoch(self): 

278 return self._version.epoch 

279 

280 @property 

281 def release(self): 

282 return self._version.release 

283 

284 @property 

285 def pre(self): 

286 return self._version.pre 

287 

288 @property 

289 def post(self): 

290 return self._version.post[1] if self._version.post else None 

291 

292 @property 

293 def dev(self): 

294 return self._version.dev[1] if self._version.dev else None 

295 

296 @property 

297 def local(self): 

298 if self._version.local: 

299 return ".".join(str(x) for x in self._version.local) 

300 else: 

301 return None 

302 

303 @property 

304 def public(self): 

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

306 

307 @property 

308 def base_version(self): 

309 parts = [] 

310 

311 # Epoch 

312 if self.epoch != 0: 

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

314 

315 # Release segment 

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

317 

318 return "".join(parts) 

319 

320 @property 

321 def is_prerelease(self): 

322 return self.dev is not None or self.pre is not None 

323 

324 @property 

325 def is_postrelease(self): 

326 return self.post is not None 

327 

328 @property 

329 def is_devrelease(self): 

330 return self.dev is not None 

331 

332 

333def _parse_letter_version(letter, number): 

334 if letter: 

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

336 # not a numeral associated with it. 

337 if number is None: 

338 number = 0 

339 

340 # We normalize any letters to their lower case form 

341 letter = letter.lower() 

342 

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

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

345 # spelling. 

346 if letter == "alpha": 

347 letter = "a" 

348 elif letter == "beta": 

349 letter = "b" 

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

351 letter = "rc" 

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

353 letter = "post" 

354 

355 return letter, int(number) 

356 if not letter and number: 

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

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

359 letter = "post" 

360 

361 return letter, int(number) 

362 

363 

364_local_version_separators = re.compile(r"[\._-]") 

365 

366 

367def _parse_local_version(local): 

368 """ 

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

370 """ 

371 if local is not None: 

372 return tuple( 

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

374 for part in _local_version_separators.split(local) 

375 ) 

376 

377 

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

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

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

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

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

383 # that for our sorting key. 

384 release = tuple( 

385 reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))) 

386 ) 

387 

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

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

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

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

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

393 pre = -Infinity 

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

395 # those with one. 

396 elif pre is None: 

397 pre = Infinity 

398 

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

400 if post is None: 

401 post = -Infinity 

402 

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

404 if dev is None: 

405 dev = Infinity 

406 

407 if local is None: 

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

409 local = -Infinity 

410 else: 

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

412 # the sorting rules in PEP440. 

413 # - Alpha numeric segments sort before numeric segments 

414 # - Alpha numeric segments sort lexicographically 

415 # - Numeric segments sort numerically 

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

417 # match exactly 

418 local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local) 

419 

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