Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/packaging/version.py: 24%

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

447 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. 

4""" 

5.. testsetup:: 

6 

7 from packaging.version import parse, normalize_pre, Version, _cmpkey 

8""" 

9 

10from __future__ import annotations 

11 

12import re 

13import sys 

14import typing 

15from collections.abc import Callable 

16from typing import ( 

17 Any, 

18 Literal, 

19 NamedTuple, 

20 SupportsInt, 

21 TypedDict, 

22) 

23 

24if typing.TYPE_CHECKING: 

25 from typing_extensions import Self, Unpack 

26 

27if sys.version_info >= (3, 13): # pragma: no cover 

28 from warnings import deprecated as _deprecated 

29elif typing.TYPE_CHECKING: 

30 from typing_extensions import deprecated as _deprecated 

31else: # pragma: no cover 

32 import functools 

33 import warnings 

34 

35 def _deprecated(message: str) -> object: 

36 def decorator(func: Callable[[...], object]) -> object: 

37 @functools.wraps(func) 

38 def wrapper(*args: object, **kwargs: object) -> object: 

39 warnings.warn( 

40 message, 

41 category=DeprecationWarning, 

42 stacklevel=2, 

43 ) 

44 return func(*args, **kwargs) 

45 

46 return wrapper 

47 

48 return decorator 

49 

50 

51_LETTER_NORMALIZATION = { 

52 "alpha": "a", 

53 "beta": "b", 

54 "c": "rc", 

55 "pre": "rc", 

56 "preview": "rc", 

57 "rev": "post", 

58 "r": "post", 

59} 

60 

61__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "normalize_pre", "parse"] 

62 

63 

64def __dir__() -> list[str]: 

65 return __all__ 

66 

67 

68LocalType = tuple[int | str, ...] 

69 

70CmpLocalType = tuple[tuple[int, str], ...] 

71CmpSuffix = tuple[int, int, int, int, int, int] 

72CmpKey = ( 

73 tuple[int, tuple[int, ...], CmpSuffix] 

74 | tuple[int, tuple[int, ...], CmpSuffix, CmpLocalType] 

75) 

76VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool] 

77 

78 

79class _VersionReplace(TypedDict, total=False): 

80 epoch: int | None 

81 release: tuple[int, ...] | None 

82 pre: tuple[str, int] | None 

83 post: int | None 

84 dev: int | None 

85 local: str | None 

86 

87 

88def normalize_pre(letter: str, /) -> str: 

89 """Normalize the pre-release segment of a version string. 

90 

91 Returns a lowercase version of the string if not a known pre-release 

92 identifier. 

93 

94 >>> normalize_pre('alpha') 

95 'a' 

96 >>> normalize_pre('BETA') 

97 'b' 

98 >>> normalize_pre('rc') 

99 'rc' 

100 

101 :param letter: 

102 

103 .. versionadded:: 26.1 

104 """ 

105 letter = letter.lower() 

106 return _LETTER_NORMALIZATION.get(letter, letter) 

107 

108 

109def parse(version: str) -> Version: 

110 """Parse the given version string. 

111 

112 This is identical to the :class:`Version` constructor. 

113 

114 >>> parse('1.0.dev1') 

115 <Version('1.0.dev1')> 

116 

117 :param version: The version string to parse. 

118 :raises InvalidVersion: When the version string is not a valid version. 

119 """ 

120 return Version(version) 

121 

122 

123class InvalidVersion(ValueError): 

124 """Raised when a version string is not a valid version. 

125 

126 >>> Version("invalid") 

127 Traceback (most recent call last): 

128 ... 

129 packaging.version.InvalidVersion: Invalid version: 'invalid' 

130 """ 

131 

132 

133class _BaseVersion: 

134 __slots__ = () 

135 

136 # This can also be a normal member (see the packaging_legacy package); 

137 # we are just requiring it to be readable. Actually defining a property 

138 # has runtime effect on subclasses, so it's typing only. 

139 if typing.TYPE_CHECKING: 

140 

141 @property 

142 def _key(self) -> tuple[Any, ...]: ... 

143 

144 def __hash__(self) -> int: 

145 return hash(self._key) 

146 

147 # Please keep the duplicated `isinstance` check 

148 # in the six comparisons hereunder 

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

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

151 if not isinstance(other, _BaseVersion): 

152 return NotImplemented 

153 

154 return self._key < other._key 

155 

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

157 if not isinstance(other, _BaseVersion): 

158 return NotImplemented 

159 

160 return self._key <= other._key 

161 

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

163 if not isinstance(other, _BaseVersion): 

164 return NotImplemented 

165 

166 return self._key == other._key 

167 

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

169 if not isinstance(other, _BaseVersion): 

170 return NotImplemented 

171 

172 return self._key >= other._key 

173 

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

175 if not isinstance(other, _BaseVersion): 

176 return NotImplemented 

177 

178 return self._key > other._key 

179 

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

181 if not isinstance(other, _BaseVersion): 

