Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/packaging/specifiers.py: 13%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is dual licensed under the terms of the Apache License, Version
2# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3# for complete details.
4"""
5.. testsetup::
7 from packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier
8 from packaging.version import Version
9"""
11from __future__ import annotations
13import abc
14import re
15import sys
16import typing
17from typing import (
18 TYPE_CHECKING,
19 Any,
20 Callable,
21 Final,
22 TypeVar,
23 Union,
24)
26from ._ranges import (
27 FULL_RANGE,
28 filter_by_ranges,
29 intersect_ranges,
30 ranges_are_prerelease_only,
31 standard_ranges,
32 trim_release,
33 wildcard_ranges,
34)
35from .utils import canonicalize_version
36from .version import InvalidVersion, Version
38if sys.version_info >= (3, 10):
39 from typing import TypeGuard # pragma: no cover
40elif TYPE_CHECKING:
41 from typing_extensions import TypeGuard
43if TYPE_CHECKING:
44 from collections.abc import Iterable, Iterator, Sequence
46 from ._ranges import VersionRange
49__all__ = [
50 "BaseSpecifier",
51 "InvalidSpecifier",
52 "Specifier",
53 "SpecifierSet",
54]
57def __dir__() -> list[str]:
58 return __all__
61def _validate_spec(spec: object, /) -> TypeGuard[tuple[str, str]]:
62 return (
63 isinstance(spec, tuple)
64 and len(spec) == 2
65 and isinstance(spec[0], str)
66 and isinstance(spec[1], str)
67 )
70def _validate_pre(pre: object, /) -> TypeGuard[bool | None]:
71 return pre is None or isinstance(pre, bool)
74T = TypeVar("T")
75UnparsedVersion = Union[Version, str]
76UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
79def _coerce_version(version: UnparsedVersion) -> Version | None:
80 if not isinstance(version, Version):
81 try:
82 version = Version(version)
83 except InvalidVersion:
84 return None
85 return version
88# Operators whose result is just a direct Version comparison, given a parsed
89# item with no local. ``<=``/``==``/``!=`` need that no-local guard because
90# PEP 440 strips locals on those; ``>=`` works regardless.
91_DIRECT_COMPARE_OPS: dict[str, Callable[[Version, Version], bool]] = {
92 ">=": Version.__ge__,
93 "<=": Version.__le__,
94 "==": Version.__eq__,
95 "!=": Version.__ne__,
96}
99def _fast_match(specifier: Specifier, parsed: Version) -> bool | None:
100 """Match ``parsed`` against ``specifier`` without building a range.
102 Handles ``>=``, ``<=``, ``==``, ``!=``, ``<``, ``>`` when the spec is
103 not a wildcard and ``parsed`` has no local. Returns ``None`` when the
104 range path must be used. Pre-release policy is left to the caller.
105 """
106 op_str, ver_str = specifier._spec
107 if ver_str.endswith(".*") or parsed.local is not None:
108 return None
110 direct_compare = _DIRECT_COMPARE_OPS.get(op_str)
111 if direct_compare is not None:
112 return direct_compare(parsed, specifier._require_spec_version(ver_str))
114 if op_str in ("<", ">"):
115 spec_v = specifier._require_spec_version(ver_str)
116 # ``<V``/``>V`` carve out V's family (pre/dev/post); that only
117 # matters when parsed shares V's epoch and trimmed release.
118 # Otherwise a direct cmpkey comparison is correct.
119 if parsed.epoch != spec_v.epoch or trim_release(parsed.release) != trim_release(
120 spec_v.release
121 ):
122 return parsed < spec_v if op_str == "<" else parsed > spec_v
123 return None
125 return None
128class InvalidSpecifier(ValueError):
129 """
130 Raised when attempting to create a :class:`Specifier` with a specifier
131 string that is invalid.
133 >>> Specifier("lolwat")
134 Traceback (most recent call last):
135 ...
136 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
137 """
140class BaseSpecifier(metaclass=abc.ABCMeta):
141 __slots__ = ()
142 __match_args__ = ("_str",)
144 @property
145 def _str(self) -> str:
146 """Internal property for match_args"""
147 return str(self)
149 @abc.abstractmethod
150 def __str__(self) -> str:
151 """
152 Returns the str representation of this Specifier-like object. This
153 should be representative of the Specifier itself.
154 """
156 @abc.abstractmethod
157 def __hash__(self) -> int:
158 """
159 Returns a hash value for this Specifier-like object.
160 """
162 @abc.abstractmethod
163 def __eq__(self, other: object) -> bool:
164 """
165 Returns a boolean representing whether or not the two Specifier-like
166 objects are equal.
168 :param other: The other object to check against.
169 """
171 @property
172 @abc.abstractmethod
173 def prereleases(self) -> bool | None:
174 """Whether or not pre-releases as a whole are allowed.
176 This can be set to either ``True`` or ``False`` to explicitly enable or disable
177 prereleases or it can be set to ``None`` (the default) to use default semantics.
178 """
180 @prereleases.setter # noqa: B027
181 def prereleases(self, value: bool) -> None:
182 """Setter for :attr:`prereleases`.
184 :param value: The value to set.
185 """
187 @abc.abstractmethod
188 def contains(self, item: str, prereleases: bool | None = None) -> bool:
189 """
190 Determines if the given item is contained within this specifier.
191 """
193 @typing.overload
194 def filter(
195 self,
196 iterable: Iterable[UnparsedVersionVar],
197 prereleases: bool | None = None,
198 key: None = ...,
199 ) -> Iterator[UnparsedVersionVar]: ...
201 @typing.overload
202 def filter(
203 self,
204 iterable: Iterable[T],
205 prereleases: bool | None = None,
206 key: Callable[[T], UnparsedVersion] = ...,
207 ) -> Iterator[T]: ...
209 @abc.abstractmethod
210 def filter(
211 self,
212 iterable: Iterable[Any],
213 prereleases: bool | None = None,
214 key: Callable[[Any], UnparsedVersion] | None = None,
215 ) -> Iterator[Any]:
216 """
217 Takes an iterable of items and filters them so that only items which
218 are contained within this specifier are allowed in it.
219 """
222class Specifier(BaseSpecifier):
223 """This class abstracts handling of version specifiers.
225 .. tip::
227 It is generally not required to instantiate this manually. You should instead
228 prefer to work with :class:`SpecifierSet` instead, which can parse
229 comma-separated version specifiers (which is what package metadata contains).
231 Instances are safe to serialize with :mod:`pickle`. They use a stable
232 format so the same pickle can be loaded in future packaging releases.
234 .. versionchanged:: 26.2
236 Added a stable pickle format. Pickles created with packaging 26.2+ can
237 be unpickled with future releases. Backward compatibility with pickles
238 from packaging < 26.2 is supported but may be removed in a future
239 release.
240 """
242 __slots__ = (
243 "_prereleases",
244 "_ranges",
245 "_spec",
246 "_spec_version",
247 )
249 _specifier_regex_str = r"""
250 (?:
251 (?:
252 # The identity operators allow for an escape hatch that will
253 # do an exact string match of the version you wish to install.
254 # This will not be parsed by PEP 440 and we cannot determine
255 # any semantic meaning from it. This operator is discouraged
256 # but included entirely as an escape hatch.
257 === # Only match for the identity operator
258 \s*
259 [^\s;)]* # The arbitrary version can be just about anything,
260 # we match everything except for whitespace, a
261 # semi-colon for marker support, and a closing paren
262 # since versions can be enclosed in them.
263 )
264 |
265 (?:
266 # The (non)equality operators allow for wild card and local
267 # versions to be specified so we have to define these two
268 # operators separately to enable that.
269 (?:==|!=) # Only match for equals and not equals
271 \s*
272 v?
273 (?:[0-9]+!)? # epoch
274 [0-9]+(?:\.[0-9]+)* # release
276 # You cannot use a wild card and a pre-release, post-release, a dev or
277 # local version together so group them with a | and make them optional.
278 (?:
279 \.\* # Wild card syntax of .*
280 |
281 (?a: # pre release
282 [-_\.]?
283 (alpha|beta|preview|pre|a|b|c|rc)
284 [-_\.]?
285 [0-9]*
286 )?
287 (?a: # post release
288 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
289 )?
290 (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
291 (?a:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
292 )?
293 )
294 |
295 (?:
296 # The compatible operator requires at least two digits in the
297 # release segment.
298 (?:~=) # Only match for the compatible operator
300 \s*
301 v?
302 (?:[0-9]+!)? # epoch
303 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
304 (?: # pre release
305 [-_\.]?
306 (alpha|beta|preview|pre|a|b|c|rc)
307 [-_\.]?
308 [0-9]*
309 )?
310 (?: # post release
311 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
312 )?
313 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
314 )
315 |
316 (?:
317 # All other operators only allow a sub set of what the
318 # (non)equality operators do. Specifically they do not allow
319 # local versions to be specified nor do they allow the prefix
320 # matching wild cards.
321 (?:<=|>=|<|>)
323 \s*
324 v?
325 (?:[0-9]+!)? # epoch
326 [0-9]+(?:\.[0-9]+)* # release
327 (?a: # pre release
328 [-_\.]?
329 (alpha|beta|preview|pre|a|b|c|rc)
330 [-_\.]?
331 [0-9]*
332 )?
333 (?a: # post release
334 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
335 )?
336 (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
337 )
338 )
339 """
341 _regex = re.compile(
342 r"\s*" + _specifier_regex_str + r"\s*", re.VERBOSE | re.IGNORECASE
343 )
345 # Legacy unused attribute, kept for backward compatibility
346 _operators: Final = {
347 "~=": "compatible",
348 "==": "equal",
349 "!=": "not_equal",
350 "<=": "less_than_equal",
351 ">=": "greater_than_equal",
352 "<": "less_than",
353 ">": "greater_than",
354 "===": "arbitrary",
355 }
357 def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
358 """Initialize a Specifier instance.
360 :param spec:
361 The string representation of a specifier which will be parsed and
362 normalized before use.
363 :param prereleases:
364 This tells the specifier if it should accept prerelease versions if
365 applicable or not. The default of ``None`` will autodetect it from the
366 given specifiers.
367 :raises InvalidSpecifier:
368 If the given specifier is invalid (i.e. bad syntax).
369 """
370 if not self._regex.fullmatch(spec):
371 raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
373 spec = spec.strip()
374 if spec.startswith("==="):
375 operator, version = spec[:3], spec[3:].strip()
376 elif spec.startswith(("~=", "==", "!=", "<=", ">=")):
377 operator, version = spec[:2], spec[2:].strip()
378 else:
379 operator, version = spec[:1], spec[1:].strip()
381 self._spec: tuple[str, str] = (operator, version)
383 # Store whether or not this Specifier should accept prereleases
384 self._prereleases = prereleases
386 # Specifier version cache
387 self._spec_version: tuple[str, Version] | None = None
389 # Version range cache (populated by _to_ranges)
390 self._ranges: Sequence[VersionRange] | None = None
392 def _get_spec_version(self, version: str) -> Version | None:
393 """One element cache, as only one spec Version is needed per Specifier."""
394 if self._spec_version is not None and self._spec_version[0] == version:
395 return self._spec_version[1]
397 version_specifier = _coerce_version(version)
398 if version_specifier is None:
399 return None
401 self._spec_version = (version, version_specifier)
402 return version_specifier
404 def _require_spec_version(self, version: str) -> Version:
405 """Get spec version, asserting it's valid (not for === operator).
407 This method should only be called for operators where version
408 strings are guaranteed to be valid PEP 440 versions (not ===).
409 """
410 spec_version = self._get_spec_version(version)
411 assert spec_version is not None
412 return spec_version
414 def _to_ranges(self) -> Sequence[VersionRange]:
415 """Convert this specifier to sorted, non-overlapping version ranges.
417 Each standard operator maps to one or two ranges. ``===`` is
418 modeled as full range (actual check done separately). Cached.
419 """
420 if self._ranges is not None:
421 return self._ranges
423 op = self.operator
424 ver_str = self.version
426 if op == "===":
427 result: Sequence[VersionRange] = FULL_RANGE
428 elif ver_str.endswith(".*"):
429 base = self._require_spec_version(ver_str[:-2])
430 result = wildcard_ranges(op, base)
431 else:
432 v = self._require_spec_version(ver_str)
433 has_local = "+" in ver_str
434 result = standard_ranges(op, v, has_local)
436 self._ranges = result
437 return result
439 @property
440 def prereleases(self) -> bool | None:
441 # If there is an explicit prereleases set for this, then we'll just
442 # blindly use that.
443 if self._prereleases is not None:
444 return self._prereleases
446 # Only the "!=" operator does not imply prereleases when
447 # the version in the specifier is a prerelease.
448 operator, version_str = self._spec
449 if operator == "!=":
450 return False
452 # The == specifier with trailing .* cannot include prereleases
453 # e.g. "==1.0a1.*" is not valid.
454 if operator == "==" and version_str.endswith(".*"):
455 return False
457 # "===" can have arbitrary string versions, so we cannot parse
458 # those, we take prereleases as unknown (None) for those.
459 version = self._get_spec_version(version_str)
460 if version is None:
461 return None
463 # For all other operators, use the check if spec Version
464 # object implies pre-releases.
465 return version.is_prerelease
467 @prereleases.setter
468 def prereleases(self, value: bool | None) -> None:
469 self._prereleases = value
471 def __getstate__(self) -> tuple[tuple[str, str], bool | None]:
472 # Return state as a 2-item tuple for compactness:
473 # ((operator, version), prereleases)
474 # Cache members are excluded and will be recomputed on demand.
475 return (self._spec, self._prereleases)
477 def __setstate__(self, state: object) -> None:
478 # Always discard cached values - they will be recomputed on demand.
479 self._spec_version = None
480 self._ranges = None
482 if isinstance(state, tuple):
483 if len(state) == 2:
484 # New format (26.2+): ((operator, version), prereleases)
485 spec, prereleases = state
486 if _validate_spec(spec) and _validate_pre(prereleases):
487 self._spec = spec
488 self._prereleases = prereleases
489 return
490 if len(state) == 2 and isinstance(state[1], dict):
491 # Format (packaging 26.0-26.1): (None, {slot: value}).
492 _, slot_dict = state
493 spec = slot_dict.get("_spec")
494 prereleases = slot_dict.get("_prereleases", "invalid")
495 if _validate_spec(spec) and _validate_pre(prereleases):
496 self._spec = spec
497 self._prereleases = prereleases
498 return
499 if isinstance(state, dict):
500 # Old format (packaging <= 25.x, no __slots__): state is a plain dict.
501 spec = state.get("_spec")
502 prereleases = state.get("_prereleases", "invalid")
503 if _validate_spec(spec) and _validate_pre(prereleases):
504 self._spec = spec
505 self._prereleases = prereleases
506 return
508 raise TypeError(f"Cannot restore Specifier from {state!r}")
510 @property
511 def operator(self) -> str:
512 """The operator of this specifier.
514 >>> Specifier("==1.2.3").operator
515 '=='
516 """
517 return self._spec[0]
519 @property
520 def version(self) -> str:
521 """The version of this specifier.
523 >>> Specifier("==1.2.3").version
524 '1.2.3'
525 """
526 return self._spec[1]
528 def __repr__(self) -> str:
529 """A representation of the Specifier that shows all internal state.
531 >>> Specifier('>=1.0.0')
532 <Specifier('>=1.0.0')>
533 >>> Specifier('>=1.0.0', prereleases=False)
534 <Specifier('>=1.0.0', prereleases=False)>
535 >>> Specifier('>=1.0.0', prereleases=True)
536 <Specifier('>=1.0.0', prereleases=True)>
537 """
538 pre = (
539 f", prereleases={self.prereleases!r}"
540 if self._prereleases is not None
541 else ""
542 )
544 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
546 def __str__(self) -> str:
547 """A string representation of the Specifier that can be round-tripped.
549 >>> str(Specifier('>=1.0.0'))
550 '>=1.0.0'
551 >>> str(Specifier('>=1.0.0', prereleases=False))
552 '>=1.0.0'
553 """
554 return "{}{}".format(*self._spec)
556 @property
557 def _canonical_spec(self) -> tuple[str, str]:
558 operator, version = self._spec
559 if operator == "===" or version.endswith(".*"):
560 return operator, version
562 spec_version = self._require_spec_version(version)
564 canonical_version = canonicalize_version(
565 spec_version, strip_trailing_zero=(operator != "~=")
566 )
568 return operator, canonical_version
570 def __hash__(self) -> int:
571 return hash(self._canonical_spec)
573 def __eq__(self, other: object) -> bool:
574 """Whether or not the two Specifier-like objects are equal.
576 :param other: The other object to check against.
578 The value of :attr:`prereleases` is ignored.
580 >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
581 True
582 >>> (Specifier("==1.2.3", prereleases=False) ==
583 ... Specifier("==1.2.3", prereleases=True))
584 True
585 >>> Specifier("==1.2.3") == "==1.2.3"
586 True
587 >>> Specifier("==1.2.3") == Specifier("==1.2.4")
588 False
589 >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
590 False
591 """
592 if isinstance(other, str):
593 try:
594 other = self.__class__(str(other))
595 except InvalidSpecifier:
596 return NotImplemented
597 elif not isinstance(other, self.__class__):
598 return NotImplemented
600 return self._canonical_spec == other._canonical_spec
602 def __contains__(self, item: str | Version) -> bool:
603 """Return whether or not the item is contained in this specifier.
605 :param item: The item to check for.
607 This is used for the ``in`` operator and behaves the same as
608 :meth:`contains` with no ``prereleases`` argument passed.
610 >>> "1.2.3" in Specifier(">=1.2.3")
611 True
612 >>> Version("1.2.3") in Specifier(">=1.2.3")
613 True
614 >>> "1.0.0" in Specifier(">=1.2.3")
615 False
616 >>> "1.3.0a1" in Specifier(">=1.2.3")
617 True
618 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
619 True
620 """
621 return self.contains(item)
623 def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
624 """Return whether or not the item is contained in this specifier.
626 :param item:
627 The item to check for, which can be a version string or a
628 :class:`Version` instance.
629 :param prereleases:
630 Whether or not to match prereleases with this Specifier. If set to
631 ``None`` (the default), it will follow the recommendation from
632 :pep:`440` and match prereleases, as there are no other versions.
634 >>> Specifier(">=1.2.3").contains("1.2.3")
635 True
636 >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
637 True
638 >>> Specifier(">=1.2.3").contains("1.0.0")
639 False
640 >>> Specifier(">=1.2.3").contains("1.3.0a1")
641 True
642 >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1")
643 False
644 >>> Specifier(">=1.2.3").contains("1.3.0a1")
645 True
646 """
647 # ``===`` compares the raw string, so a Version parse here would
648 # be wasted.
649 if self._spec[0] == "===":
650 return bool(list(self.filter([item], prereleases=prereleases)))
652 parsed = _coerce_version(item)
653 if parsed is None:
654 # Standard operators never match an unparsable input.
655 return False
657 match = _fast_match(self, parsed)
658 if match is not None:
659 if prereleases is None:
660 if self._prereleases is not None:
661 prereleases = self._prereleases
662 elif self.prereleases:
663 prereleases = True
664 if prereleases is False and parsed.is_prerelease:
665 return False
666 return match
668 # Pass the already-parsed Version so filter_by_ranges doesn't
669 # re-coerce it.
670 return bool(list(self.filter([parsed], prereleases=prereleases)))
672 @typing.overload
673 def filter(
674 self,
675 iterable: Iterable[UnparsedVersionVar],
676 prereleases: bool | None = None,
677 key: None = ...,
678 ) -> Iterator[UnparsedVersionVar]: ...
680 @typing.overload
681 def filter(
682 self,
683 iterable: Iterable[T],
684 prereleases: bool | None = None,
685 key: Callable[[T], UnparsedVersion] = ...,
686 ) -> Iterator[T]: ...
688 def filter(
689 self,
690 iterable: Iterable[Any],
691 prereleases: bool | None = None,
692 key: Callable[[Any], UnparsedVersion] | None = None,
693 ) -> Iterator[Any]:
694 """Filter items in the given iterable, that match the specifier.
696 :param iterable:
697 An iterable that can contain version strings and :class:`Version` instances.
698 The items in the iterable will be filtered according to the specifier.
699 :param prereleases:
700 Whether or not to allow prereleases in the returned iterator. If set to
701 ``None`` (the default), it will follow the recommendation from :pep:`440`
702 and match prereleases if there are no other versions.
703 :param key:
704 A callable that takes a single argument (an item from the iterable) and
705 returns a version string or :class:`Version` instance to be used for
706 filtering.
708 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
709 ['1.3']
710 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
711 ['1.2.3', '1.3', <Version('1.4')>]
712 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
713 ['1.5a1']
714 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
715 ['1.3', '1.5a1']
716 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
717 ['1.3', '1.5a1']
718 >>> list(Specifier(">=1.2.3").filter(
719 ... [{"ver": "1.2"}, {"ver": "1.3"}],
720 ... key=lambda x: x["ver"]))
721 [{'ver': '1.3'}]
722 """
723 if prereleases is None:
724 if self._prereleases is not None:
725 prereleases = self._prereleases
726 elif self.prereleases:
727 prereleases = True
729 if self.operator == "===":
730 spec_lower = self.version.lower()
731 matches = (
732 item
733 for item in iterable
734 if str(item if key is None else key(item)).lower() == spec_lower
735 )
736 return _apply_prereleases_filter(matches, key, prereleases)
738 ranges = self._ranges
739 if ranges is None:
740 ranges = self._to_ranges()
741 return filter_by_ranges(ranges, iterable, key, prereleases)
744def _apply_prereleases_filter(
745 matches: Iterable[Any],
746 key: Callable[[Any], UnparsedVersion] | None,
747 prereleases: bool | None,
748) -> Iterator[Any]:
749 """Apply ``prereleases=`` handling to an already-matched iterable.
751 ``None`` means PEP 440 default (buffer pre-releases until a final
752 appears); ``True`` yields everything; ``False`` drops pre-releases.
753 """
754 if prereleases is None:
755 return _pep440_filter_prereleases(matches, key)
756 if prereleases:
757 return iter(matches)
758 return (
759 item
760 for item in matches
761 if (parsed := _coerce_version(item if key is None else key(item))) is None
762 or not parsed.is_prerelease
763 )
766class SpecifierSet(BaseSpecifier):
767 """This class abstracts handling of a set of version specifiers.
769 It can be passed a single specifier (``>=3.0``), a comma-separated list of
770 specifiers (``>=3.0,!=3.1``), or no specifier at all.
772 Instances are safe to serialize with :mod:`pickle`. They use a stable
773 format so the same pickle can be loaded in future packaging
774 releases.
776 .. versionchanged:: 26.2
778 Added a stable pickle format. Pickles created with
779 packaging 26.2+ can be unpickled with future releases.
780 Backward compatibility with pickles from
781 packaging < 26.2 is supported but may be removed in a future
782 release.
783 """
785 __slots__ = (
786 "_canonicalized",
787 "_has_arbitrary",
788 "_is_unsatisfiable",
789 "_prereleases",
790 "_ranges",
791 "_specs",
792 )
794 def __init__(
795 self,
796 specifiers: str | Iterable[Specifier] = "",
797 prereleases: bool | None = None,
798 ) -> None:
799 """Initialize a SpecifierSet instance.
801 :param specifiers:
802 The string representation of a specifier or a comma-separated list of
803 specifiers which will be parsed and normalized before use.
804 May also be an iterable of ``Specifier`` instances, which will be used
805 as is.
806 :param prereleases:
807 This tells the SpecifierSet if it should accept prerelease versions if
808 applicable or not. The default of ``None`` will autodetect it from the
809 given specifiers.
811 :raises InvalidSpecifier:
812 If the given ``specifiers`` are not parseable than this exception will be
813 raised.
814 """
816 if isinstance(specifiers, str):
817 # Split on `,` to break each individual specifier into its own item, and
818 # strip each item to remove leading/trailing whitespace.
819 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
821 self._specs: tuple[Specifier, ...] = tuple(map(Specifier, split_specifiers))
822 # Fast substring check; avoids iterating parsed specs.
823 self._has_arbitrary = "===" in specifiers
824 else:
825 self._specs = tuple(specifiers)
826 # Substring check works for both Specifier objects and plain
827 # strings (setuptools passes lists of strings).
828 self._has_arbitrary = any("===" in str(s) for s in self._specs)
830 self._canonicalized = len(self._specs) <= 1
831 self._is_unsatisfiable: bool | None = None
832 self._ranges: Sequence[VersionRange] | None = None
834 # Store our prereleases value so we can use it later to determine if
835 # we accept prereleases or not.
836 self._prereleases = prereleases
838 def _canonical_specs(self) -> tuple[Specifier, ...]:
839 """Deduplicate, sort, and cache specs for order-sensitive operations."""
840 if not self._canonicalized:
841 self._specs = tuple(dict.fromkeys(sorted(self._specs, key=str)))
842 self._canonicalized = True
843 self._is_unsatisfiable = None
844 self._ranges = None
845 return self._specs
847 @property
848 def prereleases(self) -> bool | None:
849 # If we have been given an explicit prerelease modifier, then we'll
850 # pass that through here.
851 if self._prereleases is not None:
852 return self._prereleases
854 # If we don't have any specifiers, and we don't have a forced value,
855 # then we'll just return None since we don't know if this should have
856 # pre-releases or not.
857 if not self._specs:
858 return None
860 # Otherwise we'll see if any of the given specifiers accept
861 # prereleases, if any of them do we'll return True, otherwise False.
862 if any(s.prereleases for s in self._specs):
863 return True
865 return None
867 @prereleases.setter
868 def prereleases(self, value: bool | None) -> None:
869 self._prereleases = value
870 self._is_unsatisfiable = None
872 def __getstate__(self) -> tuple[tuple[Specifier, ...], bool | None]:
873 # Return state as a 2-item tuple for compactness:
874 # (specs, prereleases)
875 # Cache members are excluded and will be recomputed on demand.
876 return (self._specs, self._prereleases)
878 def __setstate__(self, state: object) -> None:
879 # Always discard cached values - they will be recomputed on demand.
880 self._ranges = None
881 self._is_unsatisfiable = None
883 if isinstance(state, tuple):
884 if len(state) == 2:
885 # New format (26.2+): (specs, prereleases)
886 specs, prereleases = state
887 if (
888 isinstance(specs, tuple)
889 and all(isinstance(s, Specifier) for s in specs)
890 and _validate_pre(prereleases)
891 ):
892 self._specs = specs
893 self._prereleases = prereleases
894 self._canonicalized = len(specs) <= 1
895 self._has_arbitrary = any("===" in str(s) for s in specs)
896 return
897 if len(state) == 2 and isinstance(state[1], dict):
898 # Format (packaging 26.0-26.1): (None, {slot: value}).
899 _, slot_dict = state
900 specs = slot_dict.get("_specs", ())
901 prereleases = slot_dict.get("_prereleases")
902 # Convert frozenset to tuple (26.0 stored as frozenset)
903 if isinstance(specs, frozenset):
904 specs = tuple(sorted(specs, key=str))
905 if (
906 isinstance(specs, tuple)
907 and all(isinstance(s, Specifier) for s in specs)
908 and _validate_pre(prereleases)
909 ):
910 self._specs = specs
911 self._prereleases = prereleases
912 self._canonicalized = len(self._specs) <= 1
913 self._has_arbitrary = any("===" in str(s) for s in self._specs)
914 return
915 if isinstance(state, dict):
916 # Old format (packaging <= 25.x, no __slots__): state is a plain dict.
917 specs = state.get("_specs", ())
918 prereleases = state.get("_prereleases")
919 # Convert frozenset to tuple (26.0 stored as frozenset)
920 if isinstance(specs, frozenset):
921 specs = tuple(sorted(specs, key=str))
922 if (
923 isinstance(specs, tuple)
924 and all(isinstance(s, Specifier) for s in specs)
925 and _validate_pre(prereleases)
926 ):
927 self._specs = specs
928 self._prereleases = prereleases
929 self._canonicalized = len(self._specs) <= 1
930 self._has_arbitrary = any("===" in str(s) for s in self._specs)
931 return
933 raise TypeError(f"Cannot restore SpecifierSet from {state!r}")
935 def __repr__(self) -> str:
936 """A representation of the specifier set that shows all internal state.
938 Note that the ordering of the individual specifiers within the set may not
939 match the input string.
941 >>> SpecifierSet('>=1.0.0,!=2.0.0')
942 <SpecifierSet('!=2.0.0,>=1.0.0')>
943 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
944 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
945 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
946 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
947 """
948 pre = (
949 f", prereleases={self.prereleases!r}"
950 if self._prereleases is not None
951 else ""
952 )
954 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
956 def __str__(self) -> str:
957 """A string representation of the specifier set that can be round-tripped.
959 Note that the ordering of the individual specifiers within the set may not
960 match the input string.
962 >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
963 '!=1.0.1,>=1.0.0'
964 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
965 '!=1.0.1,>=1.0.0'
966 """
967 return ",".join(str(s) for s in self._canonical_specs())
969 def __hash__(self) -> int:
970 return hash(self._canonical_specs())
972 def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
973 """Return a SpecifierSet which is a combination of the two sets.
975 :param other: The other object to combine with.
977 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
978 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
979 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
980 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
981 """
982 if isinstance(other, str):
983 other = SpecifierSet(other)
984 elif not isinstance(other, SpecifierSet):
985 return NotImplemented
987 specifier = SpecifierSet()
988 specifier._specs = self._specs + other._specs
989 specifier._canonicalized = len(specifier._specs) <= 1
990 specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary
992 # Combine prerelease settings: use common or non-None value
993 if self._prereleases is None or self._prereleases == other._prereleases:
994 specifier._prereleases = other._prereleases
995 elif other._prereleases is None:
996 specifier._prereleases = self._prereleases
997 else:
998 raise ValueError(
999 "Cannot combine SpecifierSets with True and False prerelease overrides."
1000 )
1002 return specifier
1004 def __eq__(self, other: object) -> bool:
1005 """Whether or not the two SpecifierSet-like objects are equal.
1007 :param other: The other object to check against.
1009 The value of :attr:`prereleases` is ignored.
1011 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
1012 True
1013 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
1014 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
1015 True
1016 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
1017 True
1018 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
1019 False
1020 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
1021 False
1022 """
1023 if isinstance(other, (str, Specifier)):
1024 other = SpecifierSet(str(other))
1025 elif not isinstance(other, SpecifierSet):
1026 return NotImplemented
1028 return self._canonical_specs() == other._canonical_specs()
1030 def __len__(self) -> int:
1031 """Returns the number of specifiers in this specifier set."""
1032 return len(self._specs)
1034 def __iter__(self) -> Iterator[Specifier]:
1035 """
1036 Returns an iterator over all the underlying :class:`Specifier` instances
1037 in this specifier set.
1039 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
1040 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
1041 """
1042 return iter(self._specs)
1044 def _get_ranges(self) -> Sequence[VersionRange]:
1045 """Intersect all specifiers into a single sequence of version ranges.
1047 Empty when unsatisfiable. Callers must ensure ``self._specs``
1048 is non-empty.
1049 """
1050 if self._ranges is not None:
1051 return self._ranges
1053 result: Sequence[VersionRange] | None = None
1054 for s in self._specs:
1055 sub = s._to_ranges()
1056 if result is None:
1057 result = sub
1058 else:
1059 result = intersect_ranges(result, sub)
1060 if not result:
1061 break
1063 if result is None: # pragma: no cover
1064 raise RuntimeError("_get_ranges called with no specs")
1065 self._ranges = result
1066 return result
1068 def is_unsatisfiable(self) -> bool:
1069 """Check whether this specifier set can never be satisfied.
1071 Returns True if no version can satisfy all specifiers simultaneously.
1073 >>> SpecifierSet(">=2.0,<1.0").is_unsatisfiable()
1074 True
1075 >>> SpecifierSet(">=1.0,<2.0").is_unsatisfiable()
1076 False
1077 >>> SpecifierSet("").is_unsatisfiable()
1078 False
1079 >>> SpecifierSet("==1.0,!=1.0").is_unsatisfiable()
1080 True
1081 """
1082 cached = self._is_unsatisfiable
1083 if cached is not None:
1084 return cached
1086 if not self._specs:
1087 self._is_unsatisfiable = False
1088 return False
1090 result = not self._get_ranges()
1092 if not result:
1093 result = self._check_arbitrary_unsatisfiable()
1095 if not result and self.prereleases is False:
1096 result = ranges_are_prerelease_only(self._get_ranges())
1098 self._is_unsatisfiable = result
1099 return result
1101 def _check_arbitrary_unsatisfiable(self) -> bool:
1102 """Check === (arbitrary equality) specs for unsatisfiability.
1104 === uses case-insensitive string comparison, so the only candidate
1105 that can match ``===V`` is the literal string V. This method
1106 checks whether that candidate is excluded by other specifiers.
1107 """
1108 arbitrary = [s for s in self._specs if s.operator == "==="]
1109 if not arbitrary:
1110 return False
1112 # Multiple === must agree on the same string (case-insensitive).
1113 first = arbitrary[0].version.lower()
1114 if any(s.version.lower() != first for s in arbitrary[1:]):
1115 return True
1117 # The sole candidate is the === version string. Check whether
1118 # it can satisfy every standard spec.
1119 candidate = _coerce_version(arbitrary[0].version)
1121 # With prereleases=False, a prerelease candidate is excluded
1122 # by contains() before the === string check even runs.
1123 if (
1124 self.prereleases is False
1125 and candidate is not None
1126 and candidate.is_prerelease
1127 ):
1128 return True
1130 standard = [s for s in self._specs if s.operator != "==="]
1131 if not standard:
1132 return False
1134 if candidate is None:
1135 # Unparsable string cannot satisfy any standard spec.
1136 return True
1138 return not all(s.contains(candidate) for s in standard)
1140 def __contains__(self, item: UnparsedVersion) -> bool:
1141 """Return whether or not the item is contained in this specifier.
1143 :param item: The item to check for.
1145 This is used for the ``in`` operator and behaves the same as
1146 :meth:`contains` with no ``prereleases`` argument passed.
1148 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
1149 True
1150 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
1151 True
1152 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
1153 False
1154 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
1155 True
1156 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
1157 True
1158 """
1159 return self.contains(item)
1161 def contains(
1162 self,
1163 item: UnparsedVersion,
1164 prereleases: bool | None = None,
1165 installed: bool | None = None,
1166 ) -> bool:
1167 """Return whether or not the item is contained in this SpecifierSet.
1169 :param item:
1170 The item to check for, which can be a version string or a
1171 :class:`Version` instance.
1172 :param prereleases:
1173 Whether or not to match prereleases with this SpecifierSet. If set to
1174 ``None`` (the default), it will follow the recommendation from :pep:`440`
1175 and match prereleases, as there are no other versions.
1176 :param installed:
1177 Whether or not the item is installed. If set to ``True``, it will
1178 accept prerelease versions even if the specifier does not allow them.
1180 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
1181 True
1182 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
1183 True
1184 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
1185 False
1186 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
1187 True
1188 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")
1189 False
1190 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
1191 True
1192 """
1193 version = _coerce_version(item)
1195 if version is not None and installed and version.is_prerelease:
1196 prereleases = True
1198 # When item is a string and === is involved, keep it as-is
1199 # so the comparison isn't done against the normalized form.
1200 if version is None or (self._has_arbitrary and not isinstance(item, Version)):
1201 check_item = item
1202 else:
1203 check_item = version
1205 # Fast path: skip the intersected-range build while every spec
1206 # answers directly. Once ``_ranges`` is set the cached range
1207 # path beats re-iterating specs, so fall through then. A local
1208 # on ``version`` needs PEP 440 stripping that the range path
1209 # applies.
1210 if (
1211 self._ranges is None
1212 and version is not None
1213 and not self._has_arbitrary
1214 and version.local is None
1215 and self._specs
1216 ):
1217 if version.is_prerelease and (
1218 prereleases is False
1219 or (prereleases is None and self._prereleases is False)
1220 ):
1221 return False
1222 for spec in self._specs:
1223 match = _fast_match(spec, version)
1224 if match is None:
1225 break
1226 if not match:
1227 return False
1228 else:
1229 return True
1231 return bool(list(self.filter([check_item], prereleases=prereleases)))
1233 @typing.overload
1234 def filter(
1235 self,
1236 iterable: Iterable[UnparsedVersionVar],
1237 prereleases: bool | None = None,
1238 key: None = ...,
1239 ) -> Iterator[UnparsedVersionVar]: ...
1241 @typing.overload
1242 def filter(
1243 self,
1244 iterable: Iterable[T],
1245 prereleases: bool | None = None,
1246 key: Callable[[T], UnparsedVersion] = ...,
1247 ) -> Iterator[T]: ...
1249 def filter(
1250 self,
1251 iterable: Iterable[Any],
1252 prereleases: bool | None = None,
1253 key: Callable[[Any], UnparsedVersion] | None = None,
1254 ) -> Iterator[Any]:
1255 """Filter items in the given iterable, that match the specifiers in this set.
1257 :param iterable:
1258 An iterable that can contain version strings and :class:`Version` instances.
1259 The items in the iterable will be filtered according to the specifier.
1260 :param prereleases:
1261 Whether or not to allow prereleases in the returned iterator. If set to
1262 ``None`` (the default), it will follow the recommendation from :pep:`440`
1263 and match prereleases if there are no other versions.
1264 :param key:
1265 A callable that takes a single argument (an item from the iterable) and
1266 returns a version string or :class:`Version` instance to be used for
1267 filtering.
1269 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
1270 ['1.3']
1271 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
1272 ['1.3', <Version('1.4')>]
1273 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
1274 ['1.5a1']
1275 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
1276 ['1.3', '1.5a1']
1277 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
1278 ['1.3', '1.5a1']
1279 >>> list(SpecifierSet(">=1.2.3").filter(
1280 ... [{"ver": "1.2"}, {"ver": "1.3"}],
1281 ... key=lambda x: x["ver"]))
1282 [{'ver': '1.3'}]
1284 An "empty" SpecifierSet will filter items based on the presence of prerelease
1285 versions in the set.
1287 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
1288 ['1.3']
1289 >>> list(SpecifierSet("").filter(["1.5a1"]))
1290 ['1.5a1']
1291 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
1292 ['1.3', '1.5a1']
1293 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
1294 ['1.3', '1.5a1']
1295 """
1296 # Determine if we're forcing a prerelease or not, if we're not forcing
1297 # one for this particular filter call, then we'll use whatever the
1298 # SpecifierSet thinks for whether or not we should support prereleases.
1299 if prereleases is None and self.prereleases is not None:
1300 prereleases = self.prereleases
1302 if self._specs:
1303 if self._has_arbitrary:
1304 # Slow path for ===
1305 specs = self._specs
1306 matches = (
1307 item
1308 for item in iterable
1309 if all(
1310 s.contains(item if key is None else key(item), prereleases=True)
1311 for s in specs
1312 )
1313 )
1314 return _apply_prereleases_filter(matches, key, prereleases)
1316 ranges = self._ranges
1317 if ranges is None:
1318 ranges = self._get_ranges()
1319 return filter_by_ranges(ranges, iterable, key, prereleases)
1321 # Empty SpecifierSet.
1322 return _apply_prereleases_filter(iterable, key, prereleases)
1325def _pep440_filter_prereleases(
1326 iterable: Iterable[Any], key: Callable[[Any], UnparsedVersion] | None
1327) -> Iterator[Any]:
1328 """Filter per PEP 440: exclude prereleases unless no finals exist."""
1329 # Two lists used:
1330 # * all_nonfinal to preserve order if no finals exist
1331 # * arbitrary_strings for streaming when first final found
1332 all_nonfinal: list[Any] = []
1333 arbitrary_strings: list[Any] = []
1335 found_final = False
1336 for item in iterable:
1337 parsed = _coerce_version(item if key is None else key(item))
1339 if parsed is None:
1340 # Arbitrary strings are always included as it is not
1341 # possible to determine if they are prereleases,
1342 # and they have already passed all specifiers.
1343 if found_final:
1344 yield item
1345 else:
1346 arbitrary_strings.append(item)
1347 all_nonfinal.append(item)
1348 continue
1350 if not parsed.is_prerelease:
1351 # Final release found - flush arbitrary strings, then yield
1352 if not found_final:
1353 yield from arbitrary_strings
1354 found_final = True
1355 yield item
1356 continue
1358 # Prerelease - buffer if no finals yet, otherwise skip
1359 if not found_final:
1360 all_nonfinal.append(item)
1362 # No finals found - yield all buffered items
1363 if not found_final:
1364 yield from all_nonfinal