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

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

514 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"""Private version-range helpers used by :mod:`packaging.specifiers`.""" 

5 

6from __future__ import annotations 

7 

8import enum 

9import functools 

10from typing import ( 

11 TYPE_CHECKING, 

12 Any, 

13 Final, 

14) 

15 

16from .version import InvalidVersion, Version 

17 

18if TYPE_CHECKING: 

19 from collections.abc import Callable, Iterable, Iterator, Sequence 

20 

21 # Total-order key for comparing two boundaries (boundary-vs-boundary only). 

22 # The post slot may be ``_BOUNDARY_INF`` for an AFTER_POSTS boundary. 

23 _BoundaryOrderSuffix = tuple[int, int, int, int | float, int, int] 

24 _BoundaryOrderKey = tuple[int, tuple[int, ...], _BoundaryOrderSuffix, float] 

25 

26__all__ = [ 

27 "FULL_RANGE", 

28 "bounds_for_spec", 

29 "coerce_version", 

30 "filter_by_ranges", 

31 "intersect_ranges", 

32 "intersect_specifier_bounds", 

33 "least_version_above", 

34 "matches_bounds_only", 

35 "range_is_empty", 

36 "ranges_are_disjoint", 

37 "ranges_are_prerelease_only", 

38 "resolve_prereleases", 

39 "standard_ranges", 

40 "wildcard_ranges", 

41] 

42 

43#: The smallest possible PEP 440 version. No valid version is less than this. 

44MIN_VERSION: Final[Version] = Version("0.dev0") 

45 

46#: The smallest non-pre-release version, i.e. the nearest non-pre-release at or 

47#: above the ``-inf`` floor. 

48MIN_RELEASE: Final[Version] = Version("0") 

49 

50#: Sorts above any real post number and any local label, so a boundary can be 

51#: ordered above the version family it covers when two boundaries are compared. 

52_BOUNDARY_INF: Final[float] = float("inf") 

53 

54 

55class BoundaryKind(enum.Enum): 

56 """Where a boundary marker sits in the version ordering.""" 

57 

58 AFTER_LOCALS = enum.auto() # after V+local, before V.post0 

59 AFTER_POSTS = enum.auto() # after V.postN, before next release 

60 

61 

62@functools.total_ordering 

63class BoundaryVersion: 

64 """A point on the version line between two real PEP 440 versions. 

65 

66 Relative to a base version V:: 

67 

68 V < V+local < AFTER_LOCALS(V) < V.post0 < AFTER_POSTS(V) 

69 

70 AFTER_LOCALS is the upper bound of ``<=V``, ``==V``, ``!=V`` (no 

71 local), and the lower bound of the upper-side range of ``!=V``. 

72 AFTER_POSTS is the lower bound of ``>V`` (V final or pre-release), 

73 excluding V's post-releases per PEP 440. 

74 """ 

75 

76 __slots__ = ( 

77 "_cached_dev", 

78 "_cached_epoch", 

79 "_cached_post", 

80 "_cached_pre", 

81 "_cached_trimmed_release", 

82 "kind", 

83 "version", 

84 ) 

85 

86 # Synthetic boundaries must use version operators rather than cached keys. 

87 _key_cache: None = None 

88 

89 def __init__(self, version: Version, kind: BoundaryKind) -> None: 

90 self.version = version 

91 self.kind = kind 

92 self._cached_trimmed_release = trim_release(version.release) 

93 self._cached_epoch = version.epoch 

94 self._cached_pre = version.pre 

95 self._cached_post = version.post 

96 self._cached_dev = version.dev 

97 

98 def _is_family(self, other: Version) -> bool: 

99 """Is ``other`` a version that this boundary sorts above?""" 

100 if other.epoch != self._cached_epoch: 

101 return False 

102 # Inline release-trim comparison: other.release matches the 

103 # trimmed release iff its leading slice is equal and any extra 

104 # components are zero. Avoids trim_release's tuple allocation. 

105 other_release = other.release 

106 trimmed_release = self._cached_trimmed_release 

107 trimmed_length = len(trimmed_release) 

108 if len(other_release) < trimmed_length: 

109 return False 

110 if other_release[:trimmed_length] != trimmed_release: 

111 return False 

112 for i in range(trimmed_length, len(other_release)): 

113 if other_release[i] != 0: 

114 return False 

115 if other.pre != self._cached_pre: 

116 return False 

117 if self.kind == BoundaryKind.AFTER_LOCALS: 

