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

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

441 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 sys 

16import typing 

17from typing import ( 

18 TYPE_CHECKING, 

19 Any, 

20 Callable, 

21 Final, 

22 TypeVar, 

23 Union, 

24) 

25 

26from ._ranges import ( 

27 FULL_RANGE, 

28 filter_by_ranges, 

29 intersect_ranges, 

30 ranges_are_prerelease_only, 

31 standard_ranges, 

32 trim_release, 

33 wildcard_ranges, 

34) 

35from .utils import canonicalize_version 

36from .version import InvalidVersion, Version 

37 

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

39 from typing import TypeGuard # pragma: no cover 

40elif TYPE_CHECKING: 

41 from typing_extensions import TypeGuard 

42 

43if TYPE_CHECKING: 

44 from collections.abc import Iterable, Iterator, Sequence 

45 

46 from ._ranges import VersionRange 

47 

48 

49__all__ = [ 

50 "BaseSpecifier", 

51 "InvalidSpecifier", 

52 "Specifier", 

53 "SpecifierSet", 

54] 

55 

56 

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

58 return __all__ 

59 

60 

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

62 return ( 

63 isinstance(spec, tuple) 

64 and len(spec) == 2 

65 and isinstance(spec[0], str) 

66 and isinstance(spec[1], str) 

67 ) 

68 

69 

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

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

72 

73 

74T = TypeVar("T") 

75UnparsedVersion = Union[Version, str] 

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

77 

78 

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

80 if not isinstance(version, Version): 

81 try: 

82 version = Version(version) 

83 except InvalidVersion: 

84 return None 

85 return version 

86 

87 

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

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

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

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

92 ">=": Version.__ge__, 

93 "<=": Version.__le__, 

94 "==": Version.__eq__, 

95 "!=": Version.__ne__, 

96} 

97 

98 

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

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

101 

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

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

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

105 """ 

106 op_str, ver_str = specifier._spec 

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

108 return None 

109 

110 direct_compare = _DIRECT_COMPARE_OPS.get(op_str) 

111 if direct_compare is not None: 

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

113 

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

115 spec_v = specifier._require_spec_version(ver_str) 

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

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

118 # Otherwise a direct cmpkey comparison is correct. 

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

120 spec_v.release 

121 ): 

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

123 return None 

124 

125 return None 

126 

127 

128class InvalidSpecifier(ValueError): 

129 """ 

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

131 string that is invalid. 

132 

133 >>> Specifier("lolwat") 

134 Traceback (most recent call last): 

135 ... 

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

137 """ 

138 

139 

140class BaseSpecifier(metaclass=abc.ABCMeta): 

141 __slots__ = () 

142 __match_args__ = ("_str",) 

143 

144 @property 

145 def _str(self) -> str: 

146 """Internal property for match_args""" 

147 return str(self) 

148 

149 @abc.abstractmethod 

150 def __str__(self) -> str: 

151 """ 

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

153 should be representative of the Specifier itself. 

154 """ 

155 

156 @abc.abstractmethod 

157 def __hash__(self) -> int: 

158 """ 

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

160 """ 

161 

162 @abc.abstractmethod 

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

164 """ 

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

166 objects are equal. 

167 

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

169 """ 

170 

171 @property 

172 @abc.abstractmethod 

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

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

175 

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

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

178 """ 

179 

180 @prereleases.setter # noqa: B027 

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

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

183 

184 :param value: The value to set. 

185 """ 

186 

187 @abc.abstractmethod 

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

189 """ 

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

191 """ 

192 

193 @typing.overload 

194 def filter( 

195 self, 

196 iterable: Iterable[UnparsedVersionVar], 

197 prereleases: bool | None = None, 

198 key: None = ..., 

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

200 

201 @typing.overload 

202 def filter( 

203 self, 

204 iterable: Iterable[T], 

205 prereleases: bool | None = None, 

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

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

208 

209 @abc.abstractmethod 

210 def filter( 

211 self, 

212 iterable: Iterable[Any], 

213 prereleases: bool | None = None, 

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

215 ) -> Iterator[Any]: 

216 """ 

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

218 are contained within this specifier are allowed in it. 

219 """ 

220 

221 

222class Specifier(BaseSpecifier): 

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

224 

225 .. tip:: 

226 

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

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

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

230 

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

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

233 

234 .. versionchanged:: 26.2 

235 

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

237 be unpickled with future releases. Backward compatibility with pickles 

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

239 release. 

240 """ 

