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

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

419 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 re 

15import typing 

16from typing import ( 

17 TYPE_CHECKING, 

18 Any, 

19 Callable, 

20 Final, 

21 TypeVar, 

22 Union, 

23) 

24 

25from ._ranges import ( 

26 FULL_RANGE, 

27 bounds_for_spec, 

28 coerce_version, 

29 filter_by_ranges, 

30 intersect_specifier_bounds, 

31 matches_bounds_only, 

32 ranges_are_prerelease_only, 

33 resolve_prereleases, 

34 trim_release, 

35) 

36from .utils import canonicalize_version 

37from .version import Version 

38 

39if TYPE_CHECKING: 

40 import sys 

41 from collections.abc import Iterable, Iterator, Sequence 

42 

43 if sys.version_info >= (3, 10): 

44 from typing import TypeGuard 

45 else: 

46 from typing_extensions import TypeGuard 

47 

48 from . import ranges 

49 from ._ranges import VersionRange 

50 

51 

52__all__ = [ 

53 "BaseSpecifier", 

54 "InvalidSpecifier", 

55 "Specifier", 

56 "SpecifierSet", 

57] 

58 

59 

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

61 return __all__ 

62 

63 

64def _validate_spec(spec: object, /) -> TypeGuard[tuple[str, str]]: 

65 return ( 

66 isinstance(spec, tuple) 

67 and len(spec) == 2 

68 and isinstance(spec[0], str) 

69 and isinstance(spec[1], str) 

70 ) 

71 

72 

73def _validate_pre(pre: object, /) -> TypeGuard[bool | None]: 

74 return pre is None or isinstance(pre, bool) 

75 

76 

77T = TypeVar("T") 

78UnparsedVersion = Union[Version, str] 

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

80 

81 

82# Operators whose result is just a direct Version comparison, given a parsed 

83# item with no local. ``<=``/``==``/``!=`` need that no-local guard because 

84# PEP 440 strips locals on those; ``>=`` works regardless. 

85_DIRECT_COMPARE_OPS: dict[str, Callable[[Version, Version], bool]] = { 

86 ">=": Version.__ge__, 

87 "<=": Version.__le__, 

88 "==": Version.__eq__, 

89 "!=": Version.__ne__, 

90} 

91 

92 

93def _fast_match(specifier: Specifier, parsed: Version) -> bool | None: 

94 """Match ``parsed`` against ``specifier`` without building a range. 

95 

96 Handles ``>=``, ``<=``, ``==``, ``!=``, ``<``, ``>`` when the spec is 

97 not a wildcard and ``parsed`` has no local. Returns ``None`` when the 

98 range path must be used. Pre-release policy is left to the caller. 

99 """ 

100 op_str, ver_str = specifier._spec 

101 if ver_str.endswith(".*") or parsed.local is not None: 

102 return None 

103 

104 direct_compare = _DIRECT_COMPARE_OPS.get(op_str) 

105 if direct_compare is not None: 

106 return direct_compare(parsed, specifier._require_spec_version(ver_str)) 

107 

108 if op_str in ("<", ">"): 

109 spec_v = specifier._require_spec_version(ver_str) 

110 # ``<V``/``>V`` carve out V's family (pre/dev/post); that only 

111 # matters when parsed shares V's epoch and trimmed release. 

112 # Otherwise a direct cmpkey comparison is correct. 

113 if parsed.epoch != spec_v.epoch or trim_release(parsed.release) != trim_release( 

114 spec_v.release 

115 ): 

116 return parsed < spec_v if op_str == "<" else parsed > spec_v 

117 return None 

118 

119 return None 

120 

121 

122class InvalidSpecifier(ValueError): 

123 """ 

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

125 string that is invalid. 

126 

127 >>> Specifier("lolwat") 

128 Traceback (most recent call last): 

129 ... 

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

131 """ 

132 

133 

134class BaseSpecifier(metaclass=abc.ABCMeta): 

135 """ 

136 Abstract base class for :class:`Specifier` and :class:`SpecifierSet`. 

137 """ 

138 

139 __slots__ = () 

