| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943 |
- # 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 enum
- import functools
- import itertools
- import re
- import sys
- import typing
- from typing import (
- TYPE_CHECKING,
- Any,
- Callable,
- Final,
- Iterable,
- Iterator,
- Sequence,
- TypeVar,
- Union,
- )
- from .utils import canonicalize_version
- from .version import InvalidVersion, Version
- if sys.version_info >= (3, 10):
- from typing import TypeGuard # pragma: no cover
- elif TYPE_CHECKING:
- from typing_extensions import TypeGuard
- __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)
- CallableOperator = Callable[[Version, str], bool]
- # The smallest possible PEP 440 version. No valid version is less than this.
- _MIN_VERSION: Final[Version] = Version("0.dev0")
- def _trim_release(release: tuple[int, ...]) -> tuple[int, ...]:
- """Strip trailing zeros from a release tuple for normalized comparison."""
- end = len(release)
- while end > 1 and release[end - 1] == 0:
- end -= 1
- return release if end == len(release) else release[:end]
- class _BoundaryKind(enum.Enum):
- """Where a boundary marker sits in the version ordering."""
- AFTER_LOCALS = enum.auto() # after V+local, before V.post0
- AFTER_POSTS = enum.auto() # after V.postN, before next release
- @functools.total_ordering
- class _BoundaryVersion:
- """A point on the version line between two real PEP 440 versions.
- Some specifier semantics imply boundaries between real versions:
- ``<=1.0`` includes ``1.0+local`` and ``>1.0`` excludes
- ``1.0.post0``. No real :class:`Version` falls on those boundaries,
- so this class creates values that sort between the real versions
- on either side.
- Two kinds exist, shown relative to a base version V::
- V < V+local < AFTER_LOCALS(V) < V.post0 < AFTER_POSTS(V)
- ``AFTER_LOCALS`` sits after V and every V+local, but before
- V.post0. Upper bound of ``<=V``, ``==V``, ``!=V``.
- ``AFTER_POSTS`` sits after every V.postN, but before the next
- release segment. Lower bound of ``>V`` (final or pre-release V)
- to exclude post-releases per PEP 440.
- """
- __slots__ = ("_kind", "_trimmed_release", "version")
- def __init__(self, version: Version, kind: _BoundaryKind) -> None:
- self.version = version
- self._kind = kind
- self._trimmed_release = _trim_release(version.release)
- def _is_family(self, other: Version) -> bool:
- """Is ``other`` a version that this boundary sorts above?"""
- v = self.version
- if not (
- other.epoch == v.epoch
- and _trim_release(other.release) == self._trimmed_release
- and other.pre == v.pre
- ):
- return False
- if self._kind == _BoundaryKind.AFTER_LOCALS:
- # Local family: exact same public version (any local label).
- return other.post == v.post and other.dev == v.dev
- # Post family: same base + any post-release (or identical).
- return other.dev == v.dev or other.post is not None
- def __eq__(self, other: object) -> bool:
- if isinstance(other, _BoundaryVersion):
- return self.version == other.version and self._kind == other._kind
- return NotImplemented
- def __lt__(self, other: _BoundaryVersion | Version) -> bool:
- if isinstance(other, _BoundaryVersion):
- if self.version != other.version:
- return self.version < other.version
- return self._kind.value < other._kind.value
- return not self._is_family(other) and self.version < other
- def __hash__(self) -> int:
- return hash((self.version, self._kind))
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.version!r}, {self._kind.name})"
- @functools.total_ordering
- class _LowerBound:
- """Lower bound of a version range.
- A version *v* of ``None`` means unbounded below (-inf).
- At equal versions, ``[v`` sorts before ``(v`` because an inclusive
- bound starts earlier.
- """
- __slots__ = ("inclusive", "version")
- def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None:
- self.version = version
- self.inclusive = inclusive
- def __eq__(self, other: object) -> bool:
- if not isinstance(other, _LowerBound):
- return NotImplemented # pragma: no cover
- return self.version == other.version and self.inclusive == other.inclusive
- def __lt__(self, other: _LowerBound) -> bool:
- if not isinstance(other, _LowerBound): # pragma: no cover
- return NotImplemented
- # -inf < anything (except -inf).
- if self.version is None:
- return other.version is not None
- if other.version is None:
- return False
- if self.version != other.version:
- return self.version < other.version
- # [v < (v: inclusive starts earlier.
- return self.inclusive and not other.inclusive
- def __hash__(self) -> int:
- return hash((self.version, self.inclusive))
- def __repr__(self) -> str:
- bracket = "[" if self.inclusive else "("
- return f"<{self.__class__.__name__} {bracket}{self.version!r}>"
- @functools.total_ordering
- class _UpperBound:
- """Upper bound of a version range.
- A version *v* of ``None`` means unbounded above (+inf).
- At equal versions, ``v)`` sorts before ``v]`` because an exclusive
- bound ends earlier.
- """
- __slots__ = ("inclusive", "version")
- def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None:
- self.version = version
- self.inclusive = inclusive
- def __eq__(self, other: object) -> bool:
- if not isinstance(other, _UpperBound):
- return NotImplemented # pragma: no cover
- return self.version == other.version and self.inclusive == other.inclusive
- def __lt__(self, other: _UpperBound) -> bool:
- if not isinstance(other, _UpperBound): # pragma: no cover
- return NotImplemented
- # Nothing < +inf (except +inf itself).
- if self.version is None:
- return False
- if other.version is None:
- return True
- if self.version != other.version:
- return self.version < other.version
- # v) < v]: exclusive ends earlier.
- return not self.inclusive and other.inclusive
- def __hash__(self) -> int:
- return hash((self.version, self.inclusive))
- def __repr__(self) -> str:
- bracket = "]" if self.inclusive else ")"
- return f"<{self.__class__.__name__} {self.version!r}{bracket}>"
- if typing.TYPE_CHECKING:
- _VersionOrBoundary = Union[Version, _BoundaryVersion, None]
- #: A single contiguous version range, represented as a
- #: (lower bound, upper bound) pair.
- _VersionRange = tuple[_LowerBound, _UpperBound]
- _NEG_INF = _LowerBound(None, False)
- _POS_INF = _UpperBound(None, False)
- _FULL_RANGE: tuple[_VersionRange] = ((_NEG_INF, _POS_INF),)
- def _range_is_empty(lower: _LowerBound, upper: _UpperBound) -> bool:
- """True when the range defined by *lower* and *upper* contains no versions."""
- if lower.version is None or upper.version is None:
- return False
- if lower.version == upper.version:
- return not (lower.inclusive and upper.inclusive)
- return lower.version > upper.version
- def _intersect_ranges(
- left: Sequence[_VersionRange],
- right: Sequence[_VersionRange],
- ) -> list[_VersionRange]:
- """Intersect two sorted, non-overlapping range lists (two-pointer merge)."""
- result: list[_VersionRange] = []
- left_index = right_index = 0
- while left_index < len(left) and right_index < len(right):
- left_lower, left_upper = left[left_index]
- right_lower, right_upper = right[right_index]
- lower = max(left_lower, right_lower)
- upper = min(left_upper, right_upper)
- if not _range_is_empty(lower, upper):
- result.append((lower, upper))
- # Advance whichever side has the smaller upper bound.
- if left_upper < right_upper:
- left_index += 1
- else:
- right_index += 1
- return result
- def _next_prefix_dev0(version: Version) -> Version:
- """Smallest version in the next prefix: 1.2 -> 1.3.dev0."""
- release = (*version.release[:-1], version.release[-1] + 1)
- return Version.from_parts(epoch=version.epoch, release=release, dev=0)
- def _base_dev0(version: Version) -> Version:
- """The .dev0 of a version's base release: 1.2 -> 1.2.dev0."""
- return Version.from_parts(epoch=version.epoch, release=version.release, dev=0)
- 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:
- if version.local is None:
- return version
- return version.__replace__(local=None)
- def _post_base(version: Version) -> Version:
- """The version that *version* is a post-release of.
- 1.0.post1 -> 1.0, 1.0a1.post0 -> 1.0a1, 1.0.post0.dev1 -> 1.0.
- """
- return version.__replace__(post=None, dev=None, local=None)
- def _earliest_prerelease(version: Version) -> Version:
- """Earliest pre-release of *version*.
- 1.2 -> 1.2.dev0, 1.2.post1 -> 1.2.post1.dev0.
- """
- return version.__replace__(dev=0, local=None)
- def _nearest_non_prerelease(
- v: _VersionOrBoundary,
- ) -> Version | None:
- """Smallest non-pre-release version at or above *v*, or None."""
- if v is None:
- return None
- if isinstance(v, _BoundaryVersion):
- inner = v.version
- if inner.is_prerelease:
- # AFTER_LOCALS(1.0a1) -> nearest non-pre is 1.0
- return inner.__replace__(pre=None, dev=None, local=None)
- # AFTER_LOCALS(1.0) -> nearest non-pre is 1.0.post0
- # AFTER_LOCALS(1.0.post0) -> nearest non-pre is 1.0.post1
- k = (inner.post + 1) if inner.post is not None else 0
- return inner.__replace__(post=k, local=None)
- if not v.is_prerelease:
- return v
- # Strip pre/dev to get the final or post-release form.
- return v.__replace__(pre=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",)
- @property
- def _str(self) -> str:
- """Internal property for match_args"""
- return str(self)
- @abc.abstractmethod
- def __str__(self) -> str:
- """
- Returns the str representation of this Specifier-like object. This
- should be representative of the Specifier itself.
- """
- @abc.abstractmethod
- def __hash__(self) -> int:
- """
- Returns a hash value for this Specifier-like object.
- """
- @abc.abstractmethod
- 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.
- """
- @property
- @abc.abstractmethod
- 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.
- """
- @prereleases.setter # noqa: B027
- def prereleases(self, value: bool) -> None:
- """Setter for :attr:`prereleases`.
- :param value: The value to set.
- """
- @abc.abstractmethod
- def contains(self, item: str, prereleases: bool | None = None) -> bool:
- """
- Determines if the given item is contained within this specifier.
- """
- @typing.overload
- def filter(
- self,
- iterable: Iterable[UnparsedVersionVar],
- prereleases: bool | None = None,
- key: None = ...,
- ) -> Iterator[UnparsedVersionVar]: ...
- @typing.overload
- def filter(
- self,
- iterable: Iterable[T],
- prereleases: bool | None = None,
- key: Callable[[T], UnparsedVersion] = ...,
- ) -> Iterator[T]: ...
- @abc.abstractmethod
- 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",
- "_wildcard_split",
- )
- _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
- )
- _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
- # Populated on first wildcard (==X.*) comparison
- self._wildcard_split: tuple[list[str], int] | None = None
- # Version range cache (populated by _to_ranges)
- self._ranges: Sequence[_VersionRange] | 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[_VersionRange]:
- """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 == "===":
- self._ranges = _FULL_RANGE
- return _FULL_RANGE
- if ver_str.endswith(".*"):
- result = self._wildcard_ranges(op, ver_str)
- else:
- result = self._standard_ranges(op, ver_str)
- self._ranges = result
- return result
- def _wildcard_ranges(self, op: str, ver_str: str) -> list[_VersionRange]:
- # ==1.2.* -> [1.2.dev0, 1.3.dev0); !=1.2.* -> complement.
- base = self._require_spec_version(ver_str[:-2])
- lower = _base_dev0(base)
- upper = _next_prefix_dev0(base)
- if op == "==":
- return [(_LowerBound(lower, True), _UpperBound(upper, False))]
- # !=
- return [
- (_NEG_INF, _UpperBound(lower, False)),
- (_LowerBound(upper, True), _POS_INF),
- ]
- def _standard_ranges(self, op: str, ver_str: str) -> list[_VersionRange]:
- v = self._require_spec_version(ver_str)
- if op == ">=":
- return [(_LowerBound(v, True), _POS_INF)]
- if op == "<=":
- return [
- (
- _NEG_INF,
- _UpperBound(_BoundaryVersion(v, _BoundaryKind.AFTER_LOCALS), True),
- )
- ]
- if op == ">":
- if v.dev is not None:
- # >V.devN: dev versions have no post-releases, so the
- # next real version is V.dev(N+1).
- lower_ver = v.__replace__(dev=v.dev + 1, local=None)
- return [(_LowerBound(lower_ver, True), _POS_INF)]
- if v.post is not None:
- # >V.postN: next real version is V.post(N+1).dev0.
- lower_ver = v.__replace__(post=v.post + 1, dev=0, local=None)
- return [(_LowerBound(lower_ver, True), _POS_INF)]
- # >V (final or pre-release): skip V+local and all V.postN.
- return [
- (
- _LowerBound(_BoundaryVersion(v, _BoundaryKind.AFTER_POSTS), False),
- _POS_INF,
- )
- ]
- if op == "<":
- # <V excludes prereleases of V when V is not a prerelease.
- # V.dev0 is the earliest prerelease of V (final, post, etc.).
- bound = v if v.is_prerelease else v.__replace__(dev=0, local=None)
- if bound <= _MIN_VERSION:
- return []
- return [(_NEG_INF, _UpperBound(bound, False))]
- # ==, !=: local versions of V match when spec has no local segment.
- has_local = "+" in ver_str
- after_locals = _BoundaryVersion(v, _BoundaryKind.AFTER_LOCALS)
- upper = v if has_local else after_locals
- if op == "==":
- return [(_LowerBound(v, True), _UpperBound(upper, True))]
- if op == "!=":
- return [
- (_NEG_INF, _UpperBound(v, False)),
- (_LowerBound(upper, False), _POS_INF),
- ]
- if op == "~=":
- prefix = v.__replace__(release=v.release[:-1])
- return [
- (_LowerBound(v, True), _UpperBound(_next_prefix_dev0(prefix), False))
- ]
- raise ValueError(f"Unknown operator: {op!r}") # pragma: no cover
- @property
- 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
- @prereleases.setter
- 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._wildcard_split = 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}")
- @property
- def operator(self) -> str:
- """The operator of this specifier.
- >>> Specifier("==1.2.3").operator
- '=='
- """
- return self._spec[0]
- @property
- 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)
- @property
- 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._compare_greater_than_equal(prospective, spec)) and (
- self._compare_equal(prospective, prefix)
- )
- def _get_wildcard_split(self, spec: str) -> tuple[list[str], int]:
- """Cached split of a wildcard spec into components and numeric length.
- >>> Specifier("==1.*")._get_wildcard_split("1.*")
- (['0', '1'], 2)
- >>> Specifier("==3.10.*")._get_wildcard_split("3.10.*")
- (['0', '3', '10'], 3)
- """
- wildcard_split = self._wildcard_split
- if wildcard_split is None:
- normalized = canonicalize_version(spec[:-2], strip_trailing_zero=False)
- split_spec = _version_split(normalized)
- wildcard_split = (split_spec, _numeric_prefix_len(split_spec))
- self._wildcard_split = wildcard_split
- return wildcard_split
- def _compare_equal(self, prospective: Version, spec: str) -> bool:
- # We need special logic to handle prefix matching
- if spec.endswith(".*"):
- split_spec, spec_numeric_len = self._get_wildcard_split(spec)
- # In the case of prefix matching we want to ignore local segment.
- normalized_prospective = canonicalize_version(
- _public_version(prospective), strip_trailing_zero=False
- )
- # 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 = _left_pad(split_prospective, spec_numeric_len)
- # 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
- # The spec says: "<V MUST NOT allow a pre-release of the specified
- # version unless the specified version is itself a pre-release."
- if (
- not spec.is_prerelease
- and prospective.is_prerelease
- and prospective >= _earliest_prerelease(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
- # The spec says: ">V MUST NOT allow a post-release of the specified
- # version unless the specified version is itself a post-release."
- if (
- not spec.is_postrelease
- and prospective.is_postrelease
- and _post_base(prospective) == spec
- ):
- return False
- # Per the spec: ">V MUST NOT match a local version of the specified
- # version". A "local version of V" is any version whose public part
- # equals V. So >1.0a1 must not match 1.0a1+local, but must still
- # match 1.0a2+local.
- if prospective.local is not None and _public_version(prospective) == 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)))
- @typing.overload
- def filter(
- self,
- iterable: Iterable[UnparsedVersionVar],
- prereleases: bool | None = None,
- key: None = ...,
- ) -> Iterator[UnparsedVersionVar]: ...
- @typing.overload
- 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:`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:`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'}]
- """
- 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 key is None else key(version))
- match = False
- 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 self.operator == "===":
- match = self._compare_arbitrary(
- version if key is None else key(version), self.version
- )
- else:
- match = operator_callable(parsed_version, self.version)
- if match and parsed_version is not None:
- # 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 _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
- 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 _numeric_prefix_len(split: list[str]) -> int:
- """Count leading numeric components in a :func:`_version_split` result.
- >>> _numeric_prefix_len(["0", "1", "2", "a1"])
- 3
- """
- count = 0
- for segment in split:
- if not segment.isdigit():
- break
- count += 1
- return count
- def _left_pad(split: list[str], target_numeric_len: int) -> list[str]:
- """Pad a :func:`_version_split` result with ``"0"`` segments to reach
- ``target_numeric_len`` numeric components. Suffix segments are preserved.
- >>> _left_pad(["0", "1", "a1"], 4)
- ['0', '1', '0', '0', 'a1']
- """
- numeric_len = _numeric_prefix_len(split)
- pad_needed = target_numeric_len - numeric_len
- if pad_needed <= 0:
- return split
- return [*split[:numeric_len], *(["0"] * pad_needed), *split[numeric_len:]]
- def _operator_cost(op_entry: tuple[CallableOperator, str, str]) -> int:
- """Sort key for Cost Based Ordering of specifier operators in _filter_versions.
- Operators run sequentially on a shrinking candidate set, so operators that
- reject the most versions should run first to minimize work for later ones.
- Tier 0: Exact equality (==, ===), likely to narrow candidates to one version
- Tier 1: Range checks (>=, <=, >, <), cheap and usually reject a large portion
- Tier 2: Wildcard equality (==.*) and compatible release (~=), more expensive
- Tier 3: Exact !=, cheap but rarely rejects
- Tier 4: Wildcard !=.*, expensive and rarely rejects
- """
- _, ver, op = op_entry
- if op == "==":
- return 0 if not ver.endswith(".*") else 2
- if op in (">=", "<=", ">", "<"):
- return 1
- if op == "~=":
- return 2
- if op == "!=":
- return 3 if not ver.endswith(".*") else 4
- if op == "===":
- return 0
- raise ValueError(f"Unknown operator: {op!r}") # pragma: no cover
- 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",
- "_resolved_ops",
- "_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._resolved_ops: list[tuple[CallableOperator, str, str]] | None = None
- # Store our prereleases value so we can use it later to determine if
- # we accept prereleases or not.
- self._prereleases = prereleases
- self._is_unsatisfiable: bool | None = None
- 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
- self._resolved_ops = None
- self._is_unsatisfiable = None
- return self._specs
- @property
- 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
- @prereleases.setter
- 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._resolved_ops = 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
- specifier._resolved_ops = None
- # 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[_VersionRange]:
- """Intersect all specifiers into a single list of version ranges.
- Returns an empty list when unsatisfiable. ``===`` specs are
- modeled as full range; string matching is checked separately
- by :meth:`_check_arbitrary_unsatisfiable`.
- """
- specs = self._specs
- result: Sequence[_VersionRange] | None = None
- for s in specs:
- if result is None:
- result = s._to_ranges()
- else:
- result = _intersect_ranges(result, s._to_ranges())
- if not result:
- break
- if result is None: # pragma: no cover
- raise RuntimeError("_get_ranges called with no specs")
- return result
- 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
- """
- 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 = self._check_prerelease_only_ranges()
- self._is_unsatisfiable = result
- return result
- def _check_prerelease_only_ranges(self) -> bool:
- """With prereleases=False, check if every range contains only
- pre-release versions (which would be excluded from matching)."""
- for lower, upper in self._get_ranges():
- nearest = _nearest_non_prerelease(lower.version)
- if nearest is None:
- return False
- if upper.version is None or nearest < upper.version:
- return False
- if nearest == upper.version and upper.inclusive:
- return False
- return True
- 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 __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
- # 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
- return bool(list(self.filter([check_item], prereleases=prereleases)))
- @typing.overload
- def filter(
- self,
- iterable: Iterable[UnparsedVersionVar],
- prereleases: bool | None = None,
- key: None = ...,
- ) -> Iterator[UnparsedVersionVar]: ...
- @typing.overload
- 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:`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:`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']
- """
- # 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
- # Filter versions that match all specifiers using Cost Based Ordering.
- 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.
- # Fast path: single specifier, delegate directly.
- if len(self._specs) == 1:
- filtered = self._specs[0].filter(
- iterable,
- prereleases=True if prereleases is None else prereleases,
- key=key,
- )
- else:
- filtered = self._filter_versions(
- iterable,
- key,
- prereleases=True if prereleases is None else prereleases,
- )
- if prereleases is not None:
- return filtered
- return _pep440_filter_prereleases(filtered, key)
- # Handle Empty SpecifierSet.
- if prereleases is True:
- return iter(iterable)
- if prereleases is False:
- return (
- item
- for item in iterable
- if (
- (version := _coerce_version(item if key is None else key(item)))
- is None
- or not version.is_prerelease
- )
- )
- # PEP 440: exclude prereleases unless no final releases matched
- return _pep440_filter_prereleases(iterable, key)
- def _filter_versions(
- self,
- iterable: Iterable[Any],
- key: Callable[[Any], UnparsedVersion] | None,
- prereleases: bool | None = None,
- ) -> Iterator[Any]:
- """Filter versions against all specifiers in a single pass.
- Uses Cost Based Ordering: specifiers are sorted by _operator_cost so
- that cheap range operators reject versions early, avoiding expensive
- wildcard or compatible operators on versions that would have been
- rejected anyway.
- """
- # Pre-resolve operators and sort (cached after first call).
- if self._resolved_ops is None:
- self._resolved_ops = sorted(
- (
- (spec._get_operator(spec.operator), spec.version, spec.operator)
- for spec in self._specs
- ),
- key=_operator_cost,
- )
- ops = self._resolved_ops
- exclude_prereleases = prereleases is False
- for item in iterable:
- parsed = _coerce_version(item if key is None else key(item))
- if parsed is None:
- # Only === can match non-parseable versions.
- if all(
- op == "===" and str(item).lower() == ver.lower()
- for _, ver, op in ops
- ):
- yield item
- elif exclude_prereleases and parsed.is_prerelease:
- pass
- elif all(
- str(item if key is None else key(item)).lower() == ver.lower()
- if op == "==="
- else op_fn(parsed, ver)
- for op_fn, ver, op in ops
- ):
- # Short-circuits on the first failing operator.
- yield item
|