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

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

447 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_prerelease_only", 

37 "resolve_prereleases", 

38 "standard_ranges", 

39 "wildcard_ranges", 

40] 

41 

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

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

44 

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

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

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

48 

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

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

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

52 

53 

54class BoundaryKind(enum.Enum): 

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

56 

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

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

59 

60 

61@functools.total_ordering 

62class BoundaryVersion: 

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

64 

65 Relative to a base version V:: 

66 

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

68 

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

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

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

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

73 """ 

74 

75 __slots__ = ( 

76 "_cached_dev", 

77 "_cached_epoch", 

78 "_cached_post", 

79 "_cached_pre", 

80 "_cached_trimmed_release", 

81 "kind", 

82 "version", 

83 ) 

84 

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

86 self.version = version 

87 self.kind = kind 

88 self._cached_trimmed_release = trim_release(version.release) 

89 self._cached_epoch = version.epoch 

90 self._cached_pre = version.pre 

91 self._cached_post = version.post 

92 self._cached_dev = version.dev 

93 

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

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

96 if other.epoch != self._cached_epoch: 

97 return False 

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

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

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

101 other_release = other.release 

102 trimmed_release = self._cached_trimmed_release 

103 trimmed_length = len(trimmed_release) 

104 if len(other_release) < trimmed_length: 

105 return False 

106 if other_release[:trimmed_length] != trimmed_release: 

107 return False 

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

109 if other_release[i] != 0: 

110 return False 

111 if other.pre != self._cached_pre: 

112 return False 

113 if self.kind == BoundaryKind.AFTER_LOCALS: 

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

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

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

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

118 

119 def _order_key(self) -> _BoundaryOrderKey: 

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

121 

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

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

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

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

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

127 

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

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

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

131 """ 

132 version_key = self.version._key 

133 suffix: _BoundaryOrderSuffix = version_key[2] 

134 

135 if self.kind == BoundaryKind.AFTER_POSTS: 

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

137 

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

139 

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

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

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

143 if isinstance(other, BoundaryVersion): 

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

145 return NotImplemented 

146 

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

148 if isinstance(other, BoundaryVersion): 

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

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

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

152 if not (self.version < other): 

153 return False 

154 return not self._is_family(other) 

155 

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

157 # Defined directly to bypass functools.total_ordering's 

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

159 if isinstance(other, BoundaryVersion): 

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

161 if self.version >= other: 

162 return True 

163 return self._is_family(other) 

164 

165 def __hash__(self) -> int: 

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

167 return hash(self._order_key()) 

168 

169 def __repr__(self) -> str: 

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

171 

172 

173if TYPE_CHECKING: 

174 _VersionOrBoundary = Version | BoundaryVersion | None 

175 

176 

177@functools.total_ordering 

178class LowerBound: 

179 """Lower bound of a version range. 

180 

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

182 ``inclusive`` forced to ``False``. 

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

184 bound starts earlier. 

185 """ 

186 

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

188 

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

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

191 if version is None: 

192 inclusive = False 

193 

194 self.version = version 

195 self.inclusive = inclusive 

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

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

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

199 if version is None: 

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

201 elif isinstance(version, BoundaryVersion): 

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

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

204 if version.kind == BoundaryKind.AFTER_POSTS: 

205 self._above = _make_above_after_posts(version.version) 

206 else: 

207 self._above = _make_above_after_locals(version.version) 

208 elif inclusive: 

209 self._above = version.__le__ 

210 else: 

211 self._above = version.__lt__ 

212 

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

214 if not isinstance(other, LowerBound): 

215 return NotImplemented 

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

217 

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

219 if not isinstance(other, LowerBound): 

220 return NotImplemented 

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

222 if self.version is None: 

223 return other.version is not None 

224 if other.version is None: 

225 return False 

226 if self.version != other.version: 

227 return self.version < other.version 

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

229 return self.inclusive and not other.inclusive 

230 

231 def __hash__(self) -> int: 

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

233 

234 def __repr__(self) -> str: 

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

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

237 

238 

