dycw-utilities 0.129.10__py3-none-any.whl → 0.175.17__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 (103) hide show
  1. dycw_utilities-0.175.17.dist-info/METADATA +34 -0
  2. dycw_utilities-0.175.17.dist-info/RECORD +103 -0
  3. dycw_utilities-0.175.17.dist-info/WHEEL +4 -0
  4. dycw_utilities-0.175.17.dist-info/entry_points.txt +4 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +14 -14
  7. utilities/asyncio.py +350 -819
  8. utilities/atomicwrites.py +18 -6
  9. utilities/atools.py +77 -22
  10. utilities/cachetools.py +24 -29
  11. utilities/click.py +393 -237
  12. utilities/concurrent.py +8 -11
  13. utilities/contextlib.py +216 -17
  14. utilities/contextvars.py +20 -1
  15. utilities/cryptography.py +3 -3
  16. utilities/dataclasses.py +83 -118
  17. utilities/docker.py +293 -0
  18. utilities/enum.py +26 -23
  19. utilities/errors.py +17 -3
  20. utilities/fastapi.py +29 -65
  21. utilities/fpdf2.py +3 -3
  22. utilities/functions.py +169 -416
  23. utilities/functools.py +18 -19
  24. utilities/git.py +9 -30
  25. utilities/grp.py +28 -0
  26. utilities/gzip.py +31 -0
  27. utilities/http.py +3 -2
  28. utilities/hypothesis.py +738 -589
  29. utilities/importlib.py +17 -1
  30. utilities/inflect.py +25 -0
  31. utilities/iterables.py +194 -262
  32. utilities/jinja2.py +148 -0
  33. utilities/json.py +70 -0
  34. utilities/libcst.py +38 -17
  35. utilities/lightweight_charts.py +5 -9
  36. utilities/logging.py +345 -543
  37. utilities/math.py +18 -13
  38. utilities/memory_profiler.py +11 -15
  39. utilities/more_itertools.py +200 -131
  40. utilities/operator.py +33 -29
  41. utilities/optuna.py +6 -6
  42. utilities/orjson.py +272 -137
  43. utilities/os.py +61 -4
  44. utilities/parse.py +59 -61
  45. utilities/pathlib.py +281 -40
  46. utilities/permissions.py +298 -0
  47. utilities/pickle.py +2 -2
  48. utilities/platform.py +24 -5
  49. utilities/polars.py +1214 -430
  50. utilities/polars_ols.py +1 -1
  51. utilities/postgres.py +408 -0
  52. utilities/pottery.py +113 -26
  53. utilities/pqdm.py +10 -11
  54. utilities/psutil.py +6 -57
  55. utilities/pwd.py +28 -0
  56. utilities/pydantic.py +4 -54
  57. utilities/pydantic_settings.py +240 -0
  58. utilities/pydantic_settings_sops.py +76 -0
  59. utilities/pyinstrument.py +8 -10
  60. utilities/pytest.py +227 -121
  61. utilities/pytest_plugins/__init__.py +1 -0
  62. utilities/pytest_plugins/pytest_randomly.py +23 -0
  63. utilities/pytest_plugins/pytest_regressions.py +56 -0
  64. utilities/pytest_regressions.py +26 -46
  65. utilities/random.py +13 -9
  66. utilities/re.py +58 -28
  67. utilities/redis.py +401 -550
  68. utilities/scipy.py +1 -1
  69. utilities/sentinel.py +10 -0
  70. utilities/shelve.py +4 -1
  71. utilities/shutil.py +25 -0
  72. utilities/slack_sdk.py +36 -106
  73. utilities/sqlalchemy.py +502 -473
  74. utilities/sqlalchemy_polars.py +38 -94
  75. utilities/string.py +2 -3
  76. utilities/subprocess.py +1572 -0
  77. utilities/tempfile.py +86 -4
  78. utilities/testbook.py +50 -0
  79. utilities/text.py +165 -42
  80. utilities/timer.py +37 -65
  81. utilities/traceback.py +158 -929
  82. utilities/types.py +146 -116
  83. utilities/typing.py +531 -71
  84. utilities/tzdata.py +1 -53
  85. utilities/tzlocal.py +6 -23
  86. utilities/uuid.py +43 -5
  87. utilities/version.py +27 -26
  88. utilities/whenever.py +1776 -386
  89. utilities/zoneinfo.py +84 -22
  90. dycw_utilities-0.129.10.dist-info/METADATA +0 -241
  91. dycw_utilities-0.129.10.dist-info/RECORD +0 -96
  92. dycw_utilities-0.129.10.dist-info/WHEEL +0 -4
  93. dycw_utilities-0.129.10.dist-info/licenses/LICENSE +0 -21
  94. utilities/datetime.py +0 -1409
  95. utilities/eventkit.py +0 -402
  96. utilities/loguru.py +0 -144
  97. utilities/luigi.py +0 -228
  98. utilities/period.py +0 -324
  99. utilities/pyrsistent.py +0 -89
  100. utilities/python_dotenv.py +0 -105
  101. utilities/streamlit.py +0 -105
  102. utilities/sys.py +0 -87
  103. utilities/tenacity.py +0 -145
utilities/iterables.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import builtins
4
- import datetime as dt
5
4
  from collections import Counter
