dycw-utilities 0.110.2__py3-none-any.whl → 0.110.4__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.110.2
3
+ Version: 0.110.4
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=N5L_-mBy__dabTcP63GXeZ0UmhNEqKjJ0SR5XKVgg5Y,60
1
+ utilities/__init__.py,sha256=jGUTgOEiXSlv_bOz8eQW5saCU2Pq4oTqEXt6FeWlFII,60
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
@@ -11,7 +11,7 @@ utilities/contextlib.py,sha256=OOIIEa5lXKGzFAnauaul40nlQnQko6Na4ryiMJcHkIg,478
11
11
  utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
12
12
  utilities/cryptography.py,sha256=HyOewI20cl3uRXsKivhIaeLVDInQdzgXZGaly7hS5dE,771
13
13
  utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
14
- utilities/dataclasses.py,sha256=e2YuCj1DF4eecFFxsu20MknghXJ4SNkWCmbIAOXXr18,25942
14
+ utilities/dataclasses.py,sha256=Q197PVnE_vUMn_SNnqJBCo4eRy4bdHtgMHWRbSJPtFk,26670
15
15
  utilities/datetime.py,sha256=GOs-MIEW_A49kzqa1yhIoeNeSqqPVgGO-h2AThtgTDk,37326
16
16
  utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
17
17
  utilities/errors.py,sha256=BtSNP0JC3ik536ddPyTerLomCRJV9f6kdMe6POz0QHM,361
@@ -41,7 +41,7 @@ utilities/operator.py,sha256=0M2yZJ0PODH47ogFEnkGMBe_cfxwZR02T_92LZVZvHo,3715
41
41
  utilities/optuna.py,sha256=loyJGWTzljgdJaoLhP09PT8Jz6o_pwBOwehY33lHkhw,1923
42
42
  utilities/orjson.py,sha256=Wj5pzG_VdgoAy14a7Luhem-BgYrRtRFvvl_POiszRd0,36930
43
43
  utilities/os.py,sha256=D_FyyT-6TtqiN9KSS7c9g1fnUtgxmyMtzAjmYLkk46A,3587
44
- utilities/parse.py,sha256=wxqh4YwBfQ7nm249-F_6uqiLo1js9_xji9AVvUxZ5nI,17091
44
+ utilities/parse.py,sha256=6erE2TpZP-z0FWtxnnDVO3-X1d1MHoNrr2IYiaJEuk8,18960
45
45
  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
@@ -73,11 +73,11 @@ 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=VcNAJtjA2BOGEVY6yXnC9UIAsgECgiWCQ_QnpilZV2o,10089
76
+ utilities/text.py,sha256=hfcBKF22fKT6s_U-ZdP-g5TjFQ0-NrIrQdvIwERWT80,10971
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
80
- utilities/types.py,sha256=Hi9aKaxN3G9zFVlLjx6U9xd_HMGq-eqHLxmG1cSdVpg,17967
80
+ utilities/types.py,sha256=PZjxTCDYSPFKppi3ZWnSh441mv0rgEvZkh8npqL6awo,18064
81
81
  utilities/typing.py,sha256=gLg4EbE1FX52fJ1d3ji4i08qolwu9qgWt8w_w_Y5DTk,5512
82
82
  utilities/tzdata.py,sha256=2ZsPmhTVM9Ptrxb4QrWKtKOB9RiH8IOO-A1u7ULdVbg,176
83
83
  utilities/tzlocal.py,sha256=42BCquGF54oIqIKe5RGziP4K8Nbm3Ey7uqcNn6m5ge8,534
@@ -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.110.2.dist-info/METADATA,sha256=TK9SINDsRCnIKjF2YyzOvlQ5mTpD1hjJJ6SD_aNKFI4,13004
91
- dycw_utilities-0.110.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- dycw_utilities-0.110.2.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
- dycw_utilities-0.110.2.dist-info/RECORD,,
90
+ dycw_utilities-0.110.4.dist-info/METADATA,sha256=N7v2g5hRFwzAbpEjcnZs0OZQhPZPECwQHsevYVzsXZg,13004
91
+ dycw_utilities-0.110.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
+ dycw_utilities-0.110.4.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
+ dycw_utilities-0.110.4.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.110.2"
3
+ __version__ = "0.110.4"
utilities/dataclasses.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Mapping
4
+ from contextlib import suppress
4
5
  from dataclasses import MISSING, dataclass, field, fields, replace