182 return NotImplemented 

183 

184 return self._key != other._key 

185 

186 

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

188# easier for 3rd party code to reuse 

189 

190# Note that ++ doesn't behave identically on CPython and PyPy, so not using it here 

191_VERSION_PATTERN = r""" 

192 v?+ # optional leading v 

193 (?a: 

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

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

196 (?P<pre> # pre-release 

197 [._-]?+ 

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

199 [._-]?+ 

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

201 )?+ 

202 (?P<post> # post release 

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

204 | 

205 (?: 

206 [._-]? 

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

208 [._-]? 

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

210 ) 

211 )?+ 

212 (?P<dev> # dev release 

213 [._-]?+ 

214 (?P<dev_l>dev) 

215 [._-]?+ 

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

217 )?+ 

218 ) 

219 (?a:\+ 

220 (?P<local> # local version 

221 [a-z0-9]+ 

222 (?:[._-][a-z0-9]+)*+ 

223 ) 

224 )?+ 

225""" 

226 

227_VERSION_PATTERN_OLD = _VERSION_PATTERN.replace("*+", "*").replace("?+", "?") 

228 

229# Possessive qualifiers were added in Python 3.11. 

230# CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795 

231# Older PyPy also had a bug. 

232VERSION_PATTERN = ( 

233 _VERSION_PATTERN_OLD 

234 if (sys.implementation.name == "cpython" and sys.version_info < (3, 11, 5)) 

235 or (sys.implementation.name == "pypy" and sys.version_info < (3, 11, 13)) 

236 or sys.version_info < (3, 11) 

237 else _VERSION_PATTERN 

238) 

239""" 

240A string containing the regular expression used to match a valid version. 

241 

242The pattern is not anchored at either end, and is intended for embedding in larger 

243expressions (for example, matching a version number as part of a file name). The 

244regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE`` 

245flags set. 

246 

247.. versionchanged:: 26.0 

248 

249 The regex now uses possessive qualifiers on Python 3.11 if they are 

250 supported (CPython 3.11.5+, PyPy 3.11.13+). 

251 

252:meta hide-value: 

253""" 

254 

255 

256# Validation pattern for local version in replace() 

257_LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE | re.ASCII) 

258 

259# Fast path: If a version has only digits and dots then we 

260# can skip the regex and parse it as a release segment 

261_SIMPLE_VERSION_INDICATORS = frozenset(".0123456789") 

262 

263 

264def _validate_epoch(value: object, /) -> int: 

265 epoch = value or 0 

266 if isinstance(epoch, int) and epoch >= 0: 

267 return epoch 

268 msg = f"epoch must be non-negative integer, got {epoch}" 

269 raise InvalidVersion(msg) 

270 

271 

272def _validate_release(value: object, /) -> tuple[int, ...]: 

273 release = (0,) if value is None else value 

274 if ( 

275 isinstance(release, tuple) 

276 and len(release) > 0 

277 and all(isinstance(i, int) and i >= 0 for i in release) 

278 ): 

279 return release 

280 msg = f"release must be a non-empty tuple of non-negative integers, got {release}" 

281 raise InvalidVersion(msg) 

282 

283 

284def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None: 

285 if value is None: 

286 return value 

287 if isinstance(value, tuple) and len(value) == 2: 

288 letter, number = value 

289 # The letter must be a string before it can be normalized. 

290 if ( 

291 isinstance(letter, str) 

292 and (normalized := normalize_pre(letter)) in {"a", "b", "rc"} 

293 and isinstance(number, int) 

294 and number >= 0 

295 ): 

296 # type checkers can't infer the Literal type here on letter 

297 return (normalized, number) # type: ignore[return-value] 

298 msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}" 

299 raise InvalidVersion(msg) 

300 

301 

302def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None: 

303 if value is None: 

304 return value 

305 if isinstance(value, int) and value >= 0: 

306 return ("post", value) 

307 msg = f"post must be non-negative integer, got {value}" 

308 raise InvalidVersion(msg) 

309 

310 

311def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None: 

312 if value is None: 

313 return value 

314 if isinstance(value, int) and value >= 0: 

315 return ("dev", value) 

316 msg = f"dev must be non-negative integer, got {value}" 

317 raise InvalidVersion(msg) 

318 

319 

320def _validate_local(value: object, /) -> LocalType | None: 

321 if value is None: 

322 return value 

323 if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value): 

324 return _parse_local_version(value) 

325 msg = f"local must be a valid version string, got {value!r}" 

326 raise InvalidVersion(msg) 

327 

328 

329# Backward compatibility for internals before 26.0. Do not use. 

330class _Version(NamedTuple): 

331 epoch: int 

332 release: tuple[int, ...] 

333 dev: tuple[Literal["dev"], int] | None 

334 pre: tuple[Literal["a", "b", "rc"], int] | None 

335 post: tuple[Literal["post"], int] | None 

336 local: LocalType | None 

337 

338 