239@functools.total_ordering 

240class UpperBound: 

241 """Upper bound of a version range. 

242 

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

244 ``inclusive`` forced to ``False``. 

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

246 bound ends earlier. 

247 """ 

248 

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

250 

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

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

253 if version is None: 

254 inclusive = False 

255 

256 self.version = version 

257 self.inclusive = inclusive 

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

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

260 if version is None: 

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

262 elif isinstance(version, BoundaryVersion): 

263 # Standard specifiers only ever produce AFTER_LOCALS upper 

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

265 if version.kind == BoundaryKind.AFTER_LOCALS: 

266 self._below = _make_below_after_locals(version.version) 

267 else: 

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

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

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

271 self._below = version.__ge__ 

272 elif inclusive: 

273 self._below = version.__ge__ 

274 else: 

275 self._below = version.__gt__ 

276 

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

278 if not isinstance(other, UpperBound): 

279 return NotImplemented 

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

281 

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

283 if not isinstance(other, UpperBound): 

284 return NotImplemented 

285 # Nothing < +inf (except +inf itself). 

286 if self.version is None: 

287 return False 

288 if other.version is None: 

289 return True 

290 if self.version != other.version: 

291 return self.version < other.version 

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

293 return not self.inclusive and other.inclusive 

294 

295 def __hash__(self) -> int: 

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

297 

298 def __repr__(self) -> str: 

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

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

301 

302 

303if TYPE_CHECKING: 

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

305 Interval = tuple[LowerBound, UpperBound] 

306 

307 

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

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

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

311 

312 

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

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

315 end = len(release) 

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

317 end -= 1 

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

319 

320 

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

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

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

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

325 

326 

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

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

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

330 

331 

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

333 if not isinstance(version, Version): 

334 try: 

335 version = Version(version) 

336 except InvalidVersion: 

337 return None 

338 return version 

339 

340 

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

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

343 

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

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

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

347 """ 

348 version_ge = version.__ge__ 

349 version_epoch = version.epoch 

350 version_pre = version.pre 

351 version_release_trimmed = trim_release(version.release) 

352 trimmed_length = len(version_release_trimmed) 

353 

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

355 if version_ge(parsed): 

356 return False 

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

358 # post family. 

359 if parsed.epoch != version_epoch: 

360 return True 

361 parsed_release = parsed.release 

362 if len(parsed_release) < trimmed_length: 

363 return True 

364 if parsed_release[:trimmed_length] != version_release_trimmed: 

365 return True 

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

367 if parsed_release[i] != 0: 

368 return True 

369 if parsed.pre != version_pre: 

370 return True 

371 

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

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

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

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

376 return False 

377 

378 return above 

379 

380 

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

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

383 

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

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

386 just below ``V.post0``. 

387 """ 

388 version_ge = version.__ge__ 

389 version_epoch = version.epoch 

390 version_pre = version.pre 

391 version_post = version.post 

392 version_dev = version.dev 

393 version_release_trimmed = trim_release(version.release) 

394 trimmed_length = len(version_release_trimmed) 

395 

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

397 if version_ge(parsed): 

398 return False 

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

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

401 if parsed.epoch != version_epoch: 

402 return True 

403 parsed_release = parsed.release 

404 if len(parsed_release) < trimmed_length: 

405 return True 

406 if parsed_release[:trimmed_length] != version_release_trimmed: 

407 return True 

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

409 if parsed_release[i] != 0: 

410 return True 

411 if parsed.pre != version_pre: 

412 return True 

413 if parsed.post != version_post: 

414 return True 

415 return parsed.dev != version_dev 

416 

417 return above 

418 

419 

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

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

422 

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

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

425 it is in V's local family. 

