dycw-utilities 0.109.22__py3-none-any.whl → 0.109.24__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.109.22
3
+ Version: 0.109.24
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,11 +1,11 @@
1
- utilities/__init__.py,sha256=8f1PKZxzDeABQ7A4zZsmHKuHRkJKy1jboB_m42AXKxw,61
1
+ utilities/__init__.py,sha256=lTcMVKZy93oLWx7dgqyW_K3S_kxMyIwptBXIvNDLsl8,61
2
2
  utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
3
3
  utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
4
4
  utilities/asyncio.py,sha256=41oQUurWMvadFK5gFnaG21hMM0Vmfn2WS6OpC0R9mas,14757
5
5
  utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
6
6
  utilities/atools.py,sha256=IYMuFSFGSKyuQmqD6v5IUtDlz8PPw0Sr87Cub_gRU3M,1168
7
7
  utilities/cachetools.py,sha256=C1zqOg7BYz0IfQFK8e3qaDDgEZxDpo47F15RTfJM37Q,2910
8
- utilities/click.py,sha256=JX-A6WnUyE84WYz8OK4MSpUdBg8bA_KiWkqd491WCFQ,15256
8
+ utilities/click.py,sha256=lIkJIp_3vaoqsvADI4azoLgEWqlUHVRWBQePLbPrcPo,14243
9
9
  utilities/concurrent.py,sha256=s2scTEd2AhXVTW4hpASU2qxV_DiVLALfms55cCQzCvM,2886
10
10
  utilities/contextlib.py,sha256=OOIIEa5lXKGzFAnauaul40nlQnQko6Na4ryiMJcHkIg,478
11
11
  utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
@@ -46,7 +46,7 @@ utilities/pathlib.py,sha256=31WPMXdLIyXgYOMMl_HOI2wlo66MGSE-cgeelk-Lias,1410
46
46
  utilities/period.py,sha256=ikHXsWtDLr553cfH6p9mMaiCnIAP69B7q84ckWV3HaA,10884
47
47
  utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
48
48
  utilities/platform.py,sha256=NU7ycTvAXAG-fdYmDXaM1m4EOml2cGiaYwaUzfzSqyU,1767
49
- utilities/polars.py,sha256=pFUEkCzHh01M84fm4upFc4UKrUFMKz6WeWYvAlFLAPw,58009
49
+ utilities/polars.py,sha256=woTGhyzXNLN30SQgwXz54-I1aJp1oWATJ2rpmke7gKI,58419
50
50
  utilities/polars_ols.py,sha256=efhXf0gjrHUpQrvS6a7g8yJQJWf_ATKtJnqqF2inCOU,5680
51
51
  utilities/pqdm.py,sha256=foRytQybmOQ05pjt5LF7ANyzrIa--4ScDE3T2wd31a4,3118
52
52
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -73,7 +73,7 @@ utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
73
73
  utilities/sys.py,sha256=h0Xr7Vj86wNalvwJVP1wj5Y0kD_VWm1vzuXZ_jw94mE,2743
74
74
  utilities/tempfile.py,sha256=VqmZJAhTJ1OaVywFzk5eqROV8iJbW9XQ_QYAV0bpdRo,1384
75
75
  utilities/tenacity.py,sha256=1PUvODiBVgeqIh7G5TRt5WWMSqjLYkEqP53itT97WQc,4914
76
- utilities/text.py,sha256=X_EjRQeV_PsG3oP7OiGYIyXGKWqciTnSwoKhM2tsy6M,3120
76
+ utilities/text.py,sha256=fKPnh_wNFRXF1mM1CkTUUlBUrAj7Mf2xDGnHfrUepQs,4775
77
77
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
78
78
  utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
79
79
  utilities/traceback.py,sha256=KwHPLdEbdj0fFhXo8MBfxcvem8A-VXYDwFMNJ6f0cTM,27328
@@ -87,7 +87,7 @@ utilities/warnings.py,sha256=yUgjnmkCRf6QhdyAXzl7u0qQFejhQG3PrjoSwxpbHrs,1819
87
87
  utilities/whenever.py,sha256=TjoTAJ1R27-rKXiXzdE4GzPidmYqm0W58XydDXp-QZM,17786
88
88
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
89
89
  utilities/zoneinfo.py,sha256=-DQz5a0Ikw9jfSZtL0BEQkXOMC9yGn_xiJYNCLMiqEc,1989
