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
11import abc
12import itertools
13import re
14from typing import Callable, Iterable, Iterator, List, Optional, Tuple, TypeVar, Union
15
16from .utils import canonicalize_version
17from .version import Version
18
19UnparsedVersion = Union[Version, str]
20UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
21CallableOperator = Callable[[Version, str], bool]
22
23
24def _coerce_version(version: UnparsedVersion) -> Version:
25 if not isinstance(version, Version):
26 version = Version(version)
27 return version
28
29
30class InvalidSpecifier(ValueError):
31 """
32 Raised when attempting to create a :class:`Specifier` with a specifier
33 string that is invalid.
34
35 >>> Specifier("lolwat")
36 Traceback (most recent call last):
37 ...
38 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
39 """
40
41
42class BaseSpecifier(metaclass=abc.ABCMeta):
43 @abc.abstractmethod
44 def __str__(self) -> str:
45 """
46 Returns the str representation of this Specifier-like object. This
47 should be representative of the Specifier itself.
48 """
49
50 @abc.abstractmethod
51 def __hash__(self) -> int:
52 """
53 Returns a hash value for this Specifier-like object.
54 """
55
56 @abc.abstractmethod
57 def __eq__(self, other: object) -> bool:
58 """
59 Returns a boolean representing whether or not the two Specifier-like
60 objects are equal.
61
62 :param other: The other object to check against.
63 """
64
65 @property
66 @abc.abstractmethod
67 def prereleases(self) -> Optional[bool]:
68 """Whether or not pre-releases as a whole are allowed.
69
70 This can be set to either ``True`` or ``False`` to explicitly enable or disable
71 prereleases or it can be set to ``None`` (the default) to use default semantics.
72 """
73
74 @prereleases.setter
75 def prereleases(self, value: bool) -> None:
76 """Setter for :attr:`prereleases`.
77
78 :param value: The value to set.
79 """
80
81 @abc.abstractmethod
82 def contains(self, item: str, prereleases: Optional[bool] = None) -> bool:
83 """
84 Determines if the given item is contained within this specifier.
85 """
86
87 @abc.abstractmethod
88 def filter(
89 self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None
90 ) -> Iterator[UnparsedVersionVar]:
91 """
92 Takes an iterable of items and filters them so that only items which
93 are contained within this specifier are allowed in it.
94 """
95
96
97class Specifier(BaseSpecifier):
98 """This class abstracts handling of version specifiers.
99
100 .. tip::
101
102 It is generally not required to instantiate this manually. You should instead
103 prefer to work with :class:`SpecifierSet` instead, which can parse
104 comma-separated version specifiers (which is what package metadata contains).
105 """
106
107 _operator_regex_str = r"""
108 (?P<operator>(~=|==|!=|<=|>=|<|>|===))
109 """
110 _version_regex_str = r"""
111 (?P<version>
112 (?:
113 # The identity operators allow for an escape hatch that will
114 # do an exact string match of the version you wish to install.
115 # This will not be parsed by PEP 440 and we cannot determine
116 # any semantic meaning from it. This operator is discouraged
117 # but included entirely as an escape hatch.
118 (?<====) # Only match for the identity operator
119 \s*
120 [^\s;)]* # The arbitrary version can be just about anything,
121 # we match everything except for whitespace, a
122 # semi-colon for marker support, and a closing paren
123 # since versions can be enclosed in them.
124 )
125 |
126 (?:
127 # The (non)equality operators allow for wild card and local
128 # versions to be specified so we have to define these two
129 # operators separately to enable that.
130 (?<===|!=) # Only match for equals and not equals
131
132 \s*
133 v?
134 (?:[0-9]+!)? # epoch
135 [0-9]+(?:\.[0-9]+)* # release
136
137 # You cannot use a wild card and a pre-release, post-release, a dev or
138 # local version together so group them with a | and make them optional.
139 (?:
140 \.\* # Wild card syntax of .*
141 |
142 (?: # pre release
143 [-_\.]?
144 (alpha|beta|preview|pre|a|b|c|rc)
145 [-_\.]?
146 [0-9]*
147 )?
148 (?: # post release
149 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
150 )?
151 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
152 (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
153 )?
154 )
155 |
156 (?:
157 # The compatible operator requires at least two digits in the
158 # release segment.
159 (?<=~=) # Only match for the compatible operator
160
161 \s*
162 v?
163 (?:[0-9]+!)? # epoch
164 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
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 )
176 |
177 (?:
178 # All other operators only allow a sub set of what the
179 # (non)equality operators do. Specifically they do not allow
180 # local versions to be specified nor do they allow the prefix
181 # matching wild cards.
182 (?<!==|!=|~=) # We have special cases for these
183 # operators so we want to make sure they
184 # don't match here.
185
186 \s*
187 v?
188 (?:[0-9]+!)? # epoch
189 [0-9]+(?:\.[0-9]+)* # release
190 (?: # pre release
191 [-_\.]?
192 (alpha|beta|preview|pre|a|b|c|rc)
193 [-_\.]?
194 [0-9]*
195 )?
196 (?: # post release
197 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
198 )?
199 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
200 )
201 )
202 """
203
204 _regex = re.compile(
205 r"^\s*" + _operator_regex_str + _version_regex_str + r"\s*$",
206 re.VERBOSE | re.IGNORECASE,
207 )
208
209 _operators = {
210 "~=": "compatible",
211 "==": "equal",
212 "!=": "not_equal",
213 "<=": "less_than_equal",
214 ">=": "greater_than_equal",
215 "<": "less_than",
216 ">": "greater_than",
217 "===": "arbitrary",
218 }
219
220 def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None:
221 """Initialize a Specifier instance.
222
223 :param spec:
224 The string representation of a specifier which will be parsed and
225 normalized before use.
226 :param prereleases:
227 This tells the specifier if it should accept prerelease versions if
228 applicable or not. The default of ``None`` will autodetect it from the
229 given specifiers.
230 :raises InvalidSpecifier:
231 If the given specifier is invalid (i.e. bad syntax).
232 """
233 match = self._regex.search(spec)
234 if not match:
235 raise InvalidSpecifier(f"Invalid specifier: '{spec}'")
236
237 self._spec: Tuple[str, str] = (
238 match.group("operator").strip(),
239 match.group("version").strip(),
240 )
241
242 # Store whether or not this Specifier should accept prereleases
243 self._prereleases = prereleases
244
245 # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515
246 @property # type: ignore[override]
247 def prereleases(self) -> bool:
248 # If there is an explicit prereleases set for this, then we'll just
249 # blindly use that.
250 if self._prereleases is not None:
251 return self._prereleases
252
253 # Look at all of our specifiers and determine if they are inclusive
254 # operators, and if they are if they are including an explicit
255 # prerelease.
256 operator, version = self._spec
257 if operator in ["==", ">=", "<=", "~=", "==="]:
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
368 # Compatible releases have an equivalent combination of >= and ==. That
369 # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
370 # implement this in terms of the other specifiers instead of
371 # implementing it ourselves. The only thing we need to do is construct
372 # the other specifiers.
373
374 # We want everything but the last item in the version, but we want to
375 # ignore suffix segments.
376 prefix = _version_join(
377 list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
378 )
379
380 # Add the prefix notation to the end of our string
381 prefix += ".*"
382
383 return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
384 prospective, prefix
385 )
386
387 def _compare_equal(self, prospective: Version, spec: str) -> bool:
388
389 # We need special logic to handle prefix matching
390 if spec.endswith(".*"):
391 # In the case of prefix matching we want to ignore local segment.
392 normalized_prospective = canonicalize_version(
393 prospective.public, strip_trailing_zero=False
394 )
395 # Get the normalized version string ignoring the trailing .*
396 normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)
397 # Split the spec out by bangs and dots, and pretend that there is
398 # an implicit dot in between a release segment and a pre-release segment.
399 split_spec = _version_split(normalized_spec)
400
401 # Split the prospective version out by bangs and dots, and pretend
402 # that there is an implicit dot in between a release segment and
403 # a pre-release segment.
404 split_prospective = _version_split(normalized_prospective)
405
406 # 0-pad the prospective version before shortening it to get the correct
407 # shortened version.
408 padded_prospective, _ = _pad_version(split_prospective, split_spec)
409
410 # Shorten the prospective version to be the same length as the spec
411 # so that we can determine if the specifier is a prefix of the
412 # prospective version or not.
413 shortened_prospective = padded_prospective[: len(split_spec)]
414
415 return shortened_prospective == split_spec
416 else:
417 # Convert our spec string into a Version
418 spec_version = Version(spec)
419
420 # If the specifier does not have a local segment, then we want to
421 # act as if the prospective version also does not have a local
422 # segment.
423 if not spec_version.local:
424 prospective = Version(prospective.public)
425
426 return prospective == spec_version
427
428 def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
429 return not self._compare_equal(prospective, spec)
430
431 def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
432
433 # NB: Local version identifiers are NOT permitted in the version
434 # specifier, so local version labels can be universally removed from
435 # the prospective version.
436 return Version(prospective.public) <= Version(spec)
437
438 def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
439
440 # NB: Local version identifiers are NOT permitted in the version
441 # specifier, so local version labels can be universally removed from
442 # the prospective version.
443 return Version(prospective.public) >= Version(spec)
444
445 def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
446
447 # Convert our spec to a Version instance, since we'll want to work with
448 # it as a version.
449 spec = Version(spec_str)
450
451 # Check to see if the prospective version is less than the spec
452 # version. If it's not we can short circuit and just return False now
453 # instead of doing extra unneeded work.
454 if not prospective < spec:
455 return False
456
457 # This special case is here so that, unless the specifier itself
458 # includes is a pre-release version, that we do not accept pre-release
459 # versions for the version mentioned in the specifier (e.g. <3.1 should
460 # not match 3.1.dev0, but should match 3.0.dev0).
461 if not spec.is_prerelease and prospective.is_prerelease:
462 if Version(prospective.base_version) == Version(spec.base_version):
463 return False
464
465 # If we've gotten to here, it means that prospective version is both
466 # less than the spec version *and* it's not a pre-release of the same
467 # version in the spec.
468 return True
469
470 def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
471
472 # Convert our spec to a Version instance, since we'll want to work with
473 # it as a version.
474 spec = Version(spec_str)
475
476 # Check to see if the prospective version is greater than the spec
477 # version. If it's not we can short circuit and just return False now
478 # instead of doing extra unneeded work.
479 if not prospective > spec:
480 return False
481
482 # This special case is here so that, unless the specifier itself
483 # includes is a post-release version, that we do not accept
484 # post-release versions for the version mentioned in the specifier
485 # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
486 if not spec.is_postrelease and prospective.is_postrelease:
487 if Version(prospective.base_version) == Version(spec.base_version):
488 return False
489
490 # Ensure that we do not allow a local version of the version mentioned
491 # in the specifier, which is technically greater than, to match.
492 if prospective.local is not None:
493 if Version(prospective.base_version) == Version(spec.base_version):
494 return False
495
496 # If we've gotten to here, it means that prospective version is both
497 # greater than the spec version *and* it's not a pre-release of the
498 # same version in the spec.
499 return True
500
501 def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
502 return str(prospective).lower() == str(spec).lower()
503
504 def __contains__(self, item: Union[str, Version]) -> bool:
505 """Return whether or not the item is contained in this specifier.
506
507 :param item: The item to check for.
508
509 This is used for the ``in`` operator and behaves the same as
510 :meth:`contains` with no ``prereleases`` argument passed.
511
512 >>> "1.2.3" in Specifier(">=1.2.3")
513 True
514 >>> Version("1.2.3") in Specifier(">=1.2.3")
515 True
516 >>> "1.0.0" in Specifier(">=1.2.3")
517 False
518 >>> "1.3.0a1" in Specifier(">=1.2.3")
519 False
520 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
521 True
522 """
523 return self.contains(item)
524
525 def contains(
526 self, item: UnparsedVersion, prereleases: Optional[bool] = None
527 ) -> bool:
528 """Return whether or not the item is contained in this specifier.
529
530 :param item:
531 The item to check for, which can be a version string or a
532 :class:`Version` instance.
533 :param prereleases:
534 Whether or not to match prereleases with this Specifier. If set to
535 ``None`` (the default), it uses :attr:`prereleases` to determine
536 whether or not prereleases are allowed.
537
538 >>> Specifier(">=1.2.3").contains("1.2.3")
539 True
540 >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
541 True
542 >>> Specifier(">=1.2.3").contains("1.0.0")
543 False
544 >>> Specifier(">=1.2.3").contains("1.3.0a1")
545 False
546 >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1")
547 True
548 >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True)
549 True
550 """
551
552 # Determine if prereleases are to be allowed or not.
553 if prereleases is None:
554 prereleases = self.prereleases
555
556 # Normalize item to a Version, this allows us to have a shortcut for
557 # "2.0" in Specifier(">=2")
558 normalized_item = _coerce_version(item)
559
560 # Determine if we should be supporting prereleases in this specifier
561 # or not, if we do not support prereleases than we can short circuit
562 # logic if this version is a prereleases.
563 if normalized_item.is_prerelease and not prereleases:
564 return False
565
566 # Actually do the comparison to determine if this item is contained
567 # within this Specifier or not.
568 operator_callable: CallableOperator = self._get_operator(self.operator)
569 return operator_callable(normalized_item, self.version)
570
571 def filter(
572 self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None
573 ) -> Iterator[UnparsedVersionVar]:
574 """Filter items in the given iterable, that match the specifier.
575
576 :param iterable:
577 An iterable that can contain version strings and :class:`Version` instances.
578 The items in the iterable will be filtered according to the specifier.
579 :param prereleases:
580 Whether or not to allow prereleases in the returned iterator. If set to
581 ``None`` (the default), it will be intelligently decide whether to allow
582 prereleases or not (based on the :attr:`prereleases` attribute, and
583 whether the only versions matching are prereleases).
584
585 This method is smarter than just ``filter(Specifier().contains, [...])``
586 because it implements the rule from :pep:`440` that a prerelease item
587 SHOULD be accepted if no other versions match the given specifier.
588
589 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
590 ['1.3']
591 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
592 ['1.2.3', '1.3', <Version('1.4')>]
593 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
594 ['1.5a1']
595 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
596 ['1.3', '1.5a1']
597 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
598 ['1.3', '1.5a1']
599 """
600
601 yielded = False
602 found_prereleases = []
603
604 kw = {"prereleases": prereleases if prereleases is not None else True}
605
606 # Attempt to iterate over all the values in the iterable and if any of
607 # them match, yield them.
608 for version in iterable:
609 parsed_version = _coerce_version(version)
610
611 if self.contains(parsed_version, **kw):
612 # If our version is a prerelease, and we were not set to allow
613 # prereleases, then we'll store it for later in case nothing
614 # else matches this specifier.
615 if parsed_version.is_prerelease and not (
616 prereleases or self.prereleases
617 ):
618 found_prereleases.append(version)
619 # Either this is not a prerelease, or we should have been
620 # accepting prereleases from the beginning.
621 else:
622 yielded = True
623 yield version
624
625 # Now that we've iterated over everything, determine if we've yielded
626 # any values, and if we have not and we have any prereleases stored up
627 # then we will go ahead and yield the prereleases.
628 if not yielded and found_prereleases:
629 for version in found_prereleases:
630 yield version
631
632
633_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
634
635
636def _version_split(version: str) -> List[str]:
637 """Split version into components.
638
639 The split components are intended for version comparison. The logic does
640 not attempt to retain the original version string, so joining the
641 components back with :func:`_version_join` may not produce the original
642 version string.
643 """
644 result: List[str] = []
645
646 epoch, _, rest = version.rpartition("!")
647 result.append(epoch or "0")
648
649 for item in rest.split("."):
650 match = _prefix_regex.search(item)
651 if match:
652 result.extend(match.groups())
653 else:
654 result.append(item)
655 return result
656
657
658def _version_join(components: List[str]) -> str:
659 """Join split version components into a version string.
660
661 This function assumes the input came from :func:`_version_split`, where the
662 first component must be the epoch (either empty or numeric), and all other
663 components numeric.
664 """
665 epoch, *rest = components
666 return f"{epoch}!{'.'.join(rest)}"
667
668
669def _is_not_suffix(segment: str) -> bool:
670 return not any(
671 segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
672 )
673
674
675def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]:
676 left_split, right_split = [], []
677
678 # Get the release segment of our versions
679 left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
680 right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
681
682 # Get the rest of our versions
683 left_split.append(left[len(left_split[0]) :])
684 right_split.append(right[len(right_split[0]) :])
685
686 # Insert our padding
687 left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
688 right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
689
690 return (
691 list(itertools.chain.from_iterable(left_split)),
692 list(itertools.chain.from_iterable(right_split)),
693 )
694
695
696class SpecifierSet(BaseSpecifier):
697 """This class abstracts handling of a set of version specifiers.
698
699 It can be passed a single specifier (``>=3.0``), a comma-separated list of
700 specifiers (``>=3.0,!=3.1``), or no specifier at all.
701 """
702
703 def __init__(
704 self, specifiers: str = "", prereleases: Optional[bool] = None
705 ) -> None:
706 """Initialize a SpecifierSet instance.
707
708 :param specifiers:
709 The string representation of a specifier or a comma-separated list of
710 specifiers which will be parsed and normalized before use.
711 :param prereleases:
712 This tells the SpecifierSet if it should accept prerelease versions if
713 applicable or not. The default of ``None`` will autodetect it from the
714 given specifiers.
715
716 :raises InvalidSpecifier:
717 If the given ``specifiers`` are not parseable than this exception will be
718 raised.
719 """
720
721 # Split on `,` to break each individual specifier into it's own item, and
722 # strip each item to remove leading/trailing whitespace.
723 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
724
725 # Make each individual specifier a Specifier and save in a frozen set for later.
726 self._specs = frozenset(map(Specifier, split_specifiers))
727
728 # Store our prereleases value so we can use it later to determine if
729 # we accept prereleases or not.
730 self._prereleases = prereleases
731
732 @property
733 def prereleases(self) -> Optional[bool]:
734 # If we have been given an explicit prerelease modifier, then we'll
735 # pass that through here.
736 if self._prereleases is not None:
737 return self._prereleases
738
739 # If we don't have any specifiers, and we don't have a forced value,
740 # then we'll just return None since we don't know if this should have
741 # pre-releases or not.
742 if not self._specs:
743 return None
744
745 # Otherwise we'll see if any of the given specifiers accept
746 # prereleases, if any of them do we'll return True, otherwise False.
747 return any(s.prereleases for s in self._specs)
748
749 @prereleases.setter
750 def prereleases(self, value: bool) -> None:
751 self._prereleases = value
752
753 def __repr__(self) -> str:
754 """A representation of the specifier set that shows all internal state.
755
756 Note that the ordering of the individual specifiers within the set may not
757 match the input string.
758
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 )
771
772 return f"<SpecifierSet({str(self)!r}{pre})>"
773
774 def __str__(self) -> str:
775 """A string representation of the specifier set that can be round-tripped.
776
777 Note that the ordering of the individual specifiers within the set may not
778 match the input string.
779
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))
786
787 def __hash__(self) -> int:
788 return hash(self._specs)
789
790 def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet":
791 """Return a SpecifierSet which is a combination of the two sets.
792
793 :param other: The other object to combine with.
794
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
804
805 specifier = SpecifierSet()
806 specifier._specs = frozenset(self._specs | other._specs)
807
808 if self._prereleases is None and other._prereleases is not None:
809 specifier._prereleases = other._prereleases
810 elif self._prereleases is not None and other._prereleases is None:
811 specifier._prereleases = self._prereleases
812 elif self._prereleases == other._prereleases:
813 specifier._prereleases = self._prereleases
814 else:
815 raise ValueError(
816 "Cannot combine SpecifierSets with True and False prerelease "
817 "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: Optional[bool] = None,
887 installed: Optional[bool] = 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: Optional[bool] = 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)