{"repo": "https://github.com/pypa/packaging", "target": "packaging.version:Version", "versions": [{"key": "base", "label": "Before #1082", "commit": "59f183fe90da403e41eef339ce9088bda0fe4cd3", "note": "the parent of #1082's merge", "files": {"packaging/__init__.py": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\n__title__ = \"packaging\"\n__summary__ = \"Core utilities for Python packages\"\n__uri__ = \"https://github.com/pypa/packaging\"\n\n__version__ = \"26.1.dev3\"\n\n__author__ = \"Donald Stufft and individual contributors\"\n__email__ = \"donald@stufft.io\"\n\n__license__ = \"BSD-2-Clause or Apache-2.0\"\n__copyright__ = f\"2014 {__author__}\"\n", "packaging/_structures.py": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\nimport typing\n\n\n@typing.final\nclass InfinityType:\n    __slots__ = ()\n\n    def __repr__(self) -> str:\n        return \"Infinity\"\n\n    def __hash__(self) -> int:\n        return hash(repr(self))\n\n    def __lt__(self, other: object) -> bool:\n        return False\n\n    def __le__(self, other: object) -> bool:\n        return False\n\n    def __eq__(self, other: object) -> bool:\n        return isinstance(other, self.__class__)\n\n    def __gt__(self, other: object) -> bool:\n        return True\n\n    def __ge__(self, other: object) -> bool:\n        return True\n\n    def __neg__(self: object) -> \"NegativeInfinityType\":\n        return NegativeInfinity\n\n\nInfinity = InfinityType()\n\n\n@typing.final\nclass NegativeInfinityType:\n    __slots__ = ()\n\n    def __repr__(self) -> str:\n        return \"-Infinity\"\n\n    def __hash__(self) -> int:\n        return hash(repr(self))\n\n    def __lt__(self, other: object) -> bool:\n        return True\n\n    def __le__(self, other: object) -> bool:\n        return True\n\n    def __eq__(self, other: object) -> bool:\n        return isinstance(other, self.__class__)\n\n    def __gt__(self, other: object) -> bool:\n        return False\n\n    def __ge__(self, other: object) -> bool:\n        return False\n\n    def __neg__(self: object) -> InfinityType:\n        return Infinity\n\n\nNegativeInfinity = NegativeInfinityType()\n", "packaging/version.py": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\"\"\"\n.. testsetup::\n\n    from packaging.version import parse, normalize_pre, Version\n\"\"\"\n\nfrom __future__ import annotations\n\nimport re\nimport sys\nimport typing\nfrom typing import (\n    Any,\n    Callable,\n    Literal,\n    NamedTuple,\n    SupportsInt,\n    Tuple,\n    TypedDict,\n    Union,\n)\n\nfrom ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType\n\nif typing.TYPE_CHECKING:\n    from typing_extensions import Self, Unpack\n\nif sys.version_info >= (3, 13):  # pragma: no cover\n    from warnings import deprecated as _deprecated\nelif typing.TYPE_CHECKING:\n    from typing_extensions import deprecated as _deprecated\nelse:  # pragma: no cover\n    import functools\n    import warnings\n\n    def _deprecated(message: str) -> object:\n        def decorator(func: Callable[[...], object]) -> object:\n            @functools.wraps(func)\n            def wrapper(*args: object, **kwargs: object) -> object:\n                warnings.warn(\n                    message,\n                    category=DeprecationWarning,\n                    stacklevel=2,\n                )\n                return func(*args, **kwargs)\n\n            return wrapper\n\n        return decorator\n\n\n_LETTER_NORMALIZATION = {\n    \"alpha\": \"a\",\n    \"beta\": \"b\",\n    \"c\": \"rc\",\n    \"pre\": \"rc\",\n    \"preview\": \"rc\",\n    \"rev\": \"post\",\n    \"r\": \"post\",\n}\n\n__all__ = [\"VERSION_PATTERN\", \"InvalidVersion\", \"Version\", \"normalize_pre\", \"parse\"]\n\n\ndef __dir__() -> list[str]:\n    return __all__\n\n\nLocalType = Tuple[Union[int, str], ...]\n\nCmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]\nCmpLocalType = Union[\n    NegativeInfinityType,\n    Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],\n]\nCmpKey = Tuple[\n    int,\n    Tuple[int, ...],\n    CmpPrePostDevType,\n    CmpPrePostDevType,\n    CmpPrePostDevType,\n    CmpLocalType,\n]\nVersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]\n\n\nclass _VersionReplace(TypedDict, total=False):\n    epoch: int | None\n    release: tuple[int, ...] | None\n    pre: tuple[str, int] | None\n    post: int | None\n    dev: int | None\n    local: str | None\n\n\ndef normalize_pre(letter: str, /) -> str:\n    \"\"\"Normalize the pre-release segment of a version string.\n\n    Returns a lowercase version of the string if not a known pre-release\n    identifier.\n\n    >>> normalize_pre('alpha')\n    'a'\n    >>> normalize_pre('BETA')\n    'b'\n    >>> normalize_pre('rc')\n    'rc'\n    \"\"\"\n    letter = letter.lower()\n    return _LETTER_NORMALIZATION.get(letter, letter)\n\n\ndef parse(version: str) -> Version:\n    \"\"\"Parse the given version string.\n\n    >>> parse('1.0.dev1')\n    <Version('1.0.dev1')>\n\n    :param version: The version string to parse.\n    :raises InvalidVersion: When the version string is not a valid version.\n    \"\"\"\n    return Version(version)\n\n\nclass InvalidVersion(ValueError):\n    \"\"\"Raised when a version string is not a valid version.\n\n    >>> Version(\"invalid\")\n    Traceback (most recent call last):\n        ...\n    packaging.version.InvalidVersion: Invalid version: 'invalid'\n    \"\"\"\n\n\nclass _BaseVersion:\n    __slots__ = ()\n\n    # This can also be a normal member (see the packaging_legacy package);\n    # we are just requiring it to be readable. Actually defining a property\n    # has runtime effect on subclasses, so it's typing only.\n    if typing.TYPE_CHECKING:\n\n        @property\n        def _key(self) -> tuple[Any, ...]: ...\n\n    def __hash__(self) -> int:\n        return hash(self._key)\n\n    # Please keep the duplicated `isinstance` check\n    # in the six comparisons hereunder\n    # unless you find a way to avoid adding overhead function calls.\n    def __lt__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key < other._key\n\n    def __le__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key <= other._key\n\n    def __eq__(self, other: object) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key == other._key\n\n    def __ge__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key >= other._key\n\n    def __gt__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key > other._key\n\n    def __ne__(self, other: object) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key != other._key\n\n\n# Deliberately not anchored to the start and end of the string, to make it\n# easier for 3rd party code to reuse\n\n# Note that ++ doesn't behave identically on CPython and PyPy, so not using it here\n_VERSION_PATTERN = r\"\"\"\n    v?+                                                   # optional leading v\n    (?:\n        (?:(?P<epoch>[0-9]+)!)?+                          # epoch\n        (?P<release>[0-9]+(?:\\.[0-9]+)*+)                 # release segment\n        (?P<pre>                                          # pre-release\n            [._-]?+\n            (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)\n            [._-]?+\n            (?P<pre_n>[0-9]+)?\n        )?+\n        (?P<post>                                         # post release\n            (?:-(?P<post_n1>[0-9]+))\n            |\n            (?:\n                [._-]?\n                (?P<post_l>post|rev|r)\n                [._-]?\n                (?P<post_n2>[0-9]+)?\n            )\n        )?+\n        (?P<dev>                                          # dev release\n            [._-]?+\n            (?P<dev_l>dev)\n            [._-]?+\n            (?P<dev_n>[0-9]+)?\n        )?+\n    )\n    (?:\\+\n        (?P<local>                                        # local version\n            [a-z0-9]+\n            (?:[._-][a-z0-9]+)*+\n        )\n    )?+\n\"\"\"\n\n_VERSION_PATTERN_OLD = _VERSION_PATTERN.replace(\"*+\", \"*\").replace(\"?+\", \"?\")\n\n# Possessive qualifiers were added in Python 3.11.\n# CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795\n# Older PyPy also had a bug.\nVERSION_PATTERN = (\n    _VERSION_PATTERN_OLD\n    if (sys.implementation.name == \"cpython\" and sys.version_info < (3, 11, 5))\n    or (sys.implementation.name == \"pypy\" and sys.version_info < (3, 11, 13))\n    or sys.version_info < (3, 11)\n    else _VERSION_PATTERN\n)\n\"\"\"\nA string containing the regular expression used to match a valid version.\n\nThe pattern is not anchored at either end, and is intended for embedding in larger\nexpressions (for example, matching a version number as part of a file name). The\nregular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``\nflags set.\n\n:meta hide-value:\n\"\"\"\n\n\n# Validation pattern for local version in replace()\n_LOCAL_PATTERN = re.compile(r\"[a-z0-9]+(?:[._-][a-z0-9]+)*\", re.IGNORECASE)\n\n\ndef _validate_epoch(value: object, /) -> int:\n    epoch = value or 0\n    if isinstance(epoch, int) and epoch >= 0:\n        return epoch\n    msg = f\"epoch must be non-negative integer, got {epoch}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_release(value: object, /) -> tuple[int, ...]:\n    release = (0,) if value is None else value\n    if (\n        isinstance(release, tuple)\n        and len(release) > 0\n        and all(isinstance(i, int) and i >= 0 for i in release)\n    ):\n        return release\n    msg = f\"release must be a non-empty tuple of non-negative integers, got {release}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_pre(value: object, /) -> tuple[Literal[\"a\", \"b\", \"rc\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, tuple) and len(value) == 2:\n        letter, number = value\n        letter = normalize_pre(letter)\n        if letter in {\"a\", \"b\", \"rc\"} and isinstance(number, int) and number >= 0:\n            # type checkers can't infer the Literal type here on letter\n            return (letter, number)  # type: ignore[return-value]\n    msg = f\"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_post(value: object, /) -> tuple[Literal[\"post\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, int) and value >= 0:\n        return (\"post\", value)\n    msg = f\"post must be non-negative integer, got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_dev(value: object, /) -> tuple[Literal[\"dev\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, int) and value >= 0:\n        return (\"dev\", value)\n    msg = f\"dev must be non-negative integer, got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_local(value: object, /) -> LocalType | None:\n    if value is None:\n        return value\n    if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):\n        return _parse_local_version(value)\n    msg = f\"local must be a valid version string, got {value!r}\"\n    raise InvalidVersion(msg)\n\n\n# Backward compatibility for internals before 26.0. Do not use.\nclass _Version(NamedTuple):\n    epoch: int\n    release: tuple[int, ...]\n    dev: tuple[Literal[\"dev\"], int] | None\n    pre: tuple[Literal[\"a\", \"b\", \"rc\"], int] | None\n    post: tuple[Literal[\"post\"], int] | None\n    local: LocalType | None\n\n\nclass Version(_BaseVersion):\n    \"\"\"This class abstracts handling of a project's versions.\n\n    A :class:`Version` instance is comparison aware and can be compared and\n    sorted using the standard Python interfaces.\n\n    >>> v1 = Version(\"1.0a5\")\n    >>> v2 = Version(\"1.0\")\n    >>> v1\n    <Version('1.0a5')>\n    >>> v2\n    <Version('1.0')>\n    >>> v1 < v2\n    True\n    >>> v1 == v2\n    False\n    >>> v1 > v2\n    False\n    >>> v1 >= v2\n    False\n    >>> v1 <= v2\n    True\n    \"\"\"\n\n    __slots__ = (\"_dev\", \"_epoch\", \"_key_cache\", \"_local\", \"_post\", \"_pre\", \"_release\")\n    __match_args__ = (\"_str\",)\n\n    _regex = re.compile(r\"\\s*\" + VERSION_PATTERN + r\"\\s*\", re.VERBOSE | re.IGNORECASE)\n\n    _epoch: int\n    _release: tuple[int, ...]\n    _dev: tuple[Literal[\"dev\"], int] | None\n    _pre: tuple[Literal[\"a\", \"b\", \"rc\"], int] | None\n    _post: tuple[Literal[\"post\"], int] | None\n    _local: LocalType | None\n\n    _key_cache: CmpKey | None\n\n    def __init__(self, version: str) -> None:\n        \"\"\"Initialize a Version object.\n\n        :param version:\n            The string representation of a version which will be parsed and normalized\n            before use.\n        :raises InvalidVersion:\n            If the ``version`` does not conform to PEP 440 in any way then this\n            exception will be raised.\n        \"\"\"\n        # Validate the version and parse it into pieces\n        match = self._regex.fullmatch(version)\n        if not match:\n            raise InvalidVersion(f\"Invalid version: {version!r}\")\n        self._epoch = int(match.group(\"epoch\")) if match.group(\"epoch\") else 0\n        self._release = tuple(map(int, match.group(\"release\").split(\".\")))\n        # We can type ignore the assignments below because the regex guarantees\n        # the correct strings\n        self._pre = _parse_letter_version(match.group(\"pre_l\"), match.group(\"pre_n\"))  # type: ignore[assignment]\n        self._post = _parse_letter_version(  # type: ignore[assignment]\n            match.group(\"post_l\"), match.group(\"post_n1\") or match.group(\"post_n2\")\n        )\n        self._dev = _parse_letter_version(match.group(\"dev_l\"), match.group(\"dev_n\"))  # type: ignore[assignment]\n        self._local = _parse_local_version(match.group(\"local\"))\n\n        # Key which will be used for sorting\n        self._key_cache = None\n\n    @classmethod\n    def from_parts(\n        cls,\n        *,\n        epoch: int = 0,\n        release: tuple[int, ...],\n        pre: tuple[str, int] | None = None,\n        post: int | None = None,\n        dev: int | None = None,\n        local: str | None = None,\n    ) -> Self:\n        _epoch = _validate_epoch(epoch)\n        _release = _validate_release(release)\n        _pre = _validate_pre(pre) if pre is not None else None\n        _post = _validate_post(post) if post is not None else None\n        _dev = _validate_dev(dev) if dev is not None else None\n        _local = _validate_local(local) if local is not None else None\n\n        new_version = cls.__new__(cls)\n        new_version._key_cache = None\n        new_version._epoch = _epoch\n        new_version._release = _release\n        new_version._pre = _pre\n        new_version._post = _post\n        new_version._dev = _dev\n        new_version._local = _local\n\n        return new_version\n\n    def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:\n        epoch = _validate_epoch(kwargs[\"epoch\"]) if \"epoch\" in kwargs else self._epoch\n        release = (\n            _validate_release(kwargs[\"release\"])\n            if \"release\" in kwargs\n            else self._release\n        )\n        pre = _validate_pre(kwargs[\"pre\"]) if \"pre\" in kwargs else self._pre\n        post = _validate_post(kwargs[\"post\"]) if \"post\" in kwargs else self._post\n        dev = _validate_dev(kwargs[\"dev\"]) if \"dev\" in kwargs else self._dev\n        local = _validate_local(kwargs[\"local\"]) if \"local\" in kwargs else self._local\n\n        if (\n            epoch == self._epoch\n            and release == self._release\n            and pre == self._pre\n            and post == self._post\n            and dev == self._dev\n            and local == self._local\n        ):\n            return self\n\n        new_version = self.__class__.__new__(self.__class__)\n        new_version._key_cache = None\n        new_version._epoch = epoch\n        new_version._release = release\n        new_version._pre = pre\n        new_version._post = post\n        new_version._dev = dev\n        new_version._local = local\n\n        return new_version\n\n    @property\n    def _key(self) -> CmpKey:\n        if self._key_cache is None:\n            self._key_cache = _cmpkey(\n                self._epoch,\n                self._release,\n                self._pre,\n                self._post,\n                self._dev,\n                self._local,\n            )\n        return self._key_cache\n\n    @property\n    @_deprecated(\"Version._version is private and will be removed soon\")\n    def _version(self) -> _Version:\n        return _Version(\n            self._epoch, self._release, self._dev, self._pre, self._post, self._local\n        )\n\n    @_version.setter\n    @_deprecated(\"Version._version is private and will be removed soon\")\n    def _version(self, value: _Version) -> None:\n        self._epoch = value.epoch\n        self._release = value.release\n        self._dev = value.dev\n        self._pre = value.pre\n        self._post = value.post\n        self._local = value.local\n        self._key_cache = None\n\n    def __repr__(self) -> str:\n        \"\"\"A representation of the Version that shows all internal state.\n\n        >>> Version('1.0.0')\n        <Version('1.0.0')>\n        \"\"\"\n        return f\"<Version('{self}')>\"\n\n    def __str__(self) -> str:\n        \"\"\"A string representation of the version that can be round-tripped.\n\n        >>> str(Version(\"1.0a5\"))\n        '1.0a5'\n        \"\"\"\n        # This is a hot function, so not calling self.base_version\n        version = \".\".join(map(str, self.release))\n\n        # Epoch\n        if self.epoch:\n            version = f\"{self.epoch}!{version}\"\n\n        # Pre-release\n        if self.pre is not None:\n            version += \"\".join(map(str, self.pre))\n\n        # Post-release\n        if self.post is not None:\n            version += f\".post{self.post}\"\n\n        # Development release\n        if self.dev is not None:\n            version += f\".dev{self.dev}\"\n\n        # Local version segment\n        if self.local is not None:\n            version += f\"+{self.local}\"\n\n        return version\n\n    @property\n    def _str(self) -> str:\n        \"\"\"Internal property for match_args\"\"\"\n        return str(self)\n\n    @property\n    def epoch(self) -> int:\n        \"\"\"The epoch of the version.\n\n        >>> Version(\"2.0.0\").epoch\n        0\n        >>> Version(\"1!2.0.0\").epoch\n        1\n        \"\"\"\n        return self._epoch\n\n    @property\n    def release(self) -> tuple[int, ...]:\n        \"\"\"The components of the \"release\" segment of the version.\n\n        >>> Version(\"1.2.3\").release\n        (1, 2, 3)\n        >>> Version(\"2.0.0\").release\n        (2, 0, 0)\n        >>> Version(\"1!2.0.0.post0\").release\n        (2, 0, 0)\n\n        Includes trailing zeroes but not the epoch or any pre-release / development /\n        post-release suffixes.\n        \"\"\"\n        return self._release\n\n    @property\n    def pre(self) -> tuple[Literal[\"a\", \"b\", \"rc\"], int] | None:\n        \"\"\"The pre-release segment of the version.\n\n        >>> print(Version(\"1.2.3\").pre)\n        None\n        >>> Version(\"1.2.3a1\").pre\n        ('a', 1)\n        >>> Version(\"1.2.3b1\").pre\n        ('b', 1)\n        >>> Version(\"1.2.3rc1\").pre\n        ('rc', 1)\n        \"\"\"\n        return self._pre\n\n    @property\n    def post(self) -> int | None:\n        \"\"\"The post-release number of the version.\n\n        >>> print(Version(\"1.2.3\").post)\n        None\n        >>> Version(\"1.2.3.post1\").post\n        1\n        \"\"\"\n        return self._post[1] if self._post else None\n\n    @property\n    def dev(self) -> int | None:\n        \"\"\"The development number of the version.\n\n        >>> print(Version(\"1.2.3\").dev)\n        None\n        >>> Version(\"1.2.3.dev1\").dev\n        1\n        \"\"\"\n        return self._dev[1] if self._dev else None\n\n    @property\n    def local(self) -> str | None:\n        \"\"\"The local version segment of the version.\n\n        >>> print(Version(\"1.2.3\").local)\n        None\n        >>> Version(\"1.2.3+abc\").local\n        'abc'\n        \"\"\"\n        if self._local:\n            return \".\".join(str(x) for x in self._local)\n        else:\n            return None\n\n    @property\n    def public(self) -> str:\n        \"\"\"The public portion of the version.\n\n        >>> Version(\"1.2.3\").public\n        '1.2.3'\n        >>> Version(\"1.2.3+abc\").public\n        '1.2.3'\n        >>> Version(\"1!1.2.3dev1+abc\").public\n        '1!1.2.3.dev1'\n        \"\"\"\n        return str(self).split(\"+\", 1)[0]\n\n    @property\n    def base_version(self) -> str:\n        \"\"\"The \"base version\" of the version.\n\n        >>> Version(\"1.2.3\").base_version\n        '1.2.3'\n        >>> Version(\"1.2.3+abc\").base_version\n        '1.2.3'\n        >>> Version(\"1!1.2.3dev1+abc\").base_version\n        '1!1.2.3'\n\n        The \"base version\" is the public version of the project without any pre or post\n        release markers.\n        \"\"\"\n        release_segment = \".\".join(map(str, self.release))\n        return f\"{self.epoch}!{release_segment}\" if self.epoch else release_segment\n\n    @property\n    def is_prerelease(self) -> bool:\n        \"\"\"Whether this version is a pre-release.\n\n        >>> Version(\"1.2.3\").is_prerelease\n        False\n        >>> Version(\"1.2.3a1\").is_prerelease\n        True\n        >>> Version(\"1.2.3b1\").is_prerelease\n        True\n        >>> Version(\"1.2.3rc1\").is_prerelease\n        True\n        >>> Version(\"1.2.3dev1\").is_prerelease\n        True\n        \"\"\"\n        return self.dev is not None or self.pre is not None\n\n    @property\n    def is_postrelease(self) -> bool:\n        \"\"\"Whether this version is a post-release.\n\n        >>> Version(\"1.2.3\").is_postrelease\n        False\n        >>> Version(\"1.2.3.post1\").is_postrelease\n        True\n        \"\"\"\n        return self.post is not None\n\n    @property\n    def is_devrelease(self) -> bool:\n        \"\"\"Whether this version is a development release.\n\n        >>> Version(\"1.2.3\").is_devrelease\n        False\n        >>> Version(\"1.2.3.dev1\").is_devrelease\n        True\n        \"\"\"\n        return self.dev is not None\n\n    @property\n    def major(self) -> int:\n        \"\"\"The first item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").major\n        1\n        \"\"\"\n        return self.release[0] if len(self.release) >= 1 else 0\n\n    @property\n    def minor(self) -> int:\n        \"\"\"The second item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").minor\n        2\n        >>> Version(\"1\").minor\n        0\n        \"\"\"\n        return self.release[1] if len(self.release) >= 2 else 0\n\n    @property\n    def micro(self) -> int:\n        \"\"\"The third item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").micro\n        3\n        >>> Version(\"1\").micro\n        0\n        \"\"\"\n        return self.release[2] if len(self.release) >= 3 else 0\n\n\nclass _TrimmedRelease(Version):\n    __slots__ = ()\n\n    def __init__(self, version: str | Version) -> None:\n        if isinstance(version, Version):\n            self._epoch = version._epoch\n            self._release = version._release\n            self._dev = version._dev\n            self._pre = version._pre\n            self._post = version._post\n            self._local = version._local\n            self._key_cache = version._key_cache\n            return\n        super().__init__(version)  # pragma: no cover\n\n    @property\n    def release(self) -> tuple[int, ...]:\n        \"\"\"\n        Release segment without any trailing zeros.\n\n        >>> _TrimmedRelease('1.0.0').release\n        (1,)\n        >>> _TrimmedRelease('0.0').release\n        (0,)\n        \"\"\"\n        # This leaves one 0.\n        rel = super().release\n        len_release = len(rel)\n        i = len_release\n        while i > 1 and rel[i - 1] == 0:\n            i -= 1\n        return rel if i == len_release else rel[:i]\n\n\ndef _parse_letter_version(\n    letter: str | None, number: str | bytes | SupportsInt | None\n) -> tuple[str, int] | None:\n    if letter:\n        # We normalize any letters to their lower case form\n        letter = letter.lower()\n\n        # We consider some words to be alternate spellings of other words and\n        # in those cases we want to normalize the spellings to our preferred\n        # spelling.\n        letter = _LETTER_NORMALIZATION.get(letter, letter)\n\n        # We consider there to be an implicit 0 in a pre-release if there is\n        # not a numeral associated with it.\n        return letter, int(number or 0)\n\n    if number:\n        # We assume if we are given a number, but we are not given a letter\n        # then this is using the implicit post release syntax (e.g. 1.0-1)\n        return \"post\", int(number)\n\n    return None\n\n\n_local_version_separators = re.compile(r\"[\\._-]\")\n\n\ndef _parse_local_version(local: str | None) -> LocalType | None:\n    \"\"\"\n    Takes a string like abc.1.twelve and turns it into (\"abc\", 1, \"twelve\").\n    \"\"\"\n    if local is not None:\n        return tuple(\n            part.lower() if not part.isdigit() else int(part)\n            for part in _local_version_separators.split(local)\n        )\n    return None\n\n\ndef _cmpkey(\n    epoch: int,\n    release: tuple[int, ...],\n    pre: tuple[str, int] | None,\n    post: tuple[str, int] | None,\n    dev: tuple[str, int] | None,\n    local: LocalType | None,\n) -> CmpKey:\n    # When we compare a release version, we want to compare it with all of the\n    # trailing zeros removed. We will use this for our sorting key.\n    len_release = len(release)\n    i = len_release\n    while i and release[i - 1] == 0:\n        i -= 1\n    _release = release if i == len_release else release[:i]\n\n    # We need to \"trick\" the sorting algorithm to put 1.0.dev0 before 1.0a0.\n    # We'll do this by abusing the pre segment, but we _only_ want to do this\n    # if there is not a pre or a post segment. If we have one of those then\n    # the normal sorting rules will handle this case correctly.\n    if pre is None and post is None and dev is not None:\n        _pre: CmpPrePostDevType = NegativeInfinity\n    # Versions without a pre-release (except as noted above) should sort after\n    # those with one.\n    elif pre is None:\n        _pre = Infinity\n    else:\n        _pre = pre\n\n    # Versions without a post segment should sort before those with one.\n    if post is None:\n        _post: CmpPrePostDevType = NegativeInfinity\n\n    else:\n        _post = post\n\n    # Versions without a development segment should sort after those with one.\n    if dev is None:\n        _dev: CmpPrePostDevType = Infinity\n\n    else:\n        _dev = dev\n\n    if local is None:\n        # Versions without a local segment should sort before those with one.\n        _local: CmpLocalType = NegativeInfinity\n    else:\n        # Versions with a local segment need that segment parsed to implement\n        # the sorting rules in PEP440.\n        # - Alpha numeric segments sort before numeric segments\n        # - Alpha numeric segments sort lexicographically\n        # - Numeric segments sort numerically\n        # - Shorter versions sort before longer versions when the prefixes\n        #   match exactly\n        _local = tuple(\n            (i, \"\") if isinstance(i, int) else (NegativeInfinity, i) for i in local\n        )\n\n    return epoch, _release, _pre, _post, _dev, _local\n"}}, {"key": "pr", "label": "#1082, intermediate commit", "commit": "61967cca278dc51389b8973ba65bbbf23eaa3eda", "note": "the fast path as first pushed", "files": {"packaging/__init__.py": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\n__title__ = \"packaging\"\n__summary__ = \"Core utilities for Python packages\"\n__uri__ = \"https://github.com/pypa/packaging\"\n\n__version__ = \"26.1.dev3\"\n\n__author__ = \"Donald Stufft and individual contributors\"\n__email__ = \"donald@stufft.io\"\n\n__license__ = \"BSD-2-Clause or Apache-2.0\"\n__copyright__ = f\"2014 {__author__}\"\n", "packaging/_structures.py": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\nimport typing\n\n\n@typing.final\nclass InfinityType:\n    __slots__ = ()\n\n    def __repr__(self) -> str:\n        return \"Infinity\"\n\n    def __hash__(self) -> int:\n        return hash(repr(self))\n\n    def __lt__(self, other: object) -> bool:\n        return False\n\n    def __le__(self, other: object) -> bool:\n        return False\n\n    def __eq__(self, other: object) -> bool:\n        return isinstance(other, self.__class__)\n\n    def __gt__(self, other: object) -> bool:\n        return True\n\n    def __ge__(self, other: object) -> bool:\n        return True\n\n    def __neg__(self: object) -> \"NegativeInfinityType\":\n        return NegativeInfinity\n\n\nInfinity = InfinityType()\n\n\n@typing.final\nclass NegativeInfinityType:\n    __slots__ = ()\n\n    def __repr__(self) -> str:\n        return \"-Infinity\"\n\n    def __hash__(self) -> int:\n        return hash(repr(self))\n\n    def __lt__(self, other: object) -> bool:\n        return True\n\n    def __le__(self, other: object) -> bool:\n        return True\n\n    def __eq__(self, other: object) -> bool:\n        return isinstance(other, self.__class__)\n\n    def __gt__(self, other: object) -> bool:\n        return False\n\n    def __ge__(self, other: object) -> bool:\n        return False\n\n    def __neg__(self: object) -> InfinityType:\n        return Infinity\n\n\nNegativeInfinity = NegativeInfinityType()\n", "packaging/version.py": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\"\"\"\n.. testsetup::\n\n    from packaging.version import parse, normalize_pre, Version\n\"\"\"\n\nfrom __future__ import annotations\n\nimport re\nimport sys\nimport typing\nfrom typing import (\n    Any,\n    Callable,\n    Literal,\n    NamedTuple,\n    SupportsInt,\n    Tuple,\n    TypedDict,\n    Union,\n)\n\nfrom ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType\n\nif typing.TYPE_CHECKING:\n    from typing_extensions import Self, Unpack\n\nif sys.version_info >= (3, 13):  # pragma: no cover\n    from warnings import deprecated as _deprecated\nelif typing.TYPE_CHECKING:\n    from typing_extensions import deprecated as _deprecated\nelse:  # pragma: no cover\n    import functools\n    import warnings\n\n    def _deprecated(message: str) -> object:\n        def decorator(func: Callable[[...], object]) -> object:\n            @functools.wraps(func)\n            def wrapper(*args: object, **kwargs: object) -> object:\n                warnings.warn(\n                    message,\n                    category=DeprecationWarning,\n                    stacklevel=2,\n                )\n                return func(*args, **kwargs)\n\n            return wrapper\n\n        return decorator\n\n\n_LETTER_NORMALIZATION = {\n    \"alpha\": \"a\",\n    \"beta\": \"b\",\n    \"c\": \"rc\",\n    \"pre\": \"rc\",\n    \"preview\": \"rc\",\n    \"rev\": \"post\",\n    \"r\": \"post\",\n}\n\n__all__ = [\"VERSION_PATTERN\", \"InvalidVersion\", \"Version\", \"normalize_pre\", \"parse\"]\n\n\ndef __dir__() -> list[str]:\n    return __all__\n\n\nLocalType = Tuple[Union[int, str], ...]\n\nCmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]\nCmpLocalType = Union[\n    NegativeInfinityType,\n    Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],\n]\nCmpKey = Tuple[\n    int,\n    Tuple[int, ...],\n    CmpPrePostDevType,\n    CmpPrePostDevType,\n    CmpPrePostDevType,\n    CmpLocalType,\n]\nVersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]\n\n\nclass _VersionReplace(TypedDict, total=False):\n    epoch: int | None\n    release: tuple[int, ...] | None\n    pre: tuple[str, int] | None\n    post: int | None\n    dev: int | None\n    local: str | None\n\n\ndef normalize_pre(letter: str, /) -> str:\n    \"\"\"Normalize the pre-release segment of a version string.\n\n    Returns a lowercase version of the string if not a known pre-release\n    identifier.\n\n    >>> normalize_pre('alpha')\n    'a'\n    >>> normalize_pre('BETA')\n    'b'\n    >>> normalize_pre('rc')\n    'rc'\n    \"\"\"\n    letter = letter.lower()\n    return _LETTER_NORMALIZATION.get(letter, letter)\n\n\ndef parse(version: str) -> Version:\n    \"\"\"Parse the given version string.\n\n    >>> parse('1.0.dev1')\n    <Version('1.0.dev1')>\n\n    :param version: The version string to parse.\n    :raises InvalidVersion: When the version string is not a valid version.\n    \"\"\"\n    return Version(version)\n\n\nclass InvalidVersion(ValueError):\n    \"\"\"Raised when a version string is not a valid version.\n\n    >>> Version(\"invalid\")\n    Traceback (most recent call last):\n        ...\n    packaging.version.InvalidVersion: Invalid version: 'invalid'\n    \"\"\"\n\n\nclass _BaseVersion:\n    __slots__ = ()\n\n    # This can also be a normal member (see the packaging_legacy package);\n    # we are just requiring it to be readable. Actually defining a property\n    # has runtime effect on subclasses, so it's typing only.\n    if typing.TYPE_CHECKING:\n\n        @property\n        def _key(self) -> tuple[Any, ...]: ...\n\n    def __hash__(self) -> int:\n        return hash(self._key)\n\n    # Please keep the duplicated `isinstance` check\n    # in the six comparisons hereunder\n    # unless you find a way to avoid adding overhead function calls.\n    def __lt__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key < other._key\n\n    def __le__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key <= other._key\n\n    def __eq__(self, other: object) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key == other._key\n\n    def __ge__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key >= other._key\n\n    def __gt__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key > other._key\n\n    def __ne__(self, other: object) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key != other._key\n\n\n# Deliberately not anchored to the start and end of the string, to make it\n# easier for 3rd party code to reuse\n\n# Note that ++ doesn't behave identically on CPython and PyPy, so not using it here\n_VERSION_PATTERN = r\"\"\"\n    v?+                                                   # optional leading v\n    (?:\n        (?:(?P<epoch>[0-9]+)!)?+                          # epoch\n        (?P<release>[0-9]+(?:\\.[0-9]+)*+)                 # release segment\n        (?P<pre>                                          # pre-release\n            [._-]?+\n            (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)\n            [._-]?+\n            (?P<pre_n>[0-9]+)?\n        )?+\n        (?P<post>                                         # post release\n            (?:-(?P<post_n1>[0-9]+))\n            |\n            (?:\n                [._-]?\n                (?P<post_l>post|rev|r)\n                [._-]?\n                (?P<post_n2>[0-9]+)?\n            )\n        )?+\n        (?P<dev>                                          # dev release\n            [._-]?+\n            (?P<dev_l>dev)\n            [._-]?+\n            (?P<dev_n>[0-9]+)?\n        )?+\n    )\n    (?:\\+\n        (?P<local>                                        # local version\n            [a-z0-9]+\n            (?:[._-][a-z0-9]+)*+\n        )\n    )?+\n\"\"\"\n\n_VERSION_PATTERN_OLD = _VERSION_PATTERN.replace(\"*+\", \"*\").replace(\"?+\", \"?\")\n\n# Possessive qualifiers were added in Python 3.11.\n# CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795\n# Older PyPy also had a bug.\nVERSION_PATTERN = (\n    _VERSION_PATTERN_OLD\n    if (sys.implementation.name == \"cpython\" and sys.version_info < (3, 11, 5))\n    or (sys.implementation.name == \"pypy\" and sys.version_info < (3, 11, 13))\n    or sys.version_info < (3, 11)\n    else _VERSION_PATTERN\n)\n\"\"\"\nA string containing the regular expression used to match a valid version.\n\nThe pattern is not anchored at either end, and is intended for embedding in larger\nexpressions (for example, matching a version number as part of a file name). The\nregular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``\nflags set.\n\n:meta hide-value:\n\"\"\"\n\n\n# Validation pattern for local version in replace()\n_LOCAL_PATTERN = re.compile(r\"[a-z0-9]+(?:[._-][a-z0-9]+)*\", re.IGNORECASE)\n\n# Fast path: If a version doesn't have one of these characters then\n# it is a simple version with just numbers and dots (e.g. 1.2.3).\n# Covers lower and upper letters from: alpha, beta, rc, c, pre,\n# preview, post, rev, r, dev, epoch (!), local (+), leading v,\n# and separators (-, _)\n_NON_SIMPLE_VERSION_INDICATORS = frozenset(\"!+-ABCDEHILOPRSTVW_abcdehiloprstvw\")\n\n\ndef _validate_epoch(value: object, /) -> int:\n    epoch = value or 0\n    if isinstance(epoch, int) and epoch >= 0:\n        return epoch\n    msg = f\"epoch must be non-negative integer, got {epoch}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_release(value: object, /) -> tuple[int, ...]:\n    release = (0,) if value is None else value\n    if (\n        isinstance(release, tuple)\n        and len(release) > 0\n        and all(isinstance(i, int) and i >= 0 for i in release)\n    ):\n        return release\n    msg = f\"release must be a non-empty tuple of non-negative integers, got {release}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_pre(value: object, /) -> tuple[Literal[\"a\", \"b\", \"rc\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, tuple) and len(value) == 2:\n        letter, number = value\n        letter = normalize_pre(letter)\n        if letter in {\"a\", \"b\", \"rc\"} and isinstance(number, int) and number >= 0:\n            # type checkers can't infer the Literal type here on letter\n            return (letter, number)  # type: ignore[return-value]\n    msg = f\"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_post(value: object, /) -> tuple[Literal[\"post\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, int) and value >= 0:\n        return (\"post\", value)\n    msg = f\"post must be non-negative integer, got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_dev(value: object, /) -> tuple[Literal[\"dev\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, int) and value >= 0:\n        return (\"dev\", value)\n    msg = f\"dev must be non-negative integer, got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_local(value: object, /) -> LocalType | None:\n    if value is None:\n        return value\n    if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):\n        return _parse_local_version(value)\n    msg = f\"local must be a valid version string, got {value!r}\"\n    raise InvalidVersion(msg)\n\n\n# Backward compatibility for internals before 26.0. Do not use.\nclass _Version(NamedTuple):\n    epoch: int\n    release: tuple[int, ...]\n    dev: tuple[Literal[\"dev\"], int] | None\n    pre: tuple[Literal[\"a\", \"b\", \"rc\"], int] | None\n    post: tuple[Literal[\"post\"], int] | None\n    local: LocalType | None\n\n\nclass Version(_BaseVersion):\n    \"\"\"This class abstracts handling of a project's versions.\n\n    A :class:`Version` instance is comparison aware and can be compared and\n    sorted using the standard Python interfaces.\n\n    >>> v1 = Version(\"1.0a5\")\n    >>> v2 = Version(\"1.0\")\n    >>> v1\n    <Version('1.0a5')>\n    >>> v2\n    <Version('1.0')>\n    >>> v1 < v2\n    True\n    >>> v1 == v2\n    False\n    >>> v1 > v2\n    False\n    >>> v1 >= v2\n    False\n    >>> v1 <= v2\n    True\n    \"\"\"\n\n    __slots__ = (\"_dev\", \"_epoch\", \"_key_cache\", \"_local\", \"_post\", \"_pre\", \"_release\")\n    __match_args__ = (\"_str\",)\n\n    _regex = re.compile(r\"\\s*\" + VERSION_PATTERN + r\"\\s*\", re.VERBOSE | re.IGNORECASE)\n\n    _epoch: int\n    _release: tuple[int, ...]\n    _dev: tuple[Literal[\"dev\"], int] | None\n    _pre: tuple[Literal[\"a\", \"b\", \"rc\"], int] | None\n    _post: tuple[Literal[\"post\"], int] | None\n    _local: LocalType | None\n\n    _key_cache: CmpKey | None\n\n    def __init__(self, version: str) -> None:\n        \"\"\"Initialize a Version object.\n\n        :param version:\n            The string representation of a version which will be parsed and normalized\n            before use.\n        :raises InvalidVersion:\n            If the ``version`` does not conform to PEP 440 in any way then this\n            exception will be raised.\n        \"\"\"\n        if _NON_SIMPLE_VERSION_INDICATORS.isdisjoint(version):\n            try:\n                self._release = tuple(map(int, version.strip().split(\".\")))\n            except ValueError:\n                raise InvalidVersion(f\"Invalid version: {version!r}\") from None\n\n            self._epoch = 0\n            self._pre = None\n            self._post = None\n            self._dev = None\n            self._local = None\n            self._key_cache = None\n            return\n\n        # Validate the version and parse it into pieces\n        match = self._regex.fullmatch(version)\n        if not match:\n            raise InvalidVersion(f\"Invalid version: {version!r}\")\n        self._epoch = int(match.group(\"epoch\")) if match.group(\"epoch\") else 0\n        self._release = tuple(map(int, match.group(\"release\").split(\".\")))\n        # We can type ignore the assignments below because the regex guarantees\n        # the correct strings\n        self._pre = _parse_letter_version(match.group(\"pre_l\"), match.group(\"pre_n\"))  # type: ignore[assignment]\n        self._post = _parse_letter_version(  # type: ignore[assignment]\n            match.group(\"post_l\"), match.group(\"post_n1\") or match.group(\"post_n2\")\n        )\n        self._dev = _parse_letter_version(match.group(\"dev_l\"), match.group(\"dev_n\"))  # type: ignore[assignment]\n        self._local = _parse_local_version(match.group(\"local\"))\n\n        # Key which will be used for sorting\n        self._key_cache = None\n\n    @classmethod\n    def from_parts(\n        cls,\n        *,\n        epoch: int = 0,\n        release: tuple[int, ...],\n        pre: tuple[str, int] | None = None,\n        post: int | None = None,\n        dev: int | None = None,\n        local: str | None = None,\n    ) -> Self:\n        _epoch = _validate_epoch(epoch)\n        _release = _validate_release(release)\n        _pre = _validate_pre(pre) if pre is not None else None\n        _post = _validate_post(post) if post is not None else None\n        _dev = _validate_dev(dev) if dev is not None else None\n        _local = _validate_local(local) if local is not None else None\n\n        new_version = cls.__new__(cls)\n        new_version._key_cache = None\n        new_version._epoch = _epoch\n        new_version._release = _release\n        new_version._pre = _pre\n        new_version._post = _post\n        new_version._dev = _dev\n        new_version._local = _local\n\n        return new_version\n\n    def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:\n        epoch = _validate_epoch(kwargs[\"epoch\"]) if \"epoch\" in kwargs else self._epoch\n        release = (\n            _validate_release(kwargs[\"release\"])\n            if \"release\" in kwargs\n            else self._release\n        )\n        pre = _validate_pre(kwargs[\"pre\"]) if \"pre\" in kwargs else self._pre\n        post = _validate_post(kwargs[\"post\"]) if \"post\" in kwargs else self._post\n        dev = _validate_dev(kwargs[\"dev\"]) if \"dev\" in kwargs else self._dev\n        local = _validate_local(kwargs[\"local\"]) if \"local\" in kwargs else self._local\n\n        if (\n            epoch == self._epoch\n            and release == self._release\n            and pre == self._pre\n            and post == self._post\n            and dev == self._dev\n            and local == self._local\n        ):\n            return self\n\n        new_version = self.__class__.__new__(self.__class__)\n        new_version._key_cache = None\n        new_version._epoch = epoch\n        new_version._release = release\n        new_version._pre = pre\n        new_version._post = post\n        new_version._dev = dev\n        new_version._local = local\n\n        return new_version\n\n    @property\n    def _key(self) -> CmpKey:\n        if self._key_cache is None:\n            self._key_cache = _cmpkey(\n                self._epoch,\n                self._release,\n                self._pre,\n                self._post,\n                self._dev,\n                self._local,\n            )\n        return self._key_cache\n\n    @property\n    @_deprecated(\"Version._version is private and will be removed soon\")\n    def _version(self) -> _Version:\n        return _Version(\n            self._epoch, self._release, self._dev, self._pre, self._post, self._local\n        )\n\n    @_version.setter\n    @_deprecated(\"Version._version is private and will be removed soon\")\n    def _version(self, value: _Version) -> None:\n        self._epoch = value.epoch\n        self._release = value.release\n        self._dev = value.dev\n        self._pre = value.pre\n        self._post = value.post\n        self._local = value.local\n        self._key_cache = None\n\n    def __repr__(self) -> str:\n        \"\"\"A representation of the Version that shows all internal state.\n\n        >>> Version('1.0.0')\n        <Version('1.0.0')>\n        \"\"\"\n        return f\"<Version('{self}')>\"\n\n    def __str__(self) -> str:\n        \"\"\"A string representation of the version that can be round-tripped.\n\n        >>> str(Version(\"1.0a5\"))\n        '1.0a5'\n        \"\"\"\n        # This is a hot function, so not calling self.base_version\n        version = \".\".join(map(str, self.release))\n\n        # Epoch\n        if self.epoch:\n            version = f\"{self.epoch}!{version}\"\n\n        # Pre-release\n        if self.pre is not None:\n            version += \"\".join(map(str, self.pre))\n\n        # Post-release\n        if self.post is not None:\n            version += f\".post{self.post}\"\n\n        # Development release\n        if self.dev is not None:\n            version += f\".dev{self.dev}\"\n\n        # Local version segment\n        if self.local is not None:\n            version += f\"+{self.local}\"\n\n        return version\n\n    @property\n    def _str(self) -> str:\n        \"\"\"Internal property for match_args\"\"\"\n        return str(self)\n\n    @property\n    def epoch(self) -> int:\n        \"\"\"The epoch of the version.\n\n        >>> Version(\"2.0.0\").epoch\n        0\n        >>> Version(\"1!2.0.0\").epoch\n        1\n        \"\"\"\n        return self._epoch\n\n    @property\n    def release(self) -> tuple[int, ...]:\n        \"\"\"The components of the \"release\" segment of the version.\n\n        >>> Version(\"1.2.3\").release\n        (1, 2, 3)\n        >>> Version(\"2.0.0\").release\n        (2, 0, 0)\n        >>> Version(\"1!2.0.0.post0\").release\n        (2, 0, 0)\n\n        Includes trailing zeroes but not the epoch or any pre-release / development /\n        post-release suffixes.\n        \"\"\"\n        return self._release\n\n    @property\n    def pre(self) -> tuple[Literal[\"a\", \"b\", \"rc\"], int] | None:\n        \"\"\"The pre-release segment of the version.\n\n        >>> print(Version(\"1.2.3\").pre)\n        None\n        >>> Version(\"1.2.3a1\").pre\n        ('a', 1)\n        >>> Version(\"1.2.3b1\").pre\n        ('b', 1)\n        >>> Version(\"1.2.3rc1\").pre\n        ('rc', 1)\n        \"\"\"\n        return self._pre\n\n    @property\n    def post(self) -> int | None:\n        \"\"\"The post-release number of the version.\n\n        >>> print(Version(\"1.2.3\").post)\n        None\n        >>> Version(\"1.2.3.post1\").post\n        1\n        \"\"\"\n        return self._post[1] if self._post else None\n\n    @property\n    def dev(self) -> int | None:\n        \"\"\"The development number of the version.\n\n        >>> print(Version(\"1.2.3\").dev)\n        None\n        >>> Version(\"1.2.3.dev1\").dev\n        1\n        \"\"\"\n        return self._dev[1] if self._dev else None\n\n    @property\n    def local(self) -> str | None:\n        \"\"\"The local version segment of the version.\n\n        >>> print(Version(\"1.2.3\").local)\n        None\n        >>> Version(\"1.2.3+abc\").local\n        'abc'\n        \"\"\"\n        if self._local:\n            return \".\".join(str(x) for x in self._local)\n        else:\n            return None\n\n    @property\n    def public(self) -> str:\n        \"\"\"The public portion of the version.\n\n        >>> Version(\"1.2.3\").public\n        '1.2.3'\n        >>> Version(\"1.2.3+abc\").public\n        '1.2.3'\n        >>> Version(\"1!1.2.3dev1+abc\").public\n        '1!1.2.3.dev1'\n        \"\"\"\n        return str(self).split(\"+\", 1)[0]\n\n    @property\n    def base_version(self) -> str:\n        \"\"\"The \"base version\" of the version.\n\n        >>> Version(\"1.2.3\").base_version\n        '1.2.3'\n        >>> Version(\"1.2.3+abc\").base_version\n        '1.2.3'\n        >>> Version(\"1!1.2.3dev1+abc\").base_version\n        '1!1.2.3'\n\n        The \"base version\" is the public version of the project without any pre or post\n        release markers.\n        \"\"\"\n        release_segment = \".\".join(map(str, self.release))\n        return f\"{self.epoch}!{release_segment}\" if self.epoch else release_segment\n\n    @property\n    def is_prerelease(self) -> bool:\n        \"\"\"Whether this version is a pre-release.\n\n        >>> Version(\"1.2.3\").is_prerelease\n        False\n        >>> Version(\"1.2.3a1\").is_prerelease\n        True\n        >>> Version(\"1.2.3b1\").is_prerelease\n        True\n        >>> Version(\"1.2.3rc1\").is_prerelease\n        True\n        >>> Version(\"1.2.3dev1\").is_prerelease\n        True\n        \"\"\"\n        return self.dev is not None or self.pre is not None\n\n    @property\n    def is_postrelease(self) -> bool:\n        \"\"\"Whether this version is a post-release.\n\n        >>> Version(\"1.2.3\").is_postrelease\n        False\n        >>> Version(\"1.2.3.post1\").is_postrelease\n        True\n        \"\"\"\n        return self.post is not None\n\n    @property\n    def is_devrelease(self) -> bool:\n        \"\"\"Whether this version is a development release.\n\n        >>> Version(\"1.2.3\").is_devrelease\n        False\n        >>> Version(\"1.2.3.dev1\").is_devrelease\n        True\n        \"\"\"\n        return self.dev is not None\n\n    @property\n    def major(self) -> int:\n        \"\"\"The first item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").major\n        1\n        \"\"\"\n        return self.release[0] if len(self.release) >= 1 else 0\n\n    @property\n    def minor(self) -> int:\n        \"\"\"The second item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").minor\n        2\n        >>> Version(\"1\").minor\n        0\n        \"\"\"\n        return self.release[1] if len(self.release) >= 2 else 0\n\n    @property\n    def micro(self) -> int:\n        \"\"\"The third item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").micro\n        3\n        >>> Version(\"1\").micro\n        0\n        \"\"\"\n        return self.release[2] if len(self.release) >= 3 else 0\n\n\nclass _TrimmedRelease(Version):\n    __slots__ = ()\n\n    def __init__(self, version: str | Version) -> None:\n        if isinstance(version, Version):\n            self._epoch = version._epoch\n            self._release = version._release\n            self._dev = version._dev\n            self._pre = version._pre\n            self._post = version._post\n            self._local = version._local\n            self._key_cache = version._key_cache\n            return\n        super().__init__(version)  # pragma: no cover\n\n    @property\n    def release(self) -> tuple[int, ...]:\n        \"\"\"\n        Release segment without any trailing zeros.\n\n        >>> _TrimmedRelease('1.0.0').release\n        (1,)\n        >>> _TrimmedRelease('0.0').release\n        (0,)\n        \"\"\"\n        # This leaves one 0.\n        rel = super().release\n        len_release = len(rel)\n        i = len_release\n        while i > 1 and rel[i - 1] == 0:\n            i -= 1\n        return rel if i == len_release else rel[:i]\n\n\ndef _parse_letter_version(\n    letter: str | None, number: str | bytes | SupportsInt | None\n) -> tuple[str, int] | None:\n    if letter:\n        # We normalize any letters to their lower case form\n        letter = letter.lower()\n\n        # We consider some words to be alternate spellings of other words and\n        # in those cases we want to normalize the spellings to our preferred\n        # spelling.\n        letter = _LETTER_NORMALIZATION.get(letter, letter)\n\n        # We consider there to be an implicit 0 in a pre-release if there is\n        # not a numeral associated with it.\n        return letter, int(number or 0)\n\n    if number:\n        # We assume if we are given a number, but we are not given a letter\n        # then this is using the implicit post release syntax (e.g. 1.0-1)\n        return \"post\", int(number)\n\n    return None\n\n\n_local_version_separators = re.compile(r\"[\\._-]\")\n\n\ndef _parse_local_version(local: str | None) -> LocalType | None:\n    \"\"\"\n    Takes a string like abc.1.twelve and turns it into (\"abc\", 1, \"twelve\").\n    \"\"\"\n    if local is not None:\n        return tuple(\n            part.lower() if not part.isdigit() else int(part)\n            for part in _local_version_separators.split(local)\n        )\n    return None\n\n\ndef _cmpkey(\n    epoch: int,\n    release: tuple[int, ...],\n    pre: tuple[str, int] | None,\n    post: tuple[str, int] | None,\n    dev: tuple[str, int] | None,\n    local: LocalType | None,\n) -> CmpKey:\n    # When we compare a release version, we want to compare it with all of the\n    # trailing zeros removed. We will use this for our sorting key.\n    len_release = len(release)\n    i = len_release\n    while i and release[i - 1] == 0:\n        i -= 1\n    _release = release if i == len_release else release[:i]\n\n    # We need to \"trick\" the sorting algorithm to put 1.0.dev0 before 1.0a0.\n    # We'll do this by abusing the pre segment, but we _only_ want to do this\n    # if there is not a pre or a post segment. If we have one of those then\n    # the normal sorting rules will handle this case correctly.\n    if pre is None and post is None and dev is not None:\n        _pre: CmpPrePostDevType = NegativeInfinity\n    # Versions without a pre-release (except as noted above) should sort after\n    # those with one.\n    elif pre is None:\n        _pre = Infinity\n    else:\n        _pre = pre\n\n    # Versions without a post segment should sort before those with one.\n    if post is None:\n        _post: CmpPrePostDevType = NegativeInfinity\n\n    else:\n        _post = post\n\n    # Versions without a development segment should sort after those with one.\n    if dev is None:\n        _dev: CmpPrePostDevType = Infinity\n\n    else:\n        _dev = dev\n\n    if local is None:\n        # Versions without a local segment should sort before those with one.\n        _local: CmpLocalType = NegativeInfinity\n    else:\n        # Versions with a local segment need that segment parsed to implement\n        # the sorting rules in PEP440.\n        # - Alpha numeric segments sort before numeric segments\n        # - Alpha numeric segments sort lexicographically\n        # - Numeric segments sort numerically\n        # - Shorter versions sort before longer versions when the prefixes\n        #   match exactly\n        _local = tuple(\n            (i, \"\") if isinstance(i, int) else (NegativeInfinity, i) for i in local\n        )\n\n    return epoch, _release, _pre, _post, _dev, _local\n"}}, {"key": "merged", "label": "#1082 as merged", "commit": "ee13ba2c1c9a01c97a08ca9d98ff9fa6f0a0a67b", "note": "after review fixes", "files": {"packaging/__init__.py": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\n__title__ = \"packaging\"\n__summary__ = \"Core utilities for Python packages\"\n__uri__ = \"https://github.com/pypa/packaging\"\n\n__version__ = \"26.1.dev3\"\n\n__author__ = \"Donald Stufft and individual contributors\"\n__email__ = \"donald@stufft.io\"\n\n__license__ = \"BSD-2-Clause or Apache-2.0\"\n__copyright__ = f\"2014 {__author__}\"\n", "packaging/_structures.py": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\nimport typing\n\n\n@typing.final\nclass InfinityType:\n    __slots__ = ()\n\n    def __repr__(self) -> str:\n        return \"Infinity\"\n\n    def __hash__(self) -> int:\n        return hash(repr(self))\n\n    def __lt__(self, other: object) -> bool:\n        return False\n\n    def __le__(self, other: object) -> bool:\n        return False\n\n    def __eq__(self, other: object) -> bool:\n        return isinstance(other, self.__class__)\n\n    def __gt__(self, other: object) -> bool:\n        return True\n\n    def __ge__(self, other: object) -> bool:\n        return True\n\n    def __neg__(self: object) -> \"NegativeInfinityType\":\n        return NegativeInfinity\n\n\nInfinity = InfinityType()\n\n\n@typing.final\nclass NegativeInfinityType:\n    __slots__ = ()\n\n    def __repr__(self) -> str:\n        return \"-Infinity\"\n\n    def __hash__(self) -> int:\n        return hash(repr(self))\n\n    def __lt__(self, other: object) -> bool:\n        return True\n\n    def __le__(self, other: object) -> bool:\n        return True\n\n    def __eq__(self, other: object) -> bool:\n        return isinstance(other, self.__class__)\n\n    def __gt__(self, other: object) -> bool:\n        return False\n\n    def __ge__(self, other: object) -> bool:\n        return False\n\n    def __neg__(self: object) -> InfinityType:\n        return Infinity\n\n\nNegativeInfinity = NegativeInfinityType()\n", "packaging/version.py": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\"\"\"\n.. testsetup::\n\n    from packaging.version import parse, normalize_pre, Version\n\"\"\"\n\nfrom __future__ import annotations\n\nimport re\nimport sys\nimport typing\nfrom typing import (\n    Any,\n    Callable,\n    Literal,\n    NamedTuple,\n    SupportsInt,\n    Tuple,\n    TypedDict,\n    Union,\n)\n\nfrom ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType\n\nif typing.TYPE_CHECKING:\n    from typing_extensions import Self, Unpack\n\nif sys.version_info >= (3, 13):  # pragma: no cover\n    from warnings import deprecated as _deprecated\nelif typing.TYPE_CHECKING:\n    from typing_extensions import deprecated as _deprecated\nelse:  # pragma: no cover\n    import functools\n    import warnings\n\n    def _deprecated(message: str) -> object:\n        def decorator(func: Callable[[...], object]) -> object:\n            @functools.wraps(func)\n            def wrapper(*args: object, **kwargs: object) -> object:\n                warnings.warn(\n                    message,\n                    category=DeprecationWarning,\n                    stacklevel=2,\n                )\n                return func(*args, **kwargs)\n\n            return wrapper\n\n        return decorator\n\n\n_LETTER_NORMALIZATION = {\n    \"alpha\": \"a\",\n    \"beta\": \"b\",\n    \"c\": \"rc\",\n    \"pre\": \"rc\",\n    \"preview\": \"rc\",\n    \"rev\": \"post\",\n    \"r\": \"post\",\n}\n\n__all__ = [\"VERSION_PATTERN\", \"InvalidVersion\", \"Version\", \"normalize_pre\", \"parse\"]\n\n\ndef __dir__() -> list[str]:\n    return __all__\n\n\nLocalType = Tuple[Union[int, str], ...]\n\nCmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]\nCmpLocalType = Union[\n    NegativeInfinityType,\n    Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],\n]\nCmpKey = Tuple[\n    int,\n    Tuple[int, ...],\n    CmpPrePostDevType,\n    CmpPrePostDevType,\n    CmpPrePostDevType,\n    CmpLocalType,\n]\nVersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]\n\n\nclass _VersionReplace(TypedDict, total=False):\n    epoch: int | None\n    release: tuple[int, ...] | None\n    pre: tuple[str, int] | None\n    post: int | None\n    dev: int | None\n    local: str | None\n\n\ndef normalize_pre(letter: str, /) -> str:\n    \"\"\"Normalize the pre-release segment of a version string.\n\n    Returns a lowercase version of the string if not a known pre-release\n    identifier.\n\n    >>> normalize_pre('alpha')\n    'a'\n    >>> normalize_pre('BETA')\n    'b'\n    >>> normalize_pre('rc')\n    'rc'\n    \"\"\"\n    letter = letter.lower()\n    return _LETTER_NORMALIZATION.get(letter, letter)\n\n\ndef parse(version: str) -> Version:\n    \"\"\"Parse the given version string.\n\n    >>> parse('1.0.dev1')\n    <Version('1.0.dev1')>\n\n    :param version: The version string to parse.\n    :raises InvalidVersion: When the version string is not a valid version.\n    \"\"\"\n    return Version(version)\n\n\nclass InvalidVersion(ValueError):\n    \"\"\"Raised when a version string is not a valid version.\n\n    >>> Version(\"invalid\")\n    Traceback (most recent call last):\n        ...\n    packaging.version.InvalidVersion: Invalid version: 'invalid'\n    \"\"\"\n\n\nclass _BaseVersion:\n    __slots__ = ()\n\n    # This can also be a normal member (see the packaging_legacy package);\n    # we are just requiring it to be readable. Actually defining a property\n    # has runtime effect on subclasses, so it's typing only.\n    if typing.TYPE_CHECKING:\n\n        @property\n        def _key(self) -> tuple[Any, ...]: ...\n\n    def __hash__(self) -> int:\n        return hash(self._key)\n\n    # Please keep the duplicated `isinstance` check\n    # in the six comparisons hereunder\n    # unless you find a way to avoid adding overhead function calls.\n    def __lt__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key < other._key\n\n    def __le__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key <= other._key\n\n    def __eq__(self, other: object) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key == other._key\n\n    def __ge__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key >= other._key\n\n    def __gt__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key > other._key\n\n    def __ne__(self, other: object) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key != other._key\n\n\n# Deliberately not anchored to the start and end of the string, to make it\n# easier for 3rd party code to reuse\n\n# Note that ++ doesn't behave identically on CPython and PyPy, so not using it here\n_VERSION_PATTERN = r\"\"\"\n    v?+                                                   # optional leading v\n    (?:\n        (?:(?P<epoch>[0-9]+)!)?+                          # epoch\n        (?P<release>[0-9]+(?:\\.[0-9]+)*+)                 # release segment\n        (?P<pre>                                          # pre-release\n            [._-]?+\n            (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)\n            [._-]?+\n            (?P<pre_n>[0-9]+)?\n        )?+\n        (?P<post>                                         # post release\n            (?:-(?P<post_n1>[0-9]+))\n            |\n            (?:\n                [._-]?\n                (?P<post_l>post|rev|r)\n                [._-]?\n                (?P<post_n2>[0-9]+)?\n            )\n        )?+\n        (?P<dev>                                          # dev release\n            [._-]?+\n            (?P<dev_l>dev)\n            [._-]?+\n            (?P<dev_n>[0-9]+)?\n        )?+\n    )\n    (?:\\+\n        (?P<local>                                        # local version\n            [a-z0-9]+\n            (?:[._-][a-z0-9]+)*+\n        )\n    )?+\n\"\"\"\n\n_VERSION_PATTERN_OLD = _VERSION_PATTERN.replace(\"*+\", \"*\").replace(\"?+\", \"?\")\n\n# Possessive qualifiers were added in Python 3.11.\n# CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795\n# Older PyPy also had a bug.\nVERSION_PATTERN = (\n    _VERSION_PATTERN_OLD\n    if (sys.implementation.name == \"cpython\" and sys.version_info < (3, 11, 5))\n    or (sys.implementation.name == \"pypy\" and sys.version_info < (3, 11, 13))\n    or sys.version_info < (3, 11)\n    else _VERSION_PATTERN\n)\n\"\"\"\nA string containing the regular expression used to match a valid version.\n\nThe pattern is not anchored at either end, and is intended for embedding in larger\nexpressions (for example, matching a version number as part of a file name). The\nregular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``\nflags set.\n\n:meta hide-value:\n\"\"\"\n\n\n# Validation pattern for local version in replace()\n_LOCAL_PATTERN = re.compile(r\"[a-z0-9]+(?:[._-][a-z0-9]+)*\", re.IGNORECASE)\n\n# Fast path: If a version has only digits and dots then we\n# can skip the regex and parse it as a release segment\n_SIMPLE_VERSION_INDICATORS = frozenset(\".0123456789\")\n\n\ndef _validate_epoch(value: object, /) -> int:\n    epoch = value or 0\n    if isinstance(epoch, int) and epoch >= 0:\n        return epoch\n    msg = f\"epoch must be non-negative integer, got {epoch}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_release(value: object, /) -> tuple[int, ...]:\n    release = (0,) if value is None else value\n    if (\n        isinstance(release, tuple)\n        and len(release) > 0\n        and all(isinstance(i, int) and i >= 0 for i in release)\n    ):\n        return release\n    msg = f\"release must be a non-empty tuple of non-negative integers, got {release}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_pre(value: object, /) -> tuple[Literal[\"a\", \"b\", \"rc\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, tuple) and len(value) == 2:\n        letter, number = value\n        letter = normalize_pre(letter)\n        if letter in {\"a\", \"b\", \"rc\"} and isinstance(number, int) and number >= 0:\n            # type checkers can't infer the Literal type here on letter\n            return (letter, number)  # type: ignore[return-value]\n    msg = f\"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_post(value: object, /) -> tuple[Literal[\"post\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, int) and value >= 0:\n        return (\"post\", value)\n    msg = f\"post must be non-negative integer, got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_dev(value: object, /) -> tuple[Literal[\"dev\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, int) and value >= 0:\n        return (\"dev\", value)\n    msg = f\"dev must be non-negative integer, got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_local(value: object, /) -> LocalType | None:\n    if value is None:\n        return value\n    if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):\n        return _parse_local_version(value)\n    msg = f\"local must be a valid version string, got {value!r}\"\n    raise InvalidVersion(msg)\n\n\n# Backward compatibility for internals before 26.0. Do not use.\nclass _Version(NamedTuple):\n    epoch: int\n    release: tuple[int, ...]\n    dev: tuple[Literal[\"dev\"], int] | None\n    pre: tuple[Literal[\"a\", \"b\", \"rc\"], int] | None\n    post: tuple[Literal[\"post\"], int] | None\n    local: LocalType | None\n\n\nclass Version(_BaseVersion):\n    \"\"\"This class abstracts handling of a project's versions.\n\n    A :class:`Version` instance is comparison aware and can be compared and\n    sorted using the standard Python interfaces.\n\n    >>> v1 = Version(\"1.0a5\")\n    >>> v2 = Version(\"1.0\")\n    >>> v1\n    <Version('1.0a5')>\n    >>> v2\n    <Version('1.0')>\n    >>> v1 < v2\n    True\n    >>> v1 == v2\n    False\n    >>> v1 > v2\n    False\n    >>> v1 >= v2\n    False\n    >>> v1 <= v2\n    True\n    \"\"\"\n\n    __slots__ = (\"_dev\", \"_epoch\", \"_key_cache\", \"_local\", \"_post\", \"_pre\", \"_release\")\n    __match_args__ = (\"_str\",)\n\n    _regex = re.compile(r\"\\s*\" + VERSION_PATTERN + r\"\\s*\", re.VERBOSE | re.IGNORECASE)\n\n    _epoch: int\n    _release: tuple[int, ...]\n    _dev: tuple[Literal[\"dev\"], int] | None\n    _pre: tuple[Literal[\"a\", \"b\", \"rc\"], int] | None\n    _post: tuple[Literal[\"post\"], int] | None\n    _local: LocalType | None\n\n    _key_cache: CmpKey | None\n\n    def __init__(self, version: str) -> None:\n        \"\"\"Initialize a Version object.\n\n        :param version:\n            The string representation of a version which will be parsed and normalized\n            before use.\n        :raises InvalidVersion:\n            If the ``version`` does not conform to PEP 440 in any way then this\n            exception will be raised.\n        \"\"\"\n        if _SIMPLE_VERSION_INDICATORS.issuperset(version):\n            try:\n                self._release = tuple(map(int, version.split(\".\")))\n            except ValueError:\n                raise InvalidVersion(f\"Invalid version: {version!r}\") from None\n\n            self._epoch = 0\n            self._pre = None\n            self._post = None\n            self._dev = None\n            self._local = None\n            self._key_cache = None\n            return\n\n        # Validate the version and parse it into pieces\n        match = self._regex.fullmatch(version)\n        if not match:\n            raise InvalidVersion(f\"Invalid version: {version!r}\")\n        self._epoch = int(match.group(\"epoch\")) if match.group(\"epoch\") else 0\n        self._release = tuple(map(int, match.group(\"release\").split(\".\")))\n        # We can type ignore the assignments below because the regex guarantees\n        # the correct strings\n        self._pre = _parse_letter_version(match.group(\"pre_l\"), match.group(\"pre_n\"))  # type: ignore[assignment]\n        self._post = _parse_letter_version(  # type: ignore[assignment]\n            match.group(\"post_l\"), match.group(\"post_n1\") or match.group(\"post_n2\")\n        )\n        self._dev = _parse_letter_version(match.group(\"dev_l\"), match.group(\"dev_n\"))  # type: ignore[assignment]\n        self._local = _parse_local_version(match.group(\"local\"))\n\n        # Key which will be used for sorting\n        self._key_cache = None\n\n    @classmethod\n    def from_parts(\n        cls,\n        *,\n        epoch: int = 0,\n        release: tuple[int, ...],\n        pre: tuple[str, int] | None = None,\n        post: int | None = None,\n        dev: int | None = None,\n        local: str | None = None,\n    ) -> Self:\n        _epoch = _validate_epoch(epoch)\n        _release = _validate_release(release)\n        _pre = _validate_pre(pre) if pre is not None else None\n        _post = _validate_post(post) if post is not None else None\n        _dev = _validate_dev(dev) if dev is not None else None\n        _local = _validate_local(local) if local is not None else None\n\n        new_version = cls.__new__(cls)\n        new_version._key_cache = None\n        new_version._epoch = _epoch\n        new_version._release = _release\n        new_version._pre = _pre\n        new_version._post = _post\n        new_version._dev = _dev\n        new_version._local = _local\n\n        return new_version\n\n    def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:\n        epoch = _validate_epoch(kwargs[\"epoch\"]) if \"epoch\" in kwargs else self._epoch\n        release = (\n            _validate_release(kwargs[\"release\"])\n            if \"release\" in kwargs\n            else self._release\n        )\n        pre = _validate_pre(kwargs[\"pre\"]) if \"pre\" in kwargs else self._pre\n        post = _validate_post(kwargs[\"post\"]) if \"post\" in kwargs else self._post\n        dev = _validate_dev(kwargs[\"dev\"]) if \"dev\" in kwargs else self._dev\n        local = _validate_local(kwargs[\"local\"]) if \"local\" in kwargs else self._local\n\n        if (\n            epoch == self._epoch\n            and release == self._release\n            and pre == self._pre\n            and post == self._post\n            and dev == self._dev\n            and local == self._local\n        ):\n            return self\n\n        new_version = self.__class__.__new__(self.__class__)\n        new_version._key_cache = None\n        new_version._epoch = epoch\n        new_version._release = release\n        new_version._pre = pre\n        new_version._post = post\n        new_version._dev = dev\n        new_version._local = local\n\n        return new_version\n\n    @property\n    def _key(self) -> CmpKey:\n        if self._key_cache is None:\n            self._key_cache = _cmpkey(\n                self._epoch,\n                self._release,\n                self._pre,\n                self._post,\n                self._dev,\n                self._local,\n            )\n        return self._key_cache\n\n    @property\n    @_deprecated(\"Version._version is private and will be removed soon\")\n    def _version(self) -> _Version:\n        return _Version(\n            self._epoch, self._release, self._dev, self._pre, self._post, self._local\n        )\n\n    @_version.setter\n    @_deprecated(\"Version._version is private and will be removed soon\")\n    def _version(self, value: _Version) -> None:\n        self._epoch = value.epoch\n        self._release = value.release\n        self._dev = value.dev\n        self._pre = value.pre\n        self._post = value.post\n        self._local = value.local\n        self._key_cache = None\n\n    def __repr__(self) -> str:\n        \"\"\"A representation of the Version that shows all internal state.\n\n        >>> Version('1.0.0')\n        <Version('1.0.0')>\n        \"\"\"\n        return f\"<Version('{self}')>\"\n\n    def __str__(self) -> str:\n        \"\"\"A string representation of the version that can be round-tripped.\n\n        >>> str(Version(\"1.0a5\"))\n        '1.0a5'\n        \"\"\"\n        # This is a hot function, so not calling self.base_version\n        version = \".\".join(map(str, self.release))\n\n        # Epoch\n        if self.epoch:\n            version = f\"{self.epoch}!{version}\"\n\n        # Pre-release\n        if self.pre is not None:\n            version += \"\".join(map(str, self.pre))\n\n        # Post-release\n        if self.post is not None:\n            version += f\".post{self.post}\"\n\n        # Development release\n        if self.dev is not None:\n            version += f\".dev{self.dev}\"\n\n        # Local version segment\n        if self.local is not None:\n            version += f\"+{self.local}\"\n\n        return version\n\n    @property\n    def _str(self) -> str:\n        \"\"\"Internal property for match_args\"\"\"\n        return str(self)\n\n    @property\n    def epoch(self) -> int:\n        \"\"\"The epoch of the version.\n\n        >>> Version(\"2.0.0\").epoch\n        0\n        >>> Version(\"1!2.0.0\").epoch\n        1\n        \"\"\"\n        return self._epoch\n\n    @property\n    def release(self) -> tuple[int, ...]:\n        \"\"\"The components of the \"release\" segment of the version.\n\n        >>> Version(\"1.2.3\").release\n        (1, 2, 3)\n        >>> Version(\"2.0.0\").release\n        (2, 0, 0)\n        >>> Version(\"1!2.0.0.post0\").release\n        (2, 0, 0)\n\n        Includes trailing zeroes but not the epoch or any pre-release / development /\n        post-release suffixes.\n        \"\"\"\n        return self._release\n\n    @property\n    def pre(self) -> tuple[Literal[\"a\", \"b\", \"rc\"], int] | None:\n        \"\"\"The pre-release segment of the version.\n\n        >>> print(Version(\"1.2.3\").pre)\n        None\n        >>> Version(\"1.2.3a1\").pre\n        ('a', 1)\n        >>> Version(\"1.2.3b1\").pre\n        ('b', 1)\n        >>> Version(\"1.2.3rc1\").pre\n        ('rc', 1)\n        \"\"\"\n        return self._pre\n\n    @property\n    def post(self) -> int | None:\n        \"\"\"The post-release number of the version.\n\n        >>> print(Version(\"1.2.3\").post)\n        None\n        >>> Version(\"1.2.3.post1\").post\n        1\n        \"\"\"\n        return self._post[1] if self._post else None\n\n    @property\n    def dev(self) -> int | None:\n        \"\"\"The development number of the version.\n\n        >>> print(Version(\"1.2.3\").dev)\n        None\n        >>> Version(\"1.2.3.dev1\").dev\n        1\n        \"\"\"\n        return self._dev[1] if self._dev else None\n\n    @property\n    def local(self) -> str | None:\n        \"\"\"The local version segment of the version.\n\n        >>> print(Version(\"1.2.3\").local)\n        None\n        >>> Version(\"1.2.3+abc\").local\n        'abc'\n        \"\"\"\n        if self._local:\n            return \".\".join(str(x) for x in self._local)\n        else:\n            return None\n\n    @property\n    def public(self) -> str:\n        \"\"\"The public portion of the version.\n\n        >>> Version(\"1.2.3\").public\n        '1.2.3'\n        >>> Version(\"1.2.3+abc\").public\n        '1.2.3'\n        >>> Version(\"1!1.2.3dev1+abc\").public\n        '1!1.2.3.dev1'\n        \"\"\"\n        return str(self).split(\"+\", 1)[0]\n\n    @property\n    def base_version(self) -> str:\n        \"\"\"The \"base version\" of the version.\n\n        >>> Version(\"1.2.3\").base_version\n        '1.2.3'\n        >>> Version(\"1.2.3+abc\").base_version\n        '1.2.3'\n        >>> Version(\"1!1.2.3dev1+abc\").base_version\n        '1!1.2.3'\n\n        The \"base version\" is the public version of the project without any pre or post\n        release markers.\n        \"\"\"\n        release_segment = \".\".join(map(str, self.release))\n        return f\"{self.epoch}!{release_segment}\" if self.epoch else release_segment\n\n    @property\n    def is_prerelease(self) -> bool:\n        \"\"\"Whether this version is a pre-release.\n\n        >>> Version(\"1.2.3\").is_prerelease\n        False\n        >>> Version(\"1.2.3a1\").is_prerelease\n        True\n        >>> Version(\"1.2.3b1\").is_prerelease\n        True\n        >>> Version(\"1.2.3rc1\").is_prerelease\n        True\n        >>> Version(\"1.2.3dev1\").is_prerelease\n        True\n        \"\"\"\n        return self.dev is not None or self.pre is not None\n\n    @property\n    def is_postrelease(self) -> bool:\n        \"\"\"Whether this version is a post-release.\n\n        >>> Version(\"1.2.3\").is_postrelease\n        False\n        >>> Version(\"1.2.3.post1\").is_postrelease\n        True\n        \"\"\"\n        return self.post is not None\n\n    @property\n    def is_devrelease(self) -> bool:\n        \"\"\"Whether this version is a development release.\n\n        >>> Version(\"1.2.3\").is_devrelease\n        False\n        >>> Version(\"1.2.3.dev1\").is_devrelease\n        True\n        \"\"\"\n        return self.dev is not None\n\n    @property\n    def major(self) -> int:\n        \"\"\"The first item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").major\n        1\n        \"\"\"\n        return self.release[0] if len(self.release) >= 1 else 0\n\n    @property\n    def minor(self) -> int:\n        \"\"\"The second item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").minor\n        2\n        >>> Version(\"1\").minor\n        0\n        \"\"\"\n        return self.release[1] if len(self.release) >= 2 else 0\n\n    @property\n    def micro(self) -> int:\n        \"\"\"The third item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").micro\n        3\n        >>> Version(\"1\").micro\n        0\n        \"\"\"\n        return self.release[2] if len(self.release) >= 3 else 0\n\n\nclass _TrimmedRelease(Version):\n    __slots__ = ()\n\n    def __init__(self, version: str | Version) -> None:\n        if isinstance(version, Version):\n            self._epoch = version._epoch\n            self._release = version._release\n            self._dev = version._dev\n            self._pre = version._pre\n            self._post = version._post\n            self._local = version._local\n            self._key_cache = version._key_cache\n            return\n        super().__init__(version)  # pragma: no cover\n\n    @property\n    def release(self) -> tuple[int, ...]:\n        \"\"\"\n        Release segment without any trailing zeros.\n\n        >>> _TrimmedRelease('1.0.0').release\n        (1,)\n        >>> _TrimmedRelease('0.0').release\n        (0,)\n        \"\"\"\n        # This leaves one 0.\n        rel = super().release\n        len_release = len(rel)\n        i = len_release\n        while i > 1 and rel[i - 1] == 0:\n            i -= 1\n        return rel if i == len_release else rel[:i]\n\n\ndef _parse_letter_version(\n    letter: str | None, number: str | bytes | SupportsInt | None\n) -> tuple[str, int] | None:\n    if letter:\n        # We normalize any letters to their lower case form\n        letter = letter.lower()\n\n        # We consider some words to be alternate spellings of other words and\n        # in those cases we want to normalize the spellings to our preferred\n        # spelling.\n        letter = _LETTER_NORMALIZATION.get(letter, letter)\n\n        # We consider there to be an implicit 0 in a pre-release if there is\n        # not a numeral associated with it.\n        return letter, int(number or 0)\n\n    if number:\n        # We assume if we are given a number, but we are not given a letter\n        # then this is using the implicit post release syntax (e.g. 1.0-1)\n        return \"post\", int(number)\n\n    return None\n\n\n_local_version_separators = re.compile(r\"[\\._-]\")\n\n\ndef _parse_local_version(local: str | None) -> LocalType | None:\n    \"\"\"\n    Takes a string like abc.1.twelve and turns it into (\"abc\", 1, \"twelve\").\n    \"\"\"\n    if local is not None:\n        return tuple(\n            part.lower() if not part.isdigit() else int(part)\n            for part in _local_version_separators.split(local)\n        )\n    return None\n\n\ndef _cmpkey(\n    epoch: int,\n    release: tuple[int, ...],\n    pre: tuple[str, int] | None,\n    post: tuple[str, int] | None,\n    dev: tuple[str, int] | None,\n    local: LocalType | None,\n) -> CmpKey:\n    # When we compare a release version, we want to compare it with all of the\n    # trailing zeros removed. We will use this for our sorting key.\n    len_release = len(release)\n    i = len_release\n    while i and release[i - 1] == 0:\n        i -= 1\n    _release = release if i == len_release else release[:i]\n\n    # We need to \"trick\" the sorting algorithm to put 1.0.dev0 before 1.0a0.\n    # We'll do this by abusing the pre segment, but we _only_ want to do this\n    # if there is not a pre or a post segment. If we have one of those then\n    # the normal sorting rules will handle this case correctly.\n    if pre is None and post is None and dev is not None:\n        _pre: CmpPrePostDevType = NegativeInfinity\n    # Versions without a pre-release (except as noted above) should sort after\n    # those with one.\n    elif pre is None:\n        _pre = Infinity\n    else:\n        _pre = pre\n\n    # Versions without a post segment should sort before those with one.\n    if post is None:\n        _post: CmpPrePostDevType = NegativeInfinity\n\n    else:\n        _post = post\n\n    # Versions without a development segment should sort after those with one.\n    if dev is None:\n        _dev: CmpPrePostDevType = Infinity\n\n    else:\n        _dev = dev\n\n    if local is None:\n        # Versions without a local segment should sort before those with one.\n        _local: CmpLocalType = NegativeInfinity\n    else:\n        # Versions with a local segment need that segment parsed to implement\n        # the sorting rules in PEP440.\n        # - Alpha numeric segments sort before numeric segments\n        # - Alpha numeric segments sort lexicographically\n        # - Numeric segments sort numerically\n        # - Shorter versions sort before longer versions when the prefixes\n        #   match exactly\n        _local = tuple(\n            (i, \"\") if isinstance(i, int) else (NegativeInfinity, i) for i in local\n        )\n\n    return epoch, _release, _pre, _post, _dev, _local\n"}}, {"key": "fixed", "label": "After #1155", "commit": "06c6555f44f0f7b84459c876375c5b1d52ee7fef", "note": "int-digit-limit fix", "files": {"packaging/__init__.py": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\n__title__ = \"packaging\"\n__summary__ = \"Core utilities for Python packages\"\n__uri__ = \"https://github.com/pypa/packaging\"\n\n__version__ = \"26.1.dev3\"\n\n__author__ = \"Donald Stufft and individual contributors\"\n__email__ = \"donald@stufft.io\"\n\n__license__ = \"BSD-2-Clause or Apache-2.0\"\n__copyright__ = f\"2014 {__author__}\"\n", "packaging/version.py": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\"\"\"\n.. testsetup::\n\n    from packaging.version import parse, normalize_pre, Version, _cmpkey\n\"\"\"\n\nfrom __future__ import annotations\n\nimport re\nimport sys\nimport typing\nfrom typing import (\n    Any,\n    Callable,\n    Literal,\n    NamedTuple,\n    SupportsInt,\n    Tuple,\n    TypedDict,\n    Union,\n)\n\nif typing.TYPE_CHECKING:\n    from typing_extensions import Self, Unpack\n\nif sys.version_info >= (3, 13):  # pragma: no cover\n    from warnings import deprecated as _deprecated\nelif typing.TYPE_CHECKING:\n    from typing_extensions import deprecated as _deprecated\nelse:  # pragma: no cover\n    import functools\n    import warnings\n\n    def _deprecated(message: str) -> object:\n        def decorator(func: Callable[[...], object]) -> object:\n            @functools.wraps(func)\n            def wrapper(*args: object, **kwargs: object) -> object:\n                warnings.warn(\n                    message,\n                    category=DeprecationWarning,\n                    stacklevel=2,\n                )\n                return func(*args, **kwargs)\n\n            return wrapper\n\n        return decorator\n\n\n_LETTER_NORMALIZATION = {\n    \"alpha\": \"a\",\n    \"beta\": \"b\",\n    \"c\": \"rc\",\n    \"pre\": \"rc\",\n    \"preview\": \"rc\",\n    \"rev\": \"post\",\n    \"r\": \"post\",\n}\n\n__all__ = [\"VERSION_PATTERN\", \"InvalidVersion\", \"Version\", \"normalize_pre\", \"parse\"]\n\n\ndef __dir__() -> list[str]:\n    return __all__\n\n\nLocalType = Tuple[Union[int, str], ...]\n\nCmpLocalType = Tuple[Tuple[int, str], ...]\nCmpSuffix = Tuple[int, int, int, int, int, int]\nCmpKey = Union[\n    Tuple[int, Tuple[int, ...], CmpSuffix],\n    Tuple[int, Tuple[int, ...], CmpSuffix, CmpLocalType],\n]\nVersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]\n\n\nclass _VersionReplace(TypedDict, total=False):\n    epoch: int | None\n    release: tuple[int, ...] | None\n    pre: tuple[str, int] | None\n    post: int | None\n    dev: int | None\n    local: str | None\n\n\ndef normalize_pre(letter: str, /) -> str:\n    \"\"\"Normalize the pre-release segment of a version string.\n\n    Returns a lowercase version of the string if not a known pre-release\n    identifier.\n\n    >>> normalize_pre('alpha')\n    'a'\n    >>> normalize_pre('BETA')\n    'b'\n    >>> normalize_pre('rc')\n    'rc'\n\n    :param letter:\n\n    .. versionadded:: 26.1\n    \"\"\"\n    letter = letter.lower()\n    return _LETTER_NORMALIZATION.get(letter, letter)\n\n\ndef parse(version: str) -> Version:\n    \"\"\"Parse the given version string.\n\n    This is identical to the :class:`Version` constructor.\n\n    >>> parse('1.0.dev1')\n    <Version('1.0.dev1')>\n\n    :param version: The version string to parse.\n    :raises InvalidVersion: When the version string is not a valid version.\n    \"\"\"\n    return Version(version)\n\n\nclass InvalidVersion(ValueError):\n    \"\"\"Raised when a version string is not a valid version.\n\n    >>> Version(\"invalid\")\n    Traceback (most recent call last):\n        ...\n    packaging.version.InvalidVersion: Invalid version: 'invalid'\n    \"\"\"\n\n\nclass _BaseVersion:\n    __slots__ = ()\n\n    # This can also be a normal member (see the packaging_legacy package);\n    # we are just requiring it to be readable. Actually defining a property\n    # has runtime effect on subclasses, so it's typing only.\n    if typing.TYPE_CHECKING:\n\n        @property\n        def _key(self) -> tuple[Any, ...]: ...\n\n    def __hash__(self) -> int:\n        return hash(self._key)\n\n    # Please keep the duplicated `isinstance` check\n    # in the six comparisons hereunder\n    # unless you find a way to avoid adding overhead function calls.\n    def __lt__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key < other._key\n\n    def __le__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key <= other._key\n\n    def __eq__(self, other: object) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key == other._key\n\n    def __ge__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key >= other._key\n\n    def __gt__(self, other: _BaseVersion) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key > other._key\n\n    def __ne__(self, other: object) -> bool:\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return self._key != other._key\n\n\n# Deliberately not anchored to the start and end of the string, to make it\n# easier for 3rd party code to reuse\n\n# Note that ++ doesn't behave identically on CPython and PyPy, so not using it here\n_VERSION_PATTERN = r\"\"\"\n    v?+                                                   # optional leading v\n    (?a:\n        (?:(?P<epoch>[0-9]+)!)?+                          # epoch\n        (?P<release>[0-9]+(?:\\.[0-9]+)*+)                 # release segment\n        (?P<pre>                                          # pre-release\n            [._-]?+\n            (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)\n            [._-]?+\n            (?P<pre_n>[0-9]+)?\n        )?+\n        (?P<post>                                         # post release\n            (?:-(?P<post_n1>[0-9]+))\n            |\n            (?:\n                [._-]?\n                (?P<post_l>post|rev|r)\n                [._-]?\n                (?P<post_n2>[0-9]+)?\n            )\n        )?+\n        (?P<dev>                                          # dev release\n            [._-]?+\n            (?P<dev_l>dev)\n            [._-]?+\n            (?P<dev_n>[0-9]+)?\n        )?+\n    )\n    (?a:\\+\n        (?P<local>                                        # local version\n            [a-z0-9]+\n            (?:[._-][a-z0-9]+)*+\n        )\n    )?+\n\"\"\"\n\n_VERSION_PATTERN_OLD = _VERSION_PATTERN.replace(\"*+\", \"*\").replace(\"?+\", \"?\")\n\n# Possessive qualifiers were added in Python 3.11.\n# CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795\n# Older PyPy also had a bug.\nVERSION_PATTERN = (\n    _VERSION_PATTERN_OLD\n    if (sys.implementation.name == \"cpython\" and sys.version_info < (3, 11, 5))\n    or (sys.implementation.name == \"pypy\" and sys.version_info < (3, 11, 13))\n    or sys.version_info < (3, 11)\n    else _VERSION_PATTERN\n)\n\"\"\"\nA string containing the regular expression used to match a valid version.\n\nThe pattern is not anchored at either end, and is intended for embedding in larger\nexpressions (for example, matching a version number as part of a file name). The\nregular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``\nflags set.\n\n.. versionchanged:: 26.0\n\n   The regex now uses possessive qualifiers on Python 3.11 if they are\n   supported (CPython 3.11.5+, PyPy 3.11.13+).\n\n:meta hide-value:\n\"\"\"\n\n\n# Validation pattern for local version in replace()\n_LOCAL_PATTERN = re.compile(r\"[a-z0-9]+(?:[._-][a-z0-9]+)*\", re.IGNORECASE | re.ASCII)\n\n# Fast path: If a version has only digits and dots then we\n# can skip the regex and parse it as a release segment\n_SIMPLE_VERSION_INDICATORS = frozenset(\".0123456789\")\n\n\ndef _validate_epoch(value: object, /) -> int:\n    epoch = value or 0\n    if isinstance(epoch, int) and epoch >= 0:\n        return epoch\n    msg = f\"epoch must be non-negative integer, got {epoch}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_release(value: object, /) -> tuple[int, ...]:\n    release = (0,) if value is None else value\n    if (\n        isinstance(release, tuple)\n        and len(release) > 0\n        and all(isinstance(i, int) and i >= 0 for i in release)\n    ):\n        return release\n    msg = f\"release must be a non-empty tuple of non-negative integers, got {release}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_pre(value: object, /) -> tuple[Literal[\"a\", \"b\", \"rc\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, tuple) and len(value) == 2:\n        letter, number = value\n        letter = normalize_pre(letter)\n        if letter in {\"a\", \"b\", \"rc\"} and isinstance(number, int) and number >= 0:\n            # type checkers can't infer the Literal type here on letter\n            return (letter, number)  # type: ignore[return-value]\n    msg = f\"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_post(value: object, /) -> tuple[Literal[\"post\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, int) and value >= 0:\n        return (\"post\", value)\n    msg = f\"post must be non-negative integer, got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_dev(value: object, /) -> tuple[Literal[\"dev\"], int] | None:\n    if value is None:\n        return value\n    if isinstance(value, int) and value >= 0:\n        return (\"dev\", value)\n    msg = f\"dev must be non-negative integer, got {value}\"\n    raise InvalidVersion(msg)\n\n\ndef _validate_local(value: object, /) -> LocalType | None:\n    if value is None:\n        return value\n    if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):\n        return _parse_local_version(value)\n    msg = f\"local must be a valid version string, got {value!r}\"\n    raise InvalidVersion(msg)\n\n\n# Backward compatibility for internals before 26.0. Do not use.\nclass _Version(NamedTuple):\n    epoch: int\n    release: tuple[int, ...]\n    dev: tuple[Literal[\"dev\"], int] | None\n    pre: tuple[Literal[\"a\", \"b\", \"rc\"], int] | None\n    post: tuple[Literal[\"post\"], int] | None\n    local: LocalType | None\n\n\nclass Version(_BaseVersion):\n    \"\"\"This class abstracts handling of a project's versions.\n\n    A :class:`Version` instance is comparison aware and can be compared and\n    sorted using the standard Python interfaces.\n\n    >>> v1 = Version(\"1.0a5\")\n    >>> v2 = Version(\"1.0\")\n    >>> v1\n    <Version('1.0a5')>\n    >>> v2\n    <Version('1.0')>\n    >>> v1 < v2\n    True\n    >>> v1 == v2\n    False\n    >>> v1 > v2\n    False\n    >>> v1 >= v2\n    False\n    >>> v1 <= v2\n    True\n\n    :class:`Version` is immutable; use :meth:`__replace__` to change\n    part of a version.\n    \"\"\"\n\n    __slots__ = (\n        \"_dev\",\n        \"_epoch\",\n        \"_hash_cache\",\n        \"_key_cache\",\n        \"_local\",\n        \"_post\",\n        \"_pre\",\n        \"_release\",\n    )\n    __match_args__ = (\"_str\",)\n    \"\"\"\n    Pattern matching is supported on Python 3.10+.\n\n    .. versionadded:: 26.0\n\n    :meta hide-value:\n    \"\"\"\n\n    _regex = re.compile(r\"\\s*\" + VERSION_PATTERN + r\"\\s*\", re.VERBOSE | re.IGNORECASE)\n\n    _epoch: int\n    _release: tuple[int, ...]\n    _dev: tuple[Literal[\"dev\"], int] | None\n    _pre: tuple[Literal[\"a\", \"b\", \"rc\"], int] | None\n    _post: tuple[Literal[\"post\"], int] | None\n    _local: LocalType | None\n\n    _hash_cache: int | None\n    _key_cache: CmpKey | None\n\n    def __init__(self, version: str) -> None:\n        \"\"\"Initialize a Version object.\n\n        :param version:\n            The string representation of a version which will be parsed and normalized\n            before use.\n        :raises InvalidVersion:\n            If the ``version`` does not conform to PEP 440 in any way then this\n            exception will be raised.\n        \"\"\"\n        if _SIMPLE_VERSION_INDICATORS.issuperset(version):\n            try:\n                self._release = tuple(map(int, version.split(\".\")))\n            except ValueError:\n                # Empty parts (from \"1..2\", \".1\", etc.) are invalid versions.\n                # Any other ValueError (e.g. int str-digits limit) should\n                # propagate to the caller.\n                if \"\" in version.split(\".\"):\n                    raise InvalidVersion(f\"Invalid version: {version!r}\") from None\n                # TODO: remove \"no cover\" when Python 3.9 is dropped.\n                raise  # pragma: no cover\n\n            self._epoch = 0\n            self._pre = None\n            self._post = None\n            self._dev = None\n            self._local = None\n            self._key_cache = None\n            self._hash_cache = None\n            return\n\n        # Validate the version and parse it into pieces\n        match = self._regex.fullmatch(version)\n        if not match:\n            raise InvalidVersion(f\"Invalid version: {version!r}\")\n        self._epoch = int(match.group(\"epoch\")) if match.group(\"epoch\") else 0\n        self._release = tuple(map(int, match.group(\"release\").split(\".\")))\n        # We can type ignore the assignments below because the regex guarantees\n        # the correct strings\n        self._pre = _parse_letter_version(match.group(\"pre_l\"), match.group(\"pre_n\"))  # type: ignore[assignment]\n        self._post = _parse_letter_version(  # type: ignore[assignment]\n            match.group(\"post_l\"), match.group(\"post_n1\") or match.group(\"post_n2\")\n        )\n        self._dev = _parse_letter_version(match.group(\"dev_l\"), match.group(\"dev_n\"))  # type: ignore[assignment]\n        self._local = _parse_local_version(match.group(\"local\"))\n\n        # Key which will be used for sorting\n        self._key_cache = None\n        self._hash_cache = None\n\n    @classmethod\n    def from_parts(\n        cls,\n        *,\n        epoch: int = 0,\n        release: tuple[int, ...],\n        pre: tuple[str, int] | None = None,\n        post: int | None = None,\n        dev: int | None = None,\n        local: str | None = None,\n    ) -> Self:\n        \"\"\"\n        Return a new version composed of the various parts.\n\n        This allows you to build a version without going though a string and\n        running a regular expression. It normalizes pre-release strings. The\n        ``release=`` keyword argument is required.\n\n        >>> Version.from_parts(release=(1,2,3))\n        <Version('1.2.3')>\n        >>> Version.from_parts(release=(0,1,0), pre=(\"b\", 1))\n        <Version('0.1.0b1')>\n\n        :param epoch:\n        :param release: This version tuple is required\n\n        .. versionadded:: 26.1\n        \"\"\"\n        _epoch = _validate_epoch(epoch)\n        _release = _validate_release(release)\n        _pre = _validate_pre(pre) if pre is not None else None\n        _post = _validate_post(post) if post is not None else None\n        _dev = _validate_dev(dev) if dev is not None else None\n        _local = _validate_local(local) if local is not None else None\n\n        new_version = cls.__new__(cls)\n        new_version._key_cache = None\n        new_version._hash_cache = None\n        new_version._epoch = _epoch\n        new_version._release = _release\n        new_version._pre = _pre\n        new_version._post = _post\n        new_version._dev = _dev\n        new_version._local = _local\n\n        return new_version\n\n    def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:\n        \"\"\"\n        __replace__(*, epoch=..., release=..., pre=..., post=..., dev=..., local=...)\n\n        Return a new version with parts replaced.\n\n        This returns a new version (unless no parts were changed). The\n        pre-release is normalized. Setting a value to ``None`` clears it.\n\n        >>> v = Version(\"1.2.3\")\n        >>> v.__replace__(pre=(\"a\", 1))\n        <Version('1.2.3a1')>\n\n        :param int | None epoch:\n        :param tuple[int, ...] | None release:\n        :param tuple[str, int] | None pre:\n        :param int | None post:\n        :param int | None dev:\n        :param str | None local:\n\n        .. versionadded:: 26.0\n        .. versionchanged:: 26.1\n\n           The pre-release portion is now normalized.\n        \"\"\"\n        epoch = _validate_epoch(kwargs[\"epoch\"]) if \"epoch\" in kwargs else self._epoch\n        release = (\n            _validate_release(kwargs[\"release\"])\n            if \"release\" in kwargs\n            else self._release\n        )\n        pre = _validate_pre(kwargs[\"pre\"]) if \"pre\" in kwargs else self._pre\n        post = _validate_post(kwargs[\"post\"]) if \"post\" in kwargs else self._post\n        dev = _validate_dev(kwargs[\"dev\"]) if \"dev\" in kwargs else self._dev\n        local = _validate_local(kwargs[\"local\"]) if \"local\" in kwargs else self._local\n\n        if (\n            epoch == self._epoch\n            and release == self._release\n            and pre == self._pre\n            and post == self._post\n            and dev == self._dev\n            and local == self._local\n        ):\n            return self\n\n        new_version = self.__class__.__new__(self.__class__)\n        new_version._key_cache = None\n        new_version._hash_cache = None\n        new_version._epoch = epoch\n        new_version._release = release\n        new_version._pre = pre\n        new_version._post = post\n        new_version._dev = dev\n        new_version._local = local\n\n        return new_version\n\n    @property\n    def _key(self) -> CmpKey:\n        if self._key_cache is None:\n            self._key_cache = _cmpkey(\n                self._epoch,\n                self._release,\n                self._pre,\n                self._post,\n                self._dev,\n                self._local,\n            )\n        return self._key_cache\n\n    # __hash__ must be defined when __eq__ is overridden,\n    # otherwise Python sets __hash__ to None.\n    def __hash__(self) -> int:\n        if (cached_hash := self._hash_cache) is not None:\n            return cached_hash\n\n        if (key := self._key_cache) is None:\n            self._key_cache = key = _cmpkey(\n                self._epoch,\n                self._release,\n                self._pre,\n                self._post,\n                self._dev,\n                self._local,\n            )\n        self._hash_cache = cached_hash = hash(key)\n        return cached_hash\n\n    # Override comparison methods to use direct _key_cache access\n    # This is faster than property access, especially before Python 3.12\n    def __lt__(self, other: _BaseVersion) -> bool:\n        if isinstance(other, Version):\n            if self._key_cache is None:\n                self._key_cache = _cmpkey(\n                    self._epoch,\n                    self._release,\n                    self._pre,\n                    self._post,\n                    self._dev,\n                    self._local,\n                )\n            if other._key_cache is None:\n                other._key_cache = _cmpkey(\n                    other._epoch,\n                    other._release,\n                    other._pre,\n                    other._post,\n                    other._dev,\n                    other._local,\n                )\n            return self._key_cache < other._key_cache\n\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return super().__lt__(other)\n\n    def __le__(self, other: _BaseVersion) -> bool:\n        if isinstance(other, Version):\n            if self._key_cache is None:\n                self._key_cache = _cmpkey(\n                    self._epoch,\n                    self._release,\n                    self._pre,\n                    self._post,\n                    self._dev,\n                    self._local,\n                )\n            if other._key_cache is None:\n                other._key_cache = _cmpkey(\n                    other._epoch,\n                    other._release,\n                    other._pre,\n                    other._post,\n                    other._dev,\n                    other._local,\n                )\n            return self._key_cache <= other._key_cache\n\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return super().__le__(other)\n\n    def __eq__(self, other: object) -> bool:\n        if isinstance(other, Version):\n            if self._key_cache is None:\n                self._key_cache = _cmpkey(\n                    self._epoch,\n                    self._release,\n                    self._pre,\n                    self._post,\n                    self._dev,\n                    self._local,\n                )\n            if other._key_cache is None:\n                other._key_cache = _cmpkey(\n                    other._epoch,\n                    other._release,\n                    other._pre,\n                    other._post,\n                    other._dev,\n                    other._local,\n                )\n            return self._key_cache == other._key_cache\n\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return super().__eq__(other)\n\n    def __ge__(self, other: _BaseVersion) -> bool:\n        if isinstance(other, Version):\n            if self._key_cache is None:\n                self._key_cache = _cmpkey(\n                    self._epoch,\n                    self._release,\n                    self._pre,\n                    self._post,\n                    self._dev,\n                    self._local,\n                )\n            if other._key_cache is None:\n                other._key_cache = _cmpkey(\n                    other._epoch,\n                    other._release,\n                    other._pre,\n                    other._post,\n                    other._dev,\n                    other._local,\n                )\n            return self._key_cache >= other._key_cache\n\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return super().__ge__(other)\n\n    def __gt__(self, other: _BaseVersion) -> bool:\n        if isinstance(other, Version):\n            if self._key_cache is None:\n                self._key_cache = _cmpkey(\n                    self._epoch,\n                    self._release,\n                    self._pre,\n                    self._post,\n                    self._dev,\n                    self._local,\n                )\n            if other._key_cache is None:\n                other._key_cache = _cmpkey(\n                    other._epoch,\n                    other._release,\n                    other._pre,\n                    other._post,\n                    other._dev,\n                    other._local,\n                )\n            return self._key_cache > other._key_cache\n\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return super().__gt__(other)\n\n    def __ne__(self, other: object) -> bool:\n        if isinstance(other, Version):\n            if self._key_cache is None:\n                self._key_cache = _cmpkey(\n                    self._epoch,\n                    self._release,\n                    self._pre,\n                    self._post,\n                    self._dev,\n                    self._local,\n                )\n            if other._key_cache is None:\n                other._key_cache = _cmpkey(\n                    other._epoch,\n                    other._release,\n                    other._pre,\n                    other._post,\n                    other._dev,\n                    other._local,\n                )\n            return self._key_cache != other._key_cache\n\n        if not isinstance(other, _BaseVersion):\n            return NotImplemented\n\n        return super().__ne__(other)\n\n    @property\n    @_deprecated(\"Version._version is private and will be removed soon\")\n    def _version(self) -> _Version:\n        return _Version(\n            self._epoch, self._release, self._dev, self._pre, self._post, self._local\n        )\n\n    @_version.setter\n    @_deprecated(\"Version._version is private and will be removed soon\")\n    def _version(self, value: _Version) -> None:\n        self._epoch = value.epoch\n        self._release = value.release\n        self._dev = value.dev\n        self._pre = value.pre\n        self._post = value.post\n        self._local = value.local\n        self._key_cache = None\n        self._hash_cache = None\n\n    def __repr__(self) -> str:\n        \"\"\"A representation of the Version that shows all internal state.\n\n        >>> Version('1.0.0')\n        <Version('1.0.0')>\n        \"\"\"\n        return f\"<{self.__class__.__name__}({str(self)!r})>\"\n\n    def __str__(self) -> str:\n        \"\"\"A string representation of the version that can be round-tripped.\n\n        >>> str(Version(\"1.0a5\"))\n        '1.0a5'\n        \"\"\"\n        # This is a hot function, so not calling self.base_version\n        version = \".\".join(map(str, self.release))\n\n        # Epoch\n        if self.epoch:\n            version = f\"{self.epoch}!{version}\"\n\n        # Pre-release\n        if self.pre is not None:\n            version += \"\".join(map(str, self.pre))\n\n        # Post-release\n        if self.post is not None:\n            version += f\".post{self.post}\"\n\n        # Development release\n        if self.dev is not None:\n            version += f\".dev{self.dev}\"\n\n        # Local version segment\n        if self.local is not None:\n            version += f\"+{self.local}\"\n\n        return version\n\n    @property\n    def _str(self) -> str:\n        \"\"\"Internal property for match_args\"\"\"\n        return str(self)\n\n    @property\n    def epoch(self) -> int:\n        \"\"\"The epoch of the version.\n\n        >>> Version(\"2.0.0\").epoch\n        0\n        >>> Version(\"1!2.0.0\").epoch\n        1\n        \"\"\"\n        return self._epoch\n\n    @property\n    def release(self) -> tuple[int, ...]:\n        \"\"\"The components of the \"release\" segment of the version.\n\n        >>> Version(\"1.2.3\").release\n        (1, 2, 3)\n        >>> Version(\"2.0.0\").release\n        (2, 0, 0)\n        >>> Version(\"1!2.0.0.post0\").release\n        (2, 0, 0)\n\n        Includes trailing zeroes but not the epoch or any pre-release / development /\n        post-release suffixes.\n        \"\"\"\n        return self._release\n\n    @property\n    def pre(self) -> tuple[Literal[\"a\", \"b\", \"rc\"], int] | None:\n        \"\"\"The pre-release segment of the version.\n\n        >>> print(Version(\"1.2.3\").pre)\n        None\n        >>> Version(\"1.2.3a1\").pre\n        ('a', 1)\n        >>> Version(\"1.2.3b1\").pre\n        ('b', 1)\n        >>> Version(\"1.2.3rc1\").pre\n        ('rc', 1)\n        \"\"\"\n        return self._pre\n\n    @property\n    def post(self) -> int | None:\n        \"\"\"The post-release number of the version.\n\n        >>> print(Version(\"1.2.3\").post)\n        None\n        >>> Version(\"1.2.3.post1\").post\n        1\n        \"\"\"\n        return self._post[1] if self._post else None\n\n    @property\n    def dev(self) -> int | None:\n        \"\"\"The development number of the version.\n\n        >>> print(Version(\"1.2.3\").dev)\n        None\n        >>> Version(\"1.2.3.dev1\").dev\n        1\n        \"\"\"\n        return self._dev[1] if self._dev else None\n\n    @property\n    def local(self) -> str | None:\n        \"\"\"The local version segment of the version.\n\n        >>> print(Version(\"1.2.3\").local)\n        None\n        >>> Version(\"1.2.3+abc\").local\n        'abc'\n        \"\"\"\n        if self._local:\n            return \".\".join(str(x) for x in self._local)\n        else:\n            return None\n\n    @property\n    def public(self) -> str:\n        \"\"\"The public portion of the version.\n\n        This returns a string. If you want a :class:`Version` again and care\n        about performance, use ``v.__replace__(local=None)`` instead.\n\n        >>> Version(\"1.2.3\").public\n        '1.2.3'\n        >>> Version(\"1.2.3+abc\").public\n        '1.2.3'\n        >>> Version(\"1!1.2.3dev1+abc\").public\n        '1!1.2.3.dev1'\n        \"\"\"\n        return str(self).split(\"+\", 1)[0]\n\n    @property\n    def base_version(self) -> str:\n        \"\"\"The \"base version\" of the version.\n\n        This returns a string. If you want a :class:`Version` again and care\n        about performance, use\n        ``v.__replace__(pre=None, post=None, dev=None, local=None)`` instead.\n\n        >>> Version(\"1.2.3\").base_version\n        '1.2.3'\n        >>> Version(\"1.2.3+abc\").base_version\n        '1.2.3'\n        >>> Version(\"1!1.2.3dev1+abc\").base_version\n        '1!1.2.3'\n\n        The \"base version\" is the public version of the project without any pre or post\n        release markers.\n        \"\"\"\n        release_segment = \".\".join(map(str, self.release))\n        return f\"{self.epoch}!{release_segment}\" if self.epoch else release_segment\n\n    @property\n    def is_prerelease(self) -> bool:\n        \"\"\"Whether this version is a pre-release.\n\n        >>> Version(\"1.2.3\").is_prerelease\n        False\n        >>> Version(\"1.2.3a1\").is_prerelease\n        True\n        >>> Version(\"1.2.3b1\").is_prerelease\n        True\n        >>> Version(\"1.2.3rc1\").is_prerelease\n        True\n        >>> Version(\"1.2.3dev1\").is_prerelease\n        True\n        \"\"\"\n        return self.dev is not None or self.pre is not None\n\n    @property\n    def is_postrelease(self) -> bool:\n        \"\"\"Whether this version is a post-release.\n\n        >>> Version(\"1.2.3\").is_postrelease\n        False\n        >>> Version(\"1.2.3.post1\").is_postrelease\n        True\n        \"\"\"\n        return self.post is not None\n\n    @property\n    def is_devrelease(self) -> bool:\n        \"\"\"Whether this version is a development release.\n\n        >>> Version(\"1.2.3\").is_devrelease\n        False\n        >>> Version(\"1.2.3.dev1\").is_devrelease\n        True\n        \"\"\"\n        return self.dev is not None\n\n    @property\n    def major(self) -> int:\n        \"\"\"The first item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").major\n        1\n        \"\"\"\n        return self.release[0] if len(self.release) >= 1 else 0\n\n    @property\n    def minor(self) -> int:\n        \"\"\"The second item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").minor\n        2\n        >>> Version(\"1\").minor\n        0\n        \"\"\"\n        return self.release[1] if len(self.release) >= 2 else 0\n\n    @property\n    def micro(self) -> int:\n        \"\"\"The third item of :attr:`release` or ``0`` if unavailable.\n\n        >>> Version(\"1.2.3\").micro\n        3\n        >>> Version(\"1\").micro\n        0\n        \"\"\"\n        return self.release[2] if len(self.release) >= 3 else 0\n\n\nclass _TrimmedRelease(Version):\n    __slots__ = ()\n\n    def __init__(self, version: str | Version) -> None:\n        if isinstance(version, Version):\n            self._epoch = version._epoch\n            self._release = version._release\n            self._dev = version._dev\n            self._pre = version._pre\n            self._post = version._post\n            self._local = version._local\n            self._key_cache = version._key_cache\n            return\n        super().__init__(version)  # pragma: no cover\n\n    @property\n    def release(self) -> tuple[int, ...]:\n        \"\"\"\n        Release segment without any trailing zeros.\n\n        >>> _TrimmedRelease('1.0.0').release\n        (1,)\n        >>> _TrimmedRelease('0.0').release\n        (0,)\n        \"\"\"\n        # This leaves one 0.\n        rel = super().release\n        len_release = len(rel)\n        i = len_release\n        while i > 1 and rel[i - 1] == 0:\n            i -= 1\n        return rel if i == len_release else rel[:i]\n\n\ndef _parse_letter_version(\n    letter: str | None, number: str | bytes | SupportsInt | None\n) -> tuple[str, int] | None:\n    if letter:\n        # We normalize any letters to their lower case form\n        letter = letter.lower()\n\n        # We consider some words to be alternate spellings of other words and\n        # in those cases we want to normalize the spellings to our preferred\n        # spelling.\n        letter = _LETTER_NORMALIZATION.get(letter, letter)\n\n        # We consider there to be an implicit 0 in a pre-release if there is\n        # not a numeral associated with it.\n        return letter, int(number or 0)\n\n    if number:\n        # We assume if we are given a number, but we are not given a letter\n        # then this is using the implicit post release syntax (e.g. 1.0-1)\n        return \"post\", int(number)\n\n    return None\n\n\n_local_version_separators = re.compile(r\"[\\._-]\")\n\n\ndef _parse_local_version(local: str | None) -> LocalType | None:\n    \"\"\"\n    Takes a string like ``\"abc.1.twelve\"`` and turns it into\n    ``(\"abc\", 1, \"twelve\")``.\n    \"\"\"\n    if local is not None:\n        return tuple(\n            part.lower() if not part.isdigit() else int(part)\n            for part in _local_version_separators.split(local)\n        )\n    return None\n\n\n# Sort ranks for pre-release: dev-only < a < b < rc < stable (no pre-release).\n_PRE_RANK = {\"a\": 0, \"b\": 1, \"rc\": 2}\n_PRE_RANK_DEV_ONLY = -1  # sorts before a(0)\n_PRE_RANK_STABLE = 3  # sorts after rc(2)\n\n# In local version segments, strings sort before ints per PEP 440.\n_LOCAL_STR_RANK = -1  # sorts before all non-negative ints\n\n# Pre-computed suffix for stable releases (no pre, post, or dev segments).\n# See _cmpkey() for the suffix layout.\n_STABLE_SUFFIX = (_PRE_RANK_STABLE, 0, 0, 0, 1, 0)\n\n\ndef _cmpkey(\n    epoch: int,\n    release: tuple[int, ...],\n    pre: tuple[str, int] | None,\n    post: tuple[str, int] | None,\n    dev: tuple[str, int] | None,\n    local: LocalType | None,\n) -> CmpKey:\n    \"\"\"Build a comparison key for PEP 440 ordering.\n\n    Returns ``(epoch, release, suffix)`` or\n    ``(epoch, release, suffix, local)`` so that plain tuple\n    comparison gives the correct order.\n\n    Trailing zeros are stripped from the release so that ``1.0.0 == 1``.\n\n    The suffix is a flat 6-int tuple that encodes pre/post/dev:\n    ``(pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)``\n\n    pre_rank: dev-only=-1, a=0, b=1, rc=2, no-pre=3\n        Dev-only releases (no pre or post) get -1 so they sort before\n        any alpha/beta/rc.  Releases without a pre-release tag get 3\n        so they sort after rc.\n    post_rank: no-post=0, post=1\n        Releases without a post segment sort before those with one.\n    dev_rank: dev=0, no-dev=1\n        Releases without a dev segment sort after those with one.\n\n    Local segments use ``(n, \"\")`` for ints and ``(-1, s)`` for strings,\n    following PEP 440: strings sort before ints, strings compare\n    lexicographically, ints compare numerically, and shorter segments\n    sort before longer when prefixes match.  Versions without a local\n    segment sort before those with one (3-tuple < 4-tuple).\n\n    >>> _cmpkey(0, (1, 0, 0), None, None, None, None)\n    (0, (1,), (3, 0, 0, 0, 1, 0))\n    >>> _cmpkey(0, (1,), (\"a\", 1), None, None, None)\n    (0, (1,), (0, 1, 0, 0, 1, 0))\n    >>> _cmpkey(0, (1,), None, None, None, (\"ubuntu\", 1))\n    (0, (1,), (3, 0, 0, 0, 1, 0), ((-1, 'ubuntu'), (1, '')))\n    \"\"\"\n    # Strip trailing zeros: 1.0.0 compares equal to 1.\n    len_release = len(release)\n    i = len_release\n    while i and release[i - 1] == 0:\n        i -= 1\n    trimmed = release if i == len_release else release[:i]\n\n    # Fast path: stable release with no local segment.\n    if pre is None and post is None and dev is None and local is None:\n        return epoch, trimmed, _STABLE_SUFFIX\n\n    if pre is None and post is None and dev is not None:\n        # dev-only (e.g. 1.0.dev1) sorts before all pre-releases.\n        pre_rank, pre_n = _PRE_RANK_DEV_ONLY, 0\n    elif pre is None:\n        pre_rank, pre_n = _PRE_RANK_STABLE, 0\n    else:\n        pre_rank, pre_n = _PRE_RANK[pre[0]], pre[1]\n\n    post_rank = 0 if post is None else 1\n    post_n = 0 if post is None else post[1]\n\n    dev_rank = 1 if dev is None else 0\n    dev_n = 0 if dev is None else dev[1]\n\n    suffix = (pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)\n\n    if local is None:\n        return epoch, trimmed, suffix\n\n    cmp_local: CmpLocalType = tuple(\n        (seg, \"\") if isinstance(seg, int) else (_LOCAL_STR_RANK, seg) for seg in local\n    )\n    return epoch, trimmed, suffix, cmp_local\n"}}]}