5
6
  from typing import (
6
7
  TYPE_CHECKING,
@@ -21,21 +22,23 @@ from utilities.functions import (
21
22
  )
22
23
  from utilities.iterables import OneStrEmptyError, OneStrNonUniqueError, one_str
23
24
  from utilities.operator import is_equal
24
- from utilities.parse import (
25
- LIST_SEPARATOR,
26
- PAIR_SEPARATOR,
27
- ParseObjectError,
28
- parse_object,
29
- serialize_object,
30
- )
25
+ from utilities.parse import ParseObjectError, parse_object, serialize_object
26
+ from utilities.re import ExtractGroupError, extract_group
31
27
  from utilities.sentinel import Sentinel, sentinel
32
28
  from utilities.text import (
29
+ BRACKETS,
30
+ LIST_SEPARATOR,
31
+ PAIR_SEPARATOR,
33
32
  _SplitKeyValuePairsDuplicateKeysError,
34
33
  _SplitKeyValuePairsSplitError,
35
- join_strs,
36
34
  split_key_value_pairs,
37
35
  )
38
- from utilities.types import ParseObjectExtra, StrStrMapping, TDataclass
36
+ from utilities.types import (
37
+ ParseObjectExtra,
38
+ SerializeObjectExtra,
39
+ StrStrMapping,
40
+ TDataclass,
41
+ )
39
42
  from utilities.typing import get_type_hints
40
43
 
41
44
  if TYPE_CHECKING:
@@ -408,10 +411,11 @@ def serialize_dataclass(
408
411
  exclude: Iterable[str] | None = None,
409
412
  rel_tol: float | None = None,
410
413
  abs_tol: float | None = None,
411
- extra: Mapping[type[_U], Callable[[_U, _U], bool]] | None = None,
414
+ extra_equal: Mapping[type[_U], Callable[[_U, _U], bool]] | None = None,
412
415
  defaults: bool = False,
413
416
  list_separator: str = LIST_SEPARATOR,
414
417
  pair_separator: str = PAIR_SEPARATOR,
418
+ extra_serializers: SerializeObjectExtra | None = None,
415
419
  ) -> str:
416
420
  """Serialize a Dataclass."""
417
421
  mapping: StrStrMapping = {}
@@ -426,16 +430,21 @@ def serialize_dataclass(
426
430
  exclude=exclude,
427
431
  rel_tol=rel_tol,
428
432
  abs_tol=abs_tol,
429
- extra=extra,
433
+ extra=extra_equal,
430
434
  defaults=defaults,
431
435
  ):
432
436
  mapping[fld.name] = serialize_object(
433
- fld.value, list_separator=list_separator, pair_separator=pair_separator
437
+ fld.value,
438
+ list_separator=list_separator,
439
+ pair_separator=pair_separator,
440
+ extra=extra_serializers,
434
441
  )
435
- joined_items = (
436
- join_strs(item, separator=pair_separator) for item in mapping.items()
442
+ return serialize_object(
443
+ mapping,
444
+ list_separator=list_separator,
445
+ pair_separator=pair_separator,
446
+ extra=extra_serializers,
437
447
  )
438
- return join_strs(joined_items, separator=list_separator)
439
448
 
440
449
 
441
450
  def parse_dataclass(
@@ -445,6 +454,7 @@ def parse_dataclass(
445
454
  *,
446
455
  list_separator: str = LIST_SEPARATOR,
447
456
  pair_separator: str = PAIR_SEPARATOR,
457
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
448
458
  globalns: StrMapping | None = None,
449
459
  localns: StrMapping | None = None,
450
460
  warn_name_errors: bool = False,
@@ -457,7 +467,11 @@ def parse_dataclass(
457
467
  match text_or_mapping:
458
468
  case str() as text:
459
469
  keys_to_serializes = _parse_dataclass_split_key_value_pairs(
460
- text, cls, list_separator=list_separator, pair_separator=pair_separator
470
+ text,
471
+ cls,
472
+ list_separator=list_separator,
473
+ pair_separator=pair_separator,
474
+ brackets=brackets,
461
475
  )
462
476
  case Mapping() as keys_to_serializes:
463
477
  ...
@@ -481,7 +495,14 @@ def parse_dataclass(
481
495
  )
482
496
  field_names_to_values = {
483
497
  f.name: _parse_dataclass_parse_text(
484
- f, t, cls, head=head, case_sensitive=case_sensitive, extra=extra_parsers
498
+ f,
499
+ t,
500
+ cls,
501
+ list_separator=list_separator,
502
+ pair_separator=pair_separator,
503
+ head=head,
504
+ case_sensitive=case_sensitive,
505
+ extra=extra_parsers,
485
506
  )
486
507
  for f, t in fields_to_serializes.items()
487
508
  }
@@ -505,12 +526,16 @@ def _parse_dataclass_split_key_value_pairs(
505
526
  *,
506
527
  list_separator: str = LIST_SEPARATOR,
507
528
  pair_separator: str = PAIR_SEPARATOR,
529
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
508
530
  ) -> StrStrMapping:
531
+ with suppress(ExtractGroupError):
532
+ text = extract_group(r"^\{?(.*?)\}?$", text)
509
533
  try:
510
534
  return split_key_value_pairs(
511
535
  text,
512
536
  list_separator=list_separator,
513
537
  pair_separator=pair_separator,
538
+ brackets=brackets,
514
539
  mapping=True,
515
540
  )
516
541
  except _SplitKeyValuePairsSplitError as error:
utilities/parse.py CHANGED
@@ -20,6 +20,9 @@ from utilities.math import ParseNumberError, parse_number
20
20
  from utilities.re import ExtractGroupError, extract_group
21
21
  from utilities.sentinel import ParseSentinelError, Sentinel, parse_sentinel
22
22
  from utilities.text import (
23
+ BRACKETS,
24
+ LIST_SEPARATOR,
25
+ PAIR_SEPARATOR,
23
26
  ParseBoolError,
24
27
  ParseNoneError,
25
28
  join_strs,
@@ -28,7 +31,7 @@ from utilities.text import (
28
31
  split_key_value_pairs,
29
32
  split_str,
30
33
  )
31
- from utilities.types import Duration, Number, ParseObjectExtra
34
+ from utilities.types import Duration, Number, ParseObjectExtra, SerializeObjectExtra
32
35
  from utilities.typing import (
33
36
  get_args,
34
37
  is_dict_type,
@@ -43,14 +46,10 @@ from utilities.typing import (
43
46
  from utilities.version import ParseVersionError, Version, parse_version
44
47
 
45
48
  if TYPE_CHECKING:
46
- from collections.abc import Mapping, Sequence
49
+ from collections.abc import Iterable, Mapping, Sequence
47
50
  from collections.abc import Set as AbstractSet
48
51
 
49
52
 
50
- LIST_SEPARATOR = ","
51
- PAIR_SEPARATOR = "="
52
-
53
-
54
53
  def parse_object(
55
54
  type_: Any,
56
55
  text: str,
@@ -58,6 +57,7 @@ def parse_object(
58
57
  *,
59
58
  list_separator: str = LIST_SEPARATOR,
60
59
  pair_separator: str = PAIR_SEPARATOR,
60
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
61
61
  head: bool = False,
62
62
  case_sensitive: bool = False,
63
63
  extra: ParseObjectExtra | None = None,
@@ -78,6 +78,7 @@ def parse_object(
78
78
  text,
79
79
  list_separator=list_separator,
80
80
  pair_separator=pair_separator,
81
+ brackets=brackets,
81
82
  head=head,
82
83
  case_sensitive=case_sensitive,
83
84
  extra=extra,
@@ -89,6 +90,7 @@ def parse_object(
89
90
  text,
90
91
  list_separator=list_separator,
91
92
  pair_separator=pair_separator,
93
+ brackets=brackets,
92
94
  head=head,
93
95
  case_sensitive=case_sensitive,
94
96
  extra=extra,
@@ -100,6 +102,7 @@ def parse_object(
100
102
  text,
101
103
  list_separator=list_separator,
102
104
  pair_separator=pair_separator,
105
+ brackets=brackets,
103
106
  head=head,
104
107
  case_sensitive=case_sensitive,
105
108
  extra=extra,
@@ -116,6 +119,7 @@ def parse_object(
116
119
  text,
117
120
  list_separator=list_separator,
118
121
  pair_separator=pair_separator,
122
+ brackets=brackets,
119
123
  head=head,
120
124
  case_sensitive=case_sensitive,
121
125
  extra=extra,
@@ -128,6 +132,7 @@ def parse_object(
128
132
  text,
129
133
  list_separator=list_separator,
130
134
  pair_separator=pair_separator,
135
+ brackets=brackets,
131
136
  head=head,
132
137
  case_sensitive=case_sensitive,
133
138
  extra=extra,
@@ -138,6 +143,7 @@ def parse_object(
138
143
  text,
139
144
  list_separator=list_separator,
140
145
  pair_separator=pair_separator,
146
+ brackets=brackets,
141
147
  head=head,
142
148
  case_sensitive=case_sensitive,
143
149
  extra=extra,
@@ -234,7 +240,7 @@ def _parse_object_type(
234
240
  ) from None
235
241
  else:
236
242
  return parser(text)
237
- raise _ParseObjectParseError(type_=cls, text=text) from None
243
+ raise _ParseObjectParseError(type_=cls, text=text)
238
244
 
239
245
 
240
246
  def _parse_object_dict_type(
@@ -244,6 +250,7 @@ def _parse_object_dict_type(
244
250
  *,
245
251
  list_separator: str = LIST_SEPARATOR,
246
252
  pair_separator: str = PAIR_SEPARATOR,
253
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
247
254
  head: bool = False,
248
255
  case_sensitive: bool = False,
249
256
  extra: ParseObjectExtra | None = None,
@@ -257,6 +264,7 @@ def _parse_object_dict_type(
257
264
  inner_text,
258
265
  list_separator=list_separator,
259
266
  pair_separator=pair_separator,
267
+ brackets=brackets,
260
268
  mapping=True,
261
269
  )
262
270
  keys = (
@@ -265,6 +273,7 @@ def _parse_object_dict_type(
265
273
  k,
266
274
  list_separator=list_separator,
267
275
  pair_separator=pair_separator,
276
+ brackets=brackets,
268
277
  head=head,
269
278
  case_sensitive=case_sensitive,
270
279
  extra=extra,
@@ -277,6 +286,7 @@ def _parse_object_dict_type(
277
286
  v,
278
287
  list_separator=list_separator,
279
288
  pair_separator=pair_separator,
289
+ brackets=brackets,
280
290
  head=head,
281
291
  case_sensitive=case_sensitive,
282
292
  extra=extra,
@@ -296,6 +306,7 @@ def _parse_object_list_type(
296
306
  *,
297
307
  list_separator: str = LIST_SEPARATOR,
298
308
  pair_separator: str = PAIR_SEPARATOR,
309
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
299
310
  head: bool = False,
300
311
  case_sensitive: bool = False,
301
312
  extra: ParseObjectExtra | None = None,
@@ -313,6 +324,7 @@ def _parse_object_list_type(
313
324
  t,
314
325
  list_separator=list_separator,
315
326
  pair_separator=pair_separator,
327
+ brackets=brackets,
316
328
  head=head,
317
329
  case_sensitive=case_sensitive,
318
330
  extra=extra,
@@ -330,6 +342,7 @@ def _parse_object_set_type(
330
342
  *,
331
343
  list_separator: str = LIST_SEPARATOR,
332
344
  pair_separator: str = PAIR_SEPARATOR,
345
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
333
346
  head: bool = False,
334
347
  case_sensitive: bool = False,
335
348
  extra: ParseObjectExtra | None = None,
@@ -347,6 +360,7 @@ def _parse_object_set_type(
347
360
  t,
348
361
  list_separator=list_separator,
349
362
  pair_separator=pair_separator,
363
+ brackets=brackets,
350
364
  head=head,
351
365
  case_sensitive=case_sensitive,
352
366
  extra=extra,
@@ -389,6 +403,7 @@ def _parse_object_tuple_type(
389
403
  *,
390
404
  list_separator: str = LIST_SEPARATOR,
391
405
  pair_separator: str = PAIR_SEPARATOR,
406
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
392
407
  head: bool = False,
393
408
  case_sensitive: bool = False,
394
409
  extra: ParseObjectExtra | None = None,
@@ -408,6 +423,7 @@ def _parse_object_tuple_type(
408
423
  text,
409
424
  list_separator=list_separator,
410
425
  pair_separator=pair_separator,
426
+ brackets=brackets,
411
427
  head=head,
412
428
  case_sensitive=case_sensitive,
413
429
  extra=extra,
@@ -450,6 +466,7 @@ def serialize_object(
450
466
  *,
451
467
  list_separator: str = LIST_SEPARATOR,
452
468
  pair_separator: str = PAIR_SEPARATOR,
469
+ extra: SerializeObjectExtra | None = None,
453
470
  ) -> str:
454
471
  """Convert an object to text."""
455
472
  if (obj is None) or isinstance(
@@ -490,7 +507,9 @@ def serialize_object(
490
507
  return _serialize_object_set(
491
508
  obj, list_separator=list_separator, pair_separator=pair_separator
492
509
  )
493
- raise NotImplementedError(obj)
510
+ if extra is not None:
511
+ return _serialize_object_extra(obj, extra)
512
+ raise _SerializeObjectSerializeError(obj=obj)
494
513
 
495
514
 
496
515
  def _serialize_object_dict(
@@ -518,6 +537,19 @@ def _serialize_object_dict(
518
537
  return f"{{{joined}}}"
519
538
 
520
539
 
540
+ def _serialize_object_extra(obj: Any, extra: SerializeObjectExtra, /) -> str:
541
+ try:
542
+ serializer = one(s for c, s in extra.items() if isinstance(obj, c))
543
+ except OneEmptyError:
544
+ raise _SerializeObjectSerializeError(obj=obj) from None
545
+ except OneNonUniqueError as error:
546
+ raise _SerializeObjectExtraNonUniqueError(
547
+ obj=obj, first=error.first, second=error.second
548
+ ) from None
549
+ else:
550
+ return serializer(obj)
551
+
552
+
521
553
  def _serialize_object_list(
522
554
  obj: Sequence[Any],
523
555
  /,
@@ -569,4 +601,25 @@ def _serialize_object_tuple(
569
601
  return f"({joined})"
570
602
 
571
603
 
572
- __all__ = ["LIST_SEPARATOR", "PAIR_SEPARATOR", "parse_object", "serialize_object"]
604
+ @dataclass(kw_only=True, slots=True)
605
+ class SerializeObjectError(Exception):
606
+ obj: Any
607
+
608
+
609
+ class _SerializeObjectSerializeError(SerializeObjectError):
610
+ @override
611
+ def __str__(self) -> str:
612
+ return f"Unable to serialize object {self.obj!r}"
613
+
614
+
615
+ @dataclass
616
+ class _SerializeObjectExtraNonUniqueError(SerializeObjectError):
617
+ first: type[Any]
618
+ second: type[Any]
619
+
620
+ @override
621
+ def __str__(self) -> str:
622
+ return f"Unable to serialize object {self.obj!r} since `extra` must contain exactly one parent class; got {self.first!r}, {self.second!r} and perhaps more"
623
+
624
+
625
+ __all__ = ["parse_object", "serialize_object"]
utilities/text.py CHANGED
@@ -17,6 +17,12 @@ if TYPE_CHECKING:
17
17
  from utilities.types import StrStrMapping
18
18
 
19
19
 
20
+ DEFAULT_SEPARATOR = ","
21
+
22
+
23
+ ##
24
+
25
+
20
26
  def parse_bool(text: str, /) -> bool:
21
27
  """Parse text into a boolean value."""
22
28
  if text == "0" or search("false", text, flags=IGNORECASE):
@@ -88,13 +94,19 @@ def _snake_case_title(match: Match[str], /) -> str:
88
94
  ##
89
95
 
90
96
 
97
+ LIST_SEPARATOR = DEFAULT_SEPARATOR
98
+ PAIR_SEPARATOR = "="
99
+ BRACKETS = [("(", ")"), ("[", "]"), ("{", "}")]
100
+
101
+
91
102
  @overload
92
103
  def split_key_value_pairs(
93
104
  text: str,
94
105
  /,
95
106
  *,
96
- list_separator: str = ",",
97
- pair_separator: str = "=",
107
+ list_separator: str = DEFAULT_SEPARATOR,
108
+ pair_separator: str = PAIR_SEPARATOR,
109
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
98
110
  mapping: Literal[True],
99
111
  ) -> StrStrMapping: ...
100
112
  @overload
@@ -102,8 +114,9 @@ def split_key_value_pairs(
102
114
  text: str,
103
115
  /,
104
116
  *,
105
- list_separator: str = ",",
106
- pair_separator: str = "=",
117
+ list_separator: str = DEFAULT_SEPARATOR,
118
+ pair_separator: str = PAIR_SEPARATOR,
119
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
107
120
  mapping: Literal[False] = False,
108
121
  ) -> Sequence[tuple[str, str]]: ...
109
122
  @overload
@@ -111,23 +124,29 @@ def split_key_value_pairs(
111
124
  text: str,
112
125
  /,
113
126
  *,
114
- list_separator: str = ",",
115
- pair_separator: str = "=",
127
+ list_separator: str = DEFAULT_SEPARATOR,
128
+ pair_separator: str = PAIR_SEPARATOR,
129
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
116
130
  mapping: bool = False,
117
131
  ) -> Sequence[tuple[str, str]] | StrStrMapping: ...
118
132
  def split_key_value_pairs(
119
133
  text: str,
120
134
  /,
121
135
  *,
122
- list_separator: str = ",",
123
- pair_separator: str = "=",
136
+ list_separator: str = DEFAULT_SEPARATOR,
137
+ pair_separator: str = PAIR_SEPARATOR,
138
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
124
139
  mapping: bool = False,
125
140
  ) -> Sequence[tuple[str, str]] | StrStrMapping:
126
141
  """Split a string into key-value pairs."""
142
+ try:
143
+ texts = split_str(text, separator=list_separator, brackets=brackets)
144
+ except SplitStrError as error:
145
+ raise _SplitKeyValuePairsSplitError(text=text, inner=error.text) from None
127
146
  try:
128
147
  pairs = [
129
- split_str(text_i, separator=pair_separator, n=2)
130
- for text_i in split_str(text, separator=list_separator)
148
+ split_str(text_i, separator=pair_separator, brackets=brackets, n=2)
149
+ for text_i in texts
131
150
  ]
132
151
  except SplitStrError as error:
133
152
  raise _SplitKeyValuePairsSplitError(text=text, inner=error.text) from None
@@ -153,7 +172,7 @@ class _SplitKeyValuePairsSplitError(SplitKeyValuePairsError):
153
172
 
154
173
  @override
155
174
  def __str__(self) -> str:
156
- return f"Unable to split {self.text!r} into key-value pairs; got {self.inner!r}"
175
+ return f"Unable to split {self.text!r} into key-value pairs"
157
176
 
158
177
 
159
178
  @dataclass(kw_only=True, slots=True)
@@ -173,7 +192,7 @@ def split_str(
173
192
  text: str,
174
193
  /,
175
194
  *,
176
- separator: str = ",",
195
+ separator: str = DEFAULT_SEPARATOR,
177
196
  brackets: Iterable[tuple[str, str]] | None = None,
178
197
  n: Literal[1],
179
198
  ) -> tuple[str]: ...
@@ -182,7 +201,7 @@ def split_str(
182
201
  text: str,
183
202
  /,
184
203
  *,
185
- separator: str = ",",
204
+ separator: str = DEFAULT_SEPARATOR,
186
205
  brackets: Iterable[tuple[str, str]] | None = None,
187
206
  n: Literal[2],
188
207
  ) -> tuple[str, str]: ...
@@ -191,7 +210,7 @@ def split_str(
191
210
  text: str,
192
211
  /,
193
212
  *,
194
- separator: str = ",",
213
+ separator: str = DEFAULT_SEPARATOR,
195
214
  brackets: Iterable[tuple[str, str]] | None = None,
196
215
  n: Literal[3],
197
216
  ) -> tuple[str, str, str]: ...
@@ -200,7 +219,7 @@ def split_str(
200
219
  text: str,
201
220
  /,
202
221
  *,
203
- separator: str = ",",
222
+ separator: str = DEFAULT_SEPARATOR,
204
223
  brackets: Iterable[tuple[str, str]] | None = None,
205
224
  n: Literal[4],
206
225
  ) -> tuple[str, str, str, str]: ...
@@ -209,7 +228,7 @@ def split_str(
209
228
  text: str,
210
229
  /,
211
230
  *,
212
- separator: str = ",",
231
+ separator: str = DEFAULT_SEPARATOR,
213
232
  brackets: Iterable[tuple[str, str]] | None = None,
214
233
  n: Literal[5],
215
234
  ) -> tuple[str, str, str, str, str]: ...
@@ -218,7 +237,7 @@ def split_str(
218
237
  text: str,
219
238
  /,
220
239
  *,
221
- separator: str = ",",
240
+ separator: str = DEFAULT_SEPARATOR,
222
241
  brackets: Iterable[tuple[str, str]] | None = None,
223
242
  n: int | None = None,
224
243
  ) -> Sequence[str]: ...
@@ -226,7 +245,7 @@ def split_str(
226
245
  text: str,
227
246
  /,
228
247
  *,
229
- separator: str = ",",
248
+ separator: str = DEFAULT_SEPARATOR,
230
249
  brackets: Iterable[tuple[str, str]] | None = None,
231
250
  n: int | None = None,
232
251
  ) -> Sequence[str]:
@@ -247,7 +266,11 @@ def split_str(
247
266
 
248
267
 
249
268
  def _split_str_brackets(
250
- text: str, brackets: Iterable[tuple[str, str]], /, *, separator: str = ","
269
+ text: str,
270
+ brackets: Iterable[tuple[str, str]],
271
+ /,
272
+ *,
273
+ separator: str = DEFAULT_SEPARATOR,
251
274
  ) -> Sequence[str]:
252
275
  brackets = list(brackets)
253
276
  opens, closes = transpose(brackets)
@@ -338,7 +361,7 @@ class _SplitStrOpeningBracketUnmatchedError(SplitStrError):
338
361
 
339
362
 
340
363
  def join_strs(
341
- texts: Iterable[str], /, *, sort: bool = False, separator: str = ","
364
+ texts: Iterable[str], /, *, sort: bool = False, separator: str = DEFAULT_SEPARATOR
342
365
  ) -> str:
343
366
  """Join a collection of strings, with a special provision for the empty list."""
344
367
  texts = list(texts)
@@ -351,7 +374,7 @@ def join_strs(
351
374
  return separator.join(texts)
352
375
 
353
376
 
354
- def _escape_separator(*, separator: str = ",") -> str:
377
+ def _escape_separator(*, separator: str = DEFAULT_SEPARATOR) -> str:
355
378
  return f"\\{separator}"
356
379
 
357
380
 
@@ -373,6 +396,10 @@ def strip_and_dedent(text: str, /, *, trailing: bool = False) -> str:
373
396
 
374
397
 
375
398
  __all__ = [
399
+ "BRACKETS",
400
+ "DEFAULT_SEPARATOR",
401
+ "LIST_SEPARATOR",
402
+ "PAIR_SEPARATOR",
376
403
  "ParseBoolError",
377
404
  "ParseNoneError",
378
405
  "SplitKeyValuePairsError",
utilities/types.py CHANGED
@@ -232,6 +232,7 @@ class SupportsRound(Protocol[_T_co]):
232
232
 
233
233
  # parse
234
234
  type ParseObjectExtra = Mapping[Any, Callable[[str], Any]]
235
+ type SerializeObjectExtra = Mapping[type[Any], Callable[[Any], str]]
235
236
 
236
237
 
237
238
  # pathlib
@@ -288,6 +289,7 @@ __all__ = [
288
289
  "PathLikeOrCallable",
289
290
  "RoundMode",
290
291
  "Seed",
292
+ "SerializeObjectExtra",
291
293
  "StrMapping",
292
294
  "StrStrMapping",
293
295
  "SupportsAbs",