90
- dycw_utilities-0.109.22.dist-info/METADATA,sha256=Q17jtIGlAaCIIt5pD-cHWOB9Uvq8i9yRQtNP0q8NmLI,13005
91
- dycw_utilities-0.109.22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- dycw_utilities-0.109.22.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
- dycw_utilities-0.109.22.dist-info/RECORD,,
90
+ dycw_utilities-0.109.24.dist-info/METADATA,sha256=qsqFi_RWRE2umffrznAhmCyC9F_zn5VwAbpMoaqO8iw,13005
91
+ dycw_utilities-0.109.24.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
+ dycw_utilities-0.109.24.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
+ dycw_utilities-0.109.24.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.109.22"
3
+ __version__ = "0.109.24"
utilities/click.py CHANGED
@@ -21,7 +21,6 @@ from utilities.datetime import EnsureMonthError, MonthLike, ensure_month
21
21
  from utilities.enum import EnsureEnumError, ensure_enum
22
22
  from utilities.functions import EnsureStrError, ensure_str, get_class_name
23
23
  from utilities.iterables import is_iterable_not_str
24
- from utilities.sentinel import SENTINEL_REPR
25
24
  from utilities.text import split_str
26
25
  from utilities.types import (
27
26
  DateLike,
@@ -245,13 +244,10 @@ class ZonedDateTime(ParamType):
245
244
  class FrozenSetParameter(ParamType, Generic[_TParam, _T]):
246
245
  """A frozenset-valued parameter."""
247
246
 
248
- def __init__(
249
- self, param: _TParam, /, *, separator: str = ",", empty: str = SENTINEL_REPR
250
- ) -> None:
247
+ def __init__(self, param: _TParam, /, *, separator: str = ",") -> None:
251
248
  self.name = f"FROZENSET[{param.name}]"
252
249
  self._param = param
253
250
  self._separator = separator
254
- self._empty = empty
255
251
  super().__init__()
256
252
 
257
253
  @override
@@ -273,7 +269,7 @@ class FrozenSetParameter(ParamType, Generic[_TParam, _T]):
273
269
  text = ensure_str(value)
274
270
  except EnsureStrError as error:
275
271
  return self.fail(str(error), param=param, ctx=ctx)
276
- values = split_str(text, separator=self._separator, empty=self._empty)
272
+ values = split_str(text, separator=self._separator)
277
273
  return frozenset(self._param.convert(v, param, ctx) for v in values)
278
274
 
279
275
  @override
@@ -290,15 +286,15 @@ class FrozenSetParameter(ParamType, Generic[_TParam, _T]):
290
286
  class FrozenSetBools(FrozenSetParameter[BoolParamType, str]):
291
287
  """A frozenset-of-bools-valued parameter."""
292
288
 
293
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
294
- super().__init__(BoolParamType(), separator=separator, empty=empty)
289
+ def __init__(self, *, separator: str = ",") -> None:
290
+ super().__init__(BoolParamType(), separator=separator)
295
291
 
296
292
 
297
293
  class FrozenSetDates(FrozenSetParameter[Date, dt.date]):
298
294
  """A frozenset-of-dates-valued parameter."""
299
295
 
300
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
301
- super().__init__(Date(), separator=separator, empty=empty)
296
+ def __init__(self, *, separator: str = ",") -> None:
297
+ super().__init__(Date(), separator=separator)
302
298
 
303
299
 
304
300
  class FrozenSetChoices(FrozenSetParameter[Choice, str]):
@@ -311,12 +307,9 @@ class FrozenSetChoices(FrozenSetParameter[Choice, str]):
311
307
  *,
312
308
  case_sensitive: bool = False,
313
309
  separator: str = ",",
314
- empty: str = SENTINEL_REPR,
315
310
  ) -> None:
316
311
  super().__init__(
317
- Choice(choices, case_sensitive=case_sensitive),
318
- separator=separator,
319
- empty=empty,
312
+ Choice(choices, case_sensitive=case_sensitive), separator=separator
320
313
  )
321
314
 
322
315
 
@@ -330,46 +323,43 @@ class FrozenSetEnums(FrozenSetParameter[Enum[TEnum], TEnum]):
330
323
  *,
331
324
  case_sensitive: bool = False,
332
325
  separator: str = ",",
333
- empty: str = SENTINEL_REPR,
334
326
  ) -> None:
335
- super().__init__(
336
- Enum(enum, case_sensitive=case_sensitive), separator=separator, empty=empty
337
- )
327
+ super().__init__(Enum(enum, case_sensitive=case_sensitive), separator=separator)
338
328
 
