diff options
Diffstat (limited to 'third_party/python/setuptools/pkg_resources/_vendor/packaging/version.py')
-rw-r--r-- | third_party/python/setuptools/pkg_resources/_vendor/packaging/version.py | 541 |
1 files changed, 285 insertions, 256 deletions
diff --git a/third_party/python/setuptools/pkg_resources/_vendor/packaging/version.py b/third_party/python/setuptools/pkg_resources/_vendor/packaging/version.py index 00371e86a8..b30e8cbf84 100644 --- a/third_party/python/setuptools/pkg_resources/_vendor/packaging/version.py +++ b/third_party/python/setuptools/pkg_resources/_vendor/packaging/version.py @@ -1,238 +1,116 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function +""" +.. testsetup:: + + from packaging.version import parse, Version +""" import collections import itertools import re - -from ._structures import Infinity, NegativeInfinity -from ._typing import TYPE_CHECKING - -if TYPE_CHECKING: # pragma: no cover - from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union - - from ._structures import InfinityType, NegativeInfinityType - - InfiniteTypes = Union[InfinityType, NegativeInfinityType] - PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] - SubLocalType = Union[InfiniteTypes, int, str] - LocalType = Union[ - NegativeInfinityType, - Tuple[ - Union[ - SubLocalType, - Tuple[SubLocalType, str], - Tuple[NegativeInfinityType, SubLocalType], - ], - ..., +from typing import Any, Callable, Optional, SupportsInt, Tuple, Union + +from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType + +__all__ = ["VERSION_PATTERN", "parse", "Version", "InvalidVersion"] + +InfiniteTypes = Union[InfinityType, NegativeInfinityType] +PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] +SubLocalType = Union[InfiniteTypes, int, str] +LocalType = Union[ + NegativeInfinityType, + Tuple[ + Union[ + SubLocalType, + Tuple[SubLocalType, str], + Tuple[NegativeInfinityType, SubLocalType], ], - ] - CmpKey = Tuple[ - int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType - ] - LegacyCmpKey = Tuple[int, Tuple[str, ...]] - VersionComparisonMethod = Callable[ - [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool - ] - -__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] - + ..., + ], +] +CmpKey = Tuple[ + int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType +] +VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool] _Version = collections.namedtuple( "_Version", ["epoch", "release", "dev", "pre", "post", "local"] ) -def parse(version): - # type: (str) -> Union[LegacyVersion, Version] - """ - Parse the given version string and return either a :class:`Version` object - or a :class:`LegacyVersion` object depending on if the given version is - a valid PEP 440 version or a legacy version. - """ - try: - return Version(version) - except InvalidVersion: - return LegacyVersion(version) +def parse(version: str) -> "Version": + """Parse the given version string. + >>> parse('1.0.dev1') + <Version('1.0.dev1')> -class InvalidVersion(ValueError): + :param version: The version string to parse. + :raises InvalidVersion: When the version string is not a valid version. """ - An invalid version was found, users should refer to PEP 440. - """ - - -class _BaseVersion(object): - _key = None # type: Union[CmpKey, LegacyCmpKey] - - def __hash__(self): - # type: () -> int - return hash(self._key) + return Version(version) - def __lt__(self, other): - # type: (_BaseVersion) -> bool - return self._compare(other, lambda s, o: s < o) - def __le__(self, other): - # type: (_BaseVersion) -> bool - return self._compare(other, lambda s, o: s <= o) +class InvalidVersion(ValueError): + """Raised when a version string is not a valid version. - def __eq__(self, other): - # type: (object) -> bool - return self._compare(other, lambda s, o: s == o) + >>> Version("invalid") + Traceback (most recent call last): + ... + packaging.version.InvalidVersion: Invalid version: 'invalid' + """ - def __ge__(self, other): - # type: (_BaseVersion) -> bool - return self._compare(other, lambda s, o: s >= o) - def __gt__(self, other): - # type: (_BaseVersion) -> bool - return self._compare(other, lambda s, o: s > o) +class _BaseVersion: + _key: Tuple[Any, ...] - def __ne__(self, other): - # type: (object) -> bool - return self._compare(other, lambda s, o: s != o) + def __hash__(self) -> int: + return hash(self._key) - def _compare(self, other, method): - # type: (object, VersionComparisonMethod) -> Union[bool, NotImplemented] + # Please keep the duplicated `isinstance` check + # in the six comparisons hereunder + # unless you find a way to avoid adding overhead function calls. + def __lt__(self, other: "_BaseVersion") -> bool: if not isinstance(other, _BaseVersion): return NotImplemented - return method(self._key, other._key) - - -class LegacyVersion(_BaseVersion): - def __init__(self, version): - # type: (str) -> None - self._version = str(version) - self._key = _legacy_cmpkey(self._version) - - def __str__(self): - # type: () -> str - return self._version - - def __repr__(self): - # type: () -> str - return "<LegacyVersion({0})>".format(repr(str(self))) - - @property - def public(self): - # type: () -> str - return self._version - - @property - def base_version(self): - # type: () -> str - return self._version - - @property - def epoch(self): - # type: () -> int - return -1 - - @property - def release(self): - # type: () -> None - return None - - @property - def pre(self): - # type: () -> None - return None - - @property - def post(self): - # type: () -> None - return None - - @property - def dev(self): - # type: () -> None - return None + return self._key < other._key - @property - def local(self): - # type: () -> None - return None - - @property - def is_prerelease(self): - # type: () -> bool - return False - - @property - def is_postrelease(self): - # type: () -> bool - return False - - @property - def is_devrelease(self): - # type: () -> bool - return False - - -_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) - -_legacy_version_replacement_map = { - "pre": "c", - "preview": "c", - "-": "final-", - "rc": "c", - "dev": "@", -} - - -def _parse_version_parts(s): - # type: (str) -> Iterator[str] - for part in _legacy_version_component_re.split(s): - part = _legacy_version_replacement_map.get(part, part) - - if not part or part == ".": - continue + def __le__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented - if part[:1] in "0123456789": - # pad for numeric comparison - yield part.zfill(8) - else: - yield "*" + part + return self._key <= other._key - # ensure that alpha/beta/candidate are before final - yield "*final" + def __eq__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + return self._key == other._key -def _legacy_cmpkey(version): - # type: (str) -> LegacyCmpKey + def __ge__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented - # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch - # greater than or equal to 0. This will effectively put the LegacyVersion, - # which uses the defacto standard originally implemented by setuptools, - # as before all PEP 440 versions. - epoch = -1 + return self._key >= other._key - # This scheme is taken from pkg_resources.parse_version setuptools prior to - # it's adoption of the packaging library. - parts = [] # type: List[str] - for part in _parse_version_parts(version.lower()): - if part.startswith("*"): - # remove "-" before a prerelease tag - if part < "*final": - while parts and parts[-1] == "*final-": - parts.pop() + def __gt__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented - # remove trailing zeros from each series of numeric parts - while parts and parts[-1] == "00000000": - parts.pop() + return self._key > other._key - parts.append(part) + def __ne__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented - return epoch, tuple(parts) + return self._key != other._key # Deliberately not anchored to the start and end of the string, to make it # easier for 3rd party code to reuse -VERSION_PATTERN = r""" +_VERSION_PATTERN = r""" v? (?: (?:(?P<epoch>[0-9]+)!)? # epoch @@ -263,18 +141,61 @@ VERSION_PATTERN = r""" (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version """ +VERSION_PATTERN = _VERSION_PATTERN +""" +A string containing the regular expression used to match a valid version. + +The pattern is not anchored at either end, and is intended for embedding in larger +expressions (for example, matching a version number as part of a file name). The +regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE`` +flags set. + +:meta hide-value: +""" + class Version(_BaseVersion): + """This class abstracts handling of a project's versions. + + A :class:`Version` instance is comparison aware and can be compared and + sorted using the standard Python interfaces. + + >>> v1 = Version("1.0a5") + >>> v2 = Version("1.0") + >>> v1 + <Version('1.0a5')> + >>> v2 + <Version('1.0')> + >>> v1 < v2 + True + >>> v1 == v2 + False + >>> v1 > v2 + False + >>> v1 >= v2 + False + >>> v1 <= v2 + True + """ _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) + _key: CmpKey - def __init__(self, version): - # type: (str) -> None + def __init__(self, version: str) -> None: + """Initialize a Version object. + + :param version: + The string representation of a version which will be parsed and normalized + before use. + :raises InvalidVersion: + If the ``version`` does not conform to PEP 440 in any way then this + exception will be raised. + """ # Validate the version and parse it into pieces match = self._regex.search(version) if not match: - raise InvalidVersion("Invalid version: '{0}'".format(version)) + raise InvalidVersion(f"Invalid version: '{version}'") # Store the parsed out pieces of the version self._version = _Version( @@ -298,17 +219,25 @@ class Version(_BaseVersion): self._version.local, ) - def __repr__(self): - # type: () -> str - return "<Version({0})>".format(repr(str(self))) + def __repr__(self) -> str: + """A representation of the Version that shows all internal state. + + >>> Version('1.0.0') + <Version('1.0.0')> + """ + return f"<Version('{self}')>" - def __str__(self): - # type: () -> str + def __str__(self) -> str: + """A string representation of the version that can be rounded-tripped. + + >>> str(Version("1.0a5")) + '1.0a5' + """ parts = [] # Epoch if self.epoch != 0: - parts.append("{0}!".format(self.epoch)) + parts.append(f"{self.epoch}!") # Release segment parts.append(".".join(str(x) for x in self.release)) @@ -319,67 +248,131 @@ class Version(_BaseVersion): # Post-release if self.post is not None: - parts.append(".post{0}".format(self.post)) + parts.append(f".post{self.post}") # Development release if self.dev is not None: - parts.append(".dev{0}".format(self.dev)) + parts.append(f".dev{self.dev}") # Local version segment if self.local is not None: - parts.append("+{0}".format(self.local)) + parts.append(f"+{self.local}") return "".join(parts) @property - def epoch(self): - # type: () -> int - _epoch = self._version.epoch # type: int + def epoch(self) -> int: + """The epoch of the version. + + >>> Version("2.0.0").epoch + 0 + >>> Version("1!2.0.0").epoch + 1 + """ + _epoch: int = self._version.epoch return _epoch @property - def release(self): - # type: () -> Tuple[int, ...] - _release = self._version.release # type: Tuple[int, ...] + def release(self) -> Tuple[int, ...]: + """The components of the "release" segment of the version. + + >>> Version("1.2.3").release + (1, 2, 3) + >>> Version("2.0.0").release + (2, 0, 0) + >>> Version("1!2.0.0.post0").release + (2, 0, 0) + + Includes trailing zeroes but not the epoch or any pre-release / development / + post-release suffixes. + """ + _release: Tuple[int, ...] = self._version.release return _release @property - def pre(self): - # type: () -> Optional[Tuple[str, int]] - _pre = self._version.pre # type: Optional[Tuple[str, int]] + def pre(self) -> Optional[Tuple[str, int]]: + """The pre-release segment of the version. + + >>> print(Version("1.2.3").pre) + None + >>> Version("1.2.3a1").pre + ('a', 1) + >>> Version("1.2.3b1").pre + ('b', 1) + >>> Version("1.2.3rc1").pre + ('rc', 1) + """ + _pre: Optional[Tuple[str, int]] = self._version.pre return _pre @property - def post(self): - # type: () -> Optional[Tuple[str, int]] + def post(self) -> Optional[int]: + """The post-release number of the version. + + >>> print(Version("1.2.3").post) + None + >>> Version("1.2.3.post1").post + 1 + """ return self._version.post[1] if self._version.post else None @property - def dev(self): - # type: () -> Optional[Tuple[str, int]] + def dev(self) -> Optional[int]: + """The development number of the version. + + >>> print(Version("1.2.3").dev) + None + >>> Version("1.2.3.dev1").dev + 1 + """ return self._version.dev[1] if self._version.dev else None @property - def local(self): - # type: () -> Optional[str] + def local(self) -> Optional[str]: + """The local version segment of the version. + + >>> print(Version("1.2.3").local) + None + >>> Version("1.2.3+abc").local + 'abc' + """ if self._version.local: return ".".join(str(x) for x in self._version.local) else: return None @property - def public(self): - # type: () -> str + def public(self) -> str: + """The public portion of the version. + + >>> Version("1.2.3").public + '1.2.3' + >>> Version("1.2.3+abc").public + '1.2.3' + >>> Version("1.2.3+abc.dev1").public + '1.2.3' + """ return str(self).split("+", 1)[0] @property - def base_version(self): - # type: () -> str + def base_version(self) -> str: + """The "base version" of the version. + + >>> Version("1.2.3").base_version + '1.2.3' + >>> Version("1.2.3+abc").base_version + '1.2.3' + >>> Version("1!1.2.3+abc.dev1").base_version + '1!1.2.3' + + The "base version" is the public version of the project without any pre or post + release markers. + """ parts = [] # Epoch if self.epoch != 0: - parts.append("{0}!".format(self.epoch)) + parts.append(f"{self.epoch}!") # Release segment parts.append(".".join(str(x) for x in self.release)) @@ -387,41 +380,79 @@ class Version(_BaseVersion): return "".join(parts) @property - def is_prerelease(self): - # type: () -> bool + def is_prerelease(self) -> bool: + """Whether this version is a pre-release. + + >>> Version("1.2.3").is_prerelease + False + >>> Version("1.2.3a1").is_prerelease + True + >>> Version("1.2.3b1").is_prerelease + True + >>> Version("1.2.3rc1").is_prerelease + True + >>> Version("1.2.3dev1").is_prerelease + True + """ return self.dev is not None or self.pre is not None @property - def is_postrelease(self): - # type: () -> bool + def is_postrelease(self) -> bool: + """Whether this version is a post-release. + + >>> Version("1.2.3").is_postrelease + False + >>> Version("1.2.3.post1").is_postrelease + True + """ return self.post is not None @property - def is_devrelease(self): - # type: () -> bool + def is_devrelease(self) -> bool: + """Whether this version is a development release. + + >>> Version("1.2.3").is_devrelease + False + >>> Version("1.2.3.dev1").is_devrelease + True + """ return self.dev is not None @property - def major(self): - # type: () -> int + def major(self) -> int: + """The first item of :attr:`release` or ``0`` if unavailable. + + >>> Version("1.2.3").major + 1 + """ return self.release[0] if len(self.release) >= 1 else 0 @property - def minor(self): - # type: () -> int + def minor(self) -> int: + """The second item of :attr:`release` or ``0`` if unavailable. + + >>> Version("1.2.3").minor + 2 + >>> Version("1").minor + 0 + """ return self.release[1] if len(self.release) >= 2 else 0 @property - def micro(self): - # type: () -> int + def micro(self) -> int: + """The third item of :attr:`release` or ``0`` if unavailable. + + >>> Version("1.2.3").micro + 3 + >>> Version("1").micro + 0 + """ return self.release[2] if len(self.release) >= 3 else 0 def _parse_letter_version( - letter, # type: str - number, # type: Union[str, bytes, SupportsInt] -): - # type: (...) -> Optional[Tuple[str, int]] + letter: str, number: Union[str, bytes, SupportsInt] +) -> Optional[Tuple[str, int]]: if letter: # We consider there to be an implicit 0 in a pre-release if there is @@ -458,8 +489,7 @@ def _parse_letter_version( _local_version_separators = re.compile(r"[\._-]") -def _parse_local_version(local): - # type: (str) -> Optional[LocalType] +def _parse_local_version(local: str) -> Optional[LocalType]: """ Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve"). """ @@ -472,14 +502,13 @@ def _parse_local_version(local): def _cmpkey( - epoch, # type: int - release, # type: Tuple[int, ...] - pre, # type: Optional[Tuple[str, int]] - post, # type: Optional[Tuple[str, int]] - dev, # type: Optional[Tuple[str, int]] - local, # type: Optional[Tuple[SubLocalType]] -): - # type: (...) -> CmpKey + epoch: int, + release: Tuple[int, ...], + pre: Optional[Tuple[str, int]], + post: Optional[Tuple[str, int]], + dev: Optional[Tuple[str, int]], + local: Optional[Tuple[SubLocalType]], +) -> CmpKey: # When we compare a release version, we want to compare it with all of the # trailing zeros removed. So we'll use a reverse the list, drop all the now @@ -495,7 +524,7 @@ def _cmpkey( # if there is not a pre or a post segment. If we have one of those then # the normal sorting rules will handle this case correctly. if pre is None and post is None and dev is not None: - _pre = NegativeInfinity # type: PrePostDevType + _pre: PrePostDevType = NegativeInfinity # Versions without a pre-release (except as noted above) should sort after # those with one. elif pre is None: @@ -505,21 +534,21 @@ def _cmpkey( # Versions without a post segment should sort before those with one. if post is None: - _post = NegativeInfinity # type: PrePostDevType + _post: PrePostDevType = NegativeInfinity else: _post = post # Versions without a development segment should sort after those with one. if dev is None: - _dev = Infinity # type: PrePostDevType + _dev: PrePostDevType = Infinity else: _dev = dev if local is None: # Versions without a local segment should sort before those with one. - _local = NegativeInfinity # type: LocalType + _local: LocalType = NegativeInfinity else: # Versions with a local segment need that segment parsed to implement # the sorting rules in PEP440. |