339class Version(_BaseVersion): 

340 """This class abstracts handling of a project's versions. 

341 

342 A :class:`Version` instance is comparison aware and can be compared and 

343 sorted using the standard Python interfaces. 

344 

345 >>> v1 = Version("1.0a5") 

346 >>> v2 = Version("1.0") 

347 >>> v1 

348 <Version('1.0a5')> 

349 >>> v2 

350 <Version('1.0')> 

351 >>> v1 < v2 

352 True 

353 >>> v1 == v2 

354 False 

355 >>> v1 > v2 

356 False 

357 >>> v1 >= v2 

358 False 

359 >>> v1 <= v2 

360 True 

361 

362 :class:`Version` is immutable; use :meth:`__replace__` to change 

363 part of a version. 

364 

365 Instances are safe to serialize with :mod:`pickle`. They use a stable 

366 format so the same pickle can be loaded in future packaging releases. 

367 

368 .. versionchanged:: 26.2 

369 

370 Added a stable pickle format. Pickles created with packaging 26.2+ can 

371 be unpickled with future releases. Backward compatibility with pickles 

372 from packaging < 26.2 is supported but may be removed in a future 

373 release. 

374 """ 

375 

376 __slots__ = ( 

377 "_dev", 

378 "_epoch", 

379 "_hash_cache", 

380 "_key_cache", 

381 "_local", 

382 "_post", 

383 "_pre", 

384 "_release", 

385 ) 

386 __match_args__ = ("_str",) 

387 """ 

388 Pattern matching is supported on Python 3.10+. 

389 

390 .. versionadded:: 26.0 

391 

392 :meta hide-value: 

393 """ 

394 

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

396 

397 _epoch: int 

398 _release: tuple[int, ...] 

399 _dev: tuple[Literal["dev"], int] | None 

400 _pre: tuple[Literal["a", "b", "rc"], int] | None 

401 _post: tuple[Literal["post"], int] | None 

402 _local: LocalType | None 

403 

404 _hash_cache: int | None 

405 _key_cache: CmpKey | None 

406 

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

408 """Initialize a Version object. 

409 

410 :param version: 

411 The string representation of a version which will be parsed and normalized 

412 before use. 

413 :raises InvalidVersion: 

414 If the ``version`` does not conform to PEP 440 in any way then this 

415 exception will be raised. 

416 """ 

417 try: 

418 is_simple = _SIMPLE_VERSION_INDICATORS.issuperset(version) 

419 except TypeError: 

420 raise InvalidVersion(f"Invalid version: {version!r}") from None 

421 

422 if is_simple: 

423 try: 

424 self._release = tuple(map(int, version.split("."))) 

425 except AttributeError: 

426 raise InvalidVersion(f"Invalid version: {version!r}") from None 

427 except ValueError: 

428 # Empty parts (from "1..2", ".1", etc.) are invalid versions. 

429 # Any other ValueError (e.g. int str-digits limit) should 

430 # propagate to the caller. 

431 if "" in version.split("."): 

432 raise InvalidVersion(f"Invalid version: {version!r}") from None 

433 raise 

434 

435 self._epoch = 0 

436 self._pre = None 

437 self._post = None 

438 self._dev = None 

439 self._local = None 

440 self._key_cache = None 

441 self._hash_cache = None 

442 return 

443 

444 # Validate the version and parse it into pieces 

445 try: 

446 match = self._regex.fullmatch(version) 

447 except TypeError: 

448 raise InvalidVersion(f"Invalid version: {version!r}") from None 

449 if not match: 

450 raise InvalidVersion(f"Invalid version: {version!r}") 

451 self._epoch = int(match.group("epoch")) if match.group("epoch") else 0 

452 self._release = tuple(map(int, match.group("release").split("."))) 

453 # We can type ignore the assignments below because the regex guarantees 

454 # the correct strings 

455 self._pre = _parse_letter_version(match.group("pre_l"), match.group("pre_n")) # type: ignore[assignment] 

456 self._post = _parse_letter_version( # type: ignore[assignment] 

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

458 ) 

459 self._dev = _parse_letter_version(match.group("dev_l"), match.group("dev_n")) # type: ignore[assignment] 

460 self._local = _parse_local_version(match.group("local")) 

461 

462 # Key which will be used for sorting 

463 self._key_cache = None 

464 self._hash_cache = None 

465 

466 @classmethod 

467 def from_parts( 

468 cls, 

469 *, 

470 epoch: int = 0, 

471 release: tuple[int, ...], 

472 pre: tuple[str, int] | None = None, 

473 post: int | None = None, 

474 dev: int | None = None, 

475 local: str | None = None, 

476 ) -> Self: 

