Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/packaging/specifiers.py: 21%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

384 statements  

1# This file is dual licensed under the terms of the Apache License, Version 

2# 2.0, and the BSD License. See the LICENSE file in the root of this repository 

3# for complete details. 

4""" 

5.. testsetup:: 

6 

7 from packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier 

8 from packaging.version import Version 

9""" 

10 

11from __future__ import annotations 

12 

13import abc 

14import itertools 

15import re 

16import typing 

17from typing import Any, Callable, Final, Iterable, Iterator, TypeVar, Union 

18 

19from .utils import canonicalize_version 

20from .version import InvalidVersion, Version 

21 

22__all__ = [ 

23 "BaseSpecifier", 

24 "InvalidSpecifier", 

25 "Specifier", 

26 "SpecifierSet", 

27] 

28 

29 

30def __dir__() -> list[str]: 

31 return __all__ 

32 

33 

34T = TypeVar("T") 

35UnparsedVersion = Union[Version, str] 

36UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion) 

37CallableOperator = Callable[[Version, str], bool] 

38 

39 

40def _coerce_version(version: UnparsedVersion) -> Version | None: 

41 if not isinstance(version, Version): 

42 try: 

43 version = Version(version) 

44 except InvalidVersion: 

45 return None 

46 return version 

47 

48 

49def _public_version(version: Version) -> Version: 

50 if version.local is None: 

51 return version 

52 return version.__replace__(local=None) 

53 

54 

55def _post_base(version: Version) -> Version: 

56 """The version that *version* is a post-release of. 

57 

58 1.0.post1 -> 1.0, 1.0a1.post0 -> 1.0a1, 1.0.post0.dev1 -> 1.0. 

59 """ 

60 return version.__replace__(post=None, dev=None, local=None) 

61 

62 

63def _earliest_prerelease(version: Version) -> Version: 

64 """Earliest pre-release of *version*. 

65 

66 1.2 -> 1.2.dev0, 1.2.post1 -> 1.2.post1.dev0. 

67 """ 

68 return version.__replace__(dev=0, local=None) 

69 

70 

71class InvalidSpecifier(ValueError): 

72 """ 

73 Raised when attempting to create a :class:`Specifier` with a specifier 

74 string that is invalid. 

75 

76 >>> Specifier("lolwat") 

77 Traceback (most recent call last): 

78 ... 

79 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat' 

80 """ 

81 

82 

83class BaseSpecifier(metaclass=abc.ABCMeta): 

84 __slots__ = () 

85 __match_args__ = ("_str",) 

86 

87 @property 

88 def _str(self) -> str: 

89 """Internal property for match_args""" 

90 return str(self) 

91 

92 @abc.abstractmethod 

93 def __str__(self) -> str: 

94 """ 

95 Returns the str representation of this Specifier-like object. This 

96 should be representative of the Specifier itself. 

97 """ 

98 

99 @abc.abstractmethod 

100 def __hash__(self) -> int: 

101 """ 

102 Returns a hash value for this Specifier-like object. 

103 """ 

104 

105 @abc.abstractmethod 

106 def __eq__(self, other: object) -> bool: 

107 """ 

108 Returns a boolean representing whether or not the two Specifier-like 

109 objects are equal. 

110 

111 :param other: The other object to check against. 

112 """ 

113 

114 @property 

115 @abc.abstractmethod 

116 def prereleases(self) -> bool | None: 

117 """Whether or not pre-releases as a whole are allowed. 

118 

119 This can be set to either ``True`` or ``False`` to explicitly enable or disable 

120 prereleases or it can be set to ``None`` (the default) to use default semantics. 

121 """ 

122 

123 @prereleases.setter # noqa: B027 

124 def prereleases(self, value: bool) -> None: 

125 """Setter for :attr:`prereleases`. 

126 

127 :param value: The value to set. 

128 """ 

129 

130 @abc.abstractmethod 

131 def contains(self, item: str, prereleases: bool | None = None) -> bool: 

132 """ 

133 Determines if the given item is contained within this specifier. 

134 """ 

135 

136 @typing.overload 

137 def filter( 

138 self, 

139 iterable: Iterable[UnparsedVersionVar], 

140 prereleases: bool | None = None, 

141 key: None = ..., 

142 ) -> Iterator[UnparsedVersionVar]: ... 

143 

144 @typing.overload 

145 def filter( 

146 self, 

147 iterable: Iterable[T], 

148 prereleases: bool | None = None, 

149 key: Callable[[T], UnparsedVersion] = ..., 

150 ) -> Iterator[T]: ... 

151 

152 @abc.abstractmethod 

153 def filter( 

154 self, 

155 iterable: Iterable[Any], 

156 prereleases: bool | None = None, 

157 key: Callable[[Any], UnparsedVersion] | None = None, 

158 ) -> Iterator[Any]: 

159 """ 

160 Takes an iterable of items and filters them so that only items which 

161 are contained within this specifier are allowed in it. 

162 """ 

163 

164 

165class Specifier(BaseSpecifier): 

166 """This class abstracts handling of version specifiers. 

167 

168 .. tip:: 

169 

170 It is generally not required to instantiate this manually. You should instead 

171 prefer to work with :class:`SpecifierSet` instead, which can parse 

172 comma-separated version specifiers (which is what package metadata contains). 

173 """ 

174 

175 __slots__ = ("_prereleases", "_spec", "_spec_version", "_wildcard_split") 

176 