118 # Local family: same public version, any local label. 

119 return other.post == self._cached_post and other.dev == self._cached_dev 

120 # Post family: V itself + any post-release of V. 

121 return other.dev == self._cached_dev or other.post is not None 

122 

123 def _order_key(self) -> _BoundaryOrderKey: 

124 """Sort key placing this boundary just above the versions it covers. 

125 

126 It extends ``V``'s comparison key ``(epoch, release, suffix)`` with 

127 a trailing ``_BOUNDARY_INF`` local component, so the key sorts after 

128 ``V`` and every ``V+local`` (whose keys carry a real, finite local 

129 segment). ``suffix`` is the 6-int comparison suffix 

130 ``(pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)``. 

131 

132 For an AFTER_POSTS boundary the suffix is replaced with one whose 

133 post number is ``_BOUNDARY_INF``, so the key also sorts after every 

134 ``V.postN``. An AFTER_LOCALS boundary uses ``V``'s suffix unchanged. 

135 """ 

136 version_key = self.version._key 

137 suffix: _BoundaryOrderSuffix = version_key[2] 

138 

139 if self.kind == BoundaryKind.AFTER_POSTS: 

140 suffix = (suffix[0], suffix[1], 1, _BOUNDARY_INF, 1, 0) 

141 

142 return version_key[0], version_key[1], suffix, _BOUNDARY_INF 

143 

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

145 # Key off the order key so equality matches the ``<`` / ``>`` order: 

146 # ``AFTER_POSTS(1.0)`` and ``AFTER_POSTS(1.0.post1)`` are the same point. 

147 if isinstance(other, BoundaryVersion): 

148 return self._order_key() == other._order_key() 

149 return NotImplemented 

150 

151 def __lt__(self, other: BoundaryVersion | Version) -> bool: 

152 if isinstance(other, BoundaryVersion): 

153 return self._order_key() < other._order_key() 

154 # boundary < other_version iff V < other AND other not in family. 

155 # The cheap V >= other path short-circuits before the family check. 

156 if not (self.version < other): 

157 return False 

158 return not self._is_family(other) 

159 

160 def __gt__(self, other: BoundaryVersion | Version) -> bool: 

161 # Defined directly to bypass functools.total_ordering's 

162 # NotImplemented round-trip on reflected ``Version < boundary``. 

163 if isinstance(other, BoundaryVersion): 

164 return self._order_key() > other._order_key() 

165 if self.version >= other: 

166 return True 

167 return self._is_family(other) 

168 

169 def __hash__(self) -> int: 

170 # Keyed to ``__eq__`` (the order key), so equal boundaries hash equal. 

171 return hash(self._order_key()) 

172 

173 def __repr__(self) -> str: 

174 return f"{self.__class__.__name__}({self.version!r}, {self.kind.name})" 

175 

176 

177if TYPE_CHECKING: 

178 _VersionOrBoundary = Version | BoundaryVersion | None 

179 

180 

181@functools.total_ordering 

182class LowerBound: 

183 """Lower bound of a version range. 

184 

185 A version *v* of ``None`` means unbounded below (-inf), with 

186 ``inclusive`` forced to ``False``. 

187 At equal versions, ``[v`` sorts before ``(v`` because an inclusive 

188 bound starts earlier. 

189 """ 

190 

191 __slots__ = ("_above", "inclusive", "version") 

192 

193 def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None: 

194 # Two spellings of -inf would sort above each other under total_ordering. 

195 if version is None: 

196 inclusive = False 

197 

198 self.version = version 

199 self.inclusive = inclusive 

200 # Pre-bind a predicate "is parsed at or above this lower 

201 # bound?" for the hot filter / contains loops. One direct 

202 # call per check, no operator-dispatch chain. 

203 if version is None: 

204 self._above: Callable[[Version], bool] | None = None 

205 elif isinstance(version, BoundaryVersion): 

206 # >V produces an AFTER_POSTS lower bound; the upper-side 

207 # range of !=V produces an AFTER_LOCALS lower bound. 

208 if version.kind == BoundaryKind.AFTER_POSTS: 

209 self._above = _make_above_after_posts(version.version) 

210 else: 

211 self._above = _make_above_after_locals(version.version) 

212 elif inclusive: 

213 self._above = version.__le__ 

214 else: 

215 self._above = version.__lt__ 

216 

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

218 if not isinstance(other, LowerBound): 

219 return NotImplemented 