241 

242 __slots__ = ( 

243 "_prereleases", 

244 "_ranges", 

245 "_spec", 

246 "_spec_version", 

247 ) 

248 

249 _specifier_regex_str = r""" 

250 (?: 

251 (?: 

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

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

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

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

256 # but included entirely as an escape hatch. 

257 === # Only match for the identity operator 

258 \s* 

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

260 # we match everything except for whitespace, a 

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

262 # since versions can be enclosed in them. 

263 ) 

264 | 

265 (?: 

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

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

268 # operators separately to enable that. 

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

270 

271 \s* 

272 v? 

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

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

275 

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

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

278 (?: 

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

280 | 

281 (?a: # pre release 

282 [-_\.]? 

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

284 [-_\.]? 

285 [0-9]* 

286 )? 

287 (?a: # post release 

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

289 )? 

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

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

292 )? 

293 ) 

294 | 

295 (?: 

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

297 # release segment. 

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

299 

300 \s* 

301 v? 

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

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

304 (?: # pre release 

305 [-_\.]? 

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

307 [-_\.]? 

308 [0-9]* 

309 )? 

310 (?: # post release 

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

312 )? 

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

314 ) 

315 | 

316 (?: 

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

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

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

320 # matching wild cards. 

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

322 

323 \s* 

324 v? 

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

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

327 (?a: # pre release 

328 [-_\.]? 

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

330 [-_\.]? 

331 [0-9]* 

332 )? 

333 (?a: # post release 

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

335 )? 

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

337 ) 

338 ) 

339 """ 

340 

341 _regex = re.compile( 

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

343 ) 

344 

345 # Legacy unused attribute, kept for backward compatibility 

346 _operators: Final = { 

347 "~=": "compatible", 

348 "==": "equal", 

349 "!=": "not_equal", 

350 "<=": "less_than_equal", 

351 ">=": "greater_than_equal", 

352 "<": "less_than", 

353 ">": "greater_than", 

354 "===": "arbitrary", 

355 } 

356 

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

358 """Initialize a Specifier instance. 

359 

360 :param spec: 

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

362 normalized before use. 

363 :param prereleases: 

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

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

366 given specifiers. 

367 :raises InvalidSpecifier: 

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

369 """ 

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

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

372 

373 spec = spec.strip() 

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

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

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

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

378 else: 

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

380 

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

382 

383 # Store whether or not this Specifier should accept prereleases 

384 self._prereleases = prereleases 

385 

386 # Specifier version cache 

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

388 

389 # Version range cache (populated by _to_ranges) 

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

391 

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

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

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

395 return self._spec_version[1] 

396 

397 version_specifier = _coerce_version(version) 

398 if version_specifier is None: 

399 return None 

400 

401 self._spec_version = (version, version_specifier) 

402 return version_specifier 

403 

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

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

406 

407 This method should only be called for operators where version 

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

409 """ 

410 spec_version = self._get_spec_version(version) 

411 assert spec_version is not None 

412 return spec_version 

413 

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

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

416 

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

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

419 """ 

420 if self._ranges is not None: 

421 return self._ranges 

422 

423 op = self.operator 

424 ver_str = self.version 

425 

426 if op == "===": 

427 result: Sequence[VersionRange] = FULL_RANGE 

428 elif ver_str.endswith(".*"): 

429 base = self._require_spec_version(ver_str[:-2]) 

430 result = wildcard_ranges(op, base) 

431 else: 

432 v = self._require_spec_version(ver_str) 

433 has_local = "+" in ver_str 

434 result = standard_ranges(op, v, has_local) 

435 

436 self._ranges = result 

437 return result 

438 

439 @property 

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

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

442 # blindly use that. 

443 if self._prereleases is not None: 

444 return self._prereleases 

445 

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

447 # the version in the specifier is a prerelease. 

448 operator, version_str = self._spec 

449 if operator == "!=": 

450 return False 

451 

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

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

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

455 return False 

456 

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

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

459 version = self._get_spec_version(version_str) 

460 if version is None: 

461 return None 

462 

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

464 # object implies pre-releases. 

465 return version.is_prerelease 

466 

467 @prereleases.setter 

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

469 self._prereleases = value 

470 

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

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

473 # ((operator, version), prereleases) 

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

475 return (self._spec, self._prereleases) 

476 

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

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