177 _specifier_regex_str = r""" 

178 (?: 

179 (?: 

180 # The identity operators allow for an escape hatch that will 

181 # do an exact string match of the version you wish to install. 

182 # This will not be parsed by PEP 440 and we cannot determine 

183 # any semantic meaning from it. This operator is discouraged 

184 # but included entirely as an escape hatch. 

185 === # Only match for the identity operator 

186 \s* 

187 [^\s;)]* # The arbitrary version can be just about anything, 

188 # we match everything except for whitespace, a 

189 # semi-colon for marker support, and a closing paren 

190 # since versions can be enclosed in them. 

191 ) 

192 | 

193 (?: 

194 # The (non)equality operators allow for wild card and local 

195 # versions to be specified so we have to define these two 

196 # operators separately to enable that. 

197 (?:==|!=) # Only match for equals and not equals 

198 

199 \s* 

200 v? 

201 (?:[0-9]+!)? # epoch 

202 [0-9]+(?:\.[0-9]+)* # release 

203 

204 # You cannot use a wild card and a pre-release, post-release, a dev or 

205 # local version together so group them with a | and make them optional. 

206 (?: 

207 \.\* # Wild card syntax of .* 

208 | 

209 (?a: # pre release 

210 [-_\.]? 

211 (alpha|beta|preview|pre|a|b|c|rc) 

212 [-_\.]? 

213 [0-9]* 

214 )? 

215 (?a: # post release 

216 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) 

217 )? 

218 (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release 

219 (?a:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local 

220 )? 

221 ) 

222 | 

223 (?: 

224 # The compatible operator requires at least two digits in the 

225 # release segment. 

226 (?:~=) # Only match for the compatible operator 

227 

228 \s* 

229 v? 

230 (?:[0-9]+!)? # epoch 

231 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) 

232 (?: # pre release 

233 [-_\.]? 

234 (alpha|beta|preview|pre|a|b|c|rc) 

235 [-_\.]? 

236 [0-9]* 

237 )? 

238 (?: # post release 

239 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) 

240 )? 

241 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release 

242 ) 

243 | 

244 (?: 

245 # All other operators only allow a sub set of what the 

246 # (non)equality operators do. Specifically they do not allow 

247 # local versions to be specified nor do they allow the prefix 

248 # matching wild cards. 

249 (?:<=|>=|<|>) 

250 

251 \s* 

252 v? 

253 (?:[0-9]+!)? # epoch 

254 [0-9]+(?:\.[0-9]+)* # release 

255 (?a: # pre release 

256 [-_\.]? 

257 (alpha|beta|preview|pre|a|b|c|rc) 

258 [-_\.]? 

259 [0-9]* 

260 )? 

261 (?a: # post release 

262 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) 

263 )? 

264 (?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release 

265 ) 

266 ) 

267 """ 

268 

269 _regex = re.compile( 

270 r"\s*" + _specifier_regex_str + r"\s*", re.VERBOSE | re.IGNORECASE 

271 ) 

272 

273 _operators: Final = { 

274 "~=": "compatible", 

275 "==": "equal", 

276 "!=": "not_equal", 

277 "<=": "less_than_equal", 

278 ">=": "greater_than_equal", 

279 "<": "less_than", 

280 ">": "greater_than", 

281 "===": "arbitrary", 

282 } 

283 

284 def __init__(self, spec: str = "", prereleases: bool | None = None) -> None: 

285 """Initialize a Specifier instance. 

286 

287 :param spec: 

288 The string representation of a specifier which will be parsed and 

289 normalized before use. 

290 :param prereleases: 

291 This tells the specifier if it should accept prerelease versions if 

292 applicable or not. The default of ``None`` will autodetect it from the 

293 given specifiers. 

294 :raises InvalidSpecifier: 

295 If the given specifier is invalid (i.e. bad syntax). 

296 """ 

297 if not self._regex.fullmatch(spec): 

298 raise InvalidSpecifier(f"Invalid specifier: {spec!r}") 

299 

300 spec = spec.strip() 

301 if spec.startswith("==="): 

302 operator, version = spec[:3], spec[3:].strip() 

303 elif spec.startswith(("~=", "==", "!=", "<=", ">=")): 

304 operator, version = spec[:2], spec[2:].strip() 

305 else: 

306 operator, version = spec[:1], spec[1:].strip() 

307 

308 self._spec: tuple[str, str] = (operator, version) 

309 

310 # Store whether or not this Specifier should accept prereleases 

311 self._prereleases = prereleases 

312 

313 # Specifier version cache 

314 self._spec_version: tuple[str, Version] | None = None 

315 

316 # Populated on first wildcard (==X.*) comparison 

317 self._wildcard_split: tuple[list[str], int] | None = None 

318 

319 def _get_spec_version(self, version: str) -> Version | None: 

320 """One element cache, as only one spec Version is needed per Specifier.""" 

321 if self._spec_version is not None and self._spec_version[0] == version: 

322 return self._spec_version[1] 

323 

324 version_specifier = _coerce_version(version) 

325 if version_specifier is None: 

326 return None 

327 

328 self._spec_version = (version, version_specifier) 

329 return version_specifier 

330 

331 def _require_spec_version(self, version: str) -> Version: 

332 """Get spec version, asserting it's valid (not for === operator). 

333 

334 This method should only be called for operators where version 

335 strings are guaranteed to be valid PEP 440 versions (not ===). 

336 """ 

337 spec_version = self._get_spec_version(version) 

338 assert spec_version is not None 

339 return spec_version 

340 

341 @property 

342 def prereleases(self) -> bool | None: 

343 # If there is an explicit prereleases set for this, then we'll just 

344 # blindly use that. 

345 if self._prereleases is not None: 

346 return self._prereleases 

347 

348 # Only the "!=" operator does not imply prereleases when 

349 # the version in the specifier is a prerelease. 

350 operator, version_str = self._spec 

351 if operator == "!=": 

352 return False 

353 

354 # The == specifier with trailing .* cannot include prereleases 

355 # e.g. "==1.0a1.*" is not valid. 

356 if operator == "==" and version_str.endswith(".*"): 

357 return False 

358 

