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