comparison env/lib/python3.9/site-packages/pkg_resources/_vendor/packaging/specifiers.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
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 from __future__ import absolute_import, division, print_function
5
6 import abc
7 import functools
8 import itertools
9 import re
10
11 from ._compat import string_types, with_metaclass
12 from ._typing import TYPE_CHECKING
13 from .utils import canonicalize_version
14 from .version import Version, LegacyVersion, parse
15
16 if TYPE_CHECKING: # pragma: no cover
17 from typing import (
18 List,
19 Dict,
20 Union,
21 Iterable,
22 Iterator,
23 Optional,
24 Callable,
25 Tuple,
26 FrozenSet,
27 )
28
29 ParsedVersion = Union[Version, LegacyVersion]
30 UnparsedVersion = Union[Version, LegacyVersion, str]
31 CallableOperator = Callable[[ParsedVersion, str], bool]
32
33
34 class InvalidSpecifier(ValueError):
35 """
36 An invalid specifier was found, users should refer to PEP 440.
37 """
38
39
40 class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore
41 @abc.abstractmethod
42 def __str__(self):
43 # type: () -> str
44 """
45 Returns the str representation of this Specifier like object. This
46 should be representative of the Specifier itself.
47 """
48
49 @abc.abstractmethod
50 def __hash__(self):
51 # type: () -> int
52 """
53 Returns a hash value for this Specifier like object.
54 """
55
56 @abc.abstractmethod
57 def __eq__(self, other):
58 # type: (object) -> bool
59 """
60 Returns a boolean representing whether or not the two Specifier like
61 objects are equal.
62 """
63
64 @abc.abstractmethod
65 def __ne__(self, other):
66 # type: (object) -> bool
67 """
68 Returns a boolean representing whether or not the two Specifier like
69 objects are not equal.
70 """
71
72 @abc.abstractproperty
73 def prereleases(self):
74 # type: () -> Optional[bool]
75 """
76 Returns whether or not pre-releases as a whole are allowed by this
77 specifier.
78 """
79
80 @prereleases.setter
81 def prereleases(self, value):
82 # type: (bool) -> None
83 """
84 Sets whether or not pre-releases as a whole are allowed by this
85 specifier.
86 """
87
88 @abc.abstractmethod
89 def contains(self, item, prereleases=None):
90 # type: (str, Optional[bool]) -> bool
91 """
92 Determines if the given item is contained within this specifier.
93 """
94
95 @abc.abstractmethod
96 def filter(self, iterable, prereleases=None):
97 # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion]
98 """
99 Takes an iterable of items and filters them so that only items which
100 are contained within this specifier are allowed in it.
101 """
102
103
104 class _IndividualSpecifier(BaseSpecifier):
105
106 _operators = {} # type: Dict[str, str]
107
108 def __init__(self, spec="", prereleases=None):
109 # type: (str, Optional[bool]) -> None
110 match = self._regex.search(spec)
111 if not match:
112 raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
113
114 self._spec = (
115 match.group("operator").strip(),
116 match.group("version").strip(),
117 ) # type: Tuple[str, str]
118
119 # Store whether or not this Specifier should accept prereleases
120 self._prereleases = prereleases
121
122 def __repr__(self):
123 # type: () -> str
124 pre = (
125 ", prereleases={0!r}".format(self.prereleases)
126 if self._prereleases is not None
127 else ""
128 )
129
130 return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre)
131
132 def __str__(self):
133 # type: () -> str
134 return "{0}{1}".format(*self._spec)
135
136 @property
137 def _canonical_spec(self):
138 # type: () -> Tuple[str, Union[Version, str]]
139 return self._spec[0], canonicalize_version(self._spec[1])
140
141 def __hash__(self):
142 # type: () -> int
143 return hash(self._canonical_spec)
144
145 def __eq__(self, other):
146 # type: (object) -> bool
147 if isinstance(other, string_types):
148 try:
149 other = self.__class__(str(other))
150 except InvalidSpecifier:
151 return NotImplemented
152 elif not isinstance(other, self.__class__):
153 return NotImplemented
154
155 return self._canonical_spec == other._canonical_spec
156
157 def __ne__(self, other):
158 # type: (object) -> bool
159 if isinstance(other, string_types):
160 try:
161 other = self.__class__(str(other))
162 except InvalidSpecifier:
163 return NotImplemented
164 elif not isinstance(other, self.__class__):
165 return NotImplemented
166
167 return self._spec != other._spec
168
169 def _get_operator(self, op):
170 # type: (str) -> CallableOperator
171 operator_callable = getattr(
172 self, "_compare_{0}".format(self._operators[op])
173 ) # type: CallableOperator
174 return operator_callable
175
176 def _coerce_version(self, version):
177 # type: (UnparsedVersion) -> ParsedVersion
178 if not isinstance(version, (LegacyVersion, Version)):
179 version = parse(version)
180 return version
181
182 @property
183 def operator(self):
184 # type: () -> str
185 return self._spec[0]
186
187 @property
188 def version(self):
189 # type: () -> str
190 return self._spec[1]
191
192 @property
193 def prereleases(self):
194 # type: () -> Optional[bool]
195 return self._prereleases
196
197 @prereleases.setter
198 def prereleases(self, value):
199 # type: (bool) -> None
200 self._prereleases = value
201
202 def __contains__(self, item):
203 # type: (str) -> bool
204 return self.contains(item)
205
206 def contains(self, item, prereleases=None):
207 # type: (UnparsedVersion, Optional[bool]) -> bool
208
209 # Determine if prereleases are to be allowed or not.
210 if prereleases is None:
211 prereleases = self.prereleases
212
213 # Normalize item to a Version or LegacyVersion, this allows us to have
214 # a shortcut for ``"2.0" in Specifier(">=2")
215 normalized_item = self._coerce_version(item)
216
217 # Determine if we should be supporting prereleases in this specifier
218 # or not, if we do not support prereleases than we can short circuit
219 # logic if this version is a prereleases.
220 if normalized_item.is_prerelease and not prereleases:
221 return False
222
223 # Actually do the comparison to determine if this item is contained
224 # within this Specifier or not.
225 operator_callable = self._get_operator(self.operator) # type: CallableOperator
226 return operator_callable(normalized_item, self.version)
227
228 def filter(self, iterable, prereleases=None):
229 # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion]
230
231 yielded = False
232 found_prereleases = []
233
234 kw = {"prereleases": prereleases if prereleases is not None else True}
235
236 # Attempt to iterate over all the values in the iterable and if any of
237 # them match, yield them.
238 for version in iterable:
239 parsed_version = self._coerce_version(version)
240
241 if self.contains(parsed_version, **kw):
242 # If our version is a prerelease, and we were not set to allow
243 # prereleases, then we'll store it for later incase nothing
244 # else matches this specifier.
245 if parsed_version.is_prerelease and not (
246 prereleases or self.prereleases
247 ):
248 found_prereleases.append(version)
249 # Either this is not a prerelease, or we should have been
250 # accepting prereleases from the beginning.
251 else:
252 yielded = True
253 yield version
254
255 # Now that we've iterated over everything, determine if we've yielded
256 # any values, and if we have not and we have any prereleases stored up
257 # then we will go ahead and yield the prereleases.
258 if not yielded and found_prereleases:
259 for version in found_prereleases:
260 yield version
261
262
263 class LegacySpecifier(_IndividualSpecifier):
264
265 _regex_str = r"""
266 (?P<operator>(==|!=|<=|>=|<|>))
267 \s*
268 (?P<version>
269 [^,;\s)]* # Since this is a "legacy" specifier, and the version
270 # string can be just about anything, we match everything
271 # except for whitespace, a semi-colon for marker support,
272 # a closing paren since versions can be enclosed in
273 # them, and a comma since it's a version separator.
274 )
275 """
276
277 _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
278
279 _operators = {
280 "==": "equal",
281 "!=": "not_equal",
282 "<=": "less_than_equal",
283 ">=": "greater_than_equal",
284 "<": "less_than",
285 ">": "greater_than",
286 }
287
288 def _coerce_version(self, version):
289 # type: (Union[ParsedVersion, str]) -> LegacyVersion
290 if not isinstance(version, LegacyVersion):
291 version = LegacyVersion(str(version))
292 return version
293
294 def _compare_equal(self, prospective, spec):
295 # type: (LegacyVersion, str) -> bool
296 return prospective == self._coerce_version(spec)
297
298 def _compare_not_equal(self, prospective, spec):
299 # type: (LegacyVersion, str) -> bool
300 return prospective != self._coerce_version(spec)
301
302 def _compare_less_than_equal(self, prospective, spec):
303 # type: (LegacyVersion, str) -> bool
304 return prospective <= self._coerce_version(spec)
305
306 def _compare_greater_than_equal(self, prospective, spec):
307 # type: (LegacyVersion, str) -> bool
308 return prospective >= self._coerce_version(spec)
309
310 def _compare_less_than(self, prospective, spec):
311 # type: (LegacyVersion, str) -> bool
312 return prospective < self._coerce_version(spec)
313
314 def _compare_greater_than(self, prospective, spec):
315 # type: (LegacyVersion, str) -> bool
316 return prospective > self._coerce_version(spec)
317
318
319 def _require_version_compare(
320 fn # type: (Callable[[Specifier, ParsedVersion, str], bool])
321 ):
322 # type: (...) -> Callable[[Specifier, ParsedVersion, str], bool]
323 @functools.wraps(fn)
324 def wrapped(self, prospective, spec):
325 # type: (Specifier, ParsedVersion, str) -> bool
326 if not isinstance(prospective, Version):
327 return False
328 return fn(self, prospective, spec)
329
330 return wrapped
331
332
333 class Specifier(_IndividualSpecifier):
334
335 _regex_str = r"""
336 (?P<operator>(~=|==|!=|<=|>=|<|>|===))
337 (?P<version>
338 (?:
339 # The identity operators allow for an escape hatch that will
340 # do an exact string match of the version you wish to install.
341 # This will not be parsed by PEP 440 and we cannot determine
342 # any semantic meaning from it. This operator is discouraged
343 # but included entirely as an escape hatch.
344 (?<====) # Only match for the identity operator
345 \s*
346 [^\s]* # We just match everything, except for whitespace
347 # since we are only testing for strict identity.
348 )
349 |
350 (?:
351 # The (non)equality operators allow for wild card and local
352 # versions to be specified so we have to define these two
353 # operators separately to enable that.
354 (?<===|!=) # Only match for equals and not equals
355
356 \s*
357 v?
358 (?:[0-9]+!)? # epoch
359 [0-9]+(?:\.[0-9]+)* # release
360 (?: # pre release
361 [-_\.]?
362 (a|b|c|rc|alpha|beta|pre|preview)
363 [-_\.]?
364 [0-9]*
365 )?
366 (?: # post release
367 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
368 )?
369
370 # You cannot use a wild card and a dev or local version
371 # together so group them with a | and make them optional.
372 (?:
373 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
374 (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
375 |
376 \.\* # Wild card syntax of .*
377 )?
378 )
379 |
380 (?:
381 # The compatible operator requires at least two digits in the
382 # release segment.
383 (?<=~=) # Only match for the compatible operator
384
385 \s*
386 v?
387 (?:[0-9]+!)? # epoch
388 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
389 (?: # pre release
390 [-_\.]?
391 (a|b|c|rc|alpha|beta|pre|preview)
392 [-_\.]?
393 [0-9]*
394 )?
395 (?: # post release
396 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
397 )?
398 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
399 )
400 |
401 (?:
402 # All other operators only allow a sub set of what the
403 # (non)equality operators do. Specifically they do not allow
404 # local versions to be specified nor do they allow the prefix
405 # matching wild cards.
406 (?<!==|!=|~=) # We have special cases for these
407 # operators so we want to make sure they
408 # don't match here.
409
410 \s*
411 v?
412 (?:[0-9]+!)? # epoch
413 [0-9]+(?:\.[0-9]+)* # release
414 (?: # pre release
415 [-_\.]?
416 (a|b|c|rc|alpha|beta|pre|preview)
417 [-_\.]?
418 [0-9]*
419 )?
420 (?: # post release
421 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
422 )?
423 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
424 )
425 )
426 """
427
428 _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
429
430 _operators = {
431 "~=": "compatible",
432 "==": "equal",
433 "!=": "not_equal",
434 "<=": "less_than_equal",
435 ">=": "greater_than_equal",
436 "<": "less_than",
437 ">": "greater_than",
438 "===": "arbitrary",
439 }
440
441 @_require_version_compare
442 def _compare_compatible(self, prospective, spec):
443 # type: (ParsedVersion, str) -> bool
444
445 # Compatible releases have an equivalent combination of >= and ==. That
446 # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
447 # implement this in terms of the other specifiers instead of
448 # implementing it ourselves. The only thing we need to do is construct
449 # the other specifiers.
450
451 # We want everything but the last item in the version, but we want to
452 # ignore post and dev releases and we want to treat the pre-release as
453 # it's own separate segment.
454 prefix = ".".join(
455 list(
456 itertools.takewhile(
457 lambda x: (not x.startswith("post") and not x.startswith("dev")),
458 _version_split(spec),
459 )
460 )[:-1]
461 )
462
463 # Add the prefix notation to the end of our string
464 prefix += ".*"
465
466 return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
467 prospective, prefix
468 )
469
470 @_require_version_compare
471 def _compare_equal(self, prospective, spec):
472 # type: (ParsedVersion, str) -> bool
473
474 # We need special logic to handle prefix matching
475 if spec.endswith(".*"):
476 # In the case of prefix matching we want to ignore local segment.
477 prospective = Version(prospective.public)
478 # Split the spec out by dots, and pretend that there is an implicit
479 # dot in between a release segment and a pre-release segment.
480 split_spec = _version_split(spec[:-2]) # Remove the trailing .*
481
482 # Split the prospective version out by dots, and pretend that there
483 # is an implicit dot in between a release segment and a pre-release
484 # segment.
485 split_prospective = _version_split(str(prospective))
486
487 # Shorten the prospective version to be the same length as the spec
488 # so that we can determine if the specifier is a prefix of the
489 # prospective version or not.
490 shortened_prospective = split_prospective[: len(split_spec)]
491
492 # Pad out our two sides with zeros so that they both equal the same
493 # length.
494 padded_spec, padded_prospective = _pad_version(
495 split_spec, shortened_prospective
496 )
497
498 return padded_prospective == padded_spec
499 else:
500 # Convert our spec string into a Version
501 spec_version = Version(spec)
502
503 # If the specifier does not have a local segment, then we want to
504 # act as if the prospective version also does not have a local
505 # segment.
506 if not spec_version.local:
507 prospective = Version(prospective.public)
508
509 return prospective == spec_version
510
511 @_require_version_compare
512 def _compare_not_equal(self, prospective, spec):
513 # type: (ParsedVersion, str) -> bool
514 return not self._compare_equal(prospective, spec)
515
516 @_require_version_compare
517 def _compare_less_than_equal(self, prospective, spec):
518 # type: (ParsedVersion, str) -> bool
519
520 # NB: Local version identifiers are NOT permitted in the version
521 # specifier, so local version labels can be universally removed from
522 # the prospective version.
523 return Version(prospective.public) <= Version(spec)
524
525 @_require_version_compare
526 def _compare_greater_than_equal(self, prospective, spec):
527 # type: (ParsedVersion, str) -> bool
528
529 # NB: Local version identifiers are NOT permitted in the version
530 # specifier, so local version labels can be universally removed from
531 # the prospective version.
532 return Version(prospective.public) >= Version(spec)
533
534 @_require_version_compare
535 def _compare_less_than(self, prospective, spec_str):
536 # type: (ParsedVersion, str) -> bool
537
538 # Convert our spec to a Version instance, since we'll want to work with
539 # it as a version.
540 spec = Version(spec_str)
541
542 # Check to see if the prospective version is less than the spec
543 # version. If it's not we can short circuit and just return False now
544 # instead of doing extra unneeded work.
545 if not prospective < spec:
546 return False
547
548 # This special case is here so that, unless the specifier itself
549 # includes is a pre-release version, that we do not accept pre-release
550 # versions for the version mentioned in the specifier (e.g. <3.1 should
551 # not match 3.1.dev0, but should match 3.0.dev0).
552 if not spec.is_prerelease and prospective.is_prerelease:
553 if Version(prospective.base_version) == Version(spec.base_version):
554 return False
555
556 # If we've gotten to here, it means that prospective version is both
557 # less than the spec version *and* it's not a pre-release of the same
558 # version in the spec.
559 return True
560
561 @_require_version_compare
562 def _compare_greater_than(self, prospective, spec_str):
563 # type: (ParsedVersion, str) -> bool
564
565 # Convert our spec to a Version instance, since we'll want to work with
566 # it as a version.
567 spec = Version(spec_str)
568
569 # Check to see if the prospective version is greater than the spec
570 # version. If it's not we can short circuit and just return False now
571 # instead of doing extra unneeded work.
572 if not prospective > spec:
573 return False
574
575 # This special case is here so that, unless the specifier itself
576 # includes is a post-release version, that we do not accept
577 # post-release versions for the version mentioned in the specifier
578 # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
579 if not spec.is_postrelease and prospective.is_postrelease:
580 if Version(prospective.base_version) == Version(spec.base_version):
581 return False
582
583 # Ensure that we do not allow a local version of the version mentioned
584 # in the specifier, which is technically greater than, to match.
585 if prospective.local is not None:
586 if Version(prospective.base_version) == Version(spec.base_version):
587 return False
588
589 # If we've gotten to here, it means that prospective version is both
590 # greater than the spec version *and* it's not a pre-release of the
591 # same version in the spec.
592 return True
593
594 def _compare_arbitrary(self, prospective, spec):
595 # type: (Version, str) -> bool
596 return str(prospective).lower() == str(spec).lower()
597
598 @property
599 def prereleases(self):
600 # type: () -> bool
601
602 # If there is an explicit prereleases set for this, then we'll just
603 # blindly use that.
604 if self._prereleases is not None:
605 return self._prereleases
606
607 # Look at all of our specifiers and determine if they are inclusive
608 # operators, and if they are if they are including an explicit
609 # prerelease.
610 operator, version = self._spec
611 if operator in ["==", ">=", "<=", "~=", "==="]:
612 # The == specifier can include a trailing .*, if it does we
613 # want to remove before parsing.
614 if operator == "==" and version.endswith(".*"):
615 version = version[:-2]
616
617 # Parse the version, and if it is a pre-release than this
618 # specifier allows pre-releases.
619 if parse(version).is_prerelease:
620 return True
621
622 return False
623
624 @prereleases.setter
625 def prereleases(self, value):
626 # type: (bool) -> None
627 self._prereleases = value
628
629
630 _prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
631
632
633 def _version_split(version):
634 # type: (str) -> List[str]
635 result = [] # type: List[str]
636 for item in version.split("."):
637 match = _prefix_regex.search(item)
638 if match:
639 result.extend(match.groups())
640 else:
641 result.append(item)
642 return result
643
644
645 def _pad_version(left, right):
646 # type: (List[str], List[str]) -> Tuple[List[str], List[str]]
647 left_split, right_split = [], []
648
649 # Get the release segment of our versions
650 left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
651 right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
652
653 # Get the rest of our versions
654 left_split.append(left[len(left_split[0]) :])
655 right_split.append(right[len(right_split[0]) :])
656
657 # Insert our padding
658 left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
659 right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
660
661 return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split)))
662
663
664 class SpecifierSet(BaseSpecifier):
665 def __init__(self, specifiers="", prereleases=None):
666 # type: (str, Optional[bool]) -> None
667
668 # Split on , to break each individual specifier into it's own item, and
669 # strip each item to remove leading/trailing whitespace.
670 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
671
672 # Parsed each individual specifier, attempting first to make it a
673 # Specifier and falling back to a LegacySpecifier.
674 parsed = set()
675 for specifier in split_specifiers:
676 try:
677 parsed.add(Specifier(specifier))
678 except InvalidSpecifier:
679 parsed.add(LegacySpecifier(specifier))
680
681 # Turn our parsed specifiers into a frozen set and save them for later.
682 self._specs = frozenset(parsed)
683
684 # Store our prereleases value so we can use it later to determine if
685 # we accept prereleases or not.
686 self._prereleases = prereleases
687
688 def __repr__(self):
689 # type: () -> str
690 pre = (
691 ", prereleases={0!r}".format(self.prereleases)
692 if self._prereleases is not None
693 else ""
694 )
695
696 return "<SpecifierSet({0!r}{1})>".format(str(self), pre)
697
698 def __str__(self):
699 # type: () -> str
700 return ",".join(sorted(str(s) for s in self._specs))
701
702 def __hash__(self):
703 # type: () -> int
704 return hash(self._specs)
705
706 def __and__(self, other):
707 # type: (Union[SpecifierSet, str]) -> SpecifierSet
708 if isinstance(other, string_types):
709 other = SpecifierSet(other)
710 elif not isinstance(other, SpecifierSet):
711 return NotImplemented
712
713 specifier = SpecifierSet()
714 specifier._specs = frozenset(self._specs | other._specs)
715
716 if self._prereleases is None and other._prereleases is not None:
717 specifier._prereleases = other._prereleases
718 elif self._prereleases is not None and other._prereleases is None:
719 specifier._prereleases = self._prereleases
720 elif self._prereleases == other._prereleases:
721 specifier._prereleases = self._prereleases
722 else:
723 raise ValueError(
724 "Cannot combine SpecifierSets with True and False prerelease "
725 "overrides."
726 )
727
728 return specifier
729
730 def __eq__(self, other):
731 # type: (object) -> bool
732 if isinstance(other, (string_types, _IndividualSpecifier)):
733 other = SpecifierSet(str(other))
734 elif not isinstance(other, SpecifierSet):
735 return NotImplemented
736
737 return self._specs == other._specs
738
739 def __ne__(self, other):
740 # type: (object) -> bool
741 if isinstance(other, (string_types, _IndividualSpecifier)):
742 other = SpecifierSet(str(other))
743 elif not isinstance(other, SpecifierSet):
744 return NotImplemented
745
746 return self._specs != other._specs
747
748 def __len__(self):
749 # type: () -> int
750 return len(self._specs)
751
752 def __iter__(self):
753 # type: () -> Iterator[FrozenSet[_IndividualSpecifier]]
754 return iter(self._specs)
755
756 @property
757 def prereleases(self):
758 # type: () -> Optional[bool]
759
760 # If we have been given an explicit prerelease modifier, then we'll
761 # pass that through here.
762 if self._prereleases is not None:
763 return self._prereleases
764
765 # If we don't have any specifiers, and we don't have a forced value,
766 # then we'll just return None since we don't know if this should have
767 # pre-releases or not.
768 if not self._specs:
769 return None
770
771 # Otherwise we'll see if any of the given specifiers accept
772 # prereleases, if any of them do we'll return True, otherwise False.
773 return any(s.prereleases for s in self._specs)
774
775 @prereleases.setter
776 def prereleases(self, value):
777 # type: (bool) -> None
778 self._prereleases = value
779
780 def __contains__(self, item):
781 # type: (Union[ParsedVersion, str]) -> bool
782 return self.contains(item)
783
784 def contains(self, item, prereleases=None):
785 # type: (Union[ParsedVersion, str], Optional[bool]) -> bool
786
787 # Ensure that our item is a Version or LegacyVersion instance.
788 if not isinstance(item, (LegacyVersion, Version)):
789 item = parse(item)
790
791 # Determine if we're forcing a prerelease or not, if we're not forcing
792 # one for this particular filter call, then we'll use whatever the
793 # SpecifierSet thinks for whether or not we should support prereleases.
794 if prereleases is None:
795 prereleases = self.prereleases
796
797 # We can determine if we're going to allow pre-releases by looking to
798 # see if any of the underlying items supports them. If none of them do
799 # and this item is a pre-release then we do not allow it and we can
800 # short circuit that here.
801 # Note: This means that 1.0.dev1 would not be contained in something
802 # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0
803 if not prereleases and item.is_prerelease:
804 return False
805
806 # We simply dispatch to the underlying specs here to make sure that the
807 # given version is contained within all of them.
808 # Note: This use of all() here means that an empty set of specifiers
809 # will always return True, this is an explicit design decision.
810 return all(s.contains(item, prereleases=prereleases) for s in self._specs)
811
812 def filter(
813 self,
814 iterable, # type: Iterable[Union[ParsedVersion, str]]
815 prereleases=None, # type: Optional[bool]
816 ):
817 # type: (...) -> Iterable[Union[ParsedVersion, str]]
818
819 # Determine if we're forcing a prerelease or not, if we're not forcing
820 # one for this particular filter call, then we'll use whatever the
821 # SpecifierSet thinks for whether or not we should support prereleases.
822 if prereleases is None:
823 prereleases = self.prereleases
824
825 # If we have any specifiers, then we want to wrap our iterable in the
826 # filter method for each one, this will act as a logical AND amongst
827 # each specifier.
828 if self._specs:
829 for spec in self._specs:
830 iterable = spec.filter(iterable, prereleases=bool(prereleases))
831 return iterable
832 # If we do not have any specifiers, then we need to have a rough filter
833 # which will filter out any pre-releases, unless there are no final
834 # releases, and which will filter out LegacyVersion in general.
835 else:
836 filtered = [] # type: List[Union[ParsedVersion, str]]
837 found_prereleases = [] # type: List[Union[ParsedVersion, str]]
838
839 for item in iterable:
840 # Ensure that we some kind of Version class for this item.
841 if not isinstance(item, (LegacyVersion, Version)):
842 parsed_version = parse(item)
843 else:
844 parsed_version = item
845
846 # Filter out any item which is parsed as a LegacyVersion
847 if isinstance(parsed_version, LegacyVersion):
848 continue
849
850 # Store any item which is a pre-release for later unless we've
851 # already found a final version or we are accepting prereleases
852 if parsed_version.is_prerelease and not prereleases:
853 if not filtered:
854 found_prereleases.append(item)
855 else:
856 filtered.append(item)
857
858 # If we've found no items except for pre-releases, then we'll go
859 # ahead and use the pre-releases
860 if not filtered and found_prereleases and prereleases is None:
861 return found_prereleases
862
863 return filtered