220 return self.version == other.version and self.inclusive == other.inclusive 

221 

222 def __lt__(self, other: LowerBound) -> bool: 

223 if not isinstance(other, LowerBound): 

224 return NotImplemented 

225 # -inf < anything (except -inf itself). 

226 if self.version is None: 

227 return other.version is not None 

228 if other.version is None: 

229 return False 

230 

231 # Use cached keys when both exist; cold versions and boundaries use operators. 

232 self_key = self.version._key_cache 

233 other_key = other.version._key_cache 

234 if self_key is None or other_key is None: 

235 if self.version != other.version: 

236 return self.version < other.version 

237 elif self_key != other_key: 

238 return self_key < other_key 

239 

240 # [v < (v: inclusive starts earlier. 

241 return self.inclusive and not other.inclusive 

242 

243 # Explicit operators avoid total_ordering's repeated comparison dispatch. 

244 def __gt__(self, other: LowerBound) -> bool: 

245 if not isinstance(other, LowerBound): 

246 return NotImplemented 

247 if self.version is None: 

248 return False 

249 if other.version is None: 

250 return True 

251 

252 self_key = self.version._key_cache 

253 other_key = other.version._key_cache 

254 if self_key is None or other_key is None: 

255 if self.version != other.version: 

256 return other.version < self.version 

257 elif self_key != other_key: 

258 return other_key < self_key 

259 

260 return other.inclusive and not self.inclusive 

261 

262 def __le__(self, other: LowerBound) -> bool: 

263 if not isinstance(other, LowerBound): 

264 return NotImplemented 

265 if self.version is None: 

266 return True 

267 if other.version is None: 

268 return False 

269 

270 self_key = self.version._key_cache 

271 other_key = other.version._key_cache 

272 if self_key is None or other_key is None: 

273 if self.version != other.version: 

274 return self.version < other.version 

275 elif self_key != other_key: 

276 return self_key < other_key 

277 

278 return self.inclusive or not other.inclusive 

279 

280 def __hash__(self) -> int: 

281 return hash((self.version, self.inclusive)) 

282 

283 def __repr__(self) -> str: 

284 bracket = "[" if self.inclusive else "(" 

285 return f"<{self.__class__.__name__} {bracket}{self.version!r}>" 

286 

287 

288@functools.total_ordering 

289class UpperBound: 

290 """Upper bound of a version range. 

291 

292 A version *v* of ``None`` means unbounded above (+inf), with 

293 ``inclusive`` forced to ``False``. 

294 At equal versions, ``v)`` sorts before ``v]`` because an exclusive 

295 bound ends earlier. 

296 """ 

297 

298 __slots__ = ("_below", "inclusive", "version") 

299 

300 def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None: 

301 # Two spellings of +inf would sort above each other under total_ordering. 

302 if version is None: 

303 inclusive = False 

304 

305 self.version = version 

306 self.inclusive = inclusive 

307 # Pre-bind a predicate "is parsed at or below this upper 

308 # bound?". See LowerBound for the rationale. 

309 if version is None: 

310 self._below: Callable[[Version], bool] | None = None 

311 elif isinstance(version, BoundaryVersion): 

312 # Standard specifiers only ever produce AFTER_LOCALS upper 

313 # bounds (from <=V / ==V / !=V with no local). 

314 if version.kind == BoundaryKind.AFTER_LOCALS: 

315 self._below = _make_below_after_locals(version.version) 

316 else: 

317 # An AFTER_POSTS upper is not produced by any specifier, but 

318 # range algebra reaches it: complementing ``>V`` flips the 

319 # ``AFTER_POSTS(V)`` lower into this upper bound. 

320 self._below = version.__ge__ 

321 elif inclusive: 

322 self._below = version.__ge__ 

323 else: 

324 self._below = version.__gt__ 

325 

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

327 if not isinstance(other, UpperBound): 

328 return NotImplemented 

329 return self.version == other.version and self.inclusive == other.inclusive 

330 

331 def __lt__(self, other: UpperBound) -> bool: 

332 if not isinstance(other, UpperBound): 

333 return NotImplemented 

334 # An unbounded upper endpoint does not sort before any endpoint. 

335 if self.version is None: 

336 return False 

337 if other.version is None: 

338 return True 

339 

340 # Use cached keys when both exist; cold versions and boundaries use operators. 

341 self_key = self.version._key_cache 

342 other_key = other.version._key_cache 