140 __match_args__ = ("_str",) 

141 

142 @property 

143 def _str(self) -> str: 

144 """Internal property for match_args""" 

145 return str(self) 

146 

147 @abc.abstractmethod 

148 def __str__(self) -> str: 

149 """ 

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

151 should be representative of the Specifier itself. 

152 """ 

153 

154 @abc.abstractmethod 

155 def __hash__(self) -> int: 

156 """ 

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

158 """ 

159 

160 @abc.abstractmethod 

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

162 """ 

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

164 objects are equal. 

165 

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

167 """ 

168 

169 @property 

170 @abc.abstractmethod 

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

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

173 

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

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

176 """ 

177 

178 @prereleases.setter # noqa: B027 

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

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

181 

182 :param value: The value to set. 

183 """ 

184 

185 @abc.abstractmethod 

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

187 """ 

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

189 """ 

190 

191 @typing.overload 

192 def filter( 

193 self, 

194 iterable: Iterable[UnparsedVersionVar], 

195 prereleases: bool | None = None, 

196 key: None = ..., 

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

198 

199 @typing.overload 

200 def filter( 

201 self, 

202 iterable: Iterable[T], 

203 prereleases: bool | None = None, 

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

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

206 

207 @abc.abstractmethod 

208 def filter( 

209 self, 

210 iterable: Iterable[Any], 

211 prereleases: bool | None = None, 

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

213 ) -> Iterator[Any]: 

214 """ 

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

216 are contained within this specifier are allowed in it. 

217 """ 

218 

219 

220class Specifier(BaseSpecifier): 

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

222 

223 .. tip:: 

224 

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

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

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

228 

229 Instances are safe to serialize with :mod:`pickle`. They use a stable 

230 format so the same pickle can be loaded in future packaging releases. 

231 

232 .. versionchanged:: 26.2 

233 

234 Added a stable pickle format. Pickles created with packaging 26.2+ can 

235 be unpickled with future releases. Backward compatibility with pickles 

236 from packaging < 26.2 is supported but may be removed in a future 

237 release. 

238 """ 

239 

240 __slots__ = ( 

241 "_prereleases", 

242 "_ranges", 

243 "_spec", 

244 "_spec_version", 

245 ) 

246 

247 _specifier_regex_str = r""" 

248 (?: 

249 (?: 

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

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

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

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

254 # but included entirely as an escape hatch. 

255 === # Only match for the identity operator 

256 \s* 

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

258 # we match everything except for whitespace, a 

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

260 # since versions can be enclosed in them. 

261 ) 

262 | 

263 (?: 

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

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

266 # operators separately to enable that. 

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

268 

269 \s* 

270 v? 

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

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

273 

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

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

276 (?: 

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

278 | 

279 (?a: # pre release 

280 [-_\.]? 

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

282 [-_\.]? 

283 [0-9]* 

284 )? 

285 (?a: # post release 

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

287 )? 

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

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

290 )? 

291 ) 

292 | 

293 (?: 

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

295 # release segment. 

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

297 

298 \s* 

299 v? 

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

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

302 (?: # pre release 

303 [-_\.]? 

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

305 [-_\.]? 

306 [0-9]* 

307 )? 

308 (?: # post release 

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

310 )? 

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

312 ) 

313 | 

314 (?: 

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

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

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

318 # matching wild cards. 

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

320 

321 \s* 

322 v? 

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

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

325 (?a: # pre release 

326 [-_\.]? 

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

328 [-_\.]? 

329 [0-9]* 

330 )? 

331 (?a: # post release 

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

333 )? 

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

335 ) 

336 ) 

337 """ 

338 

339 _regex = re.compile( 

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

341 ) 

342 

343 # Legacy unused attribute, kept for backward compatibility 

344 _operators: Final = { 

345 "~=": "compatible", 

346 "==": "equal", 

347 "!=": "not_equal", 

348 "<=": "less_than_equal", 

349 ">=": "greater_than_equal", 

350 "<": "less_than", 

351 ">": "greater_than", 

352 "===": "arbitrary", 

353 } 

354 

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

356 """Initialize a Specifier instance. 

357 

358 :param spec: 

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

360 normalized before use. 

361 :param prereleases: 

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

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

364 given specifiers. 

365 :raises InvalidSpecifier: 

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

367 """ 

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

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

370 

371 spec = spec.strip() 

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

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

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

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

376 else: 

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

378 

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

380 

381 # Store whether or not this Specifier should accept prereleases 

382 self._prereleases = prereleases 

383 

384 # Specifier version cache 

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

386 

387 # Version range cache (populated by _to_ranges) 

388 self._ranges: Sequence[VersionRange] | None = None 

389 

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

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

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

393 return self._spec_version[1] 

394 

395 version_specifier = coerce_version(version) 

396 if version_specifier is None: 

397 return None 

398 

399 self._spec_version = (version, version_specifier) 

400 return version_specifier 

401 

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

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

404 

405 This method should only be called for operators where version 

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

407 """ 

408 spec_version = self._get_spec_version(version) 

409 assert spec_version is not None 

410 return spec_version 

411 

412 def _to_ranges(self) -> Sequence[VersionRange]: 

413 """Convert this specifier to sorted, non-overlapping version ranges. 

414 

415 Each standard operator maps to one or two ranges. ``===`` is 

416 modeled as full range (actual check done separately). Cached. 

417 """ 

418 if self._ranges is not None: 

419 return self._ranges 

420 

421 op = self.operator 

422 ver_str = self.version 

423 

424 if op == "===": 

425 result: Sequence[VersionRange] = FULL_RANGE 

426 else: 

427 version = self._require_spec_version(ver_str.removesuffix(".*")) 

428 result = bounds_for_spec(op, ver_str, version) 

429 

430 self._ranges = result 

431 return result 

432 

433 @property 

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

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

436 # blindly use that. 

437 if self._prereleases is not None: 

438 return self._prereleases 

439 

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

441 # the version in the specifier is a prerelease. 

442 operator, version_str = self._spec 

443 if operator == "!=": 

444 return False 

445 

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

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

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

449 return False 

450 

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

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

453 version = self._get_spec_version(version_str) 

454 if version is None: 

455 return None 

456 

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

458 # object implies pre-releases. 

459 return version.is_prerelease 

460 

461 @prereleases.setter 

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

463 self._prereleases = value 

464 

465 def __getstate__(self) -> tuple[tuple[str, str], bool | None]: 

466 # Return state as a 2-item tuple for compactness: 

467 # ((operator, version), prereleases) 

468 # Cache members are excluded and will be recomputed on demand. 

469 return (self._spec, self._prereleases) 

470 

471 def __setstate__(self, state: object) -> None: 

472 # Always discard cached values - they will be recomputed on demand. 

473 self._spec_version = None 

474 self._ranges = None 

475 

476 if isinstance(state, tuple): 

477 if len(state) == 2: 

478 # New format (26.2+): ((operator, version), prereleases) 

479 spec, prereleases = state 

480 if _validate_spec(spec) and _validate_pre(prereleases): 

481 self._spec = spec 

482 self._prereleases = prereleases 

483 return 

484 if len(state) == 2 and isinstance(state[1], dict): 

485 # Format (packaging 26.0-26.1): (None, {slot: value}). 

486 _, slot_dict = state 

487 spec = slot_dict.get("_spec") 

488 prereleases = slot_dict.get("_prereleases", "invalid") 

489 if _validate_spec(spec) and _validate_pre(prereleases): 

490 self._spec = spec 

491 self._prereleases = prereleases 

492 return 

493 if isinstance(state, dict): 

494 # Old format (packaging <= 25.x, no __slots__): state is a plain dict. 

495 spec = state.get("_spec") 

496 prereleases = state.get("_prereleases", "invalid") 

497 if _validate_spec(spec) and _validate_pre(prereleases): 

498 self._spec = spec 

499 self._prereleases = prereleases 

500 return 

501 

502 raise TypeError(f"Cannot restore Specifier from {state!r}") 

503 

504 @property 

505 def operator(self) -> str: 

506 """The operator of this specifier. 

507 

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

509 '==' 

510 """ 

511 return self._spec[0] 

512 

513 @property 

514 def version(self) -> str: 

515 """The version of this specifier. 

516 

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

518 '1.2.3' 

519 """ 

520 return self._spec[1] 

521 

522 def __repr__(self) -> str: 

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

524 

525 >>> Specifier('>=1.0.0') 

526 <Specifier('>=1.0.0')> 

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

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

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

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

531 """ 

532 pre = ( 

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

534 if self._prereleases is not None 

535 else "" 

536 ) 

537 

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

539 

540 def __str__(self) -> str: 

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

542 

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

544 '>=1.0.0' 

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

546 '>=1.0.0' 

547 """ 

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

549 

550 @property 

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

552 operator, version = self._spec 

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

554 return operator, version 

555 

556 spec_version = self._require_spec_version(version) 

557 

558 canonical_version = canonicalize_version( 

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

560 ) 

561 

562 return operator, canonical_version 

563 

564 def __hash__(self) -> int: 

565 return hash(self._canonical_spec) 

566 

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

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

569 

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

571 

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

573 

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

575 True 

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

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

578 True 

579 >>> Specifier("==1.2.3") == "==1.2.3" 

580 True 

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

582 False 

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

584 False 

585 """ 

586 if isinstance(other, str): 

587 try: 

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

589 except InvalidSpecifier: 

590 return NotImplemented 

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

592 return NotImplemented 

593 

594 return self._canonical_spec == other._canonical_spec 

595 

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

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

598 

599 :param item: The item to check for. 

600 

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

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

603 

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

605 True 

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

607 True 

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

609 False 

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

611 True 

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

613 True 

614 """ 

615 return self.contains(item) 

616 

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

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

619 

620 :param item: 

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

622 :class:`~packaging.version.Version` instance. 

623 :param prereleases: 

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

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

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

627 

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

629 True 

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

631 True 

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

633 False 

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

635 True 

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

637 False 

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

639 True 

640 """ 

641 # ``===`` compares the raw string, so a Version parse here would 

642 # be wasted. 

643 if self._spec[0] == "===": 

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

645 

646 parsed = coerce_version(item) 

647 if parsed is None: 

648 # Standard operators never match an unparsable input. 

649 return False 

650 

651 if prereleases is None: 

652 prereleases = resolve_prereleases(self._prereleases, self.prereleases) 

653 

654 if prereleases is False and parsed.is_prerelease: 

655 return False 

656 

657 # ``_fast_match`` answers the simple operators without building a 

658 # range; otherwise fall back to the engine's bounds membership. 

659 match = _fast_match(self, parsed) 

660 if match is not None: 

661 return match 

662 

663 return matches_bounds_only(self._to_ranges(), parsed) 

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 

691 :class:`~packaging.version.Version` instances. The items in the 

692 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:`~packaging.version.Version` 

700 instance to be used for 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 .. versionchanged:: 26.1 

718 

719 Added the ``key`` parameter. 

720 """ 

721 if prereleases is None: 

722 prereleases = resolve_prereleases(self._prereleases, self.prereleases) 

723 

724 if self.operator == "===": 

725 spec_lower = self.version.lower() 

726 matches = ( 

727 item 

728 for item in iterable 

729 if str(item if key is None else key(item)).lower() == spec_lower 

730 ) 

731 return _apply_prereleases_filter(matches, key, prereleases) 

732 

733 return filter_by_ranges(self._to_ranges(), iterable, key, prereleases) 

734 

735 

736def _apply_prereleases_filter( 

737 matches: Iterable[Any], 

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

739 prereleases: bool | None, 

740) -> Iterator[Any]: 

741 """Apply ``prereleases=`` handling to an already-matched iterable. 

742 

743 ``None`` means PEP 440 default (buffer pre-releases until a final 

744 appears); ``True`` yields everything; ``False`` drops pre-releases. 

745 """ 

746 if prereleases is None: 

747 return _pep440_filter_prereleases(matches, key) 

748 if prereleases: 

749 return iter(matches) 

750 return ( 

751 item 

752 for item in matches 

753 if (parsed := coerce_version(item if key is None else key(item))) is None 

754 or not parsed.is_prerelease 

755 ) 

756 

757 

758class SpecifierSet(BaseSpecifier): 

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

760 

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

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

763 

764 Instances are safe to serialize with :mod:`pickle`. They use a stable 

765 format so the same pickle can be loaded in future packaging 

766 releases. 

767 

768 .. versionchanged:: 26.2 

769 

770 Added a stable pickle format. Pickles created with 

771 packaging 26.2+ can be unpickled with future releases. 

772 Backward compatibility with pickles from 

773 packaging < 26.2 is supported but may be removed in a future 

774 release. 

775 """ 

776 

777 __slots__ = ( 

778 "_canonicalized", 

779 "_has_arbitrary", 

780 "_is_unsatisfiable", 

781 "_prereleases", 

782 "_ranges", 

783 "_specs", 

784 ) 

785 

786 def __init__( 

787 self, 

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

789 prereleases: bool | None = None, 

790 ) -> None: 

791 """Initialize a SpecifierSet instance. 

792 

793 :param specifiers: 

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

795 specifiers which will be parsed and normalized before use. 

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

797 as is. 

798 :param prereleases: 

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

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

801 given specifiers. 

802 

803 :raises InvalidSpecifier: 

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

805 raised. 

806 """ 

807 

808 if isinstance(specifiers, str): 

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

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

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

812 

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

814 # Fast substring check; avoids iterating parsed specs. 

815 self._has_arbitrary = "===" in specifiers 

816 else: 

817 self._specs = tuple(specifiers) 

818 # Substring check works for both Specifier objects and plain 

819 # strings (setuptools passes lists of strings). 

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

821 

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

823 self._is_unsatisfiable: bool | None = None 

824 self._ranges: Sequence[VersionRange] | None = None 

825 

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

827 # we accept prereleases or not. 

828 self._prereleases = prereleases 

829 

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

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

832 if not self._canonicalized: 

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

834 self._canonicalized = True 

835 return self._specs 

836 

837 @property 

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

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

840 # pass that through here. 

841 if self._prereleases is not None: 

842 return self._prereleases 

843 

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

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

846 # pre-releases or not. 

847 if not self._specs: 

848 return None 

849 

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

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

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

853 return True 

854 

855 return None 

856 

857 @prereleases.setter 

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

859 self._prereleases = value 

860 self._is_unsatisfiable = None 

861 

862 def __getstate__(self) -> tuple[tuple[Specifier, ...], bool | None]: 

863 # Return state as a 2-item tuple for compactness: 

864 # (specs, prereleases) 

865 # Cache members are excluded and will be recomputed on demand. 

866 return (self._specs, self._prereleases) 

867 

868 def __setstate__(self, state: object) -> None: 

869 # Always discard cached values - they will be recomputed on demand. 

870 self._ranges = None 

871 self._is_unsatisfiable = None 

872 

873 if isinstance(state, tuple): 

874 if len(state) == 2: 

875 # New format (26.2+): (specs, prereleases) 

876 specs, prereleases = state 

877 if ( 

878 isinstance(specs, tuple) 

879 and all(isinstance(s, Specifier) for s in specs) 

880 and _validate_pre(prereleases) 

881 ): 

882 self._specs = specs 

883 self._prereleases = prereleases 

884 self._canonicalized = len(specs) <= 1 

885 self._has_arbitrary = any("===" in str(s) for s in specs) 

886 return 

887 if len(state) == 2 and isinstance(state[1], dict): 

888 # Format (packaging 26.0-26.1): (None, {slot: value}). 

889 _, slot_dict = state 

890 specs = slot_dict.get("_specs", ()) 

891 prereleases = slot_dict.get("_prereleases") 

892 # Convert frozenset to tuple (26.0 stored as frozenset) 

893 if isinstance(specs, frozenset): 

894 specs = tuple(sorted(specs, key=str)) 

895 if ( 

896 isinstance(specs, tuple) 

897 and all(isinstance(s, Specifier) for s in specs) 

898 and _validate_pre(prereleases) 

899 ): 

900 self._specs = specs 

901 self._prereleases = prereleases 

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

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

904 return 

905 if isinstance(state, dict): 

906 # Old format (packaging <= 25.x, no __slots__): state is a plain dict. 

907 specs = state.get("_specs", ()) 

908 prereleases = state.get("_prereleases") 

909 # Convert frozenset to tuple (26.0 stored as frozenset) 

910 if isinstance(specs, frozenset): 

911 specs = tuple(sorted(specs, key=str)) 

912 if ( 

913 isinstance(specs, tuple) 

914 and all(isinstance(s, Specifier) for s in specs) 

915 and _validate_pre(prereleases) 

916 ): 

917 self._specs = specs 

918 self._prereleases = prereleases 

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

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

921 return 

922 

923 raise TypeError(f"Cannot restore SpecifierSet from {state!r}") 

924 

925 def __repr__(self) -> str: 

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

927 

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

929 match the input string. 

930 

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

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

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

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

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

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

937 """ 

938 pre = ( 

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

940 if self._prereleases is not None 

941 else "" 

942 ) 

943 

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

945 

946 def __str__(self) -> str: 

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

948 

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

950 match the input string. 

951 

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

953 '!=1.0.1,>=1.0.0' 

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

955 '!=1.0.1,>=1.0.0' 

956 """ 

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

958 

959 def __hash__(self) -> int: 

960 return hash(self._canonical_specs()) 

961 

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

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

964 

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

966 

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

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

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

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

971 """ 

972 if isinstance(other, str): 

973 other = SpecifierSet(other) 

974 elif not isinstance(other, SpecifierSet): 

975 return NotImplemented 

976 

977 specifier = SpecifierSet() 

978 specifier._specs = self._specs + other._specs 

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

980 specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary 

981 

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

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

984 specifier._prereleases = other._prereleases 

985 elif other._prereleases is None: 

986 specifier._prereleases = self._prereleases 

987 else: 

988 raise ValueError( 

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

990 ) 

991 

992 return specifier 

993 

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

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

996 

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

998 

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

1000 

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

1002 True 

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

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

1005 True 

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

1007 True 

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

1009 False 

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

1011 False 

1012 """ 

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

1014 other = SpecifierSet(str(other)) 

1015 elif not isinstance(other, SpecifierSet): 

1016 return NotImplemented 

1017 

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

1019 

1020 def __len__(self) -> int: 

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

1022 return len(self._specs) 

1023 

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

1025 """ 

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

1027 in this specifier set. 

1028 

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

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

1031 """ 

1032 return iter(self._specs) 

1033 

1034 def _get_ranges(self) -> Sequence[VersionRange]: 

1035 """Intersect all specifiers into a single sequence of version ranges. 

1036 

1037 Empty when unsatisfiable. Callers must ensure ``self._specs`` 

1038 is non-empty. 

1039 """ 

1040 if self._ranges is not None: 

1041 return self._ranges 

1042 

1043 self._ranges = intersect_specifier_bounds(s._to_ranges() for s in self._specs) 

1044 return self._ranges 

1045 

1046 def is_unsatisfiable(self) -> bool: 

1047 """Check whether this specifier set can never be satisfied. 

1048 

1049 Returns True if no version can satisfy all specifiers simultaneously. 

1050 

1051 >>> SpecifierSet(">=2.0,<1.0").is_unsatisfiable() 

1052 True 

1053 >>> SpecifierSet(">=1.0,<2.0").is_unsatisfiable() 

1054 False 

1055 >>> SpecifierSet("").is_unsatisfiable() 

1056 False 

1057 >>> SpecifierSet("==1.0,!=1.0").is_unsatisfiable() 

1058 True 

1059 

1060 .. versionadded:: 26.1 

1061 """ 

1062 cached = self._is_unsatisfiable 

1063 if cached is not None: 

1064 return cached 

1065 

1066 if not self._specs: 

1067 self._is_unsatisfiable = False 

1068 return False 

1069 

1070 result = not self._get_ranges() 

1071 

1072 if not result: 

1073 result = self._check_arbitrary_unsatisfiable() 

1074 

1075 if not result and self.prereleases is False: 

1076 result = ranges_are_prerelease_only(self._get_ranges()) 

1077 

1078 self._is_unsatisfiable = result 

1079 return result 

1080 

1081 def _check_arbitrary_unsatisfiable(self) -> bool: 

1082 """Check === (arbitrary equality) specs for unsatisfiability. 

1083 

1084 === uses case-insensitive string comparison, so the only candidate 

1085 that can match ``===V`` is the literal string V. This method 

1086 checks whether that candidate is excluded by other specifiers. 

1087 """ 

1088 arbitrary = [s for s in self._specs if s.operator == "==="] 

1089 if not arbitrary: 

1090 return False 

1091 

1092 # Multiple === must agree on the same string (case-insensitive). 

1093 first = arbitrary[0].version.lower() 

1094 if any(s.version.lower() != first for s in arbitrary[1:]): 

1095 return True 

1096 

1097 # The sole candidate is the === version string. Check whether 

1098 # it can satisfy every standard spec. 

1099 candidate = coerce_version(arbitrary[0].version) 

1100 

1101 # With prereleases=False, a prerelease candidate is excluded 

1102 # by contains() before the === string check even runs. 

1103 if ( 

1104 self.prereleases is False 

1105 and candidate is not None 

1106 and candidate.is_prerelease 

1107 ): 

1108 return True 

1109 

1110 standard = [s for s in self._specs if s.operator != "==="] 

1111 if not standard: 

1112 return False 

1113 

1114 if candidate is None: 

1115 # Unparsable string cannot satisfy any standard spec. 

1116 return True 

1117 

1118 return not all(s.contains(candidate) for s in standard) 

1119 

1120 def to_range(self) -> ranges.VersionRange: 

1121 """Return the :class:`~packaging.ranges.VersionRange` this set accepts. 

1122 

1123 An empty set yields the full range; an unsatisfiable set yields the 

1124 empty range. ``===`` specifiers contribute literal-string admission. 

1125 

1126 >>> SpecifierSet(">=1.0,<2.0").to_range() 

1127 <VersionRange '[1.0, 2.0.dev0)'> 

1128 

1129 .. versionadded:: 26.3 

1130 """ 

1131 from .ranges import VersionRange # noqa: PLC0415 

1132 

1133 return VersionRange._from_specifier_set(self) 

1134 

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

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

1137 

1138 :param item: The item to check for. 

1139 

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

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

1142 

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

1144 True 

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

1146 True 

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

1148 False 

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

1150 True 

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

1152 True 

1153 """ 

1154 return self.contains(item) 

1155 

1156 def contains( 

1157 self, 

1158 item: UnparsedVersion, 

1159 prereleases: bool | None = None, 

1160 installed: bool | None = None, 

1161 ) -> bool: 

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

1163 

1164 :param item: 

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

1166 :class:`~packaging.version.Version` instance. 

1167 :param prereleases: 

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

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

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

1171 :param installed: 

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

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

1174 

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

1176 True 

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

1178 True 

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

1180 False 

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

1182 True 

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

1184 False 

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

1186 True 

1187 """ 

1188 version = coerce_version(item) 

1189 

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

1191 prereleases = True 

1192 

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

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

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

1196 check_item = item 

1197 else: 

1198 check_item = version 

1199 

1200 # Fast path: a parseable, local-free version against a rangelike set. 

1201 # A local on ``version`` needs PEP 440 stripping that the range path 

1202 # applies. 

1203 if ( 

1204 version is not None 

1205 and not self._has_arbitrary 

1206 and version.local is None 

1207 and self._specs 

1208 ): 

1209 if version.is_prerelease and ( 

1210 prereleases is False 

1211 or (prereleases is None and self._prereleases is False) 

1212 ): 

1213 return False 

1214 

1215 bounds = self._ranges 

1216 if bounds is None: 

1217 # Per-spec ``_fast_match`` answers a set of simple specifiers 

1218 # without folding anything. If a spec needs the range path, 

1219 # fold the intersected bounds once and cache them so repeated 

1220 # checks on the same set stay cheap. 

1221 for spec in self._specs: 

1222 match = _fast_match(spec, version) 

1223 if match is None: 

1224 break 

1225 if not match: 

1226 return False 

1227 else: 

1228 return True 

1229 

1230 bounds = self._ranges = self._get_ranges() 

1231 

1232 return matches_bounds_only(bounds, version) 

1233 

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

1235 

1236 @typing.overload 

1237 def filter( 

1238 self, 

1239 iterable: Iterable[UnparsedVersionVar], 

1240 prereleases: bool | None = None, 

1241 key: None = ..., 

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

1243 

1244 @typing.overload 

1245 def filter( 

1246 self, 

1247 iterable: Iterable[T], 

1248 prereleases: bool | None = None, 

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

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

1251 

1252 def filter( 

1253 self, 

1254 iterable: Iterable[Any], 

1255 prereleases: bool | None = None, 

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

1257 ) -> Iterator[Any]: 

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

1259 

1260 :param iterable: 

1261 An iterable that can contain version strings and 

1262 :class:`~packaging.version.Version` instances. The items in the 

1263 iterable will be filtered according to the specifier. 

1264 :param prereleases: 

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

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

1267 and match prereleases if there are no other versions. 

1268 :param key: 

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

1270 returns a version string or :class:`~packaging.version.Version` 

1271 instance to be used for filtering. 

1272 

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

1274 ['1.3'] 

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

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

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

1278 ['1.5a1'] 

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

1280 ['1.3', '1.5a1'] 

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

1282 ['1.3', '1.5a1'] 

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

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

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

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

1287 

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

1289 versions in the set. 

1290 

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

1292 ['1.3'] 

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

1294 ['1.5a1'] 

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

1296 ['1.3', '1.5a1'] 

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

1298 ['1.3', '1.5a1'] 

1299 

1300 .. versionchanged:: 26.1 

1301 

1302 Added the ``key`` parameter. 

1303 """ 

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

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

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

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

1308 prereleases = self.prereleases 

1309 

1310 if self._specs: 

1311 if self._has_arbitrary: 

1312 # Slow path for === 

1313 specs = self._specs 

1314 matches = ( 

1315 item 

1316 for item in iterable 

1317 if all( 

1318 s.contains(item if key is None else key(item), prereleases=True) 

1319 for s in specs 

1320 ) 

1321 ) 

1322 return _apply_prereleases_filter(matches, key, prereleases) 

1323 

1324 ranges = self._ranges 

1325 if ranges is None: 

1326 ranges = self._get_ranges() 

1327 return filter_by_ranges(ranges, iterable, key, prereleases) 

1328 

1329 # Empty SpecifierSet. 

1330 return _apply_prereleases_filter(iterable, key, prereleases) 

1331 

1332 

1333def _pep440_filter_prereleases( 

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

1335) -> Iterator[Any]: 

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

1337 # Two lists used: 

1338 # * all_nonfinal to preserve order if no finals exist 

1339 # * arbitrary_strings for streaming when first final found 

1340 all_nonfinal: list[Any] = [] 

1341 arbitrary_strings: list[Any] = [] 

1342 

1343 found_final = False 

1344 for item in iterable: 

1345 parsed = coerce_version(item if key is None else key(item)) 

1346 

1347 if parsed is None: 

1348 # Arbitrary strings are always included as it is not 

1349 # possible to determine if they are prereleases, 

1350 # and they have already passed all specifiers. 

1351 if found_final: 

1352 yield item 

1353 else: 

1354 arbitrary_strings.append(item) 

1355 all_nonfinal.append(item) 

1356 continue 

1357 

1358 if not parsed.is_prerelease: 

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

1360 if not found_final: 

1361 yield from arbitrary_strings 

1362 found_final = True 

1363 yield item 

1364 continue 

1365 

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

1367 if not found_final: 

1368 all_nonfinal.append(item) 

1369 

1370 # No finals found - yield all buffered items 

1371 if not found_final: 

1372 yield from all_nonfinal