Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
version.py1232 linesDownload Raw Back to packaging
1# This file is dual licensed under the terms of the Apache License, Version2# 2.0, and the BSD License. See the LICENSE file in the root of this repository3# for complete details.4"""5.. testsetup::6 7    from packaging.version import parse, normalize_pre, Version, _cmpkey8"""9 10from __future__ import annotations11 12import re13import sys14import typing15from typing import (16    Any,17    Callable,18    Literal,19    NamedTuple,20    SupportsInt,21    Tuple,22    TypedDict,23    Union,24)25 26if typing.TYPE_CHECKING:27    from typing_extensions import Self, Unpack28 29if sys.version_info >= (3, 13):  # pragma: no cover30    from warnings import deprecated as _deprecated31elif typing.TYPE_CHECKING:32    from typing_extensions import deprecated as _deprecated33else:  # pragma: no cover34    import functools35    import warnings36 37    def _deprecated(message: str) -> object:38        def decorator(func: Callable[[...], object]) -> object:39            @functools.wraps(func)40            def wrapper(*args: object, **kwargs: object) -> object:41                warnings.warn(42                    message,43                    category=DeprecationWarning,44                    stacklevel=2,45                )46                return func(*args, **kwargs)47 48            return wrapper49 50        return decorator51 52 53_LETTER_NORMALIZATION = {54    "alpha": "a",55    "beta": "b",56    "c": "rc",57    "pre": "rc",58    "preview": "rc",59    "rev": "post",60    "r": "post",61}62 63__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "normalize_pre", "parse"]64 65 66def __dir__() -> list[str]:67    return __all__68 69 70LocalType = Tuple[Union[int, str], ...]71 72CmpLocalType = Tuple[Tuple[int, str], ...]73CmpSuffix = Tuple[int, int, int, int, int, int]74CmpKey = Union[75    Tuple[int, Tuple[int, ...], CmpSuffix],76    Tuple[int, Tuple[int, ...], CmpSuffix, CmpLocalType],77]78VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]79 80 81class _VersionReplace(TypedDict, total=False):82    epoch: int | None83    release: tuple[int, ...] | None84    pre: tuple[str, int] | None85    post: int | None86    dev: int | None87    local: str | None88 89 90def normalize_pre(letter: str, /) -> str:91    """Normalize the pre-release segment of a version string.92 93    Returns a lowercase version of the string if not a known pre-release94    identifier.95 96    >>> normalize_pre('alpha')97    'a'98    >>> normalize_pre('BETA')99    'b'100    >>> normalize_pre('rc')101    'rc'102 103    :param letter:104 105    .. versionadded:: 26.1106    """107    letter = letter.lower()108    return _LETTER_NORMALIZATION.get(letter, letter)109 110 111def parse(version: str) -> Version:112    """Parse the given version string.113 114    This is identical to the :class:`Version` constructor.115 116    >>> parse('1.0.dev1')117    <Version('1.0.dev1')>118 119    :param version: The version string to parse.120    :raises InvalidVersion: When the version string is not a valid version.121    """122    return Version(version)123 124 125class InvalidVersion(ValueError):126    """Raised when a version string is not a valid version.127 128    >>> Version("invalid")129    Traceback (most recent call last):130        ...131    packaging.version.InvalidVersion: Invalid version: 'invalid'132    """133 134 135class _BaseVersion:136    __slots__ = ()137 138    # This can also be a normal member (see the packaging_legacy package);139    # we are just requiring it to be readable. Actually defining a property140    # has runtime effect on subclasses, so it's typing only.141    if typing.TYPE_CHECKING:142 143        @property144        def _key(self) -> tuple[Any, ...]: ...145 146    def __hash__(self) -> int:147        return hash(self._key)148 149    # Please keep the duplicated `isinstance` check150    # in the six comparisons hereunder151    # unless you find a way to avoid adding overhead function calls.152    def __lt__(self, other: _BaseVersion) -> bool:153        if not isinstance(other, _BaseVersion):154            return NotImplemented155 156        return self._key < other._key157 158    def __le__(self, other: _BaseVersion) -> bool:159        if not isinstance(other, _BaseVersion):160            return NotImplemented161 162        return self._key <= other._key163 164    def __eq__(self, other: object) -> bool:165        if not isinstance(other, _BaseVersion):166            return NotImplemented167 168        return self._key == other._key169 170    def __ge__(self, other: _BaseVersion) -> bool:171        if not isinstance(other, _BaseVersion):172            return NotImplemented173 174        return self._key >= other._key175 176    def __gt__(self, other: _BaseVersion) -> bool:177        if not isinstance(other, _BaseVersion):178            return NotImplemented179 180        return self._key > other._key181 182    def __ne__(self, other: object) -> bool:183        if not isinstance(other, _BaseVersion):184            return NotImplemented185 186        return self._key != other._key187 188 189# Deliberately not anchored to the start and end of the string, to make it190# easier for 3rd party code to reuse191 192# Note that ++ doesn't behave identically on CPython and PyPy, so not using it here193_VERSION_PATTERN = r"""194    v?+                                                   # optional leading v195    (?a:196        (?:(?P<epoch>[0-9]+)!)?+                          # epoch197        (?P<release>[0-9]+(?:\.[0-9]+)*+)                 # release segment198        (?P<pre>                                          # pre-release199            [._-]?+200            (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)201            [._-]?+202            (?P<pre_n>[0-9]+)?203        )?+204        (?P<post>                                         # post release205            (?:-(?P<post_n1>[0-9]+))206            |207            (?:208                [._-]?209                (?P<post_l>post|rev|r)210                [._-]?211                (?P<post_n2>[0-9]+)?212            )213        )?+214        (?P<dev>                                          # dev release215            [._-]?+216            (?P<dev_l>dev)217            [._-]?+218            (?P<dev_n>[0-9]+)?219        )?+220    )221    (?a:\+222        (?P<local>                                        # local version223            [a-z0-9]+224            (?:[._-][a-z0-9]+)*+225        )226    )?+227"""228 229_VERSION_PATTERN_OLD = _VERSION_PATTERN.replace("*+", "*").replace("?+", "?")230 231# Possessive qualifiers were added in Python 3.11.232# CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795233# Older PyPy also had a bug.234VERSION_PATTERN = (235    _VERSION_PATTERN_OLD236    if (sys.implementation.name == "cpython" and sys.version_info < (3, 11, 5))237    or (sys.implementation.name == "pypy" and sys.version_info < (3, 11, 13))238    or sys.version_info < (3, 11)239    else _VERSION_PATTERN240)241"""242A string containing the regular expression used to match a valid version.243 244The pattern is not anchored at either end, and is intended for embedding in larger245expressions (for example, matching a version number as part of a file name). The246regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``247flags set.248 249.. versionchanged:: 26.0250 251   The regex now uses possessive qualifiers on Python 3.11 if they are252   supported (CPython 3.11.5+, PyPy 3.11.13+).253 254:meta hide-value:255"""256 257 258# Validation pattern for local version in replace()259_LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE | re.ASCII)260 261# Fast path: If a version has only digits and dots then we262# can skip the regex and parse it as a release segment263_SIMPLE_VERSION_INDICATORS = frozenset(".0123456789")264 265 266def _validate_epoch(value: object, /) -> int:267    epoch = value or 0268    if isinstance(epoch, int) and epoch >= 0:269        return epoch270    msg = f"epoch must be non-negative integer, got {epoch}"271    raise InvalidVersion(msg)272 273 274def _validate_release(value: object, /) -> tuple[int, ...]:275    release = (0,) if value is None else value276    if (277        isinstance(release, tuple)278        and len(release) > 0279        and all(isinstance(i, int) and i >= 0 for i in release)280    ):281        return release282    msg = f"release must be a non-empty tuple of non-negative integers, got {release}"283    raise InvalidVersion(msg)284 285 286def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None:287    if value is None:288        return value289    if isinstance(value, tuple) and len(value) == 2:290        letter, number = value291        letter = normalize_pre(letter)292        if letter in {"a", "b", "rc"} and isinstance(number, int) and number >= 0:293            # type checkers can't infer the Literal type here on letter294            return (letter, number)  # type: ignore[return-value]295    msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}"296    raise InvalidVersion(msg)297 298 299def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:300    if value is None:301        return value302    if isinstance(value, int) and value >= 0:303        return ("post", value)304    msg = f"post must be non-negative integer, got {value}"305    raise InvalidVersion(msg)306 307 308def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:309    if value is None:310        return value311    if isinstance(value, int) and value >= 0:312        return ("dev", value)313    msg = f"dev must be non-negative integer, got {value}"314    raise InvalidVersion(msg)315 316 317def _validate_local(value: object, /) -> LocalType | None:318    if value is None:319        return value320    if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):321        return _parse_local_version(value)322    msg = f"local must be a valid version string, got {value!r}"323    raise InvalidVersion(msg)324 325 326# Backward compatibility for internals before 26.0. Do not use.327class _Version(NamedTuple):328    epoch: int329    release: tuple[int, ...]330    dev: tuple[Literal["dev"], int] | None331    pre: tuple[Literal["a", "b", "rc"], int] | None332    post: tuple[Literal["post"], int] | None333    local: LocalType | None334 335 336class Version(_BaseVersion):337    """This class abstracts handling of a project's versions.338 339    A :class:`Version` instance is comparison aware and can be compared and340    sorted using the standard Python interfaces.341 342    >>> v1 = Version("1.0a5")343    >>> v2 = Version("1.0")344    >>> v1345    <Version('1.0a5')>346    >>> v2347    <Version('1.0')>348    >>> v1 < v2349    True350    >>> v1 == v2351    False352    >>> v1 > v2353    False354    >>> v1 >= v2355    False356    >>> v1 <= v2357    True358 359    :class:`Version` is immutable; use :meth:`__replace__` to change360    part of a version.361 362    Instances are safe to serialize with :mod:`pickle`. They use a stable363    format so the same pickle can be loaded in future packaging releases.364 365    .. versionchanged:: 26.2366 367        Added a stable pickle format. Pickles created with packaging 26.2+ can368        be unpickled with future releases.  Backward compatibility with pickles369        from packaging < 26.2 is supported but may be removed in a future370        release.371    """372 373    __slots__ = (374        "_dev",375        "_epoch",376        "_hash_cache",377        "_key_cache",378        "_local",379        "_post",380        "_pre",381        "_release",382    )383    __match_args__ = ("_str",)384    """385    Pattern matching is supported on Python 3.10+.386 387    .. versionadded:: 26.0388 389    :meta hide-value:390    """391 392    _regex = re.compile(r"\s*" + VERSION_PATTERN + r"\s*", re.VERBOSE | re.IGNORECASE)393 394    _epoch: int395    _release: tuple[int, ...]396    _dev: tuple[Literal["dev"], int] | None397    _pre: tuple[Literal["a", "b", "rc"], int] | None398    _post: tuple[Literal["post"], int] | None399    _local: LocalType | None400 401    _hash_cache: int | None402    _key_cache: CmpKey | None403 404    def __init__(self, version: str) -> None:405        """Initialize a Version object.406 407        :param version:408            The string representation of a version which will be parsed and normalized409            before use.410        :raises InvalidVersion:411            If the ``version`` does not conform to PEP 440 in any way then this412            exception will be raised.413        """414        if _SIMPLE_VERSION_INDICATORS.issuperset(version):415            try:416                self._release = tuple(map(int, version.split(".")))417            except ValueError:418                # Empty parts (from "1..2", ".1", etc.) are invalid versions.419                # Any other ValueError (e.g. int str-digits limit) should420                # propagate to the caller.421                if "" in version.split("."):422                    raise InvalidVersion(f"Invalid version: {version!r}") from None423                # TODO: remove "no cover" when Python 3.9 is dropped.424                raise  # pragma: no cover425 426            self._epoch = 0427            self._pre = None428            self._post = None429            self._dev = None430            self._local = None431            self._key_cache = None432            self._hash_cache = None433            return434 435        # Validate the version and parse it into pieces436        match = self._regex.fullmatch(version)437        if not match:438            raise InvalidVersion(f"Invalid version: {version!r}")439        self._epoch = int(match.group("epoch")) if match.group("epoch") else 0440        self._release = tuple(map(int, match.group("release").split(".")))441        # We can type ignore the assignments below because the regex guarantees442        # the correct strings443        self._pre = _parse_letter_version(match.group("pre_l"), match.group("pre_n"))  # type: ignore[assignment]444        self._post = _parse_letter_version(  # type: ignore[assignment]445            match.group("post_l"), match.group("post_n1") or match.group("post_n2")446        )447        self._dev = _parse_letter_version(match.group("dev_l"), match.group("dev_n"))  # type: ignore[assignment]448        self._local = _parse_local_version(match.group("local"))449 450        # Key which will be used for sorting451        self._key_cache = None452        self._hash_cache = None453 454    @classmethod455    def from_parts(456        cls,457        *,458        epoch: int = 0,459        release: tuple[int, ...],460        pre: tuple[str, int] | None = None,461        post: int | None = None,462        dev: int | None = None,463        local: str | None = None,464    ) -> Self:465        """466        Return a new version composed of the various parts.467 468        This allows you to build a version without going though a string and469        running a regular expression. It normalizes pre-release strings. The470        ``release=`` keyword argument is required.471 472        >>> Version.from_parts(release=(1,2,3))473        <Version('1.2.3')>474        >>> Version.from_parts(release=(0,1,0), pre=("b", 1))475        <Version('0.1.0b1')>476 477        :param epoch:478        :param release: This version tuple is required479 480        .. versionadded:: 26.1481        """482        _epoch = _validate_epoch(epoch)483        _release = _validate_release(release)484        _pre = _validate_pre(pre) if pre is not None else None485        _post = _validate_post(post) if post is not None else None486        _dev = _validate_dev(dev) if dev is not None else None487        _local = _validate_local(local) if local is not None else None488 489        new_version = cls.__new__(cls)490        new_version._key_cache = None491        new_version._hash_cache = None492        new_version._epoch = _epoch493        new_version._release = _release494        new_version._pre = _pre495        new_version._post = _post496        new_version._dev = _dev497        new_version._local = _local498 499        return new_version500 501    def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:502        """503        __replace__(*, epoch=..., release=..., pre=..., post=..., dev=..., local=...)504 505        Return a new version with parts replaced.506 507        This returns a new version (unless no parts were changed). The508        pre-release is normalized. Setting a value to ``None`` clears it.509 510        >>> v = Version("1.2.3")511        >>> v.__replace__(pre=("a", 1))512        <Version('1.2.3a1')>513 514        :param int | None epoch:515        :param tuple[int, ...] | None release:516        :param tuple[str, int] | None pre:517        :param int | None post:518        :param int | None dev:519        :param str | None local:520 521        .. versionadded:: 26.0522        .. versionchanged:: 26.1523 524           The pre-release portion is now normalized.525        """526        epoch = _validate_epoch(kwargs["epoch"]) if "epoch" in kwargs else self._epoch527        release = (528            _validate_release(kwargs["release"])529            if "release" in kwargs530            else self._release531        )532        pre = _validate_pre(kwargs["pre"]) if "pre" in kwargs else self._pre533        post = _validate_post(kwargs["post"]) if "post" in kwargs else self._post534        dev = _validate_dev(kwargs["dev"]) if "dev" in kwargs else self._dev535        local = _validate_local(kwargs["local"]) if "local" in kwargs else self._local536 537        if (538            epoch == self._epoch539            and release == self._release540            and pre == self._pre541            and post == self._post542            and dev == self._dev543            and local == self._local544        ):545            return self546 547        new_version = self.__class__.__new__(self.__class__)548        new_version._key_cache = None549        new_version._hash_cache = None550        new_version._epoch = epoch551        new_version._release = release552        new_version._pre = pre553        new_version._post = post554        new_version._dev = dev555        new_version._local = local556 557        return new_version558 559    @property560    def _key(self) -> CmpKey:561        if self._key_cache is None:562            self._key_cache = _cmpkey(563                self._epoch,564                self._release,565                self._pre,566                self._post,567                self._dev,568                self._local,569            )570        return self._key_cache571 572    # __hash__ must be defined when __eq__ is overridden,573    # otherwise Python sets __hash__ to None.574    def __hash__(self) -> int:575        if (cached_hash := self._hash_cache) is not None:576            return cached_hash577 578        if (key := self._key_cache) is None:579            self._key_cache = key = _cmpkey(580                self._epoch,581                self._release,582                self._pre,583                self._post,584                self._dev,585                self._local,586            )587        self._hash_cache = cached_hash = hash(key)588        return cached_hash589 590    # Override comparison methods to use direct _key_cache access591    # This is faster than property access, especially before Python 3.12592    def __lt__(self, other: _BaseVersion) -> bool:593        if isinstance(other, Version):594            if self._key_cache is None:595                self._key_cache = _cmpkey(596                    self._epoch,597                    self._release,598                    self._pre,599                    self._post,600                    self._dev,601                    self._local,602                )603            if other._key_cache is None:604                other._key_cache = _cmpkey(605                    other._epoch,606                    other._release,607                    other._pre,608                    other._post,609                    other._dev,610                    other._local,611                )612            return self._key_cache < other._key_cache613 614        if not isinstance(other, _BaseVersion):615            return NotImplemented616 617        return super().__lt__(other)618 619    def __le__(self, other: _BaseVersion) -> bool:620        if isinstance(other, Version):621            if self._key_cache is None:622                self._key_cache = _cmpkey(623                    self._epoch,624                    self._release,625                    self._pre,626                    self._post,627                    self._dev,628                    self._local,629                )630            if other._key_cache is None:631                other._key_cache = _cmpkey(632                    other._epoch,633                    other._release,634                    other._pre,635                    other._post,636                    other._dev,637                    other._local,638                )639            return self._key_cache <= other._key_cache640 641        if not isinstance(other, _BaseVersion):642            return NotImplemented643 644        return super().__le__(other)645 646    def __eq__(self, other: object) -> bool:647        if isinstance(other, Version):648            if self._key_cache is None:649                self._key_cache = _cmpkey(650                    self._epoch,651                    self._release,652                    self._pre,653                    self._post,654                    self._dev,655                    self._local,656                )657            if other._key_cache is None:658                other._key_cache = _cmpkey(659                    other._epoch,660                    other._release,661                    other._pre,662                    other._post,663                    other._dev,664                    other._local,665                )666            return self._key_cache == other._key_cache667 668        if not isinstance(other, _BaseVersion):669            return NotImplemented670 671        return super().__eq__(other)672 673    def __ge__(self, other: _BaseVersion) -> bool:674        if isinstance(other, Version):675            if self._key_cache is None:676                self._key_cache = _cmpkey(677                    self._epoch,678                    self._release,679                    self._pre,680                    self._post,681                    self._dev,682                    self._local,683                )684            if other._key_cache is None:685                other._key_cache = _cmpkey(686                    other._epoch,687                    other._release,688                    other._pre,689                    other._post,690                    other._dev,691                    other._local,692                )693            return self._key_cache >= other._key_cache694 695        if not isinstance(other, _BaseVersion):696            return NotImplemented697 698        return super().__ge__(other)699 700    def __gt__(self, other: _BaseVersion) -> bool:701        if isinstance(other, Version):702            if self._key_cache is None:703                self._key_cache = _cmpkey(704                    self._epoch,705                    self._release,706                    self._pre,707                    self._post,708                    self._dev,709                    self._local,710                )711            if other._key_cache is None:712                other._key_cache = _cmpkey(713                    other._epoch,714                    other._release,715                    other._pre,716                    other._post,717                    other._dev,718                    other._local,719                )720            return self._key_cache > other._key_cache721 722        if not isinstance(other, _BaseVersion):723            return NotImplemented724 725        return super().__gt__(other)726 727    def __ne__(self, other: object) -> bool:728        if isinstance(other, Version):729            if self._key_cache is None:730                self._key_cache = _cmpkey(731                    self._epoch,732                    self._release,733                    self._pre,734                    self._post,735                    self._dev,736                    self._local,737                )738            if other._key_cache is None:739                other._key_cache = _cmpkey(740                    other._epoch,741                    other._release,742                    other._pre,743                    other._post,744                    other._dev,745                    other._local,746                )747            return self._key_cache != other._key_cache748 749        if not isinstance(other, _BaseVersion):750            return NotImplemented751 752        return super().__ne__(other)753 754    def __getstate__(755        self,756    ) -> tuple[757        int,758        tuple[int, ...],759        tuple[str, int] | None,760        tuple[str, int] | None,761        tuple[str, int] | None,762        LocalType | None,763    ]:764        # Return state as a 6-item tuple for compactness:765        #   (epoch, release, pre, post, dev, local)766        # Cache members are excluded and will be recomputed on demand767        return (768            self._epoch,769            self._release,770            self._pre,771            self._post,772            self._dev,773            self._local,774        )775 776    def __setstate__(self, state: object) -> None:777        # Always discard cached values — they may contain stale references778        # (e.g. packaging._structures.InfinityType from pre-26.1 pickles)779        # and will be recomputed on demand from the core fields above.780        self._key_cache = None781        self._hash_cache = None782 783        if isinstance(state, tuple):784            if len(state) == 6:785                # New format (26.2+): (epoch, release, pre, post, dev, local)786                (787                    self._epoch,788                    self._release,789                    self._pre,790                    self._post,791                    self._dev,792                    self._local,793                ) = state794                return795            if len(state) == 2:796                # Format (packaging 26.0-26.1): (None, {slot: value}).797                _, slot_dict = state798                if isinstance(slot_dict, dict):799                    self._epoch = slot_dict["_epoch"]800                    self._release = slot_dict["_release"]801                    self._pre = slot_dict.get("_pre")802                    self._post = slot_dict.get("_post")803                    self._dev = slot_dict.get("_dev")804                    self._local = slot_dict.get("_local")805                    return806        if isinstance(state, dict):807            # Old format (packaging <= 25.x, no __slots__): state is a plain808            # dict with "_version" (_Version NamedTuple) and "_key" entries.809            version_nt = state.get("_version")810            if version_nt is not None:811                self._epoch = version_nt.epoch812                self._release = version_nt.release813                self._pre = version_nt.pre814                self._post = version_nt.post815                self._dev = version_nt.dev816                self._local = version_nt.local817                return818 819        raise TypeError(f"Cannot restore Version from {state!r}")820 821    @property822    @_deprecated("Version._version is private and will be removed soon")823    def _version(self) -> _Version:824        return _Version(825            self._epoch, self._release, self._dev, self._pre, self._post, self._local826        )827 828    @_version.setter829    @_deprecated("Version._version is private and will be removed soon")830    def _version(self, value: _Version) -> None:831        self._epoch = value.epoch832        self._release = value.release833        self._dev = value.dev834        self._pre = value.pre835        self._post = value.post836        self._local = value.local837        self._key_cache = None838        self._hash_cache = None839 840    def __repr__(self) -> str:841        """A representation of the Version that shows all internal state.842 843        >>> Version('1.0.0')844        <Version('1.0.0')>845        """846        return f"<{self.__class__.__name__}({str(self)!r})>"847 848    def __str__(self) -> str:849        """A string representation of the version that can be round-tripped.850 851        >>> str(Version("1.0a5"))852        '1.0a5'853        """854        # This is a hot function, so not calling self.base_version855        version = ".".join(map(str, self.release))856 857        # Epoch858        if self.epoch:859            version = f"{self.epoch}!{version}"860 861        # Pre-release862        if self.pre is not None:863            version += "".join(map(str, self.pre))864 865        # Post-release866        if self.post is not None:867            version += f".post{self.post}"868 869        # Development release870        if self.dev is not None:871            version += f".dev{self.dev}"872 873        # Local version segment874        if self.local is not None:875            version += f"+{self.local}"876 877        return version878 879    @property880    def _str(self) -> str:881        """Internal property for match_args"""882        return str(self)883 884    @property885    def epoch(self) -> int:886        """The epoch of the version.887 888        >>> Version("2.0.0").epoch889        0890        >>> Version("1!2.0.0").epoch891        1892        """893        return self._epoch894 895    @property896    def release(self) -> tuple[int, ...]:897        """The components of the "release" segment of the version.898 899        >>> Version("1.2.3").release900        (1, 2, 3)901        >>> Version("2.0.0").release902        (2, 0, 0)903        >>> Version("1!2.0.0.post0").release904        (2, 0, 0)905 906        Includes trailing zeroes but not the epoch or any pre-release / development /907        post-release suffixes.908        """909        return self._release910 911    @property912    def pre(self) -> tuple[Literal["a", "b", "rc"], int] | None:913        """The pre-release segment of the version.914 915        >>> print(Version("1.2.3").pre)916        None917        >>> Version("1.2.3a1").pre918        ('a', 1)919        >>> Version("1.2.3b1").pre920        ('b', 1)921        >>> Version("1.2.3rc1").pre922        ('rc', 1)923        """924        return self._pre925 926    @property927    def post(self) -> int | None:928        """The post-release number of the version.929 930        >>> print(Version("1.2.3").post)931        None932        >>> Version("1.2.3.post1").post933        1934        """935        return self._post[1] if self._post else None936 937    @property938    def dev(self) -> int | None:939        """The development number of the version.940 941        >>> print(Version("1.2.3").dev)942        None943        >>> Version("1.2.3.dev1").dev944        1945        """946        return self._dev[1] if self._dev else None947 948    @property949    def local(self) -> str | None:950        """The local version segment of the version.951 952        >>> print(Version("1.2.3").local)953        None954        >>> Version("1.2.3+abc").local955        'abc'956        """957        if self._local:958            return ".".join(str(x) for x in self._local)959        else:960            return None961 962    @property963    def public(self) -> str:964        """The public portion of the version.965 966        This returns a string. If you want a :class:`Version` again and care967        about performance, use ``v.__replace__(local=None)`` instead.968 969        >>> Version("1.2.3").public970        '1.2.3'971        >>> Version("1.2.3+abc").public972        '1.2.3'973        >>> Version("1!1.2.3dev1+abc").public974        '1!1.2.3.dev1'975        """976        return str(self).split("+", 1)[0]977 978    @property979    def base_version(self) -> str:980        """The "base version" of the version.981 982        This returns a string. If you want a :class:`Version` again and care983        about performance, use984        ``v.__replace__(pre=None, post=None, dev=None, local=None)`` instead.985 986        >>> Version("1.2.3").base_version987        '1.2.3'988        >>> Version("1.2.3+abc").base_version989        '1.2.3'990        >>> Version("1!1.2.3dev1+abc").base_version991        '1!1.2.3'992 993        The "base version" is the public version of the project without any pre or post994        release markers.995        """996        release_segment = ".".join(map(str, self.release))997        return f"{self.epoch}!{release_segment}" if self.epoch else release_segment998 999    @property1000    def is_prerelease(self) -> bool:1001        """Whether this version is a pre-release.1002 1003        >>> Version("1.2.3").is_prerelease1004        False1005        >>> Version("1.2.3a1").is_prerelease1006        True1007        >>> Version("1.2.3b1").is_prerelease1008        True1009        >>> Version("1.2.3rc1").is_prerelease1010        True1011        >>> Version("1.2.3dev1").is_prerelease1012        True1013        """1014        return self.dev is not None or self.pre is not None1015 1016    @property1017    def is_postrelease(self) -> bool:1018        """Whether this version is a post-release.1019 1020        >>> Version("1.2.3").is_postrelease1021        False1022        >>> Version("1.2.3.post1").is_postrelease1023        True1024        """1025        return self.post is not None1026 1027    @property1028    def is_devrelease(self) -> bool:1029        """Whether this version is a development release.1030 1031        >>> Version("1.2.3").is_devrelease1032        False1033        >>> Version("1.2.3.dev1").is_devrelease1034        True1035        """1036        return self.dev is not None1037 1038    @property1039    def major(self) -> int:1040        """The first item of :attr:`release` or ``0`` if unavailable.1041 1042        >>> Version("1.2.3").major1043        11044        """1045        return self.release[0] if len(self.release) >= 1 else 01046 1047    @property1048    def minor(self) -> int:1049        """The second item of :attr:`release` or ``0`` if unavailable.1050 1051        >>> Version("1.2.3").minor1052        21053        >>> Version("1").minor1054        01055        """1056        return self.release[1] if len(self.release) >= 2 else 01057 1058    @property1059    def micro(self) -> int:1060        """The third item of :attr:`release` or ``0`` if unavailable.1061 1062        >>> Version("1.2.3").micro1063        31064        >>> Version("1").micro1065        01066        """1067        return self.release[2] if len(self.release) >= 3 else 01068 1069 1070class _TrimmedRelease(Version):1071    __slots__ = ()1072 1073    def __init__(self, version: str | Version) -> None:1074        if isinstance(version, Version):1075            self._epoch = version._epoch1076            self._release = version._release1077            self._dev = version._dev1078            self._pre = version._pre1079            self._post = version._post1080            self._local = version._local1081            self._key_cache = version._key_cache1082            return1083        super().__init__(version)  # pragma: no cover1084 1085    @property1086    def release(self) -> tuple[int, ...]:1087        """1088        Release segment without any trailing zeros.1089 1090        >>> _TrimmedRelease('1.0.0').release1091        (1,)1092        >>> _TrimmedRelease('0.0').release1093        (0,)1094        """1095        # This leaves one 0.1096        rel = super().release1097        len_release = len(rel)1098        i = len_release1099        while i > 1 and rel[i - 1] == 0:1100            i -= 11101        return rel if i == len_release else rel[:i]1102 1103 1104def _parse_letter_version(1105    letter: str | None, number: str | bytes | SupportsInt | None1106) -> tuple[str, int] | None:1107    if letter:1108        # We normalize any letters to their lower case form1109        letter = letter.lower()1110 1111        # We consider some words to be alternate spellings of other words and1112        # in those cases we want to normalize the spellings to our preferred1113        # spelling.1114        letter = _LETTER_NORMALIZATION.get(letter, letter)1115 1116        # We consider there to be an implicit 0 in a pre-release if there is1117        # not a numeral associated with it.1118        return letter, int(number or 0)1119 1120    if number:1121        # We assume if we are given a number, but we are not given a letter1122        # then this is using the implicit post release syntax (e.g. 1.0-1)1123        return "post", int(number)1124 1125    return None1126 1127 1128_local_version_separators = re.compile(r"[\._-]")1129 1130 1131def _parse_local_version(local: str | None) -> LocalType | None:1132    """1133    Takes a string like ``"abc.1.twelve"`` and turns it into1134    ``("abc", 1, "twelve")``.1135    """1136    if local is not None:1137        return tuple(1138            part.lower() if not part.isdigit() else int(part)1139            for part in _local_version_separators.split(local)1140        )1141    return None1142 1143 1144# Sort ranks for pre-release: dev-only < a < b < rc < stable (no pre-release).1145_PRE_RANK = {"a": 0, "b": 1, "rc": 2}1146_PRE_RANK_DEV_ONLY = -1  # sorts before a(0)1147_PRE_RANK_STABLE = 3  # sorts after rc(2)1148 1149# In local version segments, strings sort before ints per PEP 440.1150_LOCAL_STR_RANK = -1  # sorts before all non-negative ints1151 1152# Pre-computed suffix for stable releases (no pre, post, or dev segments).1153# See _cmpkey() for the suffix layout.1154_STABLE_SUFFIX = (_PRE_RANK_STABLE, 0, 0, 0, 1, 0)1155 1156 1157def _cmpkey(1158    epoch: int,1159    release: tuple[int, ...],1160    pre: tuple[str, int] | None,1161    post: tuple[str, int] | None,1162    dev: tuple[str, int] | None,1163    local: LocalType | None,1164) -> CmpKey:1165    """Build a comparison key for PEP 440 ordering.1166 1167    Returns ``(epoch, release, suffix)`` or1168    ``(epoch, release, suffix, local)`` so that plain tuple1169    comparison gives the correct order.1170 1171    Trailing zeros are stripped from the release so that ``1.0.0 == 1``.1172 1173    The suffix is a flat 6-int tuple that encodes pre/post/dev:1174    ``(pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)``1175 1176    pre_rank: dev-only=-1, a=0, b=1, rc=2, no-pre=31177        Dev-only releases (no pre or post) get -1 so they sort before1178        any alpha/beta/rc.  Releases without a pre-release tag get 31179        so they sort after rc.1180    post_rank: no-post=0, post=11181        Releases without a post segment sort before those with one.1182    dev_rank: dev=0, no-dev=11183        Releases without a dev segment sort after those with one.1184 1185    Local segments use ``(n, "")`` for ints and ``(-1, s)`` for strings,1186    following PEP 440: strings sort before ints, strings compare1187    lexicographically, ints compare numerically, and shorter segments1188    sort before longer when prefixes match.  Versions without a local1189    segment sort before those with one (3-tuple < 4-tuple).1190 1191    >>> _cmpkey(0, (1, 0, 0), None, None, None, None)1192    (0, (1,), (3, 0, 0, 0, 1, 0))1193    >>> _cmpkey(0, (1,), ("a", 1), None, None, None)1194    (0, (1,), (0, 1, 0, 0, 1, 0))1195    >>> _cmpkey(0, (1,), None, None, None, ("ubuntu", 1))1196    (0, (1,), (3, 0, 0, 0, 1, 0), ((-1, 'ubuntu'), (1, '')))1197    """1198    # Strip trailing zeros: 1.0.0 compares equal to 1.1199    len_release = len(release)1200    i = len_release

Showing the first 1,200 of 1232 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai