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