python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
pip/
_vendor/
packaging/
specifiers.py
       1  # This file is dual licensed under the terms of the Apache License, Version
       2  # 2.0, and the BSD License. See the LICENSE file in the root of this repository
       3  # for complete details.
       4  
       5  import abc
       6  import functools
       7  import itertools
       8  import re
       9  import warnings
      10  from 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  
      24  from .utils import canonicalize_version
      25  from .version import LegacyVersion, Version, parse
      26  
      27  ParsedVersion = Union[Version, LegacyVersion]
      28  UnparsedVersion = Union[Version, LegacyVersion, str]
      29  VersionTypeVar = TypeVar("VersionTypeVar", bound=UnparsedVersion)
      30  CallableOperator = Callable[[ParsedVersion, str], bool]
      31  
      32  
      33  class ESC[4;38;5;81mInvalidSpecifier(ESC[4;38;5;149mValueError):
      34      """
      35      An invalid specifier was found, users should refer to PEP 440.
      36      """
      37  
      38  
      39  class ESC[4;38;5;81mBaseSpecifier(metaclass=ESC[4;38;5;149mabcESC[4;38;5;149m.ESC[4;38;5;149mABCMeta):
      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  
      90  class ESC[4;38;5;81m_IndividualSpecifier(ESC[4;38;5;149mBaseSpecifier):
      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  
     227  class ESC[4;38;5;81mLegacySpecifier(ESC[4;38;5;149m_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  
     287  def _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  
     299  class ESC[4;38;5;81mSpecifier(ESC[4;38;5;149m_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  
     585  def _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  
     596  def _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  
     602  def _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  
     620  class ESC[4;38;5;81mSpecifierSet(ESC[4;38;5;149mBaseSpecifier):
     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