477 """ 

478 Return a new version composed of the various parts. 

479 

480 This allows you to build a version without going though a string and 

481 running a regular expression. It normalizes pre-release strings. The 

482 ``release=`` keyword argument is required. 

483 

484 >>> Version.from_parts(release=(1,2,3)) 

485 <Version('1.2.3')> 

486 >>> Version.from_parts(release=(0,1,0), pre=("b", 1)) 

487 <Version('0.1.0b1')> 

488 

489 :param epoch: 

490 :param release: This version tuple is required 

491 

492 .. versionadded:: 26.1 

493 """ 

494 _epoch = _validate_epoch(epoch) 

495 _release = _validate_release(release) 

496 _pre = _validate_pre(pre) if pre is not None else None 

497 _post = _validate_post(post) if post is not None else None 

498 _dev = _validate_dev(dev) if dev is not None else None 

499 _local = _validate_local(local) if local is not None else None 

500 

501 new_version = cls.__new__(cls) 

502 new_version._key_cache = None 

503 new_version._hash_cache = None 

504 new_version._epoch = _epoch 

505 new_version._release = _release 

506 new_version._pre = _pre 

507 new_version._post = _post 

508 new_version._dev = _dev 

509 new_version._local = _local 

510 

511 return new_version 

512 

513 def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self: 

514 """ 

515 __replace__(*, epoch=..., release=..., pre=..., post=..., dev=..., local=...) 

516 

517 Return a new version with parts replaced. 

518 

519 This returns a new version (unless no parts were changed). The 

520 pre-release is normalized. Setting a value to ``None`` clears it. 

521 

522 >>> v = Version("1.2.3") 

523 >>> v.__replace__(pre=("a", 1)) 

524 <Version('1.2.3a1')> 

525 

526 :param int | None epoch: 

527 :param tuple[int, ...] | None release: 

528 :param tuple[str, int] | None pre: 

529 :param int | None post: 

530 :param int | None dev: 

531 :param str | None local: 

532 

533 .. versionadded:: 26.0 

534 .. versionchanged:: 26.1 

535 

536 The pre-release portion is now normalized. 

537 """ 

538 epoch = _validate_epoch(kwargs["epoch"]) if "epoch" in kwargs else self._epoch 

539 release = ( 

540 _validate_release(kwargs["release"]) 

541 if "release" in kwargs 

542 else self._release 

543 ) 

544 pre = _validate_pre(kwargs["pre"]) if "pre" in kwargs else self._pre 

545 post = _validate_post(kwargs["post"]) if "post" in kwargs else self._post 

546 dev = _validate_dev(kwargs["dev"]) if "dev" in kwargs else self._dev 

547 local = _validate_local(kwargs["local"]) if "local" in kwargs else self._local 

548 

549 if ( 

550 epoch == self._epoch 

551 and release == self._release 

552 and pre == self._pre 

553 and post == self._post 

554 and dev == self._dev 

555 and local == self._local 

556 ): 

557 return self 

558 

559 new_version = self.__class__.__new__(self.__class__) 

560 new_version._key_cache = None 

561 new_version._hash_cache = None 

562 new_version._epoch = epoch 

563 new_version._release = release 

564 new_version._pre = pre 

565 new_version._post = post 

566 new_version._dev = dev 

567 new_version._local = local 

568 

569 return new_version 

570 

571 @property 

572 def _key(self) -> CmpKey: 

573 if self._key_cache is None: 

574 self._key_cache = _cmpkey( 

575 self._epoch, 

576 self._release, 

577 self._pre, 

578 self._post, 

579 self._dev, 

580 self._local, 

581 ) 

582 return self._key_cache 

583 

584 # __hash__ must be defined when __eq__ is overridden, 

585 # otherwise Python sets __hash__ to None. 

586 def __hash__(self) -> int: 

587 if (cached_hash := self._hash_cache) is not None: 

588 return cached_hash 

589 

590 if (key := self._key_cache) is None: 

591 self._key_cache = key = _cmpkey( 

592 self._epoch, 

593 self._release, 

594 self._pre, 

595 self._post, 

596 self._dev, 

597 self._local, 

598 ) 

599 self._hash_cache = cached_hash = hash(key) 

600 return cached_hash 

601 

602 # Override comparison methods to use direct _key_cache access 

603 # This is faster than property access, especially before Python 3.12 

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

605 if isinstance(other, Version): 

606 if self._key_cache is None: 

607 self._key_cache = _cmpkey( 

608 self._epoch, 

609 self._release, 

610 self._pre, 

611 self._post, 

612 self._dev, 

613 self._local, 

614 ) 

615 if other._key_cache is None: 

616 other._key_cache = _cmpkey( 

617 other._epoch, 

618 other._release, 

619 other._pre, 

620 other._post, 

621 other._dev, 

622 other._local, 

623 ) 

624 return self._key_cache < other._key_cache 

625 

626 if not isinstance(other, _BaseVersion): 

627 return NotImplemented 

628 

629 return super().__lt__(other) 

630 

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

632 if isinstance(other, Version): 

633 if self._key_cache is None: 

634 self._key_cache = _cmpkey( 

635 self._epoch, 

636 self._release, 

637 self._pre, 

638 self._post, 

639 self._dev, 

640 self._local, 

641 ) 