359 # "===" can have arbitrary string versions, so we cannot parse 

360 # those, we take prereleases as unknown (None) for those. 

361 version = self._get_spec_version(version_str) 

362 if version is None: 

363 return None 

364 

365 # For all other operators, use the check if spec Version 

366 # object implies pre-releases. 

367 return version.is_prerelease 

368 

369 @prereleases.setter 

370 def prereleases(self, value: bool | None) -> None: 

371 self._prereleases = value 

372 

373 @property 

374 def operator(self) -> str: 

375 """The operator of this specifier. 

376 

377 >>> Specifier("==1.2.3").operator 

378 '==' 

379 """ 

380 return self._spec[0] 

381 

382 @property 

383 def version(self) -> str: 

384 """The version of this specifier. 

385 

386 >>> Specifier("==1.2.3").version 

387 '1.2.3' 

388 """ 

389 return self._spec[1] 

390 

391 def __repr__(self) -> str: 

392 """A representation of the Specifier that shows all internal state. 

393 

394 >>> Specifier('>=1.0.0') 

395 <Specifier('>=1.0.0')> 

396 >>> Specifier('>=1.0.0', prereleases=False) 

397 <Specifier('>=1.0.0', prereleases=False)> 

398 >>> Specifier('>=1.0.0', prereleases=True) 

399 <Specifier('>=1.0.0', prereleases=True)> 

400 """ 

401 pre = ( 

402 f", prereleases={self.prereleases!r}" 

403 if self._prereleases is not None 

404 else "" 

405 ) 

406 

407 return f"<{self.__class__.__name__}({str(self)!r}{pre})>" 

408 

409 def __str__(self) -> str: 

410 """A string representation of the Specifier that can be round-tripped. 

411 

412 >>> str(Specifier('>=1.0.0')) 

413 '>=1.0.0' 

414 >>> str(Specifier('>=1.0.0', prereleases=False)) 

415 '>=1.0.0' 

416 """ 

417 return "{}{}".format(*self._spec) 

418 

419 @property 

420 def _canonical_spec(self) -> tuple[str, str]: 

421 operator, version = self._spec 

422 if operator == "===" or version.endswith(".*"): 

423 return operator, version 

424 

425 spec_version = self._require_spec_version(version) 

426 

427 canonical_version = canonicalize_version( 

428 spec_version, strip_trailing_zero=(operator != "~=") 

429 ) 

430 

431 return operator, canonical_version 

432 

433 def __hash__(self) -> int: 

434 return hash(self._canonical_spec) 

435 

436 def __eq__(self, other: object) -> bool: 

437 """Whether or not the two Specifier-like objects are equal. 

438 

439 :param other: The other object to check against. 

440 

441 The value of :attr:`prereleases` is ignored. 

442 

443 >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0") 

444 True 

445 >>> (Specifier("==1.2.3", prereleases=False) == 

446 ... Specifier("==1.2.3", prereleases=True)) 

447 True 

448 >>> Specifier("==1.2.3") == "==1.2.3" 

449 True 

450 >>> Specifier("==1.2.3") == Specifier("==1.2.4") 

451 False 

452 >>> Specifier("==1.2.3") == Specifier("~=1.2.3") 

453 False 

454 """ 

455 if isinstance(other, str): 

456 try: 

457 other = self.__class__(str(other)) 

458 except InvalidSpecifier: 

459 return NotImplemented 

460 elif not isinstance(other, self.__class__): 

461 return NotImplemented 

462 

463 return self._canonical_spec == other._canonical_spec 

464 

465 def _get_operator(self, op: str) -> CallableOperator: 

466 operator_callable: CallableOperator = getattr( 

467 self, f"_compare_{self._operators[op]}" 

468 ) 

469 return operator_callable 

470 

471 def _compare_compatible(self, prospective: Version, spec: str) -> bool: 

472 # Compatible releases have an equivalent combination of >= and ==. That 

473 # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to 

474 # implement this in terms of the other specifiers instead of 

475 # implementing it ourselves. The only thing we need to do is construct 

476 # the other specifiers. 

477 

478 # We want everything but the last item in the version, but we want to 

479 # ignore suffix segments. 

480 prefix = _version_join( 

481 list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] 

482 ) 

483 

484 # Add the prefix notation to the end of our string 

485 prefix += ".*" 

486 

487 return (self._compare_greater_than_equal(prospective, spec)) and ( 

488 self._compare_equal(prospective, prefix) 

489 ) 

490 

491 def _get_wildcard_split(self, spec: str) -> tuple[list[str], int]: 

492 """Cached split of a wildcard spec into components and numeric length. 

493 

494 >>> Specifier("==1.*")._get_wildcard_split("1.*") 

495 (['0', '1'], 2) 

496 >>> Specifier("==3.10.*")._get_wildcard_split("3.10.*") 

497 (['0', '3', '10'], 3) 

498 """ 

499 wildcard_split = self._wildcard_split 

500 if wildcard_split is None: 

501 normalized = canonicalize_version(spec[:-2], strip_trailing_zero=False) 

502 split_spec = _version_split(normalized) 

503 wildcard_split = (split_spec, _numeric_prefix_len(split_spec)) 

504 self._wildcard_split = wildcard_split 

505 return wildcard_split 

506 

507 def _compare_equal(self, prospective: Version, spec: str) -> bool: 

508 # We need special logic to handle prefix matching 

509 if spec.endswith(".*"): 

510 split_spec, spec_numeric_len = self._get_wildcard_split(spec) 

511 

512 # In the case of prefix matching we want to ignore local segment. 

513 normalized_prospective = canonicalize_version( 

514 _public_version(prospective), strip_trailing_zero=False 

515 ) 

516 # Split the prospective version out by bangs and dots, and pretend 

517 # that there is an implicit dot in between a release segment and 

518 # a pre-release segment. 

