Download envv/lib/python3.12/site-packages/packaging/specifiers.py from navingv/varybatch: direct link, hf CLI and curl.
- Browser
- Download file 52.8 kB
-
https://huggingface.co/navingv/varybatch/resolve/main/envv/lib/python3.12/site-packages/packaging/specifiers.py
- Command line
-
hf download hf://navingv/varybatch/envv/lib/python3.12/site-packages/packaging/specifiers.py
-
curl -L -o specifiers.py https://huggingface.co/navingv/varybatch/resolve/main/envv/lib/python3.12/site-packages/packaging/specifiers.py
52.8 kB
| # 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 re | |
| import typing | |
| from typing import ( | |
| TYPE_CHECKING, | |
| Any, | |
| Callable, | |
| Final, | |
| TypeVar, | |
| Union, | |
| ) | |
| from ._ranges import ( | |
| FULL_RANGE, | |
| bounds_for_spec, | |
| coerce_version, | |
| filter_by_ranges, | |
| intersect_specifier_bounds, | |
| matches_bounds_only, | |
| ranges_are_prerelease_only, | |
| resolve_prereleases, | |
| trim_release, | |
| ) | |
| from .utils import canonicalize_version | |
| from .version import Version | |
| if TYPE_CHECKING: | |
| import sys | |
| from collections.abc import Iterable, Iterator, Sequence | |
| if sys.version_info >= (3, 10): | |
| from typing import TypeGuard | |
| else: | |
| from typing_extensions import TypeGuard | |
| from . import ranges | |
| from ._ranges import Interval | |
| __all__ = [ | |
| "BaseSpecifier", | |
| "InvalidSpecifier", | |
| "Specifier", | |
| "SpecifierSet", | |
| ] | |
| def __dir__() -> list[str]: | |
| return __all__ | |
| def _validate_spec(spec: object, /) -> TypeGuard[tuple[str, str]]: | |
| return ( | |
| isinstance(spec, tuple) | |
| and len(spec) == 2 | |
| and isinstance(spec[0], str) | |
| and isinstance(spec[1], str) | |
| ) | |
| def _validate_pre(pre: object, /) -> TypeGuard[bool | None]: | |
| return pre is None or isinstance(pre, bool) | |
| T = TypeVar("T") | |
| UnparsedVersion = Union[Version, str] | |
| UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion) | |
| # Operators whose result is just a direct Version comparison, given a parsed | |
| # item with no local. ``<=``/``==``/``!=`` need that no-local guard because | |
| # PEP 440 strips locals on those; ``>=`` works regardless. | |
| _DIRECT_COMPARE_OPS: dict[str, Callable[[Version, Version], bool]] = { | |
| ">=": Version.__ge__, | |
| "<=": Version.__le__, | |
| "==": Version.__eq__, | |
| "!=": Version.__ne__, | |
| } | |
| def _fast_match(specifier: Specifier, parsed: Version) -> bool | None: | |
| """Match ``parsed`` against ``specifier`` without building a range. | |
| Handles ``>=``, ``<=``, ``==``, ``!=``, ``<``, ``>`` when the spec is | |
| not a wildcard and ``parsed`` has no local. Returns ``None`` when the | |
| range path must be used. Pre-release policy is left to the caller. | |
| """ | |
| op_str, ver_str = specifier._spec | |
| if ver_str.endswith(".*") or parsed.local is not None: | |
| return None | |
| direct_compare = _DIRECT_COMPARE_OPS.get(op_str) | |
| if direct_compare is not None: | |
| return direct_compare(parsed, specifier._require_spec_version(ver_str)) | |
| if op_str in ("<", ">"): | |
| spec_v = specifier._require_spec_version(ver_str) | |
| # ``<V``/``>V`` carve out V's family (pre/dev/post); that only | |
| # matters when parsed shares V's epoch and trimmed release. | |
| # Otherwise a direct cmpkey comparison is correct. | |
| if parsed.epoch != spec_v.epoch or trim_release(parsed.release) != trim_release( | |
| spec_v.release | |
| ): | |
| return parsed < spec_v if op_str == "<" else parsed > spec_v | |
| return None | |
| return 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): | |
| """ | |
| Abstract base class for :class:`Specifier` and :class:`SpecifierSet`. | |
| """ | |
| __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, | |
| key: None = ..., | |
| ) -> Iterator[UnparsedVersionVar]: ... | |
| def filter( | |
| self, | |
| iterable: Iterable[T], | |
| prereleases: bool | None = None, | |
| key: Callable[[T], UnparsedVersion] = ..., | |
| ) -> Iterator[T]: ... | |
| def filter( | |
| self, | |
| iterable: Iterable[Any], | |
| prereleases: bool | None = None, | |
| key: Callable[[Any], UnparsedVersion] | None = None, | |
| ) -> Iterator[Any]: | |
| """ | |
| 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). | |
| Instances are safe to serialize with :mod:`pickle`. They use a stable | |
| format so the same pickle can be loaded in future packaging releases. | |
| .. versionchanged:: 26.2 | |
| Added a stable pickle format. Pickles created with packaging 26.2+ can | |
| be unpickled with future releases. Backward compatibility with pickles | |
| from packaging < 26.2 is supported but may be removed in a future | |
| release. | |
| """ | |
| __slots__ = ( | |
| "_prereleases", | |
| "_ranges", | |
| "_spec", | |
| "_spec_version", | |
| ) | |
| _specifier_regex_str = r""" | |
| (?: | |
| (?: | |
| # 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 .* | |
| | | |
| (?a: # pre release | |
| [-_\.]? | |
| (alpha|beta|preview|pre|a|b|c|rc) | |
| [-_\.]? | |
| [0-9]* | |
| )? | |
| (?a: # post release | |
| (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) | |
| )? | |
| (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release | |
| (?a:\+[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. | |
| (?:<=|>=|<|>) | |
| \s* | |
| v? | |
| (?:[0-9]+!)? # epoch | |
| [0-9]+(?:\.[0-9]+)* # release | |
| (?a: # pre release | |
| [-_\.]? | |
| (alpha|beta|preview|pre|a|b|c|rc) | |
| [-_\.]? | |
| [0-9]* | |
| )? | |
| (?a: # post release | |
| (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) | |
| )? | |
| (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release | |
| ) | |
| ) | |
| """ | |
| _regex = re.compile( | |
| r"\s*" + _specifier_regex_str + r"\s*", re.VERBOSE | re.IGNORECASE | |
| ) | |
| # Legacy unused attribute, kept for backward compatibility | |
| _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). | |
| """ | |
| if not self._regex.fullmatch(spec): | |
| raise InvalidSpecifier(f"Invalid specifier: {spec!r}") | |
| spec = spec.strip() | |
| if spec.startswith("==="): | |
| operator, version = spec[:3], spec[3:].strip() | |
| elif spec.startswith(("~=", "==", "!=", "<=", ">=")): | |
| operator, version = spec[:2], spec[2:].strip() | |
| else: | |
| operator, version = spec[:1], spec[1:].strip() | |
| self._spec: tuple[str, str] = (operator, version) | |
| # Store whether or not this Specifier should accept prereleases | |
| self._prereleases = prereleases | |
| # Specifier version cache | |
| self._spec_version: tuple[str, Version] | None = None | |
| # Version range cache (populated by _to_ranges) | |
| self._ranges: Sequence[Interval] | 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 _to_ranges(self) -> Sequence[Interval]: | |
| """Convert this specifier to sorted, non-overlapping version ranges. | |
| Each standard operator maps to one or two ranges. ``===`` is | |
| modeled as full range (actual check done separately). Cached. | |
| """ | |
| if self._ranges is not None: | |
| return self._ranges | |
| op = self.operator | |
| ver_str = self.version | |
| if op == "===": | |
| result: Sequence[Interval] = FULL_RANGE | |
| else: | |
| version = self._require_spec_version(ver_str.removesuffix(".*")) | |
| result = bounds_for_spec(op, ver_str, version) | |
| self._ranges = result | |
| return result | |
| 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 == "!=": | |
| return False | |
| # 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. | |
| return version.is_prerelease | |
| def prereleases(self, value: bool | None) -> None: | |
| self._prereleases = value | |
| def __getstate__(self) -> tuple[tuple[str, str], bool | None]: | |
| # Return state as a 2-item tuple for compactness: | |
| # ((operator, version), prereleases) | |
| # Cache members are excluded and will be recomputed on demand. | |
| return (self._spec, self._prereleases) | |
| def __setstate__(self, state: object) -> None: | |
| # Always discard cached values - they will be recomputed on demand. | |
| self._spec_version = None | |
| self._ranges = None | |
| if isinstance(state, tuple): | |
| if len(state) == 2: | |
| # New format (26.2+): ((operator, version), prereleases) | |
| spec, prereleases = state | |
| if _validate_spec(spec) and _validate_pre(prereleases): | |
| self._spec = spec | |
| self._prereleases = prereleases | |
| return | |
| if len(state) == 2 and isinstance(state[1], dict): | |
| # Format (packaging 26.0-26.1): (None, {slot: value}). | |
| _, slot_dict = state | |
| spec = slot_dict.get("_spec") | |
| prereleases = slot_dict.get("_prereleases", "invalid") | |
| if _validate_spec(spec) and _validate_pre(prereleases): | |
| self._spec = spec | |
| self._prereleases = prereleases | |
| return | |
| if isinstance(state, dict): | |
| # Old format (packaging <= 25.x, no __slots__): state is a plain dict. | |
| spec = state.get("_spec") | |
| prereleases = state.get("_prereleases", "invalid") | |
| if _validate_spec(spec) and _validate_pre(prereleases): | |
| self._spec = spec | |
| self._prereleases = prereleases | |
| return | |
| raise TypeError(f"Cannot restore Specifier from {state!r}") | |
| 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 __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:`~packaging.version.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 | |
| .. versionchanged:: 26.0 | |
| With ``prereleases=None``, a prerelease now matches. A single | |
| version has no alternatives, so the :pep:`440` rule to accept | |
| prereleases when nothing else satisfies the specifier applies. | |
| Earlier versions rejected it. An unparsable version now returns | |
| ``False`` instead of raising :exc:`~packaging.version.InvalidVersion`. | |
| """ | |
| # ``===`` compares the raw string, so a Version parse here would | |
| # be wasted. | |
| if self._spec[0] == "===": | |
| return bool(list(self.filter([item], prereleases=prereleases))) | |
| parsed = coerce_version(item) | |
| if parsed is None: | |
| # Standard operators never match an unparsable input. | |
| return False | |
| if prereleases is None: | |
| prereleases = resolve_prereleases(self._prereleases, self.prereleases) | |
| if prereleases is False and parsed.is_prerelease: | |
| return False | |
| # ``_fast_match`` answers the simple operators without building a | |
| # range; otherwise fall back to the engine's bounds membership. | |
| match = _fast_match(self, parsed) | |
| if match is not None: | |
| return match | |
| return matches_bounds_only(self._to_ranges(), parsed) | |
| def filter( | |
| self, | |
| iterable: Iterable[UnparsedVersionVar], | |
| prereleases: bool | None = None, | |
| key: None = ..., | |
| ) -> Iterator[UnparsedVersionVar]: ... | |
| def filter( | |
| self, | |
| iterable: Iterable[T], | |
| prereleases: bool | None = None, | |
| key: Callable[[T], UnparsedVersion] = ..., | |
| ) -> Iterator[T]: ... | |
| def filter( | |
| self, | |
| iterable: Iterable[Any], | |
| prereleases: bool | None = None, | |
| key: Callable[[Any], UnparsedVersion] | None = None, | |
| ) -> Iterator[Any]: | |
| """Filter items in the given iterable, that match the specifier. | |
| :param iterable: | |
| An iterable that can contain version strings and | |
| :class:`~packaging.version.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. | |
| :param key: | |
| A callable that takes a single argument (an item from the iterable) and | |
| returns a version string or :class:`~packaging.version.Version` | |
| instance to be used for filtering. | |
| >>> 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'] | |
| >>> list(Specifier(">=1.2.3").filter( | |
| ... [{"ver": "1.2"}, {"ver": "1.3"}], | |
| ... key=lambda x: x["ver"])) | |
| [{'ver': '1.3'}] | |
| .. versionchanged:: 26.1 | |
| Added the ``key`` parameter. | |
| """ | |
| if prereleases is None: | |
| prereleases = resolve_prereleases(self._prereleases, self.prereleases) | |
| if self.operator == "===": | |
| spec_lower = self.version.lower() | |
| matches = ( | |
| item | |
| for item in iterable | |
| if str(item if key is None else key(item)).lower() == spec_lower | |
| ) | |
| return _apply_prereleases_filter(matches, key, prereleases) | |
| return filter_by_ranges(self._to_ranges(), iterable, key, prereleases) | |
| def _apply_prereleases_filter( | |
| matches: Iterable[Any], | |
| key: Callable[[Any], UnparsedVersion] | None, | |
| prereleases: bool | None, | |
| ) -> Iterator[Any]: | |
| """Apply ``prereleases=`` handling to an already-matched iterable. | |
| ``None`` means PEP 440 default (buffer pre-releases until a final | |
| appears); ``True`` yields everything; ``False`` drops pre-releases. | |
| """ | |
| if prereleases is None: | |
| return _pep440_filter_prereleases(matches, key) | |
| if prereleases: | |
| return iter(matches) | |
| return ( | |
| item | |
| for item in matches | |
| if (parsed := coerce_version(item if key is None else key(item))) is None | |
| or not parsed.is_prerelease | |
| ) | |
| 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. | |
| Instances are safe to serialize with :mod:`pickle`. They use a stable | |
| format so the same pickle can be loaded in future packaging | |
| releases. | |
| .. versionchanged:: 26.2 | |
| Added a stable pickle format. Pickles created with | |
| packaging 26.2+ can be unpickled with future releases. | |
| Backward compatibility with pickles from | |
| packaging < 26.2 is supported but may be removed in a future | |
| release. | |
| """ | |
| __slots__ = ( | |
| "_canonicalized", | |
| "_has_arbitrary", | |
| "_is_unsatisfiable", | |
| "_prereleases", | |
| "_ranges", | |
| "_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()] | |
| self._specs: tuple[Specifier, ...] = tuple(map(Specifier, split_specifiers)) | |
| # Fast substring check; avoids iterating parsed specs. | |
| self._has_arbitrary = "===" in specifiers | |
| else: | |
| self._specs = tuple(specifiers) | |
| # Substring check works for both Specifier objects and plain | |
| # strings (setuptools passes lists of strings). | |
| self._has_arbitrary = any("===" in str(s) for s in self._specs) | |
| self._canonicalized = len(self._specs) <= 1 | |
| self._is_unsatisfiable: bool | None = None | |
| self._ranges: Sequence[Interval] | None = None | |
| # Store our prereleases value so we can use it later to determine if | |
| # we accept prereleases or not. | |
| self._prereleases = prereleases | |
| def _canonical_specs(self) -> tuple[Specifier, ...]: | |
| """Deduplicate, sort, and cache specs for order-sensitive operations.""" | |
| if not self._canonicalized: | |
| self._specs = tuple(dict.fromkeys(sorted(self._specs, key=str))) | |
| self._canonicalized = True | |
| return self._specs | |
| 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 | |
| self._is_unsatisfiable = None | |
| def __getstate__(self) -> tuple[tuple[Specifier, ...], bool | None]: | |
| # Return state as a 2-item tuple for compactness: | |
| # (specs, prereleases) | |
| # Cache members are excluded and will be recomputed on demand. | |
| return (self._specs, self._prereleases) | |
| def __setstate__(self, state: object) -> None: | |
| # Always discard cached values - they will be recomputed on demand. | |
| self._ranges = None | |
| self._is_unsatisfiable = None | |
| if isinstance(state, tuple): | |
| if len(state) == 2: | |
| # New format (26.2+): (specs, prereleases) | |
| specs, prereleases = state | |
| if ( | |
| isinstance(specs, tuple) | |
| and all(isinstance(s, Specifier) for s in specs) | |
| and _validate_pre(prereleases) | |
| ): | |
| self._specs = specs | |
| self._prereleases = prereleases | |
| self._canonicalized = len(specs) <= 1 | |
| self._has_arbitrary = any("===" in str(s) for s in specs) | |
| return | |
| if len(state) == 2 and isinstance(state[1], dict): | |
| # Format (packaging 26.0-26.1): (None, {slot: value}). | |
| _, slot_dict = state | |
| specs = slot_dict.get("_specs", ()) | |
| prereleases = slot_dict.get("_prereleases") | |
| # Convert frozenset to tuple (26.0 stored as frozenset) | |
| if isinstance(specs, frozenset): | |
| specs = tuple(sorted(specs, key=str)) | |
| if ( | |
| isinstance(specs, tuple) | |
| and all(isinstance(s, Specifier) for s in specs) | |
| and _validate_pre(prereleases) | |
| ): | |
| self._specs = specs | |
| self._prereleases = prereleases | |
| self._canonicalized = len(self._specs) <= 1 | |
| self._has_arbitrary = any("===" in str(s) for s in self._specs) | |
| return | |
| if isinstance(state, dict): | |
| # Old format (packaging <= 25.x, no __slots__): state is a plain dict. | |
| specs = state.get("_specs", ()) | |
| prereleases = state.get("_prereleases") | |
| # Convert frozenset to tuple (26.0 stored as frozenset) | |
| if isinstance(specs, frozenset): | |
| specs = tuple(sorted(specs, key=str)) | |
| if ( | |
| isinstance(specs, tuple) | |
| and all(isinstance(s, Specifier) for s in specs) | |
| and _validate_pre(prereleases) | |
| ): | |
| self._specs = specs | |
| self._prereleases = prereleases | |
| self._canonicalized = len(self._specs) <= 1 | |
| self._has_arbitrary = any("===" in str(s) for s in self._specs) | |
| return | |
| raise TypeError(f"Cannot restore SpecifierSet from {state!r}") | |
| 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"<{self.__class__.__name__}({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(str(s) for s in self._canonical_specs()) | |
| def __hash__(self) -> int: | |
| return hash(self._canonical_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 = self._specs + other._specs | |
| specifier._canonicalized = len(specifier._specs) <= 1 | |
| specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary | |
| # Combine prerelease settings: use common or non-None value | |
| if self._prereleases is None or self._prereleases == other._prereleases: | |
| specifier._prereleases = other._prereleases | |
| elif other._prereleases is None: | |
| 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._canonical_specs() == other._canonical_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 _get_ranges(self) -> Sequence[Interval]: | |
| """Intersect all specifiers into a single sequence of version ranges. | |
| Empty when unsatisfiable. Callers must ensure ``self._specs`` | |
| is non-empty. | |
| """ | |
| if self._ranges is not None: | |
| return self._ranges | |
| self._ranges = intersect_specifier_bounds(s._to_ranges() for s in self._specs) | |
| return self._ranges | |
| def is_unsatisfiable(self) -> bool: | |
| """Check whether this specifier set can never be satisfied. | |
| Returns True if no version can satisfy all specifiers simultaneously. | |
| >>> SpecifierSet(">=2.0,<1.0").is_unsatisfiable() | |
| True | |
| >>> SpecifierSet(">=1.0,<2.0").is_unsatisfiable() | |
| False | |
| >>> SpecifierSet("").is_unsatisfiable() | |
| False | |
| >>> SpecifierSet("==1.0,!=1.0").is_unsatisfiable() | |
| True | |
| .. versionadded:: 26.1 | |
| """ | |
| cached = self._is_unsatisfiable | |
| if cached is not None: | |
| return cached | |
| if not self._specs: | |
| self._is_unsatisfiable = False | |
| return False | |
| result = not self._get_ranges() | |
| if not result: | |
| result = self._check_arbitrary_unsatisfiable() | |
| if not result and self.prereleases is False: | |
| result = ranges_are_prerelease_only(self._get_ranges()) | |
| self._is_unsatisfiable = result | |
| return result | |
| def _check_arbitrary_unsatisfiable(self) -> bool: | |
| """Check === (arbitrary equality) specs for unsatisfiability. | |
| === uses case-insensitive string comparison, so the only candidate | |
| that can match ``===V`` is the literal string V. This method | |
| checks whether that candidate is excluded by other specifiers. | |
| """ | |
| arbitrary = [s for s in self._specs if s.operator == "==="] | |
| if not arbitrary: | |
| return False | |
| # Multiple === must agree on the same string (case-insensitive). | |
| first = arbitrary[0].version.lower() | |
| if any(s.version.lower() != first for s in arbitrary[1:]): | |
| return True | |
| # The sole candidate is the === version string. Check whether | |
| # it can satisfy every standard spec. | |
| candidate = coerce_version(arbitrary[0].version) | |
| # With prereleases=False, a prerelease candidate is excluded | |
| # by contains() before the === string check even runs. | |
| if ( | |
| self.prereleases is False | |
| and candidate is not None | |
| and candidate.is_prerelease | |
| ): | |
| return True | |
| standard = [s for s in self._specs if s.operator != "==="] | |
| if not standard: | |
| return False | |
| if candidate is None: | |
| # Unparsable string cannot satisfy any standard spec. | |
| return True | |
| return not all(s.contains(candidate) for s in standard) | |
| def to_range(self) -> ranges.VersionRange: | |
| """Return the :class:`~packaging.ranges.VersionRange` this set accepts. | |
| An empty set yields the full range; an unsatisfiable set yields the | |
| empty range. ``===`` specifiers contribute literal-string admission. | |
| >>> SpecifierSet(">=1.0,<2.0").to_range() | |
| <VersionRange '[1.0, 2.0.dev0)'> | |
| .. versionadded:: 26.3 | |
| """ | |
| from .ranges import VersionRange # noqa: PLC0415 | |
| return VersionRange._from_specifier_set(self) | |
| def _check_relation_operand(self, other: object) -> None: | |
| if not isinstance(other, SpecifierSet): | |
| raise TypeError("expected a SpecifierSet") | |
| if self._has_arbitrary or other._has_arbitrary: | |
| raise ValueError("set relations do not support === specifiers") | |
| def is_subset(self, other: SpecifierSet) -> bool: | |
| """Return whether every version matching this set also matches other. | |
| :raises ValueError: | |
| If either set uses ``===`` specifiers, or the two sets were | |
| given different ``prereleases`` arguments (unset on one side | |
| counts as different). | |
| :raises TypeError: | |
| If other is not a :class:`SpecifierSet`. | |
| >>> SpecifierSet(">=3.12,<3.13").is_subset(SpecifierSet(">=3.12")) | |
| True | |
| >>> SpecifierSet(">=3.12").is_subset(SpecifierSet(">=3.12,<3.13")) | |
| False | |
| .. versionadded:: 26.3 | |
| """ | |
| self._check_relation_operand(other) | |
| return self.to_range().is_subset(other.to_range()) | |
| def is_superset(self, other: SpecifierSet) -> bool: | |
| """Return whether every version matching other also matches this set. | |
| :raises ValueError: | |
| If either set uses ``===`` specifiers, or the two sets were | |
| given different ``prereleases`` arguments (unset on one side | |
| counts as different). | |
| :raises TypeError: | |
| If other is not a :class:`SpecifierSet`. | |
| >>> SpecifierSet(">=3.12").is_superset(SpecifierSet(">=3.12,<3.13")) | |
| True | |
| .. versionadded:: 26.3 | |
| """ | |
| self._check_relation_operand(other) | |
| return self.to_range().is_superset(other.to_range()) | |
| def is_disjoint(self, other: SpecifierSet) -> bool: | |
| """Return whether this set and other share no matching versions. | |
| :raises ValueError: | |
| If either set uses ``===`` specifiers, or the two sets were | |
| given different ``prereleases`` arguments (unset on one side | |
| counts as different). | |
| :raises TypeError: | |
| If other is not a :class:`SpecifierSet`. | |
| >>> SpecifierSet("<3.12").is_disjoint(SpecifierSet(">=3.12")) | |
| True | |
| >>> SpecifierSet("<3.12").is_disjoint(SpecifierSet(">=3.11")) | |
| False | |
| .. versionadded:: 26.3 | |
| """ | |
| self._check_relation_operand(other) | |
| return self.to_range().is_disjoint(other.to_range()) | |
| 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:`~packaging.version.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 | |
| .. versionchanged:: 26.0 | |
| With ``prereleases=None``, a prerelease now matches. A single | |
| version has no alternatives, so the :pep:`440` rule to accept | |
| prereleases when nothing else satisfies the specifiers applies. | |
| Earlier versions rejected it. An unparsable version now returns | |
| ``False`` instead of raising :exc:`~packaging.version.InvalidVersion`. | |
| """ | |
| version = coerce_version(item) | |
| if version is not None and installed and version.is_prerelease: | |
| prereleases = True | |
| # When item is a string and === is involved, keep it as-is | |
| # so the comparison isn't done against the normalized form. | |
| if version is None or (self._has_arbitrary and not isinstance(item, Version)): | |
| check_item = item | |
| else: | |
| check_item = version | |
| # Fast path: a parseable, local-free version against a rangelike set. | |
| # A local on ``version`` needs PEP 440 stripping that the range path | |
| # applies. | |
| if ( | |
| version is not None | |
| and not self._has_arbitrary | |
| and version.local is None | |
| and self._specs | |
| ): | |
| if version.is_prerelease and ( | |
| prereleases is False | |
| or (prereleases is None and self._prereleases is False) | |
| ): | |
| return False | |
| bounds = self._ranges | |
| if bounds is None: | |
| # Per-spec ``_fast_match`` answers a set of simple specifiers | |
| # without folding anything. If a spec needs the range path, | |
| # fold the intersected bounds once and cache them so repeated | |
| # checks on the same set stay cheap. | |
| for spec in self._specs: | |
| match = _fast_match(spec, version) | |
| if match is None: | |
| break | |
| if not match: | |
| return False | |
| else: | |
| return True | |
| bounds = self._ranges = self._get_ranges() | |
| return matches_bounds_only(bounds, version) | |
| return bool(list(self.filter([check_item], prereleases=prereleases))) | |
| def filter( | |
| self, | |
| iterable: Iterable[UnparsedVersionVar], | |
| prereleases: bool | None = None, | |
| key: None = ..., | |
| ) -> Iterator[UnparsedVersionVar]: ... | |
| def filter( | |
| self, | |
| iterable: Iterable[T], | |
| prereleases: bool | None = None, | |
| key: Callable[[T], UnparsedVersion] = ..., | |
| ) -> Iterator[T]: ... | |
| def filter( | |
| self, | |
| iterable: Iterable[Any], | |
| prereleases: bool | None = None, | |
| key: Callable[[Any], UnparsedVersion] | None = None, | |
| ) -> Iterator[Any]: | |
| """Filter items in the given iterable, that match the specifiers in this set. | |
| :param iterable: | |
| An iterable that can contain version strings and | |
| :class:`~packaging.version.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. | |
| :param key: | |
| A callable that takes a single argument (an item from the iterable) and | |
| returns a version string or :class:`~packaging.version.Version` | |
| instance to be used for filtering. | |
| >>> 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'] | |
| >>> list(SpecifierSet(">=1.2.3").filter( | |
| ... [{"ver": "1.2"}, {"ver": "1.3"}], | |
| ... key=lambda x: x["ver"])) | |
| [{'ver': '1.3'}] | |
| 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'] | |
| .. versionchanged:: 26.0 | |
| Prerelease filtering now follows the PEP 440 recommendation of | |
| yielding prereleases only when no final release is present. | |
| .. versionchanged:: 26.1 | |
| Added the ``key`` parameter. | |
| """ | |
| # 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 self._specs: | |
| if self._has_arbitrary: | |
| # Slow path for === | |
| specs = self._specs | |
| matches = ( | |
| item | |
| for item in iterable | |
| if all( | |
| s.contains(item if key is None else key(item), prereleases=True) | |
| for s in specs | |
| ) | |
| ) | |
| return _apply_prereleases_filter(matches, key, prereleases) | |
| ranges = self._ranges | |
| if ranges is None: | |
| ranges = self._get_ranges() | |
| return filter_by_ranges(ranges, iterable, key, prereleases) | |
| # Empty SpecifierSet. | |
| return _apply_prereleases_filter(iterable, key, prereleases) | |
| def _pep440_filter_prereleases( | |
| iterable: Iterable[Any], key: Callable[[Any], UnparsedVersion] | None | |
| ) -> Iterator[Any]: | |
| """Filter per PEP 440: exclude prereleases unless no finals exist.""" | |
| # Two lists used: | |
| # * all_nonfinal to preserve order if no finals exist | |
| # * arbitrary_strings for streaming when first final found | |
| all_nonfinal: list[Any] = [] | |
| arbitrary_strings: list[Any] = [] | |
| found_final = False | |
| for item in iterable: | |
| parsed = coerce_version(item if key is None else key(item)) | |
| if parsed is None: | |
| # Arbitrary strings are always included as it is not | |
| # possible to determine if they are prereleases, | |
| # and they have already passed all specifiers. | |
| if found_final: | |
| yield item | |
| else: | |
| arbitrary_strings.append(item) | |
| all_nonfinal.append(item) | |
| continue | |
| if not parsed.is_prerelease: | |
| # Final release found - flush arbitrary strings, then yield | |
| if not found_final: | |
| yield from arbitrary_strings | |
| found_final = True | |
| yield item | |
| continue | |
| # Prerelease - buffer if no finals yet, otherwise skip | |
| if not found_final: | |
| all_nonfinal.append(item) | |
| # No finals found - yield all buffered items | |
| if not found_final: | |
| yield from all_nonfinal | |