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 _base_version(version: Version) -> Version: 

56 if ( 

57 version.pre is None 

58 and version.post is None 

59 and version.dev is None 

60 and version.local is None 

61 ): 

62 return version 

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

64 

65 

66class InvalidSpecifier(ValueError): 

67 """ 

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

69 string that is invalid. 

70 

71 >>> Specifier("lolwat") 

72 Traceback (most recent call last): 

73 ... 

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

75 """ 

76 

77 

78class BaseSpecifier(metaclass=abc.ABCMeta): 

79 __slots__ = () 

80 __match_args__ = ("_str",) 

81 

82 @property 

83 def _str(self) -> str: 

84 """Internal property for match_args""" 

85 return str(self) 

86 

87 @abc.abstractmethod 

88 def __str__(self) -> str: 

89 """ 

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

91 should be representative of the Specifier itself. 

92 """ 

93 

94 @abc.abstractmethod 

95 def __hash__(self) -> int: 

96 """ 

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

98 """ 

99 

100 @abc.abstractmethod 

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

102 """ 

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

104 objects are equal. 

105 

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

107 """ 

108 

109 @property 

110 @abc.abstractmethod 

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

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

113 

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

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

116 """ 

117 

118 @prereleases.setter # noqa: B027 

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

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

121 

122 :param value: The value to set. 

123 """ 

124 

125 @abc.abstractmethod 

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

127 """ 

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

129 """ 

130 

131 @typing.overload 

