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

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

440 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 from typing import Union 

21 

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

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

24 _BoundaryOrderSuffix = tuple[int, int, int, Union[int, float], int, int] 

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

26 

27__all__ = [ 

28 "FULL_RANGE", 

29 "bounds_for_spec", 

30 "coerce_version", 

31 "filter_by_ranges", 

32 "intersect_ranges", 

33 "intersect_specifier_bounds", 

34 "least_version_above", 

35 "matches_bounds_only", 

36 "range_is_empty", 

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 def __init__(self, version: Version, kind: BoundaryKind) -> None: 

87 self.version = version 

88 self.kind = kind 

89 self._cached_trimmed_release = trim_release(version.release) 

90 self._cached_epoch = version.epoch 

91 self._cached_pre = version.pre 

92 self._cached_post = version.post 

93 self._cached_dev = version.dev 

94 

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

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

97 if other.epoch != self._cached_epoch: 

98 return False 

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

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

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

102 other_release = other.release 

103 trimmed_release = self._cached_trimmed_release 

104 trimmed_length = len(trimmed_release) 

105 if len(other_release) < trimmed_length: 

106 return False 

107 if other_release[:trimmed_length] != trimmed_release: 

108 return False 

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

110 if other_release[i] != 0: 

111 return False 

112 if other.pre != self._cached_pre: 

113 return False 

114 if self.kind == BoundaryKind.AFTER_LOCALS: 

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

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

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

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

119 

120 def _order_key(self) -> _BoundaryOrderKey: 

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

122 

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

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

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

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

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

128 

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

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

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

132 """ 

133 version_key = self.version._key 

134 suffix: _BoundaryOrderSuffix = version_key[2] 

135 

136 if self.kind == BoundaryKind.AFTER_POSTS: 

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

138 

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

140 

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

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

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

144 if isinstance(other, BoundaryVersion): 

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

146 return NotImplemented 

147 

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

149 if isinstance(other, BoundaryVersion): 

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

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

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

153 if not (self.version < other): 

154 return False 

155 return not self._is_family(other) 

156 

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

158 # Defined directly to bypass functools.total_ordering's 

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

160 if isinstance(other, BoundaryVersion): 

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

162 if self.version >= other: 

163 return True 

164 return self._is_family(other) 

165 

166 def __hash__(self) -> int: 

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

168 return hash(self._order_key()) 

169 

170 def __repr__(self) -> str: 

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

172 

173 

174if TYPE_CHECKING: 

175 _VersionOrBoundary = Union[Version, BoundaryVersion, None] 

176 

177 

178@functools.total_ordering 

179class LowerBound: 

180 """Lower bound of a version range. 

181 

182 A version *v* of ``None`` means unbounded below (-inf). 

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 self.version = version 

191 self.inclusive = inclusive 

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

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

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

195 if version is None: 

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

197 elif isinstance(version, BoundaryVersion): 

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

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

200 if version.kind == BoundaryKind.AFTER_POSTS: 

201 self._above = _make_above_after_posts(version.version) 

202 else: 

203 self._above = _make_above_after_locals(version.version) 

204 elif inclusive: 

205 self._above = version.__le__ 

206 else: 

207 self._above = version.__lt__ 

208 

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

210 if not isinstance(other, LowerBound): 

211 return NotImplemented 

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

213 

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

215 if not isinstance(other, LowerBound): 

216 return NotImplemented 

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

218 if self.version is None: 

219 return other.version is not None 

220 if other.version is None: 

221 return False 

222 if self.version != other.version: 

223 return self.version < other.version 

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

225 return self.inclusive and not other.inclusive 

226 

227 def __hash__(self) -> int: 

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

229 

230 def __repr__(self) -> str: 

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

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

233 

234 

235@functools.total_ordering 

236class UpperBound: 

237 """Upper bound of a version range. 

238 

239 A version *v* of ``None`` means unbounded above (+inf). 

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

241 bound ends earlier. 

242 """ 

243 

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

245 

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

247 self.version = version 

248 self.inclusive = inclusive 

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

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

251 if version is None: 

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

253 elif isinstance(version, BoundaryVersion): 

254 # Standard specifiers only ever produce AFTER_LOCALS upper 

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

256 if version.kind == BoundaryKind.AFTER_LOCALS: 

257 self._below = _make_below_after_locals(version.version) 

258 else: 

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

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

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

262 self._below = version.__ge__ 

263 elif inclusive: 

264 self._below = version.__ge__ 

265 else: 

266 self._below = version.__gt__ 

267 

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

269 if not isinstance(other, UpperBound): 

270 return NotImplemented 

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

272 

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

274 if not isinstance(other, UpperBound): 

275 return NotImplemented 

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

277 if self.version is None: 

278 return False 

279 if other.version is None: 

280 return True 

281 if self.version != other.version: 

282 return self.version < other.version 

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

284 return not self.inclusive and other.inclusive 

285 

286 def __hash__(self) -> int: 

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

288 

289 def __repr__(self) -> str: 

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

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

292 

293 

294if TYPE_CHECKING: 

295 #: A single contiguous version range, as a (lower, upper) pair. 

296 VersionRange = tuple[LowerBound, UpperBound] 

297 

298 

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

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

301FULL_RANGE: Final[tuple[VersionRange]] = ((NEG_INF, POS_INF),) 

302 

303 

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

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

306 end = len(release) 

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

308 end -= 1 

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

310 

311 

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

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

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

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

316 

317 

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

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

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

321 

322 

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

324 if not isinstance(version, Version): 

325 try: 

326 version = Version(version) 

327 except InvalidVersion: 

328 return None 

329 return version 

330 

331 

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

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

334 

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

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

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

338 """ 

339 version_ge = version.__ge__ 

340 version_epoch = version.epoch 

341 version_pre = version.pre 

342 version_release_trimmed = trim_release(version.release) 

343 trimmed_length = len(version_release_trimmed) 

344 

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

346 if version_ge(parsed): 

347 return False 

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

349 # post family. 

350 if parsed.epoch != version_epoch: 

351 return True 

352 parsed_release = parsed.release 

353 if len(parsed_release) < trimmed_length: 

354 return True 

355 if parsed_release[:trimmed_length] != version_release_trimmed: 

356 return True 

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

358 if parsed_release[i] != 0: 

359 return True 

360 if parsed.pre != version_pre: 

361 return True 

362 

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

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

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

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

367 return False 

368 

369 return above 

370 

371 

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

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

374 

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

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

377 just below ``V.post0``. 

378 """ 

379 version_ge = version.__ge__ 

380 version_epoch = version.epoch 

381 version_pre = version.pre 

382 version_post = version.post 

383 version_dev = version.dev 

384 version_release_trimmed = trim_release(version.release) 

385 trimmed_length = len(version_release_trimmed) 

386 

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

388 if version_ge(parsed): 

389 return False 

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

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

392 if parsed.epoch != version_epoch: 

393 return True 

394 parsed_release = parsed.release 

395 if len(parsed_release) < trimmed_length: 

396 return True 

397 if parsed_release[:trimmed_length] != version_release_trimmed: 

398 return True 

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

400 if parsed_release[i] != 0: 

401 return True 

402 if parsed.pre != version_pre: 

403 return True 

404 if parsed.post != version_post: 

405 return True 

406 return parsed.dev != version_dev 

407 

408 return above 

409 

410 

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

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

413 

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

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

416 it is in V's local family. 

417 """ 

418 version_ge = version.__ge__ 

419 version_epoch = version.epoch 

420 version_pre = version.pre 

421 version_post = version.post 

422 version_dev = version.dev 

423 version_release_trimmed = trim_release(version.release) 

424 trimmed_length = len(version_release_trimmed) 

425 

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

427 if version_ge(parsed): 

428 return True 

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

430 # family. 

431 if parsed.epoch != version_epoch: 

432 return False 

433 parsed_release = parsed.release 

434 if len(parsed_release) < trimmed_length: 

435 return False 

436 if parsed_release[:trimmed_length] != version_release_trimmed: 

437 return False 

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

439 if parsed_release[i] != 0: 

440 return False 

441 if parsed.pre != version_pre: 

442 return False 

443 if parsed.post != version_post: 

444 return False 

445 return parsed.dev == version_dev 

446 

447 return below 

448 

449 

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

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

452 base = boundary.version 

453 

454 if boundary.kind == BoundaryKind.AFTER_LOCALS: 

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

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

457 if base.dev is not None: 

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

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

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

461 

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

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

464 if base.pre is not None: 

465 kind, number = base.pre 

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

467 

468 return None 

469 

470 

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

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

473 

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

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

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

477 """ 

478 if upper.version is None: 

479 return False 

480 

481 if lower.version is None: 

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

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

484 return ( 

485 not upper.inclusive 

486 and isinstance(upper.version, Version) 

487 and upper.version <= MIN_VERSION 

488 ) 

489 

490 if isinstance(lower.version, BoundaryVersion): 

491 successor = least_version_above(lower.version) 

492 if successor is not None: 

493 if upper.version == successor: 

494 return not upper.inclusive 

495 return upper.version < successor 

496 

497 if lower.version == upper.version: 

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

499 

500 return lower.version > upper.version 

501 

502 

503def intersect_ranges( 

504 left: Sequence[VersionRange], 

505 right: Sequence[VersionRange], 

506) -> list[VersionRange]: 

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

508 result: list[VersionRange] = [] 

509 left_index = right_index = 0 

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

511 left_lower, left_upper = left[left_index] 

512 right_lower, right_upper = right[right_index] 

513 

514 lower = max(left_lower, right_lower) 

515 upper = min(left_upper, right_upper) 

516 

517 if not range_is_empty(lower, upper): 

518 result.append((lower, upper)) 

519 

520 # Advance whichever side has the smaller upper bound. 

521 if left_upper < right_upper: 

522 left_index += 1 

523 else: 

524 right_index += 1 

525 

526 return result 

527 

528 

529def filter_by_ranges( 

530 ranges: Sequence[VersionRange], 

531 iterable: Iterable[Any], 

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

533 prereleases: bool | None, 

534) -> Iterator[Any]: 

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

536 

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

538 are excluded unless no final matches, in which case buffered 

539 pre-releases come out at the end. 

540 """ 

541 if prereleases is None: 

542 # PEP 440 default: yield finals immediately; buffer 

543 # pre-releases until at least one final has been emitted. 

544 prerelease_buffer: list[Any] = [] 

545 found_final = False 

546 

547 if len(ranges) == 1: 

548 lower, upper = ranges[0] 

549 above = lower._above 

550 below = upper._below 

551 for item in iterable: 

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

553 if parsed is None: 

554 continue 

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

556 continue 

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

558 continue 

559 if parsed.is_prerelease: 

560 if not found_final: 

561 prerelease_buffer.append(item) 

562 else: 

563 found_final = True 

564 yield item 

565 if not found_final: 

566 yield from prerelease_buffer 

567 return 

568 

569 for item in iterable: 

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

571 if parsed is None: 

572 continue 

573 for lower, upper in ranges: 

574 above = lower._above 

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

576 break 

577 below = upper._below 

578 if below is None or below(parsed): 

579 if parsed.is_prerelease: 

580 if not found_final: 

581 prerelease_buffer.append(item) 

582 else: 

583 found_final = True 

584 yield item 

585 break 

586 if not found_final: 

587 yield from prerelease_buffer 

588 return 

589 

590 exclude_prereleases = prereleases is False 

591 

592 if len(ranges) == 1: 

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

594 # a single contiguous range. 

595 lower, upper = ranges[0] 

596 above = lower._above 

597 below = upper._below 

598 for item in iterable: 

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

600 if parsed is None: 

601 continue 

602 if exclude_prereleases and parsed.is_prerelease: 

603 continue 

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

605 continue 

606 if below is None or below(parsed): 

607 yield item 

608 return 

609 

610 for item in iterable: 

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

612 if parsed is None: 

613 continue 

614 if exclude_prereleases and parsed.is_prerelease: 

615 continue 

616 for lower, upper in ranges: 

617 above = lower._above 

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

619 break 

620 below = upper._below 

621 if below is None or below(parsed): 

622 yield item 

623 break 

624 

625 

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

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

628 if version.pre is not None: 

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

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

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

632 

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

634 # whose final 1.0 sorts below it). 

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

636 

637 

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

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

640 

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

642 :data:`MIN_RELEASE`. 

643 """ 

644 if value is None: 

645 return MIN_RELEASE 

646 if isinstance(value, BoundaryVersion): 

647 inner_version = value.version 

648 if inner_version.is_prerelease: 

649 return _nearest_release_above_prerelease(inner_version) 

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

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

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

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

654 

655 if not value.is_prerelease: 

656 return value 

657 

658 return _nearest_release_above_prerelease(value) 

659 

660 

661def ranges_are_prerelease_only(ranges: Sequence[VersionRange]) -> bool: 

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

663 

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

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

666 """ 

667 for lower, upper in ranges: 

668 nearest = _lowest_release_at_or_above(lower.version) 

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

670 return False 

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

672 return False 

673 return True 

674 

675 

676def wildcard_ranges(op: str, base: Version) -> list[VersionRange]: 

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

678 

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

680 """ 

681 lower = _base_dev0(base) 

682 upper = _next_prefix_dev0(base) 

683 if op == "==": 

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

685 # != 

686 return [ 

687 (NEG_INF, UpperBound(lower, False)), 

688 (LowerBound(upper, True), POS_INF), 

689 ] 

690 

691 

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

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

694 

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

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

697 upper bound includes V's local family. 

698 """ 

699 if op == ">=": 

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

701 

702 if op == "<=": 

703 return [ 

704 ( 

705 NEG_INF, 

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

707 ) 

708 ] 

709 

710 if op == ">": 

711 if version.dev is not None: 

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

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

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

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

716 if version.post is not None: 

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

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

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

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

721 # every V.postN per PEP 440. 

722 return [ 

723 ( 

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

725 POS_INF, 

726 ) 

727 ] 

728 

729 if op == "<": 

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

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

732 bound = ( 

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

734 ) 

735 if bound <= MIN_VERSION: 

736 return [] 

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

738 

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

740 after_locals = BoundaryVersion(version, BoundaryKind.AFTER_LOCALS) 

741 upper = version if has_local else after_locals 

742 

743 if op == "==": 

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

745 

746 if op == "!=": 

747 return [ 

748 (NEG_INF, UpperBound(version, False)), 

749 (LowerBound(upper, False), POS_INF), 

750 ] 

751 

752 if op == "~=": 

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

754 return [ 

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

756 ] 

757 

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

759 

760 

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

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

763 

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

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

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

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

768 """ 

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

770 return wildcard_ranges(op, version) 

771 

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

773 

774 

775def intersect_specifier_bounds( 

776 per_specifier_ranges: Iterable[Sequence[VersionRange]], 

777) -> Sequence[VersionRange]: 

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

779 

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

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

782 """ 

783 result: Sequence[VersionRange] | None = None 

784 for sub in per_specifier_ranges: 

785 if result is None: 

786 result = sub 

787 else: 

788 result = intersect_ranges(result, sub) 

789 if not result: 

790 break 

791 

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

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

794 

795 return result 

796 

797 

798def matches_bounds_only(ranges: Sequence[VersionRange], version: Version) -> bool: 

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

800 

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

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

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

804 """ 

805 for lower, upper in ranges: 

806 above = lower._above 

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

808 return False 

809 

810 below = upper._below 

811 if below is None or below(version): 

812 return True 

813 

814 return False 

815 

816 

817def resolve_prereleases( 

818 configured: bool | None, autodetected: bool | None 

819) -> bool | None: 

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

821 

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

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

824 """ 

825 if configured is not None: 

826 return configured 

827 

828 if autodetected: 

829 return True 

830 

831 return None