479 self._spec_version = None 

480 self._ranges = None 

481 

482 if isinstance(state, tuple): 

483 if len(state) == 2: 

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

485 spec, prereleases = state 

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

487 self._spec = spec 

488 self._prereleases = prereleases 

489 return 

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

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

492 _, slot_dict = state 

493 spec = slot_dict.get("_spec") 

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

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

496 self._spec = spec 

497 self._prereleases = prereleases 

498 return 

499 if isinstance(state, dict): 

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

501 spec = state.get("_spec") 

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

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

504 self._spec = spec 

505 self._prereleases = prereleases 

506 return 

507 

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

509 

510 @property 

511 def operator(self) -> str: 

512 """The operator of this specifier. 

513 

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

515 '==' 

516 """ 

517 return self._spec[0] 

518 

519 @property 

520 def version(self) -> str: 

521 """The version of this specifier. 

522 

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

524 '1.2.3' 

525 """ 

526 return self._spec[1] 

527 

528 def __repr__(self) -> str: 

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

530 

531 >>> Specifier('>=1.0.0') 

532 <Specifier('>=1.0.0')> 

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

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

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

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

537 """ 

538 pre = ( 

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

540 if self._prereleases is not None 

541 else "" 

542 ) 

543 

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

545 

546 def __str__(self) -> str: 

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

548 

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

550 '>=1.0.0' 

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

552 '>=1.0.0' 

553 """ 

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

555 

556 @property 

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

558 operator, version = self._spec 

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

560 return operator, version 

561 

562 spec_version = self._require_spec_version(version) 

563 

564 canonical_version = canonicalize_version( 

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

566 ) 

567 

568 return operator, canonical_version 

569 

570 def __hash__(self) -> int: 

571 return hash(self._canonical_spec) 

572 

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

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

575 

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

577 

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

579 

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

581 True 

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

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

584 True 

585 >>> Specifier("==1.2.3") == "==1.2.3" 

586 True 

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

588 False 

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

590 False 

591 """ 

592 if isinstance(other, str): 

593 try: 

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

595 except InvalidSpecifier: 

596 return NotImplemented 

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

598 return NotImplemented 

599 

600 return self._canonical_spec == other._canonical_spec 

601 

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

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

604 

605 :param item: The item to check for. 

606 

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

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

609 

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

611 True 

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

613 True 

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

615 False 

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

617 True 

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

619 True 

620 """ 

621 return self.contains(item) 

622 

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

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

625 

626 :param item: 

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

628 :class:`Version` instance. 

629 :param prereleases: 

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

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

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

633 

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

635 True 

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

637 True 

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

639 False 

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

641 True 

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

643 False 

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

645 True 

646 """ 

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

648 # be wasted. 

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

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

651 

652 parsed = _coerce_version(item) 

653 if parsed is None: 

654 # Standard operators never match an unparsable input. 

655 return False 

656 

657 match = _fast_match(self, parsed) 

658 if match is not None: 

659 if prereleases is None: 

660 if self._prereleases is not None: 

661 prereleases = self._prereleases 

662 elif self.prereleases: 

663 prereleases = True 

664 if prereleases is False and parsed.is_prerelease: 

665 return False 

666 return match 

667 

668 # Pass the already-parsed Version so filter_by_ranges doesn't 

669 # re-coerce it. 

670 return bool(list(self.filter([parsed], prereleases=prereleases))) 

671 

672 @typing.overload 

673 def filter( 

674 self, 

675 iterable: Iterable[UnparsedVersionVar], 

676 prereleases: bool | None = None, 

677 key: None = ..., 

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

679 

680 @typing.overload 

681 def filter( 

682 self, 

683 iterable: Iterable[T], 

684 prereleases: bool | None = None, 

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

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

687 

688 def filter( 

689 self, 

690 iterable: Iterable[Any], 

691 prereleases: bool | None = None, 

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

693 ) -> Iterator[Any]: 

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

695 

696 :param iterable: 

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

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

699 :param prereleases: 

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

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

702 and match prereleases if there are no other versions. 

703 :param key: 

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

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

706 filtering. 

707 

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

709 ['1.3'] 

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

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

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

713 ['1.5a1'] 

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

715 ['1.3', '1.5a1'] 

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

717 ['1.3', '1.5a1'] 

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

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

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

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

722 """ 

723 if prereleases is None: 

724 if self._prereleases is not None: 

