version.py 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252
  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. from packaging.version import parse, normalize_pre, Version, _cmpkey
  7. """
  8. from __future__ import annotations
  9. import re
  10. import sys
  11. import typing
  12. from typing import (
  13. Any,
  14. Callable,
  15. Literal,
  16. NamedTuple,
  17. SupportsInt,
  18. TypedDict,
  19. Union,
  20. )
  21. if typing.TYPE_CHECKING:
  22. from typing_extensions import Self, Unpack
  23. if sys.version_info >= (3, 13): # pragma: no cover
  24. from warnings import deprecated as _deprecated
  25. elif typing.TYPE_CHECKING:
  26. from typing_extensions import deprecated as _deprecated
  27. else: # pragma: no cover
  28. import functools
  29. import warnings
  30. def _deprecated(message: str) -> object:
  31. def decorator(func: Callable[[...], object]) -> object:
  32. @functools.wraps(func)
  33. def wrapper(*args: object, **kwargs: object) -> object:
  34. warnings.warn(
  35. message,
  36. category=DeprecationWarning,
  37. stacklevel=2,
  38. )
  39. return func(*args, **kwargs)
  40. return wrapper
  41. return decorator
  42. _LETTER_NORMALIZATION = {
  43. "alpha": "a",
  44. "beta": "b",
  45. "c": "rc",
  46. "pre": "rc",
  47. "preview": "rc",
  48. "rev": "post",
  49. "r": "post",
  50. }
  51. __all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "normalize_pre", "parse"]
  52. def __dir__() -> list[str]:
  53. return __all__
  54. LocalType = tuple[Union[int, str], ...]
  55. CmpLocalType = tuple[tuple[int, str], ...]
  56. CmpSuffix = tuple[int, int, int, int, int, int]
  57. CmpKey = Union[
  58. tuple[int, tuple[int, ...], CmpSuffix],
  59. tuple[int, tuple[int, ...], CmpSuffix, CmpLocalType],
  60. ]
  61. VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
  62. class _VersionReplace(TypedDict, total=False):
  63. epoch: int | None
  64. release: tuple[int, ...] | None
  65. pre: tuple[str, int] | None
  66. post: int | None
  67. dev: int | None
  68. local: str | None
  69. def normalize_pre(letter: str, /) -> str:
  70. """Normalize the pre-release segment of a version string.
  71. Returns a lowercase version of the string if not a known pre-release
  72. identifier.
  73. >>> normalize_pre('alpha')
  74. 'a'
  75. >>> normalize_pre('BETA')
  76. 'b'
  77. >>> normalize_pre('rc')
  78. 'rc'
  79. :param letter:
  80. .. versionadded:: 26.1
  81. """
  82. letter = letter.lower()
  83. return _LETTER_NORMALIZATION.get(letter, letter)
  84. def parse(version: str) -> Version:
  85. """Parse the given version string.
  86. This is identical to the :class:`Version` constructor.
  87. >>> parse('1.0.dev1')
  88. <Version('1.0.dev1')>
  89. :param version: The version string to parse.
  90. :raises InvalidVersion: When the version string is not a valid version.
  91. """
  92. return Version(version)
  93. class InvalidVersion(ValueError):
  94. """Raised when a version string is not a valid version.
  95. >>> Version("invalid")
  96. Traceback (most recent call last):
  97. ...
  98. packaging.version.InvalidVersion: Invalid version: 'invalid'
  99. """
  100. class _BaseVersion:
  101. __slots__ = ()
  102. # This can also be a normal member (see the packaging_legacy package);
  103. # we are just requiring it to be readable. Actually defining a property
  104. # has runtime effect on subclasses, so it's typing only.
  105. if typing.TYPE_CHECKING:
  106. @property
  107. def _key(self) -> tuple[Any, ...]: ...
  108. def __hash__(self) -> int:
  109. return hash(self._key)
  110. # Please keep the duplicated `isinstance` check
  111. # in the six comparisons hereunder
  112. # unless you find a way to avoid adding overhead function calls.
  113. def __lt__(self, other: _BaseVersion) -> bool:
  114. if not isinstance(other, _BaseVersion):
  115. return NotImplemented
  116. return self._key < other._key
  117. def __le__(self, other: _BaseVersion) -> bool:
  118. if not isinstance(other, _BaseVersion):
  119. return NotImplemented
  120. return self._key <= other._key
  121. def __eq__(self, other: object) -> bool:
  122. if not isinstance(other, _BaseVersion):
  123. return NotImplemented
  124. return self._key == other._key
  125. def __ge__(self, other: _BaseVersion) -> bool:
  126. if not isinstance(other, _BaseVersion):
  127. return NotImplemented
  128. return self._key >= other._key
  129. def __gt__(self, other: _BaseVersion) -> bool:
  130. if not isinstance(other, _BaseVersion):
  131. return NotImplemented
  132. return self._key > other._key
  133. def __ne__(self, other: object) -> bool:
  134. if not isinstance(other, _BaseVersion):
  135. return NotImplemented
  136. return self._key != other._key
  137. # Deliberately not anchored to the start and end of the string, to make it
  138. # easier for 3rd party code to reuse
  139. # Note that ++ doesn't behave identically on CPython and PyPy, so not using it here
  140. _VERSION_PATTERN = r"""
  141. v?+ # optional leading v
  142. (?a:
  143. (?:(?P<epoch>[0-9]+)!)?+ # epoch
  144. (?P<release>[0-9]+(?:\.[0-9]+)*+) # release segment
  145. (?P<pre> # pre-release
  146. [._-]?+
  147. (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
  148. [._-]?+
  149. (?P<pre_n>[0-9]+)?
  150. )?+
  151. (?P<post> # post release
  152. (?:-(?P<post_n1>[0-9]+))
  153. |
  154. (?:
  155. [._-]?
  156. (?P<post_l>post|rev|r)
  157. [._-]?
  158. (?P<post_n2>[0-9]+)?
  159. )
  160. )?+
  161. (?P<dev> # dev release
  162. [._-]?+
  163. (?P<dev_l>dev)
  164. [._-]?+
  165. (?P<dev_n>[0-9]+)?
  166. )?+
  167. )
  168. (?a:\+
  169. (?P<local> # local version
  170. [a-z0-9]+
  171. (?:[._-][a-z0-9]+)*+
  172. )
  173. )?+
  174. """
  175. _VERSION_PATTERN_OLD = _VERSION_PATTERN.replace("*+", "*").replace("?+", "?")
  176. # Possessive qualifiers were added in Python 3.11.
  177. # CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795
  178. # Older PyPy also had a bug.
  179. VERSION_PATTERN = (
  180. _VERSION_PATTERN_OLD
  181. if (sys.implementation.name == "cpython" and sys.version_info < (3, 11, 5))
  182. or (sys.implementation.name == "pypy" and sys.version_info < (3, 11, 13))
  183. or sys.version_info < (3, 11)
  184. else _VERSION_PATTERN
  185. )
  186. """
  187. A string containing the regular expression used to match a valid version.
  188. The pattern is not anchored at either end, and is intended for embedding in larger
  189. expressions (for example, matching a version number as part of a file name). The
  190. regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
  191. flags set.
  192. .. versionchanged:: 26.0
  193. The regex now uses possessive qualifiers on Python 3.11 if they are
  194. supported (CPython 3.11.5+, PyPy 3.11.13+).
  195. :meta hide-value:
  196. """
  197. # Validation pattern for local version in replace()
  198. _LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE | re.ASCII)
  199. # Fast path: If a version has only digits and dots then we
  200. # can skip the regex and parse it as a release segment
  201. _SIMPLE_VERSION_INDICATORS = frozenset(".0123456789")
  202. def _validate_epoch(value: object, /) -> int:
  203. epoch = value or 0
  204. if isinstance(epoch, int) and epoch >= 0:
  205. return epoch
  206. msg = f"epoch must be non-negative integer, got {epoch}"
  207. raise InvalidVersion(msg)
  208. def _validate_release(value: object, /) -> tuple[int, ...]:
  209. release = (0,) if value is None else value
  210. if (
  211. isinstance(release, tuple)
  212. and len(release) > 0
  213. and all(isinstance(i, int) and i >= 0 for i in release)
  214. ):
  215. return release
  216. msg = f"release must be a non-empty tuple of non-negative integers, got {release}"
  217. raise InvalidVersion(msg)
  218. def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None:
  219. if value is None:
  220. return value
  221. if isinstance(value, tuple) and len(value) == 2:
  222. letter, number = value
  223. # The letter must be a string before it can be normalized.
  224. if (
  225. isinstance(letter, str)
  226. and (normalized := normalize_pre(letter)) in {"a", "b", "rc"}
  227. and isinstance(number, int)
  228. and number >= 0
  229. ):
  230. # type checkers can't infer the Literal type here on letter
  231. return (normalized, number) # type: ignore[return-value]
  232. msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}"
  233. raise InvalidVersion(msg)
  234. def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:
  235. if value is None:
  236. return value
  237. if isinstance(value, int) and value >= 0:
  238. return ("post", value)
  239. msg = f"post must be non-negative integer, got {value}"
  240. raise InvalidVersion(msg)
  241. def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:
  242. if value is None:
  243. return value
  244. if isinstance(value, int) and value >= 0:
  245. return ("dev", value)
  246. msg = f"dev must be non-negative integer, got {value}"
  247. raise InvalidVersion(msg)
  248. def _validate_local(value: object, /) -> LocalType | None:
  249. if value is None:
  250. return value
  251. if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):
  252. return _parse_local_version(value)
  253. msg = f"local must be a valid version string, got {value!r}"
  254. raise InvalidVersion(msg)
  255. # Backward compatibility for internals before 26.0. Do not use.
  256. class _Version(NamedTuple):
  257. epoch: int
  258. release: tuple[int, ...]
  259. dev: tuple[Literal["dev"], int] | None
  260. pre: tuple[Literal["a", "b", "rc"], int] | None
  261. post: tuple[Literal["post"], int] | None
  262. local: LocalType | None
  263. class Version(_BaseVersion):
  264. """This class abstracts handling of a project's versions.
  265. A :class:`Version` instance is comparison aware and can be compared and
  266. sorted using the standard Python interfaces.
  267. >>> v1 = Version("1.0a5")
  268. >>> v2 = Version("1.0")
  269. >>> v1
  270. <Version('1.0a5')>
  271. >>> v2
  272. <Version('1.0')>
  273. >>> v1 < v2
  274. True
  275. >>> v1 == v2
  276. False
  277. >>> v1 > v2
  278. False
  279. >>> v1 >= v2
  280. False
  281. >>> v1 <= v2
  282. True
  283. :class:`Version` is immutable; use :meth:`__replace__` to change
  284. part of a version.
  285. Instances are safe to serialize with :mod:`pickle`. They use a stable
  286. format so the same pickle can be loaded in future packaging releases.
  287. .. versionchanged:: 26.2
  288. Added a stable pickle format. Pickles created with packaging 26.2+ can
  289. be unpickled with future releases. Backward compatibility with pickles
  290. from packaging < 26.2 is supported but may be removed in a future
  291. release.
  292. """
  293. __slots__ = (
  294. "_dev",
  295. "_epoch",
  296. "_hash_cache",
  297. "_key_cache",
  298. "_local",
  299. "_post",
  300. "_pre",
  301. "_release",
  302. )
  303. __match_args__ = ("_str",)
  304. """
  305. Pattern matching is supported on Python 3.10+.
  306. .. versionadded:: 26.0
  307. :meta hide-value:
  308. """
  309. _regex = re.compile(r"\s*" + VERSION_PATTERN + r"\s*", re.VERBOSE | re.IGNORECASE)
  310. _epoch: int
  311. _release: tuple[int, ...]
  312. _dev: tuple[Literal["dev"], int] | None
  313. _pre: tuple[Literal["a", "b", "rc"], int] | None
  314. _post: tuple[Literal["post"], int] | None
  315. _local: LocalType | None
  316. _hash_cache: int | None
  317. _key_cache: CmpKey | None
  318. def __init__(self, version: str) -> None:
  319. """Initialize a Version object.
  320. :param version:
  321. The string representation of a version which will be parsed and normalized
  322. before use.
  323. :raises InvalidVersion:
  324. If the ``version`` does not conform to PEP 440 in any way then this
  325. exception will be raised.
  326. """
  327. try:
  328. is_simple = _SIMPLE_VERSION_INDICATORS.issuperset(version)
  329. except TypeError:
  330. raise InvalidVersion(f"Invalid version: {version!r}") from None
  331. if is_simple:
  332. try:
  333. self._release = tuple(map(int, version.split(".")))
  334. except AttributeError:
  335. raise InvalidVersion(f"Invalid version: {version!r}") from None
  336. except ValueError:
  337. # Empty parts (from "1..2", ".1", etc.) are invalid versions.
  338. # Any other ValueError (e.g. int str-digits limit) should
  339. # propagate to the caller.
  340. if "" in version.split("."):
  341. raise InvalidVersion(f"Invalid version: {version!r}") from None
  342. # TODO: remove "no cover" when Python 3.9 is dropped.
  343. raise # pragma: no cover
  344. self._epoch = 0
  345. self._pre = None
  346. self._post = None
  347. self._dev = None
  348. self._local = None
  349. self._key_cache = None
  350. self._hash_cache = None
  351. return
  352. # Validate the version and parse it into pieces
  353. try:
  354. match = self._regex.fullmatch(version)
  355. except TypeError:
  356. raise InvalidVersion(f"Invalid version: {version!r}") from None
  357. if not match:
  358. raise InvalidVersion(f"Invalid version: {version!r}")
  359. self._epoch = int(match.group("epoch")) if match.group("epoch") else 0
  360. self._release = tuple(map(int, match.group("release").split(".")))
  361. # We can type ignore the assignments below because the regex guarantees
  362. # the correct strings
  363. self._pre = _parse_letter_version(match.group("pre_l"), match.group("pre_n")) # type: ignore[assignment]
  364. self._post = _parse_letter_version( # type: ignore[assignment]
  365. match.group("post_l"), match.group("post_n1") or match.group("post_n2")
  366. )
  367. self._dev = _parse_letter_version(match.group("dev_l"), match.group("dev_n")) # type: ignore[assignment]
  368. self._local = _parse_local_version(match.group("local"))
  369. # Key which will be used for sorting
  370. self._key_cache = None
  371. self._hash_cache = None
  372. @classmethod
  373. def from_parts(
  374. cls,
  375. *,
  376. epoch: int = 0,
  377. release: tuple[int, ...],
  378. pre: tuple[str, int] | None = None,
  379. post: int | None = None,
  380. dev: int | None = None,
  381. local: str | None = None,
  382. ) -> Self:
  383. """
  384. Return a new version composed of the various parts.
  385. This allows you to build a version without going though a string and
  386. running a regular expression. It normalizes pre-release strings. The
  387. ``release=`` keyword argument is required.
  388. >>> Version.from_parts(release=(1,2,3))
  389. <Version('1.2.3')>
  390. >>> Version.from_parts(release=(0,1,0), pre=("b", 1))
  391. <Version('0.1.0b1')>
  392. :param epoch:
  393. :param release: This version tuple is required
  394. .. versionadded:: 26.1
  395. """
  396. _epoch = _validate_epoch(epoch)
  397. _release = _validate_release(release)
  398. _pre = _validate_pre(pre) if pre is not None else None
  399. _post = _validate_post(post) if post is not None else None
  400. _dev = _validate_dev(dev) if dev is not None else None
  401. _local = _validate_local(local) if local is not None else None
  402. new_version = cls.__new__(cls)
  403. new_version._key_cache = None
  404. new_version._hash_cache = None
  405. new_version._epoch = _epoch
  406. new_version._release = _release
  407. new_version._pre = _pre
  408. new_version._post = _post
  409. new_version._dev = _dev
  410. new_version._local = _local
  411. return new_version
  412. def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:
  413. """
  414. __replace__(*, epoch=..., release=..., pre=..., post=..., dev=..., local=...)
  415. Return a new version with parts replaced.
  416. This returns a new version (unless no parts were changed). The
  417. pre-release is normalized. Setting a value to ``None`` clears it.
  418. >>> v = Version("1.2.3")
  419. >>> v.__replace__(pre=("a", 1))
  420. <Version('1.2.3a1')>
  421. :param int | None epoch:
  422. :param tuple[int, ...] | None release:
  423. :param tuple[str, int] | None pre:
  424. :param int | None post:
  425. :param int | None dev:
  426. :param str | None local:
  427. .. versionadded:: 26.0
  428. .. versionchanged:: 26.1
  429. The pre-release portion is now normalized.
  430. """
  431. epoch = _validate_epoch(kwargs["epoch"]) if "epoch" in kwargs else self._epoch
  432. release = (
  433. _validate_release(kwargs["release"])
  434. if "release" in kwargs
  435. else self._release
  436. )
  437. pre = _validate_pre(kwargs["pre"]) if "pre" in kwargs else self._pre
  438. post = _validate_post(kwargs["post"]) if "post" in kwargs else self._post
  439. dev = _validate_dev(kwargs["dev"]) if "dev" in kwargs else self._dev
  440. local = _validate_local(kwargs["local"]) if "local" in kwargs else self._local
  441. if (
  442. epoch == self._epoch
  443. and release == self._release
  444. and pre == self._pre
  445. and post == self._post
  446. and dev == self._dev
  447. and local == self._local
  448. ):
  449. return self
  450. new_version = self.__class__.__new__(self.__class__)
  451. new_version._key_cache = None
  452. new_version._hash_cache = None
  453. new_version._epoch = epoch
  454. new_version._release = release
  455. new_version._pre = pre
  456. new_version._post = post
  457. new_version._dev = dev
  458. new_version._local = local
  459. return new_version
  460. @property
  461. def _key(self) -> CmpKey:
  462. if self._key_cache is None:
  463. self._key_cache = _cmpkey(
  464. self._epoch,
  465. self._release,
  466. self._pre,
  467. self._post,
  468. self._dev,
  469. self._local,
  470. )
  471. return self._key_cache
  472. # __hash__ must be defined when __eq__ is overridden,
  473. # otherwise Python sets __hash__ to None.
  474. def __hash__(self) -> int:
  475. if (cached_hash := self._hash_cache) is not None:
  476. return cached_hash
  477. if (key := self._key_cache) is None:
  478. self._key_cache = key = _cmpkey(
  479. self._epoch,
  480. self._release,
  481. self._pre,
  482. self._post,
  483. self._dev,
  484. self._local,
  485. )
  486. self._hash_cache = cached_hash = hash(key)
  487. return cached_hash
  488. # Override comparison methods to use direct _key_cache access
  489. # This is faster than property access, especially before Python 3.12
  490. def __lt__(self, other: _BaseVersion) -> bool:
  491. if isinstance(other, Version):
  492. if self._key_cache is None:
  493. self._key_cache = _cmpkey(
  494. self._epoch,
  495. self._release,
  496. self._pre,
  497. self._post,
  498. self._dev,
  499. self._local,
  500. )
  501. if other._key_cache is None:
  502. other._key_cache = _cmpkey(
  503. other._epoch,
  504. other._release,
  505. other._pre,
  506. other._post,
  507. other._dev,
  508. other._local,
  509. )
  510. return self._key_cache < other._key_cache
  511. if not isinstance(other, _BaseVersion):
  512. return NotImplemented
  513. return super().__lt__(other)
  514. def __le__(self, other: _BaseVersion) -> bool:
  515. if isinstance(other, Version):
  516. if self._key_cache is None:
  517. self._key_cache = _cmpkey(
  518. self._epoch,
  519. self._release,
  520. self._pre,
  521. self._post,
  522. self._dev,
  523. self._local,
  524. )
  525. if other._key_cache is None:
  526. other._key_cache = _cmpkey(
  527. other._epoch,
  528. other._release,
  529. other._pre,
  530. other._post,
  531. other._dev,
  532. other._local,
  533. )
  534. return self._key_cache <= other._key_cache
  535. if not isinstance(other, _BaseVersion):
  536. return NotImplemented
  537. return super().__le__(other)
  538. def __eq__(self, other: object) -> bool:
  539. if isinstance(other, Version):
  540. if self._key_cache is None:
  541. self._key_cache = _cmpkey(
  542. self._epoch,
  543. self._release,
  544. self._pre,
  545. self._post,
  546. self._dev,
  547. self._local,
  548. )
  549. if other._key_cache is None:
  550. other._key_cache = _cmpkey(
  551. other._epoch,
  552. other._release,
  553. other._pre,
  554. other._post,
  555. other._dev,
  556. other._local,
  557. )
  558. return self._key_cache == other._key_cache
  559. if not isinstance(other, _BaseVersion):
  560. return NotImplemented
  561. return super().__eq__(other)
  562. def __ge__(self, other: _BaseVersion) -> bool:
  563. if isinstance(other, Version):
  564. if self._key_cache is None:
  565. self._key_cache = _cmpkey(
  566. self._epoch,
  567. self._release,
  568. self._pre,
  569. self._post,
  570. self._dev,
  571. self._local,
  572. )
  573. if other._key_cache is None:
  574. other._key_cache = _cmpkey(
  575. other._epoch,
  576. other._release,
  577. other._pre,
  578. other._post,
  579. other._dev,
  580. other._local,
  581. )
  582. return self._key_cache >= other._key_cache
  583. if not isinstance(other, _BaseVersion):
  584. return NotImplemented
  585. return super().__ge__(other)
  586. def __gt__(self, other: _BaseVersion) -> bool:
  587. if isinstance(other, Version):
  588. if self._key_cache is None:
  589. self._key_cache = _cmpkey(
  590. self._epoch,
  591. self._release,
  592. self._pre,
  593. self._post,
  594. self._dev,
  595. self._local,
  596. )
  597. if other._key_cache is None:
  598. other._key_cache = _cmpkey(
  599. other._epoch,
  600. other._release,
  601. other._pre,
  602. other._post,
  603. other._dev,
  604. other._local,
  605. )
  606. return self._key_cache > other._key_cache
  607. if not isinstance(other, _BaseVersion):
  608. return NotImplemented
  609. return super().__gt__(other)
  610. def __ne__(self, other: object) -> bool:
  611. if isinstance(other, Version):
  612. if self._key_cache is None:
  613. self._key_cache = _cmpkey(
  614. self._epoch,
  615. self._release,
  616. self._pre,
  617. self._post,
  618. self._dev,
  619. self._local,
  620. )
  621. if other._key_cache is None:
  622. other._key_cache = _cmpkey(
  623. other._epoch,
  624. other._release,
  625. other._pre,
  626. other._post,
  627. other._dev,
  628. other._local,
  629. )
  630. return self._key_cache != other._key_cache
  631. if not isinstance(other, _BaseVersion):
  632. return NotImplemented
  633. return super().__ne__(other)
  634. def __getstate__(
  635. self,
  636. ) -> tuple[
  637. int,
  638. tuple[int, ...],
  639. tuple[str, int] | None,
  640. tuple[str, int] | None,
  641. tuple[str, int] | None,
  642. LocalType | None,
  643. ]:
  644. # Return state as a 6-item tuple for compactness:
  645. # (epoch, release, pre, post, dev, local)
  646. # Cache members are excluded and will be recomputed on demand
  647. return (
  648. self._epoch,
  649. self._release,
  650. self._pre,
  651. self._post,
  652. self._dev,
  653. self._local,
  654. )
  655. def __setstate__(self, state: object) -> None:
  656. # Always discard cached values — they may contain stale references
  657. # (e.g. packaging._structures.InfinityType from pre-26.1 pickles)
  658. # and will be recomputed on demand from the core fields above.
  659. self._key_cache = None
  660. self._hash_cache = None
  661. if isinstance(state, tuple):
  662. if len(state) == 6:
  663. # New format (26.2+): (epoch, release, pre, post, dev, local)
  664. (
  665. self._epoch,
  666. self._release,
  667. self._pre,
  668. self._post,
  669. self._dev,
  670. self._local,
  671. ) = state
  672. return
  673. if len(state) == 2:
  674. # Format (packaging 26.0-26.1): (None, {slot: value}).
  675. _, slot_dict = state
  676. if isinstance(slot_dict, dict):
  677. self._epoch = slot_dict["_epoch"]
  678. self._release = slot_dict["_release"]
  679. self._pre = slot_dict.get("_pre")
  680. self._post = slot_dict.get("_post")
  681. self._dev = slot_dict.get("_dev")
  682. self._local = slot_dict.get("_local")
  683. return
  684. if isinstance(state, dict):
  685. # Old format (packaging <= 25.x, no __slots__): state is a plain
  686. # dict with "_version" (_Version NamedTuple) and "_key" entries.
  687. version_nt = state.get("_version")
  688. if version_nt is not None:
  689. self._epoch = version_nt.epoch
  690. self._release = version_nt.release
  691. self._pre = version_nt.pre
  692. self._post = version_nt.post
  693. self._dev = version_nt.dev
  694. self._local = version_nt.local
  695. return
  696. raise TypeError(f"Cannot restore Version from {state!r}")
  697. @property
  698. @_deprecated("Version._version is private and will be removed soon")
  699. def _version(self) -> _Version:
  700. return _Version(
  701. self._epoch, self._release, self._dev, self._pre, self._post, self._local
  702. )
  703. @_version.setter
  704. @_deprecated("Version._version is private and will be removed soon")
  705. def _version(self, value: _Version) -> None:
  706. self._epoch = value.epoch
  707. self._release = value.release
  708. self._dev = value.dev
  709. self._pre = value.pre
  710. self._post = value.post
  711. self._local = value.local
  712. self._key_cache = None
  713. self._hash_cache = None
  714. def __repr__(self) -> str:
  715. """A representation of the Version that shows all internal state.
  716. >>> Version('1.0.0')
  717. <Version('1.0.0')>
  718. """
  719. return f"<{self.__class__.__name__}({str(self)!r})>"
  720. def __str__(self) -> str:
  721. """A string representation of the version that can be round-tripped.
  722. >>> str(Version("1.0a5"))
  723. '1.0a5'
  724. """
  725. # This is a hot function, so not calling self.base_version
  726. version = ".".join(map(str, self.release))
  727. # Epoch
  728. if self.epoch:
  729. version = f"{self.epoch}!{version}"
  730. # Pre-release
  731. if self.pre is not None:
  732. version += "".join(map(str, self.pre))
  733. # Post-release
  734. if self.post is not None:
  735. version += f".post{self.post}"
  736. # Development release
  737. if self.dev is not None:
  738. version += f".dev{self.dev}"
  739. # Local version segment
  740. if self.local is not None:
  741. version += f"+{self.local}"
  742. return version
  743. @property
  744. def _str(self) -> str:
  745. """Internal property for match_args"""
  746. return str(self)
  747. @property
  748. def epoch(self) -> int:
  749. """The epoch of the version.
  750. >>> Version("2.0.0").epoch
  751. 0
  752. >>> Version("1!2.0.0").epoch
  753. 1
  754. """
  755. return self._epoch
  756. @property
  757. def release(self) -> tuple[int, ...]:
  758. """The components of the "release" segment of the version.
  759. >>> Version("1.2.3").release
  760. (1, 2, 3)
  761. >>> Version("2.0.0").release
  762. (2, 0, 0)
  763. >>> Version("1!2.0.0.post0").release
  764. (2, 0, 0)
  765. Includes trailing zeroes but not the epoch or any pre-release / development /
  766. post-release suffixes.
  767. """
  768. return self._release
  769. @property
  770. def pre(self) -> tuple[Literal["a", "b", "rc"], int] | None:
  771. """The pre-release segment of the version.
  772. >>> print(Version("1.2.3").pre)
  773. None
  774. >>> Version("1.2.3a1").pre
  775. ('a', 1)
  776. >>> Version("1.2.3b1").pre
  777. ('b', 1)
  778. >>> Version("1.2.3rc1").pre
  779. ('rc', 1)
  780. """
  781. return self._pre
  782. @property
  783. def post(self) -> int | None:
  784. """The post-release number of the version.
  785. >>> print(Version("1.2.3").post)
  786. None
  787. >>> Version("1.2.3.post1").post
  788. 1
  789. """
  790. return self._post[1] if self._post else None
  791. @property
  792. def dev(self) -> int | None:
  793. """The development number of the version.
  794. >>> print(Version("1.2.3").dev)
  795. None
  796. >>> Version("1.2.3.dev1").dev
  797. 1
  798. """
  799. return self._dev[1] if self._dev else None
  800. @property
  801. def local(self) -> str | None:
  802. """The local version segment of the version.
  803. >>> print(Version("1.2.3").local)
  804. None
  805. >>> Version("1.2.3+abc").local
  806. 'abc'
  807. """
  808. if self._local:
  809. return ".".join(str(x) for x in self._local)
  810. else:
  811. return None
  812. @property
  813. def public(self) -> str:
  814. """The public portion of the version.
  815. This returns a string. If you want a :class:`Version` again and care
  816. about performance, use ``v.__replace__(local=None)`` instead.
  817. >>> Version("1.2.3").public
  818. '1.2.3'
  819. >>> Version("1.2.3+abc").public
  820. '1.2.3'
  821. >>> Version("1!1.2.3dev1+abc").public
  822. '1!1.2.3.dev1'
  823. """
  824. return str(self).split("+", 1)[0]
  825. @property
  826. def base_version(self) -> str:
  827. """The "base version" of the version.
  828. This returns a string. If you want a :class:`Version` again and care
  829. about performance, use
  830. ``v.__replace__(pre=None, post=None, dev=None, local=None)`` instead.
  831. >>> Version("1.2.3").base_version
  832. '1.2.3'
  833. >>> Version("1.2.3+abc").base_version
  834. '1.2.3'
  835. >>> Version("1!1.2.3dev1+abc").base_version
  836. '1!1.2.3'
  837. The "base version" is the public version of the project without any pre or post
  838. release markers.
  839. """
  840. release_segment = ".".join(map(str, self.release))
  841. return f"{self.epoch}!{release_segment}" if self.epoch else release_segment
  842. @property
  843. def is_prerelease(self) -> bool:
  844. """Whether this version is a pre-release.
  845. >>> Version("1.2.3").is_prerelease
  846. False
  847. >>> Version("1.2.3a1").is_prerelease
  848. True
  849. >>> Version("1.2.3b1").is_prerelease
  850. True
  851. >>> Version("1.2.3rc1").is_prerelease
  852. True
  853. >>> Version("1.2.3dev1").is_prerelease
  854. True
  855. """
  856. return self.dev is not None or self.pre is not None
  857. @property
  858. def is_postrelease(self) -> bool:
  859. """Whether this version is a post-release.
  860. >>> Version("1.2.3").is_postrelease
  861. False
  862. >>> Version("1.2.3.post1").is_postrelease
  863. True
  864. """
  865. return self.post is not None
  866. @property
  867. def is_devrelease(self) -> bool:
  868. """Whether this version is a development release.
  869. >>> Version("1.2.3").is_devrelease
  870. False
  871. >>> Version("1.2.3.dev1").is_devrelease
  872. True
  873. """
  874. return self.dev is not None
  875. @property
  876. def major(self) -> int:
  877. """The first item of :attr:`release` or ``0`` if unavailable.
  878. >>> Version("1.2.3").major
  879. 1
  880. .. versionadded:: 20.0
  881. """
  882. return self.release[0] if len(self.release) >= 1 else 0
  883. @property
  884. def minor(self) -> int:
  885. """The second item of :attr:`release` or ``0`` if unavailable.
  886. >>> Version("1.2.3").minor
  887. 2
  888. >>> Version("1").minor
  889. 0
  890. .. versionadded:: 20.0
  891. """
  892. return self.release[1] if len(self.release) >= 2 else 0
  893. @property
  894. def micro(self) -> int:
  895. """The third item of :attr:`release` or ``0`` if unavailable.
  896. >>> Version("1.2.3").micro
  897. 3
  898. >>> Version("1").micro
  899. 0
  900. .. versionadded:: 20.0
  901. """
  902. return self.release[2] if len(self.release) >= 3 else 0
  903. class _TrimmedRelease(Version):
  904. __slots__ = ()
  905. def __init__(self, version: str | Version) -> None:
  906. if isinstance(version, Version):
  907. self._epoch = version._epoch
  908. self._release = version._release
  909. self._dev = version._dev
  910. self._pre = version._pre
  911. self._post = version._post
  912. self._local = version._local
  913. self._key_cache = version._key_cache
  914. self._hash_cache = version._hash_cache
  915. return
  916. super().__init__(version) # pragma: no cover
  917. @property
  918. def release(self) -> tuple[int, ...]:
  919. """
  920. Release segment without any trailing zeros.
  921. >>> _TrimmedRelease('1.0.0').release
  922. (1,)
  923. >>> _TrimmedRelease('0.0').release
  924. (0,)
  925. """
  926. # This leaves one 0.
  927. rel = super().release
  928. len_release = len(rel)
  929. i = len_release
  930. while i > 1 and rel[i - 1] == 0:
  931. i -= 1
  932. return rel if i == len_release else rel[:i]
  933. def _parse_letter_version(
  934. letter: str | None, number: str | bytes | SupportsInt | None
  935. ) -> tuple[str, int] | None:
  936. if letter:
  937. # We normalize any letters to their lower case form
  938. letter = letter.lower()
  939. # We consider some words to be alternate spellings of other words and
  940. # in those cases we want to normalize the spellings to our preferred
  941. # spelling.
  942. letter = _LETTER_NORMALIZATION.get(letter, letter)
  943. # We consider there to be an implicit 0 in a pre-release if there is
  944. # not a numeral associated with it.
  945. return letter, int(number or 0)
  946. if number:
  947. # We assume if we are given a number, but we are not given a letter
  948. # then this is using the implicit post release syntax (e.g. 1.0-1)
  949. return "post", int(number)
  950. return None
  951. _local_version_separators = re.compile(r"[\._-]")
  952. def _parse_local_version(local: str | None) -> LocalType | None:
  953. """
  954. Takes a string like ``"abc.1.twelve"`` and turns it into
  955. ``("abc", 1, "twelve")``.
  956. """
  957. if local is not None:
  958. return tuple(
  959. part.lower() if not part.isdigit() else int(part)
  960. for part in _local_version_separators.split(local)
  961. )
  962. return None
  963. # Sort ranks for pre-release: dev-only < a < b < rc < stable (no pre-release).
  964. _PRE_RANK = {"a": 0, "b": 1, "rc": 2}
  965. _PRE_RANK_DEV_ONLY = -1 # sorts before a(0)
  966. _PRE_RANK_STABLE = 3 # sorts after rc(2)
  967. # In local version segments, strings sort before ints per PEP 440.
  968. _LOCAL_STR_RANK = -1 # sorts before all non-negative ints
  969. # Pre-computed suffix for stable releases (no pre, post, or dev segments).
  970. # See _cmpkey() for the suffix layout.
  971. _STABLE_SUFFIX = (_PRE_RANK_STABLE, 0, 0, 0, 1, 0)
  972. def _cmpkey(
  973. epoch: int,
  974. release: tuple[int, ...],
  975. pre: tuple[str, int] | None,
  976. post: tuple[str, int] | None,
  977. dev: tuple[str, int] | None,
  978. local: LocalType | None,
  979. ) -> CmpKey:
  980. """Build a comparison key for PEP 440 ordering.
  981. Returns ``(epoch, release, suffix)`` or
  982. ``(epoch, release, suffix, local)`` so that plain tuple
  983. comparison gives the correct order.
  984. Trailing zeros are stripped from the release so that ``1.0.0 == 1``.
  985. The suffix is a flat 6-int tuple that encodes pre/post/dev:
  986. ``(pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)``
  987. pre_rank: dev-only=-1, a=0, b=1, rc=2, no-pre=3
  988. Dev-only releases (no pre or post) get -1 so they sort before
  989. any alpha/beta/rc. Releases without a pre-release tag get 3
  990. so they sort after rc.
  991. post_rank: no-post=0, post=1
  992. Releases without a post segment sort before those with one.
  993. dev_rank: dev=0, no-dev=1
  994. Releases without a dev segment sort after those with one.
  995. Local segments use ``(n, "")`` for ints and ``(-1, s)`` for strings,
  996. following PEP 440: strings sort before ints, strings compare
  997. lexicographically, ints compare numerically, and shorter segments
  998. sort before longer when prefixes match. Versions without a local
  999. segment sort before those with one (3-tuple < 4-tuple).
  1000. >>> _cmpkey(0, (1, 0, 0), None, None, None, None)
  1001. (0, (1,), (3, 0, 0, 0, 1, 0))
  1002. >>> _cmpkey(0, (1,), ("a", 1), None, None, None)
  1003. (0, (1,), (0, 1, 0, 0, 1, 0))
  1004. >>> _cmpkey(0, (1,), None, None, None, ("ubuntu", 1))
  1005. (0, (1,), (3, 0, 0, 0, 1, 0), ((-1, 'ubuntu'), (1, '')))
  1006. """
  1007. # Strip trailing zeros: 1.0.0 compares equal to 1.
  1008. len_release = len(release)
  1009. i = len_release
  1010. while i and release[i - 1] == 0:
  1011. i -= 1
  1012. trimmed = release if i == len_release else release[:i]
  1013. # Fast path: stable release with no local segment.
  1014. if pre is None and post is None and dev is None and local is None:
  1015. return epoch, trimmed, _STABLE_SUFFIX
  1016. if pre is None and post is None and dev is not None:
  1017. # dev-only (e.g. 1.0.dev1) sorts before all pre-releases.
  1018. pre_rank, pre_n = _PRE_RANK_DEV_ONLY, 0
  1019. elif pre is None:
  1020. pre_rank, pre_n = _PRE_RANK_STABLE, 0
  1021. else:
  1022. pre_rank, pre_n = _PRE_RANK[pre[0]], pre[1]
  1023. post_rank = 0 if post is None else 1
  1024. post_n = 0 if post is None else post[1]
  1025. dev_rank = 1 if dev is None else 0
  1026. dev_n = 0 if dev is None else dev[1]
  1027. suffix = (pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)
  1028. if local is None:
  1029. return epoch, trimmed, suffix
  1030. cmp_local: CmpLocalType = tuple(
  1031. (seg, "") if isinstance(seg, int) else (_LOCAL_STR_RANK, seg) for seg in local
  1032. )
  1033. return epoch, trimmed, suffix, cmp_local