Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/packaging/specifiers.py: 27%
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
16from typing import Callable, Final, Iterable, Iterator, TypeVar, Union
18from .utils import canonicalize_version
19from .version import InvalidVersion, Version
21UnparsedVersion = Union[Version, str]
22UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
23CallableOperator = Callable[[Version, str], bool]
26def _coerce_version(version: UnparsedVersion) -> Version | None:
27 if not isinstance(version, Version):
28 try:
29 version = Version(version)
30 except InvalidVersion:
31 return None
32 return version
35def _public_version(version: Version) -> Version:
36 return version.__replace__(local=None)
39def _base_version(version: Version) -> Version:
40 return version.__replace__(pre=None, post=None, dev=None, local=None)
43class InvalidSpecifier(ValueError):
44 """
45 Raised when attempting to create a :class:`Specifier` with a specifier
46 string that is invalid.
48 >>> Specifier("lolwat")
49 Traceback (most recent call last):
50 ...
51 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
52 """
55class BaseSpecifier(metaclass=abc.ABCMeta):
56 __slots__ = ()
57 __match_args__ = ("_str",)
59 @property
60 def _str(self) -> str:
61 """Internal property for match_args"""
62 return str(self)
64 @abc.abstractmethod
65 def __str__(self) -> str:
66 """
67 Returns the str representation of this Specifier-like object. This
68 should be representative of the Specifier itself.
69 """
71 @abc.abstractmethod
72 def __hash__(self) -> int:
73 """
74 Returns a hash value for this Specifier-like object.
75 """
77 @abc.abstractmethod
78 def __eq__(self, other: object) -> bool:
79 """
80 Returns a boolean representing whether or not the two Specifier-like
81 objects are equal.
83 :param other: The other object to check against.
84 """
86 @property
87 @abc.abstractmethod
88 def prereleases(self) -> bool | None:
89 """Whether or not pre-releases as a whole are allowed.
91 This can be set to either ``True`` or ``False`` to explicitly enable or disable
92 prereleases or it can be set to ``None`` (the default) to use default semantics.
93 """
95 @prereleases.setter # noqa: B027
96 def prereleases(self, value: bool) -> None:
97 """Setter for :attr:`prereleases`.
99 :param value: The value to set.
100 """
102 @abc.abstractmethod
103 def contains(self, item: str, prereleases: bool | None = None) -> bool:
104 """
105 Determines if the given item is contained within this specifier.
106 """
108 @abc.abstractmethod
109 def filter(
110 self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
111 ) -> Iterator[UnparsedVersionVar]:
112 """
113 Takes an iterable of items and filters them so that only items which
114 are contained within this specifier are allowed in it.
115 """
118class Specifier(BaseSpecifier):
119 """This class abstracts handling of version specifiers.
121 .. tip::
123 It is generally not required to instantiate this manually. You should instead
124 prefer to work with :class:`SpecifierSet` instead, which can parse
125 comma-separated version specifiers (which is what package metadata contains).
126 """
128 __slots__ = ("_prereleases", "_spec", "_spec_version")
130 _operator_regex_str = r"""
131 (?P<operator>(~=|==|!=|<=|>=|<|>|===))
132 """
133 _version_regex_str = r"""
134 (?P<version>
135 (?:
136 # The identity operators allow for an escape hatch that will
137 # do an exact string match of the version you wish to install.
138 # This will not be parsed by PEP 440 and we cannot determine
139 # any semantic meaning from it. This operator is discouraged
140 # but included entirely as an escape hatch.
141 (?<====) # Only match for the identity operator
142 \s*
143 [^\s;)]* # The arbitrary version can be just about anything,
144 # we match everything except for whitespace, a
145 # semi-colon for marker support, and a closing paren
146 # since versions can be enclosed in them.
147 )
148 |
149 (?:
150 # The (non)equality operators allow for wild card and local
151 # versions to be specified so we have to define these two
152 # operators separately to enable that.
153 (?<===|!=) # Only match for equals and not equals
155 \s*
156 v?
157 (?:[0-9]+!)? # epoch
158 [0-9]+(?:\.[0-9]+)* # release
160 # You cannot use a wild card and a pre-release, post-release, a dev or
161 # local version together so group them with a | and make them optional.
162 (?:
163 \.\* # Wild card syntax of .*
164 |
165 (?: # pre release
166 [-_\.]?
167 (alpha|beta|preview|pre|a|b|c|rc)
168 [-_\.]?
169 [0-9]*
170 )?
171 (?: # post release
172 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
173 )?
174 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
175 (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
176 )?
177 )
178 |
179 (?:
180 # The compatible operator requires at least two digits in the
181 # release segment.
182 (?<=~=) # Only match for the compatible operator
184 \s*
185 v?
186 (?:[0-9]+!)? # epoch
187 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
188 (?: # pre release
189 [-_\.]?
190 (alpha|beta|preview|pre|a|b|c|rc)
191 [-_\.]?
192 [0-9]*
193 )?
194 (?: # post release
195 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
196 )?
197 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
198 )
199 |
200 (?:
201 # All other operators only allow a sub set of what the
202 # (non)equality operators do. Specifically they do not allow
203 # local versions to be specified nor do they allow the prefix
204 # matching wild cards.
205 (?<!==|!=|~=) # We have special cases for these
206 # operators so we want to make sure they
207 # don't match here.
209 \s*
210 v?
211 (?:[0-9]+!)? # epoch
212 [0-9]+(?:\.[0-9]+)* # release
213 (?: # pre release
214 [-_\.]?
215 (alpha|beta|preview|pre|a|b|c|rc)
216 [-_\.]?
217 [0-9]*
218 )?
219 (?: # post release
220 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
221 )?
222 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
223 )
224 )
225 """
227 _regex = re.compile(
228 r"\s*" + _operator_regex_str + _version_regex_str + r"\s*",
229 re.VERBOSE | re.IGNORECASE,
230 )
232 _operators: Final = {
233 "~=": "compatible",
234 "==": "equal",
235 "!=": "not_equal",
236 "<=": "less_than_equal",
237 ">=": "greater_than_equal",
238 "<": "less_than",
239 ">": "greater_than",
240 "===": "arbitrary",
241 }
243 def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
244 """Initialize a Specifier instance.
246 :param spec:
247 The string representation of a specifier which will be parsed and
248 normalized before use.
249 :param prereleases:
250 This tells the specifier if it should accept prerelease versions if
251 applicable or not. The default of ``None`` will autodetect it from the
252 given specifiers.
253 :raises InvalidSpecifier:
254 If the given specifier is invalid (i.e. bad syntax).
255 """
256 match = self._regex.fullmatch(spec)
257 if not match:
258 raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
260 self._spec: tuple[str, str] = (
261 match.group("operator").strip(),
262 match.group("version").strip(),
263 )
265 # Store whether or not this Specifier should accept prereleases
266 self._prereleases = prereleases
268 # Specifier version cache
269 self._spec_version: tuple[str, Version] | None = None
271 def _get_spec_version(self, version: str) -> Version | None:
272 """One element cache, as only one spec Version is needed per Specifier."""
273 if self._spec_version is not None and self._spec_version[0] == version:
274 return self._spec_version[1]
276 version_specifier = _coerce_version(version)
277 if version_specifier is None:
278 return None
280 self._spec_version = (version, version_specifier)
281 return version_specifier
283 def _require_spec_version(self, version: str) -> Version:
284 """Get spec version, asserting it's valid (not for === operator).
286 This method should only be called for operators where version
287 strings are guaranteed to be valid PEP 440 versions (not ===).
288 """
289 spec_version = self._get_spec_version(version)
290 assert spec_version is not None
291 return spec_version
293 @property
294 def prereleases(self) -> bool | None:
295 # If there is an explicit prereleases set for this, then we'll just
296 # blindly use that.
297 if self._prereleases is not None:
298 return self._prereleases
300 # Only the "!=" operator does not imply prereleases when
301 # the version in the specifier is a prerelease.
302 operator, version_str = self._spec
303 if operator != "!=":
304 # The == specifier with trailing .* cannot include prereleases
305 # e.g. "==1.0a1.*" is not valid.
306 if operator == "==" and version_str.endswith(".*"):
307 return False
309 # "===" can have arbitrary string versions, so we cannot parse
310 # those, we take prereleases as unknown (None) for those.
311 version = self._get_spec_version(version_str)
312 if version is None:
313 return None
315 # For all other operators, use the check if spec Version
316 # object implies pre-releases.
317 if version.is_prerelease:
318 return True
320 return False
322 @prereleases.setter
323 def prereleases(self, value: bool | None) -> None:
324 self._prereleases = value
326 @property
327 def operator(self) -> str:
328 """The operator of this specifier.
330 >>> Specifier("==1.2.3").operator
331 '=='
332 """
333 return self._spec[0]
335 @property
336 def version(self) -> str:
337 """The version of this specifier.
339 >>> Specifier("==1.2.3").version
340 '1.2.3'
341 """
342 return self._spec[1]
344 def __repr__(self) -> str:
345 """A representation of the Specifier that shows all internal state.
347 >>> Specifier('>=1.0.0')
348 <Specifier('>=1.0.0')>
349 >>> Specifier('>=1.0.0', prereleases=False)
350 <Specifier('>=1.0.0', prereleases=False)>
351 >>> Specifier('>=1.0.0', prereleases=True)
352 <Specifier('>=1.0.0', prereleases=True)>
353 """
354 pre = (
355 f", prereleases={self.prereleases!r}"
356 if self._prereleases is not None
357 else ""
358 )
360 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
362 def __str__(self) -> str:
363 """A string representation of the Specifier that can be round-tripped.
365 >>> str(Specifier('>=1.0.0'))
366 '>=1.0.0'
367 >>> str(Specifier('>=1.0.0', prereleases=False))
368 '>=1.0.0'
369 """
370 return "{}{}".format(*self._spec)
372 @property
373 def _canonical_spec(self) -> tuple[str, str]:
374 operator, version = self._spec
375 if operator == "===" or version.endswith(".*"):
376 return operator, version
378 spec_version = self._require_spec_version(version)
380 canonical_version = canonicalize_version(
381 spec_version, strip_trailing_zero=(operator != "~=")
382 )
384 return operator, canonical_version
386 def __hash__(self) -> int:
387 return hash(self._canonical_spec)
389 def __eq__(self, other: object) -> bool:
390 """Whether or not the two Specifier-like objects are equal.
392 :param other: The other object to check against.
394 The value of :attr:`prereleases` is ignored.
396 >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
397 True
398 >>> (Specifier("==1.2.3", prereleases=False) ==
399 ... Specifier("==1.2.3", prereleases=True))
400 True
401 >>> Specifier("==1.2.3") == "==1.2.3"
402 True
403 >>> Specifier("==1.2.3") == Specifier("==1.2.4")
404 False
405 >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
406 False
407 """
408 if isinstance(other, str):
409 try:
410 other = self.__class__(str(other))
411 except InvalidSpecifier:
412 return NotImplemented
413 elif not isinstance(other, self.__class__):
414 return NotImplemented
416 return self._canonical_spec == other._canonical_spec
418 def _get_operator(self, op: str) -> CallableOperator:
419 operator_callable: CallableOperator = getattr(
420 self, f"_compare_{self._operators[op]}"
421 )
422 return operator_callable
424 def _compare_compatible(self, prospective: Version, spec: str) -> bool:
425 # Compatible releases have an equivalent combination of >= and ==. That
426 # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
427 # implement this in terms of the other specifiers instead of
428 # implementing it ourselves. The only thing we need to do is construct
429 # the other specifiers.
431 # We want everything but the last item in the version, but we want to
432 # ignore suffix segments.
433 prefix = _version_join(
434 list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
435 )
437 # Add the prefix notation to the end of our string
438 prefix += ".*"
440 return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
441 prospective, prefix
442 )
444 def _compare_equal(self, prospective: Version, spec: str) -> bool:
445 # We need special logic to handle prefix matching
446 if spec.endswith(".*"):
447 # In the case of prefix matching we want to ignore local segment.
448 normalized_prospective = canonicalize_version(
449 _public_version(prospective), strip_trailing_zero=False
450 )
451 # Get the normalized version string ignoring the trailing .*
452 normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)
453 # Split the spec out by bangs and dots, and pretend that there is
454 # an implicit dot in between a release segment and a pre-release segment.
455 split_spec = _version_split(normalized_spec)
457 # Split the prospective version out by bangs and dots, and pretend
458 # that there is an implicit dot in between a release segment and
459 # a pre-release segment.
460 split_prospective = _version_split(normalized_prospective)
462 # 0-pad the prospective version before shortening it to get the correct
463 # shortened version.
464 padded_prospective, _ = _pad_version(split_prospective, split_spec)
466 # Shorten the prospective version to be the same length as the spec
467 # so that we can determine if the specifier is a prefix of the
468 # prospective version or not.
469 shortened_prospective = padded_prospective[: len(split_spec)]
471 return shortened_prospective == split_spec
472 else:
473 # Convert our spec string into a Version
474 spec_version = self._require_spec_version(spec)
476 # If the specifier does not have a local segment, then we want to
477 # act as if the prospective version also does not have a local
478 # segment.
479 if not spec_version.local:
480 prospective = _public_version(prospective)
482 return prospective == spec_version
484 def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
485 return not self._compare_equal(prospective, spec)
487 def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
488 # NB: Local version identifiers are NOT permitted in the version
489 # specifier, so local version labels can be universally removed from
490 # the prospective version.
491 return _public_version(prospective) <= self._require_spec_version(spec)
493 def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
494 # NB: Local version identifiers are NOT permitted in the version
495 # specifier, so local version labels can be universally removed from
496 # the prospective version.
497 return _public_version(prospective) >= self._require_spec_version(spec)
499 def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
500 # Convert our spec to a Version instance, since we'll want to work with
501 # it as a version.
502 spec = self._require_spec_version(spec_str)
504 # Check to see if the prospective version is less than the spec
505 # version. If it's not we can short circuit and just return False now
506 # instead of doing extra unneeded work.
507 if not prospective < spec:
508 return False
510 # This special case is here so that, unless the specifier itself
511 # includes is a pre-release version, that we do not accept pre-release
512 # versions for the version mentioned in the specifier (e.g. <3.1 should
513 # not match 3.1.dev0, but should match 3.0.dev0).
514 if (
515 not spec.is_prerelease
516 and prospective.is_prerelease
517 and _base_version(prospective) == _base_version(spec)
518 ):
519 return False
521 # If we've gotten to here, it means that prospective version is both
522 # less than the spec version *and* it's not a pre-release of the same
523 # version in the spec.
524 return True
526 def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
527 # Convert our spec to a Version instance, since we'll want to work with
528 # it as a version.
529 spec = self._require_spec_version(spec_str)
531 # Check to see if the prospective version is greater than the spec
532 # version. If it's not we can short circuit and just return False now
533 # instead of doing extra unneeded work.
534 if not prospective > spec:
535 return False
537 # This special case is here so that, unless the specifier itself
538 # includes is a post-release version, that we do not accept
539 # post-release versions for the version mentioned in the specifier
540 # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
541 if (
542 not spec.is_postrelease
543 and prospective.is_postrelease
544 and _base_version(prospective) == _base_version(spec)
545 ):
546 return False
548 # Ensure that we do not allow a local version of the version mentioned
549 # in the specifier, which is technically greater than, to match.
550 if prospective.local is not None and _base_version(
551 prospective
552 ) == _base_version(spec):
553 return False
555 # If we've gotten to here, it means that prospective version is both
556 # greater than the spec version *and* it's not a pre-release of the
557 # same version in the spec.
558 return True
560 def _compare_arbitrary(self, prospective: Version | str, spec: str) -> bool:
561 return str(prospective).lower() == str(spec).lower()
563 def __contains__(self, item: str | Version) -> bool:
564 """Return whether or not the item is contained in this specifier.
566 :param item: The item to check for.
568 This is used for the ``in`` operator and behaves the same as
569 :meth:`contains` with no ``prereleases`` argument passed.
571 >>> "1.2.3" in Specifier(">=1.2.3")
572 True
573 >>> Version("1.2.3") in Specifier(">=1.2.3")
574 True
575 >>> "1.0.0" in Specifier(">=1.2.3")
576 False
577 >>> "1.3.0a1" in Specifier(">=1.2.3")
578 True
579 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
580 True
581 """
582 return self.contains(item)
584 def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
585 """Return whether or not the item is contained in this specifier.
587 :param item:
588 The item to check for, which can be a version string or a
589 :class:`Version` instance.
590 :param prereleases:
591 Whether or not to match prereleases with this Specifier. If set to
592 ``None`` (the default), it will follow the recommendation from
593 :pep:`440` and match prereleases, as there are no other versions.
595 >>> Specifier(">=1.2.3").contains("1.2.3")
596 True
597 >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
598 True
599 >>> Specifier(">=1.2.3").contains("1.0.0")
600 False
601 >>> Specifier(">=1.2.3").contains("1.3.0a1")
602 True
603 >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1")
604 False
605 >>> Specifier(">=1.2.3").contains("1.3.0a1")
606 True
607 """
609 return bool(list(self.filter([item], prereleases=prereleases)))
611 def filter(
612 self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
613 ) -> Iterator[UnparsedVersionVar]:
614 """Filter items in the given iterable, that match the specifier.
616 :param iterable:
617 An iterable that can contain version strings and :class:`Version` instances.
618 The items in the iterable will be filtered according to the specifier.
619 :param prereleases:
620 Whether or not to allow prereleases in the returned iterator. If set to
621 ``None`` (the default), it will follow the recommendation from :pep:`440`
622 and match prereleases if there are no other versions.
624 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
625 ['1.3']
626 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
627 ['1.2.3', '1.3', <Version('1.4')>]
628 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
629 ['1.5a1']
630 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
631 ['1.3', '1.5a1']
632 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
633 ['1.3', '1.5a1']
634 """
635 prereleases_versions = []
636 found_non_prereleases = False
638 # Determine if to include prereleases by default
639 include_prereleases = (
640 prereleases if prereleases is not None else self.prereleases
641 )
643 # Get the matching operator
644 operator_callable = self._get_operator(self.operator)
646 # Filter versions
647 for version in iterable:
648 parsed_version = _coerce_version(version)
649 if parsed_version is None:
650 # === operator can match arbitrary (non-version) strings
651 if self.operator == "===" and self._compare_arbitrary(
652 version, self.version
653 ):
654 yield version
655 elif operator_callable(parsed_version, self.version):
656 # If it's not a prerelease or prereleases are allowed, yield it directly
657 if not parsed_version.is_prerelease or include_prereleases:
658 found_non_prereleases = True
659 yield version
660 # Otherwise collect prereleases for potential later use
661 elif prereleases is None and self._prereleases is not False:
662 prereleases_versions.append(version)
664 # If no non-prereleases were found and prereleases weren't
665 # explicitly forbidden, yield the collected prereleases
666 if (
667 not found_non_prereleases
668 and prereleases is None
669 and self._prereleases is not False
670 ):
671 yield from prereleases_versions
674_prefix_regex = re.compile(r"([0-9]+)((?:a|b|c|rc)[0-9]+)")
677def _version_split(version: str) -> list[str]:
678 """Split version into components.
680 The split components are intended for version comparison. The logic does
681 not attempt to retain the original version string, so joining the
682 components back with :func:`_version_join` may not produce the original
683 version string.
684 """
685 result: list[str] = []
687 epoch, _, rest = version.rpartition("!")
688 result.append(epoch or "0")
690 for item in rest.split("."):
691 match = _prefix_regex.fullmatch(item)
692 if match:
693 result.extend(match.groups())
694 else:
695 result.append(item)
696 return result
699def _version_join(components: list[str]) -> str:
700 """Join split version components into a version string.
702 This function assumes the input came from :func:`_version_split`, where the
703 first component must be the epoch (either empty or numeric), and all other
704 components numeric.
705 """
706 epoch, *rest = components
707 return f"{epoch}!{'.'.join(rest)}"
710def _is_not_suffix(segment: str) -> bool:
711 return not any(
712 segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
713 )
716def _pad_version(left: list[str], right: list[str]) -> tuple[list[str], list[str]]:
717 left_split, right_split = [], []
719 # Get the release segment of our versions
720 left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
721 right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
723 # Get the rest of our versions
724 left_split.append(left[len(left_split[0]) :])
725 right_split.append(right[len(right_split[0]) :])
727 # Insert our padding
728 left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
729 right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
731 return (
732 list(itertools.chain.from_iterable(left_split)),
733 list(itertools.chain.from_iterable(right_split)),
734 )
737class SpecifierSet(BaseSpecifier):
738 """This class abstracts handling of a set of version specifiers.
740 It can be passed a single specifier (``>=3.0``), a comma-separated list of
741 specifiers (``>=3.0,!=3.1``), or no specifier at all.
742 """
744 __slots__ = ("_prereleases", "_specs")
746 def __init__(
747 self,
748 specifiers: str | Iterable[Specifier] = "",
749 prereleases: bool | None = None,
750 ) -> None:
751 """Initialize a SpecifierSet instance.
753 :param specifiers:
754 The string representation of a specifier or a comma-separated list of
755 specifiers which will be parsed and normalized before use.
756 May also be an iterable of ``Specifier`` instances, which will be used
757 as is.
758 :param prereleases:
759 This tells the SpecifierSet if it should accept prerelease versions if
760 applicable or not. The default of ``None`` will autodetect it from the
761 given specifiers.
763 :raises InvalidSpecifier:
764 If the given ``specifiers`` are not parseable than this exception will be
765 raised.
766 """
768 if isinstance(specifiers, str):
769 # Split on `,` to break each individual specifier into its own item, and
770 # strip each item to remove leading/trailing whitespace.
771 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
773 # Make each individual specifier a Specifier and save in a frozen set
774 # for later.
775 self._specs = frozenset(map(Specifier, split_specifiers))
776 else:
777 # Save the supplied specifiers in a frozen set.
778 self._specs = frozenset(specifiers)
780 # Store our prereleases value so we can use it later to determine if
781 # we accept prereleases or not.
782 self._prereleases = prereleases
784 @property
785 def prereleases(self) -> bool | None:
786 # If we have been given an explicit prerelease modifier, then we'll
787 # pass that through here.
788 if self._prereleases is not None:
789 return self._prereleases
791 # If we don't have any specifiers, and we don't have a forced value,
792 # then we'll just return None since we don't know if this should have
793 # pre-releases or not.
794 if not self._specs:
795 return None
797 # Otherwise we'll see if any of the given specifiers accept
798 # prereleases, if any of them do we'll return True, otherwise False.
799 if any(s.prereleases for s in self._specs):
800 return True
802 return None
804 @prereleases.setter
805 def prereleases(self, value: bool | None) -> None:
806 self._prereleases = value
808 def __repr__(self) -> str:
809 """A representation of the specifier set that shows all internal state.
811 Note that the ordering of the individual specifiers within the set may not
812 match the input string.
814 >>> SpecifierSet('>=1.0.0,!=2.0.0')
815 <SpecifierSet('!=2.0.0,>=1.0.0')>
816 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
817 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
818 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
819 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
820 """
821 pre = (
822 f", prereleases={self.prereleases!r}"
823 if self._prereleases is not None
824 else ""
825 )
827 return f"<SpecifierSet({str(self)!r}{pre})>"
829 def __str__(self) -> str:
830 """A string representation of the specifier set that can be round-tripped.
832 Note that the ordering of the individual specifiers within the set may not
833 match the input string.
835 >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
836 '!=1.0.1,>=1.0.0'
837 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
838 '!=1.0.1,>=1.0.0'
839 """
840 return ",".join(sorted(str(s) for s in self._specs))
842 def __hash__(self) -> int:
843 return hash(self._specs)
845 def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
846 """Return a SpecifierSet which is a combination of the two sets.
848 :param other: The other object to combine with.
850 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
851 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
852 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
853 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
854 """
855 if isinstance(other, str):
856 other = SpecifierSet(other)
857 elif not isinstance(other, SpecifierSet):
858 return NotImplemented
860 specifier = SpecifierSet()
861 specifier._specs = frozenset(self._specs | other._specs)
863 if self._prereleases is None and other._prereleases is not None:
864 specifier._prereleases = other._prereleases
865 elif (
866 self._prereleases is not None and other._prereleases is None
867 ) or self._prereleases == other._prereleases:
868 specifier._prereleases = self._prereleases
869 else:
870 raise ValueError(
871 "Cannot combine SpecifierSets with True and False prerelease overrides."
872 )
874 return specifier
876 def __eq__(self, other: object) -> bool:
877 """Whether or not the two SpecifierSet-like objects are equal.
879 :param other: The other object to check against.
881 The value of :attr:`prereleases` is ignored.
883 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
884 True
885 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
886 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
887 True
888 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
889 True
890 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
891 False
892 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
893 False
894 """
895 if isinstance(other, (str, Specifier)):
896 other = SpecifierSet(str(other))
897 elif not isinstance(other, SpecifierSet):
898 return NotImplemented
900 return self._specs == other._specs
902 def __len__(self) -> int:
903 """Returns the number of specifiers in this specifier set."""
904 return len(self._specs)
906 def __iter__(self) -> Iterator[Specifier]:
907 """
908 Returns an iterator over all the underlying :class:`Specifier` instances
909 in this specifier set.
911 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
912 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
913 """
914 return iter(self._specs)
916 def __contains__(self, item: UnparsedVersion) -> bool:
917 """Return whether or not the item is contained in this specifier.
919 :param item: The item to check for.
921 This is used for the ``in`` operator and behaves the same as
922 :meth:`contains` with no ``prereleases`` argument passed.
924 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
925 True
926 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
927 True
928 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
929 False
930 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
931 True
932 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
933 True
934 """
935 return self.contains(item)
937 def contains(
938 self,
939 item: UnparsedVersion,
940 prereleases: bool | None = None,
941 installed: bool | None = None,
942 ) -> bool:
943 """Return whether or not the item is contained in this SpecifierSet.
945 :param item:
946 The item to check for, which can be a version string or a
947 :class:`Version` instance.
948 :param prereleases:
949 Whether or not to match prereleases with this SpecifierSet. If set to
950 ``None`` (the default), it will follow the recommendation from :pep:`440`
951 and match prereleases, as there are no other versions.
952 :param installed:
953 Whether or not the item is installed. If set to ``True``, it will
954 accept prerelease versions even if the specifier does not allow them.
956 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
957 True
958 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
959 True
960 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
961 False
962 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
963 True
964 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")
965 False
966 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
967 True
968 """
969 version = _coerce_version(item)
971 if version is not None and installed and version.is_prerelease:
972 prereleases = True
974 check_item = item if version is None else version
975 return bool(list(self.filter([check_item], prereleases=prereleases)))
977 def filter(
978 self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
979 ) -> Iterator[UnparsedVersionVar]:
980 """Filter items in the given iterable, that match the specifiers in this set.
982 :param iterable:
983 An iterable that can contain version strings and :class:`Version` instances.
984 The items in the iterable will be filtered according to the specifier.
985 :param prereleases:
986 Whether or not to allow prereleases in the returned iterator. If set to
987 ``None`` (the default), it will follow the recommendation from :pep:`440`
988 and match prereleases if there are no other versions.
990 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
991 ['1.3']
992 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
993 ['1.3', <Version('1.4')>]
994 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
995 ['1.5a1']
996 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
997 ['1.3', '1.5a1']
998 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
999 ['1.3', '1.5a1']
1001 An "empty" SpecifierSet will filter items based on the presence of prerelease
1002 versions in the set.
1004 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
1005 ['1.3']
1006 >>> list(SpecifierSet("").filter(["1.5a1"]))
1007 ['1.5a1']
1008 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
1009 ['1.3', '1.5a1']
1010 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
1011 ['1.3', '1.5a1']
1012 """
1013 # Determine if we're forcing a prerelease or not, if we're not forcing
1014 # one for this particular filter call, then we'll use whatever the
1015 # SpecifierSet thinks for whether or not we should support prereleases.
1016 if prereleases is None and self.prereleases is not None:
1017 prereleases = self.prereleases
1019 # If we have any specifiers, then we want to wrap our iterable in the
1020 # filter method for each one, this will act as a logical AND amongst
1021 # each specifier.
1022 if self._specs:
1023 # When prereleases is None, we need to let all versions through
1024 # the individual filters, then decide about prereleases at the end
1025 # based on whether any non-prereleases matched ALL specs.
1026 for spec in self._specs:
1027 iterable = spec.filter(
1028 iterable, prereleases=True if prereleases is None else prereleases
1029 )
1031 if prereleases is not None:
1032 # If we have a forced prereleases value,
1033 # we can immediately return the iterator.
1034 return iter(iterable)
1035 else:
1036 # Handle empty SpecifierSet cases where prereleases is not None.
1037 if prereleases is True:
1038 return iter(iterable)
1040 if prereleases is False:
1041 return (
1042 item
1043 for item in iterable
1044 if (version := _coerce_version(item)) is None
1045 or not version.is_prerelease
1046 )
1048 # Finally if prereleases is None, apply PEP 440 logic:
1049 # exclude prereleases unless there are no final releases that matched.
1050 filtered_items: list[UnparsedVersionVar] = []
1051 found_prereleases: list[UnparsedVersionVar] = []
1052 found_final_release = False
1054 for item in iterable:
1055 parsed_version = _coerce_version(item)
1056 # Arbitrary strings are always included as it is not
1057 # possible to determine if they are prereleases,
1058 # and they have already passed all specifiers.
1059 if parsed_version is None:
1060 filtered_items.append(item)
1061 found_prereleases.append(item)
1062 elif parsed_version.is_prerelease:
1063 found_prereleases.append(item)
1064 else:
1065 filtered_items.append(item)
1066 found_final_release = True
1068 return iter(filtered_items if found_final_release else found_prereleases)