Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/packaging/specifiers.py: 25%
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 _base_version(version: Version) -> Version:
56 if (
57 version.pre is None
58 and version.post is None
59 and version.dev is None
60 and version.local is None
61 ):
62 return version
63 return version.__replace__(pre=None, post=None, dev=None, local=None)
66class InvalidSpecifier(ValueError):
67 """
68 Raised when attempting to create a :class:`Specifier` with a specifier
69 string that is invalid.
71 >>> Specifier("lolwat")
72 Traceback (most recent call last):
73 ...
74 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
75 """
78class BaseSpecifier(metaclass=abc.ABCMeta):
79 __slots__ = ()
80 __match_args__ = ("_str",)
82 @property
83 def _str(self) -> str:
84 """Internal property for match_args"""
85 return str(self)
87 @abc.abstractmethod
88 def __str__(self) -> str:
89 """
90 Returns the str representation of this Specifier-like object. This
91 should be representative of the Specifier itself.
92 """
94 @abc.abstractmethod
95 def __hash__(self) -> int:
96 """
97 Returns a hash value for this Specifier-like object.
98 """
100 @abc.abstractmethod
101 def __eq__(self, other: object) -> bool:
102 """
103 Returns a boolean representing whether or not the two Specifier-like
104 objects are equal.
106 :param other: The other object to check against.
107 """
109 @property
110 @abc.abstractmethod
111 def prereleases(self) -> bool | None:
112 """Whether or not pre-releases as a whole are allowed.
114 This can be set to either ``True`` or ``False`` to explicitly enable or disable
115 prereleases or it can be set to ``None`` (the default) to use default semantics.
116 """
118 @prereleases.setter # noqa: B027
119 def prereleases(self, value: bool) -> None:
120 """Setter for :attr:`prereleases`.
122 :param value: The value to set.
123 """
125 @abc.abstractmethod
126 def contains(self, item: str, prereleases: bool | None = None) -> bool:
127 """
128 Determines if the given item is contained within this specifier.
129 """
131 @typing.overload
132 def filter(
133 self,
134 iterable: Iterable[UnparsedVersionVar],
135 prereleases: bool | None = None,
136 key: None = ...,
137 ) -> Iterator[UnparsedVersionVar]: ...
139 @typing.overload
140 def filter(
141 self,
142 iterable: Iterable[T],
143 prereleases: bool | None = None,
144 key: Callable[[T], UnparsedVersion] = ...,
145 ) -> Iterator[T]: ...
147 @abc.abstractmethod
148 def filter(
149 self,
150 iterable: Iterable[Any],
151 prereleases: bool | None = None,
152 key: Callable[[Any], UnparsedVersion] | None = None,
153 ) -> Iterator[Any]:
154 """
155 Takes an iterable of items and filters them so that only items which
156 are contained within this specifier are allowed in it.
157 """
160class Specifier(BaseSpecifier):
161 """This class abstracts handling of version specifiers.
163 .. tip::
165 It is generally not required to instantiate this manually. You should instead
166 prefer to work with :class:`SpecifierSet` instead, which can parse
167 comma-separated version specifiers (which is what package metadata contains).
168 """
170 __slots__ = ("_prereleases", "_spec", "_spec_version")
172 _operator_regex_str = r"""
173 (?P<operator>(~=|==|!=|<=|>=|<|>|===))
174 """
175 _version_regex_str = r"""
176 (?P<version>
177 (?:
178 # The identity operators allow for an escape hatch that will
179 # do an exact string match of the version you wish to install.
180 # This will not be parsed by PEP 440 and we cannot determine
181 # any semantic meaning from it. This operator is discouraged
182 # but included entirely as an escape hatch.
183 (?<====) # Only match for the identity operator
184 \s*
185 [^\s;)]* # The arbitrary version can be just about anything,
186 # we match everything except for whitespace, a
187 # semi-colon for marker support, and a closing paren
188 # since versions can be enclosed in them.
189 )
190 |
191 (?:
192 # The (non)equality operators allow for wild card and local
193 # versions to be specified so we have to define these two
194 # operators separately to enable that.
195 (?<===|!=) # Only match for equals and not equals
197 \s*
198 v?
199 (?:[0-9]+!)? # epoch
200 [0-9]+(?:\.[0-9]+)* # release
202 # You cannot use a wild card and a pre-release, post-release, a dev or
203 # local version together so group them with a | and make them optional.
204 (?:
205 \.\* # Wild card syntax of .*
206 |
207 (?: # pre release
208 [-_\.]?
209 (alpha|beta|preview|pre|a|b|c|rc)
210 [-_\.]?
211 [0-9]*
212 )?
213 (?: # post release
214 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
215 )?
216 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
217 (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
218 )?
219 )
220 |
221 (?:
222 # The compatible operator requires at least two digits in the
223 # release segment.
224 (?<=~=) # Only match for the compatible operator
226 \s*
227 v?
228 (?:[0-9]+!)? # epoch
229 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
230 (?: # pre release
231 [-_\.]?
232 (alpha|beta|preview|pre|a|b|c|rc)
233 [-_\.]?
234 [0-9]*
235 )?
236 (?: # post release
237 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
238 )?
239 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
240 )
241 |
242 (?:
243 # All other operators only allow a sub set of what the
244 # (non)equality operators do. Specifically they do not allow
245 # local versions to be specified nor do they allow the prefix
246 # matching wild cards.
247 (?<!==|!=|~=) # We have special cases for these
248 # operators so we want to make sure they
249 # don't match here.
251 \s*
252 v?
253 (?:[0-9]+!)? # epoch
254 [0-9]+(?:\.[0-9]+)* # release
255 (?: # pre release
256 [-_\.]?
257 (alpha|beta|preview|pre|a|b|c|rc)
258 [-_\.]?
259 [0-9]*
260 )?
261 (?: # post release
262 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
263 )?
264 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
265 )
266 )
267 """
269 _regex = re.compile(
270 r"\s*" + _operator_regex_str + _version_regex_str + r"\s*",
271 re.VERBOSE | re.IGNORECASE,
272 )
274 _operators: Final = {
275 "~=": "compatible",
276 "==": "equal",
277 "!=": "not_equal",
278 "<=": "less_than_equal",
279 ">=": "greater_than_equal",
280 "<": "less_than",
281 ">": "greater_than",
282 "===": "arbitrary",
283 }
285 def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
286 """Initialize a Specifier instance.
288 :param spec:
289 The string representation of a specifier which will be parsed and
290 normalized before use.
291 :param prereleases:
292 This tells the specifier if it should accept prerelease versions if
293 applicable or not. The default of ``None`` will autodetect it from the
294 given specifiers.
295 :raises InvalidSpecifier:
296 If the given specifier is invalid (i.e. bad syntax).
297 """
298 match = self._regex.fullmatch(spec)
299 if not match:
300 raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
302 self._spec: tuple[str, str] = (
303 match.group("operator").strip(),
304 match.group("version").strip(),
305 )
307 # Store whether or not this Specifier should accept prereleases
308 self._prereleases = prereleases
310 # Specifier version cache
311 self._spec_version: tuple[str, Version] | None = None
313 def _get_spec_version(self, version: str) -> Version | None:
314 """One element cache, as only one spec Version is needed per Specifier."""
315 if self._spec_version is not None and self._spec_version[0] == version:
316 return self._spec_version[1]
318 version_specifier = _coerce_version(version)
319 if version_specifier is None:
320 return None
322 self._spec_version = (version, version_specifier)
323 return version_specifier
325 def _require_spec_version(self, version: str) -> Version:
326 """Get spec version, asserting it's valid (not for === operator).
328 This method should only be called for operators where version
329 strings are guaranteed to be valid PEP 440 versions (not ===).
330 """
331 spec_version = self._get_spec_version(version)
332 assert spec_version is not None
333 return spec_version
335 @property
336 def prereleases(self) -> bool | None:
337 # If there is an explicit prereleases set for this, then we'll just
338 # blindly use that.
339 if self._prereleases is not None:
340 return self._prereleases
342 # Only the "!=" operator does not imply prereleases when
343 # the version in the specifier is a prerelease.
344 operator, version_str = self._spec
345 if operator == "!=":
346 return False
348 # The == specifier with trailing .* cannot include prereleases
349 # e.g. "==1.0a1.*" is not valid.
350 if operator == "==" and version_str.endswith(".*"):
351 return False
353 # "===" can have arbitrary string versions, so we cannot parse
354 # those, we take prereleases as unknown (None) for those.
355 version = self._get_spec_version(version_str)
356 if version is None:
357 return None
359 # For all other operators, use the check if spec Version
360 # object implies pre-releases.
361 return version.is_prerelease
363 @prereleases.setter
364 def prereleases(self, value: bool | None) -> None:
365 self._prereleases = value
367 @property
368 def operator(self) -> str:
369 """The operator of this specifier.
371 >>> Specifier("==1.2.3").operator
372 '=='
373 """
374 return self._spec[0]
376 @property
377 def version(self) -> str:
378 """The version of this specifier.
380 >>> Specifier("==1.2.3").version
381 '1.2.3'
382 """
383 return self._spec[1]
385 def __repr__(self) -> str:
386 """A representation of the Specifier that shows all internal state.
388 >>> Specifier('>=1.0.0')
389 <Specifier('>=1.0.0')>
390 >>> Specifier('>=1.0.0', prereleases=False)
391 <Specifier('>=1.0.0', prereleases=False)>
392 >>> Specifier('>=1.0.0', prereleases=True)
393 <Specifier('>=1.0.0', prereleases=True)>
394 """
395 pre = (
396 f", prereleases={self.prereleases!r}"
397 if self._prereleases is not None
398 else ""
399 )
401 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
403 def __str__(self) -> str:
404 """A string representation of the Specifier that can be round-tripped.
406 >>> str(Specifier('>=1.0.0'))
407 '>=1.0.0'
408 >>> str(Specifier('>=1.0.0', prereleases=False))
409 '>=1.0.0'
410 """
411 return "{}{}".format(*self._spec)
413 @property
414 def _canonical_spec(self) -> tuple[str, str]:
415 operator, version = self._spec
416 if operator == "===" or version.endswith(".*"):
417 return operator, version
419 spec_version = self._require_spec_version(version)
421 canonical_version = canonicalize_version(
422 spec_version, strip_trailing_zero=(operator != "~=")
423 )
425 return operator, canonical_version
427 def __hash__(self) -> int:
428 return hash(self._canonical_spec)
430 def __eq__(self, other: object) -> bool:
431 """Whether or not the two Specifier-like objects are equal.
433 :param other: The other object to check against.
435 The value of :attr:`prereleases` is ignored.
437 >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
438 True
439 >>> (Specifier("==1.2.3", prereleases=False) ==
440 ... Specifier("==1.2.3", prereleases=True))
441 True
442 >>> Specifier("==1.2.3") == "==1.2.3"
443 True
444 >>> Specifier("==1.2.3") == Specifier("==1.2.4")
445 False
446 >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
447 False
448 """
449 if isinstance(other, str):
450 try:
451 other = self.__class__(str(other))
452 except InvalidSpecifier:
453 return NotImplemented
454 elif not isinstance(other, self.__class__):
455 return NotImplemented
457 return self._canonical_spec == other._canonical_spec
459 def _get_operator(self, op: str) -> CallableOperator:
460 operator_callable: CallableOperator = getattr(
461 self, f"_compare_{self._operators[op]}"
462 )
463 return operator_callable
465 def _compare_compatible(self, prospective: Version, spec: str) -> bool:
466 # Compatible releases have an equivalent combination of >= and ==. That
467 # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
468 # implement this in terms of the other specifiers instead of
469 # implementing it ourselves. The only thing we need to do is construct
470 # the other specifiers.
472 # We want everything but the last item in the version, but we want to
473 # ignore suffix segments.
474 prefix = _version_join(
475 list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
476 )
478 # Add the prefix notation to the end of our string
479 prefix += ".*"
481 return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
482 prospective, prefix
483 )
485 def _compare_equal(self, prospective: Version, spec: str) -> bool:
486 # We need special logic to handle prefix matching
487 if spec.endswith(".*"):
488 # In the case of prefix matching we want to ignore local segment.
489 normalized_prospective = canonicalize_version(
490 _public_version(prospective), strip_trailing_zero=False
491 )
492 # Get the normalized version string ignoring the trailing .*
493 normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)
494 # Split the spec out by bangs and dots, and pretend that there is
495 # an implicit dot in between a release segment and a pre-release segment.
496 split_spec = _version_split(normalized_spec)
498 # Split the prospective version out by bangs and dots, and pretend
499 # that there is an implicit dot in between a release segment and
500 # a pre-release segment.
501 split_prospective = _version_split(normalized_prospective)
503 # 0-pad the prospective version before shortening it to get the correct
504 # shortened version.
505 padded_prospective, _ = _pad_version(split_prospective, split_spec)
507 # Shorten the prospective version to be the same length as the spec
508 # so that we can determine if the specifier is a prefix of the
509 # prospective version or not.
510 shortened_prospective = padded_prospective[: len(split_spec)]
512 return shortened_prospective == split_spec
513 else:
514 # Convert our spec string into a Version
515 spec_version = self._require_spec_version(spec)
517 # If the specifier does not have a local segment, then we want to
518 # act as if the prospective version also does not have a local
519 # segment.
520 if not spec_version.local:
521 prospective = _public_version(prospective)
523 return prospective == spec_version
525 def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
526 return not self._compare_equal(prospective, spec)
528 def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
529 # NB: Local version identifiers are NOT permitted in the version
530 # specifier, so local version labels can be universally removed from
531 # the prospective version.
532 return _public_version(prospective) <= self._require_spec_version(spec)
534 def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
535 # NB: Local version identifiers are NOT permitted in the version
536 # specifier, so local version labels can be universally removed from
537 # the prospective version.
538 return _public_version(prospective) >= self._require_spec_version(spec)
540 def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
541 # Convert our spec to a Version instance, since we'll want to work with
542 # it as a version.
543 spec = self._require_spec_version(spec_str)
545 # Check to see if the prospective version is less than the spec
546 # version. If it's not we can short circuit and just return False now
547 # instead of doing extra unneeded work.
548 if not prospective < spec:
549 return False
551 # This special case is here so that, unless the specifier itself
552 # includes is a pre-release version, that we do not accept pre-release
553 # versions for the version mentioned in the specifier (e.g. <3.1 should
554 # not match 3.1.dev0, but should match 3.0.dev0).
555 if (
556 not spec.is_prerelease
557 and prospective.is_prerelease
558 and _base_version(prospective) == _base_version(spec)
559 ):
560 return False
562 # If we've gotten to here, it means that prospective version is both
563 # less than the spec version *and* it's not a pre-release of the same
564 # version in the spec.
565 return True
567 def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
568 # Convert our spec to a Version instance, since we'll want to work with
569 # it as a version.
570 spec = self._require_spec_version(spec_str)
572 # Check to see if the prospective version is greater than the spec
573 # version. If it's not we can short circuit and just return False now
574 # instead of doing extra unneeded work.
575 if not prospective > spec:
576 return False
578 # This special case is here so that, unless the specifier itself
579 # includes is a post-release version, that we do not accept
580 # post-release versions for the version mentioned in the specifier
581 # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
582 if (
583 not spec.is_postrelease
584 and prospective.is_postrelease
585 and _base_version(prospective) == _base_version(spec)
586 ):
587 return False
589 # Ensure that we do not allow a local version of the version mentioned
590 # in the specifier, which is technically greater than, to match.
591 if prospective.local is not None and _base_version(
592 prospective
593 ) == _base_version(spec):
594 return False
596 # If we've gotten to here, it means that prospective version is both
597 # greater than the spec version *and* it's not a pre-release of the
598 # same version in the spec.
599 return True
601 def _compare_arbitrary(self, prospective: Version | str, spec: str) -> bool:
602 return str(prospective).lower() == str(spec).lower()
604 def __contains__(self, item: str | Version) -> bool:
605 """Return whether or not the item is contained in this specifier.
607 :param item: The item to check for.
609 This is used for the ``in`` operator and behaves the same as
610 :meth:`contains` with no ``prereleases`` argument passed.
612 >>> "1.2.3" in Specifier(">=1.2.3")
613 True
614 >>> Version("1.2.3") in Specifier(">=1.2.3")
615 True
616 >>> "1.0.0" in Specifier(">=1.2.3")
617 False
618 >>> "1.3.0a1" in Specifier(">=1.2.3")
619 True
620 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
621 True
622 """
623 return self.contains(item)
625 def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
626 """Return whether or not the item is contained in this specifier.
628 :param item:
629 The item to check for, which can be a version string or a
630 :class:`Version` instance.
631 :param prereleases:
632 Whether or not to match prereleases with this Specifier. If set to
633 ``None`` (the default), it will follow the recommendation from
634 :pep:`440` and match prereleases, as there are no other versions.
636 >>> Specifier(">=1.2.3").contains("1.2.3")
637 True
638 >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
639 True
640 >>> Specifier(">=1.2.3").contains("1.0.0")
641 False
642 >>> Specifier(">=1.2.3").contains("1.3.0a1")
643 True
644 >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1")
645 False
646 >>> Specifier(">=1.2.3").contains("1.3.0a1")
647 True
648 """
650 return bool(list(self.filter([item], prereleases=prereleases)))
652 @typing.overload
653 def filter(
654 self,
655 iterable: Iterable[UnparsedVersionVar],
656 prereleases: bool | None = None,
657 key: None = ...,
658 ) -> Iterator[UnparsedVersionVar]: ...
660 @typing.overload
661 def filter(
662 self,
663 iterable: Iterable[T],
664 prereleases: bool | None = None,
665 key: Callable[[T], UnparsedVersion] = ...,
666 ) -> Iterator[T]: ...
668 def filter(
669 self,
670 iterable: Iterable[Any],
671 prereleases: bool | None = None,
672 key: Callable[[Any], UnparsedVersion] | None = None,
673 ) -> Iterator[Any]:
674 """Filter items in the given iterable, that match the specifier.
676 :param iterable:
677 An iterable that can contain version strings and :class:`Version` instances.
678 The items in the iterable will be filtered according to the specifier.
679 :param prereleases:
680 Whether or not to allow prereleases in the returned iterator. If set to
681 ``None`` (the default), it will follow the recommendation from :pep:`440`
682 and match prereleases if there are no other versions.
683 :param key:
684 A callable that takes a single argument (an item from the iterable) and
685 returns a version string or :class:`Version` instance to be used for
686 filtering.
688 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
689 ['1.3']
690 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
691 ['1.2.3', '1.3', <Version('1.4')>]
692 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
693 ['1.5a1']
694 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
695 ['1.3', '1.5a1']
696 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
697 ['1.3', '1.5a1']
698 >>> list(Specifier(">=1.2.3").filter(
699 ... [{"ver": "1.2"}, {"ver": "1.3"}],
700 ... key=lambda x: x["ver"]))
701 [{'ver': '1.3'}]
702 """
703 prereleases_versions = []
704 found_non_prereleases = False
706 # Determine if to include prereleases by default
707 include_prereleases = (
708 prereleases if prereleases is not None else self.prereleases
709 )
711 # Get the matching operator
712 operator_callable = self._get_operator(self.operator)
714 # Filter versions
715 for version in iterable:
716 parsed_version = _coerce_version(version if key is None else key(version))
717 if parsed_version is None:
718 # === operator can match arbitrary (non-version) strings
719 if self.operator == "===" and self._compare_arbitrary(
720 version, self.version
721 ):
722 yield version
723 elif operator_callable(parsed_version, self.version):
724 # If it's not a prerelease or prereleases are allowed, yield it directly
725 if not parsed_version.is_prerelease or include_prereleases:
726 found_non_prereleases = True
727 yield version
728 # Otherwise collect prereleases for potential later use
729 elif prereleases is None and self._prereleases is not False:
730 prereleases_versions.append(version)
732 # If no non-prereleases were found and prereleases weren't
733 # explicitly forbidden, yield the collected prereleases
734 if (
735 not found_non_prereleases
736 and prereleases is None
737 and self._prereleases is not False
738 ):
739 yield from prereleases_versions
742_prefix_regex = re.compile(r"([0-9]+)((?:a|b|c|rc)[0-9]+)")
745def _pep440_filter_prereleases(
746 iterable: Iterable[Any], key: Callable[[Any], UnparsedVersion] | None
747) -> Iterator[Any]:
748 """Filter per PEP 440: exclude prereleases unless no finals exist."""
749 # Two lists used:
750 # * all_nonfinal to preserve order if no finals exist
751 # * arbitrary_strings for streaming when first final found
752 all_nonfinal: list[Any] = []
753 arbitrary_strings: list[Any] = []
755 found_final = False
756 for item in iterable:
757 parsed = _coerce_version(item if key is None else key(item))
759 if parsed is None:
760 # Arbitrary strings are always included as it is not
761 # possible to determine if they are prereleases,
762 # and they have already passed all specifiers.
763 if found_final:
764 yield item
765 else:
766 arbitrary_strings.append(item)
767 all_nonfinal.append(item)
768 continue
770 if not parsed.is_prerelease:
771 # Final release found - flush arbitrary strings, then yield
772 if not found_final:
773 yield from arbitrary_strings
774 found_final = True
775 yield item
776 continue
778 # Prerelease - buffer if no finals yet, otherwise skip
779 if not found_final:
780 all_nonfinal.append(item)
782 # No finals found - yield all buffered items
783 if not found_final:
784 yield from all_nonfinal
787def _version_split(version: str) -> list[str]:
788 """Split version into components.
790 The split components are intended for version comparison. The logic does
791 not attempt to retain the original version string, so joining the
792 components back with :func:`_version_join` may not produce the original
793 version string.
794 """
795 result: list[str] = []
797 epoch, _, rest = version.rpartition("!")
798 result.append(epoch or "0")
800 for item in rest.split("."):
801 match = _prefix_regex.fullmatch(item)
802 if match:
803 result.extend(match.groups())
804 else:
805 result.append(item)
806 return result
809def _version_join(components: list[str]) -> str:
810 """Join split version components into a version string.
812 This function assumes the input came from :func:`_version_split`, where the
813 first component must be the epoch (either empty or numeric), and all other
814 components numeric.
815 """
816 epoch, *rest = components
817 return f"{epoch}!{'.'.join(rest)}"
820def _is_not_suffix(segment: str) -> bool:
821 return not any(
822 segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
823 )
826def _pad_version(left: list[str], right: list[str]) -> tuple[list[str], list[str]]:
827 left_split, right_split = [], []
829 # Get the release segment of our versions
830 left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
831 right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
833 # Get the rest of our versions
834 left_split.append(left[len(left_split[0]) :])
835 right_split.append(right[len(right_split[0]) :])
837 # Insert our padding
838 left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
839 right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
841 return (
842 list(itertools.chain.from_iterable(left_split)),
843 list(itertools.chain.from_iterable(right_split)),
844 )
847class SpecifierSet(BaseSpecifier):
848 """This class abstracts handling of a set of version specifiers.
850 It can be passed a single specifier (``>=3.0``), a comma-separated list of
851 specifiers (``>=3.0,!=3.1``), or no specifier at all.
852 """
854 __slots__ = ("_prereleases", "_specs")
856 def __init__(
857 self,
858 specifiers: str | Iterable[Specifier] = "",
859 prereleases: bool | None = None,
860 ) -> None:
861 """Initialize a SpecifierSet instance.
863 :param specifiers:
864 The string representation of a specifier or a comma-separated list of
865 specifiers which will be parsed and normalized before use.
866 May also be an iterable of ``Specifier`` instances, which will be used
867 as is.
868 :param prereleases:
869 This tells the SpecifierSet if it should accept prerelease versions if
870 applicable or not. The default of ``None`` will autodetect it from the
871 given specifiers.
873 :raises InvalidSpecifier:
874 If the given ``specifiers`` are not parseable than this exception will be
875 raised.
876 """
878 if isinstance(specifiers, str):
879 # Split on `,` to break each individual specifier into its own item, and
880 # strip each item to remove leading/trailing whitespace.
881 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
883 # Make each individual specifier a Specifier and save in a frozen set
884 # for later.
885 self._specs = frozenset(map(Specifier, split_specifiers))
886 else:
887 # Save the supplied specifiers in a frozen set.
888 self._specs = frozenset(specifiers)
890 # Store our prereleases value so we can use it later to determine if
891 # we accept prereleases or not.
892 self._prereleases = prereleases
894 @property
895 def prereleases(self) -> bool | None:
896 # If we have been given an explicit prerelease modifier, then we'll
897 # pass that through here.
898 if self._prereleases is not None:
899 return self._prereleases
901 # If we don't have any specifiers, and we don't have a forced value,
902 # then we'll just return None since we don't know if this should have
903 # pre-releases or not.
904 if not self._specs:
905 return None
907 # Otherwise we'll see if any of the given specifiers accept
908 # prereleases, if any of them do we'll return True, otherwise False.
909 if any(s.prereleases for s in self._specs):
910 return True
912 return None
914 @prereleases.setter
915 def prereleases(self, value: bool | None) -> None:
916 self._prereleases = value
918 def __repr__(self) -> str:
919 """A representation of the specifier set that shows all internal state.
921 Note that the ordering of the individual specifiers within the set may not
922 match the input string.
924 >>> SpecifierSet('>=1.0.0,!=2.0.0')
925 <SpecifierSet('!=2.0.0,>=1.0.0')>
926 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
927 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
928 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
929 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
930 """
931 pre = (
932 f", prereleases={self.prereleases!r}"
933 if self._prereleases is not None
934 else ""
935 )
937 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
939 def __str__(self) -> str:
940 """A string representation of the specifier set that can be round-tripped.
942 Note that the ordering of the individual specifiers within the set may not
943 match the input string.
945 >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
946 '!=1.0.1,>=1.0.0'
947 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
948 '!=1.0.1,>=1.0.0'
949 """
950 return ",".join(sorted(str(s) for s in self._specs))
952 def __hash__(self) -> int:
953 return hash(self._specs)
955 def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
956 """Return a SpecifierSet which is a combination of the two sets.
958 :param other: The other object to combine with.
960 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
961 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
962 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
963 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
964 """
965 if isinstance(other, str):
966 other = SpecifierSet(other)
967 elif not isinstance(other, SpecifierSet):
968 return NotImplemented
970 specifier = SpecifierSet()
971 specifier._specs = frozenset(self._specs | other._specs)
973 # Combine prerelease settings: use common or non-None value
974 if self._prereleases is None or self._prereleases == other._prereleases:
975 specifier._prereleases = other._prereleases
976 elif other._prereleases is None:
977 specifier._prereleases = self._prereleases
978 else:
979 raise ValueError(
980 "Cannot combine SpecifierSets with True and False prerelease overrides."
981 )
983 return specifier
985 def __eq__(self, other: object) -> bool:
986 """Whether or not the two SpecifierSet-like objects are equal.
988 :param other: The other object to check against.
990 The value of :attr:`prereleases` is ignored.
992 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
993 True
994 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
995 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
996 True
997 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
998 True
999 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
1000 False
1001 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
1002 False
1003 """
1004 if isinstance(other, (str, Specifier)):
1005 other = SpecifierSet(str(other))
1006 elif not isinstance(other, SpecifierSet):
1007 return NotImplemented
1009 return self._specs == other._specs
1011 def __len__(self) -> int:
1012 """Returns the number of specifiers in this specifier set."""
1013 return len(self._specs)
1015 def __iter__(self) -> Iterator[Specifier]:
1016 """
1017 Returns an iterator over all the underlying :class:`Specifier` instances
1018 in this specifier set.
1020 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
1021 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
1022 """
1023 return iter(self._specs)
1025 def __contains__(self, item: UnparsedVersion) -> bool:
1026 """Return whether or not the item is contained in this specifier.
1028 :param item: The item to check for.
1030 This is used for the ``in`` operator and behaves the same as
1031 :meth:`contains` with no ``prereleases`` argument passed.
1033 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
1034 True
1035 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
1036 True
1037 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
1038 False
1039 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
1040 True
1041 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
1042 True
1043 """
1044 return self.contains(item)
1046 def contains(
1047 self,
1048 item: UnparsedVersion,
1049 prereleases: bool | None = None,
1050 installed: bool | None = None,
1051 ) -> bool:
1052 """Return whether or not the item is contained in this SpecifierSet.
1054 :param item:
1055 The item to check for, which can be a version string or a
1056 :class:`Version` instance.
1057 :param prereleases:
1058 Whether or not to match prereleases with this SpecifierSet. If set to
1059 ``None`` (the default), it will follow the recommendation from :pep:`440`
1060 and match prereleases, as there are no other versions.
1061 :param installed:
1062 Whether or not the item is installed. If set to ``True``, it will
1063 accept prerelease versions even if the specifier does not allow them.
1065 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
1066 True
1067 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
1068 True
1069 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
1070 False
1071 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
1072 True
1073 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")
1074 False
1075 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
1076 True
1077 """
1078 version = _coerce_version(item)
1080 if version is not None and installed and version.is_prerelease:
1081 prereleases = True
1083 check_item = item if version is None else version
1084 return bool(list(self.filter([check_item], prereleases=prereleases)))
1086 @typing.overload
1087 def filter(
1088 self,
1089 iterable: Iterable[UnparsedVersionVar],
1090 prereleases: bool | None = None,
1091 key: None = ...,
1092 ) -> Iterator[UnparsedVersionVar]: ...
1094 @typing.overload
1095 def filter(
1096 self,
1097 iterable: Iterable[T],
1098 prereleases: bool | None = None,
1099 key: Callable[[T], UnparsedVersion] = ...,
1100 ) -> Iterator[T]: ...
1102 def filter(
1103 self,
1104 iterable: Iterable[Any],
1105 prereleases: bool | None = None,
1106 key: Callable[[Any], UnparsedVersion] | None = None,
1107 ) -> Iterator[Any]:
1108 """Filter items in the given iterable, that match the specifiers in this set.
1110 :param iterable:
1111 An iterable that can contain version strings and :class:`Version` instances.
1112 The items in the iterable will be filtered according to the specifier.
1113 :param prereleases:
1114 Whether or not to allow prereleases in the returned iterator. If set to
1115 ``None`` (the default), it will follow the recommendation from :pep:`440`
1116 and match prereleases if there are no other versions.
1117 :param key:
1118 A callable that takes a single argument (an item from the iterable) and
1119 returns a version string or :class:`Version` instance to be used for
1120 filtering.
1122 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
1123 ['1.3']
1124 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
1125 ['1.3', <Version('1.4')>]
1126 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
1127 ['1.5a1']
1128 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
1129 ['1.3', '1.5a1']
1130 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
1131 ['1.3', '1.5a1']
1132 >>> list(SpecifierSet(">=1.2.3").filter(
1133 ... [{"ver": "1.2"}, {"ver": "1.3"}],
1134 ... key=lambda x: x["ver"]))
1135 [{'ver': '1.3'}]
1137 An "empty" SpecifierSet will filter items based on the presence of prerelease
1138 versions in the set.
1140 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
1141 ['1.3']
1142 >>> list(SpecifierSet("").filter(["1.5a1"]))
1143 ['1.5a1']
1144 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
1145 ['1.3', '1.5a1']
1146 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
1147 ['1.3', '1.5a1']
1148 """
1149 # Determine if we're forcing a prerelease or not, if we're not forcing
1150 # one for this particular filter call, then we'll use whatever the
1151 # SpecifierSet thinks for whether or not we should support prereleases.
1152 if prereleases is None and self.prereleases is not None:
1153 prereleases = self.prereleases
1155 # If we have any specifiers, then we want to wrap our iterable in the
1156 # filter method for each one, this will act as a logical AND amongst
1157 # each specifier.
1158 if self._specs:
1159 # When prereleases is None, we need to let all versions through
1160 # the individual filters, then decide about prereleases at the end
1161 # based on whether any non-prereleases matched ALL specs.
1162 for spec in self._specs:
1163 iterable = spec.filter(
1164 iterable,
1165 prereleases=True if prereleases is None else prereleases,
1166 key=key,
1167 )
1169 if prereleases is not None:
1170 # If we have a forced prereleases value,
1171 # we can immediately return the iterator.
1172 return iter(iterable)
1173 else:
1174 # Handle empty SpecifierSet cases where prereleases is not None.
1175 if prereleases is True:
1176 return iter(iterable)
1178 if prereleases is False:
1179 return (
1180 item
1181 for item in iterable
1182 if (version := _coerce_version(item)) is None
1183 or not version.is_prerelease
1184 )
1186 # PEP 440: exclude prereleases unless no final releases matched
1187 return _pep440_filter_prereleases(iterable, key)