specifiers.py 30 KB

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