642 if other._key_cache is None: 

643 other._key_cache = _cmpkey( 

644 other._epoch, 

645 other._release, 

646 other._pre, 

647 other._post, 

648 other._dev, 

649 other._local, 

650 ) 

651 return self._key_cache <= other._key_cache 

652 

653 if not isinstance(other, _BaseVersion): 

654 return NotImplemented 

655 

656 return super().__le__(other) 

657 

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

659 if isinstance(other, Version): 

660 if self._key_cache is None: 

661 self._key_cache = _cmpkey( 

662 self._epoch, 

663 self._release, 

664 self._pre, 

665 self._post, 

666 self._dev, 

667 self._local, 

668 ) 

669 if other._key_cache is None: 

670 other._key_cache = _cmpkey( 

671 other._epoch, 

672 other._release, 

673 other._pre, 

674 other._post, 

675 other._dev, 

676 other._local, 

677 ) 

678 return self._key_cache == other._key_cache 

679 

680 if not isinstance(other, _BaseVersion): 

681 return NotImplemented 

682 

683 return super().__eq__(other) 

684 

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

686 if isinstance(other, Version): 

687 if self._key_cache is None: 

688 self._key_cache = _cmpkey( 

689 self._epoch, 

690 self._release, 

691 self._pre, 

692 self._post, 

693 self._dev, 

694 self._local, 

695 ) 

696 if other._key_cache is None: 

697 other._key_cache = _cmpkey( 

698 other._epoch, 

699 other._release, 

700 other._pre, 

701 other._post, 

702 other._dev, 

703 other._local, 

704 ) 

705 return self._key_cache >= other._key_cache 

706 

707 if not isinstance(other, _BaseVersion): 

708 return NotImplemented 

709 

710 return super().__ge__(other) 

711 

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

713 if isinstance(other, Version): 

714 if self._key_cache is None: 

715 self._key_cache = _cmpkey( 

716 self._epoch, 

717 self._release, 

718 self._pre, 

719 self._post, 

720 self._dev, 

721 self._local, 

722 ) 

723 if other._key_cache is None: 

724 other._key_cache = _cmpkey( 

725 other._epoch, 

726 other._release, 

727 other._pre, 

728 other._post, 

729 other._dev, 

730 other._local, 

731 ) 

732 return self._key_cache > other._key_cache 

733 

734 if not isinstance(other, _BaseVersion): 

735 return NotImplemented 

736 

737 return super().__gt__(other) 

738 

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

740 if isinstance(other, Version): 

741 if self._key_cache is None: 

742 self._key_cache = _cmpkey( 

743 self._epoch, 

744 self._release, 

745 self._pre, 

746 self._post, 

747 self._dev, 

748 self._local, 

749 ) 

750 if other._key_cache is None: 

751 other._key_cache = _cmpkey( 

752 other._epoch, 

753 other._release, 

754 other._pre, 

755 other._post, 

756 other._dev, 

757 other._local, 

758 ) 

759 return self._key_cache != other._key_cache 

760 

761 if not isinstance(other, _BaseVersion): 

762 return NotImplemented 

763 

764 return super().__ne__(other) 

765 

766 def __getstate__( 

767 self, 

768 ) -> tuple[ 

769 int, 

770 tuple[int, ...], 

771 tuple[str, int] | None, 

772 tuple[str, int] | None, 

773 tuple[str, int] | None, 

774 LocalType | None, 

775 ]: 

776 # Return state as a 6-item tuple for compactness: 

777 # (epoch, release, pre, post, dev, local) 

778 # Cache members are excluded and will be recomputed on demand 

779 return ( 

780 self._epoch, 

781 self._release, 

782 self._pre, 

783 self._post, 

784 self._dev, 

785 self._local, 

786 ) 

787 

788 def __setstate__(self, state: object) -> None: 

789 # Always discard cached values — they may contain stale references 

790 # (e.g. packaging._structures.InfinityType from pre-26.1 pickles) 

791 # and will be recomputed on demand from the core fields above. 

792 self._key_cache = None 

793 self._hash_cache = None 

794 

795 if isinstance(state, tuple): 

796 if len(state) == 6: 

797 # New format (26.2+): (epoch, release, pre, post, dev, local) 

798 ( 

799 self._epoch, 

800 self._release, 

801 self._pre, 

802 self._post, 

803 self._dev, 

804 self._local, 

805 ) = state 

806 return 

807 if len(state) == 2: 

808 # Format (packaging 26.0-26.1): (None, {slot: value}). 

809 _, slot_dict = state 

810 if isinstance(slot_dict, dict): 

811 self._epoch = slot_dict["_epoch"] 

812 self._release = slot_dict["_release"] 

813 self._pre = slot_dict.get("_pre") 

814 self._post = slot_dict.get("_post") 

815 self._dev = slot_dict.get("_dev") 

816 self._local = slot_dict.get("_local") 