725 prereleases = self._prereleases 

726 elif self.prereleases: 

727 prereleases = True 

728 

729 if self.operator == "===": 

730 spec_lower = self.version.lower() 

731 matches = ( 

732 item 

733 for item in iterable 

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

735 ) 

736 return _apply_prereleases_filter(matches, key, prereleases) 

737 

738 ranges = self._ranges 

739 if ranges is None: 

740 ranges = self._to_ranges() 

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

742 

743 

744def _apply_prereleases_filter( 

745 matches: Iterable[Any], 

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

747 prereleases: bool | None, 

748) -> Iterator[Any]: 

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

750 

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

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

753 """ 

754 if prereleases is None: 

755 return _pep440_filter_prereleases(matches, key) 

756 if prereleases: 

757 return iter(matches) 

758 return ( 

759 item 

760 for item in matches 

761 if (parsed := _coerce_version(item if key is None else key(item))) is None 

762 or not parsed.is_prerelease 

763 ) 

764 

765 

766class SpecifierSet(BaseSpecifier): 

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

768 

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

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

771 

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

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

774 releases. 

775 

776 .. versionchanged:: 26.2 

777 

778 Added a stable pickle format. Pickles created with 

779 packaging 26.2+ can be unpickled with future releases. 

780 Backward compatibility with pickles from 

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

782 release. 

783 """ 

784 

785 __slots__ = ( 

786 "_canonicalized", 

787 "_has_arbitrary", 

788 "_is_unsatisfiable", 

789 "_prereleases", 

790 "_ranges", 

791 "_specs", 

792 ) 

793 

794 def __init__( 

795 self, 

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

797 prereleases: bool | None = None, 

798 ) -> None: 

799 """Initialize a SpecifierSet instance. 

800 

801 :param specifiers: 

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

803 specifiers which will be parsed and normalized before use. 

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

805 as is. 

806 :param prereleases: 

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

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

809 given specifiers. 

810 

811 :raises InvalidSpecifier: 

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

813 raised. 

814 """ 

815 

816 if isinstance(specifiers, str): 

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

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

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

820 

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

822 # Fast substring check; avoids iterating parsed specs. 

823 self._has_arbitrary = "===" in specifiers 

824 else: 

825 self._specs = tuple(specifiers) 

826 # Substring check works for both Specifier objects and plain 

827 # strings (setuptools passes lists of strings). 

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

829 

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

831 self._is_unsatisfiable: bool | None = None 

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

833 

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

835 # we accept prereleases or not. 

836 self._prereleases = prereleases 

837 

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

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

840 if not self._canonicalized: 

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

842 self._canonicalized = True 

843 self._is_unsatisfiable = None 

844 self._ranges = None 

845 return self._specs 

846 

847 @property 

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

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

850 # pass that through here. 

851 if self._prereleases is not None: 

852 return self._prereleases 

853 

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

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

856 # pre-releases or not. 

857 if not self._specs: 

858 return None 

859 

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

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

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

863 return True 

864 

865 return None 

866 

867 @prereleases.setter 

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

869 self._prereleases = value 

870 self._is_unsatisfiable = None 

871 

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

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

874 # (specs, prereleases) 

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

876 return (self._specs, self._prereleases) 

877 

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

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

880 self._ranges = None 

881 self._is_unsatisfiable = None 

882 

883 if isinstance(state, tuple): 

884 if len(state) == 2: 

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

886 specs, prereleases = state 

887 if ( 

888 isinstance(specs, tuple) 

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

890 and _validate_pre(prereleases) 

891 ): 

892 self._specs = specs 

893 self._prereleases = prereleases 

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

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

896 return 

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

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

899 _, slot_dict = state 

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

901 prereleases = slot_dict.get("_prereleases") 

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

903 if isinstance(specs, frozenset): 

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

905 if ( 

906 isinstance(specs, tuple) 

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

908 and _validate_pre(prereleases) 

909 ): 

910 self._specs = specs 

911 self._prereleases = prereleases 

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

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

914 return 

915 if isinstance(state, dict): 

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

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

918 prereleases = state.get("_prereleases") 

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

920 if isinstance(specs, frozenset): 

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

922 if ( 

923 isinstance(specs, tuple) 

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

925 and _validate_pre(prereleases) 

926 ): 

927 self._specs = specs 

928 self._prereleases = prereleases 

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

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

931 return 

932 

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

934 