339
329
 
340
330
  class FrozenSetFloats(FrozenSetParameter[FloatParamType, float]):
341
331
  """A frozenset-of-floats-valued parameter."""
342
332
 
343
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
344
- super().__init__(FloatParamType(), separator=separator, empty=empty)
333
+ def __init__(self, *, separator: str = ",") -> None:
334
+ super().__init__(FloatParamType(), separator=separator)
345
335
 
346
336
 
347
337
  class FrozenSetInts(FrozenSetParameter[IntParamType, int]):
348
338
  """A frozenset-of-ints-valued parameter."""
349
339
 
350
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
351
- super().__init__(IntParamType(), separator=separator, empty=empty)
340
+ def __init__(self, *, separator: str = ",") -> None:
341
+ super().__init__(IntParamType(), separator=separator)
352
342
 
353
343
 
354
344
  class FrozenSetMonths(FrozenSetParameter[Month, utilities.datetime.Month]):
355
345
  """A frozenset-of-months-valued parameter."""
356
346
 
357
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
358
- super().__init__(Month(), separator=separator, empty=empty)
347
+ def __init__(self, *, separator: str = ",") -> None:
348
+ super().__init__(Month(), separator=separator)
359
349
 
360
350
 
361
351
  class FrozenSetStrs(FrozenSetParameter[StringParamType, str]):
362
352
  """A frozenset-of-strs-valued parameter."""
363
353
 
364
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
365
- super().__init__(StringParamType(), separator=separator, empty=empty)
354
+ def __init__(self, *, separator: str = ",") -> None:
355
+ super().__init__(StringParamType(), separator=separator)
366
356
 
367
357
 
368
358
  class FrozenSetUUIDs(FrozenSetParameter[UUIDParameterType, UUID]):
369
359
  """A frozenset-of-UUIDs-valued parameter."""
370
360
 
371
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
372
- super().__init__(UUIDParameterType(), separator=separator, empty=empty)
361
+ def __init__(self, *, separator: str = ",") -> None:
362
+ super().__init__(UUIDParameterType(), separator=separator)
373
363
 
374
364
 
375
365
  # parameters - list
@@ -378,13 +368,10 @@ class FrozenSetUUIDs(FrozenSetParameter[UUIDParameterType, UUID]):
378
368
  class ListParameter(ParamType, Generic[_TParam, _T]):
379
369
  """A list-valued parameter."""
380
370
 
381
- def __init__(
382
- self, param: _TParam, /, *, separator: str = ",", empty: str = SENTINEL_REPR
383
- ) -> None:
371
+ def __init__(self, param: _TParam, /, *, separator: str = ",") -> None:
384
372
  self.name = f"LIST[{param.name}]"
385
373
  self._param = param
386
374
  self._separator = separator
387
- self._empty = empty
388
375
  super().__init__()
389
376
 
390
377
  @override
@@ -406,7 +393,7 @@ class ListParameter(ParamType, Generic[_TParam, _T]):
406
393
  text = ensure_str(value)
407
394
  except EnsureStrError as error:
408
395
  return self.fail(str(error), param=param, ctx=ctx)
409
- values = split_str(text, separator=self._separator, empty=self._empty)
396
+ values = split_str(text, separator=self._separator)
410
397
  return [self._param.convert(v, param, ctx) for v in values]
411
398
 
412
399
  @override
@@ -423,15 +410,15 @@ class ListParameter(ParamType, Generic[_TParam, _T]):
423
410
  class ListBools(ListParameter[BoolParamType, str]):
424
411
  """A list-of-bools-valued parameter."""
425
412
 
426
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
427
- super().__init__(BoolParamType(), separator=separator, empty=empty)
413
+ def __init__(self, *, separator: str = ",") -> None:
414
+ super().__init__(BoolParamType(), separator=separator)
428
415
 
429
416
 
430
417
  class ListDates(ListParameter[Date, dt.date]):
431
418
  """A list-of-dates-valued parameter."""
432
419
 
433
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
434
- super().__init__(Date(), separator=separator, empty=empty)
420
+ def __init__(self, *, separator: str = ",") -> None:
421
+ super().__init__(Date(), separator=separator)
435
422
 
436
423
 
437
424
  class ListEnums(ListParameter[Enum[TEnum], TEnum]):
@@ -444,46 +431,43 @@ class ListEnums(ListParameter[Enum[TEnum], TEnum]):
444
431
  *,