132 def filter( 

133 self, 

134 iterable: Iterable[UnparsedVersionVar], 

135 prereleases: bool | None = None, 

136 key: None = ..., 

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

138 

139 @typing.overload 

140 def filter( 

141 self, 

142 iterable: Iterable[T], 

143 prereleases: bool | None = None, 

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

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

146 

147 @abc.abstractmethod 

148 def filter( 

149 self, 

150 iterable: Iterable[Any], 

151 prereleases: bool | None = None, 

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

153 ) -> Iterator[Any]: 

154 """ 

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

156 are contained within this specifier are allowed in it. 

157 """ 

158 

159 

160class Specifier(BaseSpecifier): 

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

162 

163 .. tip:: 

164 

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

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

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

168 """ 

169 

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

171 

172 _specifier_regex_str = r""" 

173 (?: 

174 (?: 

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

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

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

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

179 # but included entirely as an escape hatch. 

180 === # Only match for the identity operator 

181 \s* 

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

183 # we match everything except for whitespace, a 

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

185 # since versions can be enclosed in them. 

186 ) 

187 | 

188 (?: 

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

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

191 # operators separately to enable that. 

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

193 

194 \s* 

195 v? 

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

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

198 

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

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

201 (?: 

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

203 | 

204 (?a: # pre release 

205 [-_\.]? 

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

207 [-_\.]? 

208 [0-9]* 

209 )? 

210 (?a: # post release 

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

212 )? 

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

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

215 )? 

216 ) 

217 | 

218 (?: 

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

220 # release segment. 

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

222 

223 \s* 

224 v? 

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

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

227 (?: # pre release 

228 [-_\.]? 

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

230 [-_\.]? 

231 [0-9]* 

232 )? 

233 (?: # post release 

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

235 )? 

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

237 ) 

238 | 

239 (?: 

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

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

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

243 # matching wild cards. 

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

245 

246 \s* 

247 v? 

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

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

250 (?a: # pre release 

251 [-_\.]? 

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

253 [-_\.]? 

254 [0-9]* 

255 )? 

256 (?a: # post release 

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

258 )? 

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

260 ) 

261 ) 

262 """ 

263 

264 _regex = re.compile( 

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

266 ) 

267 

268 _operators: Final = { 

269 "~=": "compatible", 

270 "==": "equal", 

271 "!=": "not_equal", 

272 "<=": "less_than_equal", 

273 ">=": "greater_than_equal", 

274 "<": "less_than", 

275 ">": "greater_than", 

276 "===": "arbitrary", 

277 } 

278 

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

280 """Initialize a Specifier instance. 

281 

282 :param spec: 

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

284 normalized before use. 

285 :param prereleases: 

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

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

288 given specifiers. 

289 :raises InvalidSpecifier: 

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

291 """ 

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

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

294 

295 spec = spec.strip() 

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

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

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

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

300 else: 

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

302 

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

304 

305 # Store whether or not this Specifier should accept prereleases 

306 self._prereleases = prereleases 

307 

308 # Specifier version cache 

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

310 

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

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

313 

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

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

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

317 return self._spec_version[1] 

318 

319 version_specifier = _coerce_version(version) 

320 if version_specifier is None: 

321 return None 

322 

323 self._spec_version = (version, version_specifier) 

324 return version_specifier 

325 

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

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

328 

329 This method should only be called for operators where version 

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

331 """ 

332 spec_version = self._get_spec_version(version) 

333 assert spec_version is not None 

334 return spec_version 

335 

336 @property 

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

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

339 # blindly use that. 

340 if self._prereleases is not None: 

341 return self._prereleases 

342 

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

344 # the version in the specifier is a prerelease. 

345 operator, version_str = self._spec 

346 if operator == "!=": 

347 return False 

348 

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

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

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

352 return False 

353 

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

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

356 version = self._get_spec_version(version_str) 

357 if version is None: 

358 return None 

359 

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

361 # object implies pre-releases. 

362 return version.is_prerelease 

363 

364 @prereleases.setter 

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

366 self._prereleases = value 

367 

368 @property 

369 def operator(self) -> str: 

370 """The operator of this specifier. 

371 

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

373 '==' 

374 """ 

375 return self._spec[0] 

376 

377 @property 

378 def version(self) -> str: 

379 """The version of this specifier. 

380 

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

382 '1.2.3' 

383 """ 

384 return self._spec[1] 

385 

386 def __repr__(self) -> str: 

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

388 

389 >>> Specifier('>=1.0.0') 

390 <Specifier('>=1.0.0')> 

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

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

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

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

395 """ 

396 pre = ( 

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

398 if self._prereleases is not None 

399 else "" 

400 ) 

401 

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

403 

404 def __str__(self) -> str: 

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

406 

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

408 '>=1.0.0' 

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

410 '>=1.0.0' 

411 """ 

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

413 

414 @property 

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

416 operator, version = self._spec 

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

418 return operator, version 

419 

420 spec_version = self._require_spec_version(version) 

421 

422 canonical_version = canonicalize_version( 

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

424 ) 

425 

426 return operator, canonical_version 

427 

428 def __hash__(self) -> int: 

429 return hash(self._canonical_spec) 

430 

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

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

433 

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

435 

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

437 

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

439 True 

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

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

442 True 

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

444 True 

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

446 False 

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

448 False 

449 """ 

450 if isinstance(other, str): 

451 try: 

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

453 except InvalidSpecifier: 

454 return NotImplemented 

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

456 return NotImplemented 

457 

458 return self._canonical_spec == other._canonical_spec 

459 

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

461 operator_callable: CallableOperator = getattr( 

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

463 ) 

464 return operator_callable 

465 

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

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

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

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

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

471 # the other specifiers. 

472 

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

474 # ignore suffix segments. 

475 prefix = _version_join( 

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

477 ) 

478 

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

480 prefix += ".*" 

481 

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

483 self._compare_equal(prospective, prefix) 

484 ) 

485 

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

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

488 

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

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

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

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

493 """ 

494 wildcard_split = self._wildcard_split 

495 if wildcard_split is None: 

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

497 split_spec = _version_split(normalized) 

498 wildcard_split = (split_spec, _numeric_prefix_len(split_spec)) 

499 self._wildcard_split = wildcard_split 

500 return wildcard_split 

501 

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

503 # We need special logic to handle prefix matching 

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

505 split_spec, spec_numeric_len = self._get_wildcard_split(spec) 

506 

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

508 normalized_prospective = canonicalize_version( 

509 _public_version(prospective), strip_trailing_zero=False 

510 ) 

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

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

513 # a pre-release segment. 

514 split_prospective = _version_split(normalized_prospective) 

515 

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

517 # shortened version. 

518 padded_prospective = _left_pad(split_prospective, spec_numeric_len) 

519 

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

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

522 # prospective version or not. 

523 shortened_prospective = padded_prospective[: len(split_spec)] 

524 

525 return shortened_prospective == split_spec 

526 else: 

527 # Convert our spec string into a Version 

528 spec_version = self._require_spec_version(spec) 

529 

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

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

532 # segment. 

533 if not spec_version.local: 

534 prospective = _public_version(prospective) 

535 

536 return prospective == spec_version 

537 

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

539 return not self._compare_equal(prospective, spec) 

540 

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

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

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

544 # the prospective version. 

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

546 

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

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

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

550 # the prospective version. 

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

552 

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

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

555 # it as a version. 

556 spec = self._require_spec_version(spec_str) 

557 

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

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

560 # instead of doing extra unneeded work. 

561 if not prospective < spec: 

562 return False 

563 

564 # This special case is here so that, unless the specifier itself 

565 # includes is a pre-release version, that we do not accept pre-release 

566 # versions for the version mentioned in the specifier (e.g. <3.1 should 

567 # not match 3.1.dev0, but should match 3.0.dev0). 

568 if ( 

569 not spec.is_prerelease 

570 and prospective.is_prerelease 

571 and _base_version(prospective) == _base_version(spec) 

572 ): 

573 return False 

574 

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

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

577 # version in the spec. 

578 return True 

579 

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

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

582 # it as a version. 

583 spec = self._require_spec_version(spec_str) 

584 

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

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

587 # instead of doing extra unneeded work. 

588 if not prospective > spec: 

589 return False 

590 

591 # This special case is here so that, unless the specifier itself 

592 # includes is a post-release version, that we do not accept 

593 # post-release versions for the version mentioned in the specifier 

594 # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). 

595 if ( 

596 not spec.is_postrelease 

597 and prospective.is_postrelease 

598 and _base_version(prospective) == _base_version(spec) 

599 ): 

600 return False 

601 

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

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

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

605 # match 1.0a2+local. 

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

607 return False 

608 

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

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

611 # same version in the spec. 

612 return True 

613 

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

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

616 

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

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

619 

620 :param item: The item to check for. 

621 

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

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

624 

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

626 True 

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

628 True 

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

630 False 

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

632 True 

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

634 True 

635 """ 

636 return self.contains(item) 

637 

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

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

640 

641 :param item: 

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

643 :class:`Version` instance. 

644 :param prereleases: 

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

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

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

648 

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

650 True 

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

652 True 

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

654 False 

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

656 True 

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

658 False 

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

660 True 

661 """ 

662 

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

664 

665 @typing.overload 

666 def filter( 

667 self, 

668 iterable: Iterable[UnparsedVersionVar], 

669 prereleases: bool | None = None, 

670 key: None = ..., 

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

672 

673 @typing.overload 

674 def filter( 

675 self, 

676 iterable: Iterable[T], 

677 prereleases: bool | None = None, 

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

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

680 

681 def filter( 

682 self, 

683 iterable: Iterable[Any], 

684 prereleases: bool | None = None, 

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

686 ) -> Iterator[Any]: 

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

688 

689 :param iterable: 

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

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

692 :param prereleases: 

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

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

695 and match prereleases if there are no other versions. 

696 :param key: 

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

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

699 filtering. 

700 

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

702 ['1.3'] 

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

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

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

706 ['1.5a1'] 

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

708 ['1.3', '1.5a1'] 

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

710 ['1.3', '1.5a1'] 

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

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

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

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

715 """ 

716 prereleases_versions = [] 

717 found_non_prereleases = False 

718 

719 # Determine if to include prereleases by default 

720 include_prereleases = ( 

721 prereleases if prereleases is not None else self.prereleases 

722 ) 

723 

724 # Get the matching operator 

725 operator_callable = self._get_operator(self.operator) 

726 

727 # Filter versions 

728 for version in iterable: 

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

730 match = False 

731 if parsed_version is None: 

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

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

734 version, self.version 

735 ): 

736 yield version 

737 elif self.operator == "===": 

738 match = self._compare_arbitrary( 

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

740 ) 

741 else: 

742 match = operator_callable(parsed_version, self.version) 

743 

744 if match and parsed_version is not None: 

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

746 if not parsed_version.is_prerelease or include_prereleases: 

747 found_non_prereleases = True 

748 yield version 

749 # Otherwise collect prereleases for potential later use 

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

751 prereleases_versions.append(version) 

752 

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

754 # explicitly forbidden, yield the collected prereleases 

755 if ( 

756 not found_non_prereleases 

757 and prereleases is None 

758 and self._prereleases is not False 

759 ): 

760 yield from prereleases_versions 

761 

762 

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

764 

765 

766def _pep440_filter_prereleases( 

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

768) -> Iterator[Any]: 

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

770 # Two lists used: 

771 # * all_nonfinal to preserve order if no finals exist 

772 # * arbitrary_strings for streaming when first final found 

773 all_nonfinal: list[Any] = [] 

774 arbitrary_strings: list[Any] = [] 

775 

776 found_final = False 

777 for item in iterable: 

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

779 

780 if parsed is None: 

781 # Arbitrary strings are always included as it is not 

782 # possible to determine if they are prereleases, 

783 # and they have already passed all specifiers. 

784 if found_final: 

785 yield item 

786 else: 

787 arbitrary_strings.append(item) 

788 all_nonfinal.append(item) 

789 continue 

790 

791 if not parsed.is_prerelease: 

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

793 if not found_final: 

794 yield from arbitrary_strings 

795 found_final = True 

796 yield item 

797 continue 

798 

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

800 if not found_final: 

801 all_nonfinal.append(item) 

802 

803 # No finals found - yield all buffered items 

804 if not found_final: 

805 yield from all_nonfinal 

806 

807 

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

809 """Split version into components. 

810 

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

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

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

814 version string. 

815 """ 

816 result: list[str] = [] 

817 

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

819 result.append(epoch or "0") 

820 

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

822 match = _prefix_regex.fullmatch(item) 

823 if match: 

824 result.extend(match.groups()) 

825 else: 

826 result.append(item) 

827 return result 

828 

829 

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

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

832 

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

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

835 components numeric. 

836 """ 

837 epoch, *rest = components 

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

839 

840 

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

842 return not any( 

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

844 ) 

845 

846 

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

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

849 

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

851 3 

852 """ 

853 count = 0 

854 for segment in split: 

855 if not segment.isdigit(): 

856 break 

857 count += 1 

858 return count 

859 

860 

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

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

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

864 

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

866 ['0', '1', '0', '0', 'a1'] 

867 """ 

868 numeric_len = _numeric_prefix_len(split) 

869 pad_needed = target_numeric_len - numeric_len 

870 if pad_needed <= 0: 

871 return split 

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

873 

874 

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

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

877 

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

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

880 

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

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

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

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

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

886 """ 

887 _, ver, op = op_entry 

888 if op == "==": 

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

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

891 return 1 

892 if op == "~=": 

893 return 2 

894 if op == "!=": 

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

896 if op == "===": 

897 return 0 

898 

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

900 

901 

902class SpecifierSet(BaseSpecifier): 

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

904 

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

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

907 """ 

908 

909 __slots__ = ( 

910 "_canonicalized", 

911 "_has_arbitrary", 

912 "_prereleases", 

913 "_resolved_ops", 

914 "_specs", 

915 ) 

916 

917 def __init__( 

918 self, 

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

920 prereleases: bool | None = None, 

921 ) -> None: 

922 """Initialize a SpecifierSet instance. 

923 

924 :param specifiers: 

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

926 specifiers which will be parsed and normalized before use. 

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

928 as is. 

929 :param prereleases: 

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

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

932 given specifiers. 

933 

934 :raises InvalidSpecifier: 

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

936 raised. 

937 """ 

938 

939 if isinstance(specifiers, str): 

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

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

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

943 

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

945 # Fast substring check; avoids iterating parsed specs. 

946 self._has_arbitrary = "===" in specifiers 

947 else: 

948 self._specs = tuple(specifiers) 

949 # Substring check works for both Specifier objects and plain 

950 # strings (setuptools passes lists of strings). 

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

952 

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

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

955 

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

957 # we accept prereleases or not. 

958 self._prereleases = prereleases 

959 

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

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

962 if not self._canonicalized: 

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

964 self._canonicalized = True 

965 self._resolved_ops = None 

966 return self._specs 

967 

968 @property 

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

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

971 # pass that through here. 

972 if self._prereleases is not None: 

973 return self._prereleases 

974 

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

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

977 # pre-releases or not. 

978 if not self._specs: 

979 return None 

980 

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

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

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

984 return True 

985 

986 return None 

987 

988 @prereleases.setter 

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

990 self._prereleases = value 

991 

992 def __repr__(self) -> str: 

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

994 

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

996 match the input string. 

997 

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

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

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

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

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

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

1004 """ 

1005 pre = ( 

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

1007 if self._prereleases is not None 

1008 else "" 

1009 ) 

1010 

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

1012 

1013 def __str__(self) -> str: 

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

1015 

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

1017 match the input string. 

1018 

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

1020 '!=1.0.1,>=1.0.0' 

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

1022 '!=1.0.1,>=1.0.0' 

1023 """ 

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

1025 

1026 def __hash__(self) -> int: 

1027 return hash(self._canonical_specs()) 

1028 

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

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

1031 

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

1033 

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

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

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

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

1038 """ 

1039 if isinstance(other, str): 

1040 other = SpecifierSet(other) 

1041 elif not isinstance(other, SpecifierSet): 

1042 return NotImplemented 

1043 

1044 specifier = SpecifierSet() 

1045 specifier._specs = self._specs + other._specs 

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

1047 specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary 

1048 specifier._resolved_ops = None 

1049 

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

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

1052 specifier._prereleases = other._prereleases 

1053 elif other._prereleases is None: 

1054 specifier._prereleases = self._prereleases 

1055 else: 

1056 raise ValueError( 

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

1058 ) 

1059 

1060 return specifier 

1061 

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

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

1064 

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

1066 

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

1068 

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

1070 True 

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

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

1073 True 

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

1075 True 

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

1077 False 

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

1079 False 

1080 """ 

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

1082 other = SpecifierSet(str(other)) 

1083 elif not isinstance(other, SpecifierSet): 

1084 return NotImplemented 

1085 

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

1087 

1088 def __len__(self) -> int: 

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

1090 return len(self._specs) 

1091 

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

1093 """ 

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

1095 in this specifier set. 

1096 

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

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

1099 """ 

1100 return iter(self._specs) 

1101 

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

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

1104 

1105 :param item: The item to check for. 

1106 

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

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

1109 

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

1111 True 

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

1113 True 

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

1115 False 

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

1117 True 

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

1119 True 

1120 """ 

1121 return self.contains(item) 

1122 

1123 def contains( 

1124 self, 

1125 item: UnparsedVersion, 

1126 prereleases: bool | None = None, 

1127 installed: bool | None = None, 

1128 ) -> bool: 

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

1130 

1131 :param item: 

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

1133 :class:`Version` instance. 

1134 :param prereleases: 

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

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

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

1138 :param installed: 

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

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

1141 

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

1143 True 

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

1145 True 

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

1147 False 

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

1149 True 

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

1151 False 

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

1153 True 

1154 """ 

1155 version = _coerce_version(item) 

1156 

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

1158 prereleases = True 

1159 

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

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

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

1163 check_item = item 

1164 else: 

1165 check_item = version 

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

1167 

1168 @typing.overload 

1169 def filter( 

1170 self, 

1171 iterable: Iterable[UnparsedVersionVar], 

1172 prereleases: bool | None = None, 

1173 key: None = ..., 

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

1175 

1176 @typing.overload 

1177 def filter( 

1178 self, 

1179 iterable: Iterable[T], 

1180 prereleases: bool | None = None, 

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

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

1183 

1184 def filter( 

1185 self, 

1186 iterable: Iterable[Any], 

1187 prereleases: bool | None = None, 

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

1189 ) -> Iterator[Any]: 

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

1191 

1192 :param iterable: 

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

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

1195 :param prereleases: 

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

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

1198 and match prereleases if there are no other versions. 

1199 :param key: 

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

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

1202 filtering. 

1203 

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

1205 ['1.3'] 

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

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

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

1209 ['1.5a1'] 

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

1211 ['1.3', '1.5a1'] 

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

1213 ['1.3', '1.5a1'] 

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

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

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

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

1218 

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

1220 versions in the set. 

1221 

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

1223 ['1.3'] 

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

1225 ['1.5a1'] 

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

1227 ['1.3', '1.5a1'] 

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

1229 ['1.3', '1.5a1'] 

1230 """ 

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

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

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

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

1235 prereleases = self.prereleases 

1236 

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

1238 if self._specs: 

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

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

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

1242 

1243 # Fast path: single specifier, delegate directly. 

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

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

1246 iterable, 

1247 prereleases=True if prereleases is None else prereleases, 

1248 key=key, 

1249 ) 

1250 else: 

1251 filtered = self._filter_versions( 

1252 iterable, 

1253 key, 

1254 prereleases=True if prereleases is None else prereleases, 

1255 ) 

1256 

1257 if prereleases is not None: 

1258 return filtered 

1259 

1260 return _pep440_filter_prereleases(filtered, key) 

1261 

1262 # Handle Empty SpecifierSet. 

1263 if prereleases is True: 

1264 return iter(iterable) 

1265 

1266 if prereleases is False: 

1267 return ( 

1268 item 

1269 for item in iterable 

1270 if ( 

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

1272 is None 

1273 or not version.is_prerelease 

1274 ) 

1275 ) 

1276 

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

1278 return _pep440_filter_prereleases(iterable, key) 

1279 

1280 def _filter_versions( 

1281 self, 

1282 iterable: Iterable[Any], 

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

1284 prereleases: bool | None = None, 

1285 ) -> Iterator[Any]: 

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

1287 

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

1289 that cheap range operators reject versions early, avoiding expensive 

1290 wildcard or compatible operators on versions that would have been 

1291 rejected anyway. 

1292 """ 

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

1294 if self._resolved_ops is None: 

1295 self._resolved_ops = sorted( 

1296 ( 

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

1298 for spec in self._specs 

1299 ), 

1300 key=_operator_cost, 

1301 ) 

1302 ops = self._resolved_ops 

1303 exclude_prereleases = prereleases is False 

1304 

1305 for item in iterable: 

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

1307 

1308 if parsed is None: 

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

1310 if all( 

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

1312 for _, ver, op in ops 

1313 ): 

1314 yield item 

1315 elif exclude_prereleases and parsed.is_prerelease: 

1316 pass 

1317 elif all( 

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

1319 if op == "===" 

1320 else op_fn(parsed, ver) 

1321 for op_fn, ver, op in ops 

1322 ): 

1323 # Short-circuits on the first failing operator. 

1324 yield item