343 if self_key is None or other_key is None: 

344 if self.version != other.version: 

345 return self.version < other.version 

346 elif self_key != other_key: 

347 return self_key < other_key 

348 

349 # v) < v]: exclusive ends earlier. 

350 return not self.inclusive and other.inclusive 

351 

352 def __gt__(self, other: UpperBound) -> bool: 

353 if not isinstance(other, UpperBound): 

354 return NotImplemented 

355 if self.version is None: 

356 return other.version is not None 

357 if other.version is None: 

358 return False 

359 

360 self_key = self.version._key_cache 

361 other_key = other.version._key_cache 

362 if self_key is None or other_key is None: 

363 if self.version != other.version: 

364 return other.version < self.version 

365 elif self_key != other_key: 

366 return other_key < self_key 

367 

368 return self.inclusive and not other.inclusive 

369 

370 def __hash__(self) -> int: 

371 return hash((self.version, self.inclusive)) 

372 

373 def __repr__(self) -> str: 

374 bracket = "]" if self.inclusive else ")" 

375 return f"<{self.__class__.__name__} {self.version!r}{bracket}>" 

376 

377 

378if TYPE_CHECKING: 

379 #: A single contiguous interval as a (lower, upper) bound pair. 

380 Interval = tuple[LowerBound, UpperBound] 

381 

382 

383NEG_INF: Final[LowerBound] = LowerBound(None, False) 

384POS_INF: Final[UpperBound] = UpperBound(None, False) 

385FULL_RANGE: Final[tuple[Interval]] = ((NEG_INF, POS_INF),) 

386 

387 

388def trim_release(release: tuple[int, ...]) -> tuple[int, ...]: 

389 """Strip trailing zeros from a release tuple for normalized comparison.""" 

390 end = len(release) 

391 while end > 1 and release[end - 1] == 0: 

392 end -= 1 

393 return release if end == len(release) else release[:end] 

394 

395 

396def _next_prefix_dev0(version: Version) -> Version: 

397 """Smallest version in the next prefix: 1.2 -> 1.3.dev0.""" 

398 release = (*version.release[:-1], version.release[-1] + 1) 

399 return Version.from_parts(epoch=version.epoch, release=release, dev=0) 

400 

401 

402def _base_dev0(version: Version) -> Version: 

403 """The .dev0 of a version's base release: 1.2 -> 1.2.dev0.""" 

404 return Version.from_parts(epoch=version.epoch, release=version.release, dev=0) 

405 

406 

407def coerce_version(version: Version | str) -> Version | None: 

408 if not isinstance(version, Version): 

409 try: 

410 version = Version(version) 

411 except InvalidVersion: 

412 return None 

413 return version 

414 

415 

416def _make_above_after_posts(version: Version) -> Callable[[Version], bool]: 

417 """Predicate ``parsed > AFTER_POSTS(V)`` for a lower bound. 

418 

419 Per PEP 440, ``>V`` excludes V's post-releases unless V is itself 

420 a post-release. AFTER_POSTS sits above V and every V.postN (with 

421 or without local), and just below the next release. 

422 """ 

423 version_ge = version.__ge__ 

424 version_epoch = version.epoch 

425 version_pre = version.pre 

426 version_release_trimmed = trim_release(version.release) 

427 trimmed_length = len(version_release_trimmed) 

428 

429 def above(parsed: Version) -> bool: 

430 if version_ge(parsed): 

431 return False 

432 # parsed > V cmpkey-wise: above the boundary iff NOT in V's 

433 # post family. 

434 if parsed.epoch != version_epoch: 

435 return True 

436 parsed_release = parsed.release 

437 if len(parsed_release) < trimmed_length: 

438 return True 

439 if parsed_release[:trimmed_length] != version_release_trimmed: 

440 return True 

441 for i in range(trimmed_length, len(parsed_release)): 

442 if parsed_release[i] != 0: 

443 return True 

444 if parsed.pre != version_pre: 

445 return True 

446 

447 # Same release and pre as V: parsed is in V's post family (V itself, 

448 # V+local, or V.postN), which the boundary sits above. A V.devN 

449 # (different dev, no post) sorts before V and was already caught by 

450 # ``version_ge`` above, so the answer here is always "not above". 

451 return False 

452 

453 return above 

454 

455 

456def _make_above_after_locals(version: Version) -> Callable[[Version], bool]: 

