Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scikit_learn-1.4.dev0-py3.8-linux-x86_64.egg/sklearn/externals/_packaging/version.py: 56%

230 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-12 06:31 +0000

1"""Vendoered from 

2https://github.com/pypa/packaging/blob/main/packaging/version.py 

3""" 

4# Copyright (c) Donald Stufft and individual contributors. 

5# All rights reserved. 

6 

7# Redistribution and use in source and binary forms, with or without 

8# modification, are permitted provided that the following conditions are met: 

9 

10# 1. Redistributions of source code must retain the above copyright notice, 

11# this list of conditions and the following disclaimer. 

12 

13# 2. Redistributions in binary form must reproduce the above copyright 

14# notice, this list of conditions and the following disclaimer in the 

15# documentation and/or other materials provided with the distribution. 

16 

17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 

18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 

19# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 

20# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 

21# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 

22# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 

23# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 

24# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 

25# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 

26# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

27 

28import collections 

29import itertools 

30import re 

31import warnings 

32from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union 

33 

34from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType 

35 

36__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] 

37 

38InfiniteTypes = Union[InfinityType, NegativeInfinityType] 

39PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] 

40SubLocalType = Union[InfiniteTypes, int, str] 

41LocalType = Union[ 

42 NegativeInfinityType, 

43 Tuple[ 

44 Union[ 

45 SubLocalType, 

46 Tuple[SubLocalType, str], 

47 Tuple[NegativeInfinityType, SubLocalType], 

48 ], 

49 ..., 

50 ], 

51] 

52CmpKey = Tuple[ 

53 int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType 

54] 

55LegacyCmpKey = Tuple[int, Tuple[str, ...]] 

