nab-python 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. nab_python/__init__.py +1 -0
  2. nab_python/_build/__init__.py +1 -0
  3. nab_python/_build/env.py +364 -0
  4. nab_python/_build/errors.py +17 -0
  5. nab_python/_build/runner.py +254 -0
  6. nab_python/_lockfile/__init__.py +1 -0
  7. nab_python/_lockfile/builder.py +339 -0
  8. nab_python/_lockfile/disjointness.py +207 -0
  9. nab_python/_lockfile/pylock.py +323 -0
  10. nab_python/_lockfile/requirements.py +121 -0
  11. nab_python/_packaging_provider.py +98 -0
  12. nab_python/_provider/__init__.py +1 -0
  13. nab_python/_provider/build_remote.py +95 -0
  14. nab_python/_provider/extras.py +231 -0
  15. nab_python/_provider/listing.py +442 -0
  16. nab_python/_provider/lookahead.py +156 -0
  17. nab_python/_provider/metadata_resolver.py +450 -0
  18. nab_python/_provider/priority.py +174 -0
  19. nab_python/_provider/sources.py +215 -0
  20. nab_python/_testing/__init__.py +1 -0
  21. nab_python/_testing/coordinator_fake.py +240 -0
  22. nab_python/_vcs_admission.py +209 -0
  23. nab_python/_vendor/__init__.py +6 -0
  24. nab_python/_vendor/packaging/LICENSE +3 -0
  25. nab_python/_vendor/packaging/LICENSE.APACHE +177 -0
  26. nab_python/_vendor/packaging/LICENSE.BSD +23 -0
  27. nab_python/_vendor/packaging/PROVENANCE.md +73 -0
  28. nab_python/_vendor/packaging/__init__.py +15 -0
  29. nab_python/_vendor/packaging/_elffile.py +108 -0
  30. nab_python/_vendor/packaging/_manylinux.py +265 -0
  31. nab_python/_vendor/packaging/_musllinux.py +88 -0
  32. nab_python/_vendor/packaging/_parser.py +394 -0
  33. nab_python/_vendor/packaging/_structures.py +33 -0
  34. nab_python/_vendor/packaging/_tokenizer.py +196 -0
  35. nab_python/_vendor/packaging/dependency_groups.py +302 -0
  36. nab_python/_vendor/packaging/direct_url.py +325 -0
  37. nab_python/_vendor/packaging/errors.py +94 -0
  38. nab_python/_vendor/packaging/licenses/__init__.py +186 -0
  39. nab_python/_vendor/packaging/licenses/_spdx.py +799 -0
  40. nab_python/_vendor/packaging/markers.py +506 -0
  41. nab_python/_vendor/packaging/metadata.py +964 -0
  42. nab_python/_vendor/packaging/py.typed +0 -0
  43. nab_python/_vendor/packaging/pylock.py +910 -0
  44. nab_python/_vendor/packaging/ranges.py +1803 -0
  45. nab_python/_vendor/packaging/requirements.py +132 -0
  46. nab_python/_vendor/packaging/specifiers.py +1141 -0
  47. nab_python/_vendor/packaging/tags.py +929 -0
  48. nab_python/_vendor/packaging/utils.py +296 -0
  49. nab_python/_vendor/packaging/version.py +1230 -0
  50. nab_python/build_backend.py +184 -0
  51. nab_python/config.py +805 -0
  52. nab_python/download.py +170 -0
  53. nab_python/fetch.py +827 -0
  54. nab_python/lockfile.py +238 -0
  55. nab_python/metadata.py +145 -0
  56. nab_python/provider.py +1235 -0
  57. nab_python/py.typed +0 -0
  58. nab_python/requirements_file.py +180 -0
  59. nab_python/resolve.py +497 -0
  60. nab_python/universal/__init__.py +1 -0
  61. nab_python/universal/matrix.py +235 -0
  62. nab_python/universal/provider.py +214 -0
  63. nab_python/universal/reresolve.py +310 -0
  64. nab_python/universal/resolve.py +508 -0
  65. nab_python/universal/validate.py +439 -0
  66. nab_python/universal/wheel_selection.py +327 -0
  67. nab_python/workspace.py +214 -0
  68. nab_python-0.0.1.dist-info/METADATA +49 -0
  69. nab_python-0.0.1.dist-info/RECORD +71 -0
  70. nab_python-0.0.1.dist-info/WHEEL +4 -0
  71. nab_python-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1803 @@
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
+ """Public :class:`VersionRange` API and supporting range helpers.
5
+
6
+ The :class:`VersionRange` class exposes a set-algebra view of the
7
+ versions accepted by a :class:`~packaging.specifiers.Specifier` or
8
+ :class:`~packaging.specifiers.SpecifierSet`. Private helpers in this
9
+ module also drive the range-filter hot path used by
10
+ :meth:`Specifier.contains` / :meth:`Specifier.filter` and
11
+ :meth:`SpecifierSet.contains` / :meth:`SpecifierSet.filter`.
12
+
13
+ .. testsetup::
14
+
15
+ from packaging.ranges import VersionRange
16
+ from packaging.specifiers import Specifier, SpecifierSet
17
+ from packaging.version import Version
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import enum
23
+ import functools
24
+ import typing
25
+ from typing import (
26
+ TYPE_CHECKING,
27
+ Any,
28
+ Final,
29
+ Union,
30
+ )
31
+
32
+ from .version import InvalidVersion, Version
33
+
34
+ if TYPE_CHECKING:
35
+ from collections.abc import Callable, Iterable, Iterator, Sequence
36
+
37
+ from .specifiers import Specifier, SpecifierSet
38
+
39
+
40
+ __all__ = ["VersionRange"]
41
+
42
+
43
+ def __dir__() -> list[str]:
44
+ return __all__
45
+
46
+
47
+ #: The smallest possible PEP 440 version. No valid version is less than this.
48
+ _MIN_VERSION: Final[Version] = Version("0.dev0")
49
+
50
+ #: Packed pickle form of a single bound: ``(version_str_or_None,
51
+ #: inclusive, kind_or_None)``. Uses only strings, bools, and ``None``
52
+ #: so the format stays stable across packaging releases.
53
+ _PackedBound = tuple[Union[str, None], bool, Union[str, None]]
54
+
55
+
56
+ def _trim_release(release: tuple[int, ...]) -> tuple[int, ...]:
57
+ """Strip trailing zeros from a release tuple."""
58
+ end = len(release)
59
+ while end > 1 and release[end - 1] == 0:
60
+ end -= 1
61
+ return release if end == len(release) else release[:end]
62
+
63
+
64
+ def _next_prefix_dev0(version: Version) -> Version:
65
+ """Smallest version in the next prefix: ``1.2 -> 1.3.dev0``."""
66
+ release = (*version.release[:-1], version.release[-1] + 1)
67
+ return Version.from_parts(epoch=version.epoch, release=release, dev=0)
68
+
69
+
70
+ def _base_dev0(version: Version) -> Version:
71
+ """The ``.dev0`` of a version's base release: ``1.2 -> 1.2.dev0``."""
72
+ return Version.from_parts(epoch=version.epoch, release=version.release, dev=0)
73
+
74
+
75
+ def _coerce_version(version: Version | str) -> Version | None:
76
+ """Parse *version*; ``None`` if invalid."""
77
+ if not isinstance(version, Version):
78
+ try:
79
+ version = Version(version)
80
+ except InvalidVersion:
81
+ return None
82
+ return version
83
+
84
+
85
+ class _BoundaryKind(enum.Enum):
86
+ """Where a boundary marker sits in the version ordering."""
87
+
88
+ AFTER_LOCALS = enum.auto() # after V+local, before V.post0
89
+ AFTER_POSTS = enum.auto() # after V.postN, before next release
90
+
91
+
92
+ @functools.total_ordering
93
+ class _BoundaryVersion:
94
+ """A synthetic point between two real PEP 440 versions.
95
+
96
+ PEP 440 specifier semantics imply boundaries between real versions
97
+ (``<=1.0`` includes ``1.0+local``; ``>1.0`` excludes ``1.0.post0``).
98
+ Relative to a base version V::
99
+
100
+ V < V+local < AFTER_LOCALS(V) < V.post0 < AFTER_POSTS(V)
101
+
102
+ AFTER_LOCALS is the upper bound of ``<=V``, ``==V``, ``!=V`` (no
103
+ local), and the lower bound of the upper-side range of ``!=V``.
104
+ AFTER_POSTS is the lower bound of ``>V`` (V final or pre-release),
105
+ excluding V's post-releases per PEP 440.
106
+ """
107
+
108
+ __slots__ = (
109
+ "_cached_dev",
110
+ "_cached_epoch",
111
+ "_cached_post",
112
+ "_cached_pre",
113
+ "_cached_trimmed_release",
114
+ "_kind",
115
+ "version",
116
+ )
117
+
118
+ def __init__(self, version: Version, kind: _BoundaryKind) -> None:
119
+ self.version = version
120
+ self._kind = kind
121
+ self._cached_trimmed_release = _trim_release(version.release)
122
+ self._cached_epoch = version.epoch
123
+ self._cached_pre = version.pre
124
+ self._cached_post = version.post
125
+ self._cached_dev = version.dev
126
+
127
+ def _is_family(self, other: Version) -> bool:
128
+ """Is ``other`` a version that this boundary sorts above?"""
129
+ if other.epoch != self._cached_epoch:
130
+ return False
131
+ # Inline release-trim comparison: other.release matches the
132
+ # trimmed release iff its leading slice is equal and any extra
133
+ # components are zero. Avoids _trim_release's tuple allocation.
134
+ other_release = other.release
135
+ trimmed_release = self._cached_trimmed_release
136
+ trimmed_length = len(trimmed_release)
137
+ if len(other_release) < trimmed_length:
138
+ return False
139
+ if other_release[:trimmed_length] != trimmed_release:
140
+ return False
141
+ for i in range(trimmed_length, len(other_release)):
142
+ if other_release[i] != 0:
143
+ return False
144
+ if other.pre != self._cached_pre:
145
+ return False
146
+ if self._kind == _BoundaryKind.AFTER_LOCALS:
147
+ # Local family: same public version, any local label.
148
+ return other.post == self._cached_post and other.dev == self._cached_dev
149
+ # Post family: V itself + any post-release of V.
150
+ return other.dev == self._cached_dev or other.post is not None
151
+
152
+ def __eq__(self, other: object) -> bool:
153
+ if isinstance(other, _BoundaryVersion):
154
+ return self.version == other.version and self._kind == other._kind
155
+ return NotImplemented
156
+
157
+ def __lt__(self, other: _BoundaryVersion | Version) -> bool:
158
+ if isinstance(other, _BoundaryVersion):
159
+ if self.version != other.version:
160
+ return self.version < other.version
161
+ return self._kind.value < other._kind.value # pragma: no cover
162
+ # boundary < other_version iff V < other AND other not in family.
163
+ # The cheap V >= other path short-circuits before the family check.
164
+ if not (self.version < other):
165
+ return False
166
+ return not self._is_family(other)
167
+
168
+ def __gt__(self, other: _BoundaryVersion | Version) -> bool:
169
+ # Defined directly to bypass functools.total_ordering's
170
+ # NotImplemented round-trip on reflected ``Version < boundary``.
171
+ if isinstance(other, _BoundaryVersion):
172
+ if self.version != other.version:
173
+ return self.version > other.version
174
+ return self._kind.value > other._kind.value
175
+ if self.version >= other:
176
+ return True
177
+ return self._is_family(other)
178
+
179
+ def __hash__(self) -> int:
180
+ return hash((self.version, self._kind))
181
+
182
+ def __repr__(self) -> str:
183
+ return f"{self.__class__.__name__}({self.version!r}, {self._kind.name})"
184
+
185
+
186
+ if TYPE_CHECKING:
187
+ _VersionOrBoundary = Union[Version, _BoundaryVersion, None]
188
+
189
+
190
+ def _make_above_after_posts(version: Version) -> Callable[[Version], bool]:
191
+ """Predicate ``parsed > AFTER_POSTS(v)`` for a lower bound.
192
+
193
+ Per PEP 440, ``>V`` excludes V's post-releases unless V is itself
194
+ a post-release. AFTER_POSTS sits above V and every V.postN (with
195
+ or without local), and just below the next release.
196
+ """
197
+ version_ge = version.__ge__
198
+ version_epoch = version.epoch
199
+ version_pre = version.pre
200
+ version_dev = version.dev
201
+ version_release_trimmed = _trim_release(version.release)
202
+ trimmed_length = len(version_release_trimmed)
203
+
204
+ def above(parsed: Version) -> bool:
205
+ if version_ge(parsed):
206
+ return False
207
+ # parsed > v cmpkey-wise: above the boundary iff NOT in v's
208
+ # post family.
209
+ if parsed.epoch != version_epoch:
210
+ return True
211
+ parsed_release = parsed.release
212
+ if len(parsed_release) < trimmed_length:
213
+ return True
214
+ if parsed_release[:trimmed_length] != version_release_trimmed:
215
+ return True
216
+ for i in range(trimmed_length, len(parsed_release)):
217
+ if parsed_release[i] != 0:
218
+ return True
219
+ if parsed.pre != version_pre:
220
+ return True
221
+ # In post family iff: same dev as v (covers v itself + v+local),
222
+ # or any post-release (covers v.postN + v.postN+local).
223
+ if parsed.dev == version_dev or parsed.post is not None:
224
+ return False
225
+ # Different dev with no post means parsed sorts before v
226
+ # cmpkey-wise, in which case version_ge returned True already.
227
+ return False # pragma: no cover
228
+
229
+ return above
230
+
231
+
232
+ def _make_above_after_locals(version: Version) -> Callable[[Version], bool]:
233
+ """Predicate ``parsed > AFTER_LOCALS(v)`` for a lower bound.
234
+
235
+ Used by the upper-side range of ``!=v`` (when *v* has no local
236
+ segment). AFTER_LOCALS sits above v and every ``v+local`` but
237
+ just below ``v.post0``.
238
+ """
239
+ version_ge = version.__ge__
240
+ version_epoch = version.epoch
241
+ version_pre = version.pre
242
+ version_post = version.post
243
+ version_dev = version.dev
244
+ version_release_trimmed = _trim_release(version.release)
245
+ trimmed_length = len(version_release_trimmed)
246
+
247
+ def above(parsed: Version) -> bool:
248
+ if version_ge(parsed):
249
+ return False
250
+ # parsed > v cmpkey-wise: above the boundary iff NOT in v's
251
+ # local family (same public version, any local segment).
252
+ if parsed.epoch != version_epoch:
253
+ return True
254
+ parsed_release = parsed.release
255
+ if len(parsed_release) < trimmed_length:
256
+ return True
257
+ if parsed_release[:trimmed_length] != version_release_trimmed:
258
+ return True
259
+ for i in range(trimmed_length, len(parsed_release)):
260
+ if parsed_release[i] != 0:
261
+ return True
262
+ if parsed.pre != version_pre:
263
+ return True
264
+ if parsed.post != version_post:
265
+ return True
266
+ return parsed.dev != version_dev
267
+
268
+ return above
269
+
270
+
271
+ def _make_below_after_locals(version: Version) -> Callable[[Version], bool]:
272
+ """Predicate ``parsed <= AFTER_LOCALS(v)`` for an upper bound.
273
+
274
+ Used by ``<=v``, ``==v``, ``!=v`` (no local). ``parsed`` is at or
275
+ below the boundary when it is at or below v cmpkey-wise, or when
276
+ it is in v's local family.
277
+ """
278
+ version_ge = version.__ge__
279
+ version_epoch = version.epoch
280
+ version_pre = version.pre
281
+ version_post = version.post
282
+ version_dev = version.dev
283
+ version_release_trimmed = _trim_release(version.release)
284
+ trimmed_length = len(version_release_trimmed)
285
+
286
+ def below(parsed: Version) -> bool:
287
+ if version_ge(parsed):
288
+ return True
289
+ # parsed > v cmpkey-wise: below the boundary iff in v's local
290
+ # family.
291
+ if parsed.epoch != version_epoch:
292
+ return False
293
+ parsed_release = parsed.release
294
+ if len(parsed_release) < trimmed_length:
295
+ return False
296
+ if parsed_release[:trimmed_length] != version_release_trimmed:
297
+ return False
298
+ for i in range(trimmed_length, len(parsed_release)):
299
+ if parsed_release[i] != 0:
300
+ return False
301
+ if parsed.pre != version_pre:
302
+ return False
303
+ if parsed.post != version_post:
304
+ return False
305
+ return parsed.dev == version_dev
306
+
307
+ return below
308
+
309
+
310
+ def _make_below_after_posts(version: Version) -> Callable[[Version], bool]:
311
+ """Predicate ``parsed <= AFTER_POSTS(v)`` for an upper bound.
312
+
313
+ Mirror of :func:`_make_above_after_posts`. Produced only by
314
+ :meth:`VersionRange.complement` of a range whose lower bound is
315
+ AFTER_POSTS(v). ``parsed`` is at or below the boundary when it is
316
+ at or below v cmpkey-wise, or when it is in v's post family.
317
+ """
318
+ version_ge = version.__ge__
319
+ version_epoch = version.epoch
320
+ version_pre = version.pre
321
+ version_dev = version.dev
322
+ version_release_trimmed = _trim_release(version.release)
323
+ trimmed_length = len(version_release_trimmed)
324
+
325
+ def below(parsed: Version) -> bool:
326
+ if version_ge(parsed):
327
+ return True
328
+ # parsed > v cmpkey-wise: below the boundary iff in v's post family.
329
+ if parsed.epoch != version_epoch:
330
+ return False
331
+ parsed_release = parsed.release
332
+ if len(parsed_release) < trimmed_length:
333
+ return False
334
+ if parsed_release[:trimmed_length] != version_release_trimmed:
335
+ return False
336
+ for i in range(trimmed_length, len(parsed_release)):
337
+ if parsed_release[i] != 0:
338
+ return False
339
+ if parsed.pre != version_pre:
340
+ return False
341
+ # Same dev as v with no post means parsed sorts <= v already
342
+ # (handled by version_ge above); reach here only with parsed.post set.
343
+ return parsed.dev == version_dev or parsed.post is not None
344
+
345
+ return below
346
+
347
+
348
+ @functools.total_ordering
349
+ class _LowerBound:
350
+ """Lower bound of a version range.
351
+
352
+ A ``version`` of ``None`` is unbounded below (-inf). At equal
353
+ versions, ``[v`` sorts before ``(v`` (inclusive starts earlier).
354
+ """
355
+
356
+ __slots__ = ("_above", "inclusive", "version")
357
+
358
+ def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None:
359
+ self.version = version
360
+ self.inclusive = inclusive
361
+ # Pre-bind a predicate "is parsed at or above this lower
362
+ # bound?" for the hot filter / contains loops. One direct
363
+ # call per check, no operator-dispatch chain.
364
+ if version is None:
365
+ self._above: Callable[[Version], bool] | None = None
366
+ elif isinstance(version, _BoundaryVersion):
367
+ # >v produces an AFTER_POSTS lower bound; the upper-side
368
+ # range of !=v produces an AFTER_LOCALS lower bound.
369
+ if version._kind == _BoundaryKind.AFTER_POSTS:
370
+ self._above = _make_above_after_posts(version.version)
371
+ else:
372
+ self._above = _make_above_after_locals(version.version)
373
+ elif inclusive:
374
+ self._above = version.__le__
375
+ else:
376
+ self._above = version.__lt__
377
+
378
+ def __eq__(self, other: object) -> bool:
379
+ if not isinstance(other, _LowerBound):
380
+ return NotImplemented # pragma: no cover
381
+ return self.version == other.version and self.inclusive == other.inclusive
382
+
383
+ def __lt__(self, other: _LowerBound) -> bool:
384
+ if not isinstance(other, _LowerBound): # pragma: no cover
385
+ return NotImplemented
386
+ # -inf < anything (except -inf itself).
387
+ if self.version is None:
388
+ return other.version is not None
389
+ if other.version is None:
390
+ return False
391
+ if self.version != other.version:
392
+ return self.version < other.version
393
+ # ``[v < (v``: inclusive starts earlier.
394
+ return self.inclusive and not other.inclusive
395
+
396
+ def __hash__(self) -> int:
397
+ return hash((self.version, self.inclusive))
398
+
399
+ def __repr__(self) -> str:
400
+ bracket = "[" if self.inclusive else "("
401
+ return f"<{self.__class__.__name__} {bracket}{self.version!r}>"
402
+
403
+
404
+ @functools.total_ordering
405
+ class _UpperBound:
406
+ """Upper bound of a version range.
407
+
408
+ A ``version`` of ``None`` is unbounded above (+inf). At equal
409
+ versions, ``v)`` sorts before ``v]`` (exclusive ends earlier).
410
+ """
411
+
412
+ __slots__ = ("_below", "inclusive", "version")
413
+
414
+ def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None:
415
+ self.version = version
416
+ self.inclusive = inclusive
417
+ # Pre-bind a predicate "is parsed at or below this upper
418
+ # bound?". See _LowerBound for the rationale.
419
+ if version is None:
420
+ self._below: Callable[[Version], bool] | None = None
421
+ elif isinstance(version, _BoundaryVersion):
422
+ # Standard specifiers only ever produce AFTER_LOCALS upper
423
+ # bounds (from <=v / ==v / !=v with no local). Complement
424
+ # reverses bound roles, so a range whose lower bound is
425
+ # AFTER_POSTS(v) becomes an upper bound after complementing.
426
+ if version._kind == _BoundaryKind.AFTER_LOCALS:
427
+ self._below = _make_below_after_locals(version.version)
428
+ else:
429
+ self._below = _make_below_after_posts(version.version)
430
+ elif inclusive:
431
+ self._below = version.__ge__
432
+ else:
433
+ self._below = version.__gt__
434
+
435
+ def __eq__(self, other: object) -> bool:
436
+ if not isinstance(other, _UpperBound):
437
+ return NotImplemented # pragma: no cover
438
+ return self.version == other.version and self.inclusive == other.inclusive
439
+
440
+ def __lt__(self, other: _UpperBound) -> bool:
441
+ if not isinstance(other, _UpperBound): # pragma: no cover
442
+ return NotImplemented
443
+ # Nothing < +inf (except +inf itself).
444
+ if self.version is None:
445
+ return False
446
+ if other.version is None:
447
+ return True
448
+ if self.version != other.version:
449
+ return self.version < other.version
450
+ # ``v) < v]``: exclusive ends earlier.
451
+ return not self.inclusive and other.inclusive
452
+
453
+ def __hash__(self) -> int:
454
+ return hash((self.version, self.inclusive))
455
+
456
+ def __repr__(self) -> str:
457
+ bracket = "]" if self.inclusive else ")"
458
+ return f"<{self.__class__.__name__} {self.version!r}{bracket}>"
459
+
460
+
461
+ if TYPE_CHECKING:
462
+ #: A single contiguous version range, as a (lower, upper) pair.
463
+ _VersionRange = tuple[_LowerBound, _UpperBound]
464
+
465
+
466
+ _NEG_INF = _LowerBound(None, False)
467
+ _POS_INF = _UpperBound(None, False)
468
+ _FULL_RANGE: tuple[_VersionRange] = ((_NEG_INF, _POS_INF),)
469
+
470
+
471
+ def _range_is_empty(lower: _LowerBound, upper: _UpperBound) -> bool:
472
+ """True when the range ``(lower, upper)`` contains no versions."""
473
+ if lower.version is None or upper.version is None:
474
+ return False
475
+ if lower.version == upper.version:
476
+ return not (lower.inclusive and upper.inclusive)
477
+ return lower.version > upper.version
478
+
479
+
480
+ def _intersect_ranges(
481
+ left: Sequence[_VersionRange],
482
+ right: Sequence[_VersionRange],
483
+ ) -> list[_VersionRange]:
484
+ """Intersect two sorted, non-overlapping range lists (two-pointer merge)."""
485
+ result: list[_VersionRange] = []
486
+ left_index = right_index = 0
487
+ while left_index < len(left) and right_index < len(right):
488
+ left_lower, left_upper = left[left_index]
489
+ right_lower, right_upper = right[right_index]
490
+
491
+ lower = max(left_lower, right_lower)
492
+ upper = min(left_upper, right_upper)
493
+
494
+ if not _range_is_empty(lower, upper):
495
+ result.append((lower, upper))
496
+
497
+ # Advance whichever side has the smaller upper bound.
498
+ if left_upper < right_upper:
499
+ left_index += 1
500
+ else:
501
+ right_index += 1
502
+
503
+ return result
504
+
505
+
506
+ def _union_ranges(
507
+ left: Sequence[_VersionRange],
508
+ right: Sequence[_VersionRange],
509
+ ) -> list[_VersionRange]:
510
+ """Union two sorted, non-overlapping range lists.
511
+
512
+ Linear merge over the two pre-sorted inputs followed by a single
513
+ coalescing pass: adjacent or overlapping ranges collapse so the
514
+ result is itself sorted and non-overlapping.
515
+ """
516
+ if not left:
517
+ return list(right)
518
+ if not right:
519
+ return list(left)
520
+
521
+ # Merge two sorted lists by lower bound (linear, no resort).
522
+ merged_input: list[_VersionRange] = []
523
+ left_index = right_index = 0
524
+ while left_index < len(left) and right_index < len(right):
525
+ if left[left_index][0] <= right[right_index][0]:
526
+ merged_input.append(left[left_index])
527
+ left_index += 1
528
+ else:
529
+ merged_input.append(right[right_index])
530
+ right_index += 1
531
+ merged_input.extend(left[left_index:])
532
+ merged_input.extend(right[right_index:])
533
+
534
+ merged: list[_VersionRange] = [merged_input[0]]
535
+ for lower, upper in merged_input[1:]:
536
+ prev_lower, prev_upper = merged[-1]
537
+
538
+ # Adjacent ranges merge when the previous upper sits at or past
539
+ # the new lower; +inf/-inf short-circuits collapse the
540
+ # unbounded cases.
541
+ if prev_upper.version is None:
542
+ overlaps = True
543
+ elif lower.version is None:
544
+ overlaps = True # pragma: no cover (merged_input sorted by lower)
545
+ elif prev_upper.version > lower.version:
546
+ overlaps = True
547
+ elif prev_upper.version == lower.version:
548
+ overlaps = prev_upper.inclusive or lower.inclusive
549
+ else:
550
+ overlaps = False
551
+
552
+ if overlaps:
553
+ new_upper = max(prev_upper, upper)
554
+ merged[-1] = (prev_lower, new_upper)
555
+ else:
556
+ merged.append((lower, upper))
557
+
558
+ return merged
559
+
560
+
561
+ def _complement_ranges(
562
+ ranges: Sequence[_VersionRange],
563
+ ) -> list[_VersionRange]:
564
+ """Complement a sorted, non-overlapping range list.
565
+
566
+ Yields the gaps between ranges plus a leading gap before the first
567
+ range and a trailing gap after the last. Bound inclusivity flips
568
+ so complement-of-complement round-trips back to the input.
569
+ """
570
+ if not ranges:
571
+ return list(_FULL_RANGE)
572
+
573
+ result: list[_VersionRange] = []
574
+ prev_upper: _UpperBound | None = None
575
+
576
+ for lower, upper in ranges:
577
+ if prev_upper is None:
578
+ # Leading gap from -inf up to the first range's lower.
579
+ if lower.version is not None:
580
+ gap_upper = _UpperBound(lower.version, not lower.inclusive)
581
+ result.append((_NEG_INF, gap_upper))
582
+ else:
583
+ gap_lower = _LowerBound(prev_upper.version, not prev_upper.inclusive)
584
+ gap_upper = _UpperBound(lower.version, not lower.inclusive)
585
+ # Adjacent ranges in the input are non-touching by
586
+ # construction, so the gap between them is non-empty.
587
+ if not _range_is_empty(gap_lower, gap_upper): # pragma: no branch
588
+ result.append((gap_lower, gap_upper))
589
+ prev_upper = upper
590
+
591
+ # Trailing gap from the final range's upper to +inf.
592
+ if prev_upper is not None and prev_upper.version is not None:
593
+ gap_lower = _LowerBound(prev_upper.version, not prev_upper.inclusive)
594
+ result.append((gap_lower, _POS_INF))
595
+
596
+ return result
597
+
598
+
599
+ def _filter_by_ranges(
600
+ ranges: Sequence[_VersionRange],
601
+ iterable: Iterable[Any],
602
+ key: Callable[[Any], Version | str] | None,
603
+ prereleases: bool | None,
604
+ ) -> Iterator[Any]:
605
+ """Filter *iterable* against precomputed version *ranges*.
606
+
607
+ With ``prereleases=None``, the PEP 440 default applies: pre-releases
608
+ are excluded unless no final matches, in which case buffered
609
+ pre-releases come out at the end.
610
+ """
611
+ if prereleases is None:
612
+ # PEP 440 default: yield finals immediately; buffer
613
+ # pre-releases until at least one final has been emitted.
614
+ nonfinal_buffer: list[Any] = []
615
+ found_final = False
616
+
617
+ if len(ranges) == 1:
618
+ lower, upper = ranges[0]
619
+ above = lower._above
620
+ below = upper._below
621
+ for item in iterable:
622
+ parsed = _coerce_version(item if key is None else key(item))
623
+ if parsed is None:
624
+ continue
625
+ if above is not None and not above(parsed):
626
+ continue
627
+ if below is not None and not below(parsed):
628
+ continue
629
+ if parsed.is_prerelease:
630
+ if not found_final:
631
+ nonfinal_buffer.append(item)
632
+ else:
633
+ found_final = True
634
+ yield item
635
+ if not found_final:
636
+ yield from nonfinal_buffer
637
+ return
638
+
639
+ for item in iterable:
640
+ parsed = _coerce_version(item if key is None else key(item))
641
+ if parsed is None:
642
+ continue
643
+ for lower, upper in ranges:
644
+ above = lower._above
645
+ if above is not None and not above(parsed):
646
+ break
647
+ below = upper._below
648
+ if below is None or below(parsed):
649
+ if parsed.is_prerelease:
650
+ if not found_final:
651
+ nonfinal_buffer.append(item)
652
+ else:
653
+ found_final = True
654
+ yield item
655
+ break
656
+ if not found_final:
657
+ yield from nonfinal_buffer
658
+ return
659
+
660
+ exclude_prereleases = prereleases is False
661
+
662
+ if len(ranges) == 1:
663
+ # Hot path: most specifiers and small SpecifierSets reduce to
664
+ # a single contiguous range.
665
+ lower, upper = ranges[0]
666
+ above = lower._above
667
+ below = upper._below
668
+ for item in iterable:
669
+ parsed = _coerce_version(item if key is None else key(item))
670
+ if parsed is None:
671
+ continue
672
+ if exclude_prereleases and parsed.is_prerelease:
673
+ continue
674
+ if above is not None and not above(parsed):
675
+ continue
676
+ if below is None or below(parsed):
677
+ yield item
678
+ return
679
+
680
+ for item in iterable:
681
+ parsed = _coerce_version(item if key is None else key(item))
682
+ if parsed is None:
683
+ continue
684
+ if exclude_prereleases and parsed.is_prerelease:
685
+ continue
686
+ for lower, upper in ranges:
687
+ above = lower._above
688
+ if above is not None and not above(parsed):
689
+ break
690
+ below = upper._below
691
+ if below is None or below(parsed):
692
+ yield item
693
+ break
694
+
695
+
696
+ def _matches_bounds_only(
697
+ bounds: Sequence[_VersionRange],
698
+ item: Version,
699
+ ) -> bool:
700
+ """Pure-bounds membership check for a parsed Version."""
701
+ if not bounds:
702
+ return False
703
+ if len(bounds) == 1:
704
+ lower, upper = bounds[0]
705
+ above = lower._above
706
+ if above is not None and not above(item):
707
+ return False
708
+ below = upper._below
709
+ return below is None or below(item)
710
+ for lower, upper in bounds:
711
+ above = lower._above
712
+ if above is not None and not above(item):
713
+ return False
714
+ below = upper._below
715
+ if below is None or below(item):
716
+ return True
717
+ return False
718
+
719
+
720
+ def _bound_match_string(bounds: Sequence[_VersionRange], s: str) -> bool:
721
+ """Bound-only check for the case-folded string *s*.
722
+
723
+ Full-range bounds admit any string. Other shapes require *s* to
724
+ parse and fall inside the intervals.
725
+ """
726
+ if tuple(bounds) == _FULL_RANGE:
727
+ return True
728
+ parsed = _coerce_version(s)
729
+ if parsed is None:
730
+ return False
731
+ return _matches_bounds_only(bounds, parsed)
732
+
733
+
734
+ def _lowest_release_at_or_above(
735
+ value: Version | _BoundaryVersion | None,
736
+ ) -> Version | None:
737
+ """Smallest non-pre-release version at or above *value*, or None."""
738
+ if value is None:
739
+ return None
740
+ if isinstance(value, _BoundaryVersion):
741
+ inner_version = value.version
742
+ if inner_version.is_prerelease:
743
+ # AFTER_LOCALS(1.0a1) -> nearest non-pre is 1.0
744
+ return inner_version.__replace__(pre=None, dev=None, local=None)
745
+ # AFTER_LOCALS(1.0) -> nearest non-pre is 1.0.post0
746
+ # AFTER_LOCALS(1.0.post0) -> nearest non-pre is 1.0.post1
747
+ next_post = (inner_version.post + 1) if inner_version.post is not None else 0
748
+ return inner_version.__replace__(post=next_post, local=None)
749
+ if not value.is_prerelease:
750
+ return value
751
+ # Strip pre/dev to get the final or post-release form.
752
+ return value.__replace__(pre=None, dev=None, local=None)
753
+
754
+
755
+ def _ranges_are_prerelease_only(ranges: Sequence[_VersionRange]) -> bool:
756
+ """``True`` when every range in *ranges* contains only pre-releases.
757
+
758
+ Used to detect unsatisfiable specifier sets when ``prereleases=False``:
759
+ if every range is pre-release-only, every contained version is excluded.
760
+ """
761
+ for lower, upper in ranges:
762
+ nearest = _lowest_release_at_or_above(lower.version)
763
+ if nearest is None:
764
+ return False
765
+ if upper.version is None or nearest < upper.version:
766
+ return False
767
+ if nearest == upper.version and upper.inclusive:
768
+ return False
769
+ return True
770
+
771
+
772
+ def _wildcard_ranges(op: str, base: Version) -> list[_VersionRange]:
773
+ """Ranges for ``==V.*`` and ``!=V.*``.
774
+
775
+ ``==1.2.*`` -> ``[1.2.dev0, 1.3.dev0)``; ``!=1.2.*`` -> complement.
776
+ """
777
+ lower = _base_dev0(base)
778
+ upper = _next_prefix_dev0(base)
779
+ if op == "==":
780
+ return [(_LowerBound(lower, True), _UpperBound(upper, False))]
781
+ # !=
782
+ return [
783
+ (_NEG_INF, _UpperBound(lower, False)),
784
+ (_LowerBound(upper, True), _POS_INF),
785
+ ]
786
+
787
+
788
+ def _standard_ranges(op: str, version: Version, has_local: bool) -> list[_VersionRange]:
789
+ """Ranges for the standard PEP 440 operators (no wildcard, no ===).
790
+
791
+ *has_local* indicates whether the spec string included a ``+local``
792
+ segment; relevant only for ``==`` / ``!=`` to decide whether the
793
+ upper bound includes V's local family.
794
+ """
795
+ if op == ">=":
796
+ return [(_LowerBound(version, True), _POS_INF)]
797
+
798
+ if op == "<=":
799
+ return [
800
+ (
801
+ _NEG_INF,
802
+ _UpperBound(
803
+ _BoundaryVersion(version, _BoundaryKind.AFTER_LOCALS), True
804
+ ),
805
+ )
806
+ ]
807
+
808
+ if op == ">":
809
+ if version.dev is not None:
810
+ # >V.devN: dev versions have no post-releases, so the
811
+ # next real version is V.dev(N+1).
812
+ lower_bound = version.__replace__(dev=version.dev + 1, local=None)
813
+ return [(_LowerBound(lower_bound, True), _POS_INF)]
814
+ if version.post is not None:
815
+ # >V.postN: next real version is V.post(N+1).dev0.
816
+ lower_bound = version.__replace__(post=version.post + 1, dev=0, local=None)
817
+ return [(_LowerBound(lower_bound, True), _POS_INF)]
818
+ # >V (final or pre-release V): exclude V itself, V+local, and
819
+ # every V.postN per PEP 440.
820
+ return [
821
+ (
822
+ _LowerBound(
823
+ _BoundaryVersion(version, _BoundaryKind.AFTER_POSTS), False
824
+ ),
825
+ _POS_INF,
826
+ )
827
+ ]
828
+
829
+ if op == "<":
830
+ # <V excludes pre-releases of V when V is not a pre-release.
831
+ # V.dev0 is the earliest pre-release of V.
832
+ bound = (
833
+ version if version.is_prerelease else version.__replace__(dev=0, local=None)
834
+ )
835
+ if bound <= _MIN_VERSION:
836
+ return []
837
+ return [(_NEG_INF, _UpperBound(bound, False))]
838
+
839
+ # ==, !=: local versions of V match when the spec has no local segment.
840
+ after_locals = _BoundaryVersion(version, _BoundaryKind.AFTER_LOCALS)
841
+ upper = version if has_local else after_locals
842
+
843
+ if op == "==":
844
+ return [(_LowerBound(version, True), _UpperBound(upper, True))]
845
+
846
+ if op == "!=":
847
+ return [
848
+ (_NEG_INF, _UpperBound(version, False)),
849
+ (_LowerBound(upper, False), _POS_INF),
850
+ ]
851
+
852
+ if op == "~=":
853
+ prefix = version.__replace__(release=version.release[:-1])
854
+ return [
855
+ (_LowerBound(version, True), _UpperBound(_next_prefix_dev0(prefix), False))
856
+ ]
857
+
858
+ raise ValueError(f"Unknown operator: {op!r}") # pragma: no cover
859
+
860
+
861
+ def _format_lower(bound: _LowerBound) -> str:
862
+ if bound.version is None:
863
+ return "(-inf"
864
+ bracket = "[" if bound.inclusive else "("
865
+ inner = (
866
+ bound.version.version
867
+ if isinstance(bound.version, _BoundaryVersion)
868
+ else bound.version
869
+ )
870
+ return f"{bracket}{inner}"
871
+
872
+
873
+ def _format_upper(bound: _UpperBound) -> str:
874
+ if bound.version is None:
875
+ return "+inf)"
876
+ bracket = "]" if bound.inclusive else ")"
877
+ inner = (
878
+ bound.version.version
879
+ if isinstance(bound.version, _BoundaryVersion)
880
+ else bound.version
881
+ )
882
+ return f"{inner}{bracket}"
883
+
884
+
885
+ def _pack_bound(bound: _LowerBound | _UpperBound) -> _PackedBound:
886
+ """Serialize a bound to a primitive triple. See _PackedBound."""
887
+ bound_version = bound.version
888
+ if bound_version is None:
889
+ return (None, bound.inclusive, None)
890
+ if isinstance(bound_version, _BoundaryVersion):
891
+ return (str(bound_version.version), bound.inclusive, bound_version._kind.name)
892
+ return (str(bound_version), bound.inclusive, None)
893
+
894
+
895
+ def _unpack_bound(
896
+ cls: type[_LowerBound | _UpperBound],
897
+ packed: _PackedBound,
898
+ ) -> _LowerBound | _UpperBound:
899
+ """Reverse of _pack_bound."""
900
+ version_str, inclusive, kind_name = packed
901
+ if version_str is None:
902
+ return cls(None, inclusive)
903
+ base = Version(version_str)
904
+ if kind_name is not None:
905
+ return cls(_BoundaryVersion(base, _BoundaryKind[kind_name]), inclusive)
906
+ return cls(base, inclusive)
907
+
908
+
909
+ def _restore_version_range(
910
+ packed_bounds: tuple[tuple[_PackedBound, _PackedBound], ...],
911
+ arbitrary: str | None = None,
912
+ admit: tuple[str, ...] | None = None,
913
+ reject: tuple[str, ...] | None = None,
914
+ ) -> VersionRange:
915
+ """Pickle restorer; bypasses the ``__new__`` guard via ``_build``.
916
+
917
+ The ``arbitrary`` arg is the pre-admit/reject slot from earlier
918
+ betas. New pickles pass ``admit`` and ``reject`` instead. The
919
+ matched set is preserved either way.
920
+ """
921
+ bounds = tuple(
922
+ (
923
+ typing.cast("_LowerBound", _unpack_bound(_LowerBound, lower)),
924
+ typing.cast("_UpperBound", _unpack_bound(_UpperBound, upper)),
925
+ )
926
+ for lower, upper in packed_bounds
927
+ )
928
+ if admit is not None or reject is not None:
929
+ return VersionRange._build(
930
+ bounds,
931
+ admit=frozenset(admit or ()),
932
+ reject=frozenset(reject or ()),
933
+ )
934
+ if arbitrary is None:
935
+ return VersionRange._build(bounds)
936
+ # Legacy ``arbitrary`` matched ``{arbitrary}`` if the literal was
937
+ # in bounds, empty otherwise.
938
+ literal_lower = arbitrary.lower()
939
+ legacy_range = VersionRange._build(bounds)
940
+ if literal_lower in legacy_range:
941
+ return VersionRange._build((), admit=frozenset({literal_lower}))
942
+ return VersionRange._build(())
943
+
944
+
945
+ # VersionRange to SpecifierSet conversion is partial: not every range
946
+ # has a SpecifierSet form. Examples that have no single specifier:
947
+ # - PEP 440 ``<V`` excludes pre-releases of V, so the mathematical
948
+ # complement of ``>=V`` (which keeps those pre-releases) has no
949
+ # single specifier.
950
+ # - PEP 440 ``==V`` matches ``V+local`` too, so the strict singleton
951
+ # ``[V, V]`` produced by :meth:`VersionRange.singleton` has none.
952
+ # - Disjoint unions whose gap is not a complete ``==V.*`` family or a
953
+ # ``==V`` family cannot be expressed as ``base & !=...``.
954
+
955
+
956
+ def _is_dev0_version(v: Version) -> bool:
957
+ """``True`` when *v* is exactly ``X[.Y]*.dev0``: the form ``<X`` produces."""
958
+ return v.dev == 0 and v.pre is None and v.post is None and v.local is None
959
+
960
+
961
+ class _NotEncodable:
962
+ """Sentinel for "this bound has no PEP 440 specifier representation"."""
963
+
964
+ __slots__ = ()
965
+
966
+
967
+ _NOT_ENCODABLE: Final = _NotEncodable()
968
+
969
+
970
+ def _encode_lower(lower: _LowerBound) -> list[str] | _NotEncodable:
971
+ """Encode a lower bound as a list of specifier fragments.
972
+
973
+ ``[]`` for ``-inf``, one or more fragments otherwise, or
974
+ ``_NOT_ENCODABLE`` when the shape has no specifier form.
975
+ AFTER_LOCALS lower bounds emit two fragments (``>=V`` plus
976
+ ``!=V``) since the boundary excludes V and every V+local.
977
+ """
978
+ lower_version = lower.version
979
+ if lower_version is None:
980
+ return []
981
+ if isinstance(lower_version, _BoundaryVersion):
982
+ if lower_version._kind == _BoundaryKind.AFTER_POSTS and not lower.inclusive:
983
+ return [f">{lower_version.version}"]
984
+ if lower_version._kind == _BoundaryKind.AFTER_LOCALS:
985
+ # Strictly above V's local family. ``>=V,!=V`` produces
986
+ # ``[V, +inf)`` minus ``[V, AFTER_LOCALS(V)]``, leaving
987
+ # ``(AFTER_LOCALS(V), +inf)``.
988
+ return [f">={lower_version.version}", f"!={lower_version.version}"]
989
+ # AFTER_POSTS lower with inclusive=True is unreachable from
990
+ # any specifier or set-algebra operation; defensive guard.
991
+ return _NOT_ENCODABLE # pragma: no cover
992
+ if lower.inclusive:
993
+ return [f">={lower_version}"]
994
+ return _NOT_ENCODABLE
995
+
996
+
997
+ def _encode_upper(upper: _UpperBound) -> list[str] | _NotEncodable:
998
+ """Encode an upper bound as a list of specifier fragments.
999
+
1000
+ ``[]`` for ``+inf``, one or more fragments otherwise, or
1001
+ ``_NOT_ENCODABLE`` when the shape has no specifier form.
1002
+ """
1003
+ upper_version = upper.version
1004
+ if upper_version is None:
1005
+ return []
1006
+ if isinstance(upper_version, _BoundaryVersion):
1007
+ if upper_version._kind == _BoundaryKind.AFTER_LOCALS and upper.inclusive:
1008
+ return [f"<={upper_version.version}"]
1009
+ return _NOT_ENCODABLE
1010
+ if not upper.inclusive:
1011
+ if _is_dev0_version(upper_version):
1012
+ # <V produces upper = V.dev0 (excl); strip the synthetic
1013
+ # dev0 to recover the original V.
1014
+ return [f"<{upper_version.__replace__(dev=None)}"]
1015
+ # V (excl) upper: strictly less than V cmpkey-wise, including
1016
+ # V's pre-releases. <=V,!=V produces (-inf, AFTER_LOCALS(V)]
1017
+ # minus [V, AFTER_LOCALS(V)], leaving (-inf, V (excl)).
1018
+ return [f"<={upper_version}", f"!={upper_version}"]
1019
+ return _NOT_ENCODABLE
1020
+
1021
+
1022
+ def _encode_interval(
1023
+ lower: _LowerBound,
1024
+ upper: _UpperBound,
1025
+ ) -> list[str] | None:
1026
+ """Encode one interval as a list of specifier fragments, or ``None``.
1027
+
1028
+ Special-cases ``[V, V]`` (singleton interval) when V carries a
1029
+ local segment: ``==V+local`` matches only that literal, so the
1030
+ interval round-trips. Without a local, no specifier form exists
1031
+ (``==V`` is wider since it also matches ``V+local``).
1032
+ """
1033
+ if (
1034
+ lower.version is not None
1035
+ and upper.version is not None
1036
+ and not isinstance(lower.version, _BoundaryVersion)
1037
+ and not isinstance(upper.version, _BoundaryVersion)
1038
+ and lower.inclusive
1039
+ and upper.inclusive
1040
+ and lower.version == upper.version
1041
+ and lower.version.local is not None
1042
+ ):
1043
+ return [f"=={lower.version}"]
1044
+ lower_parts = _encode_lower(lower)
1045
+ if isinstance(lower_parts, _NotEncodable):
1046
+ return None
1047
+ upper_parts = _encode_upper(upper)
1048
+ if isinstance(upper_parts, _NotEncodable):
1049
+ return None
1050
+ return lower_parts + upper_parts
1051
+
1052
+
1053
+ def _detect_not_equal(
1054
+ left_upper: _UpperBound,
1055
+ right_lower: _LowerBound,
1056
+ ) -> Version | None:
1057
+ """If ``[..., V (excl)] [AFTER_LOCALS(V) (excl), ...]`` matches, return V.
1058
+
1059
+ The gap shape ``!=V`` produces when intersected with surrounding
1060
+ bounds. Only ``!=V`` pattern that can appear inside a multi-interval
1061
+ range.
1062
+ """
1063
+ if isinstance(left_upper.version, _BoundaryVersion):
1064
+ return None
1065
+ if left_upper.version is None or left_upper.inclusive:
1066
+ return None
1067
+ if not isinstance(right_lower.version, _BoundaryVersion):
1068
+ return None
1069
+ if right_lower.version._kind != _BoundaryKind.AFTER_LOCALS:
1070
+ return None
1071
+ if right_lower.inclusive:
1072
+ # AFTER_LOCALS lower with inclusive=True does not arise from
1073
+ # any specifier or set-algebra operation; defensive guard.
1074
+ return None # pragma: no cover
1075
+ if right_lower.version.version != left_upper.version:
1076
+ # The ``!=V`` pattern is contiguous; mismatched V means a union
1077
+ # of unrelated ranges. Defensive.
1078
+ return None # pragma: no cover
1079
+ return left_upper.version
1080
+
1081
+
1082
+ def _detect_not_equal_wildcard(
1083
+ left_upper: _UpperBound,
1084
+ right_lower: _LowerBound,
1085
+ ) -> Version | None:
1086
+ """If ``[..., V.dev0 (excl)] [V_next.dev0 (incl), ...]`` matches, return V.
1087
+
1088
+ The gap shape ``!=V.*`` produces. ``V`` and ``V_next`` share an
1089
+ epoch and a release prefix differing only in the final component
1090
+ being incremented by one. Returns the prefix version (without the
1091
+ synthetic ``.dev0``) so the caller can write ``!=V.*``.
1092
+ """
1093
+ left_upper_v = left_upper.version
1094
+ right_lower_v = right_lower.version
1095
+ if isinstance(left_upper_v, _BoundaryVersion) or isinstance(
1096
+ right_lower_v, _BoundaryVersion
1097
+ ):
1098
+ return None
1099
+ if left_upper_v is None or right_lower_v is None:
1100
+ # First-interval upper or last-interval lower at infinity means
1101
+ # the interval is the universe and no second interval exists.
1102
+ return None # pragma: no cover
1103
+ if left_upper.inclusive or not right_lower.inclusive:
1104
+ return None
1105
+ if not (_is_dev0_version(left_upper_v) and _is_dev0_version(right_lower_v)):
1106
+ return None
1107
+ if left_upper_v.epoch != right_lower_v.epoch:
1108
+ return None
1109
+ left_release = left_upper_v.release
1110
+ right_release = right_lower_v.release
1111
+ if len(left_release) != len(right_release) or not left_release:
1112
+ return None
1113
+ # All components except the last must match; the last increments by 1.
1114
+ if left_release[:-1] != right_release[:-1]:
1115
+ return None
1116
+ if right_release[-1] != left_release[-1] + 1:
1117
+ return None
1118
+ return left_upper_v.__replace__(dev=None)
1119
+
1120
+
1121
+ class VersionRange:
1122
+ """A set of :class:`~packaging.version.Version` values, expressed as
1123
+ a union of disjoint intervals on the PEP 440 version ordering.
1124
+
1125
+ Construct with :meth:`from_specifier` / :meth:`from_specifier_set`,
1126
+ or via :meth:`Specifier.to_range` / :meth:`SpecifierSet.to_range`.
1127
+ Compose with :meth:`intersection`, :meth:`union`, :meth:`complement`
1128
+ (and the ``&`` / ``|`` / ``~`` operator aliases).
1129
+
1130
+ >>> r = VersionRange.from_specifier_set(SpecifierSet(">=1.0,<2.0"))
1131
+ >>> "1.5" in r
1132
+ True
1133
+ >>> "2.0" in r
1134
+ False
1135
+ >>> bool(VersionRange.from_specifier_set(SpecifierSet(">=2.0,<1.0")))
1136
+ False
1137
+
1138
+ PEP 440's ``===`` operator matches a candidate string verbatim
1139
+ (case-insensitive) rather than a set of :class:`Version` values.
1140
+ Ranges built from ``===`` specifiers still support membership,
1141
+ set operations, and conversion back to a :class:`SpecifierSet`;
1142
+ matching follows the literal-equality rule instead of the
1143
+ version-ordering rule.
1144
+ """
1145
+
1146
+ __slots__ = ("_admit", "_bounds", "_reject")
1147
+ _bounds: tuple[_VersionRange, ...]
1148
+ #: Case-folded strings the range admits in addition to its bounds.
1149
+ #: ``===wat`` produces ``_admit = {"wat"}``.
1150
+ _admit: frozenset[str]
1151
+ #: Case-folded strings the range rejects. Overrides ``_admit`` and
1152
+ #: ``_bounds``. Populated by :meth:`complement` of a range whose
1153
+ #: ``_admit`` was non-empty.
1154
+ _reject: frozenset[str]
1155
+
1156
+ def __new__(cls, *args: object, **kwargs: object) -> VersionRange: # noqa: PYI034
1157
+ raise TypeError(
1158
+ "cannot create 'VersionRange' instances directly; use "
1159
+ "VersionRange.from_specifier(), "
1160
+ "VersionRange.from_specifier_set(), "
1161
+ "Specifier.to_range(), or SpecifierSet.to_range() instead"
1162
+ )
1163
+
1164
+ @classmethod
1165
+ def _build(
1166
+ cls,
1167
+ bounds: tuple[_VersionRange, ...],
1168
+ admit: frozenset[str] = frozenset(),
1169
+ reject: frozenset[str] = frozenset(),
1170
+ ) -> VersionRange:
1171
+ """Internal factory; bypasses :meth:`__new__`.
1172
+
1173
+ Drops admit literals already covered by bounds and reject
1174
+ literals already outside bounds. Reject wins over admit on
1175
+ overlap.
1176
+ """
1177
+ if admit and reject:
1178
+ admit = admit - reject
1179
+ if admit:
1180
+ admit = frozenset(s for s in admit if not _bound_match_string(bounds, s))
1181
+ if reject:
1182
+ reject = frozenset(s for s in reject if _bound_match_string(bounds, s))
1183
+ instance = object.__new__(cls)
1184
+ instance._bounds = bounds
1185
+ instance._admit = admit
1186
+ instance._reject = reject
1187
+ return instance
1188
+
1189
+ def _has_literals(self) -> bool:
1190
+ """``True`` when ``_admit`` or ``_reject`` is non-empty."""
1191
+ return bool(self._admit) or bool(self._reject)
1192
+
1193
+ @classmethod
1194
+ def empty(cls) -> VersionRange:
1195
+ """Return the empty range. No version satisfies it.
1196
+
1197
+ >>> VersionRange.empty().is_empty
1198
+ True
1199
+ >>> "1.0" in VersionRange.empty()
1200
+ False
1201
+ """
1202
+ return cls._build(())
1203
+
1204
+ @classmethod
1205
+ def full(cls) -> VersionRange:
1206
+ """Return the full range. Every PEP 440 version satisfies it.
1207
+
1208
+ >>> "1.0" in VersionRange.full()
1209
+ True
1210
+ >>> VersionRange.full().is_empty
1211
+ False
1212
+ """
1213
+ return cls._build(_FULL_RANGE)
1214
+
1215
+ @classmethod
1216
+ def singleton(cls, version: Version | str) -> VersionRange:
1217
+ """Return the range that contains only *version*.
1218
+
1219
+ >>> r = VersionRange.singleton("1.2.3")
1220
+ >>> "1.2.3" in r
1221
+ True
1222
+ >>> "1.2.4" in r
1223
+ False
1224
+
1225
+ :raises packaging.version.InvalidVersion: if *version* is a
1226
+ string that does not parse as a PEP 440 version.
1227
+ """
1228
+ if not isinstance(version, Version):
1229
+ version = Version(version)
1230
+ lower = _LowerBound(version, True)
1231
+ upper = _UpperBound(version, True)
1232
+ return cls._build(((lower, upper),))
1233
+
1234
+ def intersection(self, other: VersionRange) -> VersionRange:
1235
+ """Range containing exactly the versions in both *self* and *other*.
1236
+
1237
+ >>> a = VersionRange.from_specifier_set(SpecifierSet(">=1.0"))
1238
+ >>> b = VersionRange.from_specifier_set(SpecifierSet("<2.0"))
1239
+ >>> ab = VersionRange.from_specifier_set(SpecifierSet(">=1.0,<2.0"))
1240
+ >>> a.intersection(b) == ab
1241
+ True
1242
+ """
1243
+ if not self._has_literals() and not other._has_literals():
1244
+ return self._build(tuple(_intersect_ranges(self._bounds, other._bounds)))
1245
+ new_bounds = tuple(_intersect_ranges(self._bounds, other._bounds))
1246
+ return self._combine_literals(other, new_bounds, intersect=True)
1247
+
1248
+ def union(self, other: VersionRange) -> VersionRange:
1249
+ """Range containing every version in *self* or *other*.
1250
+
1251
+ >>> a = VersionRange.singleton("1.0")
1252
+ >>> b = VersionRange.singleton("2.0")
1253
+ >>> "1.0" in a.union(b) and "2.0" in a.union(b)
1254
+ True
1255
+ >>> "1.5" in a.union(b)
1256
+ False
1257
+ """
1258
+ if not self._has_literals() and not other._has_literals():
1259
+ return self._build(tuple(_union_ranges(self._bounds, other._bounds)))
1260
+ new_bounds = tuple(_union_ranges(self._bounds, other._bounds))
1261
+ return self._combine_literals(other, new_bounds, intersect=False)
1262
+
1263
+ def complement(self) -> VersionRange:
1264
+ """Range containing every version *not* in *self*.
1265
+
1266
+ >>> r = VersionRange.from_specifier(Specifier(">=1.0"))
1267
+ >>> "0.5" in r.complement()
1268
+ True
1269
+ >>> "1.5" in r.complement()
1270
+ False
1271
+ >>> r.complement().complement() == r
1272
+ True
1273
+ """
1274
+ if not self._has_literals():
1275
+ return self._build(tuple(_complement_ranges(self._bounds)))
1276
+ # Swap the admit and reject sets, complement the bounds.
1277
+ # ``_build`` drops anything now redundant against the new bounds.
1278
+ return self._build(
1279
+ tuple(_complement_ranges(self._bounds)),
1280
+ admit=self._reject,
1281
+ reject=self._admit,
1282
+ )
1283
+
1284
+ def _combine_literals(
1285
+ self,
1286
+ other: VersionRange,
1287
+ new_bounds: tuple[_VersionRange, ...],
1288
+ *,
1289
+ intersect: bool,
1290
+ ) -> VersionRange:
1291
+ """Resolve admit/reject for ``self & other`` or ``self | other``.
1292
+
1293
+ The bound-only result is already in *new_bounds*. For each
1294
+ literal seen on either side, decide whether the combined
1295
+ predicate (AND for intersection, OR for union) admits it, then
1296
+ record an explicit admit or reject when the new bounds would
1297
+ give the wrong answer on their own.
1298
+ """
1299
+ admits: set[str] = set()
1300
+ rejects: set[str] = set()
1301
+ for literal in self._admit | self._reject | other._admit | other._reject:
1302
+ self_in = self._matches_literal(literal)
1303
+ other_in = other._matches_literal(literal)
1304
+ want = (self_in and other_in) if intersect else (self_in or other_in)
1305
+ bound_in = _bound_match_string(new_bounds, literal)
1306
+ if want and not bound_in:
1307
+ admits.add(literal)
1308
+ elif not want and bound_in:
1309
+ rejects.add(literal)
1310
+ return self._build(
1311
+ new_bounds, admit=frozenset(admits), reject=frozenset(rejects)
1312
+ )
1313
+
1314
+ def _matches_literal(self, literal: str) -> bool:
1315
+ """Whether *literal* (case-folded) matches this range's predicate."""
1316
+ if literal in self._reject:
1317
+ return False
1318
+ if literal in self._admit:
1319
+ return True
1320
+ return _bound_match_string(self._bounds, literal)
1321
+
1322
+ def __and__(self, other: object) -> VersionRange:
1323
+ """Operator alias for :meth:`intersection`."""
1324
+ if not isinstance(other, VersionRange):
1325
+ return NotImplemented
1326
+ return self.intersection(other)
1327
+
1328
+ def __or__(self, other: object) -> VersionRange:
1329
+ """Operator alias for :meth:`union`."""
1330
+ if not isinstance(other, VersionRange):
1331
+ return NotImplemented
1332
+ return self.union(other)
1333
+
1334
+ def __invert__(self) -> VersionRange:
1335
+ """Operator alias for :meth:`complement`."""
1336
+ return self.complement()
1337
+
1338
+ def filter(
1339
+ self,
1340
+ iterable: Iterable[Any],
1341
+ key: Callable[[Any], Version | str] | None = None,
1342
+ prereleases: bool | None = None,
1343
+ ) -> Iterator[Any]:
1344
+ """Yield items from *iterable* whose version falls inside the range.
1345
+
1346
+ With *prereleases* ``None`` the PEP 440 default applies:
1347
+ pre-releases are buffered and only emitted if no final release
1348
+ in *iterable* is in range.
1349
+
1350
+ Filtering matches :class:`SpecifierSet.filter` for the same
1351
+ :class:`Specifier` / :class:`SpecifierSet`, including
1352
+ :class:`SpecifierSet("")`'s admission of unparsable strings
1353
+ and the case-insensitive literal match for ``===``.
1354
+
1355
+ >>> r = VersionRange.from_specifier_set(SpecifierSet(">=1.0,<2.0"))
1356
+ >>> list(r.filter(["0.9", "1.5", "2.0"]))
1357
+ ['1.5']
1358
+ """
1359
+ if self._admit or self._reject:
1360
+ return self._filter_with_admission(iterable, key, prereleases)
1361
+ if self._bounds == _FULL_RANGE:
1362
+ # Full-range carve-out: admit any item, parseable or not,
1363
+ # so behaviour matches ``SpecifierSet("").filter``.
1364
+ return self._filter_with_admission(iterable, key, prereleases)
1365
+ return _filter_by_ranges(self._bounds, iterable, key, prereleases)
1366
+
1367
+ def _filter_with_admission(
1368
+ self,
1369
+ iterable: Iterable[Any],
1370
+ key: Callable[[Any], Version | str] | None,
1371
+ prereleases: bool | None,
1372
+ ) -> Iterator[Any]:
1373
+ """Filter for ranges that admit unparsable strings.
1374
+
1375
+ Used by ``===`` ranges (literal admit/reject) and the full-range
1376
+ carve-out. Same PEP 440 pre-release buffering for both, with a
1377
+ different admission check.
1378
+ """
1379
+ admit_set = self._admit
1380
+ reject_set = self._reject
1381
+ full_bounds = self._bounds == _FULL_RANGE
1382
+
1383
+ def admit(item: Any) -> tuple[bool, Version | None]: # noqa: ANN401
1384
+ raw: Version | str = item if key is None else key(item)
1385
+ raw_lower = str(raw).lower()
1386
+ if reject_set and raw_lower in reject_set:
1387
+ return False, None
1388
+ if admit_set and raw_lower in admit_set:
1389
+ return True, _coerce_version(raw)
1390
+ parsed = _coerce_version(raw)
1391
+ if parsed is None:
1392
+ return full_bounds, None
1393
+ if not full_bounds and not self._matches_bounds(parsed):
1394
+ return False, None
1395
+ return True, parsed
1396
+
1397
+ if prereleases is True:
1398
+ for item in iterable:
1399
+ ok, _ = admit(item)
1400
+ if ok:
1401
+ yield item
1402
+ return
1403
+
1404
+ if prereleases is False:
1405
+ for item in iterable:
1406
+ ok, parsed = admit(item)
1407
+ if not ok:
1408
+ continue
1409
+ if parsed is not None and parsed.is_prerelease:
1410
+ continue
1411
+ yield item
1412
+ return
1413
+
1414
+ # PEP 440 default: yield finals immediately; buffer the rest
1415
+ # until we know whether any final exists.
1416
+ all_nonfinal: list[Any] = []
1417
+ arbitrary_strings: list[Any] = []
1418
+ found_final = False
1419
+ for item in iterable:
1420
+ ok, parsed = admit(item)
1421
+ if not ok:
1422
+ continue
1423
+ if parsed is None:
1424
+ if found_final:
1425
+ yield item
1426
+ else:
1427
+ arbitrary_strings.append(item)
1428
+ all_nonfinal.append(item)
1429
+ continue
1430
+ if not parsed.is_prerelease:
1431
+ if not found_final:
1432
+ yield from arbitrary_strings
1433
+ arbitrary_strings.clear()
1434
+ found_final = True
1435
+ yield item
1436
+ continue
1437
+ if not found_final:
1438
+ all_nonfinal.append(item)
1439
+ if not found_final:
1440
+ yield from all_nonfinal
1441
+
1442
+ @classmethod
1443
+ def from_specifier(cls, specifier: Specifier) -> VersionRange:
1444
+ """Return the :class:`VersionRange` accepted by *specifier*.
1445
+
1446
+ Results are cached on the *specifier* instance.
1447
+
1448
+ >>> isinstance(VersionRange.from_specifier(Specifier(">=1.0")), VersionRange)
1449
+ True
1450
+ """
1451
+ cached = specifier._range_cache
1452
+ if cached is not None:
1453
+ return cached
1454
+
1455
+ op = specifier.operator
1456
+ if op == "===":
1457
+ arb_result = cls._build((), admit=frozenset({specifier.version.lower()}))
1458
+ specifier._range_cache = arb_result
1459
+ return arb_result
1460
+
1461
+ ver_str = specifier.version
1462
+ result: VersionRange
1463
+ if ver_str.endswith(".*"):
1464
+ base = specifier._require_spec_version(ver_str[:-2])
1465
+ result = cls._build(tuple(_wildcard_ranges(op, base)))
1466
+ else:
1467
+ version = specifier._require_spec_version(ver_str)
1468
+ has_local = "+" in ver_str
1469
+ result = cls._build(tuple(_standard_ranges(op, version, has_local)))
1470
+
1471
+ specifier._range_cache = result
1472
+ return result
1473
+
1474
+ @classmethod
1475
+ def from_specifier_set(cls, specifier_set: SpecifierSet) -> VersionRange:
1476
+ """Return the :class:`VersionRange` accepted by *specifier_set*.
1477
+
1478
+ The intersection of every specifier in the set. An empty
1479
+ :class:`SpecifierSet` yields the unbounded range; an
1480
+ unsatisfiable set yields an empty :class:`VersionRange`.
1481
+ Results are cached on the *specifier_set* instance.
1482
+
1483
+ >>> isinstance(
1484
+ ... VersionRange.from_specifier_set(SpecifierSet(">=1.0,<2.0")),
1485
+ ... VersionRange,
1486
+ ... )
1487
+ True
1488
+ >>> VersionRange.from_specifier_set(SpecifierSet(">=2.0,<1.0")).is_empty
1489
+ True
1490
+ """
1491
+ cached = specifier_set._range_cache
1492
+ if cached is not None:
1493
+ return cached
1494
+
1495
+ # ``===`` literals are handled separately from rangelike specs:
1496
+ # the rangelike specs build the bounds, and a single literal
1497
+ # is admitted only if it also satisfies those bounds.
1498
+ arbitrary_specs = [s for s in specifier_set._specs if s.operator == "==="]
1499
+ rangelike_specs = [s for s in specifier_set._specs if s.operator != "==="]
1500
+
1501
+ if not rangelike_specs:
1502
+ rangelike_result: VersionRange = cls._build(_FULL_RANGE)
1503
+ else:
1504
+ tmp: VersionRange | None = None
1505
+ for s in rangelike_specs:
1506
+ sub = cls.from_specifier(s)
1507
+ if tmp is None:
1508
+ tmp = sub
1509
+ else:
1510
+ tmp = tmp.intersection(sub)
1511
+ if tmp.is_empty:
1512
+ break
1513
+ assert tmp is not None
1514
+ rangelike_result = tmp
1515
+
1516
+ if not arbitrary_specs:
1517
+ specifier_set._range_cache = rangelike_result
1518
+ return rangelike_result
1519
+
1520
+ # Each ``===L_i`` requires the candidate's string to equal L_i.
1521
+ # Distinct literals can never all match, so the result is empty.
1522
+ literals_lower = {s.version.lower() for s in arbitrary_specs}
1523
+ result: VersionRange
1524
+ if len(literals_lower) > 1:
1525
+ result = cls._build(())
1526
+ else:
1527
+ (literal_lower,) = literals_lower
1528
+ if literal_lower in rangelike_result:
1529
+ result = cls._build((), admit=frozenset({literal_lower}))
1530
+ else:
1531
+ result = cls._build(())
1532
+
1533
+ specifier_set._range_cache = result
1534
+ return result
1535
+
1536
+ def to_specifier_set(self) -> SpecifierSet | None:
1537
+ """Return a single :class:`SpecifierSet` whose
1538
+ :meth:`from_specifier_set` yields *self*, or ``None`` if no
1539
+ such set exists.
1540
+
1541
+ :class:`SpecifierSet` cannot express every range. PEP 440's
1542
+ operator set has no syntax for the strict singleton ``{V}`` or
1543
+ for the bounds produced by complementing ``>V``; for those
1544
+ ranges the result is ``None``. Use :meth:`to_specifier_sets`
1545
+ when a tuple of specifier sets is acceptable. The empty range
1546
+ maps to ``SpecifierSet("<0")`` (``<0`` excludes ``0.dev0``,
1547
+ the smallest PEP 440 version); the full range maps to
1548
+ ``SpecifierSet("")``.
1549
+
1550
+ >>> r = VersionRange.from_specifier_set(SpecifierSet(">=1.0,<2.0"))
1551
+ >>> str(r.to_specifier_set())
1552
+ '<2.0,>=1.0'
1553
+ >>> VersionRange.singleton("1.5").to_specifier_set() is None
1554
+ True
1555
+ """
1556
+ # Local import avoids the circular .specifiers <-> .ranges load.
1557
+ from .specifiers import SpecifierSet # noqa: PLC0415
1558
+
1559
+ if self._reject:
1560
+ # No PEP 440 operator excludes a literal string while
1561
+ # admitting other versions.
1562
+ return None
1563
+ if self._admit:
1564
+ return self._admit_to_specifier_set()
1565
+ if self.is_empty:
1566
+ # ``<0`` parses to upper = 0.dev0 (excl), the smallest
1567
+ # possible PEP 440 version, so the range contains nothing.
1568
+ return SpecifierSet("<0")
1569
+ if self._bounds == _FULL_RANGE:
1570
+ return SpecifierSet("")
1571
+
1572
+ # Walk left-to-right, merging adjacent intervals whose gap is
1573
+ # a ``!=V`` or ``!=V.*`` exclusion. The merged outer bounds
1574
+ # plus the chain of ``!=`` fragments form a single SpecifierSet.
1575
+ bounds = list(self._bounds)
1576
+ outer_lower = bounds[0][0]
1577
+ outer_upper = bounds[0][1]
1578
+ exclusions: list[str] = []
1579
+ for next_lower, next_upper in bounds[1:]:
1580
+ not_equal = _detect_not_equal(outer_upper, next_lower)
1581
+ not_equal_wildcard = _detect_not_equal_wildcard(outer_upper, next_lower)
1582
+ if not_equal is not None:
1583
+ exclusions.append(f"!={not_equal}")
1584
+ elif not_equal_wildcard is not None:
1585
+ exclusions.append(f"!={not_equal_wildcard}.*")
1586
+ else:
1587
+ return None
1588
+ outer_upper = next_upper
1589
+
1590
+ outer_parts = _encode_interval(outer_lower, outer_upper)
1591
+ if outer_parts is None:
1592
+ return None
1593
+ return SpecifierSet(",".join(outer_parts + exclusions))
1594
+
1595
+ def to_specifier_sets(self) -> tuple[SpecifierSet, ...] | None:
1596
+ """Return a tuple of :class:`SpecifierSet` whose union equals
1597
+ *self*, or ``None`` if no such tuple exists.
1598
+
1599
+ Looser than :meth:`to_specifier_set`: a range that fits a
1600
+ single :class:`SpecifierSet` returns a one-tuple, otherwise
1601
+ each interval encodes separately. ``None`` only for ranges
1602
+ whose individual intervals still have no PEP 440 specifier
1603
+ (for example the singleton produced by :meth:`singleton`).
1604
+
1605
+ >>> r = (
1606
+ ... VersionRange.from_specifier_set(SpecifierSet(">=1.0,<2.0"))
1607
+ ... | VersionRange.from_specifier_set(SpecifierSet(">=3.0,<4.0"))
1608
+ ... )
1609
+ >>> [str(s) for s in r.to_specifier_sets()]
1610
+ ['<2.0,>=1.0', '<4.0,>=3.0']
1611
+ >>> VersionRange.singleton("1.5").to_specifier_sets() is None
1612
+ True
1613
+ """
1614
+ from .specifiers import SpecifierSet # noqa: PLC0415
1615
+
1616
+ if self._reject:
1617
+ return None
1618
+ if self._admit:
1619
+ single = self._admit_to_specifier_set()
1620
+ if single is None:
1621
+ return None
1622
+ return (single,)
1623
+ if self.is_empty:
1624
+ return (SpecifierSet("<0"),)
1625
+ if self._bounds == _FULL_RANGE:
1626
+ return (SpecifierSet(""),)
1627
+
1628
+ # Prefer the single-set form when it exists; that catches
1629
+ # multi-interval ``!=V`` / ``!=V.*`` patterns the per-interval
1630
+ # encoder rejects.
1631
+ single = self.to_specifier_set()
1632
+ if single is not None:
1633
+ return (single,)
1634
+
1635
+ out: list[SpecifierSet] = []
1636
+ for lower, upper in self._bounds:
1637
+ parts = _encode_interval(lower, upper)
1638
+ if parts is None:
1639
+ return None
1640
+ out.append(SpecifierSet(",".join(parts)))
1641
+ return tuple(out)
1642
+
1643
+ def _admit_to_specifier_set(self) -> SpecifierSet | None:
1644
+ """Encode a single ``===L`` range as ``SpecifierSet("===L")``.
1645
+
1646
+ Returns ``None`` for shapes PEP 440 cannot express: multiple
1647
+ admit literals (no ``=== A or === B`` syntax), or admit
1648
+ combined with a non-empty bound set.
1649
+ """
1650
+ from .specifiers import SpecifierSet # noqa: PLC0415
1651
+
1652
+ if len(self._admit) != 1 or self._bounds:
1653
+ return None
1654
+ (literal,) = self._admit
1655
+ return SpecifierSet(f"==={literal}")
1656
+
1657
+ def __reduce__(self) -> tuple[object, ...]:
1658
+ # Pickle to a primitive form (see ``_PackedBound``). The legacy
1659
+ # ``arbitrary`` slot is kept for older restorer signatures.
1660
+ return (
1661
+ _restore_version_range,
1662
+ (
1663
+ tuple(
1664
+ (_pack_bound(lower), _pack_bound(upper))
1665
+ for lower, upper in self._bounds
1666
+ ),
1667
+ None,
1668
+ tuple(sorted(self._admit)),
1669
+ tuple(sorted(self._reject)),
1670
+ ),
1671
+ )
1672
+
1673
+ @property
1674
+ def is_empty(self) -> bool:
1675
+ """``True`` if no version or string satisfies this range.
1676
+
1677
+ >>> VersionRange.from_specifier_set(SpecifierSet(">=2,<1")).is_empty
1678
+ True
1679
+ >>> VersionRange.from_specifier_set(SpecifierSet(">=1,<2")).is_empty
1680
+ False
1681
+ """
1682
+ return not self._bounds and not self._admit
1683
+
1684
+ @property
1685
+ def is_prerelease_only(self) -> bool:
1686
+ """``True`` when every match is a PEP 440 pre-release.
1687
+
1688
+ Used by :meth:`SpecifierSet.is_unsatisfiable` to detect sets
1689
+ that admit no candidate under the default ``prereleases=False``
1690
+ reading. Returns ``False`` for the empty range.
1691
+
1692
+ >>> r = VersionRange.from_specifier_set(SpecifierSet(">=1.0a1,<1.0rc1"))
1693
+ >>> r.is_prerelease_only
1694
+ True
1695
+ >>> VersionRange.from_specifier(Specifier(">=1.0")).is_prerelease_only
1696
+ False
1697
+ """
1698
+ if self.is_empty:
1699
+ return False
1700
+ if self._reject:
1701
+ return False
1702
+ for literal in self._admit:
1703
+ parsed = _coerce_version(literal)
1704
+ if parsed is None or not parsed.is_prerelease:
1705
+ return False
1706
+ if self._bounds:
1707
+ return _ranges_are_prerelease_only(self._bounds)
1708
+ return True
1709
+
1710
+ def __bool__(self) -> bool:
1711
+ """``False`` when the range is empty, ``True`` otherwise.
1712
+
1713
+ >>> bool(VersionRange.from_specifier_set(SpecifierSet(">=1,<2")))
1714
+ True
1715
+ >>> bool(VersionRange.from_specifier_set(SpecifierSet(">=2,<1")))
1716
+ False
1717
+ """
1718
+ return bool(self._bounds) or bool(self._admit)
1719
+
1720
+ def __contains__(self, item: Version | str) -> bool:
1721
+ """Return whether *item* is contained in this range.
1722
+
1723
+ Unparsable strings do not match, except where
1724
+ :class:`SpecifierSet` would also match: the full range admits
1725
+ any string, and a ``===`` range admits items whose string
1726
+ equals the literal case-insensitively.
1727
+
1728
+ >>> r = VersionRange.from_specifier_set(SpecifierSet(">=1.0,<2.0"))
1729
+ >>> "1.5" in r
1730
+ True
1731
+ >>> "2.0" in r
1732
+ False
1733
+ """
1734
+ if self._admit or self._reject:
1735
+ item_str = str(item).lower()
1736
+ if item_str in self._reject:
1737
+ return False
1738
+ if item_str in self._admit:
1739
+ return True
1740
+ if self._bounds == _FULL_RANGE:
1741
+ # ``SpecifierSet("")`` admits any string. Match that.
1742
+ return True
1743
+ if not isinstance(item, Version):
1744
+ try:
1745
+ item = Version(item)
1746
+ except InvalidVersion:
1747
+ return False
1748
+ return self._matches_bounds(item)
1749
+
1750
+ def _matches_bounds(self, item: Version) -> bool:
1751
+ """Bound-only membership check; ignores admit/reject."""
1752
+ return _matches_bounds_only(self._bounds, item)
1753
+
1754
+ def __eq__(self, other: object) -> bool:
1755
+ """Structural equality. Two ranges are equal when they admit
1756
+ exactly the same set of versions and strings.
1757
+
1758
+ >>> a = VersionRange.from_specifier_set(SpecifierSet(">=1.0,<2.0"))
1759
+ >>> b = VersionRange.from_specifier_set(SpecifierSet(">=1.0,<2.0"))
1760
+ >>> a == b
1761
+ True
1762
+ """
1763
+ if not isinstance(other, VersionRange):
1764
+ return NotImplemented
1765
+ return (
1766
+ self._bounds == other._bounds
1767
+ and self._admit == other._admit
1768
+ and self._reject == other._reject
1769
+ )
1770
+
1771
+ def __hash__(self) -> int:
1772
+ if not self._admit and not self._reject:
1773
+ return hash(self._bounds)
1774
+ return hash((self._bounds, self._admit, self._reject))
1775
+
1776
+ def __repr__(self) -> str:
1777
+ """Human-readable representation. Internal layout, debugging only.
1778
+
1779
+ >>> VersionRange.from_specifier_set(SpecifierSet(">=1.0,<2.0"))
1780
+ <VersionRange '[1.0, 2.0.dev0)'>
1781
+ >>> VersionRange.from_specifier_set(SpecifierSet(""))
1782
+ <VersionRange '(-inf, +inf)'>
1783
+ >>> VersionRange.from_specifier_set(SpecifierSet(">=2.0,<1.0"))
1784
+ <VersionRange '(empty)'>
1785
+ >>> VersionRange.from_specifier(Specifier("===wat"))
1786
+ <VersionRange '{wat}'>
1787
+ """
1788
+ if self._bounds:
1789
+ bound_body = " | ".join(
1790
+ f"{_format_lower(lower)}, {_format_upper(upper)}"
1791
+ for lower, upper in self._bounds
1792
+ )
1793
+ else:
1794
+ bound_body = "(empty)" if not self._admit else ""
1795
+ parts: list[str] = []
1796
+ if bound_body:
1797
+ parts.append(bound_body)
1798
+ if self._admit:
1799
+ parts.append("{" + ", ".join(sorted(self._admit)) + "}")
1800
+ body = " | ".join(parts) if parts else "(empty)"
1801
+ if self._reject:
1802
+ body = f"{body} \\ {{{', '.join(sorted(self._reject))}}}"
1803
+ return f"<{self.__class__.__name__} {body!r}>"