817 return 

818 if isinstance(state, dict): 

819 # Old format (packaging <= 25.x, no __slots__): state is a plain 

820 # dict with "_version" (_Version NamedTuple) and "_key" entries. 

821 version_nt = state.get("_version") 

822 if version_nt is not None: 

823 self._epoch = version_nt.epoch 

824 self._release = version_nt.release 

825 self._pre = version_nt.pre 

826 self._post = version_nt.post 

827 self._dev = version_nt.dev 

828 self._local = version_nt.local 

829 return 

830 

831 raise TypeError(f"Cannot restore Version from {state!r}") 

832 

833 @property 

834 @_deprecated("Version._version is private and will be removed soon") 

835 def _version(self) -> _Version: 

836 return _Version( 

837 self._epoch, self._release, self._dev, self._pre, self._post, self._local 

838 ) 

839 

840 @_version.setter 

841 @_deprecated("Version._version is private and will be removed soon") 

842 def _version(self, value: _Version) -> None: 

843 self._epoch = value.epoch 

844 self._release = value.release 

845 self._dev = value.dev 

846 self._pre = value.pre 

847 self._post = value.post 

848 self._local = value.local 

849 self._key_cache = None 

850 self._hash_cache = None 

851 

852 def __repr__(self) -> str: 

853 """A representation of the Version that shows all internal state. 

854 

855 >>> Version('1.0.0') 

856 <Version('1.0.0')> 

857 """ 

858 return f"<{self.__class__.__name__}({str(self)!r})>" 

859 

860 def __str__(self) -> str: 

861 """A string representation of the version that can be round-tripped. 

862 

863 >>> str(Version("1.0a5")) 

864 '1.0a5' 

865 """ 

866 # This is a hot function, so not calling self.base_version 

867 version = ".".join(map(str, self.release)) 

868 

869 # Epoch 

870 if self.epoch: 

871 version = f"{self.epoch}!{version}" 

872 

873 # Pre-release 

874 if self.pre is not None: 

875 version += "".join(map(str, self.pre)) 

876 

877 # Post-release 

878 if self.post is not None: 

879 version += f".post{self.post}" 

880 

881 # Development release 

882 if self.dev is not None: 

883 version += f".dev{self.dev}" 

884 

885 # Local version segment 

886 if self.local is not None: 

887 version += f"+{self.local}" 

888 

889 return version 

890 

891 @property 

892 def _str(self) -> str: 

893 """Internal property for match_args""" 

894 return str(self) 

895 

896 @property 

897 def epoch(self) -> int: 

898 """The epoch of the version. 

899 

900 >>> Version("2.0.0").epoch 

901 0 

902 >>> Version("1!2.0.0").epoch 

903 1 

904 """ 

905 return self._epoch 

906 

907 @property 

908 def release(self) -> tuple[int, ...]: 

909 """The components of the "release" segment of the version. 

910 

911 >>> Version("1.2.3").release 

912 (1, 2, 3) 

913 >>> Version("2.0.0").release 

914 (2, 0, 0) 

915 >>> Version("1!2.0.0.post0").release 

916 (2, 0, 0) 

917 

918 Includes trailing zeroes but not the epoch or any pre-release / development / 

919 post-release suffixes. 

920 """ 

921 return self._release 

922 

923 @property 

924 def pre(self) -> tuple[Literal["a", "b", "rc"], int] | None: 

925 """The pre-release segment of the version. 

926 

927 >>> print(Version("1.2.3").pre) 

928 None 

929 >>> Version("1.2.3a1").pre 

930 ('a', 1) 

931 >>> Version("1.2.3b1").pre 

932 ('b', 1) 

933 >>> Version("1.2.3rc1").pre 

934 ('rc', 1) 

935 """ 

936 return self._pre 

937 

938 @property 

939 def post(self) -> int | None: 

940 """The post-release number of the version. 

941 

942 >>> print(Version("1.2.3").post) 

943 None 

944 >>> Version("1.2.3.post1").post 

945 1 

946 """ 

947 return self._post[1] if self._post else None 

948 

949 @property 

950 def dev(self) -> int | None: 

951 """The development number of the version. 

952 

953 >>> print(Version("1.2.3").dev) 

954 None 

955 >>> Version("1.2.3.dev1").dev 

956 1 

957 """ 

958 return self._dev[1] if self._dev else None 

959 

960 @property 

961 def local(self) -> str | None: 

962 """The local version segment of the version. 

963 

964 >>> print(Version("1.2.3").local) 

965 None 

966 >>> Version("1.2.3+abc").local 

967 'abc' 

968 """ 

969 if self._local: 

970 return ".".join(str(x) for x in self._local) 

971 else: 

972 return None 

973 

974 @property 

975 def public(self) -> str: 

