Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
version.py793 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, Version8"""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 26from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType27 28if typing.TYPE_CHECKING:29    from typing_extensions import Self, Unpack30 31if sys.version_info >= (3, 13):  # pragma: no cover32    from warnings import deprecated as _deprecated33elif typing.TYPE_CHECKING:34    from typing_extensions import deprecated as _deprecated35else:  # pragma: no cover36    import functools37    import warnings38 39    def _deprecated(message: str) -> object:40        def decorator(func: object) -> object:41            @functools.wraps(func)42            def wrapper(*args: object, **kwargs: object) -> object:43                warnings.warn(44                    message,45                    category=DeprecationWarning,46                    stacklevel=2,47                )48                return func(*args, **kwargs)49 50            return wrapper51 52        return decorator53 54 55_LETTER_NORMALIZATION = {56    "alpha": "a",57    "beta": "b",58    "c": "rc",59    "pre": "rc",60    "preview": "rc",61    "rev": "post",62    "r": "post",63}64 65__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"]66 67LocalType = Tuple[Union[int, str], ...]68 69CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]70CmpLocalType = Union[71    NegativeInfinityType,72    Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],73]74CmpKey = Tuple[75    int,76    Tuple[int, ...],77    CmpPrePostDevType,78    CmpPrePostDevType,79    CmpPrePostDevType,80    CmpLocalType,81]82VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]83 84 85class _VersionReplace(TypedDict, total=False):86    epoch: int | None87    release: tuple[int, ...] | None88    pre: tuple[Literal["a", "b", "rc"], int] | None89    post: int | None90    dev: int | None91    local: str | None92 93 94def parse(version: str) -> Version:95    """Parse the given version string.96 97    >>> parse('1.0.dev1')98    <Version('1.0.dev1')>99 100    :param version: The version string to parse.101    :raises InvalidVersion: When the version string is not a valid version.102    """103    return Version(version)104 105 106class InvalidVersion(ValueError):107    """Raised when a version string is not a valid version.108 109    >>> Version("invalid")110    Traceback (most recent call last):111        ...112    packaging.version.InvalidVersion: Invalid version: 'invalid'113    """114 115 116class _BaseVersion:117    __slots__ = ()118 119    # This can also be a normal member (see the packaging_legacy package);120    # we are just requiring it to be readable. Actually defining a property121    # has runtime effect on subclasses, so it's typing only.122    if typing.TYPE_CHECKING:123 124        @property125        def _key(self) -> tuple[Any, ...]: ...126 127    def __hash__(self) -> int:128        return hash(self._key)129 130    # Please keep the duplicated `isinstance` check131    # in the six comparisons hereunder132    # unless you find a way to avoid adding overhead function calls.133    def __lt__(self, other: _BaseVersion) -> bool:134        if not isinstance(other, _BaseVersion):135            return NotImplemented136 137        return self._key < other._key138 139    def __le__(self, other: _BaseVersion) -> bool:140        if not isinstance(other, _BaseVersion):141            return NotImplemented142 143        return self._key <= other._key144 145    def __eq__(self, other: object) -> bool:146        if not isinstance(other, _BaseVersion):147            return NotImplemented148 149        return self._key == other._key150 151    def __ge__(self, other: _BaseVersion) -> bool:152        if not isinstance(other, _BaseVersion):153            return NotImplemented154 155        return self._key >= other._key156 157    def __gt__(self, other: _BaseVersion) -> bool:158        if not isinstance(other, _BaseVersion):159            return NotImplemented160 161        return self._key > other._key162 163    def __ne__(self, other: object) -> bool:164        if not isinstance(other, _BaseVersion):165            return NotImplemented166 167        return self._key != other._key168 169 170# Deliberately not anchored to the start and end of the string, to make it171# easier for 3rd party code to reuse172 173# Note that ++ doesn't behave identically on CPython and PyPy, so not using it here174_VERSION_PATTERN = r"""175    v?+                                                   # optional leading v176    (?:177        (?:(?P<epoch>[0-9]+)!)?+                          # epoch178        (?P<release>[0-9]+(?:\.[0-9]+)*+)                 # release segment179        (?P<pre>                                          # pre-release180            [._-]?+181            (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)182            [._-]?+183            (?P<pre_n>[0-9]+)?184        )?+185        (?P<post>                                         # post release186            (?:-(?P<post_n1>[0-9]+))187            |188            (?:189                [._-]?190                (?P<post_l>post|rev|r)191                [._-]?192                (?P<post_n2>[0-9]+)?193            )194        )?+195        (?P<dev>                                          # dev release196            [._-]?+197            (?P<dev_l>dev)198            [._-]?+199            (?P<dev_n>[0-9]+)?200        )?+201    )202    (?:\+203        (?P<local>                                        # local version204            [a-z0-9]+205            (?:[._-][a-z0-9]+)*+206        )207    )?+208"""209 210_VERSION_PATTERN_OLD = _VERSION_PATTERN.replace("*+", "*").replace("?+", "?")211 212# Possessive qualifiers were added in Python 3.11.213# CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795214# Older PyPy also had a bug.215VERSION_PATTERN = (216    _VERSION_PATTERN_OLD217    if (sys.implementation.name == "cpython" and sys.version_info < (3, 11, 5))218    or (sys.implementation.name == "pypy" and sys.version_info < (3, 11, 13))219    or sys.version_info < (3, 11)220    else _VERSION_PATTERN221)222"""223A string containing the regular expression used to match a valid version.224 225The pattern is not anchored at either end, and is intended for embedding in larger226expressions (for example, matching a version number as part of a file name). The227regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``228flags set.229 230:meta hide-value:231"""232 233 234# Validation pattern for local version in replace()235_LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE)236 237 238def _validate_epoch(value: object, /) -> int:239    epoch = value or 0240    if isinstance(epoch, int) and epoch >= 0:241        return epoch242    msg = f"epoch must be non-negative integer, got {epoch}"243    raise InvalidVersion(msg)244 245 246def _validate_release(value: object, /) -> tuple[int, ...]:247    release = (0,) if value is None else value248    if (249        isinstance(release, tuple)250        and len(release) > 0251        and all(isinstance(i, int) and i >= 0 for i in release)252    ):253        return release254    msg = f"release must be a non-empty tuple of non-negative integers, got {release}"255    raise InvalidVersion(msg)256 257 258def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None:259    if value is None:260        return value261    if (262        isinstance(value, tuple)263        and len(value) == 2264        and value[0] in ("a", "b", "rc")265        and isinstance(value[1], int)266        and value[1] >= 0267    ):268        return value269    msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}"270    raise InvalidVersion(msg)271 272 273def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:274    if value is None:275        return value276    if isinstance(value, int) and value >= 0:277        return ("post", value)278    msg = f"post must be non-negative integer, got {value}"279    raise InvalidVersion(msg)280 281 282def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:283    if value is None:284        return value285    if isinstance(value, int) and value >= 0:286        return ("dev", value)287    msg = f"dev must be non-negative integer, got {value}"288    raise InvalidVersion(msg)289 290 291def _validate_local(value: object, /) -> LocalType | None:292    if value is None:293        return value294    if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):295        return _parse_local_version(value)296    msg = f"local must be a valid version string, got {value!r}"297    raise InvalidVersion(msg)298 299 300# Backward compatibility for internals before 26.0. Do not use.301class _Version(NamedTuple):302    epoch: int303    release: tuple[int, ...]304    dev: tuple[str, int] | None305    pre: tuple[str, int] | None306    post: tuple[str, int] | None307    local: LocalType | None308 309 310class Version(_BaseVersion):311    """This class abstracts handling of a project's versions.312 313    A :class:`Version` instance is comparison aware and can be compared and314    sorted using the standard Python interfaces.315 316    >>> v1 = Version("1.0a5")317    >>> v2 = Version("1.0")318    >>> v1319    <Version('1.0a5')>320    >>> v2321    <Version('1.0')>322    >>> v1 < v2323    True324    >>> v1 == v2325    False326    >>> v1 > v2327    False328    >>> v1 >= v2329    False330    >>> v1 <= v2331    True332    """333 334    __slots__ = ("_dev", "_epoch", "_key_cache", "_local", "_post", "_pre", "_release")335    __match_args__ = ("_str",)336 337    _regex = re.compile(r"\s*" + VERSION_PATTERN + r"\s*", re.VERBOSE | re.IGNORECASE)338 339    _epoch: int340    _release: tuple[int, ...]341    _dev: tuple[str, int] | None342    _pre: tuple[str, int] | None343    _post: tuple[str, int] | None344    _local: LocalType | None345 346    _key_cache: CmpKey | None347 348    def __init__(self, version: str) -> None:349        """Initialize a Version object.350 351        :param version:352            The string representation of a version which will be parsed and normalized353            before use.354        :raises InvalidVersion:355            If the ``version`` does not conform to PEP 440 in any way then this356            exception will be raised.357        """358        # Validate the version and parse it into pieces359        match = self._regex.fullmatch(version)360        if not match:361            raise InvalidVersion(f"Invalid version: {version!r}")362        self._epoch = int(match.group("epoch")) if match.group("epoch") else 0363        self._release = tuple(map(int, match.group("release").split(".")))364        self._pre = _parse_letter_version(match.group("pre_l"), match.group("pre_n"))365        self._post = _parse_letter_version(366            match.group("post_l"), match.group("post_n1") or match.group("post_n2")367        )368        self._dev = _parse_letter_version(match.group("dev_l"), match.group("dev_n"))369        self._local = _parse_local_version(match.group("local"))370 371        # Key which will be used for sorting372        self._key_cache = None373 374    def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:375        epoch = _validate_epoch(kwargs["epoch"]) if "epoch" in kwargs else self._epoch376        release = (377            _validate_release(kwargs["release"])378            if "release" in kwargs379            else self._release380        )381        pre = _validate_pre(kwargs["pre"]) if "pre" in kwargs else self._pre382        post = _validate_post(kwargs["post"]) if "post" in kwargs else self._post383        dev = _validate_dev(kwargs["dev"]) if "dev" in kwargs else self._dev384        local = _validate_local(kwargs["local"]) if "local" in kwargs else self._local385 386        if (387            epoch == self._epoch388            and release == self._release389            and pre == self._pre390            and post == self._post391            and dev == self._dev392            and local == self._local393        ):394            return self395 396        new_version = self.__class__.__new__(self.__class__)397        new_version._key_cache = None398        new_version._epoch = epoch399        new_version._release = release400        new_version._pre = pre401        new_version._post = post402        new_version._dev = dev403        new_version._local = local404 405        return new_version406 407    @property408    def _key(self) -> CmpKey:409        if self._key_cache is None:410            self._key_cache = _cmpkey(411                self._epoch,412                self._release,413                self._pre,414                self._post,415                self._dev,416                self._local,417            )418        return self._key_cache419 420    @property421    @_deprecated("Version._version is private and will be removed soon")422    def _version(self) -> _Version:423        return _Version(424            self._epoch, self._release, self._dev, self._pre, self._post, self._local425        )426 427    @_version.setter428    @_deprecated("Version._version is private and will be removed soon")429    def _version(self, value: _Version) -> None:430        self._epoch = value.epoch431        self._release = value.release432        self._dev = value.dev433        self._pre = value.pre434        self._post = value.post435        self._local = value.local436        self._key_cache = None437 438    def __repr__(self) -> str:439        """A representation of the Version that shows all internal state.440 441        >>> Version('1.0.0')442        <Version('1.0.0')>443        """444        return f"<Version('{self}')>"445 446    def __str__(self) -> str:447        """A string representation of the version that can be round-tripped.448 449        >>> str(Version("1.0a5"))450        '1.0a5'451        """452        # This is a hot function, so not calling self.base_version453        version = ".".join(map(str, self.release))454 455        # Epoch456        if self.epoch:457            version = f"{self.epoch}!{version}"458 459        # Pre-release460        if self.pre is not None:461            version += "".join(map(str, self.pre))462 463        # Post-release464        if self.post is not None:465            version += f".post{self.post}"466 467        # Development release468        if self.dev is not None:469            version += f".dev{self.dev}"470 471        # Local version segment472        if self.local is not None:473            version += f"+{self.local}"474 475        return version476 477    @property478    def _str(self) -> str:479        """Internal property for match_args"""480        return str(self)481 482    @property483    def epoch(self) -> int:484        """The epoch of the version.485 486        >>> Version("2.0.0").epoch487        0488        >>> Version("1!2.0.0").epoch489        1490        """491        return self._epoch492 493    @property494    def release(self) -> tuple[int, ...]:495        """The components of the "release" segment of the version.496 497        >>> Version("1.2.3").release498        (1, 2, 3)499        >>> Version("2.0.0").release500        (2, 0, 0)501        >>> Version("1!2.0.0.post0").release502        (2, 0, 0)503 504        Includes trailing zeroes but not the epoch or any pre-release / development /505        post-release suffixes.506        """507        return self._release508 509    @property510    def pre(self) -> tuple[str, int] | None:511        """The pre-release segment of the version.512 513        >>> print(Version("1.2.3").pre)514        None515        >>> Version("1.2.3a1").pre516        ('a', 1)517        >>> Version("1.2.3b1").pre518        ('b', 1)519        >>> Version("1.2.3rc1").pre520        ('rc', 1)521        """522        return self._pre523 524    @property525    def post(self) -> int | None:526        """The post-release number of the version.527 528        >>> print(Version("1.2.3").post)529        None530        >>> Version("1.2.3.post1").post531        1532        """533        return self._post[1] if self._post else None534 535    @property536    def dev(self) -> int | None:537        """The development number of the version.538 539        >>> print(Version("1.2.3").dev)540        None541        >>> Version("1.2.3.dev1").dev542        1543        """544        return self._dev[1] if self._dev else None545 546    @property547    def local(self) -> str | None:548        """The local version segment of the version.549 550        >>> print(Version("1.2.3").local)551        None552        >>> Version("1.2.3+abc").local553        'abc'554        """555        if self._local:556            return ".".join(str(x) for x in self._local)557        else:558            return None559 560    @property561    def public(self) -> str:562        """The public portion of the version.563 564        >>> Version("1.2.3").public565        '1.2.3'566        >>> Version("1.2.3+abc").public567        '1.2.3'568        >>> Version("1!1.2.3dev1+abc").public569        '1!1.2.3.dev1'570        """571        return str(self).split("+", 1)[0]572 573    @property574    def base_version(self) -> str:575        """The "base version" of the version.576 577        >>> Version("1.2.3").base_version578        '1.2.3'579        >>> Version("1.2.3+abc").base_version580        '1.2.3'581        >>> Version("1!1.2.3dev1+abc").base_version582        '1!1.2.3'583 584        The "base version" is the public version of the project without any pre or post585        release markers.586        """587        release_segment = ".".join(map(str, self.release))588        return f"{self.epoch}!{release_segment}" if self.epoch else release_segment589 590    @property591    def is_prerelease(self) -> bool:592        """Whether this version is a pre-release.593 594        >>> Version("1.2.3").is_prerelease595        False596        >>> Version("1.2.3a1").is_prerelease597        True598        >>> Version("1.2.3b1").is_prerelease599        True600        >>> Version("1.2.3rc1").is_prerelease601        True602        >>> Version("1.2.3dev1").is_prerelease603        True604        """605        return self.dev is not None or self.pre is not None606 607    @property608    def is_postrelease(self) -> bool:609        """Whether this version is a post-release.610 611        >>> Version("1.2.3").is_postrelease612        False613        >>> Version("1.2.3.post1").is_postrelease614        True615        """616        return self.post is not None617 618    @property619    def is_devrelease(self) -> bool:620        """Whether this version is a development release.621 622        >>> Version("1.2.3").is_devrelease623        False624        >>> Version("1.2.3.dev1").is_devrelease625        True626        """627        return self.dev is not None628 629    @property630    def major(self) -> int:631        """The first item of :attr:`release` or ``0`` if unavailable.632 633        >>> Version("1.2.3").major634        1635        """636        return self.release[0] if len(self.release) >= 1 else 0637 638    @property639    def minor(self) -> int:640        """The second item of :attr:`release` or ``0`` if unavailable.641 642        >>> Version("1.2.3").minor643        2644        >>> Version("1").minor645        0646        """647        return self.release[1] if len(self.release) >= 2 else 0648 649    @property650    def micro(self) -> int:651        """The third item of :attr:`release` or ``0`` if unavailable.652 653        >>> Version("1.2.3").micro654        3655        >>> Version("1").micro656        0657        """658        return self.release[2] if len(self.release) >= 3 else 0659 660 661class _TrimmedRelease(Version):662    __slots__ = ()663 664    def __init__(self, version: str | Version) -> None:665        if isinstance(version, Version):666            self._epoch = version._epoch667            self._release = version._release668            self._dev = version._dev669            self._pre = version._pre670            self._post = version._post671            self._local = version._local672            self._key_cache = version._key_cache673            return674        super().__init__(version)  # pragma: no cover675 676    @property677    def release(self) -> tuple[int, ...]:678        """679        Release segment without any trailing zeros.680 681        >>> _TrimmedRelease('1.0.0').release682        (1,)683        >>> _TrimmedRelease('0.0').release684        (0,)685        """686        # This leaves one 0.687        rel = super().release688        len_release = len(rel)689        i = len_release690        while i > 1 and rel[i - 1] == 0:691            i -= 1692        return rel if i == len_release else rel[:i]693 694 695def _parse_letter_version(696    letter: str | None, number: str | bytes | SupportsInt | None697) -> tuple[str, int] | None:698    if letter:699        # We normalize any letters to their lower case form700        letter = letter.lower()701 702        # We consider some words to be alternate spellings of other words and703        # in those cases we want to normalize the spellings to our preferred704        # spelling.705        letter = _LETTER_NORMALIZATION.get(letter, letter)706 707        # We consider there to be an implicit 0 in a pre-release if there is708        # not a numeral associated with it.709        return letter, int(number or 0)710 711    if number:712        # We assume if we are given a number, but we are not given a letter713        # then this is using the implicit post release syntax (e.g. 1.0-1)714        return "post", int(number)715 716    return None717 718 719_local_version_separators = re.compile(r"[\._-]")720 721 722def _parse_local_version(local: str | None) -> LocalType | None:723    """724    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").725    """726    if local is not None:727        return tuple(728            part.lower() if not part.isdigit() else int(part)729            for part in _local_version_separators.split(local)730        )731    return None732 733 734def _cmpkey(735    epoch: int,736    release: tuple[int, ...],737    pre: tuple[str, int] | None,738    post: tuple[str, int] | None,739    dev: tuple[str, int] | None,740    local: LocalType | None,741) -> CmpKey:742    # When we compare a release version, we want to compare it with all of the743    # trailing zeros removed. We will use this for our sorting key.744    len_release = len(release)745    i = len_release746    while i and release[i - 1] == 0:747        i -= 1748    _release = release if i == len_release else release[:i]749 750    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.751    # We'll do this by abusing the pre segment, but we _only_ want to do this752    # if there is not a pre or a post segment. If we have one of those then753    # the normal sorting rules will handle this case correctly.754    if pre is None and post is None and dev is not None:755        _pre: CmpPrePostDevType = NegativeInfinity756    # Versions without a pre-release (except as noted above) should sort after757    # those with one.758    elif pre is None:759        _pre = Infinity760    else:761        _pre = pre762 763    # Versions without a post segment should sort before those with one.764    if post is None:765        _post: CmpPrePostDevType = NegativeInfinity766 767    else:768        _post = post769 770    # Versions without a development segment should sort after those with one.771    if dev is None:772        _dev: CmpPrePostDevType = Infinity773 774    else:775        _dev = dev776 777    if local is None:778        # Versions without a local segment should sort before those with one.779        _local: CmpLocalType = NegativeInfinity780    else:781        # Versions with a local segment need that segment parsed to implement782        # the sorting rules in PEP440.783        # - Alpha numeric segments sort before numeric segments784        # - Alpha numeric segments sort lexicographically785        # - Numeric segments sort numerically786        # - Shorter versions sort before longer versions when the prefixes787        #   match exactly788        _local = tuple(789            (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local790        )791 792    return epoch, _release, _pre, _post, _dev, _local793 
codekingpro/portable-devtools · Team Ai