Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/packaging/specifiers.py: 21%
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 itertools
15import re
16import typing
17from typing import Any, Callable, Final, Iterable, Iterator, TypeVar, Union
19from .utils import canonicalize_version
20from .version import InvalidVersion, Version
22__all__ = [
23 "BaseSpecifier",
24 "InvalidSpecifier",
25 "Specifier",
26 "SpecifierSet",
27]
30def __dir__() -> list[str]:
31 return __all__
34T = TypeVar("T")
35UnparsedVersion = Union[Version, str]
36UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
37CallableOperator = Callable[[Version, str], bool]
40def _coerce_version(version: UnparsedVersion) -> Version | None:
41 if not isinstance(version, Version):
42 try:
43 version = Version(version)
44 except InvalidVersion:
45 return None
46 return version
49def _public_version(version: Version) -> Version:
50 if version.local is None:
51 return version
52 return version.__replace__(local=None)
55def _post_base(version: Version) -> Version:
56 """The version that *version* is a post-release of.
58 1.0.post1 -> 1.0, 1.0a1.post0 -> 1.0a1, 1.0.post0.dev1 -> 1.0.
59 """
60 return version.__replace__(post=None, dev=None, local=None)
63def _earliest_prerelease(version: Version) -> Version:
64 """Earliest pre-release of *version*.
66 1.2 -> 1.2.dev0, 1.2.post1 -> 1.2.post1.dev0.
67 """
68 return version.__replace__(dev=0, local=None)
71class InvalidSpecifier(ValueError):
72 """
73 Raised when attempting to create a :class:`Specifier` with a specifier
74 string that is invalid.
76 >>> Specifier("lolwat")
77 Traceback (most recent call last):
78 ...
79 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
80 """
83class BaseSpecifier(metaclass=abc.ABCMeta):
84 __slots__ = ()
85 __match_args__ = ("_str",)
87 @property
88 def _str(self) -> str:
89 """Internal property for match_args"""
90 return str(self)
92 @abc.abstractmethod
93 def __str__(self) -> str:
94 """
95 Returns the str representation of this Specifier-like object. This
96 should be representative of the Specifier itself.
97 """
99 @abc.abstractmethod
100 def __hash__(self) -> int:
101 """
102 Returns a hash value for this Specifier-like object.
103 """
105 @abc.abstractmethod
106 def __eq__(self, other: object) -> bool:
107 """
108 Returns a boolean representing whether or not the two Specifier-like
109 objects are equal.
111 :param other: The other object to check against.
112 """
114 @property
115 @abc.abstractmethod
116 def prereleases(self) -> bool | None:
117 """Whether or not pre-releases as a whole are allowed.
119 This can be set to either ``True`` or ``False`` to explicitly enable or disable
120 prereleases or it can be set to ``None`` (the default) to use default semantics.
121 """
123 @prereleases.setter # noqa: B027
124 def prereleases(self, value: bool) -> None:
125 """Setter for :attr:`prereleases`.
127 :param value: The value to set.
128 """
130 @abc.abstractmethod
131 def contains(self, item: str, prereleases: bool | None = None) -> bool:
132 """
133 Determines if the given item is contained within this specifier.
134 """
136 @typing.overload
137 def filter(
138 self,
139 iterable: Iterable[UnparsedVersionVar],
140 prereleases: bool | None = None,
141 key: None = ...,
142 ) -> Iterator[UnparsedVersionVar]: ...
144 @typing.overload
145 def filter(
146 self,
147 iterable: Iterable[T],
148 prereleases: bool | None = None,
149 key: Callable[[T], UnparsedVersion] = ...,
150 ) -> Iterator[T]: ...
152 @abc.abstractmethod
153 def filter(
154 self,
155 iterable: Iterable[Any],
156 prereleases: bool | None = None,
157 key: Callable[[Any], UnparsedVersion] | None = None,
158 ) -> Iterator[Any]:
159 """
160 Takes an iterable of items and filters them so that only items which
161 are contained within this specifier are allowed in it.
162 """
165class Specifier(BaseSpecifier):
166 """This class abstracts handling of version specifiers.
168 .. tip::
170 It is generally not required to instantiate this manually. You should instead
171 prefer to work with :class:`SpecifierSet` instead, which can parse
172 comma-separated version specifiers (which is what package metadata contains).
173 """
175 __slots__ = ("_prereleases", "_spec", "_spec_version", "_wildcard_split")
177 _specifier_regex_str = r"""
178 (?:
179 (?:
180 # The identity operators allow for an escape hatch that will
181 # do an exact string match of the version you wish to install.
182 # This will not be parsed by PEP 440 and we cannot determine
183 # any semantic meaning from it. This operator is discouraged
184 # but included entirely as an escape hatch.
185 === # Only match for the identity operator
186 \s*
187 [^\s;)]* # The arbitrary version can be just about anything,
188 # we match everything except for whitespace, a
189 # semi-colon for marker support, and a closing paren
190 # since versions can be enclosed in them.
191 )
192 |
193 (?:
194 # The (non)equality operators allow for wild card and local
195 # versions to be specified so we have to define these two
196 # operators separately to enable that.
197 (?:==|!=) # Only match for equals and not equals
199 \s*
200 v?
201 (?:[0-9]+!)? # epoch
202 [0-9]+(?:\.[0-9]+)* # release
204 # You cannot use a wild card and a pre-release, post-release, a dev or
205 # local version together so group them with a | and make them optional.
206 (?:
207 \.\* # Wild card syntax of .*
208 |
209 (?a: # pre release
210 [-_\.]?
211 (alpha|beta|preview|pre|a|b|c|rc)
212 [-_\.]?
213 [0-9]*
214 )?
215 (?a: # post release
216 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
217 )?
218 (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
219 (?a:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
220 )?
221 )
222 |
223 (?:
224 # The compatible operator requires at least two digits in the
225 # release segment.
226 (?:~=) # Only match for the compatible operator
228 \s*
229 v?
230 (?:[0-9]+!)? # epoch
231 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
232 (?: # pre release
233 [-_\.]?
234 (alpha|beta|preview|pre|a|b|c|rc)
235 [-_\.]?
236 [0-9]*
237 )?
238 (?: # post release
239 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
240 )?
241 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
242 )
243 |
244 (?:
245 # All other operators only allow a sub set of what the
246 # (non)equality operators do. Specifically they do not allow
247 # local versions to be specified nor do they allow the prefix
248 # matching wild cards.
249 (?:<=|>=|<|>)
251 \s*
252 v?
253 (?:[0-9]+!)? # epoch
254 [0-9]+(?:\.[0-9]+)* # release
255 (?a: # pre release
256 [-_\.]?
257 (alpha|beta|preview|pre|a|b|c|rc)
258 [-_\.]?
259 [0-9]*
260 )?
261 (?a: # post release
262 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
263 )?
264 (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
265 )
266 )
267 """
269 _regex = re.compile(
270 r"\s*" + _specifier_regex_str + r"\s*", re.VERBOSE | re.IGNORECASE
271 )
273 _operators: Final = {
274 "~=": "compatible",
275 "==": "equal",
276 "!=": "not_equal",
277 "<=": "less_than_equal",
278 ">=": "greater_than_equal",
279 "<": "less_than",
280 ">": "greater_than",
281 "===": "arbitrary",
282 }
284 def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
285 """Initialize a Specifier instance.
287 :param spec:
288 The string representation of a specifier which will be parsed and
289 normalized before use.
290 :param prereleases:
291 This tells the specifier if it should accept prerelease versions if
292 applicable or not. The default of ``None`` will autodetect it from the
293 given specifiers.
294 :raises InvalidSpecifier:
295 If the given specifier is invalid (i.e. bad syntax).
296 """
297 if not self._regex.fullmatch(spec):
298 raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
300 spec = spec.strip()
301 if spec.startswith("==="):
302 operator, version = spec[:3], spec[3:].strip()
303 elif spec.startswith(("~=", "==", "!=", "<=", ">=")):
304 operator, version = spec[:2], spec[2:].strip()
305 else:
306 operator, version = spec[:1], spec[1:].strip()
308 self._spec: tuple[str, str] = (operator, version)
310 # Store whether or not this Specifier should accept prereleases
311 self._prereleases = prereleases
313 # Specifier version cache
314 self._spec_version: tuple[str, Version] | None = None
316 # Populated on first wildcard (==X.*) comparison
317 self._wildcard_split: tuple[list[str], int] | None = None
319 def _get_spec_version(self, version: str) -> Version | None:
320 """One element cache, as only one spec Version is needed per Specifier."""
321 if self._spec_version is not None and self._spec_version[0] == version:
322 return self._spec_version[1]
324 version_specifier = _coerce_version(version)
325 if version_specifier is None:
326 return None
328 self._spec_version = (version, version_specifier)
329 return version_specifier
331 def _require_spec_version(self, version: str) -> Version:
332 """Get spec version, asserting it's valid (not for === operator).
334 This method should only be called for operators where version
335 strings are guaranteed to be valid PEP 440 versions (not ===).
336 """
337 spec_version = self._get_spec_version(version)
338 assert spec_version is not None
339 return spec_version
341 @property
342 def prereleases(self) -> bool | None:
343 # If there is an explicit prereleases set for this, then we'll just
344 # blindly use that.
345 if self._prereleases is not None:
346 return self._prereleases
348 # Only the "!=" operator does not imply prereleases when
349 # the version in the specifier is a prerelease.
350 operator, version_str = self._spec
351 if operator == "!=":
352 return False
354 # The == specifier with trailing .* cannot include prereleases
355 # e.g. "==1.0a1.*" is not valid.
356 if operator == "==" and version_str.endswith(".*"):
357 return False
359 # "===" can have arbitrary string versions, so we cannot parse
360 # those, we take prereleases as unknown (None) for those.
361 version = self._get_spec_version(version_str)
362 if version is None:
363 return None
365 # For all other operators, use the check if spec Version
366 # object implies pre-releases.
367 return version.is_prerelease
369 @prereleases.setter
370 def prereleases(self, value: bool | None) -> None:
371 self._prereleases = value
373 @property
374 def operator(self) -> str:
375 """The operator of this specifier.
377 >>> Specifier("==1.2.3").operator
378 '=='
379 """
380 return self._spec[0]
382 @property
383 def version(self) -> str:
384 """The version of this specifier.
386 >>> Specifier("==1.2.3").version
387 '1.2.3'
388 """
389 return self._spec[1]
391 def __repr__(self) -> str:
392 """A representation of the Specifier that shows all internal state.
394 >>> Specifier('>=1.0.0')
395 <Specifier('>=1.0.0')>
396 >>> Specifier('>=1.0.0', prereleases=False)
397 <Specifier('>=1.0.0', prereleases=False)>
398 >>> Specifier('>=1.0.0', prereleases=True)
399 <Specifier('>=1.0.0', prereleases=True)>
400 """
401 pre = (
402 f", prereleases={self.prereleases!r}"
403 if self._prereleases is not None
404 else ""
405 )
407 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
409 def __str__(self) -> str:
410 """A string representation of the Specifier that can be round-tripped.
412 >>> str(Specifier('>=1.0.0'))
413 '>=1.0.0'
414 >>> str(Specifier('>=1.0.0', prereleases=False))
415 '>=1.0.0'
416 """
417 return "{}{}".format(*self._spec)
419 @property
420 def _canonical_spec(self) -> tuple[str, str]:
421 operator, version = self._spec
422 if operator == "===" or version.endswith(".*"):
423 return operator, version
425 spec_version = self._require_spec_version(version)
427 canonical_version = canonicalize_version(
428 spec_version, strip_trailing_zero=(operator != "~=")
429 )
431 return operator, canonical_version
433 def __hash__(self) -> int:
434 return hash(self._canonical_spec)
436 def __eq__(self, other: object) -> bool:
437 """Whether or not the two Specifier-like objects are equal.
439 :param other: The other object to check against.
441 The value of :attr:`prereleases` is ignored.
443 >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
444 True
445 >>> (Specifier("==1.2.3", prereleases=False) ==
446 ... Specifier("==1.2.3", prereleases=True))
447 True
448 >>> Specifier("==1.2.3") == "==1.2.3"
449 True
450 >>> Specifier("==1.2.3") == Specifier("==1.2.4")
451 False
452 >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
453 False
454 """
455 if isinstance(other, str):
456 try:
457 other = self.__class__(str(other))
458 except InvalidSpecifier:
459 return NotImplemented
460 elif not isinstance(other, self.__class__):
461 return NotImplemented
463 return self._canonical_spec == other._canonical_spec
465 def _get_operator(self, op: str) -> CallableOperator:
466 operator_callable: CallableOperator = getattr(
467 self, f"_compare_{self._operators[op]}"
468 )
469 return operator_callable
471 def _compare_compatible(self, prospective: Version, spec: str) -> bool:
472 # Compatible releases have an equivalent combination of >= and ==. That
473 # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
474 # implement this in terms of the other specifiers instead of
475 # implementing it ourselves. The only thing we need to do is construct
476 # the other specifiers.
478 # We want everything but the last item in the version, but we want to
479 # ignore suffix segments.
480 prefix = _version_join(
481 list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
482 )
484 # Add the prefix notation to the end of our string
485 prefix += ".*"
487 return (self._compare_greater_than_equal(prospective, spec)) and (
488 self._compare_equal(prospective, prefix)
489 )
491 def _get_wildcard_split(self, spec: str) -> tuple[list[str], int]:
492 """Cached split of a wildcard spec into components and numeric length.
494 >>> Specifier("==1.*")._get_wildcard_split("1.*")
495 (['0', '1'], 2)
496 >>> Specifier("==3.10.*")._get_wildcard_split("3.10.*")
497 (['0', '3', '10'], 3)
498 """
499 wildcard_split = self._wildcard_split
500 if wildcard_split is None:
501 normalized = canonicalize_version(spec[:-2], strip_trailing_zero=False)
502 split_spec = _version_split(normalized)
503 wildcard_split = (split_spec, _numeric_prefix_len(split_spec))
504 self._wildcard_split = wildcard_split
505 return wildcard_split
507 def _compare_equal(self, prospective: Version, spec: str) -> bool:
508 # We need special logic to handle prefix matching
509 if spec.endswith(".*"):
510 split_spec, spec_numeric_len = self._get_wildcard_split(spec)
512 # In the case of prefix matching we want to ignore local segment.
513 normalized_prospective = canonicalize_version(
514 _public_version(prospective), strip_trailing_zero=False
515 )
516 # Split the prospective version out by bangs and dots, and pretend
517 # that there is an implicit dot in between a release segment and
518 # a pre-release segment.
519 split_prospective = _version_split(normalized_prospective)
521 # 0-pad the prospective version before shortening it to get the correct
522 # shortened version.
523 padded_prospective = _left_pad(split_prospective, spec_numeric_len)
525 # Shorten the prospective version to be the same length as the spec
526 # so that we can determine if the specifier is a prefix of the
527 # prospective version or not.
528 shortened_prospective = padded_prospective[: len(split_spec)]
530 return shortened_prospective == split_spec
531 else:
532 # Convert our spec string into a Version
533 spec_version = self._require_spec_version(spec)
535 # If the specifier does not have a local segment, then we want to
536 # act as if the prospective version also does not have a local
537 # segment.
538 if not spec_version.local:
539 prospective = _public_version(prospective)
541 return prospective == spec_version
543 def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
544 return not self._compare_equal(prospective, spec)
546 def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
547 # NB: Local version identifiers are NOT permitted in the version
548 # specifier, so local version labels can be universally removed from
549 # the prospective version.
550 return _public_version(prospective) <= self._require_spec_version(spec)
552 def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
553 # NB: Local version identifiers are NOT permitted in the version
554 # specifier, so local version labels can be universally removed from
555 # the prospective version.
556 return _public_version(prospective) >= self._require_spec_version(spec)
558 def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
559 # Convert our spec to a Version instance, since we'll want to work with
560 # it as a version.
561 spec = self._require_spec_version(spec_str)
563 # Check to see if the prospective version is less than the spec
564 # version. If it's not we can short circuit and just return False now
565 # instead of doing extra unneeded work.
566 if not prospective < spec:
567 return False
569 # The spec says: "<V MUST NOT allow a pre-release of the specified
570 # version unless the specified version is itself a pre-release."
571 if (
572 not spec.is_prerelease
573 and prospective.is_prerelease
574 and prospective >= _earliest_prerelease(spec)
575 ):
576 return False
578 # If we've gotten to here, it means that prospective version is both
579 # less than the spec version *and* it's not a pre-release of the same
580 # version in the spec.
581 return True
583 def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
584 # Convert our spec to a Version instance, since we'll want to work with
585 # it as a version.
586 spec = self._require_spec_version(spec_str)
588 # Check to see if the prospective version is greater than the spec
589 # version. If it's not we can short circuit and just return False now
590 # instead of doing extra unneeded work.
591 if not prospective > spec:
592 return False
594 # The spec says: ">V MUST NOT allow a post-release of the specified
595 # version unless the specified version is itself a post-release."
596 if (
597 not spec.is_postrelease
598 and prospective.is_postrelease
599 and _post_base(prospective) == spec
600 ):
601 return False
603 # Per the spec: ">V MUST NOT match a local version of the specified
604 # version". A "local version of V" is any version whose public part
605 # equals V. So >1.0a1 must not match 1.0a1+local, but must still
606 # match 1.0a2+local.
607 if prospective.local is not None and _public_version(prospective) == spec:
608 return False
610 # If we've gotten to here, it means that prospective version is both
611 # greater than the spec version *and* it's not a pre-release of the
612 # same version in the spec.
613 return True
615 def _compare_arbitrary(self, prospective: Version | str, spec: str) -> bool:
616 return str(prospective).lower() == str(spec).lower()
618 def __contains__(self, item: str | Version) -> bool:
619 """Return whether or not the item is contained in this specifier.
621 :param item: The item to check for.
623 This is used for the ``in`` operator and behaves the same as
624 :meth:`contains` with no ``prereleases`` argument passed.
626 >>> "1.2.3" in Specifier(">=1.2.3")
627 True
628 >>> Version("1.2.3") in Specifier(">=1.2.3")
629 True
630 >>> "1.0.0" in Specifier(">=1.2.3")
631 False
632 >>> "1.3.0a1" in Specifier(">=1.2.3")
633 True
634 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
635 True
636 """
637 return self.contains(item)
639 def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
640 """Return whether or not the item is contained in this specifier.
642 :param item:
643 The item to check for, which can be a version string or a
644 :class:`Version` instance.
645 :param prereleases:
646 Whether or not to match prereleases with this Specifier. If set to
647 ``None`` (the default), it will follow the recommendation from
648 :pep:`440` and match prereleases, as there are no other versions.
650 >>> Specifier(">=1.2.3").contains("1.2.3")
651 True
652 >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
653 True
654 >>> Specifier(">=1.2.3").contains("1.0.0")
655 False
656 >>> Specifier(">=1.2.3").contains("1.3.0a1")
657 True
658 >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1")
659 False
660 >>> Specifier(">=1.2.3").contains("1.3.0a1")
661 True
662 """
664 return bool(list(self.filter([item], prereleases=prereleases)))
666 @typing.overload
667 def filter(
668 self,
669 iterable: Iterable[UnparsedVersionVar],
670 prereleases: bool | None = None,
671 key: None = ...,
672 ) -> Iterator[UnparsedVersionVar]: ...
674 @typing.overload
675 def filter(
676 self,
677 iterable: Iterable[T],
678 prereleases: bool | None = None,
679 key: Callable[[T], UnparsedVersion] = ...,
680 ) -> Iterator[T]: ...
682 def filter(
683 self,
684 iterable: Iterable[Any],
685 prereleases: bool | None = None,
686 key: Callable[[Any], UnparsedVersion] | None = None,
687 ) -> Iterator[Any]:
688 """Filter items in the given iterable, that match the specifier.
690 :param iterable:
691 An iterable that can contain version strings and :class:`Version` instances.
692 The items in the 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:`Version` instance to be used for
700 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'}]
716 """
717 prereleases_versions = []
718 found_non_prereleases = False
720 # Determine if to include prereleases by default
721 include_prereleases = (
722 prereleases if prereleases is not None else self.prereleases
723 )
725 # Get the matching operator
726 operator_callable = self._get_operator(self.operator)
728 # Filter versions
729 for version in iterable:
730 parsed_version = _coerce_version(version if key is None else key(version))
731 match = False
732 if parsed_version is None:
733 # === operator can match arbitrary (non-version) strings
734 if self.operator == "===" and self._compare_arbitrary(
735 version, self.version
736 ):
737 yield version
738 elif self.operator == "===":
739 match = self._compare_arbitrary(
740 version if key is None else key(version), self.version
741 )
742 else:
743 match = operator_callable(parsed_version, self.version)
745 if match and parsed_version is not None:
746 # If it's not a prerelease or prereleases are allowed, yield it directly
747 if not parsed_version.is_prerelease or include_prereleases:
748 found_non_prereleases = True
749 yield version
750 # Otherwise collect prereleases for potential later use
751 elif prereleases is None and self._prereleases is not False:
752 prereleases_versions.append(version)
754 # If no non-prereleases were found and prereleases weren't
755 # explicitly forbidden, yield the collected prereleases
756 if (
757 not found_non_prereleases
758 and prereleases is None
759 and self._prereleases is not False
760 ):
761 yield from prereleases_versions
764_prefix_regex = re.compile(r"([0-9]+)((?:a|b|c|rc)[0-9]+)")
767def _pep440_filter_prereleases(
768 iterable: Iterable[Any], key: Callable[[Any], UnparsedVersion] | None
769) -> Iterator[Any]:
770 """Filter per PEP 440: exclude prereleases unless no finals exist."""
771 # Two lists used:
772 # * all_nonfinal to preserve order if no finals exist
773 # * arbitrary_strings for streaming when first final found
774 all_nonfinal: list[Any] = []
775 arbitrary_strings: list[Any] = []
777 found_final = False
778 for item in iterable:
779 parsed = _coerce_version(item if key is None else key(item))
781 if parsed is None:
782 # Arbitrary strings are always included as it is not
783 # possible to determine if they are prereleases,
784 # and they have already passed all specifiers.
785 if found_final:
786 yield item
787 else:
788 arbitrary_strings.append(item)
789 all_nonfinal.append(item)
790 continue
792 if not parsed.is_prerelease:
793 # Final release found - flush arbitrary strings, then yield
794 if not found_final:
795 yield from arbitrary_strings
796 found_final = True
797 yield item
798 continue
800 # Prerelease - buffer if no finals yet, otherwise skip
801 if not found_final:
802 all_nonfinal.append(item)
804 # No finals found - yield all buffered items
805 if not found_final:
806 yield from all_nonfinal
809def _version_split(version: str) -> list[str]:
810 """Split version into components.
812 The split components are intended for version comparison. The logic does
813 not attempt to retain the original version string, so joining the
814 components back with :func:`_version_join` may not produce the original
815 version string.
816 """
817 result: list[str] = []
819 epoch, _, rest = version.rpartition("!")
820 result.append(epoch or "0")
822 for item in rest.split("."):
823 match = _prefix_regex.fullmatch(item)
824 if match:
825 result.extend(match.groups())
826 else:
827 result.append(item)
828 return result
831def _version_join(components: list[str]) -> str:
832 """Join split version components into a version string.
834 This function assumes the input came from :func:`_version_split`, where the
835 first component must be the epoch (either empty or numeric), and all other
836 components numeric.
837 """
838 epoch, *rest = components
839 return f"{epoch}!{'.'.join(rest)}"
842def _is_not_suffix(segment: str) -> bool:
843 return not any(
844 segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
845 )
848def _numeric_prefix_len(split: list[str]) -> int:
849 """Count leading numeric components in a :func:`_version_split` result.
851 >>> _numeric_prefix_len(["0", "1", "2", "a1"])
852 3
853 """
854 count = 0
855 for segment in split:
856 if not segment.isdigit():
857 break
858 count += 1
859 return count
862def _left_pad(split: list[str], target_numeric_len: int) -> list[str]:
863 """Pad a :func:`_version_split` result with ``"0"`` segments to reach
864 ``target_numeric_len`` numeric components. Suffix segments are preserved.
866 >>> _left_pad(["0", "1", "a1"], 4)
867 ['0', '1', '0', '0', 'a1']
868 """
869 numeric_len = _numeric_prefix_len(split)
870 pad_needed = target_numeric_len - numeric_len
871 if pad_needed <= 0:
872 return split
873 return [*split[:numeric_len], *(["0"] * pad_needed), *split[numeric_len:]]
876def _operator_cost(op_entry: tuple[CallableOperator, str, str]) -> int:
877 """Sort key for Cost Based Ordering of specifier operators in _filter_versions.
879 Operators run sequentially on a shrinking candidate set, so operators that
880 reject the most versions should run first to minimize work for later ones.
882 Tier 0: Exact equality (==, ===), likely to narrow candidates to one version
883 Tier 1: Range checks (>=, <=, >, <), cheap and usually reject a large portion
884 Tier 2: Wildcard equality (==.*) and compatible release (~=), more expensive
885 Tier 3: Exact !=, cheap but rarely rejects
886 Tier 4: Wildcard !=.*, expensive and rarely rejects
887 """
888 _, ver, op = op_entry
889 if op == "==":
890 return 0 if not ver.endswith(".*") else 2
891 if op in (">=", "<=", ">", "<"):
892 return 1
893 if op == "~=":
894 return 2
895 if op == "!=":
896 return 3 if not ver.endswith(".*") else 4
897 if op == "===":
898 return 0
900 raise ValueError(f"Unknown operator: {op!r}") # pragma: no cover
903class SpecifierSet(BaseSpecifier):
904 """This class abstracts handling of a set of version specifiers.
906 It can be passed a single specifier (``>=3.0``), a comma-separated list of
907 specifiers (``>=3.0,!=3.1``), or no specifier at all.
908 """
910 __slots__ = (
911 "_canonicalized",
912 "_has_arbitrary",
913 "_prereleases",
914 "_resolved_ops",
915 "_specs",
916 )
918 def __init__(
919 self,
920 specifiers: str | Iterable[Specifier] = "",
921 prereleases: bool | None = None,
922 ) -> None:
923 """Initialize a SpecifierSet instance.
925 :param specifiers:
926 The string representation of a specifier or a comma-separated list of
927 specifiers which will be parsed and normalized before use.
928 May also be an iterable of ``Specifier`` instances, which will be used
929 as is.
930 :param prereleases:
931 This tells the SpecifierSet if it should accept prerelease versions if
932 applicable or not. The default of ``None`` will autodetect it from the
933 given specifiers.
935 :raises InvalidSpecifier:
936 If the given ``specifiers`` are not parseable than this exception will be
937 raised.
938 """
940 if isinstance(specifiers, str):
941 # Split on `,` to break each individual specifier into its own item, and
942 # strip each item to remove leading/trailing whitespace.
943 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
945 self._specs: tuple[Specifier, ...] = tuple(map(Specifier, split_specifiers))
946 # Fast substring check; avoids iterating parsed specs.
947 self._has_arbitrary = "===" in specifiers
948 else:
949 self._specs = tuple(specifiers)
950 # Substring check works for both Specifier objects and plain
951 # strings (setuptools passes lists of strings).
952 self._has_arbitrary = any("===" in str(s) for s in self._specs)
954 self._canonicalized = len(self._specs) <= 1
955 self._resolved_ops: list[tuple[CallableOperator, str, str]] | None = None
957 # Store our prereleases value so we can use it later to determine if
958 # we accept prereleases or not.
959 self._prereleases = prereleases
961 def _canonical_specs(self) -> tuple[Specifier, ...]:
962 """Deduplicate, sort, and cache specs for order-sensitive operations."""
963 if not self._canonicalized:
964 self._specs = tuple(dict.fromkeys(sorted(self._specs, key=str)))
965 self._canonicalized = True
966 self._resolved_ops = None
967 return self._specs
969 @property
970 def prereleases(self) -> bool | None:
971 # If we have been given an explicit prerelease modifier, then we'll
972 # pass that through here.
973 if self._prereleases is not None:
974 return self._prereleases
976 # If we don't have any specifiers, and we don't have a forced value,
977 # then we'll just return None since we don't know if this should have
978 # pre-releases or not.
979 if not self._specs:
980 return None
982 # Otherwise we'll see if any of the given specifiers accept
983 # prereleases, if any of them do we'll return True, otherwise False.
984 if any(s.prereleases for s in self._specs):
985 return True
987 return None
989 @prereleases.setter
990 def prereleases(self, value: bool | None) -> None:
991 self._prereleases = value
993 def __repr__(self) -> str:
994 """A representation of the specifier set that shows all internal state.
996 Note that the ordering of the individual specifiers within the set may not
997 match the input string.
999 >>> SpecifierSet('>=1.0.0,!=2.0.0')
1000 <SpecifierSet('!=2.0.0,>=1.0.0')>
1001 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
1002 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
1003 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
1004 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
1005 """
1006 pre = (
1007 f", prereleases={self.prereleases!r}"
1008 if self._prereleases is not None
1009 else ""
1010 )
1012 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
1014 def __str__(self) -> str:
1015 """A string representation of the specifier set that can be round-tripped.
1017 Note that the ordering of the individual specifiers within the set may not
1018 match the input string.
1020 >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
1021 '!=1.0.1,>=1.0.0'
1022 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
1023 '!=1.0.1,>=1.0.0'
1024 """
1025 return ",".join(str(s) for s in self._canonical_specs())
1027 def __hash__(self) -> int:
1028 return hash(self._canonical_specs())
1030 def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
1031 """Return a SpecifierSet which is a combination of the two sets.
1033 :param other: The other object to combine with.
1035 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
1036 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
1037 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
1038 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
1039 """
1040 if isinstance(other, str):
1041 other = SpecifierSet(other)
1042 elif not isinstance(other, SpecifierSet):
1043 return NotImplemented
1045 specifier = SpecifierSet()
1046 specifier._specs = self._specs + other._specs
1047 specifier._canonicalized = len(specifier._specs) <= 1
1048 specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary
1049 specifier._resolved_ops = None
1051 # Combine prerelease settings: use common or non-None value
1052 if self._prereleases is None or self._prereleases == other._prereleases:
1053 specifier._prereleases = other._prereleases
1054 elif other._prereleases is None:
1055 specifier._prereleases = self._prereleases
1056 else:
1057 raise ValueError(
1058 "Cannot combine SpecifierSets with True and False prerelease overrides."
1059 )
1061 return specifier
1063 def __eq__(self, other: object) -> bool:
1064 """Whether or not the two SpecifierSet-like objects are equal.
1066 :param other: The other object to check against.
1068 The value of :attr:`prereleases` is ignored.
1070 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
1071 True
1072 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
1073 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
1074 True
1075 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
1076 True
1077 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
1078 False
1079 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
1080 False
1081 """
1082 if isinstance(other, (str, Specifier)):
1083 other = SpecifierSet(str(other))
1084 elif not isinstance(other, SpecifierSet):
1085 return NotImplemented
1087 return self._canonical_specs() == other._canonical_specs()
1089 def __len__(self) -> int:
1090 """Returns the number of specifiers in this specifier set."""
1091 return len(self._specs)
1093 def __iter__(self) -> Iterator[Specifier]:
1094 """
1095 Returns an iterator over all the underlying :class:`Specifier` instances
1096 in this specifier set.
1098 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
1099 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
1100 """
1101 return iter(self._specs)
1103 def __contains__(self, item: UnparsedVersion) -> bool:
1104 """Return whether or not the item is contained in this specifier.
1106 :param item: The item to check for.
1108 This is used for the ``in`` operator and behaves the same as
1109 :meth:`contains` with no ``prereleases`` argument passed.
1111 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
1112 True
1113 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
1114 True
1115 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
1116 False
1117 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
1118 True
1119 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
1120 True
1121 """
1122 return self.contains(item)
1124 def contains(
1125 self,
1126 item: UnparsedVersion,
1127 prereleases: bool | None = None,
1128 installed: bool | None = None,
1129 ) -> bool:
1130 """Return whether or not the item is contained in this SpecifierSet.
1132 :param item:
1133 The item to check for, which can be a version string or a
1134 :class:`Version` instance.
1135 :param prereleases:
1136 Whether or not to match prereleases with this SpecifierSet. If set to
1137 ``None`` (the default), it will follow the recommendation from :pep:`440`
1138 and match prereleases, as there are no other versions.
1139 :param installed:
1140 Whether or not the item is installed. If set to ``True``, it will
1141 accept prerelease versions even if the specifier does not allow them.
1143 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
1144 True
1145 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
1146 True
1147 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
1148 False
1149 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
1150 True
1151 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")
1152 False
1153 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
1154 True
1155 """
1156 version = _coerce_version(item)
1158 if version is not None and installed and version.is_prerelease:
1159 prereleases = True
1161 # When item is a string and === is involved, keep it as-is
1162 # so the comparison isn't done against the normalized form.
1163 if version is None or (self._has_arbitrary and not isinstance(item, Version)):
1164 check_item = item
1165 else:
1166 check_item = version
1167 return bool(list(self.filter([check_item], prereleases=prereleases)))
1169 @typing.overload
1170 def filter(
1171 self,
1172 iterable: Iterable[UnparsedVersionVar],
1173 prereleases: bool | None = None,
1174 key: None = ...,
1175 ) -> Iterator[UnparsedVersionVar]: ...
1177 @typing.overload
1178 def filter(
1179 self,
1180 iterable: Iterable[T],
1181 prereleases: bool | None = None,
1182 key: Callable[[T], UnparsedVersion] = ...,
1183 ) -> Iterator[T]: ...
1185 def filter(
1186 self,
1187 iterable: Iterable[Any],
1188 prereleases: bool | None = None,
1189 key: Callable[[Any], UnparsedVersion] | None = None,
1190 ) -> Iterator[Any]:
1191 """Filter items in the given iterable, that match the specifiers in this set.
1193 :param iterable:
1194 An iterable that can contain version strings and :class:`Version` instances.
1195 The items in the iterable will be filtered according to the specifier.
1196 :param prereleases:
1197 Whether or not to allow prereleases in the returned iterator. If set to
1198 ``None`` (the default), it will follow the recommendation from :pep:`440`
1199 and match prereleases if there are no other versions.
1200 :param key:
1201 A callable that takes a single argument (an item from the iterable) and
1202 returns a version string or :class:`Version` instance to be used for
1203 filtering.
1205 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
1206 ['1.3']
1207 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
1208 ['1.3', <Version('1.4')>]
1209 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
1210 ['1.5a1']
1211 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
1212 ['1.3', '1.5a1']
1213 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
1214 ['1.3', '1.5a1']
1215 >>> list(SpecifierSet(">=1.2.3").filter(
1216 ... [{"ver": "1.2"}, {"ver": "1.3"}],
1217 ... key=lambda x: x["ver"]))
1218 [{'ver': '1.3'}]
1220 An "empty" SpecifierSet will filter items based on the presence of prerelease
1221 versions in the set.
1223 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
1224 ['1.3']
1225 >>> list(SpecifierSet("").filter(["1.5a1"]))
1226 ['1.5a1']
1227 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
1228 ['1.3', '1.5a1']
1229 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
1230 ['1.3', '1.5a1']
1231 """
1232 # Determine if we're forcing a prerelease or not, if we're not forcing
1233 # one for this particular filter call, then we'll use whatever the
1234 # SpecifierSet thinks for whether or not we should support prereleases.
1235 if prereleases is None and self.prereleases is not None:
1236 prereleases = self.prereleases
1238 # Filter versions that match all specifiers using Cost Based Ordering.
1239 if self._specs:
1240 # When prereleases is None, we need to let all versions through
1241 # the individual filters, then decide about prereleases at the end
1242 # based on whether any non-prereleases matched ALL specs.
1244 # Fast path: single specifier, delegate directly.
1245 if len(self._specs) == 1:
1246 filtered = self._specs[0].filter(
1247 iterable,
1248 prereleases=True if prereleases is None else prereleases,
1249 key=key,
1250 )
1251 else:
1252 filtered = self._filter_versions(
1253 iterable,
1254 key,
1255 prereleases=True if prereleases is None else prereleases,
1256 )
1258 if prereleases is not None:
1259 return filtered
1261 return _pep440_filter_prereleases(filtered, key)
1263 # Handle Empty SpecifierSet.
1264 if prereleases is True:
1265 return iter(iterable)
1267 if prereleases is False:
1268 return (
1269 item
1270 for item in iterable
1271 if (
1272 (version := _coerce_version(item if key is None else key(item)))
1273 is None
1274 or not version.is_prerelease
1275 )
1276 )
1278 # PEP 440: exclude prereleases unless no final releases matched
1279 return _pep440_filter_prereleases(iterable, key)
1281 def _filter_versions(
1282 self,
1283 iterable: Iterable[Any],
1284 key: Callable[[Any], UnparsedVersion] | None,
1285 prereleases: bool | None = None,
1286 ) -> Iterator[Any]:
1287 """Filter versions against all specifiers in a single pass.
1289 Uses Cost Based Ordering: specifiers are sorted by _operator_cost so
1290 that cheap range operators reject versions early, avoiding expensive
1291 wildcard or compatible operators on versions that would have been
1292 rejected anyway.
1293 """
1294 # Pre-resolve operators and sort (cached after first call).
1295 if self._resolved_ops is None:
1296 self._resolved_ops = sorted(
1297 (
1298 (spec._get_operator(spec.operator), spec.version, spec.operator)
1299 for spec in self._specs
1300 ),
1301 key=_operator_cost,
1302 )
1303 ops = self._resolved_ops
1304 exclude_prereleases = prereleases is False
1306 for item in iterable:
1307 parsed = _coerce_version(item if key is None else key(item))
1309 if parsed is None:
1310 # Only === can match non-parseable versions.
1311 if all(
1312 op == "===" and str(item).lower() == ver.lower()
1313 for _, ver, op in ops
1314 ):
1315 yield item
1316 elif exclude_prereleases and parsed.is_prerelease:
1317 pass
1318 elif all(
1319 str(item if key is None else key(item)).lower() == ver.lower()
1320 if op == "==="
1321 else op_fn(parsed, ver)
1322 for op_fn, ver, op in ops
1323 ):
1324 # Short-circuits on the first failing operator.
1325 yield item