976 """The public portion of the version. 

977 

978 This returns a string. If you want a :class:`Version` again and care 

979 about performance, use ``v.__replace__(local=None)`` instead. 

980 

981 >>> Version("1.2.3").public 

982 '1.2.3' 

983 >>> Version("1.2.3+abc").public 

984 '1.2.3' 

985 >>> Version("1!1.2.3dev1+abc").public 

986 '1!1.2.3.dev1' 

987 """ 

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

989 

990 @property 

991 def base_version(self) -> str: 

992 """The "base version" of the version. 

993 

994 This returns a string. If you want a :class:`Version` again and care 

995 about performance, use 

996 ``v.__replace__(pre=None, post=None, dev=None, local=None)`` instead. 

997 

998 >>> Version("1.2.3").base_version 

999 '1.2.3' 

1000 >>> Version("1.2.3+abc").base_version 

1001 '1.2.3' 

1002 >>> Version("1!1.2.3dev1+abc").base_version 

1003 '1!1.2.3' 

1004 

1005 The "base version" is the public version of the project without any pre or post 

1006 release markers. 

1007 """ 

1008 release_segment = ".".join(map(str, self.release)) 

1009 return f"{self.epoch}!{release_segment}" if self.epoch else release_segment 

1010 

1011 @property 

1012 def is_prerelease(self) -> bool: 

1013 """Whether this version is a pre-release. 

1014 

1015 >>> Version("1.2.3").is_prerelease 

1016 False 

1017 >>> Version("1.2.3a1").is_prerelease 

1018 True 

1019 >>> Version("1.2.3b1").is_prerelease 

1020 True 

1021 >>> Version("1.2.3rc1").is_prerelease 

1022 True 

1023 >>> Version("1.2.3dev1").is_prerelease 

1024 True 

1025 """ 

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

1027 

1028 @property 

1029 def is_postrelease(self) -> bool: 

1030 """Whether this version is a post-release. 

1031 

1032 >>> Version("1.2.3").is_postrelease 

1033 False 

1034 >>> Version("1.2.3.post1").is_postrelease 

1035 True 

1036 """ 

1037 return self.post is not None 

1038 

1039 @property 

1040 def is_devrelease(self) -> bool: 

1041 """Whether this version is a development release. 

1042 

1043 >>> Version("1.2.3").is_devrelease 

1044 False 

1045 >>> Version("1.2.3.dev1").is_devrelease 

1046 True 

1047 """ 

1048 return self.dev is not None 

1049 

1050 @property 

1051 def major(self) -> int: 

1052 """The first item of :attr:`release` or ``0`` if unavailable. 

1053 

1054 >>> Version("1.2.3").major 

1055 1 

1056 

1057 .. versionadded:: 20.0 

1058 """ 

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

1060 

1061 @property 

1062 def minor(self) -> int: 

1063 """The second item of :attr:`release` or ``0`` if unavailable. 

1064 

1065 >>> Version("1.2.3").minor 

1066 2 

1067 >>> Version("1").minor 

1068 0 

1069 

1070 .. versionadded:: 20.0 

1071 """ 

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

1073 

1074 @property 

1075 def micro(self) -> int: 

1076 """The third item of :attr:`release` or ``0`` if unavailable. 

1077 

1078 >>> Version("1.2.3").micro 

1079 3 

1080 >>> Version("1").micro 

1081 0 

1082 

1083 .. versionadded:: 20.0 

1084 """ 

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

1086 

1087 

1088class _TrimmedRelease(Version): 

1089 __slots__ = () 

1090 

1091 def __init__(self, version: str | Version) -> None: 

1092 if isinstance(version, Version): 

1093 self._epoch = version._epoch 

1094 self._release = version._release 

1095 self._dev = version._dev 

1096 self._pre = version._pre 

1097 self._post = version._post 

1098 self._local = version._local 

1099 self._key_cache = version._key_cache 

1100 self._hash_cache = version._hash_cache 

1101 return 

1102 super().__init__(version) # pragma: no cover 

1103 

1104 @property 

1105 def release(self) -> tuple[int, ...]: 

1106 """ 

1107 Release segment without any trailing zeros. 

1108 

1109 >>> _TrimmedRelease('1.0.0').release 

1110 (1,) 

1111 >>> _TrimmedRelease('0.0').release 

1112 (0,) 

1113 """ 

1114 # This leaves one 0. 

1115 rel = super().release 

1116 len_release = len(rel) 

1117 i = len_release 

1118 while i > 1 and rel[i - 1] == 0: 

1119 i -= 1 

1120 return rel if i == len_release else rel[:i] 

1121 

1122 

1123def _parse_letter_version( 

1124 letter: str | None, number: str | bytes | SupportsInt | None 

1125) -> tuple[str, int] | None: 

1126 if letter: 

1127 # We normalize any letters to their lower case form 

1128 letter = letter.lower() 

1129 

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

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

1132 # spelling. 

1133 letter = _LETTER_NORMALIZATION.get(letter, letter) 

1134 

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

1136 # not a numeral associated with it. 

1137 return letter, int(number or 0) 

1138 

1139 if number: 

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

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

