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 # 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: Union[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(
520 self, item: UnparsedVersion, prereleases: Optional[bool] = None
521 ) -> bool:
522 """Return whether or not the item is contained in this specifier.
523
524 :param item:
525 The item to check for, which can be a version string or a
526 :class:`Version` instance.
527 :param prereleases:
528 Whether or not to match prereleases with this Specifier. If set to
529 ``None`` (the default), it uses :attr:`prereleases` to determine
530 whether or not prereleases are allowed.
531
532 >>> Specifier(">=1.2.3").contains("1.2.3")
533 True
534 >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
535 True
536 >>> Specifier(">=1.2.3").contains("1.0.0")
537 False
538 >>> Specifier(">=1.2.3").contains("1.3.0a1")
539 False
540 >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1")
541 True
542 >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True)
543 True
544 """
545
546 # Determine if prereleases are to be allowed or not.
547 if prereleases is None:
548 prereleases = self.prereleases
549
550 # Normalize item to a Version, this allows us to have a shortcut for
551 # "2.0" in Specifier(">=2")
552 normalized_item = _coerce_version(item)
553
554 # Determine if we should be supporting prereleases in this specifier
555 # or not, if we do not support prereleases than we can short circuit
556 # logic if this version is a prereleases.
557 if normalized_item.is_prerelease and not prereleases:
558 return False
559
560 # Actually do the comparison to determine if this item is contained
561 # within this Specifier or not.
562 operator_callable: CallableOperator = self._get_operator(self.operator)
563 return operator_callable(normalized_item, self.version)
564
565 def filter(
566 self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None
567 ) -> Iterator[UnparsedVersionVar]:
568 """Filter items in the given iterable, that match the specifier.
569
570 :param iterable:
571 An iterable that can contain version strings and :class:`Version` instances.
572 The items in the iterable will be filtered according to the specifier.
573 :param prereleases:
574 Whether or not to allow prereleases in the returned iterator. If set to
575 ``None`` (the default), it will be intelligently decide whether to allow
576 prereleases or not (based on the :attr:`prereleases` attribute, and
577 whether the only versions matching are prereleases).
578
579 This method is smarter than just ``filter(Specifier().contains, [...])``
580 because it implements the rule from :pep:`440` that a prerelease item
581 SHOULD be accepted if no other versions match the given specifier.
582
583 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
584 ['1.3']
585 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
586 ['1.2.3', '1.3', <Version('1.4')>]
587 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
588 ['1.5a1']
589 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
590 ['1.3', '1.5a1']
591 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
592 ['1.3', '1.5a1']
593 """
594
595 yielded = False
596 found_prereleases = []
597
598 kw = {"prereleases": prereleases if prereleases is not None else True}
599
600 # Attempt to iterate over all the values in the iterable and if any of
601 # them match, yield them.
602 for version in iterable:
603 parsed_version = _coerce_version(version)
604
605 if self.contains(parsed_version, **kw):
606 # If our version is a prerelease, and we were not set to allow
607 # prereleases, then we'll store it for later in case nothing
608 # else matches this specifier.
609 if parsed_version.is_prerelease and not (
610 prereleases or self.prereleases
611 ):
612 found_prereleases.append(version)
613 # Either this is not a prerelease, or we should have been
614 # accepting prereleases from the beginning.
615 else:
616 yielded = True
617 yield version
618
619 # Now that we've iterated over everything, determine if we've yielded
620 # any values, and if we have not and we have any prereleases stored up
621 # then we will go ahead and yield the prereleases.
622 if not yielded and found_prereleases:
623 for version in found_prereleases:
624 yield version
625
626
627_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
628
629
630def _version_split(version: str) -> List[str]:
631 """Split version into components.
632
633 The split components are intended for version comparison. The logic does
634 not attempt to retain the original version string, so joining the
635 components back with :func:`_version_join` may not produce the original
636 version string.
637 """
638 result: List[str] = []
639
640 epoch, _, rest = version.rpartition("!")
641 result.append(epoch or "0")
642
643 for item in rest.split("."):
644 match = _prefix_regex.search(item)
645 if match:
646 result.extend(match.groups())
647 else:
648 result.append(item)
649 return result
650
651
652def _version_join(components: List[str]) -> str:
653 """Join split version components into a version string.
654
655 This function assumes the input came from :func:`_version_split`, where the
656 first component must be the epoch (either empty or numeric), and all other
657 components numeric.
658 """
659 epoch, *rest = components
660 return f"{epoch}!{'.'.join(rest)}"
661
662
663def _is_not_suffix(segment: str) -> bool:
664 return not any(
665 segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
666 )
667
668
669def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]:
670 left_split, right_split = [], []
671
672 # Get the release segment of our versions
673 left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
674 right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
675
676 # Get the rest of our versions
677 left_split.append(left[len(left_split[0]) :])
678 right_split.append(right[len(right_split[0]) :])
679
680 # Insert our padding
681 left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
682 right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
683
684 return (
685 list(itertools.chain.from_iterable(left_split)),
686 list(itertools.chain.from_iterable(right_split)),
687 )
688
689
690class SpecifierSet(BaseSpecifier):
691 """This class abstracts handling of a set of version specifiers.
692
693 It can be passed a single specifier (``>=3.0``), a comma-separated list of
694 specifiers (``>=3.0,!=3.1``), or no specifier at all.
695 """
696
697 def __init__(
698 self, specifiers: str = "", prereleases: Optional[bool] = 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 :param prereleases:
706 This tells the SpecifierSet if it should accept prerelease versions if
707 applicable or not. The default of ``None`` will autodetect it from the
708 given specifiers.
709
710 :raises InvalidSpecifier:
711 If the given ``specifiers`` are not parseable than this exception will be
712 raised.
713 """
714
715 # Split on `,` to break each individual specifier into it's own item, and
716 # strip each item to remove leading/trailing whitespace.
717 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
718
719 # Make each individual specifier a Specifier and save in a frozen set for later.
720 self._specs = frozenset(map(Specifier, split_specifiers))
721
722 # Store our prereleases value so we can use it later to determine if
723 # we accept prereleases or not.
724 self._prereleases = prereleases
725
726 @property
727 def prereleases(self) -> Optional[bool]:
728 # If we have been given an explicit prerelease modifier, then we'll
729 # pass that through here.
730 if self._prereleases is not None:
731 return self._prereleases
732
733 # If we don't have any specifiers, and we don't have a forced value,
734 # then we'll just return None since we don't know if this should have
735 # pre-releases or not.
736 if not self._specs:
737 return None
738
739 # Otherwise we'll see if any of the given specifiers accept
740 # prereleases, if any of them do we'll return True, otherwise False.
741 return any(s.prereleases for s in self._specs)
742
743 @prereleases.setter
744 def prereleases(self, value: bool) -> None:
745 self._prereleases = value
746
747 def __repr__(self) -> str:
748 """A representation of the specifier set that shows all internal state.
749
750 Note that the ordering of the individual specifiers within the set may not
751 match the input string.
752
753 >>> SpecifierSet('>=1.0.0,!=2.0.0')
754 <SpecifierSet('!=2.0.0,>=1.0.0')>
755 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
756 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
757 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
758 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
759 """
760 pre = (
761 f", prereleases={self.prereleases!r}"
762 if self._prereleases is not None
763 else ""
764 )
765
766 return f"<SpecifierSet({str(self)!r}{pre})>"
767
768 def __str__(self) -> str:
769 """A string representation of the specifier set that can be round-tripped.
770
771 Note that the ordering of the individual specifiers within the set may not
772 match the input string.
773
774 >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
775 '!=1.0.1,>=1.0.0'
776 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
777 '!=1.0.1,>=1.0.0'
778 """
779 return ",".join(sorted(str(s) for s in self._specs))
780
781 def __hash__(self) -> int:
782 return hash(self._specs)
783
784 def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet":
785 """Return a SpecifierSet which is a combination of the two sets.
786
787 :param other: The other object to combine with.
788
789 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
790 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
791 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
792 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
793 """
794 if isinstance(other, str):
795 other = SpecifierSet(other)
796 elif not isinstance(other, SpecifierSet):
797 return NotImplemented
798
799 specifier = SpecifierSet()
800 specifier._specs = frozenset(self._specs | other._specs)
801
802 if self._prereleases is None and other._prereleases is not None:
803 specifier._prereleases = other._prereleases
804 elif self._prereleases is not None and other._prereleases is None:
805 specifier._prereleases = self._prereleases
806 elif self._prereleases == other._prereleases:
807 specifier._prereleases = self._prereleases
808 else:
809 raise ValueError(
810 "Cannot combine SpecifierSets with True and False prerelease "
811 "overrides."
812 )
813
814 return specifier
815
816 def __eq__(self, other: object) -> bool:
817 """Whether or not the two SpecifierSet-like objects are equal.
818
819 :param other: The other object to check against.
820
821 The value of :attr:`prereleases` is ignored.
822
823 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
824 True
825 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
826 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
827 True
828 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
829 True
830 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
831 False
832 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
833 False
834 """
835 if isinstance(other, (str, Specifier)):
836 other = SpecifierSet(str(other))
837 elif not isinstance(other, SpecifierSet):
838 return NotImplemented
839
840 return self._specs == other._specs
841
842 def __len__(self) -> int:
843 """Returns the number of specifiers in this specifier set."""
844 return len(self._specs)
845
846 def __iter__(self) -> Iterator[Specifier]:
847 """
848 Returns an iterator over all the underlying :class:`Specifier` instances
849 in this specifier set.
850
851 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
852 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
853 """
854 return iter(self._specs)
855
856 def __contains__(self, item: UnparsedVersion) -> bool:
857 """Return whether or not the item is contained in this specifier.
858
859 :param item: The item to check for.
860
861 This is used for the ``in`` operator and behaves the same as
862 :meth:`contains` with no ``prereleases`` argument passed.
863
864 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
865 True
866 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
867 True
868 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
869 False
870 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
871 False
872 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
873 True
874 """
875 return self.contains(item)
876
877 def contains(
878 self,
879 item: UnparsedVersion,
880 prereleases: Optional[bool] = None,
881 installed: Optional[bool] = None,
882 ) -> bool:
883 """Return whether or not the item is contained in this SpecifierSet.
884
885 :param item:
886 The item to check for, which can be a version string or a
887 :class:`Version` instance.
888 :param prereleases:
889 Whether or not to match prereleases with this SpecifierSet. If set to
890 ``None`` (the default), it uses :attr:`prereleases` to determine
891 whether or not prereleases are allowed.
892
893 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
894 True
895 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
896 True
897 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
898 False
899 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
900 False
901 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True).contains("1.3.0a1")
902 True
903 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
904 True
905 """
906 # Ensure that our item is a Version instance.
907 if not isinstance(item, Version):
908 item = Version(item)
909
910 # Determine if we're forcing a prerelease or not, if we're not forcing
911 # one for this particular filter call, then we'll use whatever the
912 # SpecifierSet thinks for whether or not we should support prereleases.
913 if prereleases is None:
914 prereleases = self.prereleases
915
916 # We can determine if we're going to allow pre-releases by looking to
917 # see if any of the underlying items supports them. If none of them do
918 # and this item is a pre-release then we do not allow it and we can
919 # short circuit that here.
920 # Note: This means that 1.0.dev1 would not be contained in something
921 # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0
922 if not prereleases and item.is_prerelease:
923 return False
924
925 if installed and item.is_prerelease:
926 item = Version(item.base_version)
927
928 # We simply dispatch to the underlying specs here to make sure that the
929 # given version is contained within all of them.
930 # Note: This use of all() here means that an empty set of specifiers
931 # will always return True, this is an explicit design decision.
932 return all(s.contains(item, prereleases=prereleases) for s in self._specs)
933
934 def filter(
935 self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None
936 ) -> Iterator[UnparsedVersionVar]:
937 """Filter items in the given iterable, that match the specifiers in this set.
938
939 :param iterable:
940 An iterable that can contain version strings and :class:`Version` instances.
941 The items in the iterable will be filtered according to the specifier.
942 :param prereleases:
943 Whether or not to allow prereleases in the returned iterator. If set to
944 ``None`` (the default), it will be intelligently decide whether to allow
945 prereleases or not (based on the :attr:`prereleases` attribute, and
946 whether the only versions matching are prereleases).
947
948 This method is smarter than just ``filter(SpecifierSet(...).contains, [...])``
949 because it implements the rule from :pep:`440` that a prerelease item
950 SHOULD be accepted if no other versions match the given specifier.
951
952 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
953 ['1.3']
954 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
955 ['1.3', <Version('1.4')>]
956 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
957 []
958 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
959 ['1.3', '1.5a1']
960 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
961 ['1.3', '1.5a1']
962
963 An "empty" SpecifierSet will filter items based on the presence of prerelease
964 versions in the set.
965
966 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
967 ['1.3']
968 >>> list(SpecifierSet("").filter(["1.5a1"]))
969 ['1.5a1']
970 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
971 ['1.3', '1.5a1']
972 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
973 ['1.3', '1.5a1']
974 """
975 # Determine if we're forcing a prerelease or not, if we're not forcing
976 # one for this particular filter call, then we'll use whatever the
977 # SpecifierSet thinks for whether or not we should support prereleases.
978 if prereleases is None:
979 prereleases = self.prereleases
980
981 # If we have any specifiers, then we want to wrap our iterable in the
982 # filter method for each one, this will act as a logical AND amongst
983 # each specifier.
984 if self._specs:
985 for spec in self._specs:
986 iterable = spec.filter(iterable, prereleases=bool(prereleases))
987 return iter(iterable)
988 # If we do not have any specifiers, then we need to have a rough filter
989 # which will filter out any pre-releases, unless there are no final
990 # releases.
991 else:
992 filtered: List[UnparsedVersionVar] = []
993 found_prereleases: List[UnparsedVersionVar] = []
994
995 for item in iterable:
996 parsed_version = _coerce_version(item)
997
998 # Store any item which is a pre-release for later unless we've
999 # already found a final version or we are accepting prereleases
1000 if parsed_version.is_prerelease and not prereleases:
1001 if not filtered:
1002 found_prereleases.append(item)
1003 else:
1004 filtered.append(item)
1005
1006 # If we've found no items except for pre-releases, then we'll go
1007 # ahead and use the pre-releases
1008 if not filtered and found_prereleases and prereleases is None:
1009 return iter(found_prereleases)
1010
1011 return iter(filtered)