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 _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", "_wildcard_split")
172 _specifier_regex_str = r"""
173 (?:
174 (?:
175 # The identity operators allow for an escape hatch that will
176 # do an exact string match of the version you wish to install.
177 # This will not be parsed by PEP 440 and we cannot determine
178 # any semantic meaning from it. This operator is discouraged
179 # but included entirely as an escape hatch.
180 === # Only match for the identity operator
181 \s*
182 [^\s;)]* # The arbitrary version can be just about anything,
183 # we match everything except for whitespace, a
184 # semi-colon for marker support, and a closing paren
185 # since versions can be enclosed in them.
186 )
187 |
188 (?:
189 # The (non)equality operators allow for wild card and local
190 # versions to be specified so we have to define these two
191 # operators separately to enable that.
192 (?:==|!=) # Only match for equals and not equals
194 \s*
195 v?
196 (?:[0-9]+!)? # epoch
197 [0-9]+(?:\.[0-9]+)* # release
199 # You cannot use a wild card and a pre-release, post-release, a dev or
200 # local version together so group them with a | and make them optional.
201 (?:
202 \.\* # Wild card syntax of .*
203 |
204 (?a: # pre release
205 [-_\.]?
206 (alpha|beta|preview|pre|a|b|c|rc)
207 [-_\.]?
208 [0-9]*
209 )?
210 (?a: # post release
211 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
212 )?
213 (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
214 (?a:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
215 )?
216 )
217 |
218 (?:
219 # The compatible operator requires at least two digits in the
220 # release segment.
221 (?:~=) # Only match for the compatible operator
223 \s*
224 v?
225 (?:[0-9]+!)? # epoch
226 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
227 (?: # pre release
228 [-_\.]?
229 (alpha|beta|preview|pre|a|b|c|rc)
230 [-_\.]?
231 [0-9]*
232 )?
233 (?: # post release
234 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
235 )?
236 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
237 )
238 |
239 (?:
240 # All other operators only allow a sub set of what the
241 # (non)equality operators do. Specifically they do not allow
242 # local versions to be specified nor do they allow the prefix
243 # matching wild cards.
244 (?:<=|>=|<|>)
246 \s*
247 v?
248 (?:[0-9]+!)? # epoch
249 [0-9]+(?:\.[0-9]+)* # release
250 (?a: # pre release
251 [-_\.]?
252 (alpha|beta|preview|pre|a|b|c|rc)
253 [-_\.]?
254 [0-9]*
255 )?
256 (?a: # post release
257 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
258 )?
259 (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
260 )
261 )
262 """
264 _regex = re.compile(
265 r"\s*" + _specifier_regex_str + r"\s*", re.VERBOSE | re.IGNORECASE
266 )
268 _operators: Final = {
269 "~=": "compatible",
270 "==": "equal",
271 "!=": "not_equal",
272 "<=": "less_than_equal",
273 ">=": "greater_than_equal",
274 "<": "less_than",
275 ">": "greater_than",
276 "===": "arbitrary",
277 }
279 def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
280 """Initialize a Specifier instance.
282 :param spec:
283 The string representation of a specifier which will be parsed and
284 normalized before use.
285 :param prereleases:
286 This tells the specifier if it should accept prerelease versions if
287 applicable or not. The default of ``None`` will autodetect it from the
288 given specifiers.
289 :raises InvalidSpecifier:
290 If the given specifier is invalid (i.e. bad syntax).
291 """
292 if not self._regex.fullmatch(spec):
293 raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
295 spec = spec.strip()
296 if spec.startswith("==="):
297 operator, version = spec[:3], spec[3:].strip()
298 elif spec.startswith(("~=", "==", "!=", "<=", ">=")):
299 operator, version = spec[:2], spec[2:].strip()
300 else:
301 operator, version = spec[:1], spec[1:].strip()
303 self._spec: tuple[str, str] = (operator, version)
305 # Store whether or not this Specifier should accept prereleases
306 self._prereleases = prereleases
308 # Specifier version cache
309 self._spec_version: tuple[str, Version] | None = None
311 # Populated on first wildcard (==X.*) comparison
312 self._wildcard_split: tuple[list[str], int] | None = None
314 def _get_spec_version(self, version: str) -> Version | None:
315 """One element cache, as only one spec Version is needed per Specifier."""
316 if self._spec_version is not None and self._spec_version[0] == version:
317 return self._spec_version[1]
319 version_specifier = _coerce_version(version)
320 if version_specifier is None:
321 return None
323 self._spec_version = (version, version_specifier)
324 return version_specifier
326 def _require_spec_version(self, version: str) -> Version:
327 """Get spec version, asserting it's valid (not for === operator).
329 This method should only be called for operators where version
330 strings are guaranteed to be valid PEP 440 versions (not ===).
331 """
332 spec_version = self._get_spec_version(version)
333 assert spec_version is not None
334 return spec_version
336 @property
337 def prereleases(self) -> bool | None:
338 # If there is an explicit prereleases set for this, then we'll just
339 # blindly use that.
340 if self._prereleases is not None:
341 return self._prereleases
343 # Only the "!=" operator does not imply prereleases when
344 # the version in the specifier is a prerelease.
345 operator, version_str = self._spec
346 if operator == "!=":
347 return False
349 # The == specifier with trailing .* cannot include prereleases
350 # e.g. "==1.0a1.*" is not valid.
351 if operator == "==" and version_str.endswith(".*"):
352 return False
354 # "===" can have arbitrary string versions, so we cannot parse
355 # those, we take prereleases as unknown (None) for those.
356 version = self._get_spec_version(version_str)
357 if version is None:
358 return None
360 # For all other operators, use the check if spec Version
361 # object implies pre-releases.
362 return version.is_prerelease
364 @prereleases.setter
365 def prereleases(self, value: bool | None) -> None:
366 self._prereleases = value
368 @property
369 def operator(self) -> str:
370 """The operator of this specifier.
372 >>> Specifier("==1.2.3").operator
373 '=='
374 """
375 return self._spec[0]
377 @property
378 def version(self) -> str:
379 """The version of this specifier.
381 >>> Specifier("==1.2.3").version
382 '1.2.3'
383 """
384 return self._spec[1]
386 def __repr__(self) -> str:
387 """A representation of the Specifier that shows all internal state.
389 >>> Specifier('>=1.0.0')
390 <Specifier('>=1.0.0')>
391 >>> Specifier('>=1.0.0', prereleases=False)
392 <Specifier('>=1.0.0', prereleases=False)>
393 >>> Specifier('>=1.0.0', prereleases=True)
394 <Specifier('>=1.0.0', prereleases=True)>
395 """
396 pre = (
397 f", prereleases={self.prereleases!r}"
398 if self._prereleases is not None
399 else ""
400 )
402 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
404 def __str__(self) -> str:
405 """A string representation of the Specifier that can be round-tripped.
407 >>> str(Specifier('>=1.0.0'))
408 '>=1.0.0'
409 >>> str(Specifier('>=1.0.0', prereleases=False))
410 '>=1.0.0'
411 """
412 return "{}{}".format(*self._spec)
414 @property
415 def _canonical_spec(self) -> tuple[str, str]:
416 operator, version = self._spec
417 if operator == "===" or version.endswith(".*"):
418 return operator, version
420 spec_version = self._require_spec_version(version)
422 canonical_version = canonicalize_version(
423 spec_version, strip_trailing_zero=(operator != "~=")
424 )
426 return operator, canonical_version
428 def __hash__(self) -> int:
429 return hash(self._canonical_spec)
431 def __eq__(self, other: object) -> bool:
432 """Whether or not the two Specifier-like objects are equal.
434 :param other: The other object to check against.
436 The value of :attr:`prereleases` is ignored.
438 >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
439 True
440 >>> (Specifier("==1.2.3", prereleases=False) ==
441 ... Specifier("==1.2.3", prereleases=True))
442 True
443 >>> Specifier("==1.2.3") == "==1.2.3"
444 True
445 >>> Specifier("==1.2.3") == Specifier("==1.2.4")
446 False
447 >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
448 False
449 """
450 if isinstance(other, str):
451 try:
452 other = self.__class__(str(other))
453 except InvalidSpecifier:
454 return NotImplemented
455 elif not isinstance(other, self.__class__):
456 return NotImplemented
458 return self._canonical_spec == other._canonical_spec
460 def _get_operator(self, op: str) -> CallableOperator:
461 operator_callable: CallableOperator = getattr(
462 self, f"_compare_{self._operators[op]}"
463 )
464 return operator_callable
466 def _compare_compatible(self, prospective: Version, spec: str) -> bool:
467 # Compatible releases have an equivalent combination of >= and ==. That
468 # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
469 # implement this in terms of the other specifiers instead of
470 # implementing it ourselves. The only thing we need to do is construct
471 # the other specifiers.
473 # We want everything but the last item in the version, but we want to
474 # ignore suffix segments.
475 prefix = _version_join(
476 list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
477 )
479 # Add the prefix notation to the end of our string
480 prefix += ".*"
482 return (self._compare_greater_than_equal(prospective, spec)) and (
483 self._compare_equal(prospective, prefix)
484 )
486 def _get_wildcard_split(self, spec: str) -> tuple[list[str], int]:
487 """Cached split of a wildcard spec into components and numeric length.
489 >>> Specifier("==1.*")._get_wildcard_split("1.*")
490 (['0', '1'], 2)
491 >>> Specifier("==3.10.*")._get_wildcard_split("3.10.*")
492 (['0', '3', '10'], 3)
493 """
494 wildcard_split = self._wildcard_split
495 if wildcard_split is None:
496 normalized = canonicalize_version(spec[:-2], strip_trailing_zero=False)
497 split_spec = _version_split(normalized)
498 wildcard_split = (split_spec, _numeric_prefix_len(split_spec))
499 self._wildcard_split = wildcard_split
500 return wildcard_split
502 def _compare_equal(self, prospective: Version, spec: str) -> bool:
503 # We need special logic to handle prefix matching
504 if spec.endswith(".*"):
505 split_spec, spec_numeric_len = self._get_wildcard_split(spec)
507 # In the case of prefix matching we want to ignore local segment.
508 normalized_prospective = canonicalize_version(
509 _public_version(prospective), strip_trailing_zero=False
510 )
511 # Split the prospective version out by bangs and dots, and pretend
512 # that there is an implicit dot in between a release segment and
513 # a pre-release segment.
514 split_prospective = _version_split(normalized_prospective)
516 # 0-pad the prospective version before shortening it to get the correct
517 # shortened version.
518 padded_prospective = _left_pad(split_prospective, spec_numeric_len)
520 # Shorten the prospective version to be the same length as the spec
521 # so that we can determine if the specifier is a prefix of the
522 # prospective version or not.
523 shortened_prospective = padded_prospective[: len(split_spec)]
525 return shortened_prospective == split_spec
526 else:
527 # Convert our spec string into a Version
528 spec_version = self._require_spec_version(spec)
530 # If the specifier does not have a local segment, then we want to
531 # act as if the prospective version also does not have a local
532 # segment.
533 if not spec_version.local:
534 prospective = _public_version(prospective)
536 return prospective == spec_version
538 def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
539 return not self._compare_equal(prospective, spec)
541 def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
542 # NB: Local version identifiers are NOT permitted in the version
543 # specifier, so local version labels can be universally removed from
544 # the prospective version.
545 return _public_version(prospective) <= self._require_spec_version(spec)
547 def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
548 # NB: Local version identifiers are NOT permitted in the version
549 # specifier, so local version labels can be universally removed from
550 # the prospective version.
551 return _public_version(prospective) >= self._require_spec_version(spec)
553 def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
554 # Convert our spec to a Version instance, since we'll want to work with
555 # it as a version.
556 spec = self._require_spec_version(spec_str)
558 # Check to see if the prospective version is less than the spec
559 # version. If it's not we can short circuit and just return False now
560 # instead of doing extra unneeded work.
561 if not prospective < spec:
562 return False
564 # This special case is here so that, unless the specifier itself
565 # includes is a pre-release version, that we do not accept pre-release
566 # versions for the version mentioned in the specifier (e.g. <3.1 should
567 # not match 3.1.dev0, but should match 3.0.dev0).
568 if (
569 not spec.is_prerelease
570 and prospective.is_prerelease
571 and _base_version(prospective) == _base_version(spec)
572 ):
573 return False
575 # If we've gotten to here, it means that prospective version is both
576 # less than the spec version *and* it's not a pre-release of the same
577 # version in the spec.
578 return True
580 def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
581 # Convert our spec to a Version instance, since we'll want to work with
582 # it as a version.
583 spec = self._require_spec_version(spec_str)
585 # Check to see if the prospective version is greater than the spec
586 # version. If it's not we can short circuit and just return False now
587 # instead of doing extra unneeded work.
588 if not prospective > spec:
589 return False
591 # This special case is here so that, unless the specifier itself
592 # includes is a post-release version, that we do not accept
593 # post-release versions for the version mentioned in the specifier
594 # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
595 if (
596 not spec.is_postrelease
597 and prospective.is_postrelease
598 and _base_version(prospective) == _base_version(spec)
599 ):
600 return False
602 # Per the spec: ">V MUST NOT match a local version of the specified
603 # version". A "local version of V" is any version whose public part
604 # equals V. So >1.0a1 must not match 1.0a1+local, but must still
605 # match 1.0a2+local.
606 if prospective.local is not None and _public_version(prospective) == spec:
607 return False
609 # If we've gotten to here, it means that prospective version is both
610 # greater than the spec version *and* it's not a pre-release of the
611 # same version in the spec.
612 return True
614 def _compare_arbitrary(self, prospective: Version | str, spec: str) -> bool:
615 return str(prospective).lower() == str(spec).lower()
617 def __contains__(self, item: str | Version) -> bool:
618 """Return whether or not the item is contained in this specifier.
620 :param item: The item to check for.
622 This is used for the ``in`` operator and behaves the same as
623 :meth:`contains` with no ``prereleases`` argument passed.
625 >>> "1.2.3" in Specifier(">=1.2.3")
626 True
627 >>> Version("1.2.3") in Specifier(">=1.2.3")
628 True
629 >>> "1.0.0" in Specifier(">=1.2.3")
630 False
631 >>> "1.3.0a1" in Specifier(">=1.2.3")
632 True
633 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
634 True
635 """
636 return self.contains(item)
638 def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
639 """Return whether or not the item is contained in this specifier.
641 :param item:
642 The item to check for, which can be a version string or a
643 :class:`Version` instance.
644 :param prereleases:
645 Whether or not to match prereleases with this Specifier. If set to
646 ``None`` (the default), it will follow the recommendation from
647 :pep:`440` and match prereleases, as there are no other versions.
649 >>> Specifier(">=1.2.3").contains("1.2.3")
650 True
651 >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
652 True
653 >>> Specifier(">=1.2.3").contains("1.0.0")
654 False
655 >>> Specifier(">=1.2.3").contains("1.3.0a1")
656 True
657 >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1")
658 False
659 >>> Specifier(">=1.2.3").contains("1.3.0a1")
660 True
661 """
663 return bool(list(self.filter([item], prereleases=prereleases)))
665 @typing.overload
666 def filter(
667 self,
668 iterable: Iterable[UnparsedVersionVar],
669 prereleases: bool | None = None,
670 key: None = ...,
671 ) -> Iterator[UnparsedVersionVar]: ...
673 @typing.overload
674 def filter(
675 self,
676 iterable: Iterable[T],
677 prereleases: bool | None = None,
678 key: Callable[[T], UnparsedVersion] = ...,
679 ) -> Iterator[T]: ...
681 def filter(
682 self,
683 iterable: Iterable[Any],
684 prereleases: bool | None = None,
685 key: Callable[[Any], UnparsedVersion] | None = None,
686 ) -> Iterator[Any]:
687 """Filter items in the given iterable, that match the specifier.
689 :param iterable:
690 An iterable that can contain version strings and :class:`Version` instances.
691 The items in the iterable will be filtered according to the specifier.
692 :param prereleases:
693 Whether or not to allow prereleases in the returned iterator. If set to
694 ``None`` (the default), it will follow the recommendation from :pep:`440`
695 and match prereleases if there are no other versions.
696 :param key:
697 A callable that takes a single argument (an item from the iterable) and
698 returns a version string or :class:`Version` instance to be used for
699 filtering.
701 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
702 ['1.3']
703 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
704 ['1.2.3', '1.3', <Version('1.4')>]
705 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
706 ['1.5a1']
707 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
708 ['1.3', '1.5a1']
709 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
710 ['1.3', '1.5a1']
711 >>> list(Specifier(">=1.2.3").filter(
712 ... [{"ver": "1.2"}, {"ver": "1.3"}],
713 ... key=lambda x: x["ver"]))
714 [{'ver': '1.3'}]
715 """
716 prereleases_versions = []
717 found_non_prereleases = False
719 # Determine if to include prereleases by default
720 include_prereleases = (
721 prereleases if prereleases is not None else self.prereleases
722 )
724 # Get the matching operator
725 operator_callable = self._get_operator(self.operator)
727 # Filter versions
728 for version in iterable:
729 parsed_version = _coerce_version(version if key is None else key(version))
730 match = False
731 if parsed_version is None:
732 # === operator can match arbitrary (non-version) strings
733 if self.operator == "===" and self._compare_arbitrary(
734 version, self.version
735 ):
736 yield version
737 elif self.operator == "===":
738 match = self._compare_arbitrary(
739 version if key is None else key(version), self.version
740 )
741 else:
742 match = operator_callable(parsed_version, self.version)
744 if match and parsed_version is not None:
745 # If it's not a prerelease or prereleases are allowed, yield it directly
746 if not parsed_version.is_prerelease or include_prereleases:
747 found_non_prereleases = True
748 yield version
749 # Otherwise collect prereleases for potential later use
750 elif prereleases is None and self._prereleases is not False:
751 prereleases_versions.append(version)
753 # If no non-prereleases were found and prereleases weren't
754 # explicitly forbidden, yield the collected prereleases
755 if (
756 not found_non_prereleases
757 and prereleases is None
758 and self._prereleases is not False
759 ):
760 yield from prereleases_versions
763_prefix_regex = re.compile(r"([0-9]+)((?:a|b|c|rc)[0-9]+)")
766def _pep440_filter_prereleases(
767 iterable: Iterable[Any], key: Callable[[Any], UnparsedVersion] | None
768) -> Iterator[Any]:
769 """Filter per PEP 440: exclude prereleases unless no finals exist."""
770 # Two lists used:
771 # * all_nonfinal to preserve order if no finals exist
772 # * arbitrary_strings for streaming when first final found
773 all_nonfinal: list[Any] = []
774 arbitrary_strings: list[Any] = []
776 found_final = False
777 for item in iterable:
778 parsed = _coerce_version(item if key is None else key(item))
780 if parsed is None:
781 # Arbitrary strings are always included as it is not
782 # possible to determine if they are prereleases,
783 # and they have already passed all specifiers.
784 if found_final:
785 yield item
786 else:
787 arbitrary_strings.append(item)
788 all_nonfinal.append(item)
789 continue
791 if not parsed.is_prerelease:
792 # Final release found - flush arbitrary strings, then yield
793 if not found_final:
794 yield from arbitrary_strings
795 found_final = True
796 yield item
797 continue
799 # Prerelease - buffer if no finals yet, otherwise skip
800 if not found_final:
801 all_nonfinal.append(item)
803 # No finals found - yield all buffered items
804 if not found_final:
805 yield from all_nonfinal
808def _version_split(version: str) -> list[str]:
809 """Split version into components.
811 The split components are intended for version comparison. The logic does
812 not attempt to retain the original version string, so joining the
813 components back with :func:`_version_join` may not produce the original
814 version string.
815 """
816 result: list[str] = []
818 epoch, _, rest = version.rpartition("!")
819 result.append(epoch or "0")
821 for item in rest.split("."):
822 match = _prefix_regex.fullmatch(item)
823 if match:
824 result.extend(match.groups())
825 else:
826 result.append(item)
827 return result
830def _version_join(components: list[str]) -> str:
831 """Join split version components into a version string.
833 This function assumes the input came from :func:`_version_split`, where the
834 first component must be the epoch (either empty or numeric), and all other
835 components numeric.
836 """
837 epoch, *rest = components
838 return f"{epoch}!{'.'.join(rest)}"
841def _is_not_suffix(segment: str) -> bool:
842 return not any(
843 segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
844 )
847def _numeric_prefix_len(split: list[str]) -> int:
848 """Count leading numeric components in a :func:`_version_split` result.
850 >>> _numeric_prefix_len(["0", "1", "2", "a1"])
851 3
852 """
853 count = 0
854 for segment in split:
855 if not segment.isdigit():
856 break
857 count += 1
858 return count
861def _left_pad(split: list[str], target_numeric_len: int) -> list[str]:
862 """Pad a :func:`_version_split` result with ``"0"`` segments to reach
863 ``target_numeric_len`` numeric components. Suffix segments are preserved.
865 >>> _left_pad(["0", "1", "a1"], 4)
866 ['0', '1', '0', '0', 'a1']
867 """
868 numeric_len = _numeric_prefix_len(split)
869 pad_needed = target_numeric_len - numeric_len
870 if pad_needed <= 0:
871 return split
872 return [*split[:numeric_len], *(["0"] * pad_needed), *split[numeric_len:]]
875def _operator_cost(op_entry: tuple[CallableOperator, str, str]) -> int:
876 """Sort key for Cost Based Ordering of specifier operators in _filter_versions.
878 Operators run sequentially on a shrinking candidate set, so operators that
879 reject the most versions should run first to minimize work for later ones.
881 Tier 0: Exact equality (==, ===), likely to narrow candidates to one version
882 Tier 1: Range checks (>=, <=, >, <), cheap and usually reject a large portion
883 Tier 2: Wildcard equality (==.*) and compatible release (~=), more expensive
884 Tier 3: Exact !=, cheap but rarely rejects
885 Tier 4: Wildcard !=.*, expensive and rarely rejects
886 """
887 _, ver, op = op_entry
888 if op == "==":
889 return 0 if not ver.endswith(".*") else 2
890 if op in (">=", "<=", ">", "<"):
891 return 1
892 if op == "~=":
893 return 2
894 if op == "!=":
895 return 3 if not ver.endswith(".*") else 4
896 if op == "===":
897 return 0
899 raise ValueError(f"Unknown operator: {op!r}") # pragma: no cover
902class SpecifierSet(BaseSpecifier):
903 """This class abstracts handling of a set of version specifiers.
905 It can be passed a single specifier (``>=3.0``), a comma-separated list of
906 specifiers (``>=3.0,!=3.1``), or no specifier at all.
907 """
909 __slots__ = (
910 "_canonicalized",
911 "_has_arbitrary",
912 "_prereleases",
913 "_resolved_ops",
914 "_specs",
915 )
917 def __init__(
918 self,
919 specifiers: str | Iterable[Specifier] = "",
920 prereleases: bool | None = None,
921 ) -> None:
922 """Initialize a SpecifierSet instance.
924 :param specifiers:
925 The string representation of a specifier or a comma-separated list of
926 specifiers which will be parsed and normalized before use.
927 May also be an iterable of ``Specifier`` instances, which will be used
928 as is.
929 :param prereleases:
930 This tells the SpecifierSet if it should accept prerelease versions if
931 applicable or not. The default of ``None`` will autodetect it from the
932 given specifiers.
934 :raises InvalidSpecifier:
935 If the given ``specifiers`` are not parseable than this exception will be
936 raised.
937 """
939 if isinstance(specifiers, str):
940 # Split on `,` to break each individual specifier into its own item, and
941 # strip each item to remove leading/trailing whitespace.
942 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
944 self._specs: tuple[Specifier, ...] = tuple(map(Specifier, split_specifiers))
945 # Fast substring check; avoids iterating parsed specs.
946 self._has_arbitrary = "===" in specifiers
947 else:
948 self._specs = tuple(specifiers)
949 # Substring check works for both Specifier objects and plain
950 # strings (setuptools passes lists of strings).
951 self._has_arbitrary = any("===" in str(s) for s in self._specs)
953 self._canonicalized = len(self._specs) <= 1
954 self._resolved_ops: list[tuple[CallableOperator, str, str]] | None = None
956 # Store our prereleases value so we can use it later to determine if
957 # we accept prereleases or not.
958 self._prereleases = prereleases
960 def _canonical_specs(self) -> tuple[Specifier, ...]:
961 """Deduplicate, sort, and cache specs for order-sensitive operations."""
962 if not self._canonicalized:
963 self._specs = tuple(dict.fromkeys(sorted(self._specs, key=str)))
964 self._canonicalized = True
965 self._resolved_ops = None
966 return self._specs
968 @property
969 def prereleases(self) -> bool | None:
970 # If we have been given an explicit prerelease modifier, then we'll
971 # pass that through here.
972 if self._prereleases is not None:
973 return self._prereleases
975 # If we don't have any specifiers, and we don't have a forced value,
976 # then we'll just return None since we don't know if this should have
977 # pre-releases or not.
978 if not self._specs:
979 return None
981 # Otherwise we'll see if any of the given specifiers accept
982 # prereleases, if any of them do we'll return True, otherwise False.
983 if any(s.prereleases for s in self._specs):
984 return True
986 return None
988 @prereleases.setter
989 def prereleases(self, value: bool | None) -> None:
990 self._prereleases = value
992 def __repr__(self) -> str:
993 """A representation of the specifier set that shows all internal state.
995 Note that the ordering of the individual specifiers within the set may not
996 match the input string.
998 >>> SpecifierSet('>=1.0.0,!=2.0.0')
999 <SpecifierSet('!=2.0.0,>=1.0.0')>
1000 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
1001 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
1002 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
1003 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
1004 """
1005 pre = (
1006 f", prereleases={self.prereleases!r}"
1007 if self._prereleases is not None
1008 else ""
1009 )
1011 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
1013 def __str__(self) -> str:
1014 """A string representation of the specifier set that can be round-tripped.
1016 Note that the ordering of the individual specifiers within the set may not
1017 match the input string.
1019 >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
1020 '!=1.0.1,>=1.0.0'
1021 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
1022 '!=1.0.1,>=1.0.0'
1023 """
1024 return ",".join(str(s) for s in self._canonical_specs())
1026 def __hash__(self) -> int:
1027 return hash(self._canonical_specs())
1029 def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
1030 """Return a SpecifierSet which is a combination of the two sets.
1032 :param other: The other object to combine with.
1034 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
1035 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
1036 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
1037 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
1038 """
1039 if isinstance(other, str):
1040 other = SpecifierSet(other)
1041 elif not isinstance(other, SpecifierSet):
1042 return NotImplemented
1044 specifier = SpecifierSet()
1045 specifier._specs = self._specs + other._specs
1046 specifier._canonicalized = len(specifier._specs) <= 1
1047 specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary
1048 specifier._resolved_ops = None
1050 # Combine prerelease settings: use common or non-None value
1051 if self._prereleases is None or self._prereleases == other._prereleases:
1052 specifier._prereleases = other._prereleases
1053 elif other._prereleases is None:
1054 specifier._prereleases = self._prereleases
1055 else:
1056 raise ValueError(
1057 "Cannot combine SpecifierSets with True and False prerelease overrides."
1058 )
1060 return specifier
1062 def __eq__(self, other: object) -> bool:
1063 """Whether or not the two SpecifierSet-like objects are equal.
1065 :param other: The other object to check against.
1067 The value of :attr:`prereleases` is ignored.
1069 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
1070 True
1071 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
1072 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
1073 True
1074 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
1075 True
1076 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
1077 False
1078 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
1079 False
1080 """
1081 if isinstance(other, (str, Specifier)):
1082 other = SpecifierSet(str(other))
1083 elif not isinstance(other, SpecifierSet):
1084 return NotImplemented
1086 return self._canonical_specs() == other._canonical_specs()
1088 def __len__(self) -> int:
1089 """Returns the number of specifiers in this specifier set."""
1090 return len(self._specs)
1092 def __iter__(self) -> Iterator[Specifier]:
1093 """
1094 Returns an iterator over all the underlying :class:`Specifier` instances
1095 in this specifier set.
1097 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
1098 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
1099 """
1100 return iter(self._specs)
1102 def __contains__(self, item: UnparsedVersion) -> bool:
1103 """Return whether or not the item is contained in this specifier.
1105 :param item: The item to check for.
1107 This is used for the ``in`` operator and behaves the same as
1108 :meth:`contains` with no ``prereleases`` argument passed.
1110 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
1111 True
1112 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
1113 True
1114 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
1115 False
1116 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
1117 True
1118 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
1119 True
1120 """
1121 return self.contains(item)
1123 def contains(
1124 self,
1125 item: UnparsedVersion,
1126 prereleases: bool | None = None,
1127 installed: bool | None = None,
1128 ) -> bool:
1129 """Return whether or not the item is contained in this SpecifierSet.
1131 :param item:
1132 The item to check for, which can be a version string or a
1133 :class:`Version` instance.
1134 :param prereleases:
1135 Whether or not to match prereleases with this SpecifierSet. If set to
1136 ``None`` (the default), it will follow the recommendation from :pep:`440`
1137 and match prereleases, as there are no other versions.
1138 :param installed:
1139 Whether or not the item is installed. If set to ``True``, it will
1140 accept prerelease versions even if the specifier does not allow them.
1142 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
1143 True
1144 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
1145 True
1146 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
1147 False
1148 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
1149 True
1150 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")
1151 False
1152 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
1153 True
1154 """
1155 version = _coerce_version(item)
1157 if version is not None and installed and version.is_prerelease:
1158 prereleases = True
1160 # When item is a string and === is involved, keep it as-is
1161 # so the comparison isn't done against the normalized form.
1162 if version is None or (self._has_arbitrary and not isinstance(item, Version)):
1163 check_item = item
1164 else:
1165 check_item = version
1166 return bool(list(self.filter([check_item], prereleases=prereleases)))
1168 @typing.overload
1169 def filter(
1170 self,
1171 iterable: Iterable[UnparsedVersionVar],
1172 prereleases: bool | None = None,
1173 key: None = ...,
1174 ) -> Iterator[UnparsedVersionVar]: ...
1176 @typing.overload
1177 def filter(
1178 self,
1179 iterable: Iterable[T],
1180 prereleases: bool | None = None,
1181 key: Callable[[T], UnparsedVersion] = ...,
1182 ) -> Iterator[T]: ...
1184 def filter(
1185 self,
1186 iterable: Iterable[Any],
1187 prereleases: bool | None = None,
1188 key: Callable[[Any], UnparsedVersion] | None = None,
1189 ) -> Iterator[Any]:
1190 """Filter items in the given iterable, that match the specifiers in this set.
1192 :param iterable:
1193 An iterable that can contain version strings and :class:`Version` instances.
1194 The items in the iterable will be filtered according to the specifier.
1195 :param prereleases:
1196 Whether or not to allow prereleases in the returned iterator. If set to
1197 ``None`` (the default), it will follow the recommendation from :pep:`440`
1198 and match prereleases if there are no other versions.
1199 :param key:
1200 A callable that takes a single argument (an item from the iterable) and
1201 returns a version string or :class:`Version` instance to be used for
1202 filtering.
1204 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
1205 ['1.3']
1206 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
1207 ['1.3', <Version('1.4')>]
1208 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
1209 ['1.5a1']
1210 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
1211 ['1.3', '1.5a1']
1212 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
1213 ['1.3', '1.5a1']
1214 >>> list(SpecifierSet(">=1.2.3").filter(
1215 ... [{"ver": "1.2"}, {"ver": "1.3"}],
1216 ... key=lambda x: x["ver"]))
1217 [{'ver': '1.3'}]
1219 An "empty" SpecifierSet will filter items based on the presence of prerelease
1220 versions in the set.
1222 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
1223 ['1.3']
1224 >>> list(SpecifierSet("").filter(["1.5a1"]))
1225 ['1.5a1']
1226 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
1227 ['1.3', '1.5a1']
1228 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
1229 ['1.3', '1.5a1']
1230 """
1231 # Determine if we're forcing a prerelease or not, if we're not forcing
1232 # one for this particular filter call, then we'll use whatever the
1233 # SpecifierSet thinks for whether or not we should support prereleases.
1234 if prereleases is None and self.prereleases is not None:
1235 prereleases = self.prereleases
1237 # Filter versions that match all specifiers using Cost Based Ordering.
1238 if self._specs:
1239 # When prereleases is None, we need to let all versions through
1240 # the individual filters, then decide about prereleases at the end
1241 # based on whether any non-prereleases matched ALL specs.
1243 # Fast path: single specifier, delegate directly.
1244 if len(self._specs) == 1:
1245 filtered = self._specs[0].filter(
1246 iterable,
1247 prereleases=True if prereleases is None else prereleases,
1248 key=key,
1249 )
1250 else:
1251 filtered = self._filter_versions(
1252 iterable,
1253 key,
1254 prereleases=True if prereleases is None else prereleases,
1255 )
1257 if prereleases is not None:
1258 return filtered
1260 return _pep440_filter_prereleases(filtered, key)
1262 # Handle Empty SpecifierSet.
1263 if prereleases is True:
1264 return iter(iterable)
1266 if prereleases is False:
1267 return (
1268 item
1269 for item in iterable
1270 if (
1271 (version := _coerce_version(item if key is None else key(item)))
1272 is None
1273 or not version.is_prerelease
1274 )
1275 )
1277 # PEP 440: exclude prereleases unless no final releases matched
1278 return _pep440_filter_prereleases(iterable, key)
1280 def _filter_versions(
1281 self,
1282 iterable: Iterable[Any],
1283 key: Callable[[Any], UnparsedVersion] | None,
1284 prereleases: bool | None = None,
1285 ) -> Iterator[Any]:
1286 """Filter versions against all specifiers in a single pass.
1288 Uses Cost Based Ordering: specifiers are sorted by _operator_cost so
1289 that cheap range operators reject versions early, avoiding expensive
1290 wildcard or compatible operators on versions that would have been
1291 rejected anyway.
1292 """
1293 # Pre-resolve operators and sort (cached after first call).
1294 if self._resolved_ops is None:
1295 self._resolved_ops = sorted(
1296 (
1297 (spec._get_operator(spec.operator), spec.version, spec.operator)
1298 for spec in self._specs
1299 ),
1300 key=_operator_cost,
1301 )
1302 ops = self._resolved_ops
1303 exclude_prereleases = prereleases is False
1305 for item in iterable:
1306 parsed = _coerce_version(item if key is None else key(item))
1308 if parsed is None:
1309 # Only === can match non-parseable versions.
1310 if all(
1311 op == "===" and str(item).lower() == ver.lower()
1312 for _, ver, op in ops
1313 ):
1314 yield item
1315 elif exclude_prereleases and parsed.is_prerelease:
1316 pass
1317 elif all(
1318 str(item if key is None else key(item)).lower() == ver.lower()
1319 if op == "==="
1320 else op_fn(parsed, ver)
1321 for op_fn, ver, op in ops
1322 ):
1323 # Short-circuits on the first failing operator.
1324 yield item