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
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
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`."""
6from __future__ import annotations
8import enum
9import functools
10from typing import (
11 TYPE_CHECKING,
12 Any,
13 Final,
14)
16from .version import InvalidVersion, Version
18if TYPE_CHECKING:
19 from collections.abc import Callable, Iterable, Iterator, Sequence
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]
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]
43#: The smallest possible PEP 440 version. No valid version is less than this.
44MIN_VERSION: Final[Version] = Version("0.dev0")
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")
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")
55class BoundaryKind(enum.Enum):
56 """Where a boundary marker sits in the version ordering."""
58 AFTER_LOCALS = enum.auto() # after V+local, before V.post0
59 AFTER_POSTS = enum.auto() # after V.postN, before next release
62@functools.total_ordering
63class BoundaryVersion:
64 """A point on the version line between two real PEP 440 versions.
66 Relative to a base version V::
68 V < V+local < AFTER_LOCALS(V) < V.post0 < AFTER_POSTS(V)
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 """
76 __slots__ = (
77 "_cached_dev",
78 "_cached_epoch",
79 "_cached_post",
80 "_cached_pre",
81 "_cached_trimmed_release",
82 "kind",
83 "version",
84 )
86 # Synthetic boundaries must use version operators rather than cached keys.
87 _key_cache: None = None
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
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
123 def _order_key(self) -> _BoundaryOrderKey:
124 """Sort key placing this boundary just above the versions it covers.
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)``.
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]
139 if self.kind == BoundaryKind.AFTER_POSTS:
140 suffix = (suffix[0], suffix[1], 1, _BOUNDARY_INF, 1, 0)
142 return version_key[0], version_key[1], suffix, _BOUNDARY_INF
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
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)
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)
169 def __hash__(self) -> int:
170 # Keyed to ``__eq__`` (the order key), so equal boundaries hash equal.
171 return hash(self._order_key())
173 def __repr__(self) -> str:
174 return f"{self.__class__.__name__}({self.version!r}, {self.kind.name})"
177if TYPE_CHECKING:
178 _VersionOrBoundary = Version | BoundaryVersion | None
181@functools.total_ordering
182class LowerBound:
183 """Lower bound of a version range.
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 """
191 __slots__ = ("_above", "inclusive", "version")
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
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__
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
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
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
240 # [v < (v: inclusive starts earlier.
241 return self.inclusive and not other.inclusive
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
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
260 return other.inclusive and not self.inclusive
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
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
278 return self.inclusive or not other.inclusive
280 def __hash__(self) -> int:
281 return hash((self.version, self.inclusive))
283 def __repr__(self) -> str:
284 bracket = "[" if self.inclusive else "("
285 return f"<{self.__class__.__name__} {bracket}{self.version!r}>"
288@functools.total_ordering
289class UpperBound:
290 """Upper bound of a version range.
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 """
298 __slots__ = ("_below", "inclusive", "version")
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
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__
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
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
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
349 # v) < v]: exclusive ends earlier.
350 return not self.inclusive and other.inclusive
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
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
368 return self.inclusive and not other.inclusive
370 def __hash__(self) -> int:
371 return hash((self.version, self.inclusive))
373 def __repr__(self) -> str:
374 bracket = "]" if self.inclusive else ")"
375 return f"<{self.__class__.__name__} {self.version!r}{bracket}>"
378if TYPE_CHECKING:
379 #: A single contiguous interval as a (lower, upper) bound pair.
380 Interval = tuple[LowerBound, UpperBound]
383NEG_INF: Final[LowerBound] = LowerBound(None, False)
384POS_INF: Final[UpperBound] = UpperBound(None, False)
385FULL_RANGE: Final[tuple[Interval]] = ((NEG_INF, POS_INF),)
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]
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)
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)
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
416def _make_above_after_posts(version: Version) -> Callable[[Version], bool]:
417 """Predicate ``parsed > AFTER_POSTS(V)`` for a lower bound.
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)
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
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
453 return above
456def _make_above_after_locals(version: Version) -> Callable[[Version], bool]:
457 """Predicate ``parsed > AFTER_LOCALS(V)`` for a lower bound.
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)
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
492 return above
495def _make_below_after_locals(version: Version) -> Callable[[Version], bool]:
496 """Predicate ``parsed <= AFTER_LOCALS(V)`` for an upper bound.
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)
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
531 return below
534def least_version_above(boundary: BoundaryVersion) -> Version | None:
535 """Smallest real version strictly above *boundary*, or ``None`` if none exists."""
536 base = boundary.version
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)
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)
552 return None
555def range_is_empty(lower: LowerBound, upper: UpperBound) -> bool:
556 """True when the range defined by *lower* and *upper* contains no versions.
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
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 )
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
581 if lower.version == upper.version:
582 return not (lower.inclusive and upper.inclusive)
584 return lower.version > upper.version
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]
598 lower = max(left_lower, right_lower)
599 upper = min(left_upper, right_upper)
601 if not range_is_empty(lower, upper):
602 result.append((lower, upper))
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
610 return result
613def ranges_are_disjoint(left: Sequence[Interval], right: Sequence[Interval]) -> bool:
614 """Test whether two sorted, non-overlapping interval lists are disjoint.
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]
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
630 return True
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*.
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
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
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
699 exclude_prereleases = prereleases is False
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
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
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)
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)
747def _lowest_release_at_or_above(value: Version | BoundaryVersion | None) -> Version:
748 """Smallest non-pre-release version at or above *value*.
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)
764 if not value.is_prerelease:
765 return value
767 return _nearest_release_above_prerelease(value)
770def ranges_are_prerelease_only(ranges: Sequence[Interval]) -> bool:
771 """True when every range in *ranges* contains only pre-releases.
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
785def wildcard_ranges(op: str, base: Version) -> list[Interval]:
786 """Ranges for ==V.* and !=V.*.
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 ]
801def standard_ranges(op: str, version: Version, has_local: bool) -> list[Interval]:
802 """Ranges for the standard PEP 440 operators (no wildcard, no ===).
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)]
811 if op == "<=":
812 return [
813 (
814 NEG_INF,
815 UpperBound(BoundaryVersion(version, BoundaryKind.AFTER_LOCALS), True),
816 )
817 ]
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 ]
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))]
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
852 if op == "==":
853 return [(LowerBound(version, True), UpperBound(upper, True))]
855 if op == "!=":
856 return [
857 (NEG_INF, UpperBound(version, False)),
858 (LowerBound(upper, False), POS_INF),
859 ]
861 if op == "~=":
862 prefix = version.__replace__(release=version.release[:-1])
863 return [
864 (LowerBound(version, True), UpperBound(_next_prefix_dev0(prefix), False))
865 ]
867 raise ValueError(f"Unknown operator: {op!r}") # pragma: no cover
870def bounds_for_spec(op: str, version_str: str, version: Version) -> list[Interval]:
871 """Ranges for one specifier's ``(op, version_str)``.
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)
881 return standard_ranges(op, version, "+" in version_str)
884def intersect_specifier_bounds(
885 per_specifier_ranges: Iterable[Sequence[Interval]],
886) -> Sequence[Interval]:
887 """Intersect each specifier's ranges into a single sequence.
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
901 if result is None: # pragma: no cover - callers guard non-empty input
902 raise RuntimeError("intersect_specifier_bounds called with no specifiers")
904 return result
907def matches_bounds_only(ranges: Sequence[Interval], version: Version) -> bool:
908 """Whether ``version`` falls within any of ``ranges``.
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
919 below = upper._below
920 if below is None or below(version):
921 return True
923 return False
926def resolve_prereleases(
927 configured: bool | None, autodetected: bool | None
928) -> bool | None:
929 """Resolve a specifier's effective default pre-release policy.
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
937 if autodetected:
938 return True
940 return None