6
5
  from collections.abc import (
7
6
  Callable,
@@ -19,14 +18,12 @@ from enum import Enum
19
18
  from functools import cmp_to_key, partial, reduce
20
19
  from itertools import accumulate, chain, groupby, islice, pairwise, product
21
20
  from math import isnan
22
- from operator import add, itemgetter, or_
21
+ from operator import add, or_
23
22
  from typing import (
24
23
  TYPE_CHECKING,
25
24
  Any,
26
- Generic,
27
25
  Literal,
28
26
  TypeGuard,
29
- TypeVar,
30
27
  assert_never,
31
28
  cast,
32
29
  overload,
@@ -34,7 +31,6 @@ from typing import (
34
31
  )
35
32
 
36
33
  from utilities.errors import ImpossibleCaseError
37
- from utilities.functions import ensure_hashable, ensure_not_none, ensure_str
38
34
  from utilities.math import (
39
35
  _CheckIntegerEqualError,
40
36
  _CheckIntegerEqualOrApproxError,
@@ -43,58 +39,35 @@ from utilities.math import (
43
39
  check_integer,
44
40
  )
45
41
  from utilities.reprlib import get_repr
46
- from utilities.sentinel import Sentinel, sentinel
47
- from utilities.types import Sign, THashable, TSupportsAdd, TSupportsLT
48
- from utilities.zoneinfo import UTC
42
+ from utilities.sentinel import Sentinel, is_sentinel, sentinel
43
+ from utilities.types import SupportsAdd, SupportsLT
49
44
 
50
45
  if TYPE_CHECKING:
51
46
  from types import NoneType
52
47
 
53
- from utilities.types import MaybeIterable, MaybeIterableHashable, StrMapping
54
-
55
-
56
- _K = TypeVar("_K")
57
- _T = TypeVar("_T")
58
- _U = TypeVar("_U")
59
- _V = TypeVar("_V")
60
- _W = TypeVar("_W")
61
- _T1 = TypeVar("_T1")
62
- _T2 = TypeVar("_T2")
63
- _T3 = TypeVar("_T3")
64
- _T4 = TypeVar("_T4")
65
- _T5 = TypeVar("_T5")
48
+ from utilities.types import MaybeIterable, Sign, StrMapping
66
49
 
67
50
 
68
51
  ##
69
52
 
70
53
 
71
- def always_iterable(obj: MaybeIterable[_T], /) -> Iterable[_T]:
54
+ def always_iterable[T](obj: MaybeIterable[T], /) -> Iterable[T]:
72
55
  """Typed version of `always_iterable`."""
73
56
  obj = cast("Any", obj)
74
57
  if isinstance(obj, str | bytes):
75
- return cast("list[_T]", [obj])
58
+ return cast("list[T]", [obj])
76
59
  try:
77
- return iter(cast("Iterable[_T]", obj))
60
+ return iter(cast("Iterable[T]", obj))
78
61
  except TypeError:
79
- return cast("list[_T]", [obj])
62
+ return cast("list[T]", [obj])
80
63
 
81
64
 
82
65
  ##
83
66
 
84
67
 
85
- def always_iterable_hashable(
86
- obj: MaybeIterable[_T] | None, /
87
- ) -> MaybeIterableHashable[_T] | None:
88
- """Ensure an object is always hashable."""
89
- return None if obj is None else tuple(always_iterable(obj))
90
-
91
-
92
- ##
93
-
94
-
95
- def apply_bijection(
96
- func: Callable[[_T], _U], iterable: Iterable[_T], /
97
- ) -> Mapping[_T, _U]:
68
+ def apply_bijection[T, U](
69
+ func: Callable[[T], U], iterable: Iterable[T], /
70
+ ) -> Mapping[T, U]:
98
71
  """Apply a function bijectively."""
99
72
  keys = list(iterable)
100
73
  try:
@@ -114,21 +87,21 @@ def apply_bijection(
114
87
 
115
88
 
116
89
  @dataclass(kw_only=True, slots=True)
117
- class ApplyBijectionError(Exception, Generic[_T]):
118
- keys: list[_T]
119
- counts: Mapping[_T, int]
90
+ class ApplyBijectionError[T](Exception):
91
+ keys: list[T]
92
+ counts: Mapping[T, int]
120
93
 
121
94
 
122
95
  @dataclass(kw_only=True, slots=True)
123
- class _ApplyBijectionDuplicateKeysError(ApplyBijectionError[_T]):
96
+ class _ApplyBijectionDuplicateKeysError[T](ApplyBijectionError[T]):
124
97
  @override
125
98
  def __str__(self) -> str:
126
99
  return f"Keys {get_repr(self.keys)} must not contain duplicates; got {get_repr(self.counts)}"
127
100
 
128
101
 
129
102
  @dataclass(kw_only=True, slots=True)
130
- class _ApplyBijectionDuplicateValuesError(ApplyBijectionError[_T], Generic[_T, _U]):
131
- values: list[_U]
103
+ class _ApplyBijectionDuplicateValuesError[T, U](ApplyBijectionError[T]):
104
+ values: list[U]
132
105
 
133
106
  @override
134
107
  def __str__(self) -> str:
@@ -138,7 +111,7 @@ class _ApplyBijectionDuplicateValuesError(ApplyBijectionError[_T], Generic[_T, _
138
111
  ##
139
112
 
140
113
 
141
- def apply_to_tuple(func: Callable[..., _T], args: tuple[Any, ...], /) -> _T:
114
+ def apply_to_tuple[T](func: Callable[..., T], args: tuple[Any, ...], /) -> T:
142
115
  """Apply a function to a tuple of args."""
143
116
  return apply_to_varargs(func, *args)
144
117
 
@@ -146,7 +119,7 @@ def apply_to_tuple(func: Callable[..., _T], args: tuple[Any, ...], /) -> _T:
146
119
  ##
147
120
 
148
121
 
149
- def apply_to_varargs(func: Callable[..., _T], *args: Any) -> _T:
122
+ def apply_to_varargs[T](func: Callable[..., T], *args: Any) -> T:
150
123
  """Apply a function to a variable number of arguments."""
151
124
  return func(*args)
152
125
 
@@ -155,17 +128,17 @@ def apply_to_varargs(func: Callable[..., _T], *args: Any) -> _T:
155
128
 
156
129
 
157
130
  @overload
158
- def chain_mappings(
159
- *mappings: Mapping[_K, _V], list: Literal[True]
160
- ) -> Mapping[_K, Sequence[_V]]: ...
131
+ def chain_mappings[K, V](
132
+ *mappings: Mapping[K, V], list: Literal[True]
133
+ ) -> Mapping[K, Sequence[V]]: ...
161
134
  @overload
162
- def chain_mappings(
163
- *mappings: Mapping[_K, _V], list: bool = False
164
- ) -> Mapping[_K, Iterable[_V]]: ...
165
- def chain_mappings(
166
- *mappings: Mapping[_K, _V],
135
+ def chain_mappings[K, V](
136
+ *mappings: Mapping[K, V], list: bool = False
137
+ ) -> Mapping[K, Iterable[V]]: ...
138
+ def chain_mappings[K, V](
139
+ *mappings: Mapping[K, V],
167
140
  list: bool = False, # noqa: A002
168
- ) -> Mapping[_K, Iterable[_V]]:
141
+ ) -> Mapping[K, Iterable[V]]:
169
142
  """Chain the values of a set of mappings."""
170
143
  try:
171
144
  first, *rest = mappings
@@ -178,9 +151,9 @@ def chain_mappings(
178
151
  return reduced
179
152
 
180
153
 
181
- def _chain_mappings_one(
182
- acc: Mapping[_K, Iterable[_V]], el: Mapping[_K, _V], /
183
- ) -> Mapping[_K, Iterable[_V]]:
154
+ def _chain_mappings_one[K, V](
155
+ acc: Mapping[K, Iterable[V]], el: Mapping[K, V], /
156
+ ) -> Mapping[K, Iterable[V]]:
184
157
  """Chain the values of a set of mappings."""
185
158
  out = dict(acc)
186
159
  for key, value in el.items():
@@ -191,7 +164,7 @@ def _chain_mappings_one(
191
164
  ##
192
165
 
193
166
 
194
- def chain_maybe_iterables(*maybe_iterables: MaybeIterable[_T]) -> Iterable[_T]:
167
+ def chain_maybe_iterables[T](*maybe_iterables: MaybeIterable[T]) -> Iterable[T]:
195
168
  """Chain a set of maybe iterables."""
196
169
  iterables = map(always_iterable, maybe_iterables)
197
170
  return chain.from_iterable(iterables)
@@ -200,7 +173,7 @@ def chain_maybe_iterables(*maybe_iterables: MaybeIterable[_T]) -> Iterable[_T]:
200
173
  ##
201
174
 
202
175
 
203
- def chain_nullable(*maybe_iterables: Iterable[_T | None] | None) -> Iterable[_T]:
176
+ def chain_nullable[T](*maybe_iterables: Iterable[T | None] | None) -> Iterable[T]:
204
177
  """Chain a set of values; ignoring nulls."""
205
178
  iterables = (mi for mi in maybe_iterables if mi is not None)
206
179
  values = ((i for i in it if i is not None) for it in iterables)
@@ -219,7 +192,7 @@ def check_bijection(mapping: Mapping[Any, Hashable], /) -> None:
219
192
 
220
193
 
221
194
  @dataclass(kw_only=True, slots=True)
222
- class CheckBijectionError(Exception, Generic[THashable]):
195
+ class CheckBijectionError[THashable](Exception):
223
196
  mapping: Mapping[Any, THashable]
224
197
  counts: Mapping[THashable, int]
225
198
 
@@ -239,7 +212,7 @@ def check_duplicates(iterable: Iterable[Hashable], /) -> None:
239
212
 
240
213
 
241
214
  @dataclass(kw_only=True, slots=True)
242
- class CheckDuplicatesError(Exception, Generic[THashable]):
215
+ class CheckDuplicatesError[THashable](Exception):
243
216
  iterable: Iterable[THashable]
244
217
  counts: Mapping[THashable, int]
245
218
 
@@ -262,8 +235,7 @@ def check_iterables_equal(left: Iterable[Any], right: Iterable[Any], /) -> None:
262
235
  if lv != rv:
263
236
  errors.append((i, lv, rv))
264
237
  except ValueError as error:
265
- msg = ensure_str(one(error.args))
266
- match msg:
238
+ match one(error.args):
267
239
  case "zip() argument 2 is longer than argument 1":
268
240
  state = "right_longer"
269
241
  case "zip() argument 2 is shorter than argument 1":
@@ -282,10 +254,10 @@ type _CheckIterablesEqualState = Literal["left_longer", "right_longer"]
282
254
 
283
255
 
284
256
  @dataclass(kw_only=True, slots=True)
285
- class CheckIterablesEqualError(Exception, Generic[_T]):
286
- left: list[_T]
287
- right: list[_T]
288
- errors: list[tuple[int, _T, _T]]
257
+ class CheckIterablesEqualError[T](Exception):
258
+ left: list[T]
259
+ right: list[T]
260
+ errors: list[tuple[int, T, T]]
289
261
  state: _CheckIterablesEqualState | None
290
262
 
291
263
  @override
@@ -311,7 +283,7 @@ class CheckIterablesEqualError(Exception, Generic[_T]):
311
283
  yield "right was longer"
312
284
  case None:
313
285
  pass
314
- case _ as never:
286
+ case never:
315
287
  assert_never(never)
316
288
 
317
289
 
@@ -436,12 +408,12 @@ def check_mappings_equal(left: Mapping[Any, Any], right: Mapping[Any, Any], /) -
436
408
 
437
409
 
438
410
  @dataclass(kw_only=True, slots=True)
439
- class CheckMappingsEqualError(Exception, Generic[_K, _V]):
440
- left: Mapping[_K, _V]
441
- right: Mapping[_K, _V]
442
- left_extra: AbstractSet[_K]
443
- right_extra: AbstractSet[_K]
444
- errors: list[tuple[_K, _V, _V]]
411
+ class CheckMappingsEqualError[K, V](Exception):
412
+ left: Mapping[K, V]
413
+ right: Mapping[K, V]
414
+ left_extra: AbstractSet[K]
415
+ right_extra: AbstractSet[K]
416
+ errors: list[tuple[K, V, V]]
445
417
 
446
418
  @override
447
419
  def __str__(self) -> str:
@@ -486,11 +458,11 @@ def check_sets_equal(left: Iterable[Any], right: Iterable[Any], /) -> None:
486
458
 
487
459
 
488
460
  @dataclass(kw_only=True, slots=True)
489
- class CheckSetsEqualError(Exception, Generic[_T]):
490
- left: AbstractSet[_T]
491
- right: AbstractSet[_T]
492
- left_extra: AbstractSet[_T]
493
- right_extra: AbstractSet[_T]
461
+ class CheckSetsEqualError[T](Exception):
462
+ left: AbstractSet[T]
463
+ right: AbstractSet[T]
464
+ left_extra: AbstractSet[T]
465
+ right_extra: AbstractSet[T]
494
466
 
495
467
  @override
496
468
  def __str__(self) -> str:
@@ -533,11 +505,11 @@ def check_submapping(left: Mapping[Any, Any], right: Mapping[Any, Any], /) -> No
533
505
 
534
506
 
535
507
  @dataclass(kw_only=True, slots=True)
536
- class CheckSubMappingError(Exception, Generic[_K, _V]):
537
- left: Mapping[_K, _V]
538
- right: Mapping[_K, _V]
539
- extra: AbstractSet[_K]
540
- errors: list[tuple[_K, _V, _V]]
508
+ class CheckSubMappingError[K, V](Exception):
509
+ left: Mapping[K, V]
510
+ right: Mapping[K, V]
511
+ extra: AbstractSet[K]
512
+ errors: list[tuple[K, V, V]]
541
513
 
542
514
  @override
543
515
  def __str__(self) -> str:
@@ -572,10 +544,10 @@ def check_subset(left: Iterable[Any], right: Iterable[Any], /) -> None:
572
544
 
573
545
 
574
546
  @dataclass(kw_only=True, slots=True)
575
- class CheckSubSetError(Exception, Generic[_T]):
576
- left: AbstractSet[_T]
577
- right: AbstractSet[_T]
578
- extra: AbstractSet[_T]
547
+ class CheckSubSetError[T](Exception):
548
+ left: AbstractSet[T]
549
+ right: AbstractSet[T]
550
+ extra: AbstractSet[T]
579
551
 
580
552
  @override
581
553
  def __str__(self) -> str:
@@ -604,11 +576,11 @@ def check_supermapping(left: Mapping[Any, Any], right: Mapping[Any, Any], /) ->
604
576
 
605
577
 
606
578
  @dataclass(kw_only=True, slots=True)
607
- class CheckSuperMappingError(Exception, Generic[_K, _V]):
608
- left: Mapping[_K, _V]
609
- right: Mapping[_K, _V]
610
- extra: AbstractSet[_K]
611
- errors: list[tuple[_K, _V, _V]]
579
+ class CheckSuperMappingError[K, V](Exception):
580
+ left: Mapping[K, V]
581
+ right: Mapping[K, V]
582
+ extra: AbstractSet[K]
583
+ errors: list[tuple[K, V, V]]
612
584
 
613
585
  @override
614
586
  def __str__(self) -> str:
@@ -643,10 +615,10 @@ def check_superset(left: Iterable[Any], right: Iterable[Any], /) -> None:
643
615
 
644
616
 
645
617
  @dataclass(kw_only=True, slots=True)
646
- class CheckSuperSetError(Exception, Generic[_T]):
647
- left: AbstractSet[_T]
648
- right: AbstractSet[_T]
649
- extra: AbstractSet[_T]
618
+ class CheckSuperSetError[T](Exception):
619
+ left: AbstractSet[T]
620
+ right: AbstractSet[T]
621
+ extra: AbstractSet[T]
650
622
 
651
623
  @override
652
624
  def __str__(self) -> str:
@@ -695,7 +667,7 @@ class _CheckUniqueModuloCaseDuplicateLowerCaseStringsError(CheckUniqueModuloCase
695
667
  ##
696
668
 
697
669
 
698
- def cmp_nullable(x: TSupportsLT | None, y: TSupportsLT | None, /) -> Sign:
670
+ def cmp_nullable[T: SupportsLT](x: T | None, y: T | None, /) -> Sign:
699
671
  """Compare two nullable objects."""
700
672
  match x, y:
701
673
  case None, None:
@@ -706,14 +678,14 @@ def cmp_nullable(x: TSupportsLT | None, y: TSupportsLT | None, /) -> Sign:
706
678
  return 1
707
679
  case _, _:
708
680
  return cast("Sign", (x > y) - (x < y))
709
- case _ as never:
681
+ case never:
710
682
  assert_never(never)
711
683
 
712
684
 
713
685
  ##
714
686
 
715
687
 
716
- def chunked(iterable: Iterable[_T], n: int, /) -> Iterator[Sequence[_T]]:
688
+ def chunked[T](iterable: Iterable[T], n: int, /) -> Iterator[Sequence[T]]:
717
689
  """Break an iterable into lists of length n."""
718
690
  return iter(partial(take, n, iter(iterable)), [])
719
691
 
@@ -721,18 +693,6 @@ def chunked(iterable: Iterable[_T], n: int, /) -> Iterator[Sequence[_T]]:
721
693
  ##
722
694
 
723
695
 
724
- def ensure_hashables(
725
- *args: Any, **kwargs: Any
726
- ) -> tuple[list[Hashable], dict[str, Hashable]]:
727
- """Ensure a set of positional & keyword arguments are all hashable."""
728
- hash_args = list(map(ensure_hashable, args))
729
- hash_kwargs = {k: ensure_hashable(v) for k, v in kwargs.items()}
730
- return hash_args, hash_kwargs
731
-
732
-
733
- ##
734
-
735
-
736
696
  def ensure_iterable(obj: Any, /) -> Iterable[Any]:
737
697
  """Ensure an object is iterable."""
738
698
  if is_iterable(obj):
@@ -771,10 +731,30 @@ class EnsureIterableNotStrError(Exception):
771
731
  ##
772
732
 
773
733
 
774
- def expanding_window(iterable: Iterable[_T], /) -> islice[list[_T]]:
734
+ _EDGE: int = 5
735
+
736
+
737
+ def enumerate_with_edge[T](
738
+ iterable: Iterable[T], /, *, start: int = 0, edge: int = _EDGE
739
+ ) -> Iterator[tuple[int, int, bool, T]]:
740
+ """Enumerate an iterable, with the edge items marked."""
741
+ as_list = list(iterable)
742
+ total = len(as_list)
743
+ indices = set(range(edge)) | set(range(total)[-edge:])
744
+ is_edge = (i in indices for i in range(total))
745
+ for (i, value), is_edge_i in zip(
746
+ enumerate(as_list, start=start), is_edge, strict=True
747
+ ):
748
+ yield i, total, is_edge_i, value
749
+
750
+
751
+ ##
752
+
753
+
754
+ def expanding_window[T](iterable: Iterable[T], /) -> islice[list[T]]:
775
755
  """Yield an expanding window over an iterable."""
776
756
 
777
- def func(acc: Iterable[_T], el: _T, /) -> list[_T]:
757
+ def func(acc: Iterable[T], el: T, /) -> list[T]:
778
758
  return list(chain(acc, [el]))
779
759
 
780
760
  return islice(accumulate(iterable, func=func, initial=[]), 1, None)
@@ -784,31 +764,31 @@ def expanding_window(iterable: Iterable[_T], /) -> islice[list[_T]]:
784
764
 
785
765
 
786
766
  @overload
787
- def filter_include_and_exclude(
788
- iterable: Iterable[_T],
767
+ def filter_include_and_exclude[T, U](
768
+ iterable: Iterable[T],
789
769
  /,
790
770
  *,
791
- include: MaybeIterable[_U] | None = None,
792
- exclude: MaybeIterable[_U] | None = None,
793
- key: Callable[[_T], _U],
794
- ) -> Iterable[_T]: ...
771
+ include: MaybeIterable[U] | None = None,
772
+ exclude: MaybeIterable[U] | None = None,
773
+ key: Callable[[T], U],
774
+ ) -> Iterable[T]: ...
795
775
  @overload
796
- def filter_include_and_exclude(
797
- iterable: Iterable[_T],
776
+ def filter_include_and_exclude[T](
777
+ iterable: Iterable[T],
798
778
  /,
799
779
  *,
800
- include: MaybeIterable[_T] | None = None,
801
- exclude: MaybeIterable[_T] | None = None,
802
- key: Callable[[_T], Any] | None = None,
803
- ) -> Iterable[_T]: ...
804
- def filter_include_and_exclude(
805
- iterable: Iterable[_T],
780
+ include: MaybeIterable[T] | None = None,
781
+ exclude: MaybeIterable[T] | None = None,
782
+ key: Callable[[T], Any] | None = None,
783
+ ) -> Iterable[T]: ...
784
+ def filter_include_and_exclude[T, U](
785
+ iterable: Iterable[T],
806
786
  /,
807
787
  *,
808
- include: MaybeIterable[_U] | None = None,
809
- exclude: MaybeIterable[_U] | None = None,
810
- key: Callable[[_T], _U] | None = None,
811
- ) -> Iterable[_T]:
788
+ include: MaybeIterable[U] | None = None,
789
+ exclude: MaybeIterable[U] | None = None,
790
+ key: Callable[[T], U] | None = None,
791
+ ) -> Iterable[T]:
812
792
  """Filter an iterable based on an inclusion/exclusion pair."""
813
793
  include, exclude = resolve_include_and_exclude(include=include, exclude=exclude)
814
794
  if include is not None:
@@ -827,35 +807,17 @@ def filter_include_and_exclude(
827
807
  ##
828
808
 
829
809
 
830
- def group_consecutive_integers(iterable: Iterable[int], /) -> Iterable[tuple[int, int]]:
831
- """Group consecutive integers."""
832
- integers = sorted(iterable)
833
- for _, group in groupby(enumerate(integers), key=lambda x: x[1] - x[0]):
834
- as_list = list(map(itemgetter(1), group))
835
- yield as_list[0], as_list[-1]
836
-
837
-
838
- def ungroup_consecutive_integers(
839
- iterable: Iterable[tuple[int, int]], /
840
- ) -> Iterable[int]:
841
- """Ungroup consecutive integers."""
842
- return chain.from_iterable(range(start, end + 1) for start, end in iterable)
843
-
844
-
845
- ##
846
-
847
-
848
810
  @overload
849
- def groupby_lists(
850
- iterable: Iterable[_T], /, *, key: None = None
851
- ) -> Iterator[tuple[_T, list[_T]]]: ...
811
+ def groupby_lists[T](
812
+ iterable: Iterable[T], /, *, key: None = None
813
+ ) -> Iterator[tuple[T, list[T]]]: ...
852
814
  @overload
853
- def groupby_lists(
854
- iterable: Iterable[_T], /, *, key: Callable[[_T], _U]
855
- ) -> Iterator[tuple[_U, list[_T]]]: ...
856
- def groupby_lists(
857
- iterable: Iterable[_T], /, *, key: Callable[[_T], _U] | None = None
858
- ) -> Iterator[tuple[_T, list[_T]]] | Iterator[tuple[_U, list[_T]]]:
815
+ def groupby_lists[T, U](
816
+ iterable: Iterable[T], /, *, key: Callable[[T], U]
817
+ ) -> Iterator[tuple[U, list[T]]]: ...
818
+ def groupby_lists[T, U](
819
+ iterable: Iterable[T], /, *, key: Callable[[T], U] | None = None
820
+ ) -> Iterator[tuple[T, list[T]]] | Iterator[tuple[U, list[T]]]:
859
821
  """Yield consecutive keys and groups (as lists)."""
860
822
  if key is None:
861
823
  for k, group in groupby(iterable):
@@ -868,7 +830,7 @@ def groupby_lists(
868
830
  ##
869
831
 
870
832
 
871
- def hashable_to_iterable(obj: THashable | None, /) -> tuple[THashable, ...] | None:
833
+ def hashable_to_iterable[T: Hashable](obj: T | None, /) -> tuple[T, ...] | None:
872
834
  """Lift a hashable singleton to an iterable of hashables."""
873
835
  return None if obj is None else (obj,)
874
836
 
@@ -904,9 +866,9 @@ def is_iterable_not_str(obj: Any, /) -> TypeGuard[Iterable[Any]]:
904
866
  ##
905
867
 
906
868
 
907
- def map_mapping(
908
- func: Callable[[_V], _W], mapping: Mapping[_K, _V], /
909
- ) -> Mapping[_K, _W]:
869
+ def map_mapping[K, V, W](
870
+ func: Callable[[V], W], mapping: Mapping[K, V], /
871
+ ) -> Mapping[K, W]:
910
872
  """Map a function over the values of a mapping."""
911
873
  return {k: func(v) for k, v in mapping.items()}
912
874
 
@@ -914,7 +876,7 @@ def map_mapping(
914
876
  ##
915
877
 
916
878
 
917
- def merge_mappings(*mappings: Mapping[_K, _V]) -> Mapping[_K, _V]:
879
+ def merge_mappings[K, V](*mappings: Mapping[K, V]) -> Mapping[K, V]:
918
880
  """Merge a set of mappings."""
919
881
  return reduce(or_, map(dict, mappings), {})
920
882
 
@@ -922,7 +884,7 @@ def merge_mappings(*mappings: Mapping[_K, _V]) -> Mapping[_K, _V]:
922
884
  ##
923
885
 
924
886
 
925
- def merge_sets(*iterables: Iterable[_T]) -> AbstractSet[_T]:
887
+ def merge_sets[T](*iterables: Iterable[T]) -> AbstractSet[T]:
926
888
  """Merge a set of sets."""
927
889
  return reduce(or_, map(set, iterables), set())
928
890
 
@@ -969,9 +931,9 @@ class MergeStrMappingsError(Exception):
969
931
  ##
970
932
 
971
933
 
972
- def one(*iterables: Iterable[_T]) -> _T:
934
+ def one[T](*iterables: Iterable[T]) -> T:
973
935
  """Return the unique value in a set of iterables."""
974
- it = iter(chain(*iterables))
936
+ it = chain(*iterables)
975
937
  try:
976
938
  first = next(it)
977
939
  except StopIteration:
@@ -984,21 +946,21 @@ def one(*iterables: Iterable[_T]) -> _T:
984
946
 
985
947
 
986
948
  @dataclass(kw_only=True, slots=True)
987
- class OneError(Exception, Generic[_T]):
988
- iterables: tuple[Iterable[_T], ...]
949
+ class OneError[T](Exception):
950
+ iterables: tuple[Iterable[T], ...]
989
951
 
990
952
 
991
953
  @dataclass(kw_only=True, slots=True)
992
- class OneEmptyError(OneError[_T]):
954
+ class OneEmptyError[T](OneError[T]):
993
955
  @override
994
956
  def __str__(self) -> str:
995
957
  return f"Iterable(s) {get_repr(self.iterables)} must not be empty"
996
958
 
997
959
 
998
960
  @dataclass(kw_only=True, slots=True)
999
- class OneNonUniqueError(OneError, Generic[_T]):
1000
- first: _T
1001
- second: _T
961
+ class OneNonUniqueError[T](OneError):
962
+ first: T
963
+ second: T
1002
964
 
1003
965
  @override
1004
966
  def __str__(self) -> str:
@@ -1008,7 +970,7 @@ class OneNonUniqueError(OneError, Generic[_T]):
1008
970
  ##
1009
971
 
1010
972
 
1011
- def one_maybe(*objs: MaybeIterable[_T]) -> _T:
973
+ def one_maybe[T](*objs: MaybeIterable[T]) -> T:
1012
974
  """Return the unique value in a set of values/iterables."""
1013
975
  try:
1014
976
  return one(chain_maybe_iterables(*objs))
@@ -1032,10 +994,10 @@ class OneMaybeEmptyError(OneMaybeError):
1032
994
 
1033
995
 
1034
996
  @dataclass(kw_only=True, slots=True)
1035
- class OneMaybeNonUniqueError(OneMaybeError, Generic[_T]):
1036
- objs: tuple[MaybeIterable[_T], ...]
1037
- first: _T
1038
- second: _T
997
+ class OneMaybeNonUniqueError[T](OneMaybeError):
998
+ objs: tuple[MaybeIterable[T], ...]
999
+ first: T
1000
+ second: T
1039
1001
 
1040
1002
  @override
1041
1003
  def __str__(self) -> str:
@@ -1064,7 +1026,7 @@ def one_str(
1064
1026
  it = (t for t in as_list if t.startswith(text))
1065
1027
  case True, False:
1066
1028
  it = (t for t in as_list if t.lower().startswith(text.lower()))
1067
- case _ as never:
1029
+ case never:
1068
1030
  assert_never(never)
1069
1031
  try:
1070
1032
  return one(it)
@@ -1105,7 +1067,7 @@ class OneStrEmptyError(OneStrError):
1105
1067
  tail = f"any string starting with {self.text!r}"
1106
1068
  case True, False:
1107
1069
  tail = f"any string starting with {self.text!r} (modulo case)"
1108
- case _ as never:
1070
+ case never:
1109
1071
  assert_never(never)
1110
1072
  return f"{head} {tail}"
1111
1073
 
@@ -1127,7 +1089,7 @@ class OneStrNonUniqueError(OneStrError):
1127
1089
  mid = f"exactly one string starting with {self.text!r}"
1128
1090
  case True, False:
1129
1091
  mid = f"exactly one string starting with {self.text!r} (modulo case)"
1130
- case _ as never:
1092
+ case never:
1131
1093
  assert_never(never)
1132
1094
  return f"{head} {mid}; got {self.first!r}, {self.second!r} and perhaps more"
1133
1095
 
@@ -1135,7 +1097,7 @@ class OneStrNonUniqueError(OneStrError):
1135
1097
  ##
1136
1098
 
1137
1099
 
1138
- def one_unique(*iterables: Iterable[THashable]) -> THashable:
1100
+ def one_unique[T: Hashable](*iterables: Iterable[T]) -> T:
1139
1101
  """Return the set-unique value in a set of iterables."""
1140
1102
  try:
1141
1103
  return one(set(chain(*iterables)))
@@ -1159,7 +1121,7 @@ class OneUniqueEmptyError(OneUniqueError):
1159
1121
 
1160
1122
 
1161
1123
  @dataclass(kw_only=True, slots=True)
1162
- class OneUniqueNonUniqueError(OneUniqueError, Generic[THashable]):
1124
+ class OneUniqueNonUniqueError[THashable](OneUniqueError):
1163
1125
  iterables: tuple[MaybeIterable[THashable], ...]
1164
1126
  first: THashable
1165
1127
  second: THashable
@@ -1172,7 +1134,7 @@ class OneUniqueNonUniqueError(OneUniqueError, Generic[THashable]):
1172
1134
  ##
1173
1135
 
1174
1136
 
1175
- def pairwise_tail(iterable: Iterable[_T], /) -> Iterator[tuple[_T, _T | Sentinel]]:
1137
+ def pairwise_tail[T](iterable: Iterable[T], /) -> Iterator[tuple[T, T | Sentinel]]:
1176
1138
  """Return pairwise elements, with the last paired with the sentinel."""
1177
1139
  return pairwise(chain(iterable, [sentinel]))
1178
1140
 
@@ -1180,11 +1142,11 @@ def pairwise_tail(iterable: Iterable[_T], /) -> Iterator[tuple[_T, _T | Sentinel
1180
1142
  ##
1181
1143
 
1182
1144
 
1183
- def product_dicts(mapping: Mapping[_K, Iterable[_V]], /) -> Iterator[Mapping[_K, _V]]:
1145
+ def product_dicts[K, V](mapping: Mapping[K, Iterable[V]], /) -> Iterator[Mapping[K, V]]:
1184
1146
  """Return the cartesian product of the values in a mapping, as mappings."""
1185
1147
  keys = list(mapping)
1186
1148
  for values in product(*mapping.values()):
1187
- yield cast("Mapping[_K, _V]", dict(zip(keys, values, strict=True)))
1149
+ yield cast("Mapping[K, V]", dict(zip(keys, values, strict=True)))
1188
1150
 
1189
1151
 
1190
1152
  ##
@@ -1241,41 +1203,39 @@ class _RangePartitionsNumError(RangePartitionsError):
1241
1203
 
1242
1204
 
1243
1205
  @overload
1244
- def reduce_mappings(
1245
- func: Callable[[_V, _V], _V], sequence: Iterable[Mapping[_K, _V]], /
1246
- ) -> Mapping[_K, _V]: ...
1206
+ def reduce_mappings[K, V](
1207
+ func: Callable[[V, V], V], sequence: Iterable[Mapping[K, V]], /
1208
+ ) -> Mapping[K, V]: ...
1247
1209
  @overload
1248
- def reduce_mappings(
1249
- func: Callable[[_W, _V], _W],
1250
- sequence: Iterable[Mapping[_K, _V]],
1210
+ def reduce_mappings[K, V, W](
1211
+ func: Callable[[W, V], W],
1212
+ sequence: Iterable[Mapping[K, V]],
1251
1213
  /,
1252
1214
  *,
1253
- initial: _W | Sentinel = sentinel,
1254
- ) -> Mapping[_K, _W]: ...
1255
- def reduce_mappings(
1256
- func: Callable[[_V, _V], _V] | Callable[[_W, _V], _W],
1257
- sequence: Iterable[Mapping[_K, _V]],
1215
+ initial: W | Sentinel = sentinel,
1216
+ ) -> Mapping[K, W]: ...
1217
+ def reduce_mappings[K, V, W](
1218
+ func: Callable[[V, V], V] | Callable[[W, V], W],
1219
+ sequence: Iterable[Mapping[K, V]],
1258
1220
  /,
1259
1221
  *,
1260
- initial: _W | Sentinel = sentinel,
1261
- ) -> Mapping[_K, _V | _W]:
1222
+ initial: W | Sentinel = sentinel,
1223
+ ) -> Mapping[K, V | W]:
1262
1224
  """Reduce a function over the values of a set of mappings."""
1263
1225
  chained = chain_mappings(*sequence)
1264
- if isinstance(initial, Sentinel):
1265
- func2 = cast("Callable[[_V, _V], _V]", func)
1226
+ if is_sentinel(initial):
1227
+ func2 = cast("Callable[[V, V], V]", func)
1266
1228
  return {k: reduce(func2, v) for k, v in chained.items()}
1267
- func2 = cast("Callable[[_W, _V], _W]", func)
1229
+ func2 = cast("Callable[[W, V], W]", func)
1268
1230
  return {k: reduce(func2, v, initial) for k, v in chained.items()}
1269
1231
 
1270
1232
 
1271
1233
  ##
1272
1234
 
1273
1235
 
1274
- def resolve_include_and_exclude(
1275
- *,
1276
- include: MaybeIterable[_T] | None = None,
1277
- exclude: MaybeIterable[_T] | None = None,
1278
- ) -> tuple[set[_T] | None, set[_T] | None]:
1236
+ def resolve_include_and_exclude[T](
1237
+ *, include: MaybeIterable[T] | None = None, exclude: MaybeIterable[T] | None = None
1238
+ ) -> tuple[set[T] | None, set[T] | None]:
1279
1239
  """Resolve an inclusion/exclusion pair."""
1280
1240
  include_use = include if include is None else set(always_iterable(include))
1281
1241
  exclude_use = exclude if exclude is None else set(always_iterable(exclude))
@@ -1289,9 +1249,9 @@ def resolve_include_and_exclude(
1289
1249
 
1290
1250
 
1291
1251
  @dataclass(kw_only=True, slots=True)
1292
- class ResolveIncludeAndExcludeError(Exception, Generic[_T]):
1293
- include: Iterable[_T]
1294
- exclude: Iterable[_T]
1252
+ class ResolveIncludeAndExcludeError[T](Exception):
1253
+ include: Iterable[T]
1254
+ exclude: Iterable[T]
1295
1255
 
1296
1256
  @override
1297
1257
  def __str__(self) -> str:
@@ -1304,7 +1264,7 @@ class ResolveIncludeAndExcludeError(Exception, Generic[_T]):
1304
1264
  ##
1305
1265
 
1306
1266
 
1307
- def sort_iterable(iterable: Iterable[_T], /) -> list[_T]:
1267
+ def sort_iterable[T](iterable: Iterable[T], /) -> list[T]:
1308
1268
  """Sort an iterable across types."""
1309
1269
  return sorted(iterable, key=cmp_to_key(_sort_iterable_cmp))
1310
1270
 
@@ -1326,9 +1286,6 @@ def _sort_iterable_cmp(x: Any, y: Any, /) -> Sign:
1326
1286
  if x is None:
1327
1287
  y = cast("NoneType", y)
1328
1288
  return 0
1329
- if isinstance(x, dt.datetime):
1330
- y = cast("dt.datetime", y)
1331
- return _sort_iterable_cmp_datetimes(x, y)
1332
1289
  if isinstance(x, float):
1333
1290
  y = cast("float", y)
1334
1291
  return _sort_iterable_cmp_floats(x, y)
@@ -1371,30 +1328,6 @@ class SortIterableError(Exception):
1371
1328
  return f"Unable to sort {get_repr(self.x)} and {get_repr(self.y)}"
1372
1329
 
1373
1330
 
1374
- def _sort_iterable_cmp_datetimes(x: dt.datetime, y: dt.datetime, /) -> Sign:
1375
- """Compare two datetimes."""
1376
- match x.tzinfo, y.tzinfo:
1377
- case None, None:
1378
- return cast("Sign", (x > y) - (x < y))
1379
- case dt.tzinfo(), None:
1380
- return 1
1381
- case None, dt.tzinfo():
1382
- return -1
1383
- case dt.tzinfo(), dt.tzinfo():
1384
- x_utc = x.astimezone(tz=UTC)
1385
- y_utc = y.astimezone(tz=UTC)
1386
- result = cast("Sign", (x_utc > y_utc) - (x_utc < y_utc))
1387
- if result != 0:
1388
- return result
1389
- x_time_zone = ensure_not_none(ensure_not_none(x.tzinfo).tzname(x))
1390
- y_time_zone = ensure_not_none(ensure_not_none(y.tzinfo).tzname(y))
1391
- return cast(
1392
- "Sign", (x_time_zone > y_time_zone) - (x_time_zone < y_time_zone)
1393
- )
1394
- case _ as never:
1395
- assert_never(never)
1396
-
1397
-
1398
1331
  def _sort_iterable_cmp_floats(x: float, y: float, /) -> Sign:
1399
1332
  """Compare two floats."""
1400
1333
  x_nan, y_nan = map(isnan, [x, y])
@@ -1407,14 +1340,16 @@ def _sort_iterable_cmp_floats(x: float, y: float, /) -> Sign:
1407
1340
  return -1
1408
1341
  case False, False:
1409
1342
  return cast("Sign", (x > y) - (x < y))
1410
- case _ as never:
1343
+ case never:
1411
1344
  assert_never(never)
1412
1345
 
1413
1346
 
1414
1347
  ##
1415
1348
 
1416
1349
 
1417
- def sum_mappings(*mappings: Mapping[_K, TSupportsAdd]) -> Mapping[_K, TSupportsAdd]:
1350
+ def sum_mappings[K: Hashable, V: SupportsAdd](
1351
+ *mappings: Mapping[K, V],
1352
+ ) -> Mapping[K, V]:
1418
1353
  """Sum the values of a set of mappings."""
1419
1354
  return reduce_mappings(add, mappings, initial=0)
1420
1355
 
@@ -1422,7 +1357,7 @@ def sum_mappings(*mappings: Mapping[_K, TSupportsAdd]) -> Mapping[_K, TSupportsA
1422
1357
  ##
1423
1358
 
1424
1359
 
1425
- def take(n: int, iterable: Iterable[_T], /) -> Sequence[_T]:
1360
+ def take[T](n: int, iterable: Iterable[T], /) -> Sequence[T]:
1426
1361
  """Return first n items of the iterable as a list."""
1427
1362
  return list(islice(iterable, n))
1428
1363
 
@@ -1431,23 +1366,23 @@ def take(n: int, iterable: Iterable[_T], /) -> Sequence[_T]:
1431
1366
 
1432
1367
 
1433
1368
  @overload
1434
- def transpose(iterable: Iterable[tuple[_T1]], /) -> tuple[list[_T1]]: ...
1369
+ def transpose[T1](iterable: Iterable[tuple[T1]], /) -> tuple[list[T1]]: ...
1435
1370
  @overload
1436
- def transpose(
1437
- iterable: Iterable[tuple[_T1, _T2]], /
1438
- ) -> tuple[list[_T1], list[_T2]]: ...
1371
+ def transpose[T1, T2](
1372
+ iterable: Iterable[tuple[T1, T2]], /
1373
+ ) -> tuple[list[T1], list[T2]]: ...
1439
1374
  @overload
1440
- def transpose(
1441
- iterable: Iterable[tuple[_T1, _T2, _T3]], /
1442
- ) -> tuple[list[_T1], list[_T2], list[_T3]]: ...
1375
+ def transpose[T1, T2, T3](
1376
+ iterable: Iterable[tuple[T1, T2, T3]], /
1377
+ ) -> tuple[list[T1], list[T2], list[T3]]: ...
1443
1378
  @overload
1444
- def transpose(
1445
- iterable: Iterable[tuple[_T1, _T2, _T3, _T4]], /
1446
- ) -> tuple[list[_T1], list[_T2], list[_T3], list[_T4]]: ...
1379
+ def transpose[T1, T2, T3, T4](
1380
+ iterable: Iterable[tuple[T1, T2, T3, T4]], /
1381
+ ) -> tuple[list[T1], list[T2], list[T3], list[T4]]: ...
1447
1382
  @overload
1448
- def transpose(
1449
- iterable: Iterable[tuple[_T1, _T2, _T3, _T4, _T5]], /
1450
- ) -> tuple[list[_T1], list[_T2], list[_T3], list[_T4], list[_T5]]: ...
1383
+ def transpose[T1, T2, T3, T4, T5](
1384
+ iterable: Iterable[tuple[T1, T2, T3, T4, T5]], /
1385
+ ) -> tuple[list[T1], list[T2], list[T3], list[T4], list[T5]]: ...
1451
1386
  def transpose(iterable: Iterable[tuple[Any]]) -> tuple[list[Any], ...]: # pyright: ignore[reportInconsistentOverload]
1452
1387
  """Typed verison of `transpose`."""
1453
1388
  return tuple(map(list, zip(*iterable, strict=True)))
@@ -1456,9 +1391,9 @@ def transpose(iterable: Iterable[tuple[Any]]) -> tuple[list[Any], ...]: # pyrig
1456
1391
  ##
1457
1392
 
1458
1393
 
1459
- def unique_everseen(
1460
- iterable: Iterable[_T], /, *, key: Callable[[_T], Any] | None = None
1461
- ) -> Iterator[_T]:
1394
+ def unique_everseen[T](
1395
+ iterable: Iterable[T], /, *, key: Callable[[T], Any] | None = None
1396
+ ) -> Iterator[T]:
1462
1397
  """Yield unique elements, preserving order."""
1463
1398
  seenset = set()
1464
1399
  seenset_add = seenset.add
@@ -1512,7 +1447,6 @@ __all__ = [
1512
1447
  "ResolveIncludeAndExcludeError",
1513
1448
  "SortIterableError",
1514
1449
  "always_iterable",
1515
- "always_iterable_hashable",
1516
1450
  "apply_bijection",
1517
1451
  "apply_to_tuple",
1518
1452
  "apply_to_varargs",
@@ -1532,12 +1466,11 @@ __all__ = [
1532
1466
  "check_unique_modulo_case",
1533
1467
  "chunked",
1534
1468
  "cmp_nullable",
1535
- "ensure_hashables",
1536
1469
  "ensure_iterable",
1537
1470
  "ensure_iterable_not_str",
1471
+ "enumerate_with_edge",
1538
1472
  "expanding_window",
1539
1473
  "filter_include_and_exclude",
1540
- "group_consecutive_integers",
1541
1474
  "groupby_lists",
1542
1475
  "hashable_to_iterable",
1543
1476
  "is_iterable",
@@ -1560,6 +1493,5 @@ __all__ = [
1560
1493
  "sum_mappings",
1561
1494
  "take",
1562
1495
  "transpose",
1563
- "ungroup_consecutive_integers",
1564
1496
  "unique_everseen",
1565
1497
  ]