version.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  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 itertools
  10. import re
  11. from typing import Any, Callable, NamedTuple, SupportsInt, Tuple, Union
  12. from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
  13. __all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"]
  14. LocalType = Tuple[Union[int, str], ...]
  15. CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]
  16. CmpLocalType = Union[
  17. NegativeInfinityType,
  18. Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],
  19. ]
  20. CmpKey = Tuple[
  21. int,
  22. Tuple[int, ...],
  23. CmpPrePostDevType,
  24. CmpPrePostDevType,
  25. CmpPrePostDevType,
  26. CmpLocalType,
  27. ]
  28. VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
  29. class _Version(NamedTuple):
  30. epoch: int
  31. release: tuple[int, ...]
  32. dev: tuple[str, int] | None
  33. pre: tuple[str, int] | None
  34. post: tuple[str, int] | None
  35. local: LocalType | None
  36. def parse(version: str) -> Version:
  37. """Parse the given version string.
  38. >>> parse('1.0.dev1')
  39. <Version('1.0.dev1')>
  40. :param version: The version string to parse.
  41. :raises InvalidVersion: When the version string is not a valid version.
  42. """
  43. return Version(version)
  44. class InvalidVersion(ValueError):
  45. """Raised when a version string is not a valid version.
  46. >>> Version("invalid")
  47. Traceback (most recent call last):
  48. ...
  49. packaging.version.InvalidVersion: Invalid version: 'invalid'
  50. """
  51. class _BaseVersion:
  52. _key: tuple[Any, ...]
  53. def __hash__(self) -> int:
  54. return hash(self._key)
  55. # Please keep the duplicated `isinstance` check
  56. # in the six comparisons hereunder
  57. # unless you find a way to avoid adding overhead function calls.
  58. def __lt__(self, other: _BaseVersion) -> bool:
  59. if not isinstance(other, _BaseVersion):
  60. return NotImplemented
  61. return self._key < other._key
  62. def __le__(self, other: _BaseVersion) -> bool:
  63. if not isinstance(other, _BaseVersion):
  64. return NotImplemented
  65. return self._key <= other._key
  66. def __eq__(self, other: object) -> bool:
  67. if not isinstance(other, _BaseVersion):
  68. return NotImplemented
  69. return self._key == other._key
  70. def __ge__(self, other: _BaseVersion) -> bool:
  71. if not isinstance(other, _BaseVersion):
  72. return NotImplemented
  73. return self._key >= other._key
  74. def __gt__(self, other: _BaseVersion) -> bool:
  75. if not isinstance(other, _BaseVersion):
  76. return NotImplemented
  77. return self._key > other._key
  78. def __ne__(self, other: object) -> bool:
  79. if not isinstance(other, _BaseVersion):
  80. return NotImplemented
  81. return self._key != other._key
  82. # Deliberately not anchored to the start and end of the string, to make it
  83. # easier for 3rd party code to reuse
  84. _VERSION_PATTERN = r"""
  85. v?
  86. (?:
  87. (?:(?P<epoch>[0-9]+)!)? # epoch
  88. (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
  89. (?P<pre> # pre-release
  90. [-_\.]?
  91. (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
  92. [-_\.]?
  93. (?P<pre_n>[0-9]+)?
  94. )?
  95. (?P<post> # post release
  96. (?:-(?P<post_n1>[0-9]+))
  97. |
  98. (?:
  99. [-_\.]?
  100. (?P<post_l>post|rev|r)
  101. [-_\.]?
  102. (?P<post_n2>[0-9]+)?
  103. )
  104. )?
  105. (?P<dev> # dev release
  106. [-_\.]?
  107. (?P<dev_l>dev)
  108. [-_\.]?
  109. (?P<dev_n>[0-9]+)?
  110. )?
  111. )
  112. (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
  113. """
  114. VERSION_PATTERN = _VERSION_PATTERN
  115. """
  116. A string containing the regular expression used to match a valid version.
  117. The pattern is not anchored at either end, and is intended for embedding in larger
  118. expressions (for example, matching a version number as part of a file name). The
  119. regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
  120. flags set.
  121. :meta hide-value:
  122. """
  123. class Version(_BaseVersion):
  124. """This class abstracts handling of a project's versions.
  125. A :class:`Version` instance is comparison aware and can be compared and
  126. sorted using the standard Python interfaces.
  127. >>> v1 = Version("1.0a5")
  128. >>> v2 = Version("1.0")
  129. >>> v1
  130. <Version('1.0a5')>
  131. >>> v2
  132. <Version('1.0')>
  133. >>> v1 < v2
  134. True
  135. >>> v1 == v2
  136. False
  137. >>> v1 > v2
  138. False
  139. >>> v1 >= v2
  140. False
  141. >>> v1 <= v2
  142. True
  143. """
  144. _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
  145. _key: CmpKey
  146. def __init__(self, version: str) -> None:
  147. """Initialize a Version object.
  148. :param version:
  149. The string representation of a version which will be parsed and normalized
  150. before use.
  151. :raises InvalidVersion:
  152. If the ``version`` does not conform to PEP 440 in any way then this
  153. exception will be raised.
  154. """
  155. # Validate the version and parse it into pieces
  156. match = self._regex.search(version)
  157. if not match:
  158. raise InvalidVersion(f"Invalid version: {version!r}")
  159. # Store the parsed out pieces of the version
  160. self._version = _Version(
  161. epoch=int(match.group("epoch")) if match.group("epoch") else 0,
  162. release=tuple(int(i) for i in match.group("release").split(".")),
  163. pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
  164. post=_parse_letter_version(
  165. match.group("post_l"), match.group("post_n1") or match.group("post_n2")
  166. ),
  167. dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
  168. local=_parse_local_version(match.group("local")),
  169. )
  170. # Generate a key which will be used for sorting
  171. self._key = _cmpkey(
  172. self._version.epoch,
  173. self._version.release,
  174. self._version.pre,
  175. self._version.post,
  176. self._version.dev,
  177. self._version.local,
  178. )
  179. def __repr__(self) -> str:
  180. """A representation of the Version that shows all internal state.
  181. >>> Version('1.0.0')
  182. <Version('1.0.0')>
  183. """
  184. return f"<Version('{self}')>"
  185. def __str__(self) -> str:
  186. """A string representation of the version that can be round-tripped.
  187. >>> str(Version("1.0a5"))
  188. '1.0a5'
  189. """
  190. parts = []
  191. # Epoch
  192. if self.epoch != 0:
  193. parts.append(f"{self.epoch}!")
  194. # Release segment
  195. parts.append(".".join(str(x) for x in self.release))
  196. # Pre-release
  197. if self.pre is not None:
  198. parts.append("".join(str(x) for x in self.pre))
  199. # Post-release
  200. if self.post is not None:
  201. parts.append(f".post{self.post}")
  202. # Development release
  203. if self.dev is not None:
  204. parts.append(f".dev{self.dev}")
  205. # Local version segment
  206. if self.local is not None:
  207. parts.append(f"+{self.local}")
  208. return "".join(parts)
  209. @property
  210. def epoch(self) -> int:
  211. """The epoch of the version.
  212. >>> Version("2.0.0").epoch
  213. 0
  214. >>> Version("1!2.0.0").epoch
  215. 1
  216. """
  217. return self._version.epoch
  218. @property
  219. def release(self) -> tuple[int, ...]:
  220. """The components of the "release" segment of the version.
  221. >>> Version("1.2.3").release
  222. (1, 2, 3)
  223. >>> Version("2.0.0").release
  224. (2, 0, 0)
  225. >>> Version("1!2.0.0.post0").release
  226. (2, 0, 0)
  227. Includes trailing zeroes but not the epoch or any pre-release / development /
  228. post-release suffixes.
  229. """
  230. return self._version.release
  231. @property
  232. def pre(self) -> tuple[str, int] | None:
  233. """The pre-release segment of the version.
  234. >>> print(Version("1.2.3").pre)
  235. None
  236. >>> Version("1.2.3a1").pre
  237. ('a', 1)
  238. >>> Version("1.2.3b1").pre
  239. ('b', 1)
  240. >>> Version("1.2.3rc1").pre
  241. ('rc', 1)
  242. """
  243. return self._version.pre
  244. @property
  245. def post(self) -> int | None:
  246. """The post-release number of the version.
  247. >>> print(Version("1.2.3").post)
  248. None
  249. >>> Version("1.2.3.post1").post
  250. 1
  251. """
  252. return self._version.post[1] if self._version.post else None
  253. @property
  254. def dev(self) -> int | None:
  255. """The development number of the version.
  256. >>> print(Version("1.2.3").dev)
  257. None
  258. >>> Version("1.2.3.dev1").dev
  259. 1
  260. """
  261. return self._version.dev[1] if self._version.dev else None
  262. @property
  263. def local(self) -> str | None:
  264. """The local version segment of the version.
  265. >>> print(Version("1.2.3").local)
  266. None
  267. >>> Version("1.2.3+abc").local
  268. 'abc'
  269. """
  270. if self._version.local:
  271. return ".".join(str(x) for x in self._version.local)
  272. else:
  273. return None
  274. @property
  275. def public(self) -> str:
  276. """The public portion of the version.
  277. >>> Version("1.2.3").public
  278. '1.2.3'
  279. >>> Version("1.2.3+abc").public
  280. '1.2.3'
  281. >>> Version("1!1.2.3dev1+abc").public
  282. '1!1.2.3.dev1'
  283. """
  284. return str(self).split("+", 1)[0]
  285. @property
  286. def base_version(self) -> str:
  287. """The "base version" of the version.
  288. >>> Version("1.2.3").base_version
  289. '1.2.3'
  290. >>> Version("1.2.3+abc").base_version
  291. '1.2.3'
  292. >>> Version("1!1.2.3dev1+abc").base_version
  293. '1!1.2.3'
  294. The "base version" is the public version of the project without any pre or post
  295. release markers.
  296. """
  297. parts = []
  298. # Epoch
  299. if self.epoch != 0:
  300. parts.append(f"{self.epoch}!")
  301. # Release segment
  302. parts.append(".".join(str(x) for x in self.release))
  303. return "".join(parts)
  304. @property
  305. def is_prerelease(self) -> bool:
  306. """Whether this version is a pre-release.
  307. >>> Version("1.2.3").is_prerelease
  308. False
  309. >>> Version("1.2.3a1").is_prerelease
  310. True
  311. >>> Version("1.2.3b1").is_prerelease
  312. True
  313. >>> Version("1.2.3rc1").is_prerelease
  314. True
  315. >>> Version("1.2.3dev1").is_prerelease
  316. True
  317. """
  318. return self.dev is not None or self.pre is not None
  319. @property
  320. def is_postrelease(self) -> bool:
  321. """Whether this version is a post-release.
  322. >>> Version("1.2.3").is_postrelease
  323. False
  324. >>> Version("1.2.3.post1").is_postrelease
  325. True
  326. """
  327. return self.post is not None
  328. @property
  329. def is_devrelease(self) -> bool:
  330. """Whether this version is a development release.
  331. >>> Version("1.2.3").is_devrelease
  332. False
  333. >>> Version("1.2.3.dev1").is_devrelease
  334. True
  335. """
  336. return self.dev is not None
  337. @property
  338. def major(self) -> int:
  339. """The first item of :attr:`release` or ``0`` if unavailable.
  340. >>> Version("1.2.3").major
  341. 1
  342. """
  343. return self.release[0] if len(self.release) >= 1 else 0
  344. @property
  345. def minor(self) -> int:
  346. """The second item of :attr:`release` or ``0`` if unavailable.
  347. >>> Version("1.2.3").minor
  348. 2
  349. >>> Version("1").minor
  350. 0
  351. """
  352. return self.release[1] if len(self.release) >= 2 else 0
  353. @property
  354. def micro(self) -> int:
  355. """The third item of :attr:`release` or ``0`` if unavailable.
  356. >>> Version("1.2.3").micro
  357. 3
  358. >>> Version("1").micro
  359. 0
  360. """
  361. return self.release[2] if len(self.release) >= 3 else 0
  362. class _TrimmedRelease(Version):
  363. @property
  364. def release(self) -> tuple[int, ...]:
  365. """
  366. Release segment without any trailing zeros.
  367. >>> _TrimmedRelease('1.0.0').release
  368. (1,)
  369. >>> _TrimmedRelease('0.0').release
  370. (0,)
  371. """
  372. rel = super().release
  373. nonzeros = (index for index, val in enumerate(rel) if val)
  374. last_nonzero = max(nonzeros, default=0)
  375. return rel[: last_nonzero + 1]
  376. def _parse_letter_version(
  377. letter: str | None, number: str | bytes | SupportsInt | None
  378. ) -> tuple[str, int] | None:
  379. if letter:
  380. # We consider there to be an implicit 0 in a pre-release if there is
  381. # not a numeral associated with it.
  382. if number is None:
  383. number = 0
  384. # We normalize any letters to their lower case form
  385. letter = letter.lower()
  386. # We consider some words to be alternate spellings of other words and
  387. # in those cases we want to normalize the spellings to our preferred
  388. # spelling.
  389. if letter == "alpha":
  390. letter = "a"
  391. elif letter == "beta":
  392. letter = "b"
  393. elif letter in ["c", "pre", "preview"]:
  394. letter = "rc"
  395. elif letter in ["rev", "r"]:
  396. letter = "post"
  397. return letter, int(number)
  398. assert not letter
  399. if number:
  400. # We assume if we are given a number, but we are not given a letter
  401. # then this is using the implicit post release syntax (e.g. 1.0-1)
  402. letter = "post"
  403. return letter, int(number)
  404. return None
  405. _local_version_separators = re.compile(r"[\._-]")
  406. def _parse_local_version(local: str | None) -> LocalType | None:
  407. """
  408. Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
  409. """
  410. if local is not None:
  411. return tuple(
  412. part.lower() if not part.isdigit() else int(part)
  413. for part in _local_version_separators.split(local)
  414. )
  415. return None
  416. def _cmpkey(
  417. epoch: int,
  418. release: tuple[int, ...],
  419. pre: tuple[str, int] | None,
  420. post: tuple[str, int] | None,
  421. dev: tuple[str, int] | None,
  422. local: LocalType | None,
  423. ) -> CmpKey:
  424. # When we compare a release version, we want to compare it with all of the
  425. # trailing zeros removed. So we'll use a reverse the list, drop all the now
  426. # leading zeros until we come to something non zero, then take the rest
  427. # re-reverse it back into the correct order and make it a tuple and use
  428. # that for our sorting key.
  429. _release = tuple(
  430. reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
  431. )
  432. # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
  433. # We'll do this by abusing the pre segment, but we _only_ want to do this
  434. # if there is not a pre or a post segment. If we have one of those then
  435. # the normal sorting rules will handle this case correctly.
  436. if pre is None and post is None and dev is not None:
  437. _pre: CmpPrePostDevType = NegativeInfinity
  438. # Versions without a pre-release (except as noted above) should sort after
  439. # those with one.
  440. elif pre is None:
  441. _pre = Infinity
  442. else:
  443. _pre = pre
  444. # Versions without a post segment should sort before those with one.
  445. if post is None:
  446. _post: CmpPrePostDevType = NegativeInfinity
  447. else:
  448. _post = post
  449. # Versions without a development segment should sort after those with one.
  450. if dev is None:
  451. _dev: CmpPrePostDevType = Infinity
  452. else:
  453. _dev = dev
  454. if local is None:
  455. # Versions without a local segment should sort before those with one.
  456. _local: CmpLocalType = NegativeInfinity
  457. else:
  458. # Versions with a local segment need that segment parsed to implement
  459. # the sorting rules in PEP440.
  460. # - Alpha numeric segments sort before numeric segments
  461. # - Alpha numeric segments sort lexicographically
  462. # - Numeric segments sort numerically
  463. # - Shorter versions sort before longer versions when the prefixes
  464. # match exactly
  465. _local = tuple(
  466. (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
  467. )
  468. return epoch, _release, _pre, _post, _dev, _local