Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/packaging/_ranges.py: 30%
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
20 from typing import Union
22__all__ = [
23 "FULL_RANGE",
24 "filter_by_ranges",
25 "intersect_ranges",
26 "ranges_are_prerelease_only",
27 "standard_ranges",
28 "wildcard_ranges",
29]
31#: The smallest possible PEP 440 version. No valid version is less than this.
32_MIN_VERSION: Final[Version] = Version("0.dev0")
35class _BoundaryKind(enum.Enum):
36 """Where a boundary marker sits in the version ordering."""
38 AFTER_LOCALS = enum.auto() # after V+local, before V.post0
39 AFTER_POSTS = enum.auto() # after V.postN, before next release
42@functools.total_ordering
43class _BoundaryVersion:
44 """A point on the version line between two real PEP 440 versions.
46 Relative to a base version V::
48 V < V+local < AFTER_LOCALS(V) < V.post0 < AFTER_POSTS(V)
50 AFTER_LOCALS is the upper bound of ``<=V``, ``==V``, ``!=V`` (no
51 local), and the lower bound of the upper-side range of ``!=V``.
52 AFTER_POSTS is the lower bound of ``>V`` (V final or pre-release),
53 excluding V's post-releases per PEP 440.
54 """
56 __slots__ = (
57 "_cached_dev",
58 "_cached_epoch",
59 "_cached_post",
60 "_cached_pre",
61 "_cached_trimmed_release",
62 "_kind",
63 "version",
64 )
66 def __init__(self, version: Version, kind: _BoundaryKind) -> None:
67 self.version = version
68 self._kind = kind
69 self._cached_trimmed_release = trim_release(version.release)
70 self._cached_epoch = version.epoch
71 self._cached_pre = version.pre
72 self._cached_post = version.post
73 self._cached_dev = version.dev
75 def _is_family(self, other: Version) -> bool:
76 """Is ``other`` a version that this boundary sorts above?"""
77 if other.epoch != self._cached_epoch:
78 return False
79 # Inline release-trim comparison: other.release matches the
80 # trimmed release iff its leading slice is equal and any extra
81 # components are zero. Avoids trim_release's tuple allocation.
82 other_release = other.release
83 trimmed_release = self._cached_trimmed_release
84 trimmed_length = len(trimmed_release)
85 if len(other_release) < trimmed_length:
86 return False
87 if other_release[:trimmed_length] != trimmed_release:
88 return False
89 for i in range(trimmed_length, len(other_release)):
90 if other_release[i] != 0:
91 return False
92 if other.pre != self._cached_pre:
93 return False
94 if self._kind == _BoundaryKind.AFTER_LOCALS:
95 # Local family: same public version, any local label.
96 return other.post == self._cached_post and other.dev == self._cached_dev
97 # Post family: V itself + any post-release of V.
98 return other.dev == self._cached_dev or other.post is not None
100 def __eq__(self, other: object) -> bool:
101 if isinstance(other, _BoundaryVersion):
102 return self.version == other.version and self._kind == other._kind
103 return NotImplemented
105 def __lt__(self, other: _BoundaryVersion | Version) -> bool:
106 if isinstance(other, _BoundaryVersion):
107 if self.version != other.version:
108 return self.version < other.version
109 return self._kind.value < other._kind.value # pragma: no cover
110 # boundary < other_version iff V < other AND other not in family.
111 # The cheap V >= other path short-circuits before the family check.
112 if not (self.version < other):
113 return False
114 return not self._is_family(other)
116 def __gt__(self, other: _BoundaryVersion | Version) -> bool:
117 # Defined directly to bypass functools.total_ordering's
118 # NotImplemented round-trip on reflected ``Version < boundary``.
119 if isinstance(other, _BoundaryVersion):
120 if self.version != other.version:
121 return self.version > other.version
122 return self._kind.value > other._kind.value
123 if self.version >= other:
124 return True
125 return self._is_family(other)
127 def __hash__(self) -> int:
128 return hash((self.version, self._kind))
130 def __repr__(self) -> str:
131 return f"{self.__class__.__name__}({self.version!r}, {self._kind.name})"
134if TYPE_CHECKING:
135 _VersionOrBoundary = Union[Version, _BoundaryVersion, None]
138@functools.total_ordering
139class _LowerBound:
140 """Lower bound of a version range.
142 A version *v* of ``None`` means unbounded below (-inf).
143 At equal versions, ``[v`` sorts before ``(v`` because an inclusive
144 bound starts earlier.
145 """
147 __slots__ = ("_above", "inclusive", "version")
149 def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None:
150 self.version = version
151 self.inclusive = inclusive
152 # Pre-bind a predicate "is parsed at or above this lower
153 # bound?" for the hot filter / contains loops. One direct
154 # call per check, no operator-dispatch chain.
155 if version is None:
156 self._above: Callable[[Version], bool] | None = None
157 elif isinstance(version, _BoundaryVersion):
158 # >V produces an AFTER_POSTS lower bound; the upper-side
159 # range of !=V produces an AFTER_LOCALS lower bound.
160 if version._kind == _BoundaryKind.AFTER_POSTS:
161 self._above = _make_above_after_posts(version.version)
162 else:
163 self._above = _make_above_after_locals(version.version)
164 elif inclusive:
165 self._above = version.__le__
166 else:
167 self._above = version.__lt__
169 def __eq__(self, other: object) -> bool:
170 if not isinstance(other, _LowerBound):
171 return NotImplemented # pragma: no cover
172 return self.version == other.version and self.inclusive == other.inclusive
174 def __lt__(self, other: _LowerBound) -> bool:
175 if not isinstance(other, _LowerBound): # pragma: no cover
176 return NotImplemented
177 # -inf < anything (except -inf itself).
178 if self.version is None:
179 return other.version is not None
180 if other.version is None:
181 return False
182 if self.version != other.version:
183 return self.version < other.version
184 # [v < (v: inclusive starts earlier.
185 return self.inclusive and not other.inclusive
187 def __hash__(self) -> int:
188 return hash((self.version, self.inclusive))
190 def __repr__(self) -> str:
191 bracket = "[" if self.inclusive else "("
192 return f"<{self.__class__.__name__} {bracket}{self.version!r}>"
195@functools.total_ordering
196class _UpperBound:
197 """Upper bound of a version range.
199 A version *v* of ``None`` means unbounded above (+inf).
200 At equal versions, ``v)`` sorts before ``v]`` because an exclusive
201 bound ends earlier.
202 """
204 __slots__ = ("_below", "inclusive", "version")
206 def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None:
207 self.version = version
208 self.inclusive = inclusive
209 # Pre-bind a predicate "is parsed at or below this upper
210 # bound?". See _LowerBound for the rationale.
211 if version is None:
212 self._below: Callable[[Version], bool] | None = None
213 elif isinstance(version, _BoundaryVersion):
214 # Standard specifiers only ever produce AFTER_LOCALS upper
215 # bounds (from <=V / ==V / !=V with no local).
216 if version._kind == _BoundaryKind.AFTER_LOCALS:
217 self._below = _make_below_after_locals(version.version)
218 else: # pragma: no cover (AFTER_POSTS upper not produced by specifiers)
219 self._below = version.__ge__
220 elif inclusive:
221 self._below = version.__ge__
222 else:
223 self._below = version.__gt__
225 def __eq__(self, other: object) -> bool:
226 if not isinstance(other, _UpperBound):
227 return NotImplemented # pragma: no cover
228 return self.version == other.version and self.inclusive == other.inclusive
230 def __lt__(self, other: _UpperBound) -> bool:
231 if not isinstance(other, _UpperBound): # pragma: no cover
232 return NotImplemented
233 # Nothing < +inf (except +inf itself).
234 if self.version is None:
235 return False
236 if other.version is None:
237 return True
238 if self.version != other.version:
239 return self.version < other.version
240 # v) < v]: exclusive ends earlier.
241 return not self.inclusive and other.inclusive
243 def __hash__(self) -> int:
244 return hash((self.version, self.inclusive))
246 def __repr__(self) -> str:
247 bracket = "]" if self.inclusive else ")"
248 return f"<{self.__class__.__name__} {self.version!r}{bracket}>"
251if TYPE_CHECKING:
252 #: A single contiguous version range, as a (lower, upper) pair.
253 VersionRange = tuple[_LowerBound, _UpperBound]
256_NEG_INF: Final[_LowerBound] = _LowerBound(None, False)
257_POS_INF: Final[_UpperBound] = _UpperBound(None, False)
258FULL_RANGE: Final[tuple[VersionRange]] = ((_NEG_INF, _POS_INF),)
261def trim_release(release: tuple[int, ...]) -> tuple[int, ...]:
262 """Strip trailing zeros from a release tuple for normalized comparison."""
263 end = len(release)
264 while end > 1 and release[end - 1] == 0:
265 end -= 1
266 return release if end == len(release) else release[:end]
269def _next_prefix_dev0(version: Version) -> Version:
270 """Smallest version in the next prefix: 1.2 -> 1.3.dev0."""
271 release = (*version.release[:-1], version.release[-1] + 1)
272 return Version.from_parts(epoch=version.epoch, release=release, dev=0)
275def _base_dev0(version: Version) -> Version:
276 """The .dev0 of a version's base release: 1.2 -> 1.2.dev0."""
277 return Version.from_parts(epoch=version.epoch, release=version.release, dev=0)
280def _coerce_version(version: Version | str) -> Version | None:
281 if not isinstance(version, Version):
282 try:
283 version = Version(version)
284 except InvalidVersion:
285 return None
286 return version
289def _make_above_after_posts(version: Version) -> Callable[[Version], bool]:
290 """Predicate ``parsed > AFTER_POSTS(V)`` for a lower bound.
292 Per PEP 440, ``>V`` excludes V's post-releases unless V is itself
293 a post-release. AFTER_POSTS sits above V and every V.postN (with
294 or without local), and just below the next release.
295 """
296 version_ge = version.__ge__
297 version_epoch = version.epoch
298 version_pre = version.pre
299 version_dev = version.dev
300 version_release_trimmed = trim_release(version.release)
301 trimmed_length = len(version_release_trimmed)
303 def above(parsed: Version) -> bool:
304 if version_ge(parsed):
305 return False
306 # parsed > V cmpkey-wise: above the boundary iff NOT in V's
307 # post family.
308 if parsed.epoch != version_epoch:
309 return True
310 parsed_release = parsed.release
311 if len(parsed_release) < trimmed_length:
312 return True
313 if parsed_release[:trimmed_length] != version_release_trimmed:
314 return True
315 for i in range(trimmed_length, len(parsed_release)):
316 if parsed_release[i] != 0:
317 return True
318 if parsed.pre != version_pre:
319 return True
320 # In post family iff: same dev as V (covers V itself + V+local),
321 # or any post-release (covers V.postN + V.postN+local).
322 if parsed.dev == version_dev or parsed.post is not None:
323 return False
324 # Different dev with no post means parsed sorts before V
325 # cmpkey-wise, in which case version_ge returned True already.
326 return False # pragma: no cover
328 return above
331def _make_above_after_locals(version: Version) -> Callable[[Version], bool]:
332 """Predicate ``parsed > AFTER_LOCALS(V)`` for a lower bound.
334 Used by the upper-side range of ``!=V`` (when V has no local
335 segment). AFTER_LOCALS sits above V and every ``V+local`` but
336 just below ``V.post0``.
337 """
338 version_ge = version.__ge__
339 version_epoch = version.epoch
340 version_pre = version.pre
341 version_post = version.post
342 version_dev = version.dev
343 version_release_trimmed = trim_release(version.release)
344 trimmed_length = len(version_release_trimmed)
346 def above(parsed: Version) -> bool:
347 if version_ge(parsed):
348 return False
349 # parsed > V cmpkey-wise: above the boundary iff NOT in V's
350 # local family (same public version, any local segment).
351 if parsed.epoch != version_epoch:
352 return True
353 parsed_release = parsed.release
354 if len(parsed_release) < trimmed_length:
355 return True
356 if parsed_release[:trimmed_length] != version_release_trimmed:
357 return True
358 for i in range(trimmed_length, len(parsed_release)):
359 if parsed_release[i] != 0:
360 return True
361 if parsed.pre != version_pre:
362 return True
363 if parsed.post != version_post:
364 return True
365 return parsed.dev != version_dev
367 return above
370def _make_below_after_locals(version: Version) -> Callable[[Version], bool]:
371 """Predicate ``parsed <= AFTER_LOCALS(V)`` for an upper bound.
373 Used by ``<=V``, ``==V``, ``!=V`` (no local). ``parsed`` is at or
374 below the boundary when it is at or below V cmpkey-wise, or when
375 it is in V's local family.
376 """
377 version_ge = version.__ge__
378 version_epoch = version.epoch
379 version_pre = version.pre
380 version_post = version.post
381 version_dev = version.dev
382 version_release_trimmed = trim_release(version.release)
383 trimmed_length = len(version_release_trimmed)
385 def below(parsed: Version) -> bool:
386 if version_ge(parsed):
387 return True
388 # parsed > V cmpkey-wise: below the boundary iff in V's local
389 # family.
390 if parsed.epoch != version_epoch:
391 return False
392 parsed_release = parsed.release
393 if len(parsed_release) < trimmed_length:
394 return False
395 if parsed_release[:trimmed_length] != version_release_trimmed:
396 return False
397 for i in range(trimmed_length, len(parsed_release)):
398 if parsed_release[i] != 0:
399 return False
400 if parsed.pre != version_pre:
401 return False
402 if parsed.post != version_post:
403 return False
404 return parsed.dev == version_dev
406 return below
409def _range_is_empty(lower: _LowerBound, upper: _UpperBound) -> bool:
410 """True when the range defined by *lower* and *upper* contains no versions."""
411 if lower.version is None or upper.version is None:
412 return False
413 if lower.version == upper.version:
414 return not (lower.inclusive and upper.inclusive)
415 return lower.version > upper.version
418def intersect_ranges(
419 left: Sequence[VersionRange],
420 right: Sequence[VersionRange],
421) -> list[VersionRange]:
422 """Intersect two sorted, non-overlapping range lists (two-pointer merge)."""
423 result: list[VersionRange] = []
424 left_index = right_index = 0
425 while left_index < len(left) and right_index < len(right):
426 left_lower, left_upper = left[left_index]
427 right_lower, right_upper = right[right_index]
429 lower = max(left_lower, right_lower)
430 upper = min(left_upper, right_upper)
432 if not _range_is_empty(lower, upper):
433 result.append((lower, upper))
435 # Advance whichever side has the smaller upper bound.
436 if left_upper < right_upper:
437 left_index += 1
438 else:
439 right_index += 1
441 return result
444def filter_by_ranges(
445 ranges: Sequence[VersionRange],
446 iterable: Iterable[Any],
447 key: Callable[[Any], Version | str] | None,
448 prereleases: bool | None,
449) -> Iterator[Any]:
450 """Filter *iterable* against precomputed version *ranges*.
452 With ``prereleases=None``, the PEP 440 default applies: pre-releases
453 are excluded unless no final matches, in which case buffered
454 pre-releases come out at the end.
455 """
456 if prereleases is None:
457 # PEP 440 default: yield finals immediately; buffer
458 # pre-releases until at least one final has been emitted.
459 prerelease_buffer: list[Any] = []
460 found_final = False
462 if len(ranges) == 1:
463 lower, upper = ranges[0]
464 above = lower._above
465 below = upper._below
466 for item in iterable:
467 parsed = _coerce_version(item if key is None else key(item))
468 if parsed is None:
469 continue
470 if above is not None and not above(parsed):
471 continue
472 if below is not None and not below(parsed):
473 continue
474 if parsed.is_prerelease:
475 if not found_final:
476 prerelease_buffer.append(item)
477 else:
478 found_final = True
479 yield item
480 if not found_final:
481 yield from prerelease_buffer
482 return
484 for item in iterable:
485 parsed = _coerce_version(item if key is None else key(item))
486 if parsed is None:
487 continue
488 for lower, upper in ranges:
489 above = lower._above
490 if above is not None and not above(parsed):
491 break
492 below = upper._below
493 if below is None or below(parsed):
494 if parsed.is_prerelease:
495 if not found_final:
496 prerelease_buffer.append(item)
497 else:
498 found_final = True
499 yield item
500 break
501 if not found_final:
502 yield from prerelease_buffer
503 return
505 exclude_prereleases = prereleases is False
507 if len(ranges) == 1:
508 # Hot path: most specifiers and small SpecifierSets reduce to
509 # a single contiguous range.
510 lower, upper = ranges[0]
511 above = lower._above
512 below = upper._below
513 for item in iterable:
514 parsed = _coerce_version(item if key is None else key(item))
515 if parsed is None:
516 continue
517 if exclude_prereleases and parsed.is_prerelease:
518 continue
519 if above is not None and not above(parsed):
520 continue
521 if below is None or below(parsed):
522 yield item
523 return
525 for item in iterable:
526 parsed = _coerce_version(item if key is None else key(item))
527 if parsed is None:
528 continue
529 if exclude_prereleases and parsed.is_prerelease:
530 continue
531 for lower, upper in ranges:
532 above = lower._above
533 if above is not None and not above(parsed):
534 break
535 below = upper._below
536 if below is None or below(parsed):
537 yield item
538 break
541def _lowest_release_at_or_above(
542 value: _VersionOrBoundary,
543) -> Version | None:
544 """Smallest non-pre-release version at or above *value*, or None."""
545 if value is None:
546 return None
547 if isinstance(value, _BoundaryVersion):
548 inner_version = value.version
549 if inner_version.is_prerelease:
550 # AFTER_LOCALS(1.0a1) -> nearest non-pre is 1.0
551 return inner_version.__replace__(pre=None, dev=None, local=None)
552 # AFTER_LOCALS(1.0) -> nearest non-pre is 1.0.post0
553 # AFTER_LOCALS(1.0.post0) -> nearest non-pre is 1.0.post1
554 next_post = (inner_version.post + 1) if inner_version.post is not None else 0
555 return inner_version.__replace__(post=next_post, local=None)
556 if not value.is_prerelease:
557 return value
558 # Strip pre/dev to get the final or post-release form.
559 return value.__replace__(pre=None, dev=None, local=None)
562def ranges_are_prerelease_only(ranges: Sequence[VersionRange]) -> bool:
563 """True when every range in *ranges* contains only pre-releases.
565 Used to detect unsatisfiable specifier sets when ``prereleases=False``:
566 if every range is pre-release-only, every contained version is excluded.
567 """
568 for lower, upper in ranges:
569 nearest = _lowest_release_at_or_above(lower.version)
570 if nearest is None:
571 return False
572 if upper.version is None or nearest < upper.version:
573 return False
574 if nearest == upper.version and upper.inclusive:
575 return False
576 return True
579def wildcard_ranges(op: str, base: Version) -> list[VersionRange]:
580 """Ranges for ==V.* and !=V.*.
582 ==1.2.* -> [1.2.dev0, 1.3.dev0); !=1.2.* -> complement.
583 """
584 lower = _base_dev0(base)
585 upper = _next_prefix_dev0(base)
586 if op == "==":
587 return [(_LowerBound(lower, True), _UpperBound(upper, False))]
588 # !=
589 return [
590 (_NEG_INF, _UpperBound(lower, False)),
591 (_LowerBound(upper, True), _POS_INF),
592 ]
595def standard_ranges(op: str, version: Version, has_local: bool) -> list[VersionRange]:
596 """Ranges for the standard PEP 440 operators (no wildcard, no ===).
598 *has_local* indicates whether the spec string included a ``+local``
599 segment; relevant only for ``==`` / ``!=`` to decide whether the
600 upper bound includes V's local family.
601 """
602 if op == ">=":
603 return [(_LowerBound(version, True), _POS_INF)]
605 if op == "<=":
606 return [
607 (
608 _NEG_INF,
609 _UpperBound(
610 _BoundaryVersion(version, _BoundaryKind.AFTER_LOCALS), True
611 ),
612 )
613 ]
615 if op == ">":
616 if version.dev is not None:
617 # >V.devN: dev versions have no post-releases, so the
618 # next real version is V.dev(N+1).
619 lower_bound = version.__replace__(dev=version.dev + 1, local=None)
620 return [(_LowerBound(lower_bound, True), _POS_INF)]
621 if version.post is not None:
622 # >V.postN: next real version is V.post(N+1).dev0.
623 lower_bound = version.__replace__(post=version.post + 1, dev=0, local=None)
624 return [(_LowerBound(lower_bound, True), _POS_INF)]
625 # >V (final or pre-release V): exclude V itself, V+local, and
626 # every V.postN per PEP 440.
627 return [
628 (
629 _LowerBound(
630 _BoundaryVersion(version, _BoundaryKind.AFTER_POSTS), False
631 ),
632 _POS_INF,
633 )
634 ]
636 if op == "<":
637 # <V excludes pre-releases of V when V is not a pre-release.
638 # V.dev0 is the earliest pre-release of V.
639 bound = (
640 version if version.is_prerelease else version.__replace__(dev=0, local=None)
641 )
642 if bound <= _MIN_VERSION:
643 return []
644 return [(_NEG_INF, _UpperBound(bound, False))]
646 # ==, !=: local versions of V match when the spec has no local segment.
647 after_locals = _BoundaryVersion(version, _BoundaryKind.AFTER_LOCALS)
648 upper = version if has_local else after_locals
650 if op == "==":
651 return [(_LowerBound(version, True), _UpperBound(upper, True))]
653 if op == "!=":
654 return [
655 (_NEG_INF, _UpperBound(version, False)),
656 (_LowerBound(upper, False), _POS_INF),
657 ]
659 if op == "~=":
660 prefix = version.__replace__(release=version.release[:-1])
661 return [
662 (_LowerBound(version, True), _UpperBound(_next_prefix_dev0(prefix), False))
663 ]
665 raise ValueError(f"Unknown operator: {op!r}") # pragma: no cover