519 split_prospective = _version_split(normalized_prospective) 

520 

521 # 0-pad the prospective version before shortening it to get the correct 

522 # shortened version. 

523 padded_prospective = _left_pad(split_prospective, spec_numeric_len) 

524 

525 # Shorten the prospective version to be the same length as the spec 

526 # so that we can determine if the specifier is a prefix of the 

527 # prospective version or not. 

528 shortened_prospective = padded_prospective[: len(split_spec)] 

529 

530 return shortened_prospective == split_spec 

531 else: 

532 # Convert our spec string into a Version 

533 spec_version = self._require_spec_version(spec) 

534 

535 # If the specifier does not have a local segment, then we want to 

536 # act as if the prospective version also does not have a local 

537 # segment. 

538 if not spec_version.local: 

539 prospective = _public_version(prospective) 

540 

541 return prospective == spec_version 

542 

543 def _compare_not_equal(self, prospective: Version, spec: str) -> bool: 

544 return not self._compare_equal(prospective, spec) 

545 

546 def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool: 

547 # NB: Local version identifiers are NOT permitted in the version 

548 # specifier, so local version labels can be universally removed from 

549 # the prospective version. 

550 return _public_version(prospective) <= self._require_spec_version(spec) 

551 

552 def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool: 

553 # NB: Local version identifiers are NOT permitted in the version 

554 # specifier, so local version labels can be universally removed from 

555 # the prospective version. 

556 return _public_version(prospective) >= self._require_spec_version(spec) 

557 

558 def _compare_less_than(self, prospective: Version, spec_str: str) -> bool: 

559 # Convert our spec to a Version instance, since we'll want to work with 

560 # it as a version. 

561 spec = self._require_spec_version(spec_str) 

562 

563 # Check to see if the prospective version is less than the spec 

564 # version. If it's not we can short circuit and just return False now 

565 # instead of doing extra unneeded work. 

566 if not prospective < spec: 

567 return False 

568 

569 # The spec says: "<V MUST NOT allow a pre-release of the specified 

570 # version unless the specified version is itself a pre-release." 

571 if ( 

572 not spec.is_prerelease 

573 and prospective.is_prerelease 

574 and prospective >= _earliest_prerelease(spec) 

575 ): 

576 return False 

577 

578 # If we've gotten to here, it means that prospective version is both 

579 # less than the spec version *and* it's not a pre-release of the same 

580 # version in the spec. 

581 return True 

582 

583 def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool: 

584 # Convert our spec to a Version instance, since we'll want to work with 

585 # it as a version. 

586 spec = self._require_spec_version(spec_str) 

587 

588 # Check to see if the prospective version is greater than the spec 

589 # version. If it's not we can short circuit and just return False now 

590 # instead of doing extra unneeded work. 

591 if not prospective > spec: 

592 return False 

593 

594 # The spec says: ">V MUST NOT allow a post-release of the specified 

595 # version unless the specified version is itself a post-release." 

596 if ( 

597 not spec.is_postrelease 

598 and prospective.is_postrelease 

599 and _post_base(prospective) == spec 

600 ): 

601 return False 

602 

603 # Per the spec: ">V MUST NOT match a local version of the specified 

604 # version". A "local version of V" is any version whose public part 

605 # equals V. So >1.0a1 must not match 1.0a1+local, but must still 

606 # match 1.0a2+local. 

607 if prospective.local is not None and _public_version(prospective) == spec: 

608 return False 

609 

610 # If we've gotten to here, it means that prospective version is both 

611 # greater than the spec version *and* it's not a pre-release of the 

612 # same version in the spec. 

613 return True 

614 

615 def _compare_arbitrary(self, prospective: Version | str, spec: str) -> bool: 

616 return str(prospective).lower() == str(spec).lower() 

617 

618 def __contains__(self, item: str | Version) -> bool: 

619 """Return whether or not the item is contained in this specifier. 

620 

621 :param item: The item to check for. 

622 

623 This is used for the ``in`` operator and behaves the same as 

624 :meth:`contains` with no ``prereleases`` argument passed. 

625 

626 >>> "1.2.3" in Specifier(">=1.2.3") 

627 True 

628 >>> Version("1.2.3") in Specifier(">=1.2.3") 

629 True 

630 >>> "1.0.0" in Specifier(">=1.2.3") 

631 False 

632 >>> "1.3.0a1" in Specifier(">=1.2.3") 

633 True 

634 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True) 

635 True 

636 """ 

637 return self.contains(item) 

638 

639 def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool: 

640 """Return whether or not the item is contained in this specifier. 

641 

642 :param item: 

643 The item to check for, which can be a version string or a 

644 :class:`Version` instance. 

645 :param prereleases: 

646 Whether or not to match prereleases with this Specifier. If set to 

647 ``None`` (the default), it will follow the recommendation from 

648 :pep:`440` and match prereleases, as there are no other versions. 

649 

650 >>> Specifier(">=1.2.3").contains("1.2.3") 

651 True 

652 >>> Specifier(">=1.2.3").contains(Version("1.2.3")) 

653 True 

654 >>> Specifier(">=1.2.3").contains("1.0.0") 

655 False 

656 >>> Specifier(">=1.2.3").contains("1.3.0a1") 

657 True 

658 >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1") 

659 False 

660 >>> Specifier(">=1.2.3").contains("1.3.0a1") 

661 True 

662 """ 

663 

664 return bool(list(self.filter([item], prereleases=prereleases))) 

665 

666 @typing.overload 

667 def filter( 

668 self, 

669 iterable: Iterable[UnparsedVersionVar], 

670 prereleases: bool | None = None, 

671 key: None = ..., 

672 ) -> Iterator[UnparsedVersionVar]: ... 

673 

674 @typing.overload 

