Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/packaging/specifiers.py: 26%
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
35class InvalidSpecifier(ValueError):
36 """
37 Raised when attempting to create a :class:`Specifier` with a specifier
38 string that is invalid.
40 >>> Specifier("lolwat")
41 Traceback (most recent call last):
42 ...
43 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
44 """
47class BaseSpecifier(metaclass=abc.ABCMeta):
48 @abc.abstractmethod
49 def __str__(self) -> str:
50 """
51 Returns the str representation of this Specifier-like object. This
52 should be representative of the Specifier itself.
53 """
55 @abc.abstractmethod
56 def __hash__(self) -> int:
57 """
58 Returns a hash value for this Specifier-like object.
59 """
61 @abc.abstractmethod
62 def __eq__(self, other: object) -> bool:
63 """
64 Returns a boolean representing whether or not the two Specifier-like
65 objects are equal.
67 :param other: The other object to check against.
68 """
70 @property
71 @abc.abstractmethod
72 def prereleases(self) -> bool | None:
73 """Whether or not pre-releases as a whole are allowed.
75 This can be set to either ``True`` or ``False`` to explicitly enable or disable
76 prereleases or it can be set to ``None`` (the default) to use default semantics.
77 """
79 @prereleases.setter # noqa: B027
80 def prereleases(self, value: bool) -> None:
81 """Setter for :attr:`prereleases`.
83 :param value: The value to set.
84 """
86 @abc.abstractmethod
87 def contains(self, item: str, prereleases: bool | None = None) -> bool:
88 """
89 Determines if the given item is contained within this specifier.
90 """
92 @abc.abstractmethod
93 def filter(
94 self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
95 ) -> Iterator[UnparsedVersionVar]:
96 """
97 Takes an iterable of items and filters them so that only items which
98 are contained within this specifier are allowed in it.
99 """
102class Specifier(BaseSpecifier):
103 """This class abstracts handling of version specifiers.
105 .. tip::
107 It is generally not required to instantiate this manually. You should instead
108 prefer to work with :class:`SpecifierSet` instead, which can parse
109 comma-separated version specifiers (which is what package metadata contains).
110 """
112 _operator_regex_str = r"""
113 (?P<operator>(~=|==|!=|<=|>=|<|>|===))
114 """
115 _version_regex_str = r"""
116 (?P<version>
117 (?:
118 # The identity operators allow for an escape hatch that will
119 # do an exact string match of the version you wish to install.
120 # This will not be parsed by PEP 440 and we cannot determine
121 # any semantic meaning from it. This operator is discouraged
122 # but included entirely as an escape hatch.
123 (?<====) # Only match for the identity operator
124 \s*
125 [^\s;)]* # The arbitrary version can be just about anything,
126 # we match everything except for whitespace, a
127 # semi-colon for marker support, and a closing paren
128 # since versions can be enclosed in them.
129 )
130 |
131 (?:
132 # The (non)equality operators allow for wild card and local
133 # versions to be specified so we have to define these two
134 # operators separately to enable that.
135 (?<===|!=) # Only match for equals and not equals
137 \s*
138 v?
139 (?:[0-9]+!)? # epoch
140 [0-9]+(?:\.[0-9]+)* # release
142 # You cannot use a wild card and a pre-release, post-release, a dev or
143 # local version together so group them with a | and make them optional.
144 (?:
145 \.\* # Wild card syntax of .*
146 |
147 (?: # pre release
148 [-_\.]?
149 (alpha|beta|preview|pre|a|b|c|rc)
150 [-_\.]?
151 [0-9]*
152 )?
153 (?: # post release
154 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
155 )?
156 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
157 (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
158 )?
159 )
160 |
161 (?:
162 # The compatible operator requires at least two digits in the
163 # release segment.
164 (?<=~=) # Only match for the compatible operator
166 \s*
167 v?
168 (?:[0-9]+!)? # epoch
169 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
170 (?: # pre release
171 [-_\.]?
172 (alpha|beta|preview|pre|a|b|c|rc)
173 [-_\.]?
174 [0-9]*
175 )?
176 (?: # post release
177 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
178 )?
179 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
180 )
181 |
182 (?:
183 # All other operators only allow a sub set of what the
184 # (non)equality operators do. Specifically they do not allow
185 # local versions to be specified nor do they allow the prefix
186 # matching wild cards.
187 (?<!==|!=|~=) # We have special cases for these
188 # operators so we want to make sure they
189 # don't match here.
191 \s*
192 v?
193 (?:[0-9]+!)? # epoch
194 [0-9]+(?:\.[0-9]+)* # release
195 (?: # pre release
196 [-_\.]?
197 (alpha|beta|preview|pre|a|b|c|rc)
198 [-_\.]?
199 [0-9]*
200 )?
201 (?: # post release
202 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
203 )?
204 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
205 )
206 )
207 """
209 _regex = re.compile(
210 r"^\s*" + _operator_regex_str + _version_regex_str + r"\s*$",
211 re.VERBOSE | re.IGNORECASE,
212 )
214 _operators: Final = {
215 "~=": "compatible",
216 "==": "equal",
217 "!=": "not_equal",
218 "<=": "less_than_equal",
219 ">=": "greater_than_equal",
220 "<": "less_than",
221 ">": "greater_than",
222 "===": "arbitrary",
223 }
225 def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
226 """Initialize a Specifier instance.
228 :param spec:
229 The string representation of a specifier which will be parsed and
230 normalized before use.
231 :param prereleases:
232 This tells the specifier if it should accept prerelease versions if
233 applicable or not. The default of ``None`` will autodetect it from the
234 given specifiers.
235 :raises InvalidSpecifier:
236 If the given specifier is invalid (i.e. bad syntax).
237 """
238 match = self._regex.search(spec)
239 if not match:
240 raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
242 self._spec: tuple[str, str] = (
243 match.group("operator").strip(),
244 match.group("version").strip(),
245 )
247 # Store whether or not this Specifier should accept prereleases
248 self._prereleases = prereleases
250 @property
251 def prereleases(self) -> bool:
252 # If there is an explicit prereleases set for this, then we'll just
253 # blindly use that.
254 if self._prereleases is not None:
255 return self._prereleases
257 # Only the "!=" operator does not imply prereleases when
258 # the version in the specifier is a prerelease.
259 operator, version = self._spec
260 if operator != "!=":
261 # The == specifier can include a trailing .*, if it does we
262 # want to remove before parsing.
263 if operator == "==" and version.endswith(".*"):
264 version = version[:-2]
266 # Parse the version, and if it is a pre-release than this
267 # specifier allows pre-releases.
268 if Version(version).is_prerelease:
269 return True
271 return False
273 @prereleases.setter
274 def prereleases(self, value: bool | None) -> None:
275 self._prereleases = value
277 @property
278 def operator(self) -> str:
279 """The operator of this specifier.
281 >>> Specifier("==1.2.3").operator
282 '=='
283 """
284 return self._spec[0]
286 @property
287 def version(self) -> str:
288 """The version of this specifier.
290 >>> Specifier("==1.2.3").version
291 '1.2.3'
292 """
293 return self._spec[1]
295 def __repr__(self) -> str:
296 """A representation of the Specifier that shows all internal state.
298 >>> Specifier('>=1.0.0')
299 <Specifier('>=1.0.0')>
300 >>> Specifier('>=1.0.0', prereleases=False)
301 <Specifier('>=1.0.0', prereleases=False)>
302 >>> Specifier('>=1.0.0', prereleases=True)
303 <Specifier('>=1.0.0', prereleases=True)>
304 """
305 pre = (
306 f", prereleases={self.prereleases!r}"
307 if self._prereleases is not None
308 else ""
309 )
311 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
313 def __str__(self) -> str:
314 """A string representation of the Specifier that can be round-tripped.
316 >>> str(Specifier('>=1.0.0'))
317 '>=1.0.0'
318 >>> str(Specifier('>=1.0.0', prereleases=False))
319 '>=1.0.0'
320 """
321 return "{}{}".format(*self._spec)
323 @property
324 def _canonical_spec(self) -> tuple[str, str]:
325 operator, version = self._spec
326 if operator == "===":
327 return operator, version
329 canonical_version = canonicalize_version(
330 version,
331 strip_trailing_zero=(operator != "~="),
332 )
334 return operator, canonical_version
336 def __hash__(self) -> int:
337 return hash(self._canonical_spec)
339 def __eq__(self, other: object) -> bool:
340 """Whether or not the two Specifier-like objects are equal.
342 :param other: The other object to check against.
344 The value of :attr:`prereleases` is ignored.
346 >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
347 True
348 >>> (Specifier("==1.2.3", prereleases=False) ==
349 ... Specifier("==1.2.3", prereleases=True))
350 True
351 >>> Specifier("==1.2.3") == "==1.2.3"
352 True
353 >>> Specifier("==1.2.3") == Specifier("==1.2.4")
354 False
355 >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
356 False
357 """
358 if isinstance(other, str):
359 try:
360 other = self.__class__(str(other))
361 except InvalidSpecifier:
362 return NotImplemented
363 elif not isinstance(other, self.__class__):
364 return NotImplemented
366 return self._canonical_spec == other._canonical_spec
368 def _get_operator(self, op: str) -> CallableOperator:
369 operator_callable: CallableOperator = getattr(
370 self, f"_compare_{self._operators[op]}"
371 )
372 return operator_callable
374 def _compare_compatible(self, prospective: Version, spec: str) -> bool:
375 # Compatible releases have an equivalent combination of >= and ==. That
376 # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
377 # implement this in terms of the other specifiers instead of
378 # implementing it ourselves. The only thing we need to do is construct
379 # the other specifiers.
381 # We want everything but the last item in the version, but we want to
382 # ignore suffix segments.
383 prefix = _version_join(
384 list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
385 )
387 # Add the prefix notation to the end of our string
388 prefix += ".*"
390 return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
391 prospective, prefix
392 )
394 def _compare_equal(self, prospective: Version, spec: str) -> bool:
395 # We need special logic to handle prefix matching
396 if spec.endswith(".*"):
397 # In the case of prefix matching we want to ignore local segment.
398 normalized_prospective = canonicalize_version(
399 prospective.public, strip_trailing_zero=False
400 )
401 # Get the normalized version string ignoring the trailing .*
402 normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)
403 # Split the spec out by bangs and dots, and pretend that there is
404 # an implicit dot in between a release segment and a pre-release segment.
405 split_spec = _version_split(normalized_spec)
407 # Split the prospective version out by bangs and dots, and pretend
408 # that there is an implicit dot in between a release segment and
409 # a pre-release segment.
410 split_prospective = _version_split(normalized_prospective)
412 # 0-pad the prospective version before shortening it to get the correct
413 # shortened version.
414 padded_prospective, _ = _pad_version(split_prospective, split_spec)
416 # Shorten the prospective version to be the same length as the spec
417 # so that we can determine if the specifier is a prefix of the
418 # prospective version or not.
419 shortened_prospective = padded_prospective[: len(split_spec)]
421 return shortened_prospective == split_spec
422 else:
423 # Convert our spec string into a Version
424 spec_version = Version(spec)
426 # If the specifier does not have a local segment, then we want to
427 # act as if the prospective version also does not have a local
428 # segment.
429 if not spec_version.local:
430 prospective = Version(prospective.public)
432 return prospective == spec_version
434 def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
435 return not self._compare_equal(prospective, spec)
437 def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
438 # NB: Local version identifiers are NOT permitted in the version
439 # specifier, so local version labels can be universally removed from
440 # the prospective version.
441 return Version(prospective.public) <= Version(spec)
443 def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
444 # NB: Local version identifiers are NOT permitted in the version
445 # specifier, so local version labels can be universally removed from
446 # the prospective version.
447 return Version(prospective.public) >= Version(spec)
449 def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
450 # Convert our spec to a Version instance, since we'll want to work with
451 # it as a version.
452 spec = Version(spec_str)
454 # Check to see if the prospective version is less than the spec
455 # version. If it's not we can short circuit and just return False now
456 # instead of doing extra unneeded work.
457 if not prospective < spec:
458 return False
460 # This special case is here so that, unless the specifier itself
461 # includes is a pre-release version, that we do not accept pre-release
462 # versions for the version mentioned in the specifier (e.g. <3.1 should
463 # not match 3.1.dev0, but should match 3.0.dev0).
464 if (
465 not spec.is_prerelease
466 and prospective.is_prerelease
467 and Version(prospective.base_version) == Version(spec.base_version)
468 ):
469 return False
471 # If we've gotten to here, it means that prospective version is both
472 # less than the spec version *and* it's not a pre-release of the same
473 # version in the spec.
474 return True
476 def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
477 # Convert our spec to a Version instance, since we'll want to work with
478 # it as a version.
479 spec = Version(spec_str)
481 # Check to see if the prospective version is greater than the spec
482 # version. If it's not we can short circuit and just return False now
483 # instead of doing extra unneeded work.
484 if not prospective > spec:
485 return False
487 # This special case is here so that, unless the specifier itself
488 # includes is a post-release version, that we do not accept
489 # post-release versions for the version mentioned in the specifier
490 # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
491 if (
492 not spec.is_postrelease
493 and prospective.is_postrelease
494 and Version(prospective.base_version) == Version(spec.base_version)
495 ):
496 return False
498 # Ensure that we do not allow a local version of the version mentioned
499 # in the specifier, which is technically greater than, to match.
500 if prospective.local is not None and Version(
501 prospective.base_version
502 ) == Version(spec.base_version):
503 return False
505 # If we've gotten to here, it means that prospective version is both
506 # greater than the spec version *and* it's not a pre-release of the
507 # same version in the spec.
508 return True
510 def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
511 return str(prospective).lower() == str(spec).lower()
513 def __contains__(self, item: str | Version) -> bool:
514 """Return whether or not the item is contained in this specifier.
516 :param item: The item to check for.
518 This is used for the ``in`` operator and behaves the same as
519 :meth:`contains` with no ``prereleases`` argument passed.
521 >>> "1.2.3" in Specifier(">=1.2.3")
522 True
523 >>> Version("1.2.3") in Specifier(">=1.2.3")
524 True
525 >>> "1.0.0" in Specifier(">=1.2.3")
526 False
527 >>> "1.3.0a1" in Specifier(">=1.2.3")
528 True
529 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
530 True
531 """
532 return self.contains(item)
534 def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
535 """Return whether or not the item is contained in this specifier.
537 :param item:
538 The item to check for, which can be a version string or a
539 :class:`Version` instance.
540 :param prereleases:
541 Whether or not to match prereleases with this Specifier. If set to
542 ``None`` (the default), it will follow the recommendation from
543 :pep:`440` and match prereleases, as there are no other versions.
545 >>> Specifier(">=1.2.3").contains("1.2.3")
546 True
547 >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
548 True
549 >>> Specifier(">=1.2.3").contains("1.0.0")
550 False
551 >>> Specifier(">=1.2.3").contains("1.3.0a1")
552 True
553 >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1")
554 False
555 >>> Specifier(">=1.2.3").contains("1.3.0a1")
556 True
557 """
559 return bool(list(self.filter([item], prereleases=prereleases)))
561 def filter(
562 self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
563 ) -> Iterator[UnparsedVersionVar]:
564 """Filter items in the given iterable, that match the specifier.
566 :param iterable:
567 An iterable that can contain version strings and :class:`Version` instances.
568 The items in the iterable will be filtered according to the specifier.
569 :param prereleases:
570 Whether or not to allow prereleases in the returned iterator. If set to
571 ``None`` (the default), it will follow the recommendation from :pep:`440`
572 and match prereleases if there are no other versions.
574 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
575 ['1.3']
576 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
577 ['1.2.3', '1.3', <Version('1.4')>]
578 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
579 ['1.5a1']
580 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
581 ['1.3', '1.5a1']
582 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
583 ['1.3', '1.5a1']
584 """
585 prereleases_versions = []
586 found_non_prereleases = False
588 # Determine if to include prereleases by default
589 include_prereleases = (
590 prereleases if prereleases is not None else self.prereleases
591 )
593 # Get the matching operator
594 operator_callable = self._get_operator(self.operator)
596 # Filter versions
597 for version in iterable:
598 parsed_version = _coerce_version(version)
599 if parsed_version is None:
600 continue
602 if operator_callable(parsed_version, self.version):
603 # If it's not a prerelease or prereleases are allowed, yield it directly
604 if not parsed_version.is_prerelease or include_prereleases:
605 found_non_prereleases = True
606 yield version
607 # Otherwise collect prereleases for potential later use
608 elif prereleases is None and self._prereleases is not False:
609 prereleases_versions.append(version)
611 # If no non-prereleases were found and prereleases weren't
612 # explicitly forbidden, yield the collected prereleases
613 if (
614 not found_non_prereleases
615 and prereleases is None
616 and self._prereleases is not False
617 ):
618 yield from prereleases_versions
621_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
624def _version_split(version: str) -> list[str]:
625 """Split version into components.
627 The split components are intended for version comparison. The logic does
628 not attempt to retain the original version string, so joining the
629 components back with :func:`_version_join` may not produce the original
630 version string.
631 """
632 result: list[str] = []
634 epoch, _, rest = version.rpartition("!")
635 result.append(epoch or "0")
637 for item in rest.split("."):
638 match = _prefix_regex.search(item)
639 if match:
640 result.extend(match.groups())
641 else:
642 result.append(item)
643 return result
646def _version_join(components: list[str]) -> str:
647 """Join split version components into a version string.
649 This function assumes the input came from :func:`_version_split`, where the
650 first component must be the epoch (either empty or numeric), and all other
651 components numeric.
652 """
653 epoch, *rest = components
654 return f"{epoch}!{'.'.join(rest)}"
657def _is_not_suffix(segment: str) -> bool:
658 return not any(
659 segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
660 )
663def _pad_version(left: list[str], right: list[str]) -> tuple[list[str], list[str]]:
664 left_split, right_split = [], []
666 # Get the release segment of our versions
667 left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
668 right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
670 # Get the rest of our versions
671 left_split.append(left[len(left_split[0]) :])
672 right_split.append(right[len(right_split[0]) :])
674 # Insert our padding
675 left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
676 right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
678 return (
679 list(itertools.chain.from_iterable(left_split)),
680 list(itertools.chain.from_iterable(right_split)),
681 )
684class SpecifierSet(BaseSpecifier):
685 """This class abstracts handling of a set of version specifiers.
687 It can be passed a single specifier (``>=3.0``), a comma-separated list of
688 specifiers (``>=3.0,!=3.1``), or no specifier at all.
689 """
691 def __init__(
692 self,
693 specifiers: str | Iterable[Specifier] = "",
694 prereleases: bool | None = None,
695 ) -> None:
696 """Initialize a SpecifierSet instance.
698 :param specifiers:
699 The string representation of a specifier or a comma-separated list of
700 specifiers which will be parsed and normalized before use.
701 May also be an iterable of ``Specifier`` instances, which will be used
702 as is.
703 :param prereleases:
704 This tells the SpecifierSet if it should accept prerelease versions if
705 applicable or not. The default of ``None`` will autodetect it from the
706 given specifiers.
708 :raises InvalidSpecifier:
709 If the given ``specifiers`` are not parseable than this exception will be
710 raised.
711 """
713 if isinstance(specifiers, str):
714 # Split on `,` to break each individual specifier into its own item, and
715 # strip each item to remove leading/trailing whitespace.
716 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
718 # Make each individual specifier a Specifier and save in a frozen set
719 # for later.
720 self._specs = frozenset(map(Specifier, split_specifiers))
721 else:
722 # Save the supplied specifiers in a frozen set.
723 self._specs = frozenset(specifiers)
725 # Store our prereleases value so we can use it later to determine if
726 # we accept prereleases or not.
727 self._prereleases = prereleases
729 @property
730 def prereleases(self) -> bool | None:
731 # If we have been given an explicit prerelease modifier, then we'll
732 # pass that through here.
733 if self._prereleases is not None:
734 return self._prereleases
736 # If we don't have any specifiers, and we don't have a forced value,
737 # then we'll just return None since we don't know if this should have
738 # pre-releases or not.
739 if not self._specs:
740 return None
742 # Otherwise we'll see if any of the given specifiers accept
743 # prereleases, if any of them do we'll return True, otherwise False.
744 if any(s.prereleases for s in self._specs):
745 return True
747 return None
749 @prereleases.setter
750 def prereleases(self, value: bool | None) -> None:
751 self._prereleases = value
753 def __repr__(self) -> str:
754 """A representation of the specifier set that shows all internal state.
756 Note that the ordering of the individual specifiers within the set may not
757 match the input string.
759 >>> SpecifierSet('>=1.0.0,!=2.0.0')
760 <SpecifierSet('!=2.0.0,>=1.0.0')>
761 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
762 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
763 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
764 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
765 """
766 pre = (
767 f", prereleases={self.prereleases!r}"
768 if self._prereleases is not None
769 else ""
770 )
772 return f"<SpecifierSet({str(self)!r}{pre})>"
774 def __str__(self) -> str:
775 """A string representation of the specifier set that can be round-tripped.
777 Note that the ordering of the individual specifiers within the set may not
778 match the input string.
780 >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
781 '!=1.0.1,>=1.0.0'
782 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
783 '!=1.0.1,>=1.0.0'
784 """
785 return ",".join(sorted(str(s) for s in self._specs))
787 def __hash__(self) -> int:
788 return hash(self._specs)
790 def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
791 """Return a SpecifierSet which is a combination of the two sets.
793 :param other: The other object to combine with.
795 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
796 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
797 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
798 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
799 """
800 if isinstance(other, str):
801 other = SpecifierSet(other)
802 elif not isinstance(other, SpecifierSet):
803 return NotImplemented
805 specifier = SpecifierSet()
806 specifier._specs = frozenset(self._specs | other._specs)
808 if self._prereleases is None and other._prereleases is not None:
809 specifier._prereleases = other._prereleases
810 elif (
811 self._prereleases is not None and other._prereleases is None
812 ) or self._prereleases == other._prereleases:
813 specifier._prereleases = self._prereleases
814 else:
815 raise ValueError(
816 "Cannot combine SpecifierSets with True and False prerelease overrides."
817 )
819 return specifier
821 def __eq__(self, other: object) -> bool:
822 """Whether or not the two SpecifierSet-like objects are equal.
824 :param other: The other object to check against.
826 The value of :attr:`prereleases` is ignored.
828 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
829 True
830 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
831 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
832 True
833 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
834 True
835 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
836 False
837 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
838 False
839 """
840 if isinstance(other, (str, Specifier)):
841 other = SpecifierSet(str(other))
842 elif not isinstance(other, SpecifierSet):
843 return NotImplemented
845 return self._specs == other._specs
847 def __len__(self) -> int:
848 """Returns the number of specifiers in this specifier set."""
849 return len(self._specs)
851 def __iter__(self) -> Iterator[Specifier]:
852 """
853 Returns an iterator over all the underlying :class:`Specifier` instances
854 in this specifier set.
856 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
857 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
858 """
859 return iter(self._specs)
861 def __contains__(self, item: UnparsedVersion) -> bool:
862 """Return whether or not the item is contained in this specifier.
864 :param item: The item to check for.
866 This is used for the ``in`` operator and behaves the same as
867 :meth:`contains` with no ``prereleases`` argument passed.
869 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
870 True
871 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
872 True
873 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
874 False
875 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
876 True
877 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
878 True
879 """
880 return self.contains(item)
882 def contains(
883 self,
884 item: UnparsedVersion,
885 prereleases: bool | None = None,
886 installed: bool | None = None,
887 ) -> bool:
888 """Return whether or not the item is contained in this SpecifierSet.
890 :param item:
891 The item to check for, which can be a version string or a
892 :class:`Version` instance.
893 :param prereleases:
894 Whether or not to match prereleases with this SpecifierSet. If set to
895 ``None`` (the default), it will follow the recommendation from :pep:`440`
896 and match prereleases, as there are no other versions.
897 :param installed:
898 Whether or not the item is installed. If set to ``True``, it will
899 accept prerelease versions even if the specifier does not allow them.
901 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
902 True
903 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
904 True
905 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
906 False
907 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
908 True
909 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")
910 False
911 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
912 True
913 """
914 version = _coerce_version(item)
915 if version is None:
916 return False
918 if installed and version.is_prerelease:
919 prereleases = True
921 return bool(list(self.filter([version], prereleases=prereleases)))
923 def filter(
924 self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
925 ) -> Iterator[UnparsedVersionVar]:
926 """Filter items in the given iterable, that match the specifiers in this set.
928 :param iterable:
929 An iterable that can contain version strings and :class:`Version` instances.
930 The items in the iterable will be filtered according to the specifier.
931 :param prereleases:
932 Whether or not to allow prereleases in the returned iterator. If set to
933 ``None`` (the default), it will follow the recommendation from :pep:`440`
934 and match prereleases if there are no other versions.
936 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
937 ['1.3']
938 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
939 ['1.3', <Version('1.4')>]
940 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
941 ['1.5a1']
942 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
943 ['1.3', '1.5a1']
944 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
945 ['1.3', '1.5a1']
947 An "empty" SpecifierSet will filter items based on the presence of prerelease
948 versions in the set.
950 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
951 ['1.3']
952 >>> list(SpecifierSet("").filter(["1.5a1"]))
953 ['1.5a1']
954 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
955 ['1.3', '1.5a1']
956 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
957 ['1.3', '1.5a1']
958 """
959 # Determine if we're forcing a prerelease or not, if we're not forcing
960 # one for this particular filter call, then we'll use whatever the
961 # SpecifierSet thinks for whether or not we should support prereleases.
962 if prereleases is None and self.prereleases is not None:
963 prereleases = self.prereleases
965 # If we have any specifiers, then we want to wrap our iterable in the
966 # filter method for each one, this will act as a logical AND amongst
967 # each specifier.
968 if self._specs:
969 # When prereleases is None, we need to let all versions through
970 # the individual filters, then decide about prereleases at the end
971 # based on whether any non-prereleases matched ALL specs.
972 for spec in self._specs:
973 iterable = spec.filter(
974 iterable, prereleases=True if prereleases is None else prereleases
975 )
977 if prereleases is not None:
978 # If we have a forced prereleases value,
979 # we can immediately return the iterator.
980 return iter(iterable)
981 else:
982 # Handle empty SpecifierSet cases where prereleases is not None.
983 if prereleases is True:
984 return iter(iterable)
986 if prereleases is False:
987 return (
988 item
989 for item in iterable
990 if (version := _coerce_version(item)) is not None
991 and not version.is_prerelease
992 )
994 # Finally if prereleases is None, apply PEP 440 logic:
995 # exclude prereleases unless there are no final releases that matched.
996 filtered: list[UnparsedVersionVar] = []
997 found_prereleases: list[UnparsedVersionVar] = []
999 for item in iterable:
1000 parsed_version = _coerce_version(item)
1001 if parsed_version is None:
1002 continue
1003 if parsed_version.is_prerelease:
1004 found_prereleases.append(item)
1005 else:
1006 filtered.append(item)
1008 return iter(filtered if filtered else found_prereleases)