935 def __repr__(self) -> str: 

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

937 

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

939 match the input string. 

940 

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

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

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

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

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

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

947 """ 

948 pre = ( 

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

950 if self._prereleases is not None 

951 else "" 

952 ) 

953 

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

955 

956 def __str__(self) -> str: 

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

958 

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

960 match the input string. 

961 

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

963 '!=1.0.1,>=1.0.0' 

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

965 '!=1.0.1,>=1.0.0' 

966 """ 

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

968 

969 def __hash__(self) -> int: 

970 return hash(self._canonical_specs()) 

971 

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

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

974 

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

976 

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

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

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

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

981 """ 

982 if isinstance(other, str): 

983 other = SpecifierSet(other) 

984 elif not isinstance(other, SpecifierSet): 

985 return NotImplemented 

986 

987 specifier = SpecifierSet() 

988 specifier._specs = self._specs + other._specs 

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

990 specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary 

991 

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

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

994 specifier._prereleases = other._prereleases 

995 elif other._prereleases is None: 

996 specifier._prereleases = self._prereleases 

997 else: 

998 raise ValueError( 

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

1000 ) 

1001 

1002 return specifier 

1003 

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

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

1006 

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

1008 

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

1010 

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

1012 True 

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

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

1015 True 

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

1017 True 

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

1019 False 

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

1021 False 

1022 """ 

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

1024 other = SpecifierSet(str(other)) 

1025 elif not isinstance(other, SpecifierSet): 

1026 return NotImplemented 

1027 

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

1029 

1030 def __len__(self) -> int: 

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

1032 return len(self._specs) 

1033 

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

1035 """ 

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

1037 in this specifier set. 

1038 

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

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

1041 """ 

1042 return iter(self._specs) 

1043 

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

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

1046 

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

1048 is non-empty. 

1049 """ 

1050 if self._ranges is not None: 

1051 return self._ranges 

1052 

1053 result: Sequence[VersionRange] | None = None 

1054 for s in self._specs: 

1055 sub = s._to_ranges() 

1056 if result is None: 

1057 result = sub 

1058 else: 

1059 result = intersect_ranges(result, sub) 

1060 if not result: 

1061 break 

1062 

1063 if result is None: # pragma: no cover 

1064 raise RuntimeError("_get_ranges called with no specs") 

1065 self._ranges = result 

1066 return result 

1067 

1068 def is_unsatisfiable(self) -> bool: 

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

1070 

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

1072 

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

1074 True 

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

1076 False 

1077 >>> SpecifierSet("").is_unsatisfiable() 

1078 False 

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

1080 True 

1081 """ 

1082 cached = self._is_unsatisfiable 

1083 if cached is not None: 

1084 return cached 

1085 

1086 if not self._specs: 

1087 self._is_unsatisfiable = False 

1088 return False 

1089 

1090 result = not self._get_ranges() 

1091 

1092 if not result: 

1093 result = self._check_arbitrary_unsatisfiable() 

1094 

1095 if not result and self.prereleases is False: 

1096 result = ranges_are_prerelease_only(self._get_ranges()) 

1097 

1098 self._is_unsatisfiable = result 

1099 return result 

1100 

1101 def _check_arbitrary_unsatisfiable(self) -> bool: 

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

1103 

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

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

1106 checks whether that candidate is excluded by other specifiers. 

1107 """ 

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

1109 if not arbitrary: 

1110 return False 

1111 

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

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

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

1115 return True 

1116 

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

1118 # it can satisfy every standard spec. 

1119 candidate = _coerce_version(arbitrary[0].version) 

1120 

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

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

1123 if ( 

1124 self.prereleases is False 

1125 and candidate is not None 

1126 and candidate.is_prerelease 

1127 ): 

1128 return True 

1129 

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

1131 if not standard: 

1132 return False 

1133 

1134 if candidate is None: 

1135 # Unparsable string cannot satisfy any standard spec. 

1136 return True 

1137 

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

1139 

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

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

1142 

1143 :param item: The item to check for. 

1144 

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

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

1147 

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

1149 True 

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

1151 True 

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

1153 False 

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

1155 True 

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

1157 True 

1158 """ 

1159 return self.contains(item) 

1160 

1161 def contains( 

1162 self, 

1163 item: UnparsedVersion, 

1164 prereleases: bool | None = None, 

1165 installed: bool | None = None, 

1166 ) -> bool: 

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