457 """Predicate ``parsed > AFTER_LOCALS(V)`` for a lower bound. 

458 

459 Used by the upper-side range of ``!=V`` (when V has no local 

460 segment). AFTER_LOCALS sits above V and every ``V+local`` but 

461 just below ``V.post0``. 

462 """ 

463 version_ge = version.__ge__ 

464 version_epoch = version.epoch 

465 version_pre = version.pre 

466 version_post = version.post 

467 version_dev = version.dev 

468 version_release_trimmed = trim_release(version.release) 

469 trimmed_length = len(version_release_trimmed) 

470 

471 def above(parsed: Version) -> bool: 

472 if version_ge(parsed): 

473 return False 

474 # parsed > V cmpkey-wise: above the boundary iff NOT in V's 

475 # local family (same public version, any local segment). 

476 if parsed.epoch != version_epoch: 

477 return True 

478 parsed_release = parsed.release 

479 if len(parsed_release) < trimmed_length: 

480 return True 

481 if parsed_release[:trimmed_length] != version_release_trimmed: 

482 return True 

483 for i in range(trimmed_length, len(parsed_release)): 

484 if parsed_release[i] != 0: 

485 return True 

486 if parsed.pre != version_pre: 

487 return True 

488 if parsed.post != version_post: 

489 return True 

490 return parsed.dev != version_dev 

491 

492 return above 

493 

494 

495def _make_below_after_locals(version: Version) -> Callable[[Version], bool]: 

496 """Predicate ``parsed <= AFTER_LOCALS(V)`` for an upper bound. 

497 

498 Used by ``<=V``, ``==V``, ``!=V`` (no local). ``parsed`` is at or 

499 below the boundary when it is at or below V cmpkey-wise, or when 

500 it is in V's local family. 

501 """ 

502 version_ge = version.__ge__ 

503 version_epoch = version.epoch 

504 version_pre = version.pre 

505 version_post = version.post 

506 version_dev = version.dev 

507 version_release_trimmed = trim_release(version.release) 

508 trimmed_length = len(version_release_trimmed) 

509 

510 def below(parsed: Version) -> bool: 

511 if version_ge(parsed): 

512 return True 

513 # parsed > V cmpkey-wise: below the boundary iff in V's local 

514 # family. 

515 if parsed.epoch != version_epoch: 

516 return False 

517 parsed_release = parsed.release 

518 if len(parsed_release) < trimmed_length: 

519 return False 

520 if parsed_release[:trimmed_length] != version_release_trimmed: 

521 return False 

522 for i in range(trimmed_length, len(parsed_release)): 

523 if parsed_release[i] != 0: 

524 return False 

525 if parsed.pre != version_pre: 

526 return False 

527 if parsed.post != version_post: 

528 return False 

529 return parsed.dev == version_dev 

530 

531 return below 

532 

533 

534def least_version_above(boundary: BoundaryVersion) -> Version | None: 

535 """Smallest real version strictly above *boundary*, or ``None`` if none exists.""" 

536 base = boundary.version 

537 

538 if boundary.kind == BoundaryKind.AFTER_LOCALS: 

539 # AFTER_LOCALS(V) sits just below V.post0, so its least successor is 

540 # V.post0.dev0 (V.dev(N+1) if V has a dev, V.post(N+1).dev0 if a post). 

541 if base.dev is not None: 

542 return base.__replace__(dev=base.dev + 1, local=None) 

543 next_post = (base.post + 1) if base.post is not None else 0 

544 return base.__replace__(post=next_post, dev=0, local=None) 

545 

546 # AFTER_POSTS(V): a pre-release V steps to the next pre-release's .dev0; 

547 # a final-release AFTER_POSTS has no least successor. 

548 if base.pre is not None: 

549 kind, number = base.pre 

550 return base.__replace__(pre=(kind, number + 1), post=None, dev=0, local=None) 

551 

552 return None 

553 

554 

555def range_is_empty(lower: LowerBound, upper: UpperBound) -> bool: 

556 """True when the range defined by *lower* and *upper* contains no versions. 

557 

558 A boundary lower sits just below the next real version, so an ordered pair 

559 is still empty when the upper excludes that least successor: 

560 ``(AFTER_POSTS(1.0a1), 1.0a2.dev0)`` holds no version. 

561 """ 

562 if upper.version is None: 

563 return False 

564 

565 if lower.version is None: 

566 # Nothing sorts below MIN_VERSION, so an exclusive upper at or below it 

567 # leaves an empty floor interval such as ``(-inf, 0.dev0)``. 

