codekingpro/portable-devtools
114k
1# This file is dual licensed under the terms of the Apache License, Version2# 2.0, and the BSD License. See the LICENSE file in the root of this repository3# for complete details.4"""5.. testsetup::6 7 from packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier8 from packaging.version import Version9"""10 11import abc12import itertools13import re14from typing import Callable, Iterable, Iterator, List, Optional, Tuple, TypeVar, Union15 16from .utils import canonicalize_version17from .version import Version18 19UnparsedVersion = Union[Version, str]20UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)21CallableOperator = Callable[[Version, str], bool]22 23 24def _coerce_version(version: UnparsedVersion) -> Version:25 if not isinstance(version, Version):26 version = Version(version)27 return version28 29 30class InvalidSpecifier(ValueError):31 """32 Raised when attempting to create a :class:`Specifier` with a specifier33 string that is invalid.34 35 >>> Specifier("lolwat")36 Traceback (most recent call last):37 ...38 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'39 """40 41 42class BaseSpecifier(metaclass=abc.ABCMeta):43 @abc.abstractmethod44 def __str__(self) -> str:45 """46 Returns the str representation of this Specifier-like object. This47 should be representative of the Specifier itself.48 """49 50 @abc.abstractmethod51 def __hash__(self) -> int:52 """53 Returns a hash value for this Specifier-like object.54 """55 56 @abc.abstractmethod57 def __eq__(self, other: object) -> bool:58 """59 Returns a boolean representing whether or not the two Specifier-like60 objects are equal.61 62 :param other: The other object to check against.63 """64 65 @property66 @abc.abstractmethod67 def prereleases(self) -> Optional[bool]:68 """Whether or not pre-releases as a whole are allowed.69 70 This can be set to either ``True`` or ``False`` to explicitly enable or disable71 prereleases or it can be set to ``None`` (the default) to use default semantics.72 """73 74 @prereleases.setter75 def prereleases(self, value: bool) -> None:76 """Setter for :attr:`prereleases`.77 78 :param value: The value to set.79 """80 81 @abc.abstractmethod82 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.abstractmethod88 def filter(89 self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None90 ) -> Iterator[UnparsedVersionVar]:91 """92 Takes an iterable of items and filters them so that only items which93 are contained within this specifier are allowed in it.94 """95 96 97class Specifier(BaseSpecifier):98 """This class abstracts handling of version specifiers.99 100 .. tip::101 102 It is generally not required to instantiate this manually. You should instead103 prefer to work with :class:`SpecifierSet` instead, which can parse104 comma-separated version specifiers (which is what package metadata contains).105 """106 107 _operator_regex_str = r"""108 (?P<operator>(~=|==|!=|<=|>=|<|>|===))109 """110 _version_regex_str = r"""111 (?P<version>112 (?:113 # The identity operators allow for an escape hatch that will114 # do an exact string match of the version you wish to install.115 # This will not be parsed by PEP 440 and we cannot determine116 # any semantic meaning from it. This operator is discouraged117 # but included entirely as an escape hatch.118 (?<====) # Only match for the identity operator119 \s*120 [^\s;)]* # The arbitrary version can be just about anything,121 # we match everything except for whitespace, a122 # semi-colon for marker support, and a closing paren123 # since versions can be enclosed in them.124 )125 |126 (?:127 # The (non)equality operators allow for wild card and local128 # versions to be specified so we have to define these two129 # operators separately to enable that.130 (?<===|!=) # Only match for equals and not equals131 132 \s*133 v?134 (?:[0-9]+!)? # epoch135 [0-9]+(?:\.[0-9]+)* # release136 137 # You cannot use a wild card and a pre-release, post-release, a dev or138 # local version together so group them with a | and make them optional.139 (?:140 \.\* # Wild card syntax of .*141 |142 (?: # pre release143 [-_\.]?144 (alpha|beta|preview|pre|a|b|c|rc)145 [-_\.]?146 [0-9]*147 )?148 (?: # post release149 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)150 )?151 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release152 (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local153 )?154 )155 |156 (?:157 # The compatible operator requires at least two digits in the158 # release segment.159 (?<=~=) # Only match for the compatible operator160 161 \s*162 v?163 (?:[0-9]+!)? # epoch164 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)165 (?: # pre release166 [-_\.]?167 (alpha|beta|preview|pre|a|b|c|rc)168 [-_\.]?169 [0-9]*170 )?171 (?: # post release172 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)173 )?174 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release175 )176 |177 (?:178 # All other operators only allow a sub set of what the179 # (non)equality operators do. Specifically they do not allow180 # local versions to be specified nor do they allow the prefix181 # matching wild cards.182 (?<!==|!=|~=) # We have special cases for these183 # operators so we want to make sure they184 # don't match here.185 186 \s*187 v?188 (?:[0-9]+!)? # epoch189 [0-9]+(?:\.[0-9]+)* # release190 (?: # pre release191 [-_\.]?192 (alpha|beta|preview|pre|a|b|c|rc)193 [-_\.]?194 [0-9]*195 )?196 (?: # post release197 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)198 )?199 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release200 )201 )202 """203 204 _regex = re.compile(205 r"^\s*" + _operator_regex_str + _version_regex_str + r"\s*$",206 re.VERBOSE | re.IGNORECASE,207 )208 209 _operators = {210 "~=": "compatible",211 "==": "equal",212 "!=": "not_equal",213 "<=": "less_than_equal",214 ">=": "greater_than_equal",215 "<": "less_than",216 ">": "greater_than",217 "===": "arbitrary",218 }219 220 def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None:221 """Initialize a Specifier instance.222 223 :param spec:224 The string representation of a specifier which will be parsed and225 normalized before use.226 :param prereleases:227 This tells the specifier if it should accept prerelease versions if228 applicable or not. The default of ``None`` will autodetect it from the229 given specifiers.230 :raises InvalidSpecifier:231 If the given specifier is invalid (i.e. bad syntax).232 """233 match = self._regex.search(spec)234 if not match:235 raise InvalidSpecifier(f"Invalid specifier: '{spec}'")236 237 self._spec: Tuple[str, str] = (238 match.group("operator").strip(),239 match.group("version").strip(),240 )241 242 # Store whether or not this Specifier should accept prereleases243 self._prereleases = prereleases244 245 # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515246 @property # type: ignore[override]247 def prereleases(self) -> bool:248 # If there is an explicit prereleases set for this, then we'll just249 # blindly use that.250 if self._prereleases is not None:251 return self._prereleases252 253 # Look at all of our specifiers and determine if they are inclusive254 # operators, and if they are if they are including an explicit255 # prerelease.256 operator, version = self._spec257 if operator in ["==", ">=", "<=", "~=", "==="]:258 # The == specifier can include a trailing .*, if it does we259 # want to remove before parsing.260 if operator == "==" and version.endswith(".*"):261 version = version[:-2]262 263 # Parse the version, and if it is a pre-release than this264 # specifier allows pre-releases.265 if Version(version).is_prerelease:266 return True267 268 return False269 270 @prereleases.setter271 def prereleases(self, value: bool) -> None:272 self._prereleases = value273 274 @property275 def operator(self) -> str:276 """The operator of this specifier.277 278 >>> Specifier("==1.2.3").operator279 '=='280 """281 return self._spec[0]282 283 @property284 def version(self) -> str:285 """The version of this specifier.286 287 >>> Specifier("==1.2.3").version288 '1.2.3'289 """290 return self._spec[1]291 292 def __repr__(self) -> str:293 """A representation of the Specifier that shows all internal state.294 295 >>> Specifier('>=1.0.0')296 <Specifier('>=1.0.0')>297 >>> Specifier('>=1.0.0', prereleases=False)298 <Specifier('>=1.0.0', prereleases=False)>299 >>> Specifier('>=1.0.0', prereleases=True)300 <Specifier('>=1.0.0', prereleases=True)>301 """302 pre = (303 f", prereleases={self.prereleases!r}"304 if self._prereleases is not None305 else ""306 )307 308 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"309 310 def __str__(self) -> str:311 """A string representation of the Specifier that can be round-tripped.312 313 >>> str(Specifier('>=1.0.0'))314 '>=1.0.0'315 >>> str(Specifier('>=1.0.0', prereleases=False))316 '>=1.0.0'317 """318 return "{}{}".format(*self._spec)319 320 @property321 def _canonical_spec(self) -> Tuple[str, str]:322 canonical_version = canonicalize_version(323 self._spec[1],324 strip_trailing_zero=(self._spec[0] != "~="),325 )326 return self._spec[0], canonical_version327 328 def __hash__(self) -> int:329 return hash(self._canonical_spec)330 331 def __eq__(self, other: object) -> bool:332 """Whether or not the two Specifier-like objects are equal.333 334 :param other: The other object to check against.335 336 The value of :attr:`prereleases` is ignored.337 338 >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")339 True340 >>> (Specifier("==1.2.3", prereleases=False) ==341 ... Specifier("==1.2.3", prereleases=True))342 True343 >>> Specifier("==1.2.3") == "==1.2.3"344 True345 >>> Specifier("==1.2.3") == Specifier("==1.2.4")346 False347 >>> Specifier("==1.2.3") == Specifier("~=1.2.3")348 False349 """350 if isinstance(other, str):351 try:352 other = self.__class__(str(other))353 except InvalidSpecifier:354 return NotImplemented355 elif not isinstance(other, self.__class__):356 return NotImplemented357 358 return self._canonical_spec == other._canonical_spec359 360 def _get_operator(self, op: str) -> CallableOperator:361 operator_callable: CallableOperator = getattr(362 self, f"_compare_{self._operators[op]}"363 )364 return operator_callable365 366 def _compare_compatible(self, prospective: Version, spec: str) -> bool:367 368 # Compatible releases have an equivalent combination of >= and ==. That369 # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to370 # implement this in terms of the other specifiers instead of371 # implementing it ourselves. The only thing we need to do is construct372 # the other specifiers.373 374 # We want everything but the last item in the version, but we want to375 # ignore suffix segments.376 prefix = _version_join(377 list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]378 )379 380 # Add the prefix notation to the end of our string381 prefix += ".*"382 383 return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(384 prospective, prefix385 )386 387 def _compare_equal(self, prospective: Version, spec: str) -> bool:388 389 # We need special logic to handle prefix matching390 if spec.endswith(".*"):391 # In the case of prefix matching we want to ignore local segment.392 normalized_prospective = canonicalize_version(393 prospective.public, strip_trailing_zero=False394 )395 # Get the normalized version string ignoring the trailing .*396 normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)397 # Split the spec out by bangs and dots, and pretend that there is398 # an implicit dot in between a release segment and a pre-release segment.399 split_spec = _version_split(normalized_spec)400 401 # Split the prospective version out by bangs and dots, and pretend402 # that there is an implicit dot in between a release segment and403 # a pre-release segment.404 split_prospective = _version_split(normalized_prospective)405 406 # 0-pad the prospective version before shortening it to get the correct407 # shortened version.408 padded_prospective, _ = _pad_version(split_prospective, split_spec)409 410 # Shorten the prospective version to be the same length as the spec411 # so that we can determine if the specifier is a prefix of the412 # prospective version or not.413 shortened_prospective = padded_prospective[: len(split_spec)]414 415 return shortened_prospective == split_spec416 else:417 # Convert our spec string into a Version418 spec_version = Version(spec)419 420 # If the specifier does not have a local segment, then we want to421 # act as if the prospective version also does not have a local422 # segment.423 if not spec_version.local:424 prospective = Version(prospective.public)425 426 return prospective == spec_version427 428 def _compare_not_equal(self, prospective: Version, spec: str) -> bool:429 return not self._compare_equal(prospective, spec)430 431 def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:432 433 # NB: Local version identifiers are NOT permitted in the version434 # specifier, so local version labels can be universally removed from435 # the prospective version.436 return Version(prospective.public) <= Version(spec)437 438 def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:439 440 # NB: Local version identifiers are NOT permitted in the version441 # specifier, so local version labels can be universally removed from442 # the prospective version.443 return Version(prospective.public) >= Version(spec)444 445 def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:446 447 # Convert our spec to a Version instance, since we'll want to work with448 # it as a version.449 spec = Version(spec_str)450 451 # Check to see if the prospective version is less than the spec452 # version. If it's not we can short circuit and just return False now453 # instead of doing extra unneeded work.454 if not prospective < spec:455 return False456 457 # This special case is here so that, unless the specifier itself458 # includes is a pre-release version, that we do not accept pre-release459 # versions for the version mentioned in the specifier (e.g. <3.1 should460 # not match 3.1.dev0, but should match 3.0.dev0).461 if not spec.is_prerelease and prospective.is_prerelease:462 if Version(prospective.base_version) == Version(spec.base_version):463 return False464 465 # If we've gotten to here, it means that prospective version is both466 # less than the spec version *and* it's not a pre-release of the same467 # version in the spec.468 return True469 470 def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:471 472 # Convert our spec to a Version instance, since we'll want to work with473 # it as a version.474 spec = Version(spec_str)475 476 # Check to see if the prospective version is greater than the spec477 # version. If it's not we can short circuit and just return False now478 # instead of doing extra unneeded work.479 if not prospective > spec:480 return False481 482 # This special case is here so that, unless the specifier itself483 # includes is a post-release version, that we do not accept484 # post-release versions for the version mentioned in the specifier485 # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).486 if not spec.is_postrelease and prospective.is_postrelease:487 if Version(prospective.base_version) == Version(spec.base_version):488 return False489 490 # Ensure that we do not allow a local version of the version mentioned491 # in the specifier, which is technically greater than, to match.492 if prospective.local is not None:493 if Version(prospective.base_version) == Version(spec.base_version):494 return False495 496 # If we've gotten to here, it means that prospective version is both497 # greater than the spec version *and* it's not a pre-release of the498 # same version in the spec.499 return True500 501 def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:502 return str(prospective).lower() == str(spec).lower()503 504 def __contains__(self, item: Union[str, Version]) -> bool:505 """Return whether or not the item is contained in this specifier.506 507 :param item: The item to check for.508 509 This is used for the ``in`` operator and behaves the same as510 :meth:`contains` with no ``prereleases`` argument passed.511 512 >>> "1.2.3" in Specifier(">=1.2.3")513 True514 >>> Version("1.2.3") in Specifier(">=1.2.3")515 True516 >>> "1.0.0" in Specifier(">=1.2.3")517 False518 >>> "1.3.0a1" in Specifier(">=1.2.3")519 False520 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)521 True522 """523 return self.contains(item)524 525 def contains(526 self, item: UnparsedVersion, prereleases: Optional[bool] = None527 ) -> bool:528 """Return whether or not the item is contained in this specifier.529 530 :param item:531 The item to check for, which can be a version string or a532 :class:`Version` instance.533 :param prereleases:534 Whether or not to match prereleases with this Specifier. If set to535 ``None`` (the default), it uses :attr:`prereleases` to determine536 whether or not prereleases are allowed.537 538 >>> Specifier(">=1.2.3").contains("1.2.3")539 True540 >>> Specifier(">=1.2.3").contains(Version("1.2.3"))541 True542 >>> Specifier(">=1.2.3").contains("1.0.0")543 False544 >>> Specifier(">=1.2.3").contains("1.3.0a1")545 False546 >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1")547 True548 >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True)549 True550 """551 552 # Determine if prereleases are to be allowed or not.553 if prereleases is None:554 prereleases = self.prereleases555 556 # Normalize item to a Version, this allows us to have a shortcut for557 # "2.0" in Specifier(">=2")558 normalized_item = _coerce_version(item)559 560 # Determine if we should be supporting prereleases in this specifier561 # or not, if we do not support prereleases than we can short circuit562 # logic if this version is a prereleases.563 if normalized_item.is_prerelease and not prereleases:564 return False565 566 # Actually do the comparison to determine if this item is contained567 # within this Specifier or not.568 operator_callable: CallableOperator = self._get_operator(self.operator)569 return operator_callable(normalized_item, self.version)570 571 def filter(572 self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None573 ) -> Iterator[UnparsedVersionVar]:574 """Filter items in the given iterable, that match the specifier.575 576 :param iterable:577 An iterable that can contain version strings and :class:`Version` instances.578 The items in the iterable will be filtered according to the specifier.579 :param prereleases:580 Whether or not to allow prereleases in the returned iterator. If set to581 ``None`` (the default), it will be intelligently decide whether to allow582 prereleases or not (based on the :attr:`prereleases` attribute, and583 whether the only versions matching are prereleases).584 585 This method is smarter than just ``filter(Specifier().contains, [...])``586 because it implements the rule from :pep:`440` that a prerelease item587 SHOULD be accepted if no other versions match the given specifier.588 589 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))590 ['1.3']591 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))592 ['1.2.3', '1.3', <Version('1.4')>]593 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))594 ['1.5a1']595 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))596 ['1.3', '1.5a1']597 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))598 ['1.3', '1.5a1']599 """600 601 yielded = False602 found_prereleases = []603 604 kw = {"prereleases": prereleases if prereleases is not None else True}605 606 # Attempt to iterate over all the values in the iterable and if any of607 # them match, yield them.608 for version in iterable:609 parsed_version = _coerce_version(version)610 611 if self.contains(parsed_version, **kw):612 # If our version is a prerelease, and we were not set to allow613 # prereleases, then we'll store it for later in case nothing614 # else matches this specifier.615 if parsed_version.is_prerelease and not (616 prereleases or self.prereleases617 ):618 found_prereleases.append(version)619 # Either this is not a prerelease, or we should have been620 # accepting prereleases from the beginning.621 else:622 yielded = True623 yield version624 625 # Now that we've iterated over everything, determine if we've yielded626 # any values, and if we have not and we have any prereleases stored up627 # then we will go ahead and yield the prereleases.628 if not yielded and found_prereleases:629 for version in found_prereleases:630 yield version631 632 633_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")634 635 636def _version_split(version: str) -> List[str]:637 """Split version into components.638 639 The split components are intended for version comparison. The logic does640 not attempt to retain the original version string, so joining the641 components back with :func:`_version_join` may not produce the original642 version string.643 """644 result: List[str] = []645 646 epoch, _, rest = version.rpartition("!")647 result.append(epoch or "0")648 649 for item in rest.split("."):650 match = _prefix_regex.search(item)651 if match:652 result.extend(match.groups())653 else:654 result.append(item)655 return result656 657 658def _version_join(components: List[str]) -> str:659 """Join split version components into a version string.660 661 This function assumes the input came from :func:`_version_split`, where the662 first component must be the epoch (either empty or numeric), and all other663 components numeric.664 """665 epoch, *rest = components666 return f"{epoch}!{'.'.join(rest)}"667 668 669def _is_not_suffix(segment: str) -> bool:670 return not any(671 segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")672 )673 674 675def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]:676 left_split, right_split = [], []677 678 # Get the release segment of our versions679 left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))680 right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))681 682 # Get the rest of our versions683 left_split.append(left[len(left_split[0]) :])684 right_split.append(right[len(right_split[0]) :])685 686 # Insert our padding687 left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))688 right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))689 690 return (691 list(itertools.chain.from_iterable(left_split)),692 list(itertools.chain.from_iterable(right_split)),693 )694 695 696class SpecifierSet(BaseSpecifier):697 """This class abstracts handling of a set of version specifiers.698 699 It can be passed a single specifier (``>=3.0``), a comma-separated list of700 specifiers (``>=3.0,!=3.1``), or no specifier at all.701 """702 703 def __init__(704 self, specifiers: str = "", prereleases: Optional[bool] = None705 ) -> None:706 """Initialize a SpecifierSet instance.707 708 :param specifiers:709 The string representation of a specifier or a comma-separated list of710 specifiers which will be parsed and normalized before use.711 :param prereleases:712 This tells the SpecifierSet if it should accept prerelease versions if713 applicable or not. The default of ``None`` will autodetect it from the714 given specifiers.715 716 :raises InvalidSpecifier:717 If the given ``specifiers`` are not parseable than this exception will be718 raised.719 """720 721 # Split on `,` to break each individual specifier into it's own item, and722 # strip each item to remove leading/trailing whitespace.723 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]724 725 # Make each individual specifier a Specifier and save in a frozen set for later.726 self._specs = frozenset(map(Specifier, split_specifiers))727 728 # Store our prereleases value so we can use it later to determine if729 # we accept prereleases or not.730 self._prereleases = prereleases731 732 @property733 def prereleases(self) -> Optional[bool]:734 # If we have been given an explicit prerelease modifier, then we'll735 # pass that through here.736 if self._prereleases is not None:737 return self._prereleases738 739 # If we don't have any specifiers, and we don't have a forced value,740 # then we'll just return None since we don't know if this should have741 # pre-releases or not.742 if not self._specs:743 return None744 745 # Otherwise we'll see if any of the given specifiers accept746 # prereleases, if any of them do we'll return True, otherwise False.747 return any(s.prereleases for s in self._specs)748 749 @prereleases.setter750 def prereleases(self, value: bool) -> None:751 self._prereleases = value752 753 def __repr__(self) -> str:754 """A representation of the specifier set that shows all internal state.755 756 Note that the ordering of the individual specifiers within the set may not757 match the input string.758 759 >>> SpecifierSet('>=1.0.0,!=2.0.0')760 <SpecifierSet('!=2.0.0,>=1.0.0')>761 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)762 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>763 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)764 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>765 """766 pre = (767 f", prereleases={self.prereleases!r}"768 if self._prereleases is not None769 else ""770 )771 772 return f"<SpecifierSet({str(self)!r}{pre})>"773 774 def __str__(self) -> str:775 """A string representation of the specifier set that can be round-tripped.776 777 Note that the ordering of the individual specifiers within the set may not778 match the input string.779 780 >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))781 '!=1.0.1,>=1.0.0'782 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))783 '!=1.0.1,>=1.0.0'784 """785 return ",".join(sorted(str(s) for s in self._specs))786 787 def __hash__(self) -> int:788 return hash(self._specs)789 790 def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet":791 """Return a SpecifierSet which is a combination of the two sets.792 793 :param other: The other object to combine with.794 795 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'796 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>797 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')798 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>799 """800 if isinstance(other, str):801 other = SpecifierSet(other)802 elif not isinstance(other, SpecifierSet):803 return NotImplemented804 805 specifier = SpecifierSet()806 specifier._specs = frozenset(self._specs | other._specs)807 808 if self._prereleases is None and other._prereleases is not None:809 specifier._prereleases = other._prereleases810 elif self._prereleases is not None and other._prereleases is None:811 specifier._prereleases = self._prereleases812 elif self._prereleases == other._prereleases:813 specifier._prereleases = self._prereleases814 else:815 raise ValueError(816 "Cannot combine SpecifierSets with True and False prerelease "817 "overrides."818 )819 820 return specifier821 822 def __eq__(self, other: object) -> bool:823 """Whether or not the two SpecifierSet-like objects are equal.824 825 :param other: The other object to check against.826 827 The value of :attr:`prereleases` is ignored.828 829 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")830 True831 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==832 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))833 True834 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"835 True836 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")837 False838 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")839 False840 """841 if isinstance(other, (str, Specifier)):842 other = SpecifierSet(str(other))843 elif not isinstance(other, SpecifierSet):844 return NotImplemented845 846 return self._specs == other._specs847 848 def __len__(self) -> int:849 """Returns the number of specifiers in this specifier set."""850 return len(self._specs)851 852 def __iter__(self) -> Iterator[Specifier]:853 """854 Returns an iterator over all the underlying :class:`Specifier` instances855 in this specifier set.856 857 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)858 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]859 """860 return iter(self._specs)861 862 def __contains__(self, item: UnparsedVersion) -> bool:863 """Return whether or not the item is contained in this specifier.864 865 :param item: The item to check for.866 867 This is used for the ``in`` operator and behaves the same as868 :meth:`contains` with no ``prereleases`` argument passed.869 870 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")871 True872 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")873 True874 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")875 False876 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")877 False878 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)879 True880 """881 return self.contains(item)882 883 def contains(884 self,885 item: UnparsedVersion,886 prereleases: Optional[bool] = None,887 installed: Optional[bool] = None,888 ) -> bool:889 """Return whether or not the item is contained in this SpecifierSet.890 891 :param item:892 The item to check for, which can be a version string or a893 :class:`Version` instance.894 :param prereleases:895 Whether or not to match prereleases with this SpecifierSet. If set to896 ``None`` (the default), it uses :attr:`prereleases` to determine897 whether or not prereleases are allowed.898 899 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")900 True901 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))902 True903 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")904 False905 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")906 False907 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True).contains("1.3.0a1")908 True909 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)910 True911 """912 # Ensure that our item is a Version instance.913 if not isinstance(item, Version):914 item = Version(item)915 916 # Determine if we're forcing a prerelease or not, if we're not forcing917 # one for this particular filter call, then we'll use whatever the918 # SpecifierSet thinks for whether or not we should support prereleases.919 if prereleases is None:920 prereleases = self.prereleases921 922 # We can determine if we're going to allow pre-releases by looking to923 # see if any of the underlying items supports them. If none of them do924 # and this item is a pre-release then we do not allow it and we can925 # short circuit that here.926 # Note: This means that 1.0.dev1 would not be contained in something927 # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0928 if not prereleases and item.is_prerelease:929 return False930 931 if installed and item.is_prerelease:932 item = Version(item.base_version)933 934 # We simply dispatch to the underlying specs here to make sure that the935 # given version is contained within all of them.936 # Note: This use of all() here means that an empty set of specifiers937 # will always return True, this is an explicit design decision.938 return all(s.contains(item, prereleases=prereleases) for s in self._specs)939 940 def filter(941 self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None942 ) -> Iterator[UnparsedVersionVar]:943 """Filter items in the given iterable, that match the specifiers in this set.944 945 :param iterable:946 An iterable that can contain version strings and :class:`Version` instances.947 The items in the iterable will be filtered according to the specifier.948 :param prereleases:949 Whether or not to allow prereleases in the returned iterator. If set to950 ``None`` (the default), it will be intelligently decide whether to allow951 prereleases or not (based on the :attr:`prereleases` attribute, and952 whether the only versions matching are prereleases).953 954 This method is smarter than just ``filter(SpecifierSet(...).contains, [...])``955 because it implements the rule from :pep:`440` that a prerelease item956 SHOULD be accepted if no other versions match the given specifier.957 958 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))959 ['1.3']960 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))961 ['1.3', <Version('1.4')>]962 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))963 []964 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))965 ['1.3', '1.5a1']966 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))967 ['1.3', '1.5a1']968 969 An "empty" SpecifierSet will filter items based on the presence of prerelease970 versions in the set.971 972 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))973 ['1.3']974 >>> list(SpecifierSet("").filter(["1.5a1"]))975 ['1.5a1']976 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))977 ['1.3', '1.5a1']978 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))979 ['1.3', '1.5a1']980 """981 # Determine if we're forcing a prerelease or not, if we're not forcing982 # one for this particular filter call, then we'll use whatever the983 # SpecifierSet thinks for whether or not we should support prereleases.984 if prereleases is None:985 prereleases = self.prereleases986 987 # If we have any specifiers, then we want to wrap our iterable in the988 # filter method for each one, this will act as a logical AND amongst989 # each specifier.990 if self._specs:991 for spec in self._specs:992 iterable = spec.filter(iterable, prereleases=bool(prereleases))993 return iter(iterable)994 # If we do not have any specifiers, then we need to have a rough filter995 # which will filter out any pre-releases, unless there are no final996 # releases.997 else:998 filtered: List[UnparsedVersionVar] = []999 found_prereleases: List[UnparsedVersionVar] = []1000 1001 for item in iterable:1002 parsed_version = _coerce_version(item)1003 1004 # Store any item which is a pre-release for later unless we've1005 # already found a final version or we are accepting prereleases1006 if parsed_version.is_prerelease and not prereleases:1007 if not filtered:1008 found_prereleases.append(item)1009 else:1010 filtered.append(item)1011 1012 # If we've found no items except for pre-releases, then we'll go1013 # ahead and use the pre-releases1014 if not filtered and found_prereleases and prereleases is None:1015 return iter(found_prereleases)1016 1017 return iter(filtered)1018 