1168 

1169 :param item: 

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

1171 :class:`Version` instance. 

1172 :param prereleases: 

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

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

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

1176 :param installed: 

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

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

1179 

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

1181 True 

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

1183 True 

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

1185 False 

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

1187 True 

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

1189 False 

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

1191 True 

1192 """ 

1193 version = _coerce_version(item) 

1194 

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

1196 prereleases = True 

1197 

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

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

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

1201 check_item = item 

1202 else: 

1203 check_item = version 

1204 

1205 # Fast path: skip the intersected-range build while every spec 

1206 # answers directly. Once ``_ranges`` is set the cached range 

1207 # path beats re-iterating specs, so fall through then. A local 

1208 # on ``version`` needs PEP 440 stripping that the range path 

1209 # applies. 

1210 if ( 

1211 self._ranges is None 

1212 and version is not None 

1213 and not self._has_arbitrary 

1214 and version.local is None 

1215 and self._specs 

1216 ): 

1217 if version.is_prerelease and ( 

1218 prereleases is False 

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

1220 ): 

1221 return False 

1222 for spec in self._specs: 

1223 match = _fast_match(spec, version) 

1224 if match is None: 

1225 break 

1226 if not match: 

1227 return False 

1228 else: 

1229 return True 

1230 

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

1232 

1233 @typing.overload 

1234 def filter( 

1235 self, 

1236 iterable: Iterable[UnparsedVersionVar], 

1237 prereleases: bool | None = None, 

1238 key: None = ..., 

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

1240 

1241 @typing.overload 

1242 def filter( 

1243 self, 

1244 iterable: Iterable[T], 

1245 prereleases: bool | None = None, 

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

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

1248 

1249 def filter( 

1250 self, 

1251 iterable: Iterable[Any], 

1252 prereleases: bool | None = None, 

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

1254 ) -> Iterator[Any]: 

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

1256 

1257 :param iterable: 

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

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

1260 :param prereleases: 

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

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

1263 and match prereleases if there are no other versions. 

1264 :param key: 

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

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

1267 filtering. 

1268 

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

1270 ['1.3'] 

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

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

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

1274 ['1.5a1'] 

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

1276 ['1.3', '1.5a1'] 

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

1278 ['1.3', '1.5a1'] 

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

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

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

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

1283 

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

1285 versions in the set. 

1286 

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

1288 ['1.3'] 

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

1290 ['1.5a1'] 

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

1292 ['1.3', '1.5a1'] 

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

1294 ['1.3', '1.5a1'] 

1295 """ 

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

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

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

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

1300 prereleases = self.prereleases 

1301 

1302 if self._specs: 

1303 if self._has_arbitrary: 

1304 # Slow path for === 

1305 specs = self._specs 

1306 matches = ( 

1307 item 

1308 for item in iterable 

1309 if all( 

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

1311 for s in specs 

1312 ) 

1313 ) 

1314 return _apply_prereleases_filter(matches, key, prereleases) 

1315 

1316 ranges = self._ranges 

1317 if ranges is None: 

1318 ranges = self._get_ranges() 

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

1320 

1321 # Empty SpecifierSet. 

1322 return _apply_prereleases_filter(iterable, key, prereleases) 

1323 

1324 

1325def _pep440_filter_prereleases( 

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

1327) -> Iterator[Any]: 

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

1329 # Two lists used: 

1330 # * all_nonfinal to preserve order if no finals exist 

1331 # * arbitrary_strings for streaming when first final found 

1332 all_nonfinal: list[Any] = [] 

1333 arbitrary_strings: list[Any] = [] 

1334 

1335 found_final = False 

1336 for item in iterable: 

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

1338 

1339 if parsed is None: 

1340 # Arbitrary strings are always included as it is not 

1341 # possible to determine if they are prereleases, 

1342 # and they have already passed all specifiers. 

1343 if found_final: 

1344 yield item 

1345 else: 

1346 arbitrary_strings.append(item) 

1347 all_nonfinal.append(item) 

1348 continue 

1349 

1350 if not parsed.is_prerelease: 

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

1352 if not found_final: 

1353 yield from arbitrary_strings 

1354 found_final = True 

1355 yield item 

1356 continue 

1357 

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

1359 if not found_final: 

1360 all_nonfinal.append(item) 

1361 

1362 # No finals found - yield all buffered items 

1363 if not found_final: 

1364 yield from all_nonfinal