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