675 def filter( 

676 self, 

677 iterable: Iterable[T], 

678 prereleases: bool | None = None, 

679 key: Callable[[T], UnparsedVersion] = ..., 

680 ) -> Iterator[T]: ... 

681 

682 def filter( 

683 self, 

684 iterable: Iterable[Any], 

685 prereleases: bool | None = None, 

686 key: Callable[[Any], UnparsedVersion] | None = None, 

687 ) -> Iterator[Any]: 

688 """Filter items in the given iterable, that match the specifier. 

689 

690 :param iterable: 

691 An iterable that can contain version strings and :class:`Version` instances. 

692 The items in the iterable will be filtered according to the specifier. 

693 :param prereleases: 

694 Whether or not to allow prereleases in the returned iterator. If set to 

695 ``None`` (the default), it will follow the recommendation from :pep:`440` 

696 and match prereleases if there are no other versions. 

697 :param key: 

698 A callable that takes a single argument (an item from the iterable) and 

699 returns a version string or :class:`Version` instance to be used for 

700 filtering. 

701 

702 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) 

703 ['1.3'] 

704 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")])) 

705 ['1.2.3', '1.3', <Version('1.4')>] 

706 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"])) 

707 ['1.5a1'] 

708 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) 

709 ['1.3', '1.5a1'] 

710 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) 

711 ['1.3', '1.5a1'] 

712 >>> list(Specifier(">=1.2.3").filter( 

713 ... [{"ver": "1.2"}, {"ver": "1.3"}], 

714 ... key=lambda x: x["ver"])) 

715 [{'ver': '1.3'}] 

716 """ 

717 prereleases_versions = [] 

718 found_non_prereleases = False 

719 

720 # Determine if to include prereleases by default 

721 include_prereleases = ( 

722 prereleases if prereleases is not None else self.prereleases 

723 ) 

724 

725 # Get the matching operator 

726 operator_callable = self._get_operator(self.operator) 

727 

728 # Filter versions 

729 for version in iterable: 

730 parsed_version = _coerce_version(version if key is None else key(version)) 

731 match = False 

732 if parsed_version is None: 

733 # === operator can match arbitrary (non-version) strings 

734 if self.operator == "===" and self._compare_arbitrary( 

735 version, self.version 

736 ): 

737 yield version 

738 elif self.operator == "===": 

739 match = self._compare_arbitrary( 

740 version if key is None else key(version), self.version 

741 ) 

742 else: 

743 match = operator_callable(parsed_version, self.version) 

744 

745 if match and parsed_version is not None: 

746 # If it's not a prerelease or prereleases are allowed, yield it directly 

747 if not parsed_version.is_prerelease or include_prereleases: 

748 found_non_prereleases = True 

749 yield version 

750 # Otherwise collect prereleases for potential later use 

751 elif prereleases is None and self._prereleases is not False: 

752 prereleases_versions.append(version) 

753 

754 # If no non-prereleases were found and prereleases weren't 

755 # explicitly forbidden, yield the collected prereleases 

756 if ( 

757 not found_non_prereleases 

758 and prereleases is None 

759 and self._prereleases is not False 

760 ): 

761 yield from prereleases_versions 

762 

763 

764_prefix_regex = re.compile(r"([0-9]+)((?:a|b|c|rc)[0-9]+)") 

765 

766 

767def _pep440_filter_prereleases( 

768 iterable: Iterable[Any], key: Callable[[Any], UnparsedVersion] | None 

769) -> Iterator[Any]: 

770 """Filter per PEP 440: exclude prereleases unless no finals exist.""" 

771 # Two lists used: 

772 # * all_nonfinal to preserve order if no finals exist 

773 # * arbitrary_strings for streaming when first final found 

774 all_nonfinal: list[Any] = [] 

775 arbitrary_strings: list[Any] = [] 

776 

777 found_final = False 

778 for item in iterable: 

779 parsed = _coerce_version(item if key is None else key(item)) 

780 

781 if parsed is None: 

782 # Arbitrary strings are always included as it is not 

783 # possible to determine if they are prereleases, 

784 # and they have already passed all specifiers. 

785 if found_final: 

786 yield item 

787 else: 

788 arbitrary_strings.append(item) 

789 all_nonfinal.append(item) 

790 continue 

791 

792 if not parsed.is_prerelease: 

793 # Final release found - flush arbitrary strings, then yield 

794 if not found_final: 

795 yield from arbitrary_strings 

796 found_final = True 

797 yield item 

798 continue 

799 

800 # Prerelease - buffer if no finals yet, otherwise skip 

801 if not found_final: 

802 all_nonfinal.append(item) 

803 

804 # No finals found - yield all buffered items 

805 if not found_final: 

806 yield from all_nonfinal 

807 

808 

809def _version_split(version: str) -> list[str]: 

810 """Split version into components. 

811 

812 The split components are intended for version comparison. The logic does 

813 not attempt to retain the original version string, so joining the 

814 components back with :func:`_version_join` may not produce the original 

815 version string. 

816 """ 

817 result: list[str] = [] 

818 

819 epoch, _, rest = version.rpartition("!") 

820 result.append(epoch or "0") 

821 

822 for item in rest.split("."): 

823 match = _prefix_regex.fullmatch(item) 

824 if match: 

825 result.extend(match.groups()) 

826 else: 

827 result.append(item) 

828 return result 

829 

830 

831def _version_join(components: list[str]) -> str: 

832 """Join split version components into a version string. 

833 

834 This function assumes the input came from :func:`_version_split`, where the 

835 first component must be the epoch (either empty or numeric), and all other 

836 components numeric. 

837 """ 

838 epoch, *rest = components 

839 return f"{epoch}!{'.'.join(rest)}" 

840 

841 

842def _is_not_suffix(segment: str) -> bool: 

843 return not any( 

844 segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") 

845 ) 