426 """ 

427 version_ge = version.__ge__ 

428 version_epoch = version.epoch 

429 version_pre = version.pre 

430 version_post = version.post 

431 version_dev = version.dev 

432 version_release_trimmed = trim_release(version.release) 

433 trimmed_length = len(version_release_trimmed) 

434 

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

436 if version_ge(parsed): 

437 return True 

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

439 # family. 

440 if parsed.epoch != version_epoch: 

441 return False 

442 parsed_release = parsed.release 

443 if len(parsed_release) < trimmed_length: 

444 return False 

445 if parsed_release[:trimmed_length] != version_release_trimmed: 

446 return False 

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

448 if parsed_release[i] != 0: 

449 return False 

450 if parsed.pre != version_pre: 

451 return False 

452 if parsed.post != version_post: 

453 return False 

454 return parsed.dev == version_dev 

455 

456 return below 

457 

458 

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

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

461 base = boundary.version 

462 

463 if boundary.kind == BoundaryKind.AFTER_LOCALS: 

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

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

466 if base.dev is not None: 

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

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

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

470 

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

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

473 if base.pre is not None: 

474 kind, number = base.pre 

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

476 

477 return None 

478 

479 

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

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

482 

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

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

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

486 """ 

487 if upper.version is None: 

488 return False 

489 

490 if lower.version is None: 

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

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

493 return ( 

494 not upper.inclusive 

495 and isinstance(upper.version, Version) 

496 and upper.version <= MIN_VERSION 

497 ) 

498 

499 if isinstance(lower.version, BoundaryVersion): 

500 successor = least_version_above(lower.version) 

501 if successor is not None: 

502 if upper.version == successor: 

503 return not upper.inclusive 

504 return upper.version < successor 

505 

506 if lower.version == upper.version: 

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

508 

509 return lower.version > upper.version 

510 

511 

512def intersect_ranges( 

513 left: Sequence[Interval], 

514 right: Sequence[Interval], 

515) -> list[Interval]: 

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

517 result: list[Interval] = [] 

518 left_index = right_index = 0 

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

520 left_lower, left_upper = left[left_index] 

521 right_lower, right_upper = right[right_index] 

522 

523 lower = max(left_lower, right_lower) 

524 upper = min(left_upper, right_upper) 

525 

526 if not range_is_empty(lower, upper): 

527 result.append((lower, upper)) 

528 

529 # Advance whichever side has the smaller upper bound. 

530 if left_upper < right_upper: 

531 left_index += 1 

532 else: 

533 right_index += 1 

534 

535 return result 

536 

537 

538def filter_by_ranges( 

539 ranges: Sequence[Interval], 

540 iterable: Iterable[Any], 

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

542 prereleases: bool | None, 

543 region: Sequence[Interval] = (), 

544) -> Iterator[Any]: 

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

546 

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

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

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

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

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

552 """ 

553 if prereleases is None: 

554 prerelease_buffer: list[Any] = [] 

555 found_final = False 

556 

557 if len(ranges) == 1: 

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

559 # a single contiguous range. 

560 lower, upper = ranges[0] 

561 above = lower._above 

562 below = upper._below 

563 for item in iterable: 

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

565 if parsed is None: 

566 continue 

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

568 continue 

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

570 continue 

571 if not parsed.is_prerelease: 

572 found_final = True 

573 yield item 

574 elif region and matches_bounds_only(region, parsed): 

575 yield item 

576 elif not found_final: 

577 prerelease_buffer.append(item) 

578 if not found_final: 

579 yield from prerelease_buffer 

580 return 

581 

582 for item in iterable: 

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

584 if parsed is None: 

585 continue 

586 for lower, upper in ranges: 

587 above = lower._above 

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

589 break 

590 below = upper._below 

591 if below is None or below(parsed): 

592 if not parsed.is_prerelease: 

593 found_final = True 

594 yield item 

595 elif region and matches_bounds_only(region, parsed): 

596 yield item 

597 elif not found_final: 

598 prerelease_buffer.append(item) 

599 break 

600 if not found_final: 

601 yield from prerelease_buffer 

602 return 

603 

604 exclude_prereleases = prereleases is False 

605 

606 if len(ranges) == 1: 

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

608 # a single contiguous range. 

609 lower, upper = ranges[0] 

610 above = lower._above 

611 below = upper._below 

612 for item in iterable: 

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

614 if parsed is None: 

615 continue 

616 if exclude_prereleases and parsed.is_prerelease: 

617 continue 

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

619 continue 

620 if below is None or below(parsed): 

621 yield item 

622 return 

623 

624 for item in iterable: 

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

626 if parsed is None: 

627 continue 

628 if exclude_prereleases and parsed.is_prerelease: 

629 continue 

630 for lower, upper in ranges: 

631 above = lower._above 

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

633 break 

634 below = upper._below 

635 if below is None or below(parsed): 

636 yield item 

637 break 

638 

639 

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

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

642 if version.pre is not None: 

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

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

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

646 

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

648 # whose final 1.0 sorts below it). 

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

650 

651 

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

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

654 

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

656 :data:`MIN_RELEASE`. 

657 """ 

