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