CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
version.py583 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 itertools13import re14from typing import Any, Callable, NamedTuple, SupportsInt, Tuple, Union15 16from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType17 18__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"]19 20LocalType = Tuple[Union[int, str], ...]21 22CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]23CmpLocalType = Union[24    NegativeInfinityType,25    Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],26]27CmpKey = Tuple[28    int,29    Tuple[int, ...],30    CmpPrePostDevType,31    CmpPrePostDevType,32    CmpPrePostDevType,33    CmpLocalType,34]35VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]36 37 38class _Version(NamedTuple):39    epoch: int40    release: tuple[int, ...]41    dev: tuple[str, int] | None42    pre: tuple[str, int] | None43    post: tuple[str, int] | None44    local: LocalType | None45 46 47def parse(version: str) -> Version:48    """Parse the given version string.49 50    >>> parse('1.0.dev1')51    <Version('1.0.dev1')>52 53    :param version: The version string to parse.54    :raises InvalidVersion: When the version string is not a valid version.55    """56    return Version(version)57 58 59class InvalidVersion(ValueError):60    """Raised when a version string is not a valid version.61 62    >>> Version("invalid")63    Traceback (most recent call last):64        ...65    packaging.version.InvalidVersion: Invalid version: 'invalid'66    """67 68 69class _BaseVersion:70    _key: tuple[Any, ...]71 72    def __hash__(self) -> int:73        return hash(self._key)74 75    # Please keep the duplicated `isinstance` check76    # in the six comparisons hereunder77    # unless you find a way to avoid adding overhead function calls.78    def __lt__(self, other: _BaseVersion) -> bool:79        if not isinstance(other, _BaseVersion):80            return NotImplemented81 82        return self._key < other._key83 84    def __le__(self, other: _BaseVersion) -> bool:85        if not isinstance(other, _BaseVersion):86            return NotImplemented87 88        return self._key <= other._key89 90    def __eq__(self, other: object) -> bool:91        if not isinstance(other, _BaseVersion):92            return NotImplemented93 94        return self._key == other._key95 96    def __ge__(self, other: _BaseVersion) -> bool:97        if not isinstance(other, _BaseVersion):98            return NotImplemented99 100        return self._key >= other._key101 102    def __gt__(self, other: _BaseVersion) -> bool:103        if not isinstance(other, _BaseVersion):104            return NotImplemented105 106        return self._key > other._key107 108    def __ne__(self, other: object) -> bool:109        if not isinstance(other, _BaseVersion):110            return NotImplemented111 112        return self._key != other._key113 114 115# Deliberately not anchored to the start and end of the string, to make it116# easier for 3rd party code to reuse117_VERSION_PATTERN = r"""118    v?119    (?:120        (?:(?P<epoch>[0-9]+)!)?                           # epoch121        (?P<release>[0-9]+(?:\.[0-9]+)*)                  # release segment122        (?P<pre>                                          # pre-release123            [-_\.]?124            (?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)125            [-_\.]?126            (?P<pre_n>[0-9]+)?127        )?128        (?P<post>                                         # post release129            (?:-(?P<post_n1>[0-9]+))130            |131            (?:132                [-_\.]?133                (?P<post_l>post|rev|r)134                [-_\.]?135                (?P<post_n2>[0-9]+)?136            )137        )?138        (?P<dev>                                          # dev release139            [-_\.]?140            (?P<dev_l>dev)141            [-_\.]?142            (?P<dev_n>[0-9]+)?143        )?144    )145    (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version146"""147 148VERSION_PATTERN = _VERSION_PATTERN149"""150A string containing the regular expression used to match a valid version.151 152The pattern is not anchored at either end, and is intended for embedding in larger153expressions (for example, matching a version number as part of a file name). The154regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``155flags set.156 157:meta hide-value:158"""159 160 161class Version(_BaseVersion):162    """This class abstracts handling of a project's versions.163 164    A :class:`Version` instance is comparison aware and can be compared and165    sorted using the standard Python interfaces.166 167    >>> v1 = Version("1.0a5")168    >>> v2 = Version("1.0")169    >>> v1170    <Version('1.0a5')>171    >>> v2172    <Version('1.0')>173    >>> v1 < v2174    True175    >>> v1 == v2176    False177    >>> v1 > v2178    False179    >>> v1 >= v2180    False181    >>> v1 <= v2182    True183    """184 185    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)186    _key: CmpKey187 188    def __init__(self, version: str) -> None:189        """Initialize a Version object.190 191        :param version:192            The string representation of a version which will be parsed and normalized193            before use.194        :raises InvalidVersion:195            If the ``version`` does not conform to PEP 440 in any way then this196            exception will be raised.197        """198 199        # Validate the version and parse it into pieces200        match = self._regex.search(version)201        if not match:202            raise InvalidVersion(f"Invalid version: {version!r}")203 204        # Store the parsed out pieces of the version205        self._version = _Version(206            epoch=int(match.group("epoch")) if match.group("epoch") else 0,207            release=tuple(int(i) for i in match.group("release").split(".")),208            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),209            post=_parse_letter_version(210                match.group("post_l"), match.group("post_n1") or match.group("post_n2")211            ),212            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),213            local=_parse_local_version(match.group("local")),214        )215 216        # Generate a key which will be used for sorting217        self._key = _cmpkey(218            self._version.epoch,219            self._version.release,220            self._version.pre,221            self._version.post,222            self._version.dev,223            self._version.local,224        )225 226    def __repr__(self) -> str:227        """A representation of the Version that shows all internal state.228 229        >>> Version('1.0.0')230        <Version('1.0.0')>231        """232        return f"<Version('{self}')>"233 234    def __str__(self) -> str:235        """A string representation of the version that can be round-tripped.236 237        >>> str(Version("1.0a5"))238        '1.0a5'239        """240        parts = []241 242        # Epoch243        if self.epoch != 0:244            parts.append(f"{self.epoch}!")245 246        # Release segment247        parts.append(".".join(str(x) for x in self.release))248 249        # Pre-release250        if self.pre is not None:251            parts.append("".join(str(x) for x in self.pre))252 253        # Post-release254        if self.post is not None:255            parts.append(f".post{self.post}")256 257        # Development release258        if self.dev is not None:259            parts.append(f".dev{self.dev}")260 261        # Local version segment262        if self.local is not None:263            parts.append(f"+{self.local}")264 265        return "".join(parts)266 267    @property268    def epoch(self) -> int:269        """The epoch of the version.270 271        >>> Version("2.0.0").epoch272        0273        >>> Version("1!2.0.0").epoch274        1275        """276        return self._version.epoch277 278    @property279    def release(self) -> tuple[int, ...]:280        """The components of the "release" segment of the version.281 282        >>> Version("1.2.3").release283        (1, 2, 3)284        >>> Version("2.0.0").release285        (2, 0, 0)286        >>> Version("1!2.0.0.post0").release287        (2, 0, 0)288 289        Includes trailing zeroes but not the epoch or any pre-release / development /290        post-release suffixes.291        """292        return self._version.release293 294    @property295    def pre(self) -> tuple[str, int] | None:296        """The pre-release segment of the version.297 298        >>> print(Version("1.2.3").pre)299        None300        >>> Version("1.2.3a1").pre301        ('a', 1)302        >>> Version("1.2.3b1").pre303        ('b', 1)304        >>> Version("1.2.3rc1").pre305        ('rc', 1)306        """307        return self._version.pre308 309    @property310    def post(self) -> int | None:311        """The post-release number of the version.312 313        >>> print(Version("1.2.3").post)314        None315        >>> Version("1.2.3.post1").post316        1317        """318        return self._version.post[1] if self._version.post else None319 320    @property321    def dev(self) -> int | None:322        """The development number of the version.323 324        >>> print(Version("1.2.3").dev)325        None326        >>> Version("1.2.3.dev1").dev327        1328        """329        return self._version.dev[1] if self._version.dev else None330 331    @property332    def local(self) -> str | None:333        """The local version segment of the version.334 335        >>> print(Version("1.2.3").local)336        None337        >>> Version("1.2.3+abc").local338        'abc'339        """340        if self._version.local:341            return ".".join(str(x) for x in self._version.local)342        else:343            return None344 345    @property346    def public(self) -> str:347        """The public portion of the version.348 349        >>> Version("1.2.3").public350        '1.2.3'351        >>> Version("1.2.3+abc").public352        '1.2.3'353        >>> Version("1!1.2.3dev1+abc").public354        '1!1.2.3.dev1'355        """356        return str(self).split("+", 1)[0]357 358    @property359    def base_version(self) -> str:360        """The "base version" of the version.361 362        >>> Version("1.2.3").base_version363        '1.2.3'364        >>> Version("1.2.3+abc").base_version365        '1.2.3'366        >>> Version("1!1.2.3dev1+abc").base_version367        '1!1.2.3'368 369        The "base version" is the public version of the project without any pre or post370        release markers.371        """372        parts = []373 374        # Epoch375        if self.epoch != 0:376            parts.append(f"{self.epoch}!")377 378        # Release segment379        parts.append(".".join(str(x) for x in self.release))380 381        return "".join(parts)382 383    @property384    def is_prerelease(self) -> bool:385        """Whether this version is a pre-release.386 387        >>> Version("1.2.3").is_prerelease388        False389        >>> Version("1.2.3a1").is_prerelease390        True391        >>> Version("1.2.3b1").is_prerelease392        True393        >>> Version("1.2.3rc1").is_prerelease394        True395        >>> Version("1.2.3dev1").is_prerelease396        True397        """398        return self.dev is not None or self.pre is not None399 400    @property401    def is_postrelease(self) -> bool:402        """Whether this version is a post-release.403 404        >>> Version("1.2.3").is_postrelease405        False406        >>> Version("1.2.3.post1").is_postrelease407        True408        """409        return self.post is not None410 411    @property412    def is_devrelease(self) -> bool:413        """Whether this version is a development release.414 415        >>> Version("1.2.3").is_devrelease416        False417        >>> Version("1.2.3.dev1").is_devrelease418        True419        """420        return self.dev is not None421 422    @property423    def major(self) -> int:424        """The first item of :attr:`release` or ``0`` if unavailable.425 426        >>> Version("1.2.3").major427        1428        """429        return self.release[0] if len(self.release) >= 1 else 0430 431    @property432    def minor(self) -> int:433        """The second item of :attr:`release` or ``0`` if unavailable.434 435        >>> Version("1.2.3").minor436        2437        >>> Version("1").minor438        0439        """440        return self.release[1] if len(self.release) >= 2 else 0441 442    @property443    def micro(self) -> int:444        """The third item of :attr:`release` or ``0`` if unavailable.445 446        >>> Version("1.2.3").micro447        3448        >>> Version("1").micro449        0450        """451        return self.release[2] if len(self.release) >= 3 else 0452 453 454class _TrimmedRelease(Version):455    @property456    def release(self) -> tuple[int, ...]:457        """458        Release segment without any trailing zeros.459 460        >>> _TrimmedRelease('1.0.0').release461        (1,)462        >>> _TrimmedRelease('0.0').release463        (0,)464        """465        rel = super().release466        nonzeros = (index for index, val in enumerate(rel) if val)467        last_nonzero = max(nonzeros, default=0)468        return rel[: last_nonzero + 1]469 470 471def _parse_letter_version(472    letter: str | None, number: str | bytes | SupportsInt | None473) -> tuple[str, int] | None:474    if letter:475        # We consider there to be an implicit 0 in a pre-release if there is476        # not a numeral associated with it.477        if number is None:478            number = 0479 480        # We normalize any letters to their lower case form481        letter = letter.lower()482 483        # We consider some words to be alternate spellings of other words and484        # in those cases we want to normalize the spellings to our preferred485        # spelling.486        if letter == "alpha":487            letter = "a"488        elif letter == "beta":489            letter = "b"490        elif letter in ["c", "pre", "preview"]:491            letter = "rc"492        elif letter in ["rev", "r"]:493            letter = "post"494 495        return letter, int(number)496 497    assert not letter498    if number:499        # We assume if we are given a number, but we are not given a letter500        # then this is using the implicit post release syntax (e.g. 1.0-1)501        letter = "post"502 503        return letter, int(number)504 505    return None506 507 508_local_version_separators = re.compile(r"[\._-]")509 510 511def _parse_local_version(local: str | None) -> LocalType | None:512    """513    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").514    """515    if local is not None:516        return tuple(517            part.lower() if not part.isdigit() else int(part)518            for part in _local_version_separators.split(local)519        )520    return None521 522 523def _cmpkey(524    epoch: int,525    release: tuple[int, ...],526    pre: tuple[str, int] | None,527    post: tuple[str, int] | None,528    dev: tuple[str, int] | None,529    local: LocalType | None,530) -> CmpKey:531    # When we compare a release version, we want to compare it with all of the532    # trailing zeros removed. So we'll use a reverse the list, drop all the now533    # leading zeros until we come to something non zero, then take the rest534    # re-reverse it back into the correct order and make it a tuple and use535    # that for our sorting key.536    _release = tuple(537        reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))538    )539 540    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.541    # We'll do this by abusing the pre segment, but we _only_ want to do this542    # if there is not a pre or a post segment. If we have one of those then543    # the normal sorting rules will handle this case correctly.544    if pre is None and post is None and dev is not None:545        _pre: CmpPrePostDevType = NegativeInfinity546    # Versions without a pre-release (except as noted above) should sort after547    # those with one.548    elif pre is None:549        _pre = Infinity550    else:551        _pre = pre552 553    # Versions without a post segment should sort before those with one.554    if post is None:555        _post: CmpPrePostDevType = NegativeInfinity556 557    else:558        _post = post559 560    # Versions without a development segment should sort after those with one.561    if dev is None:562        _dev: CmpPrePostDevType = Infinity563 564    else:565        _dev = dev566 567    if local is None:568        # Versions without a local segment should sort before those with one.569        _local: CmpLocalType = NegativeInfinity570    else:571        # Versions with a local segment need that segment parsed to implement572        # the sorting rules in PEP440.573        # - Alpha numeric segments sort before numeric segments574        # - Alpha numeric segments sort lexicographically575        # - Numeric segments sort numerically576        # - Shorter versions sort before longer versions when the prefixes577        #   match exactly578        _local = tuple(579            (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local580        )581 582    return epoch, _release, _pre, _post, _dev, _local583 
Aluode/PerceptionLabPortable · CoolFace