dycw-utilities 0.166.30__py3-none-any.whl → 0.185.8__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 (96) hide show
  1. dycw_utilities-0.185.8.dist-info/METADATA +33 -0
  2. dycw_utilities-0.185.8.dist-info/RECORD +90 -0
  3. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +1 -1
  4. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +1 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +17 -10
  7. utilities/asyncio.py +50 -72
  8. utilities/atools.py +9 -11
  9. utilities/cachetools.py +16 -11
  10. utilities/click.py +76 -19
  11. utilities/concurrent.py +1 -1
  12. utilities/constants.py +492 -0
  13. utilities/contextlib.py +23 -30
  14. utilities/contextvars.py +1 -23
  15. utilities/core.py +2581 -0
  16. utilities/dataclasses.py +16 -119
  17. utilities/docker.py +387 -0
  18. utilities/enum.py +1 -1
  19. utilities/errors.py +2 -16
  20. utilities/fastapi.py +5 -5
  21. utilities/fpdf2.py +2 -1
  22. utilities/functions.py +34 -265
  23. utilities/http.py +2 -3
  24. utilities/hypothesis.py +84 -29
  25. utilities/importlib.py +17 -1
  26. utilities/iterables.py +39 -575
  27. utilities/jinja2.py +145 -0
  28. utilities/jupyter.py +5 -3
  29. utilities/libcst.py +1 -1
  30. utilities/lightweight_charts.py +4 -6
  31. utilities/logging.py +24 -24
  32. utilities/math.py +1 -36
  33. utilities/more_itertools.py +4 -6
  34. utilities/numpy.py +2 -1
  35. utilities/operator.py +2 -2
  36. utilities/orjson.py +42 -43
  37. utilities/os.py +4 -147
  38. utilities/packaging.py +129 -0
  39. utilities/parse.py +35 -15
  40. utilities/pathlib.py +3 -120
  41. utilities/platform.py +8 -90
  42. utilities/polars.py +38 -32
  43. utilities/postgres.py +37 -33
  44. utilities/pottery.py +20 -18
  45. utilities/pqdm.py +3 -4
  46. utilities/psutil.py +2 -3
  47. utilities/pydantic.py +25 -0
  48. utilities/pydantic_settings.py +87 -16
  49. utilities/pydantic_settings_sops.py +16 -3
  50. utilities/pyinstrument.py +4 -4
  51. utilities/pytest.py +96 -125
  52. utilities/pytest_plugins/pytest_regressions.py +2 -2
  53. utilities/pytest_regressions.py +32 -11
  54. utilities/random.py +2 -8
  55. utilities/redis.py +98 -94
  56. utilities/reprlib.py +11 -118
  57. utilities/shellingham.py +66 -0
  58. utilities/shutil.py +25 -0
  59. utilities/slack_sdk.py +13 -12
  60. utilities/sqlalchemy.py +57 -30
  61. utilities/sqlalchemy_polars.py +16 -25
  62. utilities/subprocess.py +2590 -0
  63. utilities/tabulate.py +32 -0
  64. utilities/testbook.py +8 -8
  65. utilities/text.py +24 -99
  66. utilities/throttle.py +159 -0
  67. utilities/time.py +18 -0
  68. utilities/timer.py +31 -14
  69. utilities/traceback.py +16 -23
  70. utilities/types.py +42 -2
  71. utilities/typing.py +26 -14
  72. utilities/uuid.py +1 -1
  73. utilities/version.py +202 -45
  74. utilities/whenever.py +53 -150
  75. dycw_utilities-0.166.30.dist-info/METADATA +0 -41
  76. dycw_utilities-0.166.30.dist-info/RECORD +0 -98
  77. dycw_utilities-0.166.30.dist-info/licenses/LICENSE +0 -21
  78. utilities/aeventkit.py +0 -388
  79. utilities/atomicwrites.py +0 -182
  80. utilities/cryptography.py +0 -41
  81. utilities/getpass.py +0 -8
  82. utilities/git.py +0 -19
  83. utilities/gzip.py +0 -31
  84. utilities/json.py +0 -70
  85. utilities/pickle.py +0 -25
  86. utilities/re.py +0 -156
  87. utilities/sentinel.py +0 -73
  88. utilities/socket.py +0 -8
  89. utilities/string.py +0 -20
  90. utilities/tempfile.py +0 -77
  91. utilities/typed_settings.py +0 -152
  92. utilities/tzdata.py +0 -11
  93. utilities/tzlocal.py +0 -28
  94. utilities/warnings.py +0 -65
  95. utilities/zipfile.py +0 -25
  96. utilities/zoneinfo.py +0 -133
utilities/iterables.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import builtins
4
3
  from collections import Counter