658 if value is None: 

659 return MIN_RELEASE 

660 if isinstance(value, BoundaryVersion): 

661 inner_version = value.version 

662 if inner_version.is_prerelease: 

663 return _nearest_release_above_prerelease(inner_version) 

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

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

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

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

668 

669 if not value.is_prerelease: 

670 return value 

671 

672 return _nearest_release_above_prerelease(value) 

673 

674 

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

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

677 

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

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

680 """ 

681 for lower, upper in ranges: 

682 nearest = _lowest_release_at_or_above(lower.version) 

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

684 return False 

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

686 return False 

687 return True 

688 

689 

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

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

692 

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

694 """ 

695 lower = _base_dev0(base) 

696 upper = _next_prefix_dev0(base) 

697 if op == "==": 

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

699 # != 

700 return [ 

701 (NEG_INF, UpperBound(lower, False)), 

702 (LowerBound(upper, True), POS_INF), 

703 ] 

704 

705 

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

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

708 

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

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

711 upper bound includes V's local family. 

712 """ 

713 if op == ">=": 

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

715 

716 if op == "<=": 

717 return [ 

718 ( 

719 NEG_INF, 

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

721 ) 

722 ] 

723 

724 if op == ">": 

725 if version.dev is not None: 

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

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

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

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

730 if version.post is not None: 

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

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

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

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

735 # every V.postN per PEP 440. 

736 return [ 

737 ( 

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

739 POS_INF, 

740 ) 

741 ] 

742 

743 if op == "<": 

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

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

746 bound = ( 

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

748 ) 

749 if bound <= MIN_VERSION: 

750 return [] 

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

752 

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

754 after_locals = BoundaryVersion(version, BoundaryKind.AFTER_LOCALS) 

755 upper = version if has_local else after_locals 

756 

757 if op == "==": 

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

759 

760 if op == "!=": 

761 return [ 

762 (NEG_INF, UpperBound(version, False)), 

763 (LowerBound(upper, False), POS_INF), 

764 ] 

765 

766 if op == "~=": 

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

768 return [ 

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

770 ] 

771 

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

773 

774 

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

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

777 

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

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

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

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

782 """ 

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

784 return wildcard_ranges(op, version) 

785 

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

787 

788 

789def intersect_specifier_bounds( 

790 per_specifier_ranges: Iterable[Sequence[Interval]], 

791) -> Sequence[Interval]: 

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

793 

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

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

796 """ 

797 result: Sequence[Interval] | None = None 

798 for sub in per_specifier_ranges: 

799 if result is None: 

800 result = sub 

801 else: 

802 result = intersect_ranges(result, sub) 

803 if not result: 

804 break 

805 

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

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

808 

809 return result 

810 

811 

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

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

814 

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

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

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

818 """ 

819 for lower, upper in ranges: 

820 above = lower._above 

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

822 return False 

823 

824 below = upper._below 

825 if below is None or below(version): 

826 return True 

827 

828 return False 

829 

830 

831def resolve_prereleases( 

832 configured: bool | None, autodetected: bool | None 

833) -> bool | None: 

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

835 

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

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

838 """ 

839 if configured is not None: 

840 return configured 

841 

842 if autodetected: 

843 return True 

844 

845 return None