445
432
  case_sensitive: bool = False,
446
433
  separator: str = ",",
447
- empty: str = SENTINEL_REPR,
448
434
  ) -> None:
449
- super().__init__(
450
- Enum(enum, case_sensitive=case_sensitive), separator=separator, empty=empty
451
- )
435
+ super().__init__(Enum(enum, case_sensitive=case_sensitive), separator=separator)
452
436
 
453
437
 
454
438
  class ListFloats(ListParameter[FloatParamType, float]):
455
439
  """A list-of-floats-valued parameter."""
456
440
 
457
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
458
- super().__init__(FloatParamType(), separator=separator, empty=empty)
441
+ def __init__(self, *, separator: str = ",") -> None:
442
+ super().__init__(FloatParamType(), separator=separator)
459
443
 
460
444
 
461
445
  class ListInts(ListParameter[IntParamType, int]):
462
446
  """A list-of-ints-valued parameter."""
463
447
 
464
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
465
- super().__init__(IntParamType(), separator=separator, empty=empty)
448
+ def __init__(self, *, separator: str = ",") -> None:
449
+ super().__init__(IntParamType(), separator=separator)
466
450
 
467
451
 
468
452
  class ListMonths(ListParameter[Month, utilities.datetime.Month]):
469
453
  """A list-of-months-valued parameter."""
470
454
 
471
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
472
- super().__init__(Month(), separator=separator, empty=empty)
455
+ def __init__(self, *, separator: str = ",") -> None:
456
+ super().__init__(Month(), separator=separator)
473
457
 
474
458
 
475
459
  class ListStrs(ListParameter[StringParamType, str]):
476
460
  """A list-of-strs-valued parameter."""
477
461
 
478
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
479
- super().__init__(StringParamType(), separator=separator, empty=empty)
462
+ def __init__(self, *, separator: str = ",") -> None:
463
+ super().__init__(StringParamType(), separator=separator)
480
464
 
481
465
 
482
466
  class ListUUIDs(ListParameter[UUIDParameterType, UUID]):
483
467
  """A list-of-UUIDs-valued parameter."""
484
468
 
485
- def __init__(self, *, separator: str = ",", empty: str = SENTINEL_REPR) -> None:
486
- super().__init__(UUIDParameterType(), separator=separator, empty=empty)
469
+ def __init__(self, *, separator: str = ",") -> None:
470
+ super().__init__(UUIDParameterType(), separator=separator)
487
471
 
488
472
 
489
473
  # private
