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::
6
7 from pip._vendor.packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier
8 from pip._vendor.packaging.version import Version
9"""
10
11from __future__ import annotations
12
13import abc
14import re
15import typing
16from typing import (
17 TYPE_CHECKING,
18 Any,
19 Callable,
20 Final,
21 TypeVar,
22 Union,
23)
24
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
38
39if TYPE_CHECKING:
40 import sys
41 from collections.abc import Iterable, Iterator, Sequence
42
43 if sys.version_info >= (3, 10):
44 from typing import TypeGuard
45 else:
46 from typing_extensions import TypeGuard
47
48 from . import ranges
49 from ._ranges import Interval
50
51
52__all__ = [
53 "BaseSpecifier",
54 "InvalidSpecifier",
55 "Specifier",
56 "SpecifierSet",
57]
58
59
60def __dir__() -> list[str]:
61 return __all__
62
63
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 )
71
72
73def _validate_pre(pre: object, /) -> TypeGuard[bool | None]:
74 return pre is None or isinstance(pre, bool)
75
76
77T = TypeVar("T")
78UnparsedVersion = Union[Version, str]
79UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
80
81
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}
91
92
93def _fast_match(specifier: Specifier, parsed: Version) -> bool | None:
94 """Match ``parsed`` against ``specifier`` without building a range.
95
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
103
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))
107
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
118
119 return None
120
121
122class InvalidSpecifier(ValueError):
123 """
124 Raised when attempting to create a :class:`Specifier` with a specifier
125 string that is invalid.
126
127 >>> Specifier("lolwat")
128 Traceback (most recent call last):
129 ...
130 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
131 """
132
133
134class BaseSpecifier(metaclass=abc.ABCMeta):
135 """
136 Abstract base class for :class:`Specifier` and :class:`SpecifierSet`.
137 """
138
139 __slots__ = ()
140 __match_args__ = ("_str",)
141
142 @property
143 def _str(self) -> str:
144 """Internal property for match_args"""
145 return str(self)
146
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 """
153
154 @abc.abstractmethod
155 def __hash__(self) -> int:
156 """
157 Returns a hash value for this Specifier-like object.
158 """
159
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.
165
166 :param other: The other object to check against.
167 """
168
169 @property
170 @abc.abstractmethod
171 def prereleases(self) -> bool | None:
172 """Whether or not pre-releases as a whole are allowed.
173
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 """
177
178 @prereleases.setter # noqa: B027
179 def prereleases(self, value: bool) -> None:
180 """Setter for :attr:`prereleases`.
181
182 :param value: The value to set.
183 """
184
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 """
190
191 @typing.overload
192 def filter(
193 self,
194 iterable: Iterable[UnparsedVersionVar],
195 prereleases: bool | None = None,
196 key: None = ...,
197 ) -> Iterator[UnparsedVersionVar]: ...
198
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]: ...
206
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 """
218
219
220class Specifier(BaseSpecifier):
221 """This class abstracts handling of version specifiers.
222
223 .. tip::
224
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).
228
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.
231
232 .. versionchanged:: 26.2
233
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 pip._vendor.packaging < 26.2 is supported but may be removed in a future
237 release.
238 """
239
240 __slots__ = (
241 "_prereleases",
242 "_ranges",
243 "_spec",
244 "_spec_version",
245 )
246
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
268
269 \s*
270 v?
271 (?:[0-9]+!)? # epoch
272 [0-9]+(?:\.[0-9]+)* # release
273
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
297
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 (?:<=|>=|<|>)
320
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 """
338
339 _regex = re.compile(
340 r"\s*" + _specifier_regex_str + r"\s*", re.VERBOSE | re.IGNORECASE
341 )
342
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 }
354
355 def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
356 """Initialize a Specifier instance.
357
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}")
370
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()
378
379 self._spec: tuple[str, str] = (operator, version)
380
381 # Store whether or not this Specifier should accept prereleases
382 self._prereleases = prereleases
383
384 # Specifier version cache
385 self._spec_version: tuple[str, Version] | None = None
386
387 # Version range cache (populated by _to_ranges)
388 self._ranges: Sequence[Interval] | None = None
389
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]
394
395 version_specifier = coerce_version(version)
396 if version_specifier is None:
397 return None
398
399 self._spec_version = (version, version_specifier)
400 return version_specifier
401
402 def _require_spec_version(self, version: str) -> Version:
403 """Get spec version, asserting it's valid (not for === operator).
404
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
411
412 def _to_ranges(self) -> Sequence[Interval]:
413 """Convert this specifier to sorted, non-overlapping version ranges.
414
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
420
421 op = self.operator
422 ver_str = self.version
423
424 if op == "===":
425 result: Sequence[Interval] = FULL_RANGE
426 else:
427 version = self._require_spec_version(ver_str.removesuffix(".*"))
428 result = bounds_for_spec(op, ver_str, version)
429
430 self._ranges = result
431 return result
432
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
439
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
445
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
450
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
456
457 # For all other operators, use the check if spec Version
458 # object implies pre-releases.
459 return version.is_prerelease
460
461 @prereleases.setter
462 def prereleases(self, value: bool | None) -> None:
463 self._prereleases = value
464
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)
470
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
475
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
501
502 raise TypeError(f"Cannot restore Specifier from {state!r}")
503
504 @property
505 def operator(self) -> str:
506 """The operator of this specifier.
507
508 >>> Specifier("==1.2.3").operator
509 '=='
510 """
511 return self._spec[0]
512
513 @property
514 def version(self) -> str:
515 """The version of this specifier.
516
517 >>> Specifier("==1.2.3").version
518 '1.2.3'
519 """
520 return self._spec[1]
521
522 def __repr__(self) -> str:
523 """A representation of the Specifier that shows all internal state.
524
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 )
537
538 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
539
540 def __str__(self) -> str:
541 """A string representation of the Specifier that can be round-tripped.
542
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)
549
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
555
556 spec_version = self._require_spec_version(version)
557
558 canonical_version = canonicalize_version(
559 spec_version, strip_trailing_zero=(operator != "~=")
560 )
561
562 return operator, canonical_version
563
564 def __hash__(self) -> int:
565 return hash(self._canonical_spec)
566
567 def __eq__(self, other: object) -> bool:
568 """Whether or not the two Specifier-like objects are equal.
569
570 :param other: The other object to check against.
571
572 The value of :attr:`prereleases` is ignored.
573
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
593
594 return self._canonical_spec == other._canonical_spec
595
596 def __contains__(self, item: str | Version) -> bool:
597 """Return whether or not the item is contained in this specifier.
598
599 :param item: The item to check for.
600
601 This is used for the ``in`` operator and behaves the same as
602 :meth:`contains` with no ``prereleases`` argument passed.
603
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)
616
617 def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
618 """Return whether or not the item is contained in this specifier.
619
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.
627
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 .. versionchanged:: 26.0
642
643 With ``prereleases=None``, a prerelease now matches. A single
644 version has no alternatives, so the :pep:`440` rule to accept
645 prereleases when nothing else satisfies the specifier applies.
646 Earlier versions rejected it. An unparsable version now returns
647 ``False`` instead of raising :exc:`~packaging.version.InvalidVersion`.
648 """
649 # ``===`` compares the raw string, so a Version parse here would
650 # be wasted.
651 if self._spec[0] == "===":
652 return bool(list(self.filter([item], prereleases=prereleases)))
653
654 parsed = coerce_version(item)
655 if parsed is None:
656 # Standard operators never match an unparsable input.
657 return False
658
659 if prereleases is None:
660 prereleases = resolve_prereleases(self._prereleases, self.prereleases)
661
662 if prereleases is False and parsed.is_prerelease:
663 return False
664
665 # ``_fast_match`` answers the simple operators without building a
666 # range; otherwise fall back to the engine's bounds membership.
667 match = _fast_match(self, parsed)
668 if match is not None:
669 return match
670
671 return matches_bounds_only(self._to_ranges(), parsed)
672
673 @typing.overload
674 def filter(
675 self,
676 iterable: Iterable[UnparsedVersionVar],
677 prereleases: bool | None = None,
678 key: None = ...,
679 ) -> Iterator[UnparsedVersionVar]: ...
680
681 @typing.overload
682 def filter(
683 self,
684 iterable: Iterable[T],
685 prereleases: bool | None = None,
686 key: Callable[[T], UnparsedVersion] = ...,
687 ) -> Iterator[T]: ...
688
689 def filter(
690 self,
691 iterable: Iterable[Any],
692 prereleases: bool | None = None,
693 key: Callable[[Any], UnparsedVersion] | None = None,
694 ) -> Iterator[Any]:
695 """Filter items in the given iterable, that match the specifier.
696
697 :param iterable:
698 An iterable that can contain version strings and
699 :class:`~packaging.version.Version` instances. The items in the
700 iterable will be filtered according to the specifier.
701 :param prereleases:
702 Whether or not to allow prereleases in the returned iterator. If set to
703 ``None`` (the default), it will follow the recommendation from :pep:`440`
704 and match prereleases if there are no other versions.
705 :param key:
706 A callable that takes a single argument (an item from the iterable) and
707 returns a version string or :class:`~packaging.version.Version`
708 instance to be used for filtering.
709
710 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
711 ['1.3']
712 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
713 ['1.2.3', '1.3', <Version('1.4')>]
714 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
715 ['1.5a1']
716 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
717 ['1.3', '1.5a1']
718 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
719 ['1.3', '1.5a1']
720 >>> list(Specifier(">=1.2.3").filter(
721 ... [{"ver": "1.2"}, {"ver": "1.3"}],
722 ... key=lambda x: x["ver"]))
723 [{'ver': '1.3'}]
724
725 .. versionchanged:: 26.1
726
727 Added the ``key`` parameter.
728 """
729 if prereleases is None:
730 prereleases = resolve_prereleases(self._prereleases, self.prereleases)
731
732 if self.operator == "===":
733 spec_lower = self.version.lower()
734 matches = (
735 item
736 for item in iterable
737 if str(item if key is None else key(item)).lower() == spec_lower
738 )
739 return _apply_prereleases_filter(matches, key, prereleases)
740
741 return filter_by_ranges(self._to_ranges(), iterable, key, prereleases)
742
743
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.
750
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 )
764
765
766class SpecifierSet(BaseSpecifier):
767 """This class abstracts handling of a set of version specifiers.
768
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.
771
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.
775
776 .. versionchanged:: 26.2
777
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 """
784
785 __slots__ = (
786 "_canonicalized",
787 "_has_arbitrary",
788 "_is_unsatisfiable",
789 "_prereleases",
790 "_ranges",
791 "_specs",
792 )
793
794 def __init__(
795 self,
796 specifiers: str | Iterable[Specifier] = "",
797 prereleases: bool | None = None,
798 ) -> None:
799 """Initialize a SpecifierSet instance.
800
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.
810
811 :raises InvalidSpecifier:
812 If the given ``specifiers`` are not parseable than this exception will be
813 raised.
814 """
815
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()]
820
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)
829
830 self._canonicalized = len(self._specs) <= 1
831 self._is_unsatisfiable: bool | None = None
832 self._ranges: Sequence[Interval] | None = None
833
834 # Store our prereleases value so we can use it later to determine if
835 # we accept prereleases or not.
836 self._prereleases = prereleases
837
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 return self._specs
844
845 @property
846 def prereleases(self) -> bool | None:
847 # If we have been given an explicit prerelease modifier, then we'll
848 # pass that through here.
849 if self._prereleases is not None:
850 return self._prereleases
851
852 # If we don't have any specifiers, and we don't have a forced value,
853 # then we'll just return None since we don't know if this should have
854 # pre-releases or not.
855 if not self._specs:
856 return None
857
858 # Otherwise we'll see if any of the given specifiers accept
859 # prereleases, if any of them do we'll return True, otherwise False.
860 if any(s.prereleases for s in self._specs):
861 return True
862
863 return None
864
865 @prereleases.setter
866 def prereleases(self, value: bool | None) -> None:
867 self._prereleases = value
868 self._is_unsatisfiable = None
869
870 def __getstate__(self) -> tuple[tuple[Specifier, ...], bool | None]:
871 # Return state as a 2-item tuple for compactness:
872 # (specs, prereleases)
873 # Cache members are excluded and will be recomputed on demand.
874 return (self._specs, self._prereleases)
875
876 def __setstate__(self, state: object) -> None:
877 # Always discard cached values - they will be recomputed on demand.
878 self._ranges = None
879 self._is_unsatisfiable = None
880
881 if isinstance(state, tuple):
882 if len(state) == 2:
883 # New format (26.2+): (specs, prereleases)
884 specs, prereleases = state
885 if (
886 isinstance(specs, tuple)
887 and all(isinstance(s, Specifier) for s in specs)
888 and _validate_pre(prereleases)
889 ):
890 self._specs = specs
891 self._prereleases = prereleases
892 self._canonicalized = len(specs) <= 1
893 self._has_arbitrary = any("===" in str(s) for s in specs)
894 return
895 if len(state) == 2 and isinstance(state[1], dict):
896 # Format (packaging 26.0-26.1): (None, {slot: value}).
897 _, slot_dict = state
898 specs = slot_dict.get("_specs", ())
899 prereleases = slot_dict.get("_prereleases")
900 # Convert frozenset to tuple (26.0 stored as frozenset)
901 if isinstance(specs, frozenset):
902 specs = tuple(sorted(specs, key=str))
903 if (
904 isinstance(specs, tuple)
905 and all(isinstance(s, Specifier) for s in specs)
906 and _validate_pre(prereleases)
907 ):
908 self._specs = specs
909 self._prereleases = prereleases
910 self._canonicalized = len(self._specs) <= 1
911 self._has_arbitrary = any("===" in str(s) for s in self._specs)
912 return
913 if isinstance(state, dict):
914 # Old format (packaging <= 25.x, no __slots__): state is a plain dict.
915 specs = state.get("_specs", ())
916 prereleases = state.get("_prereleases")
917 # Convert frozenset to tuple (26.0 stored as frozenset)
918 if isinstance(specs, frozenset):
919 specs = tuple(sorted(specs, key=str))
920 if (
921 isinstance(specs, tuple)
922 and all(isinstance(s, Specifier) for s in specs)
923 and _validate_pre(prereleases)
924 ):
925 self._specs = specs
926 self._prereleases = prereleases
927 self._canonicalized = len(self._specs) <= 1
928 self._has_arbitrary = any("===" in str(s) for s in self._specs)
929 return
930
931 raise TypeError(f"Cannot restore SpecifierSet from {state!r}")
932
933 def __repr__(self) -> str:
934 """A representation of the specifier set that shows all internal state.
935
936 Note that the ordering of the individual specifiers within the set may not
937 match the input string.
938
939 >>> SpecifierSet('>=1.0.0,!=2.0.0')
940 <SpecifierSet('!=2.0.0,>=1.0.0')>
941 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
942 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
943 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
944 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
945 """
946 pre = (
947 f", prereleases={self.prereleases!r}"
948 if self._prereleases is not None
949 else ""
950 )
951
952 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
953
954 def __str__(self) -> str:
955 """A string representation of the specifier set that can be round-tripped.
956
957 Note that the ordering of the individual specifiers within the set may not
958 match the input string.
959
960 >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
961 '!=1.0.1,>=1.0.0'
962 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
963 '!=1.0.1,>=1.0.0'
964 """
965 return ",".join(str(s) for s in self._canonical_specs())
966
967 def __hash__(self) -> int:
968 return hash(self._canonical_specs())
969
970 def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
971 """Return a SpecifierSet which is a combination of the two sets.
972
973 :param other: The other object to combine with.
974
975 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
976 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
977 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
978 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
979 """
980 if isinstance(other, str):
981 other = SpecifierSet(other)
982 elif not isinstance(other, SpecifierSet):
983 return NotImplemented
984
985 specifier = SpecifierSet()
986 specifier._specs = self._specs + other._specs
987 specifier._canonicalized = len(specifier._specs) <= 1
988 specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary
989
990 # Combine prerelease settings: use common or non-None value
991 if self._prereleases is None or self._prereleases == other._prereleases:
992 specifier._prereleases = other._prereleases
993 elif other._prereleases is None:
994 specifier._prereleases = self._prereleases
995 else:
996 raise ValueError(
997 "Cannot combine SpecifierSets with True and False prerelease overrides."
998 )
999
1000 return specifier
1001
1002 def __eq__(self, other: object) -> bool:
1003 """Whether or not the two SpecifierSet-like objects are equal.
1004
1005 :param other: The other object to check against.
1006
1007 The value of :attr:`prereleases` is ignored.
1008
1009 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
1010 True
1011 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
1012 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
1013 True
1014 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
1015 True
1016 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
1017 False
1018 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
1019 False
1020 """
1021 if isinstance(other, (str, Specifier)):
1022 other = SpecifierSet(str(other))
1023 elif not isinstance(other, SpecifierSet):
1024 return NotImplemented
1025
1026 return self._canonical_specs() == other._canonical_specs()
1027
1028 def __len__(self) -> int:
1029 """Returns the number of specifiers in this specifier set."""
1030 return len(self._specs)
1031
1032 def __iter__(self) -> Iterator[Specifier]:
1033 """
1034 Returns an iterator over all the underlying :class:`Specifier` instances
1035 in this specifier set.
1036
1037 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
1038 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
1039 """
1040 return iter(self._specs)
1041
1042 def _get_ranges(self) -> Sequence[Interval]:
1043 """Intersect all specifiers into a single sequence of version ranges.
1044
1045 Empty when unsatisfiable. Callers must ensure ``self._specs``
1046 is non-empty.
1047 """
1048 if self._ranges is not None:
1049 return self._ranges
1050
1051 self._ranges = intersect_specifier_bounds(s._to_ranges() for s in self._specs)
1052 return self._ranges
1053
1054 def is_unsatisfiable(self) -> bool:
1055 """Check whether this specifier set can never be satisfied.
1056
1057 Returns True if no version can satisfy all specifiers simultaneously.
1058
1059 >>> SpecifierSet(">=2.0,<1.0").is_unsatisfiable()
1060 True
1061 >>> SpecifierSet(">=1.0,<2.0").is_unsatisfiable()
1062 False
1063 >>> SpecifierSet("").is_unsatisfiable()
1064 False
1065 >>> SpecifierSet("==1.0,!=1.0").is_unsatisfiable()
1066 True
1067
1068 .. versionadded:: 26.1
1069 """
1070 cached = self._is_unsatisfiable
1071 if cached is not None:
1072 return cached
1073
1074 if not self._specs:
1075 self._is_unsatisfiable = False
1076 return False
1077
1078 result = not self._get_ranges()
1079
1080 if not result:
1081 result = self._check_arbitrary_unsatisfiable()
1082
1083 if not result and self.prereleases is False:
1084 result = ranges_are_prerelease_only(self._get_ranges())
1085
1086 self._is_unsatisfiable = result
1087 return result
1088
1089 def _check_arbitrary_unsatisfiable(self) -> bool:
1090 """Check === (arbitrary equality) specs for unsatisfiability.
1091
1092 === uses case-insensitive string comparison, so the only candidate
1093 that can match ``===V`` is the literal string V. This method
1094 checks whether that candidate is excluded by other specifiers.
1095 """
1096 arbitrary = [s for s in self._specs if s.operator == "==="]
1097 if not arbitrary:
1098 return False
1099
1100 # Multiple === must agree on the same string (case-insensitive).
1101 first = arbitrary[0].version.lower()
1102 if any(s.version.lower() != first for s in arbitrary[1:]):
1103 return True
1104
1105 # The sole candidate is the === version string. Check whether
1106 # it can satisfy every standard spec.
1107 candidate = coerce_version(arbitrary[0].version)
1108
1109 # With prereleases=False, a prerelease candidate is excluded
1110 # by contains() before the === string check even runs.
1111 if (
1112 self.prereleases is False
1113 and candidate is not None
1114 and candidate.is_prerelease
1115 ):
1116 return True
1117
1118 standard = [s for s in self._specs if s.operator != "==="]
1119 if not standard:
1120 return False
1121
1122 if candidate is None:
1123 # Unparsable string cannot satisfy any standard spec.
1124 return True
1125
1126 return not all(s.contains(candidate) for s in standard)
1127
1128 def to_range(self) -> ranges.VersionRange:
1129 """Return the :class:`~packaging.ranges.VersionRange` this set accepts.
1130
1131 An empty set yields the full range; an unsatisfiable set yields the
1132 empty range. ``===`` specifiers contribute literal-string admission.
1133
1134 >>> SpecifierSet(">=1.0,<2.0").to_range()
1135 <VersionRange '[1.0, 2.0.dev0)'>
1136
1137 .. versionadded:: 26.3
1138 """
1139 from .ranges import VersionRange # noqa: PLC0415
1140
1141 return VersionRange._from_specifier_set(self)
1142
1143 def _check_relation_operand(self, other: object) -> None:
1144 if not isinstance(other, SpecifierSet):
1145 raise TypeError("expected a SpecifierSet")
1146 if self._has_arbitrary or other._has_arbitrary:
1147 raise ValueError("set relations do not support === specifiers")
1148
1149 def is_subset(self, other: SpecifierSet) -> bool:
1150 """Return whether every version matching this set also matches other.
1151
1152 :raises ValueError:
1153 If either set uses ``===`` specifiers, or the two sets were
1154 given different ``prereleases`` arguments (unset on one side
1155 counts as different).
1156 :raises TypeError:
1157 If other is not a :class:`SpecifierSet`.
1158
1159 >>> SpecifierSet(">=3.12,<3.13").is_subset(SpecifierSet(">=3.12"))
1160 True
1161 >>> SpecifierSet(">=3.12").is_subset(SpecifierSet(">=3.12,<3.13"))
1162 False
1163
1164 .. versionadded:: 26.3
1165 """
1166 self._check_relation_operand(other)
1167 return self.to_range().is_subset(other.to_range())
1168
1169 def is_superset(self, other: SpecifierSet) -> bool:
1170 """Return whether every version matching other also matches this set.
1171
1172 :raises ValueError:
1173 If either set uses ``===`` specifiers, or the two sets were
1174 given different ``prereleases`` arguments (unset on one side
1175 counts as different).
1176 :raises TypeError:
1177 If other is not a :class:`SpecifierSet`.
1178
1179 >>> SpecifierSet(">=3.12").is_superset(SpecifierSet(">=3.12,<3.13"))
1180 True
1181
1182 .. versionadded:: 26.3
1183 """
1184 self._check_relation_operand(other)
1185 return self.to_range().is_superset(other.to_range())
1186
1187 def is_disjoint(self, other: SpecifierSet) -> bool:
1188 """Return whether this set and other share no matching versions.
1189
1190 :raises ValueError:
1191 If either set uses ``===`` specifiers, or the two sets were
1192 given different ``prereleases`` arguments (unset on one side
1193 counts as different).
1194 :raises TypeError:
1195 If other is not a :class:`SpecifierSet`.
1196
1197 >>> SpecifierSet("<3.12").is_disjoint(SpecifierSet(">=3.12"))
1198 True
1199 >>> SpecifierSet("<3.12").is_disjoint(SpecifierSet(">=3.11"))
1200 False
1201
1202 .. versionadded:: 26.3
1203 """
1204 self._check_relation_operand(other)
1205 return self.to_range().is_disjoint(other.to_range())
1206
1207 def __contains__(self, item: UnparsedVersion) -> bool:
1208 """Return whether or not the item is contained in this specifier.
1209
1210 :param item: The item to check for.
1211
1212 This is used for the ``in`` operator and behaves the same as
1213 :meth:`contains` with no ``prereleases`` argument passed.
1214
1215 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
1216 True
1217 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
1218 True
1219 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
1220 False
1221 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
1222 True
1223 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
1224 True
1225 """
1226 return self.contains(item)
1227
1228 def contains(
1229 self,
1230 item: UnparsedVersion,
1231 prereleases: bool | None = None,
1232 installed: bool | None = None,
1233 ) -> bool:
1234 """Return whether or not the item is contained in this SpecifierSet.
1235
1236 :param item:
1237 The item to check for, which can be a version string or a
1238 :class:`~packaging.version.Version` instance.
1239 :param prereleases:
1240 Whether or not to match prereleases with this SpecifierSet. If set to
1241 ``None`` (the default), it will follow the recommendation from :pep:`440`
1242 and match prereleases, as there are no other versions.
1243 :param installed:
1244 Whether or not the item is installed. If set to ``True``, it will
1245 accept prerelease versions even if the specifier does not allow them.
1246
1247 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
1248 True
1249 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
1250 True
1251 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
1252 False
1253 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
1254 True
1255 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")
1256 False
1257 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
1258 True
1259
1260 .. versionchanged:: 26.0
1261
1262 With ``prereleases=None``, a prerelease now matches. A single
1263 version has no alternatives, so the :pep:`440` rule to accept
1264 prereleases when nothing else satisfies the specifiers applies.
1265 Earlier versions rejected it. An unparsable version now returns
1266 ``False`` instead of raising :exc:`~packaging.version.InvalidVersion`.
1267 """
1268 version = coerce_version(item)
1269
1270 if version is not None and installed and version.is_prerelease:
1271 prereleases = True
1272
1273 # When item is a string and === is involved, keep it as-is
1274 # so the comparison isn't done against the normalized form.
1275 if version is None or (self._has_arbitrary and not isinstance(item, Version)):
1276 check_item = item
1277 else:
1278 check_item = version
1279
1280 # Fast path: a parseable, local-free version against a rangelike set.
1281 # A local on ``version`` needs PEP 440 stripping that the range path
1282 # applies.
1283 if (
1284 version is not None
1285 and not self._has_arbitrary
1286 and version.local is None
1287 and self._specs
1288 ):
1289 if version.is_prerelease and (
1290 prereleases is False
1291 or (prereleases is None and self._prereleases is False)
1292 ):
1293 return False
1294
1295 bounds = self._ranges
1296 if bounds is None:
1297 # Per-spec ``_fast_match`` answers a set of simple specifiers
1298 # without folding anything. If a spec needs the range path,
1299 # fold the intersected bounds once and cache them so repeated
1300 # checks on the same set stay cheap.
1301 for spec in self._specs:
1302 match = _fast_match(spec, version)
1303 if match is None:
1304 break
1305 if not match:
1306 return False
1307 else:
1308 return True
1309
1310 bounds = self._ranges = self._get_ranges()
1311
1312 return matches_bounds_only(bounds, version)
1313
1314 return bool(list(self.filter([check_item], prereleases=prereleases)))
1315
1316 @typing.overload
1317 def filter(
1318 self,
1319 iterable: Iterable[UnparsedVersionVar],
1320 prereleases: bool | None = None,
1321 key: None = ...,
1322 ) -> Iterator[UnparsedVersionVar]: ...
1323
1324 @typing.overload
1325 def filter(
1326 self,
1327 iterable: Iterable[T],
1328 prereleases: bool | None = None,
1329 key: Callable[[T], UnparsedVersion] = ...,
1330 ) -> Iterator[T]: ...
1331
1332 def filter(
1333 self,
1334 iterable: Iterable[Any],
1335 prereleases: bool | None = None,
1336 key: Callable[[Any], UnparsedVersion] | None = None,
1337 ) -> Iterator[Any]:
1338 """Filter items in the given iterable, that match the specifiers in this set.
1339
1340 :param iterable:
1341 An iterable that can contain version strings and
1342 :class:`~packaging.version.Version` instances. The items in the
1343 iterable will be filtered according to the specifier.
1344 :param prereleases:
1345 Whether or not to allow prereleases in the returned iterator. If set to
1346 ``None`` (the default), it will follow the recommendation from :pep:`440`
1347 and match prereleases if there are no other versions.
1348 :param key:
1349 A callable that takes a single argument (an item from the iterable) and
1350 returns a version string or :class:`~packaging.version.Version`
1351 instance to be used for filtering.
1352
1353 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
1354 ['1.3']
1355 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
1356 ['1.3', <Version('1.4')>]
1357 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
1358 ['1.5a1']
1359 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
1360 ['1.3', '1.5a1']
1361 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
1362 ['1.3', '1.5a1']
1363 >>> list(SpecifierSet(">=1.2.3").filter(
1364 ... [{"ver": "1.2"}, {"ver": "1.3"}],
1365 ... key=lambda x: x["ver"]))
1366 [{'ver': '1.3'}]
1367
1368 An "empty" SpecifierSet will filter items based on the presence of prerelease
1369 versions in the set.
1370
1371 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
1372 ['1.3']
1373 >>> list(SpecifierSet("").filter(["1.5a1"]))
1374 ['1.5a1']
1375 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
1376 ['1.3', '1.5a1']
1377 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
1378 ['1.3', '1.5a1']
1379
1380 .. versionchanged:: 26.0
1381
1382 Prerelease filtering now follows the PEP 440 recommendation of
1383 yielding prereleases only when no final release is present.
1384
1385 .. versionchanged:: 26.1
1386
1387 Added the ``key`` parameter.
1388 """
1389 # Determine if we're forcing a prerelease or not, if we're not forcing
1390 # one for this particular filter call, then we'll use whatever the
1391 # SpecifierSet thinks for whether or not we should support prereleases.
1392 if prereleases is None and self.prereleases is not None:
1393 prereleases = self.prereleases
1394
1395 if self._specs:
1396 if self._has_arbitrary:
1397 # Slow path for ===
1398 specs = self._specs
1399 matches = (
1400 item
1401 for item in iterable
1402 if all(
1403 s.contains(item if key is None else key(item), prereleases=True)
1404 for s in specs
1405 )
1406 )
1407 return _apply_prereleases_filter(matches, key, prereleases)
1408
1409 ranges = self._ranges
1410 if ranges is None:
1411 ranges = self._get_ranges()
1412 return filter_by_ranges(ranges, iterable, key, prereleases)
1413
1414 # Empty SpecifierSet.
1415 return _apply_prereleases_filter(iterable, key, prereleases)
1416
1417
1418def _pep440_filter_prereleases(
1419 iterable: Iterable[Any], key: Callable[[Any], UnparsedVersion] | None
1420) -> Iterator[Any]:
1421 """Filter per PEP 440: exclude prereleases unless no finals exist."""
1422 # Two lists used:
1423 # * all_nonfinal to preserve order if no finals exist
1424 # * arbitrary_strings for streaming when first final found
1425 all_nonfinal: list[Any] = []
1426 arbitrary_strings: list[Any] = []
1427
1428 found_final = False
1429 for item in iterable:
1430 parsed = coerce_version(item if key is None else key(item))
1431
1432 if parsed is None:
1433 # Arbitrary strings are always included as it is not
1434 # possible to determine if they are prereleases,
1435 # and they have already passed all specifiers.
1436 if found_final:
1437 yield item
1438 else:
1439 arbitrary_strings.append(item)
1440 all_nonfinal.append(item)
1441 continue
1442
1443 if not parsed.is_prerelease:
1444 # Final release found - flush arbitrary strings, then yield
1445 if not found_final:
1446 yield from arbitrary_strings
1447 found_final = True
1448 yield item
1449 continue
1450
1451 # Prerelease - buffer if no finals yet, otherwise skip
1452 if not found_final:
1453 all_nonfinal.append(item)
1454
1455 # No finals found - yield all buffered items
1456 if not found_final:
1457 yield from all_nonfinal