568 return ( 

569 not upper.inclusive 

570 and isinstance(upper.version, Version) 

571 and upper.version <= MIN_VERSION 

572 ) 

573 

574 if isinstance(lower.version, BoundaryVersion): 

575 successor = least_version_above(lower.version) 

576 if successor is not None: 

577 if upper.version == successor: 

578 return not upper.inclusive 

579 return upper.version < successor 

580 

581 if lower.version == upper.version: 

582 return not (lower.inclusive and upper.inclusive) 

583 

584 return lower.version > upper.version 

585 

586 

587def intersect_ranges( 

588 left: Sequence[Interval], 

589 right: Sequence[Interval], 

590) -> list[Interval]: 

591 """Intersect two sorted, non-overlapping range lists (two-pointer merge).""" 

592 result: list[Interval] = [] 

593 left_index = right_index = 0 

594 while left_index < len(left) and right_index < len(right): 

595 left_lower, left_upper = left[left_index] 

596 right_lower, right_upper = right[right_index] 

597 

598 lower = max(left_lower, right_lower) 

599 upper = min(left_upper, right_upper) 

600 

601 if not range_is_empty(lower, upper): 

602 result.append((lower, upper)) 

603 

604 # Advance whichever side has the smaller upper bound. 

605 if left_upper < right_upper: 

606 left_index += 1 

607 else: 

608 right_index += 1 

609 

610 return result 

611 

612 

613def ranges_are_disjoint(left: Sequence[Interval], right: Sequence[Interval]) -> bool: 

614 """Test whether two sorted, non-overlapping interval lists are disjoint. 

615 

616 Each interval must contain at least one version; empty input lists are allowed. 

617 """ 

618 left_index = right_index = 0 

619 while left_index < len(left) and right_index < len(right): 

620 left_lower, left_upper = left[left_index] 

621 right_lower, right_upper = right[right_index] 

622 

623 if range_is_empty(left_lower, right_upper): 

624 right_index += 1 

625 elif range_is_empty(right_lower, left_upper): 

626 left_index += 1 

627 else: 

628 return False 

629 

630 return True 

631 

632 

633def filter_by_ranges( 

634 ranges: Sequence[Interval], 

635 iterable: Iterable[Any], 

636 key: Callable[[Any], Version | str] | None, 

637 prereleases: bool | None, 

638 region: Sequence[Interval] = (), 

639) -> Iterator[Any]: 

640 """Filter *iterable* against precomputed version *ranges*. 

641 

642 With ``prereleases=None``, the PEP 440 default applies: pre-releases are 

643 excluded unless no final matches, in which case buffered pre-releases come 

644 out at the end. A pre-release inside the opt-in ``region`` is the exception: 

645 it is force-admitted in place, as ``prereleases=True`` would yield it. A 

646 force-admitted pre-release is not a final, so it never suppresses the buffer. 

647 """ 

648 if prereleases is None: 

649 prerelease_buffer: list[Any] = [] 

650 found_final = False 

651 

652 if len(ranges) == 1: 

653 # Hot path: most specifiers and small SpecifierSets reduce to 

654 # a single contiguous range. 

655 lower, upper = ranges[0] 

656 above = lower._above 

657 below = upper._below 

658 for item in iterable: 

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

660 if parsed is None: 

661 continue 

662 if above is not None and not above(parsed): 

663 continue 

664 if below is not None and not below(parsed): 

665 continue 

666 if not parsed.is_prerelease: 

667 found_final = True 

668 yield item 

669 elif region and matches_bounds_only(region, parsed): 

670 yield item 

671 elif not found_final: 

672 prerelease_buffer.append(item) 

673 if not found_final: 

674 yield from prerelease_buffer 

675 return 

676 

677 for item in iterable: 

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

679 if parsed is None: 

680 continue 

681 for lower, upper in ranges: 

682 above = lower._above 

683 if above is not None and not above(parsed): 

684 break 

685 below = upper._below 

686 if below is None or below(parsed): 

687 if not parsed.is_prerelease: 

688 found_final = True 

689 yield item 

690 elif region and matches_bounds_only(region, parsed): 

691 yield item 

692 elif not found_final: 

693 prerelease_buffer.append(item) 

694 break 

695 if not found_final: 

696 yield from prerelease_buffer 

697 return 

698 

699 exclude_prereleases = prereleases is False 

700 

701 if len(ranges) == 1: 