utilities/polars.py CHANGED
@@ -1228,6 +1228,21 @@ class _GetDataTypeOrSeriesTimeZoneNotZonedError(GetDataTypeOrSeriesTimeZoneError
1228
1228
  ##
1229
1229
 
1230
1230
 
1231
+ def get_expr_name(obj: Series | DataFrame, expr: Expr, /) -> str:
1232
+ """Get the name of an expression."""
1233
+ match obj:
1234
+ case Series() as series:
1235
+ return get_expr_name(series.to_frame(), expr)
1236
+ case DataFrame() as df:
1237
+ selected = df.select(expr)
1238
+ return one(selected.columns)
1239
+ case _ as never:
1240
+ assert_never(never)
1241
+
1242
+
1243
+ ##
1244
+
1245
+
1231
1246
  def get_frequency_spectrum(series: Series, /, *, d: int = 1) -> DataFrame:
1232
1247
  """Get the frequency spectrum."""
1233
1248
  import utilities.numpy
@@ -1970,6 +1985,7 @@ __all__ = [
1970
1985
  "finite_ewm_mean",
1971
1986
  "floor_datetime",
1972
1987
  "get_data_type_or_series_time_zone",
1988
+ "get_expr_name",
1973
1989
  "get_frequency_spectrum",
1974
1990
  "get_series_number_of_decimals",
1975
1991
  "insert_after",
utilities/text.py CHANGED
@@ -4,30 +4,10 @@ import re
4
4
  from dataclasses import dataclass
5
5
  from re import IGNORECASE, Match, search
6
6
  from textwrap import dedent
7
- from typing import TYPE_CHECKING, Any, override
8
-
9
- from utilities.sentinel import SENTINEL_REPR
7
+ from typing import TYPE_CHECKING, Any, Literal, overload, override
10
8
 
11
9
  if TYPE_CHECKING:
12
- from collections.abc import Iterable
13
-
14
-
15
- def join_strs(
16
- texts: Iterable[str],
17
- /,
18
- *,
19
- sort: bool = False,
20
- separator: str = ",",
21
- empty: str = SENTINEL_REPR,
22
- ) -> str:
23
- """Join a collection of strings, with a special provision for the empty list."""
24
- texts = sorted(texts) if sort else list(texts)
25
- if len(texts) >= 1:
26
- return separator.join(texts)
27
- return empty
28
-
29
-
30
- ##
10
+ from collections.abc import Iterable, Sequence
31
11
 
32
12
 
33
13
  def parse_bool(text: str, /) -> bool:
@@ -101,11 +81,85 @@ def _snake_case_title(match: Match[str], /) -> str:
101
81
  ##
102
82
 
103
83
 
84
+ def split_key_value_pairs(
85
+ text: str, /, *, list_separator: str = ",", pair_separator: str = "="
86
+ ) -> Sequence[tuple[str, str]]:
87
+ """Split a string into key-value pairs."""
88
+ return [
89
+ split_str(text_i, separator=pair_separator, n=2)
90
+ for text_i in split_str(text, separator=list_separator)
91
+ ]
92
+
93
+
94
+ ##
95
+
96
+
97
+ @overload
98
+ def split_str(text: str, /, *, separator: str = ",", n: Literal[1]) -> tuple[str]: ...
99
+ @overload
100
+ def split_str(
101
+ text: str, /, *, separator: str = ",", n: Literal[2]
102
+ ) -> tuple[str, str]: ...
103
+ @overload
104
+ def split_str(
105
+ text: str, /, *, separator: str = ",", n: Literal[3]
106
+ ) -> tuple[str, str, str]: ...
107
+ @overload
108
+ def split_str(
109
+ text: str, /, *, separator: str = ",", n: Literal[4]
110
+ ) -> tuple[str, str, str, str]: ...
111
+ @overload
104
112
  def split_str(
105
- text: str, /, *, separator: str = ",", empty: str = SENTINEL_REPR
106
- ) -> list[str]:
113
+ text: str, /, *, separator: str = ",", n: Literal[5]
114
+ ) -> tuple[str, str, str, str, str]: ...
115
+ @overload
116
+ def split_str(
117
+ text: str, /, *, separator: str = ",", n: int | None = None
118
+ ) -> Sequence[str]: ...
119
+ def split_str(
120
+ text: str, /, *, separator: str = ",", n: int | None = None
121
+ ) -> Sequence[str]:
107
122
  """Split a string, with a special provision for the empty string."""
108
- return [] if text == empty else text.split(separator)
123
+ if text == "":
124
+ texts = []
125
+ elif text == _escape_separator(separator=separator):
126
+ texts = [""]
127
+ else:
128
+ texts = text.split(separator)
129
+ if n is None:
130
+ return texts
131
+ if len(texts) != n:
132
+ raise SplitStrError(text=text, n=n, texts=texts)
133
+ return tuple(texts)
134
+
135
+
136
+ @dataclass(kw_only=True, slots=True)
137
+ class SplitStrError(Exception):
138
+ text: str
139
+ n: int
140
+ texts: Sequence[str]
141
+
142
+ @override
143
+ def __str__(self) -> str:
144
+ return f"Unable to split {self.text!r} into {self.n} part(s); got {len(self.texts)}"
145
+
146
+
147
+ def join_strs(
148
+ texts: Iterable[str], /, *, sort: bool = False, separator: str = ","
149
+ ) -> str:
150
+ """Join a collection of strings, with a special provision for the empty list."""
151
+ texts = list(texts)
152
+ if sort:
153
+ texts = sorted(texts)
154
+ if texts == []:
155
+ return ""
156
+ if texts == [""]:
157
+ return _escape_separator(separator=separator)
158
+ return separator.join(texts)
159
+
160
+
161
+ def _escape_separator(*, separator: str = ",") -> str:
162
+ return f"\\{separator}"
109
163
 
110
164
 
111
165
  ##
@@ -128,11 +182,13 @@ def strip_and_dedent(text: str, /, *, trailing: bool = False) -> str:
128
182
  __all__ = [
129
183
  "ParseBoolError",
130
184
  "ParseNoneError",
185
+ "SplitStrError",
131
186
  "join_strs",
132
187
  "parse_bool",
133
188
  "parse_none",
134
189
  "repr_encode",
135
190
  "snake_case",
191
+ "split_key_value_pairs",
136
192
  "split_str",
137
193
  "str_encode",
138
194
  "strip_and_dedent",