version.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  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, Version
  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. Tuple,
  19. TypedDict,
  20. Union,
  21. )
  22. from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
  23. if typing.TYPE_CHECKING:
  24. from typing_extensions import Self, Unpack
  25. if sys.version_info >= (3, 13): # pragma: no cover
  26. from warnings import deprecated as _deprecated
  27. elif typing.TYPE_CHECKING:
  28. from typing_extensions import deprecated as _deprecated
  29. else: # pragma: no cover
  30. import functools
  31. import warnings
  32. def _deprecated(message: str) -> object:
  33. def decorator(func: object) -> object:
  34. @functools.wraps(func)
  35. def wrapper(*args: object, **kwargs: object) -> object:
  36. warnings.warn(
  37. message,
  38. category=DeprecationWarning,
  39. stacklevel=2,
  40. )
  41. return func(*args, **kwargs)
  42. return wrapper
  43. return decorator
  44. _LETTER_NORMALIZATION = {
  45. "alpha": "a",
  46. "beta": "b",
  47. "c": "rc",
  48. "pre": "rc",
  49. "preview": "rc",
  50. "rev": "post",
  51. "r": "post",
  52. }
  53. __all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"]
  54. LocalType = Tuple[Union[int, str], ...]
  55. CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]
  56. CmpLocalType = Union[
  57. NegativeInfinityType,
  58. Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],
  59. ]
  60. CmpKey = Tuple[
  61. int,
  62. Tuple[int, ...],
  63. CmpPrePostDevType,
  64. CmpPrePostDevType,
  65. CmpPrePostDevType,
  66. CmpLocalType,
  67. ]
  68. VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
  69. class _VersionReplace(TypedDict, total=False):
  70. epoch: int | None
  71. release: tuple[int, ...] | None
  72. pre: tuple[Literal["a", "b", "rc"], int] | None
  73. post: int | None
  74. dev: int | None
  75. local: str | None
  76. def parse(version: str) -> Version:
  77. """Parse the given version string.
  78. >>> parse('1.0.dev1')
  79. <Version('1.0.dev1')>
  80. :param version: The version string to parse.
  81. :raises InvalidVersion: When the version string is not a valid version.
  82. """
  83. return Version(version)
  84. class InvalidVersion(ValueError):
  85. """Raised when a version string is not a valid version.
  86. >>> Version("invalid")
  87. Traceback (most recent call last):
  88. ...
  89. packaging.version.InvalidVersion: Invalid version: 'invalid'
  90. """
  91. class _BaseVersion:
  92. __slots__ = ()
  93. # This can also be a normal member (see the packaging_legacy package);
  94. # we are just requiring it to be readable. Actually defining a property
  95. # has runtime effect on subclasses, so it's typing only.
  96. if typing.TYPE_CHECKING:
  97. @property
  98. def _key(self) -> tuple[Any, ...]: ...
  99. def __hash__(self) -> int:
  100. return hash(self._key)
  101. # Please keep the duplicated `isinstance` check
  102. # in the six comparisons hereunder
  103. # unless you find a way to avoid adding overhead function calls.
  104. def __lt__(self, other: _BaseVersion) -> bool:
  105. if not isinstance(other, _BaseVersion):
  106. return NotImplemented
  107. return self._key < other._key
  108. def __le__(self, other: _BaseVersion) -> bool:
  109. if not isinstance(other, _BaseVersion):
  110. return NotImplemented
  111. return self._key <= other._key
  112. def __eq__(self, other: object) -> bool:
  113. if not isinstance(other, _BaseVersion):
  114. return NotImplemented
  115. return self._key == other._key
  116. def __ge__(self, other: _BaseVersion) -> bool:
  117. if not isinstance(other, _BaseVersion):
  118. return NotImplemented
  119. return self._key >= other._key
  120. def __gt__(self, other: _BaseVersion) -> bool:
  121. if not isinstance(other, _BaseVersion):
  122. return NotImplemented
  123. return self._key > other._key
  124. def __ne__(self, other: object) -> bool:
  125. if not isinstance(other, _BaseVersion):
  126. return NotImplemented
  127. return self._key != other._key
  128. # Deliberately not anchored to the start and end of the string, to make it
  129. # easier for 3rd party code to reuse
  130. # Note that ++ doesn't behave identically on CPython and PyPy, so not using it here
  131. _VERSION_PATTERN = r"""
  132. v?+ # optional leading v
  133. (?:
  134. (?:(?P<epoch>[0-9]+)!)?+ # epoch
  135. (?P<release>[0-9]+(?:\.[0-9]+)*+) # release segment
  136. (?P<pre> # pre-release
  137. [._-]?+
  138. (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
  139. [._-]?+
  140. (?P<pre_n>[0-9]+)?
  141. )?+
  142. (?P<post> # post release
  143. (?:-(?P<post_n1>[0-9]+))
  144. |
  145. (?:
  146. [._-]?
  147. (?P<post_l>post|rev|r)
  148. [._-]?
  149. (?P<post_n2>[0-9]+)?
  150. )
  151. )?+
  152. (?P<dev> # dev release
  153. [._-]?+
  154. (?P<dev_l>dev)
  155. [._-]?+
  156. (?P<dev_n>[0-9]+)?
  157. )?+
  158. )
  159. (?:\+
  160. (?P<local> # local version
  161. [a-z0-9]+
  162. (?:[._-][a-z0-9]+)*+
  163. )
  164. )?+
  165. """
  166. _VERSION_PATTERN_OLD = _VERSION_PATTERN.replace("*+", "*").replace("?+", "?")
  167. # Possessive qualifiers were added in Python 3.11.
  168. # CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795
  169. # Older PyPy also had a bug.
  170. VERSION_PATTERN = (
  171. _VERSION_PATTERN_OLD
  172. if (sys.implementation.name == "cpython" and sys.version_info < (3, 11, 5))
  173. or (sys.implementation.name == "pypy" and sys.version_info < (3, 11, 13))
  174. or sys.version_info < (3, 11)
  175. else _VERSION_PATTERN
  176. )
  177. """
  178. A string containing the regular expression used to match a valid version.
  179. The pattern is not anchored at either end, and is intended for embedding in larger
  180. expressions (for example, matching a version number as part of a file name). The
  181. regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
  182. flags set.
  183. :meta hide-value:
  184. """
  185. # Validation pattern for local version in replace()
  186. _LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE)
  187. def _validate_epoch(value: object, /) -> int:
  188. epoch = value or 0
  189. if isinstance(epoch, int) and epoch >= 0:
  190. return epoch
  191. msg = f"epoch must be non-negative integer, got {epoch}"
  192. raise InvalidVersion(msg)
  193. def _validate_release(value: object, /) -> tuple[int, ...]:
  194. release = (0,) if value is None else value
  195. if (
  196. isinstance(release, tuple)
  197. and len(release) > 0
  198. and all(isinstance(i, int) and i >= 0 for i in release)
  199. ):
  200. return release
  201. msg = f"release must be a non-empty tuple of non-negative integers, got {release}"
  202. raise InvalidVersion(msg)
  203. def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None:
  204. if value is None:
  205. return value
  206. if (
  207. isinstance(value, tuple)
  208. and len(value) == 2
  209. and value[0] in ("a", "b", "rc")
  210. and isinstance(value[1], int)
  211. and value[1] >= 0
  212. ):
  213. return value
  214. msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}"
  215. raise InvalidVersion(msg)
  216. def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:
  217. if value is None:
  218. return value
  219. if isinstance(value, int) and value >= 0:
  220. return ("post", value)
  221. msg = f"post must be non-negative integer, got {value}"
  222. raise InvalidVersion(msg)
  223. def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:
  224. if value is None:
  225. return value
  226. if isinstance(value, int) and value >= 0:
  227. return ("dev", value)
  228. msg = f"dev must be non-negative integer, got {value}"
  229. raise InvalidVersion(msg)
  230. def _validate_local(value: object, /) -> LocalType | None:
  231. if value is None:
  232. return value
  233. if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):
  234. return _parse_local_version(value)
  235. msg = f"local must be a valid version string, got {value!r}"
  236. raise InvalidVersion(msg)
  237. # Backward compatibility for internals before 26.0. Do not use.
  238. class _Version(NamedTuple):
  239. epoch: int
  240. release: tuple[int, ...]
  241. dev: tuple[str, int] | None
  242. pre: tuple[str, int] | None
  243. post: tuple[str, int] | None
  244. local: LocalType | None
  245. class Version(_BaseVersion):
  246. """This class abstracts handling of a project's versions.
  247. A :class:`Version` instance is comparison aware and can be compared and
  248. sorted using the standard Python interfaces.
  249. >>> v1 = Version("1.0a5")
  250. >>> v2 = Version("1.0")
  251. >>> v1
  252. <Version('1.0a5')>
  253. >>> v2
  254. <Version('1.0')>
  255. >>> v1 < v2
  256. True
  257. >>> v1 == v2
  258. False
  259. >>> v1 > v2
  260. False
  261. >>> v1 >= v2
  262. False
  263. >>> v1 <= v2
  264. True
  265. """
  266. __slots__ = ("_dev", "_epoch", "_key_cache", "_local", "_post", "_pre", "_release")
  267. __match_args__ = ("_str",)
  268. _regex = re.compile(r"\s*" + VERSION_PATTERN + r"\s*", re.VERBOSE | re.IGNORECASE)
  269. _epoch: int
  270. _release: tuple[int, ...]
  271. _dev: tuple[str, int] | None
  272. _pre: tuple[str, int] | None
  273. _post: tuple[str, int] | None
  274. _local: LocalType | None
  275. _key_cache: CmpKey | None
  276. def __init__(self, version: str) -> None:
  277. """Initialize a Version object.
  278. :param version:
  279. The string representation of a version which will be parsed and normalized
  280. before use.
  281. :raises InvalidVersion:
  282. If the ``version`` does not conform to PEP 440 in any way then this
  283. exception will be raised.
  284. """
  285. # Validate the version and parse it into pieces
  286. match = self._regex.fullmatch(version)
  287. if not match:
  288. raise InvalidVersion(f"Invalid version: {version!r}")
  289. self._epoch = int(match.group("epoch")) if match.group("epoch") else 0
  290. self._release = tuple(map(int, match.group("release").split(".")))
  291. self._pre = _parse_letter_version(match.group("pre_l"), match.group("pre_n"))
  292. self._post = _parse_letter_version(
  293. match.group("post_l"), match.group("post_n1") or match.group("post_n2")
  294. )
  295. self._dev = _parse_letter_version(match.group("dev_l"), match.group("dev_n"))
  296. self._local = _parse_local_version(match.group("local"))
  297. # Key which will be used for sorting
  298. self._key_cache = None
  299. def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:
  300. epoch = _validate_epoch(kwargs["epoch"]) if "epoch" in kwargs else self._epoch
  301. release = (
  302. _validate_release(kwargs["release"])
  303. if "release" in kwargs
  304. else self._release
  305. )
  306. pre = _validate_pre(kwargs["pre"]) if "pre" in kwargs else self._pre
  307. post = _validate_post(kwargs["post"]) if "post" in kwargs else self._post
  308. dev = _validate_dev(kwargs["dev"]) if "dev" in kwargs else self._dev
  309. local = _validate_local(kwargs["local"]) if "local" in kwargs else self._local
  310. if (
  311. epoch == self._epoch
  312. and release == self._release
  313. and pre == self._pre
  314. and post == self._post
  315. and dev == self._dev
  316. and local == self._local
  317. ):
  318. return self
  319. new_version = self.__class__.__new__(self.__class__)
  320. new_version._key_cache = None
  321. new_version._epoch = epoch
  322. new_version._release = release
  323. new_version._pre = pre
  324. new_version._post = post
  325. new_version._dev = dev
  326. new_version._local = local
  327. return new_version
  328. @property
  329. def _key(self) -> CmpKey:
  330. if self._key_cache is None:
  331. self._key_cache = _cmpkey(
  332. self._epoch,
  333. self._release,
  334. self._pre,
  335. self._post,
  336. self._dev,
  337. self._local,
  338. )
  339. return self._key_cache
  340. @property
  341. @_deprecated("Version._version is private and will be removed soon")
  342. def _version(self) -> _Version:
  343. return _Version(
  344. self._epoch, self._release, self._dev, self._pre, self._post, self._local
  345. )
  346. @_version.setter
  347. @_deprecated("Version._version is private and will be removed soon")
  348. def _version(self, value: _Version) -> None:
  349. self._epoch = value.epoch
  350. self._release = value.release
  351. self._dev = value.dev
  352. self._pre = value.pre
  353. self._post = value.post
  354. self._local = value.local
  355. self._key_cache = None
  356. def __repr__(self) -> str:
  357. """A representation of the Version that shows all internal state.
  358. >>> Version('1.0.0')
  359. <Version('1.0.0')>
  360. """
  361. return f"<Version('{self}')>"
  362. def __str__(self) -> str:
  363. """A string representation of the version that can be round-tripped.
  364. >>> str(Version("1.0a5"))
  365. '1.0a5'
  366. """
  367. # This is a hot function, so not calling self.base_version
  368. version = ".".join(map(str, self.release))
  369. # Epoch
  370. if self.epoch:
  371. version = f"{self.epoch}!{version}"
  372. # Pre-release
  373. if self.pre is not None:
  374. version += "".join(map(str, self.pre))
  375. # Post-release
  376. if self.post is not None:
  377. version += f".post{self.post}"
  378. # Development release
  379. if self.dev is not None:
  380. version += f".dev{self.dev}"
  381. # Local version segment
  382. if self.local is not None:
  383. version += f"+{self.local}"
  384. return version
  385. @property
  386. def _str(self) -> str:
  387. """Internal property for match_args"""
  388. return str(self)
  389. @property
  390. def epoch(self) -> int:
  391. """The epoch of the version.
  392. >>> Version("2.0.0").epoch
  393. 0
  394. >>> Version("1!2.0.0").epoch
  395. 1
  396. """
  397. return self._epoch
  398. @property
  399. def release(self) -> tuple[int, ...]:
  400. """The components of the "release" segment of the version.
  401. >>> Version("1.2.3").release
  402. (1, 2, 3)
  403. >>> Version("2.0.0").release
  404. (2, 0, 0)
  405. >>> Version("1!2.0.0.post0").release
  406. (2, 0, 0)
  407. Includes trailing zeroes but not the epoch or any pre-release / development /
  408. post-release suffixes.
  409. """
  410. return self._release
  411. @property
  412. def pre(self) -> tuple[str, int] | None:
  413. """The pre-release segment of the version.
  414. >>> print(Version("1.2.3").pre)
  415. None
  416. >>> Version("1.2.3a1").pre
  417. ('a', 1)
  418. >>> Version("1.2.3b1").pre
  419. ('b', 1)
  420. >>> Version("1.2.3rc1").pre
  421. ('rc', 1)
  422. """
  423. return self._pre
  424. @property
  425. def post(self) -> int | None:
  426. """The post-release number of the version.
  427. >>> print(Version("1.2.3").post)
  428. None
  429. >>> Version("1.2.3.post1").post
  430. 1
  431. """
  432. return self._post[1] if self._post else None
  433. @property
  434. def dev(self) -> int | None:
  435. """The development number of the version.
  436. >>> print(Version("1.2.3").dev)
  437. None
  438. >>> Version("1.2.3.dev1").dev
  439. 1
  440. """
  441. return self._dev[1] if self._dev else None
  442. @property
  443. def local(self) -> str | None:
  444. """The local version segment of the version.
  445. >>> print(Version("1.2.3").local)
  446. None
  447. >>> Version("1.2.3+abc").local
  448. 'abc'
  449. """
  450. if self._local:
  451. return ".".join(str(x) for x in self._local)
  452. else:
  453. return None
  454. @property
  455. def public(self) -> str:
  456. """The public portion of the version.
  457. >>> Version("1.2.3").public
  458. '1.2.3'
  459. >>> Version("1.2.3+abc").public
  460. '1.2.3'
  461. >>> Version("1!1.2.3dev1+abc").public
  462. '1!1.2.3.dev1'
  463. """
  464. return str(self).split("+", 1)[0]
  465. @property
  466. def base_version(self) -> str:
  467. """The "base version" of the version.
  468. >>> Version("1.2.3").base_version
  469. '1.2.3'
  470. >>> Version("1.2.3+abc").base_version
  471. '1.2.3'
  472. >>> Version("1!1.2.3dev1+abc").base_version
  473. '1!1.2.3'
  474. The "base version" is the public version of the project without any pre or post
  475. release markers.
  476. """
  477. release_segment = ".".join(map(str, self.release))
  478. return f"{self.epoch}!{release_segment}" if self.epoch else release_segment
  479. @property
  480. def is_prerelease(self) -> bool:
  481. """Whether this version is a pre-release.
  482. >>> Version("1.2.3").is_prerelease
  483. False
  484. >>> Version("1.2.3a1").is_prerelease
  485. True
  486. >>> Version("1.2.3b1").is_prerelease
  487. True
  488. >>> Version("1.2.3rc1").is_prerelease
  489. True
  490. >>> Version("1.2.3dev1").is_prerelease
  491. True
  492. """
  493. return self.dev is not None or self.pre is not None
  494. @property
  495. def is_postrelease(self) -> bool:
  496. """Whether this version is a post-release.
  497. >>> Version("1.2.3").is_postrelease
  498. False
  499. >>> Version("1.2.3.post1").is_postrelease
  500. True
  501. """
  502. return self.post is not None
  503. @property
  504. def is_devrelease(self) -> bool:
  505. """Whether this version is a development release.
  506. >>> Version("1.2.3").is_devrelease
  507. False
  508. >>> Version("1.2.3.dev1").is_devrelease
  509. True
  510. """
  511. return self.dev is not None
  512. @property
  513. def major(self) -> int:
  514. """The first item of :attr:`release` or ``0`` if unavailable.
  515. >>> Version("1.2.3").major
  516. 1
  517. """
  518. return self.release[0] if len(self.release) >= 1 else 0
  519. @property
  520. def minor(self) -> int:
  521. """The second item of :attr:`release` or ``0`` if unavailable.
  522. >>> Version("1.2.3").minor
  523. 2
  524. >>> Version("1").minor
  525. 0
  526. """
  527. return self.release[1] if len(self.release) >= 2 else 0
  528. @property
  529. def micro(self) -> int:
  530. """The third item of :attr:`release` or ``0`` if unavailable.
  531. >>> Version("1.2.3").micro
  532. 3
  533. >>> Version("1").micro
  534. 0
  535. """
  536. return self.release[2] if len(self.release) >= 3 else 0
  537. class _TrimmedRelease(Version):
  538. __slots__ = ()
  539. def __init__(self, version: str | Version) -> None:
  540. if isinstance(version, Version):
  541. self._epoch = version._epoch
  542. self._release = version._release
  543. self._dev = version._dev
  544. self._pre = version._pre
  545. self._post = version._post
  546. self._local = version._local
  547. self._key_cache = version._key_cache
  548. return
  549. super().__init__(version) # pragma: no cover
  550. @property
  551. def release(self) -> tuple[int, ...]:
  552. """
  553. Release segment without any trailing zeros.
  554. >>> _TrimmedRelease('1.0.0').release
  555. (1,)
  556. >>> _TrimmedRelease('0.0').release
  557. (0,)
  558. """
  559. # This leaves one 0.
  560. rel = super().release
  561. len_release = len(rel)
  562. i = len_release
  563. while i > 1 and rel[i - 1] == 0:
  564. i -= 1
  565. return rel if i == len_release else rel[:i]
  566. def _parse_letter_version(
  567. letter: str | None, number: str | bytes | SupportsInt | None
  568. ) -> tuple[str, int] | None:
  569. if letter:
  570. # We normalize any letters to their lower case form
  571. letter = letter.lower()
  572. # We consider some words to be alternate spellings of other words and
  573. # in those cases we want to normalize the spellings to our preferred
  574. # spelling.
  575. letter = _LETTER_NORMALIZATION.get(letter, letter)
  576. # We consider there to be an implicit 0 in a pre-release if there is
  577. # not a numeral associated with it.
  578. return letter, int(number or 0)
  579. if number:
  580. # We assume if we are given a number, but we are not given a letter
  581. # then this is using the implicit post release syntax (e.g. 1.0-1)
  582. return "post", int(number)
  583. return None
  584. _local_version_separators = re.compile(r"[\._-]")
  585. def _parse_local_version(local: str | None) -> LocalType | None:
  586. """
  587. Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
  588. """
  589. if local is not None:
  590. return tuple(
  591. part.lower() if not part.isdigit() else int(part)
  592. for part in _local_version_separators.split(local)
  593. )
  594. return None
  595. def _cmpkey(
  596. epoch: int,
  597. release: tuple[int, ...],
  598. pre: tuple[str, int] | None,
  599. post: tuple[str, int] | None,
  600. dev: tuple[str, int] | None,
  601. local: LocalType | None,
  602. ) -> CmpKey:
  603. # When we compare a release version, we want to compare it with all of the
  604. # trailing zeros removed. We will use this for our sorting key.
  605. len_release = len(release)
  606. i = len_release
  607. while i and release[i - 1] == 0:
  608. i -= 1
  609. _release = release if i == len_release else release[:i]
  610. # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
  611. # We'll do this by abusing the pre segment, but we _only_ want to do this
  612. # if there is not a pre or a post segment. If we have one of those then
  613. # the normal sorting rules will handle this case correctly.
  614. if pre is None and post is None and dev is not None:
  615. _pre: CmpPrePostDevType = NegativeInfinity
  616. # Versions without a pre-release (except as noted above) should sort after
  617. # those with one.
  618. elif pre is None:
  619. _pre = Infinity
  620. else:
  621. _pre = pre
  622. # Versions without a post segment should sort before those with one.
  623. if post is None:
  624. _post: CmpPrePostDevType = NegativeInfinity
  625. else:
  626. _post = post
  627. # Versions without a development segment should sort after those with one.
  628. if dev is None:
  629. _dev: CmpPrePostDevType = Infinity
  630. else:
  631. _dev = dev
  632. if local is None:
  633. # Versions without a local segment should sort before those with one.
  634. _local: CmpLocalType = NegativeInfinity
  635. else:
  636. # Versions with a local segment need that segment parsed to implement
  637. # the sorting rules in PEP440.
  638. # - Alpha numeric segments sort before numeric segments
  639. # - Alpha numeric segments sort lexicographically
  640. # - Numeric segments sort numerically
  641. # - Shorter versions sort before longer versions when the prefixes
  642. # match exactly
  643. _local = tuple(
  644. (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
  645. )
  646. return epoch, _release, _pre, _post, _dev, _local