702 # Hot path: most specifiers and small SpecifierSets reduce to 

703 # a single contiguous range. 

704 lower, upper = ranges[0] 

705 above = lower._above 

706 below = upper._below 

707 for item in iterable: 

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

709 if parsed is None: 

710 continue 

711 if exclude_prereleases and parsed.is_prerelease: 

712 continue 

713 if above is not None and not above(parsed): 

714 continue 

715 if below is None or below(parsed): 

716 yield item 

717 return 

718 

719 for item in iterable: 

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

721 if parsed is None: 

722 continue 

723 if exclude_prereleases and parsed.is_prerelease: 

724 continue 

725 for lower, upper in ranges: 

726 above = lower._above 

727 if above is not None and not above(parsed): 

728 break 

729 below = upper._below 

730 if below is None or below(parsed): 

731 yield item 

732 break 

733 

734 

735def _nearest_release_above_prerelease(version: Version) -> Version: 

736 """Smallest non-pre-release at or above a pre-release *version*.""" 

737 if version.pre is not None: 

738 # An a/b/rc pre-release drops to its final release, which outranks 

739 # every post-release of that pre-release (1.0a1.post0 -> 1.0). 

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

741 

742 # A dev-only release keeps its post-release (1.0.post0.dev0 -> 1.0.post0, 

743 # whose final 1.0 sorts below it). 

744 return version.__replace__(dev=None, local=None) 

745 

746 

747def _lowest_release_at_or_above(value: Version | BoundaryVersion | None) -> Version: 

748 """Smallest non-pre-release version at or above *value*. 

749 

750 ``None`` is the ``-inf`` floor, whose nearest non-pre-release is 

751 :data:`MIN_RELEASE`. 

752 """ 

753 if value is None: 

754 return MIN_RELEASE 

755 if isinstance(value, BoundaryVersion): 

756 inner_version = value.version 

757 if inner_version.is_prerelease: 

758 return _nearest_release_above_prerelease(inner_version) 

759 # AFTER_LOCALS(1.0) -> nearest non-pre is 1.0.post0 

760 # AFTER_LOCALS(1.0.post0) -> nearest non-pre is 1.0.post1 

761 next_post = (inner_version.post + 1) if inner_version.post is not None else 0 

762 return inner_version.__replace__(post=next_post, local=None) 

763 

764 if not value.is_prerelease: 

765 return value 

766 

767 return _nearest_release_above_prerelease(value) 

768 

769 

770def ranges_are_prerelease_only(ranges: Sequence[Interval]) -> bool: 

771 """True when every range in *ranges* contains only pre-releases. 

772 

773 Used to detect unsatisfiable specifier sets when ``prereleases=False``: 

774 if every range is pre-release-only, every contained version is excluded. 

775 """ 

776 for lower, upper in ranges: 

777 nearest = _lowest_release_at_or_above(lower.version) 

778 if upper.version is None or nearest < upper.version: 

779 return False 

780 if nearest == upper.version and upper.inclusive: 

781 return False 

782 return True 

783 

784 

785def wildcard_ranges(op: str, base: Version) -> list[Interval]: 

786 """Ranges for ==V.* and !=V.*. 

787 

788 ==1.2.* -> [1.2.dev0, 1.3.dev0); !=1.2.* -> complement. 

789 """ 

790 lower = _base_dev0(base) 

791 upper = _next_prefix_dev0(base) 

792 if op == "==": 

793 return [(LowerBound(lower, True), UpperBound(upper, False))] 

794 # != 

795 return [ 

796 (NEG_INF, UpperBound(lower, False)), 

797 (LowerBound(upper, True), POS_INF), 

798 ] 

799 

800 

801def standard_ranges(op: str, version: Version, has_local: bool) -> list[Interval]: 

802 """Ranges for the standard PEP 440 operators (no wildcard, no ===). 

803 

804 *has_local* indicates whether the spec string included a ``+local`` 

805 segment; relevant only for ``==`` / ``!=`` to decide whether the 

806 upper bound includes V's local family. 

807 """ 

808 if op == ">=": 

809 return [(LowerBound(version, True), POS_INF)] 

810 

811 if op == "<=": 

812 return [ 

813 ( 

814 NEG_INF, 

815 UpperBound(BoundaryVersion(version, BoundaryKind.AFTER_LOCALS), True), 

816 ) 

817 ] 

818 

819 if op == ">": 

820 if version.dev is not None: 