846 

847 

848def _numeric_prefix_len(split: list[str]) -> int: 

849 """Count leading numeric components in a :func:`_version_split` result. 

850 

851 >>> _numeric_prefix_len(["0", "1", "2", "a1"]) 

852 3 

853 """ 

854 count = 0 

855 for segment in split: 

856 if not segment.isdigit(): 

857 break 

858 count += 1 

859 return count 

860 

861 

862def _left_pad(split: list[str], target_numeric_len: int) -> list[str]: 

863 """Pad a :func:`_version_split` result with ``"0"`` segments to reach 

864 ``target_numeric_len`` numeric components. Suffix segments are preserved. 

865 

866 >>> _left_pad(["0", "1", "a1"], 4) 

867 ['0', '1', '0', '0', 'a1'] 

868 """ 

869 numeric_len = _numeric_prefix_len(split) 

870 pad_needed = target_numeric_len - numeric_len 

871 if pad_needed <= 0: 

872 return split 

873 return [*split[:numeric_len], *(["0"] * pad_needed), *split[numeric_len:]] 

874 

875 

876def _operator_cost(op_entry: tuple[CallableOperator, str, str]) -> int: 

877 """Sort key for Cost Based Ordering of specifier operators in _filter_versions. 

878 

879 Operators run sequentially on a shrinking candidate set, so operators that 

880 reject the most versions should run first to minimize work for later ones. 

881 

882 Tier 0: Exact equality (==, ===), likely to narrow candidates to one version 

883 Tier 1: Range checks (>=, <=, >, <), cheap and usually reject a large portion 

884 Tier 2: Wildcard equality (==.*) and compatible release (~=), more expensive 

885 Tier 3: Exact !=, cheap but rarely rejects 

886 Tier 4: Wildcard !=.*, expensive and rarely rejects 

887 """ 

888 _, ver, op = op_entry 

889 if op == "==": 

890 return 0 if not ver.endswith(".*") else 2 

891 if op in (">=", "<=", ">", "<"): 

892 return 1 

893 if op == "~=": 

894 return 2 

895 if op == "!=": 

896 return 3 if not ver.endswith(".*") else 4 

897 if op == "===": 

898 return 0 

899 

900 raise ValueError(f"Unknown operator: {op!r}") # pragma: no cover 

901 

902 

903class SpecifierSet(BaseSpecifier): 

904 """This class abstracts handling of a set of version specifiers. 

905 

906 It can be passed a single specifier (``>=3.0``), a comma-separated list of 

907 specifiers (``>=3.0,!=3.1``), or no specifier at all. 

908 """ 

909 

910 __slots__ = ( 

911 "_canonicalized", 

912 "_has_arbitrary", 

913 "_prereleases", 

914 "_resolved_ops", 

915 "_specs", 

916 ) 

917 

918 def __init__( 

919 self, 

920 specifiers: str | Iterable[Specifier] = "", 

921 prereleases: bool | None = None, 

922 ) -> None: 

923 """Initialize a SpecifierSet instance. 

924 

925 :param specifiers: 

926 The string representation of a specifier or a comma-separated list of 

927 specifiers which will be parsed and normalized before use. 

928 May also be an iterable of ``Specifier`` instances, which will be used 

929 as is. 

930 :param prereleases: 

931 This tells the SpecifierSet if it should accept prerelease versions if 

932 applicable or not. The default of ``None`` will autodetect it from the 

933 given specifiers. 

934 

935 :raises InvalidSpecifier: 

936 If the given ``specifiers`` are not parseable than this exception will be 

937 raised. 

938 """ 

939 

940 if isinstance(specifiers, str): 

941 # Split on `,` to break each individual specifier into its own item, and 

942 # strip each item to remove leading/trailing whitespace. 

943 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] 

944 

945 self._specs: tuple[Specifier, ...] = tuple(map(Specifier, split_specifiers)) 

946 # Fast substring check; avoids iterating parsed specs. 

947 self._has_arbitrary = "===" in specifiers 

948 else: 

949 self._specs = tuple(specifiers) 

950 # Substring check works for both Specifier objects and plain 

951 # strings (setuptools passes lists of strings). 

952 self._has_arbitrary = any("===" in str(s) for s in self._specs) 

953 

954 self._canonicalized = len(self._specs) <= 1 

955 self._resolved_ops: list[tuple[CallableOperator, str, str]] | None = None 

956 

957 # Store our prereleases value so we can use it later to determine if 

958 # we accept prereleases or not. 

959 self._prereleases = prereleases 

960 

961 def _canonical_specs(self) -> tuple[Specifier, ...]: 

962 """Deduplicate, sort, and cache specs for order-sensitive operations.""" 

963 if not self._canonicalized: 

964 self._specs = tuple(dict.fromkeys(sorted(self._specs, key=str))) 

965 self._canonicalized = True 

966 self._resolved_ops = None 

967 return self._specs 

968 

969 @property 

970 def prereleases(self) -> bool | None: 

971 # If we have been given an explicit prerelease modifier, then we'll 

972 # pass that through here. 

973 if self._prereleases is not None: 

974 return self._prereleases 

975 

976 # If we don't have any specifiers, and we don't have a forced value, 

977 # then we'll just return None since we don't know if this should have 

978 # pre-releases or not. 

979 if not self._specs: 

980 return None 

981 

982 # Otherwise we'll see if any of the given specifiers accept 

983 # prereleases, if any of them do we'll return True, otherwise False. 

984 if any(s.prereleases for s in self._specs): 

985 return True 

986 

987 return None 

988 

989 @prereleases.setter 

990 def prereleases(self, value: bool | None) -> None: 

991 self._prereleases = value 

992 

993 def __repr__(self) -> str: 

