Buckets:
| # This file is dual licensed under the terms of the Apache License, Version | |
| # 2.0, and the BSD License. See the LICENSE file in the root of this repository | |
| # for complete details. | |
| """ | |
| .. testsetup:: | |
| from packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier | |
| from packaging.version import Version | |
| """ | |
| from __future__ import annotations | |
| import abc | |
| import itertools | |
| import re | |
| from typing import Callable, Final, Iterable, Iterator, TypeVar, Union | |
| from .utils import canonicalize_version | |
| from .version import InvalidVersion, Version | |
| UnparsedVersion = Union[Version, str] | |
| UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion) | |
| CallableOperator = Callable[[Version, str], bool] | |
| def _coerce_version(version: UnparsedVersion) -> Version | None: | |
| if not isinstance(version, Version): | |
| try: | |
| version = Version(version) | |
| except InvalidVersion: | |
| return None | |
| return version | |
| def _public_version(version: Version) -> Version: | |
| return version.__replace__(local=None) | |
| def _base_version(version: Version) -> Version: | |
| return version.__replace__(pre=None, post=None, dev=None, local=None) | |
| class InvalidSpecifier(ValueError): | |
| """ | |
| Raised when attempting to create a :class:`Specifier` with a specifier | |
| string that is invalid. | |
| >>> Specifier("lolwat") | |
| Traceback (most recent call last): | |
| ... | |
| packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat' | |
| """ | |
| class BaseSpecifier(metaclass=abc.ABCMeta): | |
| __slots__ = () | |
| __match_args__ = ("_str",) | |
| def _str(self) -> str: | |
| """Internal property for match_args""" | |
| return str(self) | |
| def __str__(self) -> str: | |
| """ | |
| Returns the str representation of this Specifier-like object. This | |
| should be representative of the Specifier itself. | |
| """ | |
| def __hash__(self) -> int: | |
| """ | |
| Returns a hash value for this Specifier-like object. | |
| """ | |
| def __eq__(self, other: object) -> bool: | |
| """ | |
| Returns a boolean representing whether or not the two Specifier-like | |
| objects are equal. | |
| :param other: The other object to check against. | |
| """ | |
| def prereleases(self) -> bool | None: | |
| """Whether or not pre-releases as a whole are allowed. | |
| This can be set to either ``True`` or ``False`` to explicitly enable or disable | |
| prereleases or it can be set to ``None`` (the default) to use default semantics. | |
| """ | |
| # noqa: B027 | |
| def prereleases(self, value: bool) -> None: | |
| """Setter for :attr:`prereleases`. | |
| :param value: The value to set. | |
| """ | |
| def contains(self, item: str, prereleases: bool | None = None) -> bool: | |
| """ | |
| Determines if the given item is contained within this specifier. | |
| """ | |
| def filter( | |
| self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None | |
| ) -> Iterator[UnparsedVersionVar]: | |
| """ | |
| Takes an iterable of items and filters them so that only items which | |
| are contained within this specifier are allowed in it. | |
| """ | |
| class Specifier(BaseSpecifier): | |
| """This class abstracts handling of version specifiers. | |
| .. tip:: | |
| It is generally not required to instantiate this manually. You should instead | |
| prefer to work with :class:`SpecifierSet` instead, which can parse | |
| comma-separated version specifiers (which is what package metadata contains). | |
| """ | |
| __slots__ = ("_prereleases", "_spec", "_spec_version") | |
| _operator_regex_str = r""" | |
| (?P<operator>(~=|==|!=|<=|>=|<|>|===)) | |
| """ | |
| _version_regex_str = r""" | |
| (?P<version> | |
| (?: | |
| # The identity operators allow for an escape hatch that will | |
| # do an exact string match of the version you wish to install. | |
| # This will not be parsed by PEP 440 and we cannot determine | |
| # any semantic meaning from it. This operator is discouraged | |
| # but included entirely as an escape hatch. | |
| (?<====) # Only match for the identity operator | |
| \s* | |
| [^\s;)]* # The arbitrary version can be just about anything, | |
| # we match everything except for whitespace, a | |
| # semi-colon for marker support, and a closing paren | |
| # since versions can be enclosed in them. | |
| ) | |
| | | |
| (?: | |
| # The (non)equality operators allow for wild card and local | |
| # versions to be specified so we have to define these two | |
| # operators separately to enable that. | |
| (?<===|!=) # Only match for equals and not equals | |
| \s* | |
| v? | |
| (?:[0-9]+!)? # epoch | |
| [0-9]+(?:\.[0-9]+)* # release | |
| # You cannot use a wild card and a pre-release, post-release, a dev or | |
| # local version together so group them with a | and make them optional. | |
| (?: | |
| \.\* # Wild card syntax of .* | |
| | | |
| (?: # pre release | |
| [-_\.]? | |
| (alpha|beta|preview|pre|a|b|c|rc) | |
| [-_\.]? | |
| [0-9]* | |
| )? | |
| (?: # post release | |
| (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) | |
| )? | |
| (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release | |
| (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local | |
| )? | |
| ) | |
| | | |
| (?: | |
| # The compatible operator requires at least two digits in the | |
| # release segment. | |
| (?<=~=) # Only match for the compatible operator | |
| \s* | |
| v? | |
| (?:[0-9]+!)? # epoch | |
| [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) | |
| (?: # pre release | |
| [-_\.]? | |
| (alpha|beta|preview|pre|a|b|c|rc) | |
| [-_\.]? | |
| [0-9]* | |
| )? | |
| (?: # post release | |
| (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) | |
| )? | |
| (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release | |
| ) | |
| | | |
| (?: | |
| # All other operators only allow a sub set of what the | |
| # (non)equality operators do. Specifically they do not allow | |
| # local versions to be specified nor do they allow the prefix | |
| # matching wild cards. | |
| (?<!==|!=|~=) # We have special cases for these | |
| # operators so we want to make sure they | |
| # don't match here. | |
| \s* | |
| v? | |
| (?:[0-9]+!)? # epoch | |
| [0-9]+(?:\.[0-9]+)* # release | |
| (?: # pre release | |
| [-_\.]? | |
| (alpha|beta|preview|pre|a|b|c|rc) | |
| [-_\.]? | |
| [0-9]* | |
| )? | |
| (?: # post release | |
| (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) | |
| )? | |
| (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release | |
| ) | |
| ) | |
| """ | |
| _regex = re.compile( | |
| r"\s*" + _operator_regex_str + _version_regex_str + r"\s*", | |
| re.VERBOSE | re.IGNORECASE, | |
| ) | |
| _operators: Final = { | |
| "~=": "compatible", | |
| "==": "equal", | |
| "!=": "not_equal", | |
| "<=": "less_than_equal", | |
| ">=": "greater_than_equal", | |
| "<": "less_than", | |
| ">": "greater_than", | |
| "===": "arbitrary", | |
| } | |
| def __init__(self, spec: str = "", prereleases: bool | None = None) -> None: | |
| """Initialize a Specifier instance. | |
| :param spec: | |
| The string representation of a specifier which will be parsed and | |
| normalized before use. | |
| :param prereleases: | |
| This tells the specifier if it should accept prerelease versions if | |
| applicable or not. The default of ``None`` will autodetect it from the | |
| given specifiers. | |
| :raises InvalidSpecifier: | |
| If the given specifier is invalid (i.e. bad syntax). | |
| """ | |
| match = self._regex.fullmatch(spec) | |
| if not match: | |
| raise InvalidSpecifier(f"Invalid specifier: {spec!r}") | |
| self._spec: tuple[str, str] = ( | |
| match.group("operator").strip(), | |
| match.group("version").strip(), | |
| ) | |
| # Store whether or not this Specifier should accept prereleases | |
| self._prereleases = prereleases | |
| # Specifier version cache | |
| self._spec_version: tuple[str, Version] | None = None | |
| def _get_spec_version(self, version: str) -> Version | None: | |
| """One element cache, as only one spec Version is needed per Specifier.""" | |
| if self._spec_version is not None and self._spec_version[0] == version: | |
| return self._spec_version[1] | |
| version_specifier = _coerce_version(version) | |
| if version_specifier is None: | |
| return None | |
| self._spec_version = (version, version_specifier) | |
| return version_specifier | |
| def _require_spec_version(self, version: str) -> Version: | |
| """Get spec version, asserting it's valid (not for === operator). | |
| This method should only be called for operators where version | |
| strings are guaranteed to be valid PEP 440 versions (not ===). | |
| """ | |
| spec_version = self._get_spec_version(version) | |
| assert spec_version is not None | |
| return spec_version | |
| def prereleases(self) -> bool | None: | |
| # If there is an explicit prereleases set for this, then we'll just | |
| # blindly use that. | |
| if self._prereleases is not None: | |
| return self._prereleases | |
| # Only the "!=" operator does not imply prereleases when | |
| # the version in the specifier is a prerelease. | |
| operator, version_str = self._spec | |
| if operator != "!=": | |
| # The == specifier with trailing .* cannot include prereleases | |
| # e.g. "==1.0a1.*" is not valid. | |
| if operator == "==" and version_str.endswith(".*"): | |
| return False | |
| # "===" can have arbitrary string versions, so we cannot parse | |
| # those, we take prereleases as unknown (None) for those. | |
| version = self._get_spec_version(version_str) | |
| if version is None: | |
| return None | |
| # For all other operators, use the check if spec Version | |
| # object implies pre-releases. | |
| if version.is_prerelease: | |
| return True | |
| return False | |
| def prereleases(self, value: bool | None) -> None: | |
| self._prereleases = value | |
| def operator(self) -> str: | |
| """The operator of this specifier. | |
| >>> Specifier("==1.2.3").operator | |
| '==' | |
| """ | |
| return self._spec[0] | |
| def version(self) -> str: | |
| """The version of this specifier. | |
| >>> Specifier("==1.2.3").version | |
| '1.2.3' | |
| """ | |
| return self._spec[1] | |
| def __repr__(self) -> str: | |
| """A representation of the Specifier that shows all internal state. | |
| >>> Specifier('>=1.0.0') | |
| <Specifier('>=1.0.0')> | |
| >>> Specifier('>=1.0.0', prereleases=False) | |
| <Specifier('>=1.0.0', prereleases=False)> | |
| >>> Specifier('>=1.0.0', prereleases=True) | |
| <Specifier('>=1.0.0', prereleases=True)> | |
| """ | |
| pre = ( | |
| f", prereleases={self.prereleases!r}" | |
| if self._prereleases is not None | |
| else "" | |
| ) | |
| return f"<{self.__class__.__name__}({str(self)!r}{pre})>" | |
| def __str__(self) -> str: | |
| """A string representation of the Specifier that can be round-tripped. | |
| >>> str(Specifier('>=1.0.0')) | |
| '>=1.0.0' | |
| >>> str(Specifier('>=1.0.0', prereleases=False)) | |
| '>=1.0.0' | |
| """ | |
| return "{}{}".format(*self._spec) | |
| def _canonical_spec(self) -> tuple[str, str]: | |
| operator, version = self._spec | |
| if operator == "===" or version.endswith(".*"): | |
| return operator, version | |
| spec_version = self._require_spec_version(version) | |
| canonical_version = canonicalize_version( | |
| spec_version, strip_trailing_zero=(operator != "~=") | |
| ) | |
| return operator, canonical_version | |
| def __hash__(self) -> int: | |
| return hash(self._canonical_spec) | |
| def __eq__(self, other: object) -> bool: | |
| """Whether or not the two Specifier-like objects are equal. | |
| :param other: The other object to check against. | |
| The value of :attr:`prereleases` is ignored. | |
| >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0") | |
| True | |
| >>> (Specifier("==1.2.3", prereleases=False) == | |
| ... Specifier("==1.2.3", prereleases=True)) | |
| True | |
| >>> Specifier("==1.2.3") == "==1.2.3" | |
| True | |
| >>> Specifier("==1.2.3") == Specifier("==1.2.4") | |
| False | |
| >>> Specifier("==1.2.3") == Specifier("~=1.2.3") | |
| False | |
| """ | |
| if isinstance(other, str): | |
| try: | |
| other = self.__class__(str(other)) | |
| except InvalidSpecifier: | |
| return NotImplemented | |
| elif not isinstance(other, self.__class__): | |
| return NotImplemented | |
| return self._canonical_spec == other._canonical_spec | |
| def _get_operator(self, op: str) -> CallableOperator: | |
| operator_callable: CallableOperator = getattr( | |
| self, f"_compare_{self._operators[op]}" | |
| ) | |
| return operator_callable | |
| def _compare_compatible(self, prospective: Version, spec: str) -> bool: | |
| # Compatible releases have an equivalent combination of >= and ==. That | |
| # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to | |
| # implement this in terms of the other specifiers instead of | |
| # implementing it ourselves. The only thing we need to do is construct | |
| # the other specifiers. | |
| # We want everything but the last item in the version, but we want to | |
| # ignore suffix segments. | |
| prefix = _version_join( | |
| list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] | |
| ) | |
| # Add the prefix notation to the end of our string | |
| prefix += ".*" | |
| return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( | |
| prospective, prefix | |
| ) | |
| def _compare_equal(self, prospective: Version, spec: str) -> bool: | |
| # We need special logic to handle prefix matching | |
| if spec.endswith(".*"): | |
| # In the case of prefix matching we want to ignore local segment. | |
| normalized_prospective = canonicalize_version( | |
| _public_version(prospective), strip_trailing_zero=False | |
| ) | |
| # Get the normalized version string ignoring the trailing .* | |
| normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False) | |
| # Split the spec out by bangs and dots, and pretend that there is | |
| # an implicit dot in between a release segment and a pre-release segment. | |
| split_spec = _version_split(normalized_spec) | |
| # Split the prospective version out by bangs and dots, and pretend | |
| # that there is an implicit dot in between a release segment and | |
| # a pre-release segment. | |
| split_prospective = _version_split(normalized_prospective) | |
| # 0-pad the prospective version before shortening it to get the correct | |
| # shortened version. | |
| padded_prospective, _ = _pad_version(split_prospective, split_spec) | |
| # Shorten the prospective version to be the same length as the spec | |
| # so that we can determine if the specifier is a prefix of the | |
| # prospective version or not. | |
| shortened_prospective = padded_prospective[: len(split_spec)] | |
| return shortened_prospective == split_spec | |
| else: | |
| # Convert our spec string into a Version | |
| spec_version = self._require_spec_version(spec) | |
| # If the specifier does not have a local segment, then we want to | |
| # act as if the prospective version also does not have a local | |
| # segment. | |
| if not spec_version.local: | |
| prospective = _public_version(prospective) | |
| return prospective == spec_version | |
| def _compare_not_equal(self, prospective: Version, spec: str) -> bool: | |
| return not self._compare_equal(prospective, spec) | |
| def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool: | |
| # NB: Local version identifiers are NOT permitted in the version | |
| # specifier, so local version labels can be universally removed from | |
| # the prospective version. | |
| return _public_version(prospective) <= self._require_spec_version(spec) | |
| def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool: | |
| # NB: Local version identifiers are NOT permitted in the version | |
| # specifier, so local version labels can be universally removed from | |
| # the prospective version. | |
| return _public_version(prospective) >= self._require_spec_version(spec) | |
| def _compare_less_than(self, prospective: Version, spec_str: str) -> bool: | |
| # Convert our spec to a Version instance, since we'll want to work with | |
| # it as a version. | |
| spec = self._require_spec_version(spec_str) | |
| # Check to see if the prospective version is less than the spec | |
| # version. If it's not we can short circuit and just return False now | |
| # instead of doing extra unneeded work. | |
| if not prospective < spec: | |
| return False | |
| # This special case is here so that, unless the specifier itself | |
| # includes is a pre-release version, that we do not accept pre-release | |
| # versions for the version mentioned in the specifier (e.g. <3.1 should | |
| # not match 3.1.dev0, but should match 3.0.dev0). | |
| if ( | |
| not spec.is_prerelease | |
| and prospective.is_prerelease | |
| and _base_version(prospective) == _base_version(spec) | |
| ): | |
| return False | |
| # If we've gotten to here, it means that prospective version is both | |
| # less than the spec version *and* it's not a pre-release of the same | |
| # version in the spec. | |
| return True | |
| def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool: | |
| # Convert our spec to a Version instance, since we'll want to work with | |
| # it as a version. | |
| spec = self._require_spec_version(spec_str) | |
| # Check to see if the prospective version is greater than the spec | |
| # version. If it's not we can short circuit and just return False now | |
| # instead of doing extra unneeded work. | |
| if not prospective > spec: | |
| return False | |
| # This special case is here so that, unless the specifier itself | |
| # includes is a post-release version, that we do not accept | |
| # post-release versions for the version mentioned in the specifier | |
| # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). | |
| if ( | |
| not spec.is_postrelease | |
| and prospective.is_postrelease | |
| and _base_version(prospective) == _base_version(spec) | |
| ): | |
| return False | |
| # Ensure that we do not allow a local version of the version mentioned | |
| # in the specifier, which is technically greater than, to match. | |
| if prospective.local is not None and _base_version( | |
| prospective | |
| ) == _base_version(spec): | |
| return False | |
| # If we've gotten to here, it means that prospective version is both | |
| # greater than the spec version *and* it's not a pre-release of the | |
| # same version in the spec. | |
| return True | |
| def _compare_arbitrary(self, prospective: Version | str, spec: str) -> bool: | |
| return str(prospective).lower() == str(spec).lower() | |
| def __contains__(self, item: str | Version) -> bool: | |
| """Return whether or not the item is contained in this specifier. | |
| :param item: The item to check for. | |
| This is used for the ``in`` operator and behaves the same as | |
| :meth:`contains` with no ``prereleases`` argument passed. | |
| >>> "1.2.3" in Specifier(">=1.2.3") | |
| True | |
| >>> Version("1.2.3") in Specifier(">=1.2.3") | |
| True | |
| >>> "1.0.0" in Specifier(">=1.2.3") | |
| False | |
| >>> "1.3.0a1" in Specifier(">=1.2.3") | |
| True | |
| >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True) | |
| True | |
| """ | |
| return self.contains(item) | |
| def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool: | |
| """Return whether or not the item is contained in this specifier. | |
| :param item: | |
| The item to check for, which can be a version string or a | |
| :class:`Version` instance. | |
| :param prereleases: | |
| Whether or not to match prereleases with this Specifier. If set to | |
| ``None`` (the default), it will follow the recommendation from | |
| :pep:`440` and match prereleases, as there are no other versions. | |
| >>> Specifier(">=1.2.3").contains("1.2.3") | |
| True | |
| >>> Specifier(">=1.2.3").contains(Version("1.2.3")) | |
| True | |
| >>> Specifier(">=1.2.3").contains("1.0.0") | |
| False | |
| >>> Specifier(">=1.2.3").contains("1.3.0a1") | |
| True | |
| >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1") | |
| False | |
| >>> Specifier(">=1.2.3").contains("1.3.0a1") | |
| True | |
| """ | |
| return bool(list(self.filter([item], prereleases=prereleases))) | |
| def filter( | |
| self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None | |
| ) -> Iterator[UnparsedVersionVar]: | |
| """Filter items in the given iterable, that match the specifier. | |
| :param iterable: | |
| An iterable that can contain version strings and :class:`Version` instances. | |
| The items in the iterable will be filtered according to the specifier. | |
| :param prereleases: | |
| Whether or not to allow prereleases in the returned iterator. If set to | |
| ``None`` (the default), it will follow the recommendation from :pep:`440` | |
| and match prereleases if there are no other versions. | |
| >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) | |
| ['1.3'] | |
| >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")])) | |
| ['1.2.3', '1.3', <Version('1.4')>] | |
| >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"])) | |
| ['1.5a1'] | |
| >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) | |
| ['1.3', '1.5a1'] | |
| >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) | |
| ['1.3', '1.5a1'] | |
| """ | |
| prereleases_versions = [] | |
| found_non_prereleases = False | |
| # Determine if to include prereleases by default | |
| include_prereleases = ( | |
| prereleases if prereleases is not None else self.prereleases | |
| ) | |
| # Get the matching operator | |
| operator_callable = self._get_operator(self.operator) | |
| # Filter versions | |
| for version in iterable: | |
| parsed_version = _coerce_version(version) | |
| if parsed_version is None: | |
| # === operator can match arbitrary (non-version) strings | |
| if self.operator == "===" and self._compare_arbitrary( | |
| version, self.version | |
| ): | |
| yield version | |
| elif operator_callable(parsed_version, self.version): | |
| # If it's not a prerelease or prereleases are allowed, yield it directly | |
| if not parsed_version.is_prerelease or include_prereleases: | |
| found_non_prereleases = True | |
| yield version | |
| # Otherwise collect prereleases for potential later use | |
| elif prereleases is None and self._prereleases is not False: | |
| prereleases_versions.append(version) | |
| # If no non-prereleases were found and prereleases weren't | |
| # explicitly forbidden, yield the collected prereleases | |
| if ( | |
| not found_non_prereleases | |
| and prereleases is None | |
| and self._prereleases is not False | |
| ): | |
| yield from prereleases_versions | |
| _prefix_regex = re.compile(r"([0-9]+)((?:a|b|c|rc)[0-9]+)") | |
| def _version_split(version: str) -> list[str]: | |
| """Split version into components. | |
| The split components are intended for version comparison. The logic does | |
| not attempt to retain the original version string, so joining the | |
| components back with :func:`_version_join` may not produce the original | |
| version string. | |
| """ | |
| result: list[str] = [] | |
| epoch, _, rest = version.rpartition("!") | |
| result.append(epoch or "0") | |
| for item in rest.split("."): | |
| match = _prefix_regex.fullmatch(item) | |
| if match: | |
| result.extend(match.groups()) | |
| else: | |
| result.append(item) | |
| return result | |
| def _version_join(components: list[str]) -> str: | |
| """Join split version components into a version string. | |
| This function assumes the input came from :func:`_version_split`, where the | |
| first component must be the epoch (either empty or numeric), and all other | |
| components numeric. | |
| """ | |
| epoch, *rest = components | |
| return f"{epoch}!{'.'.join(rest)}" | |
| def _is_not_suffix(segment: str) -> bool: | |
| return not any( | |
| segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") | |
| ) | |
| def _pad_version(left: list[str], right: list[str]) -> tuple[list[str], list[str]]: | |
| left_split, right_split = [], [] | |
| # Get the release segment of our versions | |
| left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) | |
| right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) | |
| # Get the rest of our versions | |
| left_split.append(left[len(left_split[0]) :]) | |
| right_split.append(right[len(right_split[0]) :]) | |
| # Insert our padding | |
| left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) | |
| right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) | |
| return ( | |
| list(itertools.chain.from_iterable(left_split)), | |
| list(itertools.chain.from_iterable(right_split)), | |
| ) | |
| class SpecifierSet(BaseSpecifier): | |
| """This class abstracts handling of a set of version specifiers. | |
| It can be passed a single specifier (``>=3.0``), a comma-separated list of | |
| specifiers (``>=3.0,!=3.1``), or no specifier at all. | |
| """ | |
| __slots__ = ("_prereleases", "_specs") | |
| def __init__( | |
| self, | |
| specifiers: str | Iterable[Specifier] = "", | |
| prereleases: bool | None = None, | |
| ) -> None: | |
| """Initialize a SpecifierSet instance. | |
| :param specifiers: | |
| The string representation of a specifier or a comma-separated list of | |
| specifiers which will be parsed and normalized before use. | |
| May also be an iterable of ``Specifier`` instances, which will be used | |
| as is. | |
| :param prereleases: | |
| This tells the SpecifierSet if it should accept prerelease versions if | |
| applicable or not. The default of ``None`` will autodetect it from the | |
| given specifiers. | |
| :raises InvalidSpecifier: | |
| If the given ``specifiers`` are not parseable than this exception will be | |
| raised. | |
| """ | |
| if isinstance(specifiers, str): | |
| # Split on `,` to break each individual specifier into its own item, and | |
| # strip each item to remove leading/trailing whitespace. | |
| split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] | |
| # Make each individual specifier a Specifier and save in a frozen set | |
| # for later. | |
| self._specs = frozenset(map(Specifier, split_specifiers)) | |
| else: | |
| # Save the supplied specifiers in a frozen set. | |
| self._specs = frozenset(specifiers) | |
| # Store our prereleases value so we can use it later to determine if | |
| # we accept prereleases or not. | |
| self._prereleases = prereleases | |
| def prereleases(self) -> bool | None: | |
| # If we have been given an explicit prerelease modifier, then we'll | |
| # pass that through here. | |
| if self._prereleases is not None: | |
| return self._prereleases | |
| # If we don't have any specifiers, and we don't have a forced value, | |
| # then we'll just return None since we don't know if this should have | |
| # pre-releases or not. | |
| if not self._specs: | |
| return None | |
| # Otherwise we'll see if any of the given specifiers accept | |
| # prereleases, if any of them do we'll return True, otherwise False. | |
| if any(s.prereleases for s in self._specs): | |
| return True | |
| return None | |
| def prereleases(self, value: bool | None) -> None: | |
| self._prereleases = value | |
| def __repr__(self) -> str: | |
| """A representation of the specifier set that shows all internal state. | |
| Note that the ordering of the individual specifiers within the set may not | |
| match the input string. | |
| >>> SpecifierSet('>=1.0.0,!=2.0.0') | |
| <SpecifierSet('!=2.0.0,>=1.0.0')> | |
| >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False) | |
| <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)> | |
| >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True) | |
| <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)> | |
| """ | |
| pre = ( | |
| f", prereleases={self.prereleases!r}" | |
| if self._prereleases is not None | |
| else "" | |
| ) | |
| return f"<SpecifierSet({str(self)!r}{pre})>" | |
| def __str__(self) -> str: | |
| """A string representation of the specifier set that can be round-tripped. | |
| Note that the ordering of the individual specifiers within the set may not | |
| match the input string. | |
| >>> str(SpecifierSet(">=1.0.0,!=1.0.1")) | |
| '!=1.0.1,>=1.0.0' | |
| >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False)) | |
| '!=1.0.1,>=1.0.0' | |
| """ | |
| return ",".join(sorted(str(s) for s in self._specs)) | |
| def __hash__(self) -> int: | |
| return hash(self._specs) | |
| def __and__(self, other: SpecifierSet | str) -> SpecifierSet: | |
| """Return a SpecifierSet which is a combination of the two sets. | |
| :param other: The other object to combine with. | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1' | |
| <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')> | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1') | |
| <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')> | |
| """ | |
| if isinstance(other, str): | |
| other = SpecifierSet(other) | |
| elif not isinstance(other, SpecifierSet): | |
| return NotImplemented | |
| specifier = SpecifierSet() | |
| specifier._specs = frozenset(self._specs | other._specs) | |
| if self._prereleases is None and other._prereleases is not None: | |
| specifier._prereleases = other._prereleases | |
| elif ( | |
| self._prereleases is not None and other._prereleases is None | |
| ) or self._prereleases == other._prereleases: | |
| specifier._prereleases = self._prereleases | |
| else: | |
| raise ValueError( | |
| "Cannot combine SpecifierSets with True and False prerelease overrides." | |
| ) | |
| return specifier | |
| def __eq__(self, other: object) -> bool: | |
| """Whether or not the two SpecifierSet-like objects are equal. | |
| :param other: The other object to check against. | |
| The value of :attr:`prereleases` is ignored. | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1") | |
| True | |
| >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) == | |
| ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)) | |
| True | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1" | |
| True | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0") | |
| False | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2") | |
| False | |
| """ | |
| if isinstance(other, (str, Specifier)): | |
| other = SpecifierSet(str(other)) | |
| elif not isinstance(other, SpecifierSet): | |
| return NotImplemented | |
| return self._specs == other._specs | |
| def __len__(self) -> int: | |
| """Returns the number of specifiers in this specifier set.""" | |
| return len(self._specs) | |
| def __iter__(self) -> Iterator[Specifier]: | |
| """ | |
| Returns an iterator over all the underlying :class:`Specifier` instances | |
| in this specifier set. | |
| >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str) | |
| [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>] | |
| """ | |
| return iter(self._specs) | |
| def __contains__(self, item: UnparsedVersion) -> bool: | |
| """Return whether or not the item is contained in this specifier. | |
| :param item: The item to check for. | |
| This is used for the ``in`` operator and behaves the same as | |
| :meth:`contains` with no ``prereleases`` argument passed. | |
| >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1") | |
| True | |
| >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1") | |
| True | |
| >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1") | |
| False | |
| >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1") | |
| True | |
| >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True) | |
| True | |
| """ | |
| return self.contains(item) | |
| def contains( | |
| self, | |
| item: UnparsedVersion, | |
| prereleases: bool | None = None, | |
| installed: bool | None = None, | |
| ) -> bool: | |
| """Return whether or not the item is contained in this SpecifierSet. | |
| :param item: | |
| The item to check for, which can be a version string or a | |
| :class:`Version` instance. | |
| :param prereleases: | |
| Whether or not to match prereleases with this SpecifierSet. If set to | |
| ``None`` (the default), it will follow the recommendation from :pep:`440` | |
| and match prereleases, as there are no other versions. | |
| :param installed: | |
| Whether or not the item is installed. If set to ``True``, it will | |
| accept prerelease versions even if the specifier does not allow them. | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3") | |
| True | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3")) | |
| True | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1") | |
| False | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1") | |
| True | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1") | |
| False | |
| >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True) | |
| True | |
| """ | |
| version = _coerce_version(item) | |
| if version is not None and installed and version.is_prerelease: | |
| prereleases = True | |
| check_item = item if version is None else version | |
| return bool(list(self.filter([check_item], prereleases=prereleases))) | |
| def filter( | |
| self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None | |
| ) -> Iterator[UnparsedVersionVar]: | |
| """Filter items in the given iterable, that match the specifiers in this set. | |
| :param iterable: | |
| An iterable that can contain version strings and :class:`Version` instances. | |
| The items in the iterable will be filtered according to the specifier. | |
| :param prereleases: | |
| Whether or not to allow prereleases in the returned iterator. If set to | |
| ``None`` (the default), it will follow the recommendation from :pep:`440` | |
| and match prereleases if there are no other versions. | |
| >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) | |
| ['1.3'] | |
| >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")])) | |
| ['1.3', <Version('1.4')>] | |
| >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"])) | |
| ['1.5a1'] | |
| >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) | |
| ['1.3', '1.5a1'] | |
| >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) | |
| ['1.3', '1.5a1'] | |
| An "empty" SpecifierSet will filter items based on the presence of prerelease | |
| versions in the set. | |
| >>> list(SpecifierSet("").filter(["1.3", "1.5a1"])) | |
| ['1.3'] | |
| >>> list(SpecifierSet("").filter(["1.5a1"])) | |
| ['1.5a1'] | |
| >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"])) | |
| ['1.3', '1.5a1'] | |
| >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True)) | |
| ['1.3', '1.5a1'] | |
| """ | |
| # Determine if we're forcing a prerelease or not, if we're not forcing | |
| # one for this particular filter call, then we'll use whatever the | |
| # SpecifierSet thinks for whether or not we should support prereleases. | |
| if prereleases is None and self.prereleases is not None: | |
| prereleases = self.prereleases | |
| # If we have any specifiers, then we want to wrap our iterable in the | |
| # filter method for each one, this will act as a logical AND amongst | |
| # each specifier. | |
| if self._specs: | |
| # When prereleases is None, we need to let all versions through | |
| # the individual filters, then decide about prereleases at the end | |
| # based on whether any non-prereleases matched ALL specs. | |
| for spec in self._specs: | |
| iterable = spec.filter( | |
| iterable, prereleases=True if prereleases is None else prereleases | |
| ) | |
| if prereleases is not None: | |
| # If we have a forced prereleases value, | |
| # we can immediately return the iterator. | |
| return iter(iterable) | |
| else: | |
| # Handle empty SpecifierSet cases where prereleases is not None. | |
| if prereleases is True: | |
| return iter(iterable) | |
| if prereleases is False: | |
| return ( | |
| item | |
| for item in iterable | |
| if (version := _coerce_version(item)) is None | |
| or not version.is_prerelease | |
| ) | |
| # Finally if prereleases is None, apply PEP 440 logic: | |
| # exclude prereleases unless there are no final releases that matched. | |
| filtered_items: list[UnparsedVersionVar] = [] | |
| found_prereleases: list[UnparsedVersionVar] = [] | |
| found_final_release = False | |
| for item in iterable: | |
| parsed_version = _coerce_version(item) | |
| # Arbitrary strings are always included as it is not | |
| # possible to determine if they are prereleases, | |
| # and they have already passed all specifiers. | |
| if parsed_version is None: | |
| filtered_items.append(item) | |
| found_prereleases.append(item) | |
| elif parsed_version.is_prerelease: | |
| found_prereleases.append(item) | |
| else: | |
| filtered_items.append(item) | |
| found_final_release = True | |
| return iter(filtered_items if found_final_release else found_prereleases) | |
Xet Storage Details
- Size:
- 40.8 kB
- Xet hash:
- 880a0d7191f3c118e429154c939fd45113abbb20d35078aebe7bd9fbde02a4d6
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.