Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
specifiers.py1069 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.specifiers import Specifier, SpecifierSet, InvalidSpecifier8    from packaging.version import Version9"""10 11from __future__ import annotations12 13import abc14import itertools15import re16from typing import Callable, Final, Iterable, Iterator, TypeVar, Union17 18from .utils import canonicalize_version19from .version import InvalidVersion, Version20 21UnparsedVersion = Union[Version, str]22UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)23CallableOperator = Callable[[Version, str], bool]24 25 26def _coerce_version(version: UnparsedVersion) -> Version | None:27    if not isinstance(version, Version):28        try:29            version = Version(version)30        except InvalidVersion:31            return None32    return version33 34 35def _public_version(version: Version) -> Version:36    return version.__replace__(local=None)37 38 39def _base_version(version: Version) -> Version:40    return version.__replace__(pre=None, post=None, dev=None, local=None)41 42 43class InvalidSpecifier(ValueError):44    """45    Raised when attempting to create a :class:`Specifier` with a specifier46    string that is invalid.47 48    >>> Specifier("lolwat")49    Traceback (most recent call last):50        ...51    packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'52    """53 54 55class BaseSpecifier(metaclass=abc.ABCMeta):56    __slots__ = ()57    __match_args__ = ("_str",)58 59    @property60    def _str(self) -> str:61        """Internal property for match_args"""62        return str(self)63 64    @abc.abstractmethod65    def __str__(self) -> str:66        """67        Returns the str representation of this Specifier-like object. This68        should be representative of the Specifier itself.69        """70 71    @abc.abstractmethod72    def __hash__(self) -> int:73        """74        Returns a hash value for this Specifier-like object.75        """76 77    @abc.abstractmethod78    def __eq__(self, other: object) -> bool:79        """80        Returns a boolean representing whether or not the two Specifier-like81        objects are equal.82 83        :param other: The other object to check against.84        """85 86    @property87    @abc.abstractmethod88    def prereleases(self) -> bool | None:89        """Whether or not pre-releases as a whole are allowed.90 91        This can be set to either ``True`` or ``False`` to explicitly enable or disable92        prereleases or it can be set to ``None`` (the default) to use default semantics.93        """94 95    @prereleases.setter  # noqa: B02796    def prereleases(self, value: bool) -> None:97        """Setter for :attr:`prereleases`.98 99        :param value: The value to set.100        """101 102    @abc.abstractmethod103    def contains(self, item: str, prereleases: bool | None = None) -> bool:104        """105        Determines if the given item is contained within this specifier.106        """107 108    @abc.abstractmethod109    def filter(110        self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None111    ) -> Iterator[UnparsedVersionVar]:112        """113        Takes an iterable of items and filters them so that only items which114        are contained within this specifier are allowed in it.115        """116 117 118class Specifier(BaseSpecifier):119    """This class abstracts handling of version specifiers.120 121    .. tip::122 123        It is generally not required to instantiate this manually. You should instead124        prefer to work with :class:`SpecifierSet` instead, which can parse125        comma-separated version specifiers (which is what package metadata contains).126    """127 128    __slots__ = ("_prereleases", "_spec", "_spec_version")129 130    _operator_regex_str = r"""131        (?P<operator>(~=|==|!=|<=|>=|<|>|===))132        """133    _version_regex_str = r"""134        (?P<version>135            (?:136                # The identity operators allow for an escape hatch that will137                # do an exact string match of the version you wish to install.138                # This will not be parsed by PEP 440 and we cannot determine139                # any semantic meaning from it. This operator is discouraged140                # but included entirely as an escape hatch.141                (?<====)  # Only match for the identity operator142                \s*143                [^\s;)]*  # The arbitrary version can be just about anything,144                          # we match everything except for whitespace, a145                          # semi-colon for marker support, and a closing paren146                          # since versions can be enclosed in them.147            )148            |149            (?:150                # The (non)equality operators allow for wild card and local151                # versions to be specified so we have to define these two152                # operators separately to enable that.153                (?<===|!=)            # Only match for equals and not equals154 155                \s*156                v?157                (?:[0-9]+!)?          # epoch158                [0-9]+(?:\.[0-9]+)*   # release159 160                # You cannot use a wild card and a pre-release, post-release, a dev or161                # local version together so group them with a | and make them optional.162                (?:163                    \.\*  # Wild card syntax of .*164                    |165                    (?:                                  # pre release166                        [-_\.]?167                        (alpha|beta|preview|pre|a|b|c|rc)168                        [-_\.]?169                        [0-9]*170                    )?171                    (?:                                  # post release172                        (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)173                    )?174                    (?:[-_\.]?dev[-_\.]?[0-9]*)?         # dev release175                    (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local176                )?177            )178            |179            (?:180                # The compatible operator requires at least two digits in the181                # release segment.182                (?<=~=)               # Only match for the compatible operator183 184                \s*185                v?186                (?:[0-9]+!)?          # epoch187                [0-9]+(?:\.[0-9]+)+   # release  (We have a + instead of a *)188                (?:                   # pre release189                    [-_\.]?190                    (alpha|beta|preview|pre|a|b|c|rc)191                    [-_\.]?192                    [0-9]*193                )?194                (?:                                   # post release195                    (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)196                )?197                (?:[-_\.]?dev[-_\.]?[0-9]*)?          # dev release198            )199            |200            (?:201                # All other operators only allow a sub set of what the202                # (non)equality operators do. Specifically they do not allow203                # local versions to be specified nor do they allow the prefix204                # matching wild cards.205                (?<!==|!=|~=)         # We have special cases for these206                                      # operators so we want to make sure they207                                      # don't match here.208 209                \s*210                v?211                (?:[0-9]+!)?          # epoch212                [0-9]+(?:\.[0-9]+)*   # release213                (?:                   # pre release214                    [-_\.]?215                    (alpha|beta|preview|pre|a|b|c|rc)216                    [-_\.]?217                    [0-9]*218                )?219                (?:                                   # post release220                    (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)221                )?222                (?:[-_\.]?dev[-_\.]?[0-9]*)?          # dev release223            )224        )225        """226 227    _regex = re.compile(228        r"\s*" + _operator_regex_str + _version_regex_str + r"\s*",229        re.VERBOSE | re.IGNORECASE,230    )231 232    _operators: Final = {233        "~=": "compatible",234        "==": "equal",235        "!=": "not_equal",236        "<=": "less_than_equal",237        ">=": "greater_than_equal",238        "<": "less_than",239        ">": "greater_than",240        "===": "arbitrary",241    }242 243    def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:244        """Initialize a Specifier instance.245 246        :param spec:247            The string representation of a specifier which will be parsed and248            normalized before use.249        :param prereleases:250            This tells the specifier if it should accept prerelease versions if251            applicable or not. The default of ``None`` will autodetect it from the252            given specifiers.253        :raises InvalidSpecifier:254            If the given specifier is invalid (i.e. bad syntax).255        """256        match = self._regex.fullmatch(spec)257        if not match:258            raise InvalidSpecifier(f"Invalid specifier: {spec!r}")259 260        self._spec: tuple[str, str] = (261            match.group("operator").strip(),262            match.group("version").strip(),263        )264 265        # Store whether or not this Specifier should accept prereleases266        self._prereleases = prereleases267 268        # Specifier version cache269        self._spec_version: tuple[str, Version] | None = None270 271    def _get_spec_version(self, version: str) -> Version | None:272        """One element cache, as only one spec Version is needed per Specifier."""273        if self._spec_version is not None and self._spec_version[0] == version:274            return self._spec_version[1]275 276        version_specifier = _coerce_version(version)277        if version_specifier is None:278            return None279 280        self._spec_version = (version, version_specifier)281        return version_specifier282 283    def _require_spec_version(self, version: str) -> Version:284        """Get spec version, asserting it's valid (not for === operator).285 286        This method should only be called for operators where version287        strings are guaranteed to be valid PEP 440 versions (not ===).288        """289        spec_version = self._get_spec_version(version)290        assert spec_version is not None291        return spec_version292 293    @property294    def prereleases(self) -> bool | None:295        # If there is an explicit prereleases set for this, then we'll just296        # blindly use that.297        if self._prereleases is not None:298            return self._prereleases299 300        # Only the "!=" operator does not imply prereleases when301        # the version in the specifier is a prerelease.302        operator, version_str = self._spec303        if operator != "!=":304            # The == specifier with trailing .* cannot include prereleases305            # e.g. "==1.0a1.*" is not valid.306            if operator == "==" and version_str.endswith(".*"):307                return False308 309            # "===" can have arbitrary string versions, so we cannot parse310            # those, we take prereleases as unknown (None) for those.311            version = self._get_spec_version(version_str)312            if version is None:313                return None314 315            # For all other operators, use the check if spec Version316            # object implies pre-releases.317            if version.is_prerelease:318                return True319 320        return False321 322    @prereleases.setter323    def prereleases(self, value: bool | None) -> None:324        self._prereleases = value325 326    @property327    def operator(self) -> str:328        """The operator of this specifier.329 330        >>> Specifier("==1.2.3").operator331        '=='332        """333        return self._spec[0]334 335    @property336    def version(self) -> str:337        """The version of this specifier.338 339        >>> Specifier("==1.2.3").version340        '1.2.3'341        """342        return self._spec[1]343 344    def __repr__(self) -> str:345        """A representation of the Specifier that shows all internal state.346 347        >>> Specifier('>=1.0.0')348        <Specifier('>=1.0.0')>349        >>> Specifier('>=1.0.0', prereleases=False)350        <Specifier('>=1.0.0', prereleases=False)>351        >>> Specifier('>=1.0.0', prereleases=True)352        <Specifier('>=1.0.0', prereleases=True)>353        """354        pre = (355            f", prereleases={self.prereleases!r}"356            if self._prereleases is not None357            else ""358        )359 360        return f"<{self.__class__.__name__}({str(self)!r}{pre})>"361 362    def __str__(self) -> str:363        """A string representation of the Specifier that can be round-tripped.364 365        >>> str(Specifier('>=1.0.0'))366        '>=1.0.0'367        >>> str(Specifier('>=1.0.0', prereleases=False))368        '>=1.0.0'369        """370        return "{}{}".format(*self._spec)371 372    @property373    def _canonical_spec(self) -> tuple[str, str]:374        operator, version = self._spec375        if operator == "===" or version.endswith(".*"):376            return operator, version377 378        spec_version = self._require_spec_version(version)379 380        canonical_version = canonicalize_version(381            spec_version, strip_trailing_zero=(operator != "~=")382        )383 384        return operator, canonical_version385 386    def __hash__(self) -> int:387        return hash(self._canonical_spec)388 389    def __eq__(self, other: object) -> bool:390        """Whether or not the two Specifier-like objects are equal.391 392        :param other: The other object to check against.393 394        The value of :attr:`prereleases` is ignored.395 396        >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")397        True398        >>> (Specifier("==1.2.3", prereleases=False) ==399        ...  Specifier("==1.2.3", prereleases=True))400        True401        >>> Specifier("==1.2.3") == "==1.2.3"402        True403        >>> Specifier("==1.2.3") == Specifier("==1.2.4")404        False405        >>> Specifier("==1.2.3") == Specifier("~=1.2.3")406        False407        """408        if isinstance(other, str):409            try:410                other = self.__class__(str(other))411            except InvalidSpecifier:412                return NotImplemented413        elif not isinstance(other, self.__class__):414            return NotImplemented415 416        return self._canonical_spec == other._canonical_spec417 418    def _get_operator(self, op: str) -> CallableOperator:419        operator_callable: CallableOperator = getattr(420            self, f"_compare_{self._operators[op]}"421        )422        return operator_callable423 424    def _compare_compatible(self, prospective: Version, spec: str) -> bool:425        # Compatible releases have an equivalent combination of >= and ==. That426        # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to427        # implement this in terms of the other specifiers instead of428        # implementing it ourselves. The only thing we need to do is construct429        # the other specifiers.430 431        # We want everything but the last item in the version, but we want to432        # ignore suffix segments.433        prefix = _version_join(434            list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]435        )436 437        # Add the prefix notation to the end of our string438        prefix += ".*"439 440        return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(441            prospective, prefix442        )443 444    def _compare_equal(self, prospective: Version, spec: str) -> bool:445        # We need special logic to handle prefix matching446        if spec.endswith(".*"):447            # In the case of prefix matching we want to ignore local segment.448            normalized_prospective = canonicalize_version(449                _public_version(prospective), strip_trailing_zero=False450            )451            # Get the normalized version string ignoring the trailing .*452            normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)453            # Split the spec out by bangs and dots, and pretend that there is454            # an implicit dot in between a release segment and a pre-release segment.455            split_spec = _version_split(normalized_spec)456 457            # Split the prospective version out by bangs and dots, and pretend458            # that there is an implicit dot in between a release segment and459            # a pre-release segment.460            split_prospective = _version_split(normalized_prospective)461 462            # 0-pad the prospective version before shortening it to get the correct463            # shortened version.464            padded_prospective, _ = _pad_version(split_prospective, split_spec)465 466            # Shorten the prospective version to be the same length as the spec467            # so that we can determine if the specifier is a prefix of the468            # prospective version or not.469            shortened_prospective = padded_prospective[: len(split_spec)]470 471            return shortened_prospective == split_spec472        else:473            # Convert our spec string into a Version474            spec_version = self._require_spec_version(spec)475 476            # If the specifier does not have a local segment, then we want to477            # act as if the prospective version also does not have a local478            # segment.479            if not spec_version.local:480                prospective = _public_version(prospective)481 482            return prospective == spec_version483 484    def _compare_not_equal(self, prospective: Version, spec: str) -> bool:485        return not self._compare_equal(prospective, spec)486 487    def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:488        # NB: Local version identifiers are NOT permitted in the version489        # specifier, so local version labels can be universally removed from490        # the prospective version.491        return _public_version(prospective) <= self._require_spec_version(spec)492 493    def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:494        # NB: Local version identifiers are NOT permitted in the version495        # specifier, so local version labels can be universally removed from496        # the prospective version.497        return _public_version(prospective) >= self._require_spec_version(spec)498 499    def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:500        # Convert our spec to a Version instance, since we'll want to work with501        # it as a version.502        spec = self._require_spec_version(spec_str)503 504        # Check to see if the prospective version is less than the spec505        # version. If it's not we can short circuit and just return False now506        # instead of doing extra unneeded work.507        if not prospective < spec:508            return False509 510        # This special case is here so that, unless the specifier itself511        # includes is a pre-release version, that we do not accept pre-release512        # versions for the version mentioned in the specifier (e.g. <3.1 should513        # not match 3.1.dev0, but should match 3.0.dev0).514        if (515            not spec.is_prerelease516            and prospective.is_prerelease517            and _base_version(prospective) == _base_version(spec)518        ):519            return False520 521        # If we've gotten to here, it means that prospective version is both522        # less than the spec version *and* it's not a pre-release of the same523        # version in the spec.524        return True525 526    def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:527        # Convert our spec to a Version instance, since we'll want to work with528        # it as a version.529        spec = self._require_spec_version(spec_str)530 531        # Check to see if the prospective version is greater than the spec532        # version. If it's not we can short circuit and just return False now533        # instead of doing extra unneeded work.534        if not prospective > spec:535            return False536 537        # This special case is here so that, unless the specifier itself538        # includes is a post-release version, that we do not accept539        # post-release versions for the version mentioned in the specifier540        # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).541        if (542            not spec.is_postrelease543            and prospective.is_postrelease544            and _base_version(prospective) == _base_version(spec)545        ):546            return False547 548        # Ensure that we do not allow a local version of the version mentioned549        # in the specifier, which is technically greater than, to match.550        if prospective.local is not None and _base_version(551            prospective552        ) == _base_version(spec):553            return False554 555        # If we've gotten to here, it means that prospective version is both556        # greater than the spec version *and* it's not a pre-release of the557        # same version in the spec.558        return True559 560    def _compare_arbitrary(self, prospective: Version | str, spec: str) -> bool:561        return str(prospective).lower() == str(spec).lower()562 563    def __contains__(self, item: str | Version) -> bool:564        """Return whether or not the item is contained in this specifier.565 566        :param item: The item to check for.567 568        This is used for the ``in`` operator and behaves the same as569        :meth:`contains` with no ``prereleases`` argument passed.570 571        >>> "1.2.3" in Specifier(">=1.2.3")572        True573        >>> Version("1.2.3") in Specifier(">=1.2.3")574        True575        >>> "1.0.0" in Specifier(">=1.2.3")576        False577        >>> "1.3.0a1" in Specifier(">=1.2.3")578        True579        >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)580        True581        """582        return self.contains(item)583 584    def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:585        """Return whether or not the item is contained in this specifier.586 587        :param item:588            The item to check for, which can be a version string or a589            :class:`Version` instance.590        :param prereleases:591            Whether or not to match prereleases with this Specifier. If set to592            ``None`` (the default), it will follow the recommendation from593            :pep:`440` and match prereleases, as there are no other versions.594 595        >>> Specifier(">=1.2.3").contains("1.2.3")596        True597        >>> Specifier(">=1.2.3").contains(Version("1.2.3"))598        True599        >>> Specifier(">=1.2.3").contains("1.0.0")600        False601        >>> Specifier(">=1.2.3").contains("1.3.0a1")602        True603        >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1")604        False605        >>> Specifier(">=1.2.3").contains("1.3.0a1")606        True607        """608 609        return bool(list(self.filter([item], prereleases=prereleases)))610 611    def filter(612        self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None613    ) -> Iterator[UnparsedVersionVar]:614        """Filter items in the given iterable, that match the specifier.615 616        :param iterable:617            An iterable that can contain version strings and :class:`Version` instances.618            The items in the iterable will be filtered according to the specifier.619        :param prereleases:620            Whether or not to allow prereleases in the returned iterator. If set to621            ``None`` (the default), it will follow the recommendation from :pep:`440`622            and match prereleases if there are no other versions.623 624        >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))625        ['1.3']626        >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))627        ['1.2.3', '1.3', <Version('1.4')>]628        >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))629        ['1.5a1']630        >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))631        ['1.3', '1.5a1']632        >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))633        ['1.3', '1.5a1']634        """635        prereleases_versions = []636        found_non_prereleases = False637 638        # Determine if to include prereleases by default639        include_prereleases = (640            prereleases if prereleases is not None else self.prereleases641        )642 643        # Get the matching operator644        operator_callable = self._get_operator(self.operator)645 646        # Filter versions647        for version in iterable:648            parsed_version = _coerce_version(version)649            if parsed_version is None:650                # === operator can match arbitrary (non-version) strings651                if self.operator == "===" and self._compare_arbitrary(652                    version, self.version653                ):654                    yield version655            elif operator_callable(parsed_version, self.version):656                # If it's not a prerelease or prereleases are allowed, yield it directly657                if not parsed_version.is_prerelease or include_prereleases:658                    found_non_prereleases = True659                    yield version660                # Otherwise collect prereleases for potential later use661                elif prereleases is None and self._prereleases is not False:662                    prereleases_versions.append(version)663 664        # If no non-prereleases were found and prereleases weren't665        # explicitly forbidden, yield the collected prereleases666        if (667            not found_non_prereleases668            and prereleases is None669            and self._prereleases is not False670        ):671            yield from prereleases_versions672 673 674_prefix_regex = re.compile(r"([0-9]+)((?:a|b|c|rc)[0-9]+)")675 676 677def _version_split(version: str) -> list[str]:678    """Split version into components.679 680    The split components are intended for version comparison. The logic does681    not attempt to retain the original version string, so joining the682    components back with :func:`_version_join` may not produce the original683    version string.684    """685    result: list[str] = []686 687    epoch, _, rest = version.rpartition("!")688    result.append(epoch or "0")689 690    for item in rest.split("."):691        match = _prefix_regex.fullmatch(item)692        if match:693            result.extend(match.groups())694        else:695            result.append(item)696    return result697 698 699def _version_join(components: list[str]) -> str:700    """Join split version components into a version string.701 702    This function assumes the input came from :func:`_version_split`, where the703    first component must be the epoch (either empty or numeric), and all other704    components numeric.705    """706    epoch, *rest = components707    return f"{epoch}!{'.'.join(rest)}"708 709 710def _is_not_suffix(segment: str) -> bool:711    return not any(712        segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")713    )714 715 716def _pad_version(left: list[str], right: list[str]) -> tuple[list[str], list[str]]:717    left_split, right_split = [], []718 719    # Get the release segment of our versions720    left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))721    right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))722 723    # Get the rest of our versions724    left_split.append(left[len(left_split[0]) :])725    right_split.append(right[len(right_split[0]) :])726 727    # Insert our padding728    left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))729    right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))730 731    return (732        list(itertools.chain.from_iterable(left_split)),733        list(itertools.chain.from_iterable(right_split)),734    )735 736 737class SpecifierSet(BaseSpecifier):738    """This class abstracts handling of a set of version specifiers.739 740    It can be passed a single specifier (``>=3.0``), a comma-separated list of741    specifiers (``>=3.0,!=3.1``), or no specifier at all.742    """743 744    __slots__ = ("_prereleases", "_specs")745 746    def __init__(747        self,748        specifiers: str | Iterable[Specifier] = "",749        prereleases: bool | None = None,750    ) -> None:751        """Initialize a SpecifierSet instance.752 753        :param specifiers:754            The string representation of a specifier or a comma-separated list of755            specifiers which will be parsed and normalized before use.756            May also be an iterable of ``Specifier`` instances, which will be used757            as is.758        :param prereleases:759            This tells the SpecifierSet if it should accept prerelease versions if760            applicable or not. The default of ``None`` will autodetect it from the761            given specifiers.762 763        :raises InvalidSpecifier:764            If the given ``specifiers`` are not parseable than this exception will be765            raised.766        """767 768        if isinstance(specifiers, str):769            # Split on `,` to break each individual specifier into its own item, and770            # strip each item to remove leading/trailing whitespace.771            split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]772 773            # Make each individual specifier a Specifier and save in a frozen set774            # for later.775            self._specs = frozenset(map(Specifier, split_specifiers))776        else:777            # Save the supplied specifiers in a frozen set.778            self._specs = frozenset(specifiers)779 780        # Store our prereleases value so we can use it later to determine if781        # we accept prereleases or not.782        self._prereleases = prereleases783 784    @property785    def prereleases(self) -> bool | None:786        # If we have been given an explicit prerelease modifier, then we'll787        # pass that through here.788        if self._prereleases is not None:789            return self._prereleases790 791        # If we don't have any specifiers, and we don't have a forced value,792        # then we'll just return None since we don't know if this should have793        # pre-releases or not.794        if not self._specs:795            return None796 797        # Otherwise we'll see if any of the given specifiers accept798        # prereleases, if any of them do we'll return True, otherwise False.799        if any(s.prereleases for s in self._specs):800            return True801 802        return None803 804    @prereleases.setter805    def prereleases(self, value: bool | None) -> None:806        self._prereleases = value807 808    def __repr__(self) -> str:809        """A representation of the specifier set that shows all internal state.810 811        Note that the ordering of the individual specifiers within the set may not812        match the input string.813 814        >>> SpecifierSet('>=1.0.0,!=2.0.0')815        <SpecifierSet('!=2.0.0,>=1.0.0')>816        >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)817        <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>818        >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)819        <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>820        """821        pre = (822            f", prereleases={self.prereleases!r}"823            if self._prereleases is not None824            else ""825        )826 827        return f"<SpecifierSet({str(self)!r}{pre})>"828 829    def __str__(self) -> str:830        """A string representation of the specifier set that can be round-tripped.831 832        Note that the ordering of the individual specifiers within the set may not833        match the input string.834 835        >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))836        '!=1.0.1,>=1.0.0'837        >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))838        '!=1.0.1,>=1.0.0'839        """840        return ",".join(sorted(str(s) for s in self._specs))841 842    def __hash__(self) -> int:843        return hash(self._specs)844 845    def __and__(self, other: SpecifierSet | str) -> SpecifierSet:846        """Return a SpecifierSet which is a combination of the two sets.847 848        :param other: The other object to combine with.849 850        >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'851        <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>852        >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')853        <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>854        """855        if isinstance(other, str):856            other = SpecifierSet(other)857        elif not isinstance(other, SpecifierSet):858            return NotImplemented859 860        specifier = SpecifierSet()861        specifier._specs = frozenset(self._specs | other._specs)862 863        if self._prereleases is None and other._prereleases is not None:864            specifier._prereleases = other._prereleases865        elif (866            self._prereleases is not None and other._prereleases is None867        ) or self._prereleases == other._prereleases:868            specifier._prereleases = self._prereleases869        else:870            raise ValueError(871                "Cannot combine SpecifierSets with True and False prerelease overrides."872            )873 874        return specifier875 876    def __eq__(self, other: object) -> bool:877        """Whether or not the two SpecifierSet-like objects are equal.878 879        :param other: The other object to check against.880 881        The value of :attr:`prereleases` is ignored.882 883        >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")884        True885        >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==886        ...  SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))887        True888        >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"889        True890        >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")891        False892        >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")893        False894        """895        if isinstance(other, (str, Specifier)):896            other = SpecifierSet(str(other))897        elif not isinstance(other, SpecifierSet):898            return NotImplemented899 900        return self._specs == other._specs901 902    def __len__(self) -> int:903        """Returns the number of specifiers in this specifier set."""904        return len(self._specs)905 906    def __iter__(self) -> Iterator[Specifier]:907        """908        Returns an iterator over all the underlying :class:`Specifier` instances909        in this specifier set.910 911        >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)912        [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]913        """914        return iter(self._specs)915 916    def __contains__(self, item: UnparsedVersion) -> bool:917        """Return whether or not the item is contained in this specifier.918 919        :param item: The item to check for.920 921        This is used for the ``in`` operator and behaves the same as922        :meth:`contains` with no ``prereleases`` argument passed.923 924        >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")925        True926        >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")927        True928        >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")929        False930        >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")931        True932        >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)933        True934        """935        return self.contains(item)936 937    def contains(938        self,939        item: UnparsedVersion,940        prereleases: bool | None = None,941        installed: bool | None = None,942    ) -> bool:943        """Return whether or not the item is contained in this SpecifierSet.944 945        :param item:946            The item to check for, which can be a version string or a947            :class:`Version` instance.948        :param prereleases:949            Whether or not to match prereleases with this SpecifierSet. If set to950            ``None`` (the default), it will follow the recommendation from :pep:`440`951            and match prereleases, as there are no other versions.952        :param installed:953            Whether or not the item is installed. If set to ``True``, it will954            accept prerelease versions even if the specifier does not allow them.955 956        >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")957        True958        >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))959        True960        >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")961        False962        >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")963        True964        >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")965        False966        >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)967        True968        """969        version = _coerce_version(item)970 971        if version is not None and installed and version.is_prerelease:972            prereleases = True973 974        check_item = item if version is None else version975        return bool(list(self.filter([check_item], prereleases=prereleases)))976 977    def filter(978        self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None979    ) -> Iterator[UnparsedVersionVar]:980        """Filter items in the given iterable, that match the specifiers in this set.981 982        :param iterable:983            An iterable that can contain version strings and :class:`Version` instances.984            The items in the iterable will be filtered according to the specifier.985        :param prereleases:986            Whether or not to allow prereleases in the returned iterator. If set to987            ``None`` (the default), it will follow the recommendation from :pep:`440`988            and match prereleases if there are no other versions.989 990        >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))991        ['1.3']992        >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))993        ['1.3', <Version('1.4')>]994        >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))995        ['1.5a1']996        >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))997        ['1.3', '1.5a1']998        >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))999        ['1.3', '1.5a1']1000 1001        An "empty" SpecifierSet will filter items based on the presence of prerelease1002        versions in the set.1003 1004        >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))1005        ['1.3']1006        >>> list(SpecifierSet("").filter(["1.5a1"]))1007        ['1.5a1']1008        >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))1009        ['1.3', '1.5a1']1010        >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))1011        ['1.3', '1.5a1']1012        """1013        # Determine if we're forcing a prerelease or not, if we're not forcing1014        # one for this particular filter call, then we'll use whatever the1015        # SpecifierSet thinks for whether or not we should support prereleases.1016        if prereleases is None and self.prereleases is not None:1017            prereleases = self.prereleases1018 1019        # If we have any specifiers, then we want to wrap our iterable in the1020        # filter method for each one, this will act as a logical AND amongst1021        # each specifier.1022        if self._specs:1023            # When prereleases is None, we need to let all versions through1024            # the individual filters, then decide about prereleases at the end1025            # based on whether any non-prereleases matched ALL specs.1026            for spec in self._specs:1027                iterable = spec.filter(1028                    iterable, prereleases=True if prereleases is None else prereleases1029                )1030 1031            if prereleases is not None:1032                # If we have a forced prereleases value,1033                # we can immediately return the iterator.1034                return iter(iterable)1035        else:1036            # Handle empty SpecifierSet cases where prereleases is not None.1037            if prereleases is True:1038                return iter(iterable)1039 1040            if prereleases is False:1041                return (1042                    item1043                    for item in iterable1044                    if (version := _coerce_version(item)) is None1045                    or not version.is_prerelease1046                )1047 1048        # Finally if prereleases is None, apply PEP 440 logic:1049        # exclude prereleases unless there are no final releases that matched.1050        filtered_items: list[UnparsedVersionVar] = []1051        found_prereleases: list[UnparsedVersionVar] = []1052        found_final_release = False1053 1054        for item in iterable:1055            parsed_version = _coerce_version(item)1056            # Arbitrary strings are always included as it is not1057            # possible to determine if they are prereleases,1058            # and they have already passed all specifiers.1059            if parsed_version is None:1060                filtered_items.append(item)1061                found_prereleases.append(item)1062            elif parsed_version.is_prerelease:1063                found_prereleases.append(item)1064            else:1065                filtered_items.append(item)1066                found_final_release = True1067 1068        return iter(filtered_items if found_final_release else found_prereleases)1069 
codekingpro/portable-devtools · Team Ai