5
4
  from collections.abc import (
6
5
  Callable,
@@ -14,11 +13,10 @@ from collections.abc import (
14
13
  from collections.abc import Set as AbstractSet
15
14
  from contextlib import suppress
16
15
  from dataclasses import dataclass
17
- from enum import Enum
18
- from functools import cmp_to_key, partial, reduce
19
- from itertools import accumulate, chain, groupby, islice, pairwise, product
16
+ from functools import cmp_to_key, reduce
17
+ from itertools import groupby
20
18
  from math import isnan
21
- from operator import add, or_
19
+ from operator import or_
22
20
  from typing import (
23
21
  TYPE_CHECKING,
24
22
  Any,
@@ -30,6 +28,7 @@ from typing import (
30
28
  override,
31
29
  )
32
30
 
31
+ from utilities.core import OneStrEmptyError, always_iterable, one, one_str, repr_
33
32
  from utilities.errors import ImpossibleCaseError
34
33
  from utilities.math import (
35
34
  _CheckIntegerEqualError,
@@ -38,9 +37,7 @@ from utilities.math import (
38
37
  _CheckIntegerMinError,
39
38
  check_integer,
40
39
  )
41
- from utilities.reprlib import get_repr
42
- from utilities.sentinel import Sentinel, is_sentinel, sentinel
43
- from utilities.types import SupportsAdd, SupportsLT
40
+ from utilities.types import SupportsLT
44
41
 
45
42
  if TYPE_CHECKING:
46
43
  from types import NoneType
@@ -48,23 +45,6 @@ if TYPE_CHECKING:
48
45
  from utilities.types import MaybeIterable, Sign, StrMapping
49
46
 
50
47
 
51
- ##
52
-
53
-
54
- def always_iterable[T](obj: MaybeIterable[T], /) -> Iterable[T]:
55
- """Typed version of `always_iterable`."""
56
- obj = cast("Any", obj)
57
- if isinstance(obj, str | bytes):
58
- return cast("list[T]", [obj])
59
- try:
60
- return iter(cast("Iterable[T]", obj))
61
- except TypeError:
62
- return cast("list[T]", [obj])
63
-
64
-
65
- ##
66
-
67
-
68
48
  def apply_bijection[T, U](
69
49
  func: Callable[[T], U], iterable: Iterable[T], /
70
50
  ) -> Mapping[T, U]:
@@ -96,7 +76,7 @@ class ApplyBijectionError[T](Exception):
96
76
  class _ApplyBijectionDuplicateKeysError[T](ApplyBijectionError[T]):
97
77
  @override
98
78
  def __str__(self) -> str:
99
- return f"Keys {get_repr(self.keys)} must not contain duplicates; got {get_repr(self.counts)}"
79
+ return f"Keys {repr_(self.keys)} must not contain duplicates; got {repr_(self.counts)}"
100
80
 
101
81
 
102
82
  @dataclass(kw_only=True, slots=True)
@@ -105,7 +85,7 @@ class _ApplyBijectionDuplicateValuesError[T, U](ApplyBijectionError[T]):
105
85
 
106
86
  @override
107
87
  def __str__(self) -> str:
108
- return f"Values {get_repr(self.values)} must not contain duplicates; got {get_repr(self.counts)}"
88
+ return f"Values {repr_(self.values)} must not contain duplicates; got {repr_(self.counts)}"
109
89
 
110
90
 
111
91
  ##
@@ -116,9 +96,6 @@ def apply_to_tuple[T](func: Callable[..., T], args: tuple[Any, ...], /) -> T:
116
96
  return apply_to_varargs(func, *args)
117
97
 
118
98
 
119
- ##
120
-
121
-
122
99
  def apply_to_varargs[T](func: Callable[..., T], *args: Any) -> T:
123
100
  """Apply a function to a variable number of arguments."""
124
101
  return func(*args)
@@ -127,62 +104,6 @@ def apply_to_varargs[T](func: Callable[..., T], *args: Any) -> T:
127
104
  ##
128
105
 
129
106
 
130
- @overload
131
- def chain_mappings[K, V](
132
- *mappings: Mapping[K, V], list: Literal[True]
133
- ) -> Mapping[K, Sequence[V]]: ...
134
- @overload
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],
140
- list: bool = False, # noqa: A002
141
- ) -> Mapping[K, Iterable[V]]:
142
- """Chain the values of a set of mappings."""
143
- try:
144
- first, *rest = mappings
145
- except ValueError:
146
- return {}
147
- initial = {k: [v] for k, v in first.items()}
148
- reduced = reduce(_chain_mappings_one, rest, initial)
149
- if list:
150
- return {k: builtins.list(v) for k, v in reduced.items()}
151
- return reduced
152
-
153
-
154
- def _chain_mappings_one[K, V](
155
- acc: Mapping[K, Iterable[V]], el: Mapping[K, V], /
156
- ) -> Mapping[K, Iterable[V]]:
157
- """Chain the values of a set of mappings."""
158
- out = dict(acc)
159
- for key, value in el.items():
160
- out[key] = chain(out.get(key, []), [value])
161
- return out
162
-
163
-
164
- ##
165
-
166
-
167
- def chain_maybe_iterables[T](*maybe_iterables: MaybeIterable[T]) -> Iterable[T]:
168
- """Chain a set of maybe iterables."""
169
- iterables = map(always_iterable, maybe_iterables)
170
- return chain.from_iterable(iterables)
171
-
172
-
173
- ##
174
-
175
-
176
- def chain_nullable[T](*maybe_iterables: Iterable[T | None] | None) -> Iterable[T]:
177
- """Chain a set of values; ignoring nulls."""
178
- iterables = (mi for mi in maybe_iterables if mi is not None)
179
- values = ((i for i in it if i is not None) for it in iterables)
180
- return chain.from_iterable(values)
181
-
182
-
183
- ##
184
-
185
-
186
107
  def check_bijection(mapping: Mapping[Any, Hashable], /) -> None:
187
108
  """Check if a mapping is a bijection."""
188
109
  try:
@@ -198,7 +119,7 @@ class CheckBijectionError[THashable](Exception):
198
119
 
199
120
  @override
200
121
  def __str__(self) -> str:
201
- return f"Mapping {get_repr(self.mapping)} must be a bijection; got duplicates {get_repr(self.counts)}"
122
+ return f"Mapping {repr_(self.mapping)} must be a bijection; got duplicates {repr_(self.counts)}"
202
123
 
203
124
 
204
125
  ##
@@ -218,7 +139,7 @@ class CheckDuplicatesError[THashable](Exception):
218
139
 
219
140
  @override
220
141
  def __str__(self) -> str:
221
- return f"Iterable {get_repr(self.iterable)} must not contain duplicates; got {get_repr(self.counts)}"
142
+ return f"Iterable {repr_(self.iterable)} must not contain duplicates; got {repr_(self.counts)}"
222
143
 
223
144
 
224
145
  ##
@@ -270,12 +191,12 @@ class CheckIterablesEqualError[T](Exception):
270
191
  desc = f"{first} and {second}"
271
192
  case _: # pragma: no cover
272
193
  raise ImpossibleCaseError(case=[f"{parts=}"])
273
- return f"Iterables {get_repr(self.left)} and {get_repr(self.right)} must be equal; {desc}"
194
+ return f"Iterables {repr_(self.left)} and {repr_(self.right)} must be equal; {desc}"
274
195
 
275
196
  def _yield_parts(self) -> Iterator[str]:
276
197
  if len(self.errors) >= 1:
277
198
  errors = [(f"{i=}", lv, rv) for i, lv, rv in self.errors]
278
- yield f"differing items were {get_repr(errors)}"
199
+ yield f"differing items were {repr_(errors)}"
279
200
  match self.state:
280
201
  case "left_longer":
281
202
  yield "left was longer"
@@ -326,7 +247,7 @@ class _CheckLengthEqualError(CheckLengthError):
326
247
 
327
248
  @override
328
249
  def __str__(self) -> str:
329
- return f"Object {get_repr(self.obj)} must have length {self.equal}; got {len(self.obj)}"
250
+ return f"Object {repr_(self.obj)} must have length {self.equal}; got {len(self.obj)}"
330
251
 
331
252
 
332
253
  @dataclass(kw_only=True, slots=True)
@@ -340,7 +261,7 @@ class _CheckLengthEqualOrApproxError(CheckLengthError):
340
261
  desc = f"approximate length {target} (error {error:%})"
341
262
  case target:
342
263
  desc = f"length {target}"
343
- return f"Object {get_repr(self.obj)} must have {desc}; got {len(self.obj)}"
264
+ return f"Object {repr_(self.obj)} must have {desc}; got {len(self.obj)}"
344
265
 
345
266
 
346
267
  @dataclass(kw_only=True, slots=True)
@@ -349,7 +270,7 @@ class _CheckLengthMinError(CheckLengthError):
349
270
 
350
271
  @override
351
272
  def __str__(self) -> str:
352
- return f"Object {get_repr(self.obj)} must have minimum length {self.min_}; got {len(self.obj)}"
273
+ return f"Object {repr_(self.obj)} must have minimum length {self.min_}; got {len(self.obj)}"
353
274
 
354
275
 
355
276
  @dataclass(kw_only=True, slots=True)
@@ -358,7 +279,7 @@ class _CheckLengthMaxError(CheckLengthError):
358
279
 
359
280
  @override
360
281
  def __str__(self) -> str:
361
- return f"Object {get_repr(self.obj)} must have maximum length {self.max_}; got {len(self.obj)}"
282
+ return f"Object {repr_(self.obj)} must have maximum length {self.max_}; got {len(self.obj)}"
362
283
 
363
284
 
364
285
  ##
@@ -377,7 +298,7 @@ class CheckLengthsEqualError(Exception):
377
298
 
378
299
  @override
379
300
  def __str__(self) -> str:
380
- return f"Sized objects {get_repr(self.left)} and {get_repr(self.right)} must have the same length; got {len(self.left)} and {len(self.right)}"
301
+ return f"Sized objects {repr_(self.left)} and {repr_(self.right)} must have the same length; got {len(self.left)} and {len(self.right)}"
381
302
 
382
303
 
383
304
  ##
@@ -427,16 +348,18 @@ class CheckMappingsEqualError[K, V](Exception):
427
348
  desc = f"{first}, {second} and {third}"
428
349
  case _: # pragma: no cover
429
350
  raise ImpossibleCaseError(case=[f"{parts=}"])
430
- return f"Mappings {get_repr(self.left)} and {get_repr(self.right)} must be equal; {desc}"
351
+ return (
352
+ f"Mappings {repr_(self.left)} and {repr_(self.right)} must be equal; {desc}"
353
+ )
431
354
 
432
355
  def _yield_parts(self) -> Iterator[str]:
433
356
  if len(self.left_extra) >= 1:
434
- yield f"left had extra keys {get_repr(self.left_extra)}"
357
+ yield f"left had extra keys {repr_(self.left_extra)}"
435
358
  if len(self.right_extra) >= 1:
436
- yield f"right had extra keys {get_repr(self.right_extra)}"
359
+ yield f"right had extra keys {repr_(self.right_extra)}"
437
360
  if len(self.errors) >= 1:
438
361
  errors = [(f"{k=}", lv, rv) for k, lv, rv in self.errors]
439
- yield f"differing values were {get_repr(errors)}"
362
+ yield f"differing values were {repr_(errors)}"
440
363
 
441
364
 
442
365
  ##
@@ -474,13 +397,13 @@ class CheckSetsEqualError[T](Exception):
474
397
  desc = f"{first} and {second}"
475
398
  case _: # pragma: no cover
476
399
  raise ImpossibleCaseError(case=[f"{parts=}"])
477
- return f"Sets {get_repr(self.left)} and {get_repr(self.right)} must be equal; {desc}"
400
+ return f"Sets {repr_(self.left)} and {repr_(self.right)} must be equal; {desc}"
478
401
 
479
402
  def _yield_parts(self) -> Iterator[str]:
480
403
  if len(self.left_extra) >= 1:
481
- yield f"left had extra items {get_repr(self.left_extra)}"
404
+ yield f"left had extra items {repr_(self.left_extra)}"
482
405
  if len(self.right_extra) >= 1:
483
- yield f"right had extra items {get_repr(self.right_extra)}"
406
+ yield f"right had extra items {repr_(self.right_extra)}"
484
407
 
485
408
 
486
409
  ##
@@ -521,14 +444,14 @@ class CheckSubMappingError[K, V](Exception):
521
444
  desc = f"{first} and {second}"
522
445
  case _: # pragma: no cover
523
446
  raise ImpossibleCaseError(case=[f"{parts=}"])
524
- return f"Mapping {get_repr(self.left)} must be a submapping of {get_repr(self.right)}; {desc}"
447
+ return f"Mapping {repr_(self.left)} must be a submapping of {repr_(self.right)}; {desc}"
525
448
 
526
449
  def _yield_parts(self) -> Iterator[str]:
527
450
  if len(self.extra) >= 1:
528
- yield f"left had extra keys {get_repr(self.extra)}"
451
+ yield f"left had extra keys {repr_(self.extra)}"
529
452
  if len(self.errors) >= 1:
530
453
  errors = [(f"{k=}", lv, rv) for k, lv, rv in self.errors]
531
- yield f"differing values were {get_repr(errors)}"
454
+ yield f"differing values were {repr_(errors)}"
532
455
 
533
456
 
534
457
  ##
@@ -551,7 +474,7 @@ class CheckSubSetError[T](Exception):
551
474
 
552
475
  @override
553
476
  def __str__(self) -> str:
554
- return f"Set {get_repr(self.left)} must be a subset of {get_repr(self.right)}; left had extra items {get_repr(self.extra)}"
477
+ return f"Set {repr_(self.left)} must be a subset of {repr_(self.right)}; left had extra items {repr_(self.extra)}"
555
478
 
556
479
 
557
480
  ##
@@ -592,14 +515,14 @@ class CheckSuperMappingError[K, V](Exception):
592
515
  desc = f"{first} and {second}"
593
516
  case _: # pragma: no cover
594
517
  raise ImpossibleCaseError(case=[f"{parts=}"])
595
- return f"Mapping {get_repr(self.left)} must be a supermapping of {get_repr(self.right)}; {desc}"
518
+ return f"Mapping {repr_(self.left)} must be a supermapping of {repr_(self.right)}; {desc}"
596
519
 
597
520
  def _yield_parts(self) -> Iterator[str]:
598
521
  if len(self.extra) >= 1:
599
- yield f"right had extra keys {get_repr(self.extra)}"
522
+ yield f"right had extra keys {repr_(self.extra)}"
600
523
  if len(self.errors) >= 1:
601
524
  errors = [(f"{k=}", lv, rv) for k, lv, rv in self.errors]
602
- yield f"differing values were {get_repr(errors)}"
525
+ yield f"differing values were {repr_(errors)}"
603
526
 
604
527
 
605
528
  ##
@@ -622,7 +545,7 @@ class CheckSuperSetError[T](Exception):
622
545
 
623
546
  @override
624
547
  def __str__(self) -> str:
625
- return f"Set {get_repr(self.left)} must be a superset of {get_repr(self.right)}; right had extra items {get_repr(self.extra)}."
548
+ return f"Set {repr_(self.left)} must be a superset of {repr_(self.right)}; right had extra items {repr_(self.extra)}."
626
549
 
627
550
 
628
551
  ##
@@ -652,7 +575,7 @@ class CheckUniqueModuloCaseError(Exception):
652
575
  class _CheckUniqueModuloCaseDuplicateStringsError(CheckUniqueModuloCaseError):
653
576
  @override
654
577
  def __str__(self) -> str:
655
- return f"Strings {get_repr(self.keys)} must not contain duplicates; got {get_repr(self.counts)}"
578
+ return f"Strings {repr_(self.keys)} must not contain duplicates; got {repr_(self.counts)}"
656
579
 
657
580
 
658
581
  @dataclass(kw_only=True, slots=True)
@@ -661,7 +584,7 @@ class _CheckUniqueModuloCaseDuplicateLowerCaseStringsError(CheckUniqueModuloCase
661
584
 
662
585
  @override
663
586
  def __str__(self) -> str:
664
- return f"Strings {get_repr(self.values)} must not contain duplicates (modulo case); got {get_repr(self.counts)}"
587
+ return f"Strings {repr_(self.values)} must not contain duplicates (modulo case); got {repr_(self.counts)}"
665
588
 
666
589
 
667
590
  ##
@@ -685,14 +608,6 @@ def cmp_nullable[T: SupportsLT](x: T | None, y: T | None, /) -> Sign:
685
608
  ##
686
609
 
687
610
 
688
- def chunked[T](iterable: Iterable[T], n: int, /) -> Iterator[Sequence[T]]:
689
- """Break an iterable into lists of length n."""
690
- return iter(partial(take, n, iter(iterable)), [])
691
-
692
-
693
- ##
694
-
695
-
696
611
  def ensure_iterable(obj: Any, /) -> Iterable[Any]:
697
612
  """Ensure an object is iterable."""
698
613
  if is_iterable(obj):
@@ -706,26 +621,7 @@ class EnsureIterableError(Exception):
706
621
 
707
622
  @override
708
623
  def __str__(self) -> str:
709
- return f"Object {get_repr(self.obj)} must be iterable"
710
-
711
-
712
- ##
713
-
714
-
715
- def ensure_iterable_not_str(obj: Any, /) -> Iterable[Any]:
716
- """Ensure an object is iterable, but not a string."""
717
- if is_iterable_not_str(obj):
718
- return obj
719
- raise EnsureIterableNotStrError(obj=obj)
720
-
721
-
722
- @dataclass(kw_only=True, slots=True)
723
- class EnsureIterableNotStrError(Exception):
724
- obj: Any
725
-
726
- @override
727
- def __str__(self) -> str:
728
- return f"Object {get_repr(self.obj)} must be iterable, but not a string"
624
+ return f"Object {repr_(self.obj)} must be iterable"
729
625
 
730
626
 
731
627
  ##
@@ -751,18 +647,6 @@ def enumerate_with_edge[T](
751
647
  ##
752
648
 
753
649
 
754
- def expanding_window[T](iterable: Iterable[T], /) -> islice[list[T]]:
755
- """Yield an expanding window over an iterable."""
756
-
757
- def func(acc: Iterable[T], el: T, /) -> list[T]:
758
- return list(chain(acc, [el]))
759
-
760
- return islice(accumulate(iterable, func=func, initial=[]), 1, None)
761
-
762
-
763
- ##
764
-
765
-
766
650
  @overload
767
651
  def filter_include_and_exclude[T, U](
768
652
  iterable: Iterable[T],
@@ -830,14 +714,6 @@ def groupby_lists[T, U](
830
714
  ##
831
715
 
832
716
 
833
- def hashable_to_iterable[T: Hashable](obj: T | None, /) -> tuple[T, ...] | None:
834
- """Lift a hashable singleton to an iterable of hashables."""
835
- return None if obj is None else (obj,)
836
-
837
-
838
- ##
839
-
840
-
841
717
  def is_iterable(obj: Any, /) -> TypeGuard[Iterable[Any]]:
842
718
  """Check if an object is iterable."""
843
719
  try:
@@ -850,14 +726,6 @@ def is_iterable(obj: Any, /) -> TypeGuard[Iterable[Any]]:
850
726
  ##
851
727
 
852
728
 
853
- def is_iterable_not_enum(obj: Any, /) -> TypeGuard[Iterable[Any]]:
854
- """Check if an object is iterable, but not an Enum."""
855
- return is_iterable(obj) and not (isinstance(obj, type) and issubclass(obj, Enum))
856
-
857
-
858
- ##
859
-
860
-
861
729
  def is_iterable_not_str(obj: Any, /) -> TypeGuard[Iterable[Any]]:
862
730
  """Check if an object is iterable, but not a string."""
863
731
  return is_iterable(obj) and not isinstance(obj, str)
@@ -925,309 +793,7 @@ class MergeStrMappingsError(Exception):
925
793
 
926
794
  @override
927
795
  def __str__(self) -> str:
928
- return f"Mapping {get_repr(self.mapping)} keys must not contain duplicates (modulo case); got {get_repr(self.counts)}"
929
-
930
-
931
- ##
932
-
933
-
934
- def one[T](*iterables: Iterable[T]) -> T:
935
- """Return the unique value in a set of iterables."""
936
- it = chain(*iterables)
937
- try:
938
- first = next(it)
939
- except StopIteration:
940
- raise OneEmptyError(iterables=iterables) from None
941
- try:
942
- second = next(it)
943
- except StopIteration:
944
- return first
945
- raise OneNonUniqueError(iterables=iterables, first=first, second=second)
946
-
947
-
948
- @dataclass(kw_only=True, slots=True)
949
- class OneError[T](Exception):
950
- iterables: tuple[Iterable[T], ...]
951
-
952
-
953
- @dataclass(kw_only=True, slots=True)
954
- class OneEmptyError[T](OneError[T]):
955
- @override
956
- def __str__(self) -> str:
957
- return f"Iterable(s) {get_repr(self.iterables)} must not be empty"
958
-
959
-
960
- @dataclass(kw_only=True, slots=True)
961
- class OneNonUniqueError[T](OneError):
962
- first: T
963
- second: T
964
-
965
- @override
966
- def __str__(self) -> str:
967
- return f"Iterable(s) {get_repr(self.iterables)} must contain exactly one item; got {self.first}, {self.second} and perhaps more"
968
-
969
-
970
- ##
971
-
972
-
973
- def one_maybe[T](*objs: MaybeIterable[T]) -> T:
974
- """Return the unique value in a set of values/iterables."""
975
- try:
976
- return one(chain_maybe_iterables(*objs))
977
- except OneEmptyError:
978
- raise OneMaybeEmptyError from None
979
- except OneNonUniqueError as error:
980
- raise OneMaybeNonUniqueError(
981
- objs=objs, first=error.first, second=error.second
982
- ) from None
983
-
984
-
985
- @dataclass(kw_only=True, slots=True)
986
- class OneMaybeError(Exception): ...
987
-
988
-
989
- @dataclass(kw_only=True, slots=True)
990
- class OneMaybeEmptyError(OneMaybeError):
991
- @override
992
- def __str__(self) -> str:
993
- return "Object(s) must not be empty"
994
-
995
-
996
- @dataclass(kw_only=True, slots=True)
997
- class OneMaybeNonUniqueError[T](OneMaybeError):
998
- objs: tuple[MaybeIterable[T], ...]
999
- first: T
1000
- second: T
1001
-
1002
- @override
1003
- def __str__(self) -> str:
1004
- return f"Object(s) {get_repr(self.objs)} must contain exactly one item; got {self.first}, {self.second} and perhaps more"
1005
-
1006
-
1007
- ##
1008
-
1009
-
1010
- def one_str(
1011
- iterable: Iterable[str],
1012
- text: str,
1013
- /,
1014
- *,
1015
- head: bool = False,
1016
- case_sensitive: bool = False,
1017
- ) -> str:
1018
- """Find the unique string in an iterable."""
1019
- as_list = list(iterable)
1020
- match head, case_sensitive:
1021
- case False, True:
1022
- it = (t for t in as_list if t == text)
1023
- case False, False:
1024
- it = (t for t in as_list if t.lower() == text.lower())
1025
- case True, True:
1026
- it = (t for t in as_list if t.startswith(text))
1027
- case True, False:
1028
- it = (t for t in as_list if t.lower().startswith(text.lower()))
1029
- case never:
1030
- assert_never(never)
1031
- try:
1032
- return one(it)
1033
- except OneEmptyError:
1034
- raise OneStrEmptyError(
1035
- iterable=as_list, text=text, head=head, case_sensitive=case_sensitive
1036
- ) from None
1037
- except OneNonUniqueError as error:
1038
- raise OneStrNonUniqueError(
1039
- iterable=as_list,
1040
- text=text,
1041
- head=head,
1042
- case_sensitive=case_sensitive,
1043
- first=error.first,
1044
- second=error.second,
1045
- ) from None
1046
-
1047
-
1048
- @dataclass(kw_only=True, slots=True)
1049
- class OneStrError(Exception):
1050
- iterable: Iterable[str]
1051
- text: str
1052
- head: bool = False
1053
- case_sensitive: bool = False
1054
-
1055
-
1056
- @dataclass(kw_only=True, slots=True)
1057
- class OneStrEmptyError(OneStrError):
1058
- @override
1059
- def __str__(self) -> str:
1060
- head = f"Iterable {get_repr(self.iterable)} does not contain"
1061
- match self.head, self.case_sensitive:
1062
- case False, True:
1063
- tail = repr(self.text)
1064
- case False, False:
1065
- tail = f"{self.text!r} (modulo case)"
1066
- case True, True:
1067
- tail = f"any string starting with {self.text!r}"
1068
- case True, False:
1069
- tail = f"any string starting with {self.text!r} (modulo case)"
1070
- case never:
1071
- assert_never(never)
1072
- return f"{head} {tail}"
1073
-
1074
-
1075
- @dataclass(kw_only=True, slots=True)
1076
- class OneStrNonUniqueError(OneStrError):
1077
- first: str
1078
- second: str
1079
-
1080
- @override
1081
- def __str__(self) -> str:
1082
- head = f"Iterable {get_repr(self.iterable)} must contain"
1083
- match self.head, self.case_sensitive:
1084
- case False, True:
1085
- mid = f"{self.text!r} exactly once"
1086
- case False, False:
1087
- mid = f"{self.text!r} exactly once (modulo case)"
1088
- case True, True:
1089
- mid = f"exactly one string starting with {self.text!r}"
1090
- case True, False:
1091
- mid = f"exactly one string starting with {self.text!r} (modulo case)"
1092
- case never:
1093
- assert_never(never)
1094
- return f"{head} {mid}; got {self.first!r}, {self.second!r} and perhaps more"
1095
-
1096
-
1097
- ##
1098
-
1099
-
1100
- def one_unique[T: Hashable](*iterables: Iterable[T]) -> T:
1101
- """Return the set-unique value in a set of iterables."""
1102
- try:
1103
- return one(set(chain(*iterables)))
1104
- except OneEmptyError:
1105
- raise OneUniqueEmptyError from None
1106
- except OneNonUniqueError as error:
1107
- raise OneUniqueNonUniqueError(
1108
- iterables=iterables, first=error.first, second=error.second
1109
- ) from None
1110
-
1111
-
1112
- @dataclass(kw_only=True, slots=True)
1113
- class OneUniqueError(Exception): ...
1114
-
1115
-
1116
- @dataclass(kw_only=True, slots=True)
1117
- class OneUniqueEmptyError(OneUniqueError):
1118
- @override
1119
- def __str__(self) -> str:
1120
- return "Iterable(s) must not be empty"
1121
-
1122
-
1123
- @dataclass(kw_only=True, slots=True)
1124
- class OneUniqueNonUniqueError[THashable](OneUniqueError):
1125
- iterables: tuple[MaybeIterable[THashable], ...]
1126
- first: THashable
1127
- second: THashable
1128
-
1129
- @override
1130
- def __str__(self) -> str:
1131
- return f"Iterable(s) {get_repr(self.iterables)} must contain exactly one item; got {self.first}, {self.second} and perhaps more"
1132
-
1133
-
1134
- ##
1135
-
1136
-
1137
- def pairwise_tail[T](iterable: Iterable[T], /) -> Iterator[tuple[T, T | Sentinel]]:
1138
- """Return pairwise elements, with the last paired with the sentinel."""
1139
- return pairwise(chain(iterable, [sentinel]))
1140
-
1141
-
1142
- ##
1143
-
1144
-
1145
- def product_dicts[K, V](mapping: Mapping[K, Iterable[V]], /) -> Iterator[Mapping[K, V]]:
1146
- """Return the cartesian product of the values in a mapping, as mappings."""
1147
- keys = list(mapping)
1148
- for values in product(*mapping.values()):
1149
- yield cast("Mapping[K, V]", dict(zip(keys, values, strict=True)))
1150
-
1151
-
1152
- ##
1153
-
1154
-
1155
- def range_partitions(stop: int, num: int, total: int, /) -> range:
1156
- """Partition a range."""
1157
- if stop <= 0:
1158
- raise _RangePartitionsStopError(stop=stop)
1159
- if not (1 <= total <= stop):
1160
- raise _RangePartitionsTotalError(stop=stop, total=total)
1161
- if not (0 <= num < total):
1162
- raise _RangePartitionsNumError(num=num, total=total)
1163
- q, r = divmod(stop, total)
1164
- start = num * q + min(num, r)
1165
- end = start + q + (1 if num < r else 0)
1166
- return range(start, end)
1167
-
1168
-
1169
- @dataclass(kw_only=True, slots=True)
1170
- class RangePartitionsError(Exception): ...
1171
-
1172
-
1173
- @dataclass(kw_only=True, slots=True)
1174
- class _RangePartitionsStopError(RangePartitionsError):
1175
- stop: int
1176
-
1177
- @override
1178
- def __str__(self) -> str:
1179
- return f"'stop' must be positive; got {self.stop}"
1180
-
1181
-
1182
- @dataclass(kw_only=True, slots=True)
1183
- class _RangePartitionsTotalError(RangePartitionsError):
1184
- stop: int
1185
- total: int
1186
-
1187
- @override
1188
- def __str__(self) -> str:
1189
- return f"'total' must be in [1, {self.stop}]; got {self.total}"
1190
-
1191
-
1192
- @dataclass(kw_only=True, slots=True)
1193
- class _RangePartitionsNumError(RangePartitionsError):
1194
- num: int
1195
- total: int
1196
-
1197
- @override
1198
- def __str__(self) -> str:
1199
- return f"'num' must be in [0, {self.total - 1}]; got {self.num}"
1200
-
1201
-
1202
- ##
1203
-
1204
-
1205
- @overload
1206
- def reduce_mappings[K, V](
1207
- func: Callable[[V, V], V], sequence: Iterable[Mapping[K, V]], /
1208
- ) -> Mapping[K, V]: ...
1209
- @overload
1210
- def reduce_mappings[K, V, W](
1211
- func: Callable[[W, V], W],
1212
- sequence: Iterable[Mapping[K, V]],
1213
- /,
1214
- *,
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]],
1220
- /,
1221
- *,
1222
- initial: W | Sentinel = sentinel,
1223
- ) -> Mapping[K, V | W]:
1224
- """Reduce a function over the values of a set of mappings."""
1225
- chained = chain_mappings(*sequence)
1226
- if is_sentinel(initial):
1227
- func2 = cast("Callable[[V, V], V]", func)
1228
- return {k: reduce(func2, v) for k, v in chained.items()}
1229
- func2 = cast("Callable[[W, V], W]", func)
1230
- return {k: reduce(func2, v, initial) for k, v in chained.items()}
796
+ return f"Mapping {repr_(self.mapping)} keys must not contain duplicates (modulo case); got {repr_(self.counts)}"
1231
797
 
1232
798
 
1233
799
  ##
@@ -1258,7 +824,7 @@ class ResolveIncludeAndExcludeError[T](Exception):
1258
824
  include = list(self.include)
1259
825
  exclude = list(self.exclude)
1260
826
  overlap = set(include) & set(exclude)
1261
- return f"Iterables {get_repr(include)} and {get_repr(exclude)} must not overlap; got {get_repr(overlap)}"
827
+ return f"Iterables {repr_(include)} and {repr_(exclude)} must not overlap; got {repr_(overlap)}"
1262
828
 
1263
829
 
1264
830
  ##
@@ -1325,7 +891,7 @@ class SortIterableError(Exception):
1325
891
 
1326
892
  @override
1327
893
  def __str__(self) -> str:
1328
- return f"Unable to sort {get_repr(self.x)} and {get_repr(self.y)}"
894
+ return f"Unable to sort {repr_(self.x)} and {repr_(self.y)}"
1329
895
 
1330
896
 
1331
897
  def _sort_iterable_cmp_floats(x: float, y: float, /) -> Sign:
@@ -1347,74 +913,6 @@ def _sort_iterable_cmp_floats(x: float, y: float, /) -> Sign:
1347
913
  ##
1348
914
 
1349
915
 
1350
- def sum_mappings[K: Hashable, V: SupportsAdd](
1351
- *mappings: Mapping[K, V],
1352
- ) -> Mapping[K, V]:
1353
- """Sum the values of a set of mappings."""
1354
- return reduce_mappings(add, mappings, initial=0)
1355
-
1356
-
1357
- ##
1358
-
1359
-
1360
- def take[T](n: int, iterable: Iterable[T], /) -> Sequence[T]:
1361
- """Return first n items of the iterable as a list."""
1362
- return list(islice(iterable, n))
1363
-
1364
-
1365
- ##
1366
-
1367
-
1368
- @overload
1369
- def transpose[T1](iterable: Iterable[tuple[T1]], /) -> tuple[list[T1]]: ...
1370
- @overload
1371
- def transpose[T1, T2](
1372
- iterable: Iterable[tuple[T1, T2]], /
1373
- ) -> tuple[list[T1], list[T2]]: ...
1374
- @overload
1375
- def transpose[T1, T2, T3](
1376
- iterable: Iterable[tuple[T1, T2, T3]], /
1377
- ) -> tuple[list[T1], list[T2], list[T3]]: ...
1378
- @overload
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]]: ...
1382
- @overload
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]]: ...
1386
- def transpose(iterable: Iterable[tuple[Any]]) -> tuple[list[Any], ...]: # pyright: ignore[reportInconsistentOverload]
1387
- """Typed verison of `transpose`."""
1388
- return tuple(map(list, zip(*iterable, strict=True)))
1389
-
1390
-
1391
- ##
1392
-
1393
-
1394
- def unique_everseen[T](
1395
- iterable: Iterable[T], /, *, key: Callable[[T], Any] | None = None
1396
- ) -> Iterator[T]:
1397
- """Yield unique elements, preserving order."""
1398
- seenset = set()
1399
- seenset_add = seenset.add
1400
- seenlist = []
1401
- seenlist_add = seenlist.append
1402
- use_key = key is not None
1403
- for element in iterable:
1404
- k = key(element) if use_key else element
1405
- try:
1406
- if k not in seenset:
1407
- seenset_add(k)
1408
- yield element
1409
- except TypeError:
1410
- if k not in seenlist:
1411
- seenlist_add(k)
1412
- yield element
1413
-
1414
-
1415
- ##
1416
-
1417
-
1418
916
  __all__ = [
1419
917
  "ApplyBijectionError",
1420
918
  "CheckBijectionError",
@@ -1429,30 +927,13 @@ __all__ = [
1429
927
  "CheckSuperSetError",
1430
928
  "CheckUniqueModuloCaseError",
1431
929
  "EnsureIterableError",
1432
- "EnsureIterableNotStrError",
1433
930
  "MergeStrMappingsError",
1434
- "OneEmptyError",
1435
- "OneError",
1436
- "OneMaybeEmptyError",
1437
- "OneMaybeError",
1438
- "OneMaybeNonUniqueError",
1439
- "OneNonUniqueError",
1440
- "OneStrEmptyError",
1441
- "OneStrError",
1442
- "OneStrNonUniqueError",
1443
- "OneUniqueEmptyError",
1444
- "OneUniqueError",
1445
- "OneUniqueNonUniqueError",
1446
- "RangePartitionsError",
1447
931
  "ResolveIncludeAndExcludeError",
1448
932
  "SortIterableError",
1449
933
  "always_iterable",
1450
934
  "apply_bijection",
1451
935
  "apply_to_tuple",
1452
936
  "apply_to_varargs",
1453
- "chain_mappings",
1454
- "chain_maybe_iterables",
1455
- "chain_nullable",
1456
937
  "check_bijection",
1457
938
  "check_duplicates",
1458
939
  "check_iterables_equal",
@@ -1464,34 +945,17 @@ __all__ = [
1464
945
  "check_supermapping",
1465
946
  "check_superset",
1466
947
  "check_unique_modulo_case",
1467
- "chunked",
1468
948
  "cmp_nullable",
1469
949
  "ensure_iterable",
1470
- "ensure_iterable_not_str",
1471
950
  "enumerate_with_edge",
1472
- "expanding_window",
1473
951
  "filter_include_and_exclude",
1474
952
  "groupby_lists",
1475
- "hashable_to_iterable",
1476
953
  "is_iterable",
1477
- "is_iterable_not_enum",
1478
954
  "is_iterable_not_str",
1479
955
  "map_mapping",
1480
956
  "merge_mappings",
1481
957
  "merge_sets",
1482
958
  "merge_str_mappings",
1483
- "one",
1484
- "one_maybe",
1485
- "one_str",
1486
- "one_unique",
1487
- "pairwise_tail",
1488
- "product_dicts",
1489
- "range_partitions",
1490
- "reduce_mappings",
1491
959
  "resolve_include_and_exclude",
1492
960
  "sort_iterable",
1493
- "sum_mappings",
1494
- "take",
1495
- "transpose",
1496
- "unique_everseen",
1497
961
  ]