821 # >V.devN: dev versions have no post-releases, so the 

822 # next real version is V.dev(N+1). 

823 lower_bound = version.__replace__(dev=version.dev + 1, local=None) 

824 return [(LowerBound(lower_bound, True), POS_INF)] 

825 if version.post is not None: 

826 # >V.postN: next real version is V.post(N+1).dev0. 

827 lower_bound = version.__replace__(post=version.post + 1, dev=0, local=None) 

828 return [(LowerBound(lower_bound, True), POS_INF)] 

829 # >V (final or pre-release V): exclude V itself, V+local, and 

830 # every V.postN per PEP 440. 

831 return [ 

832 ( 

833 LowerBound(BoundaryVersion(version, BoundaryKind.AFTER_POSTS), False), 

834 POS_INF, 

835 ) 

836 ] 

837 

838 if op == "<": 

839 # <V excludes pre-releases of V when V is not a pre-release. 

840 # V.dev0 is the earliest pre-release of V. 

841 bound = ( 

842 version if version.is_prerelease else version.__replace__(dev=0, local=None) 

843 ) 

844 if bound <= MIN_VERSION: 

845 return [] 

846 return [(NEG_INF, UpperBound(bound, False))] 

847 

848 # ==, !=: local versions of V match when the spec has no local segment. 

849 after_locals = BoundaryVersion(version, BoundaryKind.AFTER_LOCALS) 

850 upper = version if has_local else after_locals 

851 

852 if op == "==": 

853 return [(LowerBound(version, True), UpperBound(upper, True))] 

854 

855 if op == "!=": 

856 return [ 

857 (NEG_INF, UpperBound(version, False)), 

858 (LowerBound(upper, False), POS_INF), 

859 ] 

860 

861 if op == "~=": 

862 prefix = version.__replace__(release=version.release[:-1]) 

863 return [ 

864 (LowerBound(version, True), UpperBound(_next_prefix_dev0(prefix), False)) 

865 ] 

866 

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

868 

869 

870def bounds_for_spec(op: str, version_str: str, version: Version) -> list[Interval]: 

871 """Ranges for one specifier's ``(op, version_str)``. 

872 

873 Dispatches between the wildcard and standard builders. ``version`` is the 

874 parsed ``version_str`` (its base, without the trailing ``.*``, for 

875 wildcards). ``===`` is not handled here; its match is a literal string 

876 compared in :mod:`packaging.specifiers`. 

877 """ 

878 if version_str.endswith(".*"): 

879 return wildcard_ranges(op, version) 

880 

881 return standard_ranges(op, version, "+" in version_str) 

882 

883 

884def intersect_specifier_bounds( 

885 per_specifier_ranges: Iterable[Sequence[Interval]], 

886) -> Sequence[Interval]: 

887 """Intersect each specifier's ranges into a single sequence. 

888 

889 Short-circuits once the running intersection is empty, since no later 

890 specifier can revive it. Callers must pass at least one specifier. 

891 """ 

892 result: Sequence[Interval] | None = None 

893 for sub in per_specifier_ranges: 

894 if result is None: 

895 result = sub 

896 else: 

897 result = intersect_ranges(result, sub) 

898 if not result: 

899 break 

900 

901 if result is None: # pragma: no cover - callers guard non-empty input 

902 raise RuntimeError("intersect_specifier_bounds called with no specifiers") 

903 

904 return result 

905 

906 

907def matches_bounds_only(ranges: Sequence[Interval], version: Version) -> bool: 

908 """Whether ``version`` falls within any of ``ranges``. 

909 

910 The pure bounds membership test, for a single already-parsed version with 

911 no pre-release policy applied. ``ranges`` are sorted and non-overlapping, 

912 so a version below one range's lower bound is below every later range too. 

913 """ 

914 for lower, upper in ranges: 

915 above = lower._above 

916 if above is not None and not above(version): 

917 return False 

918 

919 below = upper._below 

920 if below is None or below(version): 

921 return True 

922 

923 return False 

924 

925 

926def resolve_prereleases( 

927 configured: bool | None, autodetected: bool | None 

928) -> bool | None: 

929 """Resolve a specifier's effective default pre-release policy. 

930 

931 An explicit ``configured`` value wins; otherwise an autodetected ``True`` 

932 propagates and anything else falls back to the PEP 440 default (``None``). 

933 """ 

934 if configured is not None: 

935 return configured 

936 

937 if autodetected: 

938 return True 

939 

940 return None