994 """A representation of the specifier set that shows all internal state. 

995 

996 Note that the ordering of the individual specifiers within the set may not 

997 match the input string. 

998 

999 >>> SpecifierSet('>=1.0.0,!=2.0.0') 

1000 <SpecifierSet('!=2.0.0,>=1.0.0')> 

1001 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False) 

1002 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)> 

1003 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True) 

1004 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)> 

1005 """ 

1006 pre = ( 

1007 f", prereleases={self.prereleases!r}" 

1008 if self._prereleases is not None 

1009 else "" 

1010 ) 

1011 

1012 return f"<{self.__class__.__name__}({str(self)!r}{pre})>" 

1013 

1014 def __str__(self) -> str: 

1015 """A string representation of the specifier set that can be round-tripped. 

1016 

1017 Note that the ordering of the individual specifiers within the set may not 

1018 match the input string. 

1019 

1020 >>> str(SpecifierSet(">=1.0.0,!=1.0.1")) 

1021 '!=1.0.1,>=1.0.0' 

1022 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False)) 

1023 '!=1.0.1,>=1.0.0' 

1024 """ 

1025 return ",".join(str(s) for s in self._canonical_specs()) 

1026 

1027 def __hash__(self) -> int: 

1028 return hash(self._canonical_specs()) 

1029 

1030 def __and__(self, other: SpecifierSet | str) -> SpecifierSet: 

1031 """Return a SpecifierSet which is a combination of the two sets. 

1032 

1033 :param other: The other object to combine with. 

1034 

1035 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1' 

1036 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')> 

1037 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1') 

1038 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')> 

1039 """ 

1040 if isinstance(other, str): 

1041 other = SpecifierSet(other) 

1042 elif not isinstance(other, SpecifierSet): 

1043 return NotImplemented 

1044 

1045 specifier = SpecifierSet() 

1046 specifier._specs = self._specs + other._specs 

1047 specifier._canonicalized = len(specifier._specs) <= 1 

1048 specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary 

1049 specifier._resolved_ops = None 

1050 

1051 # Combine prerelease settings: use common or non-None value 

1052 if self._prereleases is None or self._prereleases == other._prereleases: 

1053 specifier._prereleases = other._prereleases 

1054 elif other._prereleases is None: 

1055 specifier._prereleases = self._prereleases 

1056 else: 

1057 raise ValueError( 

1058 "Cannot combine SpecifierSets with True and False prerelease overrides." 

1059 ) 

1060 

1061 return specifier 

1062 

1063 def __eq__(self, other: object) -> bool: 

1064 """Whether or not the two SpecifierSet-like objects are equal. 

1065 

1066 :param other: The other object to check against. 

1067 

1068 The value of :attr:`prereleases` is ignored. 

1069 

1070 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1") 

1071 True 

1072 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) == 

1073 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)) 

1074 True 

1075 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1" 

1076 True 

1077 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0") 

1078 False 

1079 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2") 

1080 False 

1081 """ 

1082 if isinstance(other, (str, Specifier)): 

1083 other = SpecifierSet(str(other)) 

1084 elif not isinstance(other, SpecifierSet): 

1085 return NotImplemented 

1086 

1087 return self._canonical_specs() == other._canonical_specs() 

1088 

1089 def __len__(self) -> int: 

1090 """Returns the number of specifiers in this specifier set.""" 

1091 return len(self._specs) 

1092 

1093 def __iter__(self) -> Iterator[Specifier]: 

1094 """ 

1095 Returns an iterator over all the underlying :class:`Specifier` instances 

1096 in this specifier set. 

1097 

1098 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str) 

1099 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>] 

1100 """ 

1101 return iter(self._specs) 

1102 

1103 def __contains__(self, item: UnparsedVersion) -> bool: 

1104 """Return whether or not the item is contained in this specifier. 

1105 

1106 :param item: The item to check for. 

1107 

1108 This is used for the ``in`` operator and behaves the same as 

1109 :meth:`contains` with no ``prereleases`` argument passed. 

1110 

1111 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1") 

1112 True 

1113 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1") 

1114 True 

1115 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1") 

1116 False 

1117 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1") 

1118 True 

1119 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True) 

1120 True 

1121 """ 

1122 return self.contains(item) 

1123 

1124 def contains( 

1125 self, 

1126 item: UnparsedVersion, 

1127 prereleases: bool | None = None, 

1128 installed: bool | None = None, 

1129 ) -> bool: 

1130 """Return whether or not the item is contained in this SpecifierSet. 

1131 

1132 :param item: 

1133 The item to check for, which can be a version string or a 

1134 :class:`Version` instance. 

1135 :param prereleases: 

1136 Whether or not to match prereleases with this SpecifierSet. If set to 

1137 ``None`` (the default), it will follow the recommendation from :pep:`440` 

1138 and match prereleases, as there are no other versions. 

1139 :param installed: 

1140 Whether or not the item is installed. If set to ``True``, it will 

1141 accept prerelease versions even if the specifier does not allow them. 

1142 

1143 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3") 

1144 True 

1145 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3")) 

1146 True 

1147 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1") 

1148 False 

1149 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1") 

1150 True 

1151 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1") 

1152 False 

1153 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True) 

1154 True 

1155 """ 

1156 version = _coerce_version(item) 

1157 

1158 if version is not None and installed and version.is_prerelease: 

1159 prereleases = True 

1160 

1161 # When item is a string and === is involved, keep it as-is 

1162 # so the comparison isn't done against the normalized form. 

1163 if version is None or (self._has_arbitrary and not isinstance(item, Version)): 

1164 check_item = item 

1165 else: 

1166 check_item = version 

1167 return bool(list(self.filter([check_item], prereleases=prereleases))) 

1168 

1169 @typing.overload 