56VersionComparisonMethod = Callable[ 

57 [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool 

58] 

59 

60_Version = collections.namedtuple( 

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

62) 

63 

64 

65def parse(version: str) -> Union["LegacyVersion", "Version"]: 

66 """Parse the given version from a string to an appropriate class. 

67 

68 Parameters 

69 ---------- 

70 version : str 

71 Version in a string format, eg. "0.9.1" or "1.2.dev0". 

72 

73 Returns 

74 ------- 

75 version : :class:`Version` object or a :class:`LegacyVersion` object 

76 Returned class depends on the given version: if is a valid 

77 PEP 440 version or a legacy version. 

78 """ 

79 try: 

80 return Version(version) 

81 except InvalidVersion: 

82 return LegacyVersion(version) 

83 

84 

85class InvalidVersion(ValueError): 

86 """ 

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

88 """ 

89 

90 

91class _BaseVersion: 

92 _key: Union[CmpKey, LegacyCmpKey] 

93 

94 def __hash__(self) -> int: 

95 return hash(self._key) 

96 

97 # Please keep the duplicated `isinstance` check 

98 # in the six comparisons hereunder 

99 # unless you find a way to avoid adding overhead function calls. 

100 def __lt__(self, other: "_BaseVersion") -> bool: 

101 if not isinstance(other, _BaseVersion): 

102 return NotImplemented 

103 

104 return self._key < other._key 

105 

106 def __le__(self, other: "_BaseVersion") -> bool: 

107 if not isinstance(other, _BaseVersion): 

108 return NotImplemented 

109 

110 return self._key <= other._key 

111 

112 def __eq__(self, other: object) -> bool: 

113 if not isinstance(other, _BaseVersion): 

114 return NotImplemented 

115 

116 return self._key == other._key 

117 

118 def __ge__(self, other: "_BaseVersion") -> bool: 

119 if not isinstance(other, _BaseVersion): 

120 return NotImplemented 

121 

122 return self._key >= other._key 

123 

124 def __gt__(self, other: "_BaseVersion") -> bool: 

125 if not isinstance(other, _BaseVersion): 

126 return NotImplemented 

127 

128 return self._key > other._key 

129 

130 def __ne__(self, other: object) -> bool: 

131 if not isinstance(other, _BaseVersion): 

132 return NotImplemented 

133 

134 return self._key != other._key 

135 

136 

137class LegacyVersion(_BaseVersion): 

138 def __init__(self, version: str) -> None: 

139 self._version = str(version) 

140 self._key = _legacy_cmpkey(self._version) 

141 

142 warnings.warn( 

143 "Creating a LegacyVersion has been deprecated and will be " 

144 "removed in the next major release", 

145 DeprecationWarning, 

146 ) 

147 

148 def __str__(self) -> str: 

149 return self._version 

150 

151 def __repr__(self) -> str: 

152 return f"<LegacyVersion('{self}')>" 

153 

154 @property 

155 def public(self) -> str: 

156 return self._version 

157 

158 @property 

159 def base_version(self) -> str: 

160 return self._version 

161 

162 @property 

163 def epoch(self) -> int: 

164 return -1 

165 

166 @property 

167 def release(self) -> None: 

168 return None 

169 

170 @property 

171 def pre(self) -> None: 

172 return None 

173 

174 @property 

175 def post(self) -> None: 

176 return None 

177 

178 @property 

179 def dev(self) -> None: 

180 return None 

181 

182 @property 

183 def local(self) -> None: 

184 return None 

185 

186 @property 

187 def is_prerelease(self) -> bool: 

188 return False 

189 

190 @property 

191 def is_postrelease(self) -> bool: 

192 return False 

193 

194 @property 

195 def is_devrelease(self) -> bool: 

196 return False 

197 

198 

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

200 

201_legacy_version_replacement_map = { 

202 "pre": "c", 

203 "preview": "c", 

204 "-": "final-", 

205 "rc": "c", 

206 "dev": "@", 

207} 

208 

209 

210def _parse_version_parts(s: str) -> Iterator[str]: 

211 for part in _legacy_version_component_re.split(s): 

212 part = _legacy_version_replacement_map.get(part, part) 

213 

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

215 continue 

216 

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

218 # pad for numeric comparison 

219 yield part.zfill(8) 

220 else: 

221 yield "*" + part 

222 

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

224 yield "*final" 

225 

226 

227def _legacy_cmpkey(version: str) -> LegacyCmpKey: 

228 

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

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

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

232 # as before all PEP 440 versions. 

233 epoch = -1 

234 

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

236 # it's adoption of the packaging library. 

237 parts: List[str] = [] 

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

239 if part.startswith("*"): 

240 # remove "-" before a prerelease tag 

241 if part < "*final": 

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

243 parts.pop() 

244 

245 # remove trailing zeros from each series of numeric parts 

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

247 parts.pop() 

248 

249 parts.append(part) 

250 

251 return epoch, tuple(parts) 

252 

253 

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

255# easier for 3rd party code to reuse 

256VERSION_PATTERN = r""" 

257 v? 

258 (?: 

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

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

261 (?P<pre> # pre-release 

262 [-_\.]? 

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

264 [-_\.]? 

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

266 )? 

267 (?P<post> # post release 

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

269 | 

270 (?: 

271 [-_\.]? 

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

273 [-_\.]? 

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

275 ) 

276 )? 

277 (?P<dev> # dev release 

278 [-_\.]? 

279 (?P<dev_l>dev) 

280 [-_\.]? 

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

282 )? 

283 ) 

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

285""" 

286 

287 

288class Version(_BaseVersion): 

289 

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

291 

292 def __init__(self, version: str) -> None: 

293 

294 # Validate the version and parse it into pieces 

295 match = self._regex.search(version) 

296 if not match: 

297 raise InvalidVersion(f"Invalid version: '{version}'") 

298 

299 # Store the parsed out pieces of the version 

300 self._version = _Version( 

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

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

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

304 post=_parse_letter_version( 

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

306 ), 

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

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

309 ) 

310 

311 # Generate a key which will be used for sorting 

312 self._key = _cmpkey( 

313 self._version.epoch, 

314 self._version.release, 

315 self._version.pre, 

316 self._version.post, 

317 self._version.dev, 

318 self._version.local, 

319 ) 

320 

321 def __repr__(self) -> str: 

322 return f"<Version('{self}')>" 

323 

324 def __str__(self) -> str: 

325 parts = [] 

326 

327 # Epoch 

328 if self.epoch != 0: 

329 parts.append(f"{self.epoch}!") 

330 

331 # Release segment 

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

333 

334 # Pre-release 

335 if self.pre is not None: 

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

337 

338 # Post-release 

339 if self.post is not None: 

340 parts.append(f".post{self.post}") 

341 

342 # Development release 

343 if self.dev is not None: 

344 parts.append(f".dev{self.dev}") 

345 

346 # Local version segment 

347 if self.local is not None: 

348 parts.append(f"+{self.local}") 

349 

350 return "".join(parts) 

351 

352 @property 

353 def epoch(self) -> int: 

354 _epoch: int = self._version.epoch 

355 return _epoch 

356 

357 @property 

358 def release(self) -> Tuple[int, ...]: 

359 _release: Tuple[int, ...] = self._version.release 

360 return _release 

361 

362 @property 

363 def pre(self) -> Optional[Tuple[str, int]]: 

364 _pre: Optional[Tuple[str, int]] = self._version.pre 

365 return _pre 

366 

367 @property 

368 def post(self) -> Optional[int]: 

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

370 

371 @property 

372 def dev(self) -> Optional[int]: 

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

374 

375 @property 

376 def local(self) -> Optional[str]: 

377 if self._version.local: 

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

379 else: 

380 return None 

381 

382 @property 

383 def public(self) -> str: 

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

385 

386 @property 

387 def base_version(self) -> str: 

388 parts = [] 

389 

390 # Epoch 

391 if self.epoch != 0: 

392 parts.append(f"{self.epoch}!") 

393 

394 # Release segment 

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

396 

397 return "".join(parts) 

398 

399 @property 

400 def is_prerelease(self) -> bool: 

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

402 

403 @property 

404 def is_postrelease(self) -> bool: 

405 return self.post is not None 

406 

407 @property 

408 def is_devrelease(self) -> bool: 

409 return self.dev is not None 

410 

411 @property 

412 def major(self) -> int: 

413 return self.release[0] if len(self.release) >= 1 else 0 

414 

415 @property 

416 def minor(self) -> int: 

417 return self.release[1] if len(self.release) >= 2 else 0 

418 

419 @property 

420 def micro(self) -> int: 

421 return self.release[2] if len(self.release) >= 3 else 0 

422 

423 

424def _parse_letter_version( 

425 letter: str, number: Union[str, bytes, SupportsInt] 

426) -> Optional[Tuple[str, int]]: 

427 

428 if letter: 

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

430 # not a numeral associated with it. 

431 if number is None: 

432 number = 0 

433 

434 # We normalize any letters to their lower case form 

435 letter = letter.lower() 

436 

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

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

439 # spelling. 

440 if letter == "alpha": 

441 letter = "a" 

442 elif letter == "beta": 

443 letter = "b" 

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

445 letter = "rc" 

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

447 letter = "post" 

448 

449 return letter, int(number) 

450 if not letter and number: 

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

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

453 letter = "post" 

454 

455 return letter, int(number) 

456 

457 return None 

458 

459 

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

461 

462 

463def _parse_local_version(local: str) -> Optional[LocalType]: 

464 """ 

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

466 """ 

467 if local is not None: 

468 return tuple( 

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

470 for part in _local_version_separators.split(local) 

471 ) 

472 return None 

473 

474 

475def _cmpkey( 

476 epoch: int, 

477 release: Tuple[int, ...], 

478 pre: Optional[Tuple[str, int]], 

479 post: Optional[Tuple[str, int]], 

480 dev: Optional[Tuple[str, int]], 

481 local: Optional[Tuple[SubLocalType]], 

482) -> CmpKey: 

483 

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

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

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

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

488 # that for our sorting key. 

489 _release = tuple( 

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

491 ) 

492 

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

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

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

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

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

498 _pre: PrePostDevType = NegativeInfinity 

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

500 # those with one. 

501 elif pre is None: 

502 _pre = Infinity 

503 else: 

504 _pre = pre 

505 

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

507 if post is None: 

508 _post: PrePostDevType = NegativeInfinity 

509 

510 else: 

511 _post = post 

512 

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

514 if dev is None: 

515 _dev: PrePostDevType = Infinity 

516 

517 else: 

518 _dev = dev 

519 

520 if local is None: 

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

522 _local: LocalType = NegativeInfinity 

523 else: 

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

525 # the sorting rules in PEP440. 

526 # - Alpha numeric segments sort before numeric segments 

527 # - Alpha numeric segments sort lexicographically 

528 # - Numeric segments sort numerically 

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

530 # match exactly 

531 _local = tuple( 

532 (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local 

533 ) 

534 

535 return epoch, _release, _pre, _post, _dev, _local