1142 return "post", int(number) 

1143 

1144 return None 

1145 

1146 

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

1148 

1149 

1150def _parse_local_version(local: str | None) -> LocalType | None: 

1151 """ 

1152 Takes a string like ``"abc.1.twelve"`` and turns it into 

1153 ``("abc", 1, "twelve")``. 

1154 """ 

1155 if local is not None: 

1156 return tuple( 

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

1158 for part in _local_version_separators.split(local) 

1159 ) 

1160 return None 

1161 

1162 

1163# Sort ranks for pre-release: dev-only < a < b < rc < stable (no pre-release). 

1164_PRE_RANK = {"a": 0, "b": 1, "rc": 2} 

1165_PRE_RANK_DEV_ONLY = -1 # sorts before a(0) 

1166_PRE_RANK_STABLE = 3 # sorts after rc(2) 

1167 

1168# In local version segments, strings sort before ints per PEP 440. 

1169_LOCAL_STR_RANK = -1 # sorts before all non-negative ints 

1170 

1171# Pre-computed suffix for stable releases (no pre, post, or dev segments). 

1172# See _cmpkey() for the suffix layout. 

1173_STABLE_SUFFIX = (_PRE_RANK_STABLE, 0, 0, 0, 1, 0) 

1174 

1175 

1176def _cmpkey( 

1177 epoch: int, 

1178 release: tuple[int, ...], 

1179 pre: tuple[str, int] | None, 

1180 post: tuple[str, int] | None, 

1181 dev: tuple[str, int] | None, 

1182 local: LocalType | None, 

1183) -> CmpKey: 

1184 """Build a comparison key for PEP 440 ordering. 

1185 

1186 Returns ``(epoch, release, suffix)`` or 

1187 ``(epoch, release, suffix, local)`` so that plain tuple 

1188 comparison gives the correct order. 

1189 

1190 Trailing zeros are stripped from the release so that ``1.0.0 == 1``. 

1191 

1192 The suffix is a flat 6-int tuple that encodes pre/post/dev: 

1193 ``(pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)`` 

1194 

1195 pre_rank: dev-only=-1, a=0, b=1, rc=2, no-pre=3 

1196 Dev-only releases (no pre or post) get -1 so they sort before 

1197 any alpha/beta/rc. Releases without a pre-release tag get 3 

1198 so they sort after rc. 

1199 post_rank: no-post=0, post=1 

1200 Releases without a post segment sort before those with one. 

1201 dev_rank: dev=0, no-dev=1 

1202 Releases without a dev segment sort after those with one. 

1203 

1204 Local segments use ``(n, "")`` for ints and ``(-1, s)`` for strings, 

1205 following PEP 440: strings sort before ints, strings compare 

1206 lexicographically, ints compare numerically, and shorter segments 

1207 sort before longer when prefixes match. Versions without a local 

1208 segment sort before those with one (3-tuple < 4-tuple). 

1209 

1210 >>> _cmpkey(0, (1, 0, 0), None, None, None, None) 

1211 (0, (1,), (3, 0, 0, 0, 1, 0)) 

1212 >>> _cmpkey(0, (1,), ("a", 1), None, None, None) 

1213 (0, (1,), (0, 1, 0, 0, 1, 0)) 

1214 >>> _cmpkey(0, (1,), None, None, None, ("ubuntu", 1)) 

1215 (0, (1,), (3, 0, 0, 0, 1, 0), ((-1, 'ubuntu'), (1, ''))) 

1216 """ 

1217 # Strip trailing zeros: 1.0.0 compares equal to 1. 

1218 len_release = len(release) 

1219 i = len_release 

1220 while i and release[i - 1] == 0: 

1221 i -= 1 

1222 trimmed = release if i == len_release else release[:i] 

1223 

1224 # Fast path: stable release with no local segment. 

1225 if pre is None and post is None and dev is None and local is None: 

1226 return epoch, trimmed, _STABLE_SUFFIX 

1227 

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

1229 # dev-only (e.g. 1.0.dev1) sorts before all pre-releases. 

1230 pre_rank, pre_n = _PRE_RANK_DEV_ONLY, 0 

1231 elif pre is None: 

1232 pre_rank, pre_n = _PRE_RANK_STABLE, 0 

1233 else: 

1234 pre_rank, pre_n = _PRE_RANK[pre[0]], pre[1] 

1235 

1236 post_rank = 0 if post is None else 1 

1237 post_n = 0 if post is None else post[1] 

1238 

1239 dev_rank = 1 if dev is None else 0 

1240 dev_n = 0 if dev is None else dev[1] 

1241 

1242 suffix = (pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n) 

1243 

1244 if local is None: 

1245 return epoch, trimmed, suffix 

1246 

1247 cmp_local: CmpLocalType = tuple( 

1248 (seg, "") if isinstance(seg, int) else (_LOCAL_STR_RANK, seg) for seg in local 

1249 ) 

1250 return epoch, trimmed, suffix, cmp_local