1170 def filter( 

1171 self, 

1172 iterable: Iterable[UnparsedVersionVar], 

1173 prereleases: bool | None = None, 

1174 key: None = ..., 

1175 ) -> Iterator[UnparsedVersionVar]: ... 

1176 

1177 @typing.overload 

1178 def filter( 

1179 self, 

1180 iterable: Iterable[T], 

1181 prereleases: bool | None = None, 

1182 key: Callable[[T], UnparsedVersion] = ..., 

1183 ) -> Iterator[T]: ... 

1184 

1185 def filter( 

1186 self, 

1187 iterable: Iterable[Any], 

1188 prereleases: bool | None = None, 

1189 key: Callable[[Any], UnparsedVersion] | None = None, 

1190 ) -> Iterator[Any]: 

1191 """Filter items in the given iterable, that match the specifiers in this set. 

1192 

1193 :param iterable: 

1194 An iterable that can contain version strings and :class:`Version` instances. 

1195 The items in the iterable will be filtered according to the specifier. 

1196 :param prereleases: 

1197 Whether or not to allow prereleases in the returned iterator. If set to 

1198 ``None`` (the default), it will follow the recommendation from :pep:`440` 

1199 and match prereleases if there are no other versions. 

1200 :param key: 

1201 A callable that takes a single argument (an item from the iterable) and 

1202 returns a version string or :class:`Version` instance to be used for 

1203 filtering. 

1204 

1205 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) 

1206 ['1.3'] 

1207 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")])) 

1208 ['1.3', <Version('1.4')>] 

1209 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"])) 

1210 ['1.5a1'] 

1211 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) 

1212 ['1.3', '1.5a1'] 

1213 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) 

1214 ['1.3', '1.5a1'] 

1215 >>> list(SpecifierSet(">=1.2.3").filter( 

1216 ... [{"ver": "1.2"}, {"ver": "1.3"}], 

1217 ... key=lambda x: x["ver"])) 

1218 [{'ver': '1.3'}] 

1219 

1220 An "empty" SpecifierSet will filter items based on the presence of prerelease 

1221 versions in the set. 

1222 

1223 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"])) 

1224 ['1.3'] 

1225 >>> list(SpecifierSet("").filter(["1.5a1"])) 

1226 ['1.5a1'] 

1227 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"])) 

1228 ['1.3', '1.5a1'] 

1229 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True)) 

1230 ['1.3', '1.5a1'] 

1231 """ 

1232 # Determine if we're forcing a prerelease or not, if we're not forcing 

1233 # one for this particular filter call, then we'll use whatever the 

1234 # SpecifierSet thinks for whether or not we should support prereleases. 

1235 if prereleases is None and self.prereleases is not None: 

1236 prereleases = self.prereleases 

1237 

1238 # Filter versions that match all specifiers using Cost Based Ordering. 

1239 if self._specs: 

1240 # When prereleases is None, we need to let all versions through 

1241 # the individual filters, then decide about prereleases at the end 

1242 # based on whether any non-prereleases matched ALL specs. 

1243 

1244 # Fast path: single specifier, delegate directly. 

1245 if len(self._specs) == 1: 

1246 filtered = self._specs[0].filter( 

1247 iterable, 

1248 prereleases=True if prereleases is None else prereleases, 

1249 key=key, 

1250 ) 

1251 else: 

1252 filtered = self._filter_versions( 

1253 iterable, 

1254 key, 

1255 prereleases=True if prereleases is None else prereleases, 

1256 ) 

1257 

1258 if prereleases is not None: 

1259 return filtered 

1260 

1261 return _pep440_filter_prereleases(filtered, key) 

1262 

1263 # Handle Empty SpecifierSet. 

1264 if prereleases is True: 

1265 return iter(iterable) 

1266 

1267 if prereleases is False: 

1268 return ( 

1269 item 

1270 for item in iterable 

1271 if ( 

1272 (version := _coerce_version(item if key is None else key(item))) 

1273 is None 

1274 or not version.is_prerelease 

1275 ) 

1276 ) 

1277 

1278 # PEP 440: exclude prereleases unless no final releases matched 

1279 return _pep440_filter_prereleases(iterable, key) 

1280 

1281 def _filter_versions( 

1282 self, 

1283 iterable: Iterable[Any], 

1284 key: Callable[[Any], UnparsedVersion] | None, 

1285 prereleases: bool | None = None, 

1286 ) -> Iterator[Any]: 

1287 """Filter versions against all specifiers in a single pass. 

1288 

1289 Uses Cost Based Ordering: specifiers are sorted by _operator_cost so 

1290 that cheap range operators reject versions early, avoiding expensive 

1291 wildcard or compatible operators on versions that would have been 

1292 rejected anyway. 

1293 """ 

1294 # Pre-resolve operators and sort (cached after first call). 

1295 if self._resolved_ops is None: 

1296 self._resolved_ops = sorted( 

1297 ( 

1298 (spec._get_operator(spec.operator), spec.version, spec.operator) 

1299 for spec in self._specs 

1300 ), 

1301 key=_operator_cost, 

1302 ) 

1303 ops = self._resolved_ops 

1304 exclude_prereleases = prereleases is False 

1305 

1306 for item in iterable: 

1307 parsed = _coerce_version(item if key is None else key(item)) 

1308 

1309 if parsed is None: 

1310 # Only === can match non-parseable versions. 

1311 if all( 

1312 op == "===" and str(item).lower() == ver.lower() 

1313 for _, ver, op in ops 

1314 ): 

1315 yield item 

1316 elif exclude_prereleases and parsed.is_prerelease: 

1317 pass 

1318 elif all( 

1319 str(item if key is None else key(item)).lower() == ver.lower() 

1320 if op == "===" 

1321 else op_fn(parsed, ver) 

1322 for op_fn, ver, op in ops 

1323 ): 

1324 # Short-circuits on the first failing operator. 

1325 yield item