Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/packaging/specifiers.py: 14%
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 typing
16from typing import (
17 TYPE_CHECKING,
18 Any,
19 Callable,
20 Final,
21 TypeVar,
22 Union,
23)
25from ._ranges import (
26 FULL_RANGE,
27 bounds_for_spec,
28 coerce_version,
29 filter_by_ranges,
30 intersect_specifier_bounds,
31 matches_bounds_only,
32 ranges_are_prerelease_only,
33 resolve_prereleases,
34 trim_release,
35)
36from .utils import canonicalize_version
37from .version import Version
39if TYPE_CHECKING:
40 import sys
41 from collections.abc import Iterable, Iterator, Sequence
43 if sys.version_info >= (3, 10):
44 from typing import TypeGuard
45 else:
46 from typing_extensions import TypeGuard
48 from . import ranges
49 from ._ranges import VersionRange
52__all__ = [
53 "BaseSpecifier",
54 "InvalidSpecifier",
55 "Specifier",
56 "SpecifierSet",
57]
60def __dir__() -> list[str]:
61 return __all__
64def _validate_spec(spec: object, /) -> TypeGuard[tuple[str, str]]:
65 return (
66 isinstance(spec, tuple)
67 and len(spec) == 2
68 and isinstance(spec[0], str)
69 and isinstance(spec[1], str)
70 )
73def _validate_pre(pre: object, /) -> TypeGuard[bool | None]:
74 return pre is None or isinstance(pre, bool)
77T = TypeVar("T")
78UnparsedVersion = Union[Version, str]
79UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
82# Operators whose result is just a direct Version comparison, given a parsed
83# item with no local. ``<=``/``==``/``!=`` need that no-local guard because
84# PEP 440 strips locals on those; ``>=`` works regardless.
85_DIRECT_COMPARE_OPS: dict[str, Callable[[Version, Version], bool]] = {
86 ">=": Version.__ge__,
87 "<=": Version.__le__,
88 "==": Version.__eq__,
89 "!=": Version.__ne__,
90}
93def _fast_match(specifier: Specifier, parsed: Version) -> bool | None:
94 """Match ``parsed`` against ``specifier`` without building a range.
96 Handles ``>=``, ``<=``, ``==``, ``!=``, ``<``, ``>`` when the spec is
97 not a wildcard and ``parsed`` has no local. Returns ``None`` when the
98 range path must be used. Pre-release policy is left to the caller.
99 """
100 op_str, ver_str = specifier._spec
101 if ver_str.endswith(".*") or parsed.local is not None:
102 return None
104 direct_compare = _DIRECT_COMPARE_OPS.get(op_str)
105 if direct_compare is not None:
106 return direct_compare(parsed, specifier._require_spec_version(ver_str))
108 if op_str in ("<", ">"):
109 spec_v = specifier._require_spec_version(ver_str)
110 # ``<V``/``>V`` carve out V's family (pre/dev/post); that only
111 # matters when parsed shares V's epoch and trimmed release.
112 # Otherwise a direct cmpkey comparison is correct.
113 if parsed.epoch != spec_v.epoch or trim_release(parsed.release) != trim_release(
114 spec_v.release
115 ):
116 return parsed < spec_v if op_str == "<" else parsed > spec_v
117 return None
119 return None
122class InvalidSpecifier(ValueError):
123 """
124 Raised when attempting to create a :class:`Specifier` with a specifier
125 string that is invalid.
127 >>> Specifier("lolwat")
128 Traceback (most recent call last):
129 ...
130 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
131 """
134class BaseSpecifier(metaclass=abc.ABCMeta):
135 """
136 Abstract base class for :class:`Specifier` and :class:`SpecifierSet`.
137 """
139 __slots__ = ()
140 __match_args__ = ("_str",)
142 @property
143 def _str(self) -> str:
144 """Internal property for match_args"""
145 return str(self)
147 @abc.abstractmethod
148 def __str__(self) -> str:
149 """
150 Returns the str representation of this Specifier-like object. This
151 should be representative of the Specifier itself.
152 """
154 @abc.abstractmethod
155 def __hash__(self) -> int:
156 """
157 Returns a hash value for this Specifier-like object.
158 """
160 @abc.abstractmethod
161 def __eq__(self, other: object) -> bool:
162 """
163 Returns a boolean representing whether or not the two Specifier-like
164 objects are equal.
166 :param other: The other object to check against.
167 """
169 @property
170 @abc.abstractmethod
171 def prereleases(self) -> bool | None:
172 """Whether or not pre-releases as a whole are allowed.
174 This can be set to either ``True`` or ``False`` to explicitly enable or disable
175 prereleases or it can be set to ``None`` (the default) to use default semantics.
176 """
178 @prereleases.setter # noqa: B027
179 def prereleases(self, value: bool) -> None:
180 """Setter for :attr:`prereleases`.
182 :param value: The value to set.
183 """
185 @abc.abstractmethod
186 def contains(self, item: str, prereleases: bool | None = None) -> bool:
187 """
188 Determines if the given item is contained within this specifier.
189 """
191 @typing.overload
192 def filter(
193 self,
194 iterable: Iterable[UnparsedVersionVar],
195 prereleases: bool | None = None,
196 key: None = ...,
197 ) -> Iterator[UnparsedVersionVar]: ...
199 @typing.overload
200 def filter(
201 self,
202 iterable: Iterable[T],
203 prereleases: bool | None = None,
204 key: Callable[[T], UnparsedVersion] = ...,
205 ) -> Iterator[T]: ...
207 @abc.abstractmethod
208 def filter(
209 self,
210 iterable: Iterable[Any],
211 prereleases: bool | None = None,
212 key: Callable[[Any], UnparsedVersion] | None = None,
213 ) -> Iterator[Any]:
214 """
215 Takes an iterable of items and filters them so that only items which
216 are contained within this specifier are allowed in it.
217 """
220class Specifier(BaseSpecifier):
221 """This class abstracts handling of version specifiers.
223 .. tip::
225 It is generally not required to instantiate this manually. You should instead
226 prefer to work with :class:`SpecifierSet` instead, which can parse
227 comma-separated version specifiers (which is what package metadata contains).
229 Instances are safe to serialize with :mod:`pickle`. They use a stable
230 format so the same pickle can be loaded in future packaging releases.
232 .. versionchanged:: 26.2
234 Added a stable pickle format. Pickles created with packaging 26.2+ can
235 be unpickled with future releases. Backward compatibility with pickles
236 from packaging < 26.2 is supported but may be removed in a future
237 release.
238 """
240 __slots__ = (
241 "_prereleases",
242 "_ranges",
243 "_spec",
244 "_spec_version",
245 )
247 _specifier_regex_str = r"""
248 (?:
249 (?:
250 # The identity operators allow for an escape hatch that will
251 # do an exact string match of the version you wish to install.
252 # This will not be parsed by PEP 440 and we cannot determine
253 # any semantic meaning from it. This operator is discouraged
254 # but included entirely as an escape hatch.
255 === # Only match for the identity operator
256 \s*
257 [^\s;)]* # The arbitrary version can be just about anything,
258 # we match everything except for whitespace, a
259 # semi-colon for marker support, and a closing paren
260 # since versions can be enclosed in them.
261 )
262 |
263 (?:
264 # The (non)equality operators allow for wild card and local
265 # versions to be specified so we have to define these two
266 # operators separately to enable that.
267 (?:==|!=) # Only match for equals and not equals
269 \s*
270 v?
271 (?:[0-9]+!)? # epoch
272 [0-9]+(?:\.[0-9]+)* # release
274 # You cannot use a wild card and a pre-release, post-release, a dev or
275 # local version together so group them with a | and make them optional.
276 (?:
277 \.\* # Wild card syntax of .*
278 |
279 (?a: # pre release
280 [-_\.]?
281 (alpha|beta|preview|pre|a|b|c|rc)
282 [-_\.]?
283 [0-9]*
284 )?
285 (?a: # post release
286 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
287 )?
288 (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
289 (?a:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
290 )?
291 )
292 |
293 (?:
294 # The compatible operator requires at least two digits in the
295 # release segment.
296 (?:~=) # Only match for the compatible operator
298 \s*
299 v?
300 (?:[0-9]+!)? # epoch
301 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
302 (?: # pre release
303 [-_\.]?
304 (alpha|beta|preview|pre|a|b|c|rc)
305 [-_\.]?
306 [0-9]*
307 )?
308 (?: # post release
309 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
310 )?
311 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
312 )
313 |
314 (?:
315 # All other operators only allow a sub set of what the
316 # (non)equality operators do. Specifically they do not allow
317 # local versions to be specified nor do they allow the prefix
318 # matching wild cards.
319 (?:<=|>=|<|>)
321 \s*
322 v?
323 (?:[0-9]+!)? # epoch
324 [0-9]+(?:\.[0-9]+)* # release
325 (?a: # pre release
326 [-_\.]?
327 (alpha|beta|preview|pre|a|b|c|rc)
328 [-_\.]?
329 [0-9]*
330 )?
331 (?a: # post release
332 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
333 )?
334 (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
335 )
336 )
337 """
339 _regex = re.compile(
340 r"\s*" + _specifier_regex_str + r"\s*", re.VERBOSE | re.IGNORECASE
341 )
343 # Legacy unused attribute, kept for backward compatibility
344 _operators: Final = {
345 "~=": "compatible",
346 "==": "equal",
347 "!=": "not_equal",
348 "<=": "less_than_equal",
349 ">=": "greater_than_equal",
350 "<": "less_than",
351 ">": "greater_than",
352 "===": "arbitrary",
353 }
355 def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
356 """Initialize a Specifier instance.
358 :param spec:
359 The string representation of a specifier which will be parsed and
360 normalized before use.
361 :param prereleases:
362 This tells the specifier if it should accept prerelease versions if
363 applicable or not. The default of ``None`` will autodetect it from the
364 given specifiers.
365 :raises InvalidSpecifier:
366 If the given specifier is invalid (i.e. bad syntax).
367 """
368 if not self._regex.fullmatch(spec):
369 raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
371 spec = spec.strip()
372 if spec.startswith("==="):
373 operator, version = spec[:3], spec[3:].strip()
374 elif spec.startswith(("~=", "==", "!=", "<=", ">=")):
375 operator, version = spec[:2], spec[2:].strip()
376 else:
377 operator, version = spec[:1], spec[1:].strip()
379 self._spec: tuple[str, str] = (operator, version)
381 # Store whether or not this Specifier should accept prereleases
382 self._prereleases = prereleases
384 # Specifier version cache
385 self._spec_version: tuple[str, Version] | None = None
387 # Version range cache (populated by _to_ranges)
388 self._ranges: Sequence[VersionRange] | None = None
390 def _get_spec_version(self, version: str) -> Version | None:
391 """One element cache, as only one spec Version is needed per Specifier."""
392 if self._spec_version is not None and self._spec_version[0] == version:
393 return self._spec_version[1]
395 version_specifier = coerce_version(version)
396 if version_specifier is None:
397 return None
399 self._spec_version = (version, version_specifier)
400 return version_specifier
402 def _require_spec_version(self, version: str) -> Version:
403 """Get spec version, asserting it's valid (not for === operator).
405 This method should only be called for operators where version
406 strings are guaranteed to be valid PEP 440 versions (not ===).
407 """
408 spec_version = self._get_spec_version(version)
409 assert spec_version is not None
410 return spec_version
412 def _to_ranges(self) -> Sequence[VersionRange]:
413 """Convert this specifier to sorted, non-overlapping version ranges.
415 Each standard operator maps to one or two ranges. ``===`` is
416 modeled as full range (actual check done separately). Cached.
417 """
418 if self._ranges is not None:
419 return self._ranges
421 op = self.operator
422 ver_str = self.version
424 if op == "===":
425 result: Sequence[VersionRange] = FULL_RANGE
426 else:
427 version = self._require_spec_version(ver_str.removesuffix(".*"))
428 result = bounds_for_spec(op, ver_str, version)
430 self._ranges = result
431 return result
433 @property
434 def prereleases(self) -> bool | None:
435 # If there is an explicit prereleases set for this, then we'll just
436 # blindly use that.
437 if self._prereleases is not None:
438 return self._prereleases
440 # Only the "!=" operator does not imply prereleases when
441 # the version in the specifier is a prerelease.
442 operator, version_str = self._spec
443 if operator == "!=":
444 return False
446 # The == specifier with trailing .* cannot include prereleases
447 # e.g. "==1.0a1.*" is not valid.
448 if operator == "==" and version_str.endswith(".*"):
449 return False
451 # "===" can have arbitrary string versions, so we cannot parse
452 # those, we take prereleases as unknown (None) for those.
453 version = self._get_spec_version(version_str)
454 if version is None:
455 return None
457 # For all other operators, use the check if spec Version
458 # object implies pre-releases.
459 return version.is_prerelease
461 @prereleases.setter
462 def prereleases(self, value: bool | None) -> None:
463 self._prereleases = value
465 def __getstate__(self) -> tuple[tuple[str, str], bool | None]:
466 # Return state as a 2-item tuple for compactness:
467 # ((operator, version), prereleases)
468 # Cache members are excluded and will be recomputed on demand.
469 return (self._spec, self._prereleases)
471 def __setstate__(self, state: object) -> None:
472 # Always discard cached values - they will be recomputed on demand.
473 self._spec_version = None
474 self._ranges = None
476 if isinstance(state, tuple):
477 if len(state) == 2:
478 # New format (26.2+): ((operator, version), prereleases)
479 spec, prereleases = state
480 if _validate_spec(spec) and _validate_pre(prereleases):
481 self._spec = spec
482 self._prereleases = prereleases
483 return
484 if len(state) == 2 and isinstance(state[1], dict):
485 # Format (packaging 26.0-26.1): (None, {slot: value}).
486 _, slot_dict = state
487 spec = slot_dict.get("_spec")
488 prereleases = slot_dict.get("_prereleases", "invalid")
489 if _validate_spec(spec) and _validate_pre(prereleases):
490 self._spec = spec
491 self._prereleases = prereleases
492 return
493 if isinstance(state, dict):
494 # Old format (packaging <= 25.x, no __slots__): state is a plain dict.
495 spec = state.get("_spec")
496 prereleases = state.get("_prereleases", "invalid")
497 if _validate_spec(spec) and _validate_pre(prereleases):
498 self._spec = spec
499 self._prereleases = prereleases
500 return
502 raise TypeError(f"Cannot restore Specifier from {state!r}")
504 @property
505 def operator(self) -> str:
506 """The operator of this specifier.
508 >>> Specifier("==1.2.3").operator
509 '=='
510 """
511 return self._spec[0]
513 @property
514 def version(self) -> str:
515 """The version of this specifier.
517 >>> Specifier("==1.2.3").version
518 '1.2.3'
519 """
520 return self._spec[1]
522 def __repr__(self) -> str:
523 """A representation of the Specifier that shows all internal state.
525 >>> Specifier('>=1.0.0')
526 <Specifier('>=1.0.0')>
527 >>> Specifier('>=1.0.0', prereleases=False)
528 <Specifier('>=1.0.0', prereleases=False)>
529 >>> Specifier('>=1.0.0', prereleases=True)
530 <Specifier('>=1.0.0', prereleases=True)>
531 """
532 pre = (
533 f", prereleases={self.prereleases!r}"
534 if self._prereleases is not None
535 else ""
536 )
538 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
540 def __str__(self) -> str:
541 """A string representation of the Specifier that can be round-tripped.
543 >>> str(Specifier('>=1.0.0'))
544 '>=1.0.0'
545 >>> str(Specifier('>=1.0.0', prereleases=False))
546 '>=1.0.0'
547 """
548 return "{}{}".format(*self._spec)
550 @property
551 def _canonical_spec(self) -> tuple[str, str]:
552 operator, version = self._spec
553 if operator == "===" or version.endswith(".*"):
554 return operator, version
556 spec_version = self._require_spec_version(version)
558 canonical_version = canonicalize_version(
559 spec_version, strip_trailing_zero=(operator != "~=")
560 )
562 return operator, canonical_version
564 def __hash__(self) -> int:
565 return hash(self._canonical_spec)
567 def __eq__(self, other: object) -> bool:
568 """Whether or not the two Specifier-like objects are equal.
570 :param other: The other object to check against.
572 The value of :attr:`prereleases` is ignored.
574 >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
575 True
576 >>> (Specifier("==1.2.3", prereleases=False) ==
577 ... Specifier("==1.2.3", prereleases=True))
578 True
579 >>> Specifier("==1.2.3") == "==1.2.3"
580 True
581 >>> Specifier("==1.2.3") == Specifier("==1.2.4")
582 False
583 >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
584 False
585 """
586 if isinstance(other, str):
587 try:
588 other = self.__class__(str(other))
589 except InvalidSpecifier:
590 return NotImplemented
591 elif not isinstance(other, self.__class__):
592 return NotImplemented
594 return self._canonical_spec == other._canonical_spec
596 def __contains__(self, item: str | Version) -> bool:
597 """Return whether or not the item is contained in this specifier.
599 :param item: The item to check for.
601 This is used for the ``in`` operator and behaves the same as
602 :meth:`contains` with no ``prereleases`` argument passed.
604 >>> "1.2.3" in Specifier(">=1.2.3")
605 True
606 >>> Version("1.2.3") in Specifier(">=1.2.3")
607 True
608 >>> "1.0.0" in Specifier(">=1.2.3")
609 False
610 >>> "1.3.0a1" in Specifier(">=1.2.3")
611 True
612 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
613 True
614 """
615 return self.contains(item)
617 def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
618 """Return whether or not the item is contained in this specifier.
620 :param item:
621 The item to check for, which can be a version string or a
622 :class:`~packaging.version.Version` instance.
623 :param prereleases:
624 Whether or not to match prereleases with this Specifier. If set to
625 ``None`` (the default), it will follow the recommendation from
626 :pep:`440` and match prereleases, as there are no other versions.
628 >>> Specifier(">=1.2.3").contains("1.2.3")
629 True
630 >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
631 True
632 >>> Specifier(">=1.2.3").contains("1.0.0")
633 False
634 >>> Specifier(">=1.2.3").contains("1.3.0a1")
635 True
636 >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1")
637 False
638 >>> Specifier(">=1.2.3").contains("1.3.0a1")
639 True
640 """
641 # ``===`` compares the raw string, so a Version parse here would
642 # be wasted.
643 if self._spec[0] == "===":
644 return bool(list(self.filter([item], prereleases=prereleases)))
646 parsed = coerce_version(item)
647 if parsed is None:
648 # Standard operators never match an unparsable input.
649 return False
651 if prereleases is None:
652 prereleases = resolve_prereleases(self._prereleases, self.prereleases)
654 if prereleases is False and parsed.is_prerelease:
655 return False
657 # ``_fast_match`` answers the simple operators without building a
658 # range; otherwise fall back to the engine's bounds membership.
659 match = _fast_match(self, parsed)
660 if match is not None:
661 return match
663 return matches_bounds_only(self._to_ranges(), parsed)
665 @typing.overload
666 def filter(
667 self,
668 iterable: Iterable[UnparsedVersionVar],
669 prereleases: bool | None = None,
670 key: None = ...,
671 ) -> Iterator[UnparsedVersionVar]: ...
673 @typing.overload
674 def filter(
675 self,
676 iterable: Iterable[T],
677 prereleases: bool | None = None,
678 key: Callable[[T], UnparsedVersion] = ...,
679 ) -> Iterator[T]: ...
681 def filter(
682 self,
683 iterable: Iterable[Any],
684 prereleases: bool | None = None,
685 key: Callable[[Any], UnparsedVersion] | None = None,
686 ) -> Iterator[Any]:
687 """Filter items in the given iterable, that match the specifier.
689 :param iterable:
690 An iterable that can contain version strings and
691 :class:`~packaging.version.Version` instances. The items in the
692 iterable will be filtered according to the specifier.
693 :param prereleases:
694 Whether or not to allow prereleases in the returned iterator. If set to
695 ``None`` (the default), it will follow the recommendation from :pep:`440`
696 and match prereleases if there are no other versions.
697 :param key:
698 A callable that takes a single argument (an item from the iterable) and
699 returns a version string or :class:`~packaging.version.Version`
700 instance to be used for filtering.
702 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
703 ['1.3']
704 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
705 ['1.2.3', '1.3', <Version('1.4')>]
706 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
707 ['1.5a1']
708 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
709 ['1.3', '1.5a1']
710 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
711 ['1.3', '1.5a1']
712 >>> list(Specifier(">=1.2.3").filter(
713 ... [{"ver": "1.2"}, {"ver": "1.3"}],
714 ... key=lambda x: x["ver"]))
715 [{'ver': '1.3'}]
717 .. versionchanged:: 26.1
719 Added the ``key`` parameter.
720 """
721 if prereleases is None:
722 prereleases = resolve_prereleases(self._prereleases, self.prereleases)
724 if self.operator == "===":
725 spec_lower = self.version.lower()
726 matches = (
727 item
728 for item in iterable
729 if str(item if key is None else key(item)).lower() == spec_lower
730 )
731 return _apply_prereleases_filter(matches, key, prereleases)
733 return filter_by_ranges(self._to_ranges(), iterable, key, prereleases)
736def _apply_prereleases_filter(
737 matches: Iterable[Any],
738 key: Callable[[Any], UnparsedVersion] | None,
739 prereleases: bool | None,
740) -> Iterator[Any]:
741 """Apply ``prereleases=`` handling to an already-matched iterable.
743 ``None`` means PEP 440 default (buffer pre-releases until a final
744 appears); ``True`` yields everything; ``False`` drops pre-releases.
745 """
746 if prereleases is None:
747 return _pep440_filter_prereleases(matches, key)
748 if prereleases:
749 return iter(matches)
750 return (
751 item
752 for item in matches
753 if (parsed := coerce_version(item if key is None else key(item))) is None
754 or not parsed.is_prerelease
755 )
758class SpecifierSet(BaseSpecifier):
759 """This class abstracts handling of a set of version specifiers.
761 It can be passed a single specifier (``>=3.0``), a comma-separated list of
762 specifiers (``>=3.0,!=3.1``), or no specifier at all.
764 Instances are safe to serialize with :mod:`pickle`. They use a stable
765 format so the same pickle can be loaded in future packaging
766 releases.
768 .. versionchanged:: 26.2
770 Added a stable pickle format. Pickles created with
771 packaging 26.2+ can be unpickled with future releases.
772 Backward compatibility with pickles from
773 packaging < 26.2 is supported but may be removed in a future
774 release.
775 """
777 __slots__ = (
778 "_canonicalized",
779 "_has_arbitrary",
780 "_is_unsatisfiable",
781 "_prereleases",
782 "_ranges",
783 "_specs",
784 )
786 def __init__(
787 self,
788 specifiers: str | Iterable[Specifier] = "",
789 prereleases: bool | None = None,
790 ) -> None:
791 """Initialize a SpecifierSet instance.
793 :param specifiers:
794 The string representation of a specifier or a comma-separated list of
795 specifiers which will be parsed and normalized before use.
796 May also be an iterable of ``Specifier`` instances, which will be used
797 as is.
798 :param prereleases:
799 This tells the SpecifierSet if it should accept prerelease versions if
800 applicable or not. The default of ``None`` will autodetect it from the
801 given specifiers.
803 :raises InvalidSpecifier:
804 If the given ``specifiers`` are not parseable than this exception will be
805 raised.
806 """
808 if isinstance(specifiers, str):
809 # Split on `,` to break each individual specifier into its own item, and
810 # strip each item to remove leading/trailing whitespace.
811 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
813 self._specs: tuple[Specifier, ...] = tuple(map(Specifier, split_specifiers))
814 # Fast substring check; avoids iterating parsed specs.
815 self._has_arbitrary = "===" in specifiers
816 else:
817 self._specs = tuple(specifiers)
818 # Substring check works for both Specifier objects and plain
819 # strings (setuptools passes lists of strings).
820 self._has_arbitrary = any("===" in str(s) for s in self._specs)
822 self._canonicalized = len(self._specs) <= 1
823 self._is_unsatisfiable: bool | None = None
824 self._ranges: Sequence[VersionRange] | None = None
826 # Store our prereleases value so we can use it later to determine if
827 # we accept prereleases or not.
828 self._prereleases = prereleases
830 def _canonical_specs(self) -> tuple[Specifier, ...]:
831 """Deduplicate, sort, and cache specs for order-sensitive operations."""
832 if not self._canonicalized:
833 self._specs = tuple(dict.fromkeys(sorted(self._specs, key=str)))
834 self._canonicalized = True
835 return self._specs
837 @property
838 def prereleases(self) -> bool | None:
839 # If we have been given an explicit prerelease modifier, then we'll
840 # pass that through here.
841 if self._prereleases is not None:
842 return self._prereleases
844 # If we don't have any specifiers, and we don't have a forced value,
845 # then we'll just return None since we don't know if this should have
846 # pre-releases or not.
847 if not self._specs:
848 return None
850 # Otherwise we'll see if any of the given specifiers accept
851 # prereleases, if any of them do we'll return True, otherwise False.
852 if any(s.prereleases for s in self._specs):
853 return True
855 return None
857 @prereleases.setter
858 def prereleases(self, value: bool | None) -> None:
859 self._prereleases = value
860 self._is_unsatisfiable = None
862 def __getstate__(self) -> tuple[tuple[Specifier, ...], bool | None]:
863 # Return state as a 2-item tuple for compactness:
864 # (specs, prereleases)
865 # Cache members are excluded and will be recomputed on demand.
866 return (self._specs, self._prereleases)
868 def __setstate__(self, state: object) -> None:
869 # Always discard cached values - they will be recomputed on demand.
870 self._ranges = None
871 self._is_unsatisfiable = None
873 if isinstance(state, tuple):
874 if len(state) == 2:
875 # New format (26.2+): (specs, prereleases)
876 specs, prereleases = state
877 if (
878 isinstance(specs, tuple)
879 and all(isinstance(s, Specifier) for s in specs)
880 and _validate_pre(prereleases)
881 ):
882 self._specs = specs
883 self._prereleases = prereleases
884 self._canonicalized = len(specs) <= 1
885 self._has_arbitrary = any("===" in str(s) for s in specs)
886 return
887 if len(state) == 2 and isinstance(state[1], dict):
888 # Format (packaging 26.0-26.1): (None, {slot: value}).
889 _, slot_dict = state
890 specs = slot_dict.get("_specs", ())
891 prereleases = slot_dict.get("_prereleases")
892 # Convert frozenset to tuple (26.0 stored as frozenset)
893 if isinstance(specs, frozenset):
894 specs = tuple(sorted(specs, key=str))
895 if (
896 isinstance(specs, tuple)
897 and all(isinstance(s, Specifier) for s in specs)
898 and _validate_pre(prereleases)
899 ):
900 self._specs = specs
901 self._prereleases = prereleases
902 self._canonicalized = len(self._specs) <= 1
903 self._has_arbitrary = any("===" in str(s) for s in self._specs)
904 return
905 if isinstance(state, dict):
906 # Old format (packaging <= 25.x, no __slots__): state is a plain dict.
907 specs = state.get("_specs", ())
908 prereleases = state.get("_prereleases")
909 # Convert frozenset to tuple (26.0 stored as frozenset)
910 if isinstance(specs, frozenset):
911 specs = tuple(sorted(specs, key=str))
912 if (
913 isinstance(specs, tuple)
914 and all(isinstance(s, Specifier) for s in specs)
915 and _validate_pre(prereleases)
916 ):
917 self._specs = specs
918 self._prereleases = prereleases
919 self._canonicalized = len(self._specs) <= 1
920 self._has_arbitrary = any("===" in str(s) for s in self._specs)
921 return
923 raise TypeError(f"Cannot restore SpecifierSet from {state!r}")
925 def __repr__(self) -> str:
926 """A representation of the specifier set that shows all internal state.
928 Note that the ordering of the individual specifiers within the set may not
929 match the input string.
931 >>> SpecifierSet('>=1.0.0,!=2.0.0')
932 <SpecifierSet('!=2.0.0,>=1.0.0')>
933 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
934 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
935 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
936 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
937 """
938 pre = (
939 f", prereleases={self.prereleases!r}"
940 if self._prereleases is not None
941 else ""
942 )
944 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
946 def __str__(self) -> str:
947 """A string representation of the specifier set that can be round-tripped.
949 Note that the ordering of the individual specifiers within the set may not
950 match the input string.
952 >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
953 '!=1.0.1,>=1.0.0'
954 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
955 '!=1.0.1,>=1.0.0'
956 """
957 return ",".join(str(s) for s in self._canonical_specs())
959 def __hash__(self) -> int:
960 return hash(self._canonical_specs())
962 def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
963 """Return a SpecifierSet which is a combination of the two sets.
965 :param other: The other object to combine with.
967 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
968 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
969 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
970 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
971 """
972 if isinstance(other, str):
973 other = SpecifierSet(other)
974 elif not isinstance(other, SpecifierSet):
975 return NotImplemented
977 specifier = SpecifierSet()
978 specifier._specs = self._specs + other._specs
979 specifier._canonicalized = len(specifier._specs) <= 1
980 specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary
982 # Combine prerelease settings: use common or non-None value
983 if self._prereleases is None or self._prereleases == other._prereleases:
984 specifier._prereleases = other._prereleases
985 elif other._prereleases is None:
986 specifier._prereleases = self._prereleases
987 else:
988 raise ValueError(
989 "Cannot combine SpecifierSets with True and False prerelease overrides."
990 )
992 return specifier
994 def __eq__(self, other: object) -> bool:
995 """Whether or not the two SpecifierSet-like objects are equal.
997 :param other: The other object to check against.
999 The value of :attr:`prereleases` is ignored.
1001 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
1002 True
1003 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
1004 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
1005 True
1006 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
1007 True
1008 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
1009 False
1010 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
1011 False
1012 """
1013 if isinstance(other, (str, Specifier)):
1014 other = SpecifierSet(str(other))
1015 elif not isinstance(other, SpecifierSet):
1016 return NotImplemented
1018 return self._canonical_specs() == other._canonical_specs()
1020 def __len__(self) -> int:
1021 """Returns the number of specifiers in this specifier set."""
1022 return len(self._specs)
1024 def __iter__(self) -> Iterator[Specifier]:
1025 """
1026 Returns an iterator over all the underlying :class:`Specifier` instances
1027 in this specifier set.
1029 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
1030 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
1031 """
1032 return iter(self._specs)
1034 def _get_ranges(self) -> Sequence[VersionRange]:
1035 """Intersect all specifiers into a single sequence of version ranges.
1037 Empty when unsatisfiable. Callers must ensure ``self._specs``
1038 is non-empty.
1039 """
1040 if self._ranges is not None:
1041 return self._ranges
1043 self._ranges = intersect_specifier_bounds(s._to_ranges() for s in self._specs)
1044 return self._ranges
1046 def is_unsatisfiable(self) -> bool:
1047 """Check whether this specifier set can never be satisfied.
1049 Returns True if no version can satisfy all specifiers simultaneously.
1051 >>> SpecifierSet(">=2.0,<1.0").is_unsatisfiable()
1052 True
1053 >>> SpecifierSet(">=1.0,<2.0").is_unsatisfiable()
1054 False
1055 >>> SpecifierSet("").is_unsatisfiable()
1056 False
1057 >>> SpecifierSet("==1.0,!=1.0").is_unsatisfiable()
1058 True
1060 .. versionadded:: 26.1
1061 """
1062 cached = self._is_unsatisfiable
1063 if cached is not None:
1064 return cached
1066 if not self._specs:
1067 self._is_unsatisfiable = False
1068 return False
1070 result = not self._get_ranges()
1072 if not result:
1073 result = self._check_arbitrary_unsatisfiable()
1075 if not result and self.prereleases is False:
1076 result = ranges_are_prerelease_only(self._get_ranges())
1078 self._is_unsatisfiable = result
1079 return result
1081 def _check_arbitrary_unsatisfiable(self) -> bool:
1082 """Check === (arbitrary equality) specs for unsatisfiability.
1084 === uses case-insensitive string comparison, so the only candidate
1085 that can match ``===V`` is the literal string V. This method
1086 checks whether that candidate is excluded by other specifiers.
1087 """
1088 arbitrary = [s for s in self._specs if s.operator == "==="]
1089 if not arbitrary:
1090 return False
1092 # Multiple === must agree on the same string (case-insensitive).
1093 first = arbitrary[0].version.lower()
1094 if any(s.version.lower() != first for s in arbitrary[1:]):
1095 return True
1097 # The sole candidate is the === version string. Check whether
1098 # it can satisfy every standard spec.
1099 candidate = coerce_version(arbitrary[0].version)
1101 # With prereleases=False, a prerelease candidate is excluded
1102 # by contains() before the === string check even runs.
1103 if (
1104 self.prereleases is False
1105 and candidate is not None
1106 and candidate.is_prerelease
1107 ):
1108 return True
1110 standard = [s for s in self._specs if s.operator != "==="]
1111 if not standard:
1112 return False
1114 if candidate is None:
1115 # Unparsable string cannot satisfy any standard spec.
1116 return True
1118 return not all(s.contains(candidate) for s in standard)
1120 def to_range(self) -> ranges.VersionRange:
1121 """Return the :class:`~packaging.ranges.VersionRange` this set accepts.
1123 An empty set yields the full range; an unsatisfiable set yields the
1124 empty range. ``===`` specifiers contribute literal-string admission.
1126 >>> SpecifierSet(">=1.0,<2.0").to_range()
1127 <VersionRange '[1.0, 2.0.dev0)'>
1129 .. versionadded:: 26.3
1130 """
1131 from .ranges import VersionRange # noqa: PLC0415
1133 return VersionRange._from_specifier_set(self)
1135 def __contains__(self, item: UnparsedVersion) -> bool:
1136 """Return whether or not the item is contained in this specifier.
1138 :param item: The item to check for.
1140 This is used for the ``in`` operator and behaves the same as
1141 :meth:`contains` with no ``prereleases`` argument passed.
1143 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
1144 True
1145 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
1146 True
1147 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
1148 False
1149 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
1150 True
1151 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
1152 True
1153 """
1154 return self.contains(item)
1156 def contains(
1157 self,
1158 item: UnparsedVersion,
1159 prereleases: bool | None = None,
1160 installed: bool | None = None,
1161 ) -> bool:
1162 """Return whether or not the item is contained in this SpecifierSet.
1164 :param item:
1165 The item to check for, which can be a version string or a
1166 :class:`~packaging.version.Version` instance.
1167 :param prereleases:
1168 Whether or not to match prereleases with this SpecifierSet. If set to
1169 ``None`` (the default), it will follow the recommendation from :pep:`440`
1170 and match prereleases, as there are no other versions.
1171 :param installed:
1172 Whether or not the item is installed. If set to ``True``, it will
1173 accept prerelease versions even if the specifier does not allow them.
1175 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
1176 True
1177 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
1178 True
1179 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
1180 False
1181 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
1182 True
1183 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")
1184 False
1185 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
1186 True
1187 """
1188 version = coerce_version(item)
1190 if version is not None and installed and version.is_prerelease:
1191 prereleases = True
1193 # When item is a string and === is involved, keep it as-is
1194 # so the comparison isn't done against the normalized form.
1195 if version is None or (self._has_arbitrary and not isinstance(item, Version)):
1196 check_item = item
1197 else:
1198 check_item = version
1200 # Fast path: a parseable, local-free version against a rangelike set.
1201 # A local on ``version`` needs PEP 440 stripping that the range path
1202 # applies.
1203 if (
1204 version is not None
1205 and not self._has_arbitrary
1206 and version.local is None
1207 and self._specs
1208 ):
1209 if version.is_prerelease and (
1210 prereleases is False
1211 or (prereleases is None and self._prereleases is False)
1212 ):
1213 return False
1215 bounds = self._ranges
1216 if bounds is None:
1217 # Per-spec ``_fast_match`` answers a set of simple specifiers
1218 # without folding anything. If a spec needs the range path,
1219 # fold the intersected bounds once and cache them so repeated
1220 # checks on the same set stay cheap.
1221 for spec in self._specs:
1222 match = _fast_match(spec, version)
1223 if match is None:
1224 break
1225 if not match:
1226 return False
1227 else:
1228 return True
1230 bounds = self._ranges = self._get_ranges()
1232 return matches_bounds_only(bounds, version)
1234 return bool(list(self.filter([check_item], prereleases=prereleases)))
1236 @typing.overload
1237 def filter(
1238 self,
1239 iterable: Iterable[UnparsedVersionVar],
1240 prereleases: bool | None = None,
1241 key: None = ...,
1242 ) -> Iterator[UnparsedVersionVar]: ...
1244 @typing.overload
1245 def filter(
1246 self,
1247 iterable: Iterable[T],
1248 prereleases: bool | None = None,
1249 key: Callable[[T], UnparsedVersion] = ...,
1250 ) -> Iterator[T]: ...
1252 def filter(
1253 self,
1254 iterable: Iterable[Any],
1255 prereleases: bool | None = None,
1256 key: Callable[[Any], UnparsedVersion] | None = None,
1257 ) -> Iterator[Any]:
1258 """Filter items in the given iterable, that match the specifiers in this set.
1260 :param iterable:
1261 An iterable that can contain version strings and
1262 :class:`~packaging.version.Version` instances. The items in the
1263 iterable will be filtered according to the specifier.
1264 :param prereleases:
1265 Whether or not to allow prereleases in the returned iterator. If set to
1266 ``None`` (the default), it will follow the recommendation from :pep:`440`
1267 and match prereleases if there are no other versions.
1268 :param key:
1269 A callable that takes a single argument (an item from the iterable) and
1270 returns a version string or :class:`~packaging.version.Version`
1271 instance to be used for filtering.
1273 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
1274 ['1.3']
1275 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
1276 ['1.3', <Version('1.4')>]
1277 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
1278 ['1.5a1']
1279 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
1280 ['1.3', '1.5a1']
1281 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
1282 ['1.3', '1.5a1']
1283 >>> list(SpecifierSet(">=1.2.3").filter(
1284 ... [{"ver": "1.2"}, {"ver": "1.3"}],
1285 ... key=lambda x: x["ver"]))
1286 [{'ver': '1.3'}]
1288 An "empty" SpecifierSet will filter items based on the presence of prerelease
1289 versions in the set.
1291 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
1292 ['1.3']
1293 >>> list(SpecifierSet("").filter(["1.5a1"]))
1294 ['1.5a1']
1295 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
1296 ['1.3', '1.5a1']
1297 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
1298 ['1.3', '1.5a1']
1300 .. versionchanged:: 26.1
1302 Added the ``key`` parameter.
1303 """
1304 # Determine if we're forcing a prerelease or not, if we're not forcing
1305 # one for this particular filter call, then we'll use whatever the
1306 # SpecifierSet thinks for whether or not we should support prereleases.
1307 if prereleases is None and self.prereleases is not None:
1308 prereleases = self.prereleases
1310 if self._specs:
1311 if self._has_arbitrary:
1312 # Slow path for ===
1313 specs = self._specs
1314 matches = (
1315 item
1316 for item in iterable
1317 if all(
1318 s.contains(item if key is None else key(item), prereleases=True)
1319 for s in specs
1320 )
1321 )
1322 return _apply_prereleases_filter(matches, key, prereleases)
1324 ranges = self._ranges
1325 if ranges is None:
1326 ranges = self._get_ranges()
1327 return filter_by_ranges(ranges, iterable, key, prereleases)
1329 # Empty SpecifierSet.
1330 return _apply_prereleases_filter(iterable, key, prereleases)
1333def _pep440_filter_prereleases(
1334 iterable: Iterable[Any], key: Callable[[Any], UnparsedVersion] | None
1335) -> Iterator[Any]:
1336 """Filter per PEP 440: exclude prereleases unless no finals exist."""
1337 # Two lists used:
1338 # * all_nonfinal to preserve order if no finals exist
1339 # * arbitrary_strings for streaming when first final found
1340 all_nonfinal: list[Any] = []
1341 arbitrary_strings: list[Any] = []
1343 found_final = False
1344 for item in iterable:
1345 parsed = coerce_version(item if key is None else key(item))
1347 if parsed is None:
1348 # Arbitrary strings are always included as it is not
1349 # possible to determine if they are prereleases,
1350 # and they have already passed all specifiers.
1351 if found_final:
1352 yield item
1353 else:
1354 arbitrary_strings.append(item)
1355 all_nonfinal.append(item)
1356 continue
1358 if not parsed.is_prerelease:
1359 # Final release found - flush arbitrary strings, then yield
1360 if not found_final:
1361 yield from arbitrary_strings
1362 found_final = True
1363 yield item
1364 continue
1366 # Prerelease - buffer if no finals yet, otherwise skip
1367 if not found_final:
1368 all_nonfinal.append(item)
1370 # No finals found - yield all buffered items
1371 if not found_final:
1372 yield from all_nonfinal