Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
version.py564 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 10import itertools11import re12from typing import Any, Callable, NamedTuple, Optional, SupportsInt, Tuple, Union13 14from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType15 16__all__ = ["VERSION_PATTERN", "parse", "Version", "InvalidVersion"]17 18LocalType = Tuple[Union[int, str], ...]19 20CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]21CmpLocalType = Union[22    NegativeInfinityType,23    Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],24]25CmpKey = Tuple[26    int,27    Tuple[int, ...],28    CmpPrePostDevType,29    CmpPrePostDevType,30    CmpPrePostDevType,31    CmpLocalType,32]33VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]34 35 36class _Version(NamedTuple):37    epoch: int38    release: Tuple[int, ...]39    dev: Optional[Tuple[str, int]]40    pre: Optional[Tuple[str, int]]41    post: Optional[Tuple[str, int]]42    local: Optional[LocalType]43 44 45def parse(version: str) -> "Version":46    """Parse the given version string.47 48    >>> parse('1.0.dev1')49    <Version('1.0.dev1')>50 51    :param version: The version string to parse.52    :raises InvalidVersion: When the version string is not a valid version.53    """54    return Version(version)55 56 57class InvalidVersion(ValueError):58    """Raised when a version string is not a valid version.59 60    >>> Version("invalid")61    Traceback (most recent call last):62        ...63    packaging.version.InvalidVersion: Invalid version: 'invalid'64    """65 66 67class _BaseVersion:68    _key: Tuple[Any, ...]69 70    def __hash__(self) -> int:71        return hash(self._key)72 73    # Please keep the duplicated `isinstance` check74    # in the six comparisons hereunder75    # unless you find a way to avoid adding overhead function calls.76    def __lt__(self, other: "_BaseVersion") -> bool:77        if not isinstance(other, _BaseVersion):78            return NotImplemented79 80        return self._key < other._key81 82    def __le__(self, other: "_BaseVersion") -> bool:83        if not isinstance(other, _BaseVersion):84            return NotImplemented85 86        return self._key <= other._key87 88    def __eq__(self, other: object) -> bool:89        if not isinstance(other, _BaseVersion):90            return NotImplemented91 92        return self._key == other._key93 94    def __ge__(self, other: "_BaseVersion") -> bool:95        if not isinstance(other, _BaseVersion):96            return NotImplemented97 98        return self._key >= other._key99 100    def __gt__(self, other: "_BaseVersion") -> bool:101        if not isinstance(other, _BaseVersion):102            return NotImplemented103 104        return self._key > other._key105 106    def __ne__(self, other: object) -> bool:107        if not isinstance(other, _BaseVersion):108            return NotImplemented109 110        return self._key != other._key111 112 113# Deliberately not anchored to the start and end of the string, to make it114# easier for 3rd party code to reuse115_VERSION_PATTERN = r"""116    v?117    (?:118        (?:(?P<epoch>[0-9]+)!)?                           # epoch119        (?P<release>[0-9]+(?:\.[0-9]+)*)                  # release segment120        (?P<pre>                                          # pre-release121            [-_\.]?122            (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)123            [-_\.]?124            (?P<pre_n>[0-9]+)?125        )?126        (?P<post>                                         # post release127            (?:-(?P<post_n1>[0-9]+))128            |129            (?:130                [-_\.]?131                (?P<post_l>post|rev|r)132                [-_\.]?133                (?P<post_n2>[0-9]+)?134            )135        )?136        (?P<dev>                                          # dev release137            [-_\.]?138            (?P<dev_l>dev)139            [-_\.]?140            (?P<dev_n>[0-9]+)?141        )?142    )143    (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version144"""145 146VERSION_PATTERN = _VERSION_PATTERN147"""148A string containing the regular expression used to match a valid version.149 150The pattern is not anchored at either end, and is intended for embedding in larger151expressions (for example, matching a version number as part of a file name). The152regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``153flags set.154 155:meta hide-value:156"""157 158 159class Version(_BaseVersion):160    """This class abstracts handling of a project's versions.161 162    A :class:`Version` instance is comparison aware and can be compared and163    sorted using the standard Python interfaces.164 165    >>> v1 = Version("1.0a5")166    >>> v2 = Version("1.0")167    >>> v1168    <Version('1.0a5')>169    >>> v2170    <Version('1.0')>171    >>> v1 < v2172    True173    >>> v1 == v2174    False175    >>> v1 > v2176    False177    >>> v1 >= v2178    False179    >>> v1 <= v2180    True181    """182 183    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)184    _key: CmpKey185 186    def __init__(self, version: str) -> None:187        """Initialize a Version object.188 189        :param version:190            The string representation of a version which will be parsed and normalized191            before use.192        :raises InvalidVersion:193            If the ``version`` does not conform to PEP 440 in any way then this194            exception will be raised.195        """196 197        # Validate the version and parse it into pieces198        match = self._regex.search(version)199        if not match:200            raise InvalidVersion(f"Invalid version: '{version}'")201 202        # Store the parsed out pieces of the version203        self._version = _Version(204            epoch=int(match.group("epoch")) if match.group("epoch") else 0,205            release=tuple(int(i) for i in match.group("release").split(".")),206            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),207            post=_parse_letter_version(208                match.group("post_l"), match.group("post_n1") or match.group("post_n2")209            ),210            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),211            local=_parse_local_version(match.group("local")),212        )213 214        # Generate a key which will be used for sorting215        self._key = _cmpkey(216            self._version.epoch,217            self._version.release,218            self._version.pre,219            self._version.post,220            self._version.dev,221            self._version.local,222        )223 224    def __repr__(self) -> str:225        """A representation of the Version that shows all internal state.226 227        >>> Version('1.0.0')228        <Version('1.0.0')>229        """230        return f"<Version('{self}')>"231 232    def __str__(self) -> str:233        """A string representation of the version that can be rounded-tripped.234 235        >>> str(Version("1.0a5"))236        '1.0a5'237        """238        parts = []239 240        # Epoch241        if self.epoch != 0:242            parts.append(f"{self.epoch}!")243 244        # Release segment245        parts.append(".".join(str(x) for x in self.release))246 247        # Pre-release248        if self.pre is not None:249            parts.append("".join(str(x) for x in self.pre))250 251        # Post-release252        if self.post is not None:253            parts.append(f".post{self.post}")254 255        # Development release256        if self.dev is not None:257            parts.append(f".dev{self.dev}")258 259        # Local version segment260        if self.local is not None:261            parts.append(f"+{self.local}")262 263        return "".join(parts)264 265    @property266    def epoch(self) -> int:267        """The epoch of the version.268 269        >>> Version("2.0.0").epoch270        0271        >>> Version("1!2.0.0").epoch272        1273        """274        return self._version.epoch275 276    @property277    def release(self) -> Tuple[int, ...]:278        """The components of the "release" segment of the version.279 280        >>> Version("1.2.3").release281        (1, 2, 3)282        >>> Version("2.0.0").release283        (2, 0, 0)284        >>> Version("1!2.0.0.post0").release285        (2, 0, 0)286 287        Includes trailing zeroes but not the epoch or any pre-release / development /288        post-release suffixes.289        """290        return self._version.release291 292    @property293    def pre(self) -> Optional[Tuple[str, int]]:294        """The pre-release segment of the version.295 296        >>> print(Version("1.2.3").pre)297        None298        >>> Version("1.2.3a1").pre299        ('a', 1)300        >>> Version("1.2.3b1").pre301        ('b', 1)302        >>> Version("1.2.3rc1").pre303        ('rc', 1)304        """305        return self._version.pre306 307    @property308    def post(self) -> Optional[int]:309        """The post-release number of the version.310 311        >>> print(Version("1.2.3").post)312        None313        >>> Version("1.2.3.post1").post314        1315        """316        return self._version.post[1] if self._version.post else None317 318    @property319    def dev(self) -> Optional[int]:320        """The development number of the version.321 322        >>> print(Version("1.2.3").dev)323        None324        >>> Version("1.2.3.dev1").dev325        1326        """327        return self._version.dev[1] if self._version.dev else None328 329    @property330    def local(self) -> Optional[str]:331        """The local version segment of the version.332 333        >>> print(Version("1.2.3").local)334        None335        >>> Version("1.2.3+abc").local336        'abc'337        """338        if self._version.local:339            return ".".join(str(x) for x in self._version.local)340        else:341            return None342 343    @property344    def public(self) -> str:345        """The public portion of the version.346 347        >>> Version("1.2.3").public348        '1.2.3'349        >>> Version("1.2.3+abc").public350        '1.2.3'351        >>> Version("1.2.3+abc.dev1").public352        '1.2.3'353        """354        return str(self).split("+", 1)[0]355 356    @property357    def base_version(self) -> str:358        """The "base version" of the version.359 360        >>> Version("1.2.3").base_version361        '1.2.3'362        >>> Version("1.2.3+abc").base_version363        '1.2.3'364        >>> Version("1!1.2.3+abc.dev1").base_version365        '1!1.2.3'366 367        The "base version" is the public version of the project without any pre or post368        release markers.369        """370        parts = []371 372        # Epoch373        if self.epoch != 0:374            parts.append(f"{self.epoch}!")375 376        # Release segment377        parts.append(".".join(str(x) for x in self.release))378 379        return "".join(parts)380 381    @property382    def is_prerelease(self) -> bool:383        """Whether this version is a pre-release.384 385        >>> Version("1.2.3").is_prerelease386        False387        >>> Version("1.2.3a1").is_prerelease388        True389        >>> Version("1.2.3b1").is_prerelease390        True391        >>> Version("1.2.3rc1").is_prerelease392        True393        >>> Version("1.2.3dev1").is_prerelease394        True395        """396        return self.dev is not None or self.pre is not None397 398    @property399    def is_postrelease(self) -> bool:400        """Whether this version is a post-release.401 402        >>> Version("1.2.3").is_postrelease403        False404        >>> Version("1.2.3.post1").is_postrelease405        True406        """407        return self.post is not None408 409    @property410    def is_devrelease(self) -> bool:411        """Whether this version is a development release.412 413        >>> Version("1.2.3").is_devrelease414        False415        >>> Version("1.2.3.dev1").is_devrelease416        True417        """418        return self.dev is not None419 420    @property421    def major(self) -> int:422        """The first item of :attr:`release` or ``0`` if unavailable.423 424        >>> Version("1.2.3").major425        1426        """427        return self.release[0] if len(self.release) >= 1 else 0428 429    @property430    def minor(self) -> int:431        """The second item of :attr:`release` or ``0`` if unavailable.432 433        >>> Version("1.2.3").minor434        2435        >>> Version("1").minor436        0437        """438        return self.release[1] if len(self.release) >= 2 else 0439 440    @property441    def micro(self) -> int:442        """The third item of :attr:`release` or ``0`` if unavailable.443 444        >>> Version("1.2.3").micro445        3446        >>> Version("1").micro447        0448        """449        return self.release[2] if len(self.release) >= 3 else 0450 451 452def _parse_letter_version(453    letter: Optional[str], number: Union[str, bytes, SupportsInt, None]454) -> Optional[Tuple[str, int]]:455 456    if letter:457        # We consider there to be an implicit 0 in a pre-release if there is458        # not a numeral associated with it.459        if number is None:460            number = 0461 462        # We normalize any letters to their lower case form463        letter = letter.lower()464 465        # We consider some words to be alternate spellings of other words and466        # in those cases we want to normalize the spellings to our preferred467        # spelling.468        if letter == "alpha":469            letter = "a"470        elif letter == "beta":471            letter = "b"472        elif letter in ["c", "pre", "preview"]:473            letter = "rc"474        elif letter in ["rev", "r"]:475            letter = "post"476 477        return letter, int(number)478    if not letter and number:479        # We assume if we are given a number, but we are not given a letter480        # then this is using the implicit post release syntax (e.g. 1.0-1)481        letter = "post"482 483        return letter, int(number)484 485    return None486 487 488_local_version_separators = re.compile(r"[\._-]")489 490 491def _parse_local_version(local: Optional[str]) -> Optional[LocalType]:492    """493    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").494    """495    if local is not None:496        return tuple(497            part.lower() if not part.isdigit() else int(part)498            for part in _local_version_separators.split(local)499        )500    return None501 502 503def _cmpkey(504    epoch: int,505    release: Tuple[int, ...],506    pre: Optional[Tuple[str, int]],507    post: Optional[Tuple[str, int]],508    dev: Optional[Tuple[str, int]],509    local: Optional[LocalType],510) -> CmpKey:511 512    # When we compare a release version, we want to compare it with all of the513    # trailing zeros removed. So we'll use a reverse the list, drop all the now514    # leading zeros until we come to something non zero, then take the rest515    # re-reverse it back into the correct order and make it a tuple and use516    # that for our sorting key.517    _release = tuple(518        reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))519    )520 521    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.522    # We'll do this by abusing the pre segment, but we _only_ want to do this523    # if there is not a pre or a post segment. If we have one of those then524    # the normal sorting rules will handle this case correctly.525    if pre is None and post is None and dev is not None:526        _pre: CmpPrePostDevType = NegativeInfinity527    # Versions without a pre-release (except as noted above) should sort after528    # those with one.529    elif pre is None:530        _pre = Infinity531    else:532        _pre = pre533 534    # Versions without a post segment should sort before those with one.535    if post is None:536        _post: CmpPrePostDevType = NegativeInfinity537 538    else:539        _post = post540 541    # Versions without a development segment should sort after those with one.542    if dev is None:543        _dev: CmpPrePostDevType = Infinity544 545    else:546        _dev = dev547 548    if local is None:549        # Versions without a local segment should sort before those with one.550        _local: CmpLocalType = NegativeInfinity551    else:552        # Versions with a local segment need that segment parsed to implement553        # the sorting rules in PEP440.554        # - Alpha numeric segments sort before numeric segments555        # - Alpha numeric segments sort lexicographically556        # - Numeric segments sort numerically557        # - Shorter versions sort before longer versions when the prefixes558        #   match exactly559        _local = tuple(560            (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local561        )562 563    return epoch, _release, _pre, _post, _dev, _local564 
codekingpro/portable-devtools · Team Ai