dycw-utilities 0.133.7__py3-none-any.whl → 0.134.0__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.
utilities/dataclasses.py CHANGED
@@ -3,16 +3,7 @@ from __future__ import annotations
3
3
  from collections.abc import Mapping
4
4
  from contextlib import suppress
5
5
  from dataclasses import MISSING, dataclass, field, fields, replace
6
- from typing import (
7
- TYPE_CHECKING,
8
- Any,
9
- Generic,
10
- Literal,
11
- TypeVar,
12
- assert_never,
13
- overload,
14
- override,
15
- )
6
+ from typing import TYPE_CHECKING, Any, Literal, assert_never, overload, override
16
7
 
17
8
  from utilities.errors import ImpossibleCaseError
18
9
  from utilities.functions import (
@@ -43,30 +34,23 @@ from utilities.text import (
43
34
  _SplitKeyValuePairsSplitError,
44
35
  split_key_value_pairs,
45
36
  )
46
- from utilities.types import (
47
- ParseObjectExtra,
48
- SerializeObjectExtra,
49
- StrStrMapping,
50
- TDataclass,
51
- TSupportsLT,
52
- )
37
+ from utilities.types import SupportsLT
53
38
  from utilities.typing import get_type_hints
54
39
 
55
40
  if TYPE_CHECKING:
56
41
  from collections.abc import Callable, Iterable, Iterator
57
42
  from collections.abc import Set as AbstractSet
58
43
 
59
- from utilities.types import Dataclass, StrMapping
60
-
61
-
62
- _T = TypeVar("_T")
63
- _U = TypeVar("_U")
64
-
65
-
66
- ##
44
+ from utilities.types import (
45
+ Dataclass,
46
+ ParseObjectExtra,
47
+ SerializeObjectExtra,
48
+ StrMapping,
49
+ StrStrMapping,
50
+ )
67
51
 
68
52
 
69
- def dataclass_repr(
53
+ def dataclass_repr[T](
70
54
  obj: Dataclass,
71
55
  /,
72
56
  *,
@@ -77,7 +61,7 @@ def dataclass_repr(
77
61
  exclude: Iterable[str] | None = None,
78
62
  rel_tol: float | None = None,
79
63
  abs_tol: float | None = None,
80
- extra: Mapping[type[_T], Callable[[_T, _T], bool]] | None = None,
64
+ extra: Mapping[type[T], Callable[[T, T], bool]] | None = None,
81
65
  defaults: bool = False,
82
66
  recursive: bool = False,
83
67
  ) -> str:
@@ -145,7 +129,7 @@ def dataclass_repr(
145
129
  ##
146
130
 
147
131
 
148
- def dataclass_to_dict(
132
+ def dataclass_to_dict[T](
149
133
  obj: Dataclass,
150
134
  /,
151
135
  *,
@@ -156,7 +140,7 @@ def dataclass_to_dict(
156
140
  exclude: Iterable[str] | None = None,
157
141
  rel_tol: float | None = None,
158
142
  abs_tol: float | None = None,
159
- extra: Mapping[type[_T], Callable[[_T, _T], bool]] | None = None,
143
+ extra: Mapping[type[T], Callable[[T, T], bool]] | None = None,
160
144
  defaults: bool = False,
161
145
  final: Callable[[type[Dataclass], StrMapping], StrMapping] | None = None,
162
146
  recursive: bool = False,
@@ -221,7 +205,7 @@ def dataclass_to_dict(
221
205
  ##
222
206
 
223
207
 
224
- def is_nullable_lt(x: TSupportsLT | None, y: TSupportsLT | None, /) -> bool | None:
208
+ def is_nullable_lt[T: SupportsLT](x: T | None, y: T | None, /) -> bool | None:
225
209
  """Compare two nullable fields."""
226
210
  match cmp_nullable(x, y):
227
211
  case 1:
@@ -237,8 +221,8 @@ def is_nullable_lt(x: TSupportsLT | None, y: TSupportsLT | None, /) -> bool | No
237
221
  ##
238
222
 
239
223
 
240
- def mapping_to_dataclass(
241
- cls: type[TDataclass],
224
+ def mapping_to_dataclass[T: Dataclass](
225
+ cls: type[T],
242
226
  mapping: StrMapping,
243
227
  /,
244
228
  *,
@@ -249,7 +233,7 @@ def mapping_to_dataclass(
249
233
  head: bool = False,
250
234
  case_sensitive: bool = False,
251
235
  allow_extra: bool = False,
252
- ) -> TDataclass:
236
+ ) -> T:
253
237
  """Construct a dataclass from a mapping."""
254
238
  if fields is None:
255
239
  fields_use = list(
@@ -302,12 +286,12 @@ def mapping_to_dataclass(
302
286
 
303
287
 
304
288
  @dataclass(kw_only=True, slots=True)
305
- class MappingToDataclassError(Exception, Generic[TDataclass]):
306
- cls: type[TDataclass]
289
+ class MappingToDataclassError[T: Dataclass](Exception):
290
+ cls: type[T]
307
291
 
308
292
 
309
293
  @dataclass(kw_only=True, slots=True)
310
- class _MappingToDataClassEmptyError(MappingToDataclassError[TDataclass]):
294
+ class _MappingToDataClassEmptyError(MappingToDataclassError):
311
295
  key: str
312
296
  head: bool = False
313
297
  case_sensitive: bool = False
@@ -320,7 +304,7 @@ class _MappingToDataClassEmptyError(MappingToDataclassError[TDataclass]):
320
304
 
321
305
 
322
306
  @dataclass(kw_only=True, slots=True)
323
- class _MappingToDataClassNonUniqueError(MappingToDataclassError[TDataclass]):
307
+ class _MappingToDataClassNonUniqueError(MappingToDataclassError):
324
308
  key: str
325
309
  head: bool = False
326
310
  case_sensitive: bool = False
@@ -340,7 +324,7 @@ class _MappingToDataClassNonUniqueError(MappingToDataclassError[TDataclass]):
340
324
 
341
325
 
342
326
  @dataclass(kw_only=True, slots=True)
343
- class _MappingToDataClassMissingValuesError(MappingToDataclassError[TDataclass]):
327
+ class _MappingToDataClassMissingValuesError(MappingToDataclassError):
344
328
  fields: AbstractSet[str]
345
329
 
346
330
  @override
@@ -396,15 +380,15 @@ def one_field(
396
380
 
397
381
 
398
382
  @dataclass(kw_only=True, slots=True)
399
- class OneFieldError(Exception, Generic[TDataclass]):
400
- cls: type[TDataclass]
383
+ class OneFieldError[T: Dataclass](Exception):
384
+ cls: type[T]
401
385
  key: str
402
386
  head: bool = False
403
387
  case_sensitive: bool = False
404
388
 
405
389
 
406
390
  @dataclass(kw_only=True, slots=True)
407
- class _OneFieldEmptyError(OneFieldError[TDataclass]):
391
+ class _OneFieldEmptyError(OneFieldError):
408
392
  @override
409
393
  def __str__(self) -> str:
410
394
  return _empty_error_str(
@@ -413,7 +397,7 @@ class _OneFieldEmptyError(OneFieldError[TDataclass]):
413
397
 
414
398
 
415
399
  @dataclass(kw_only=True, slots=True)
416
- class _OneFieldNonUniqueError(OneFieldError[TDataclass]):
400
+ class _OneFieldNonUniqueError(OneFieldError):
417
401
  first: str
418
402
  second: str
419
403
 
@@ -434,19 +418,19 @@ class _OneFieldNonUniqueError(OneFieldError[TDataclass]):
434
418
 
435
419
  @overload
436
420
  def replace_non_sentinel(
437
- obj: Any, /, *, in_place: Literal[True], **kwargs: Any
421
+ obj: Dataclass, /, *, in_place: Literal[True], **kwargs: Any
438
422
  ) -> None: ...
439
423
  @overload
440
- def replace_non_sentinel(
441
- obj: TDataclass, /, *, in_place: Literal[False] = False, **kwargs: Any
442
- ) -> TDataclass: ...
424
+ def replace_non_sentinel[T: Dataclass](
425
+ obj: T, /, *, in_place: Literal[False] = False, **kwargs: Any
426
+ ) -> T: ...
443
427
  @overload
444
- def replace_non_sentinel(
445
- obj: TDataclass, /, *, in_place: bool = False, **kwargs: Any
446
- ) -> TDataclass | None: ...
447
- def replace_non_sentinel(
448
- obj: TDataclass, /, *, in_place: bool = False, **kwargs: Any
449
- ) -> TDataclass | None:
428
+ def replace_non_sentinel[T: Dataclass](
429
+ obj: T, /, *, in_place: bool = False, **kwargs: Any
430
+ ) -> T | None: ...
431
+ def replace_non_sentinel[T: Dataclass](
432
+ obj: T, /, *, in_place: bool = False, **kwargs: Any
433
+ ) -> T | None:
450
434
  """Replace attributes on a dataclass, filtering out sentinel values."""
451
435
  if in_place:
452
436
  for k, v in kwargs.items():
@@ -461,7 +445,7 @@ def replace_non_sentinel(
461
445
  ##
462
446
 
463
447
 
464
- def serialize_dataclass(
448
+ def serialize_dataclass[T](
465
449
  obj: Dataclass,
466
450
  /,
467
451
  *,
@@ -472,7 +456,7 @@ def serialize_dataclass(
472
456
  exclude: Iterable[str] | None = None,
473
457
  rel_tol: float | None = None,
474
458
  abs_tol: float | None = None,
475
- extra_equal: Mapping[type[_U], Callable[[_U, _U], bool]] | None = None,
459
+ extra_equal: Mapping[type[T], Callable[[T, T], bool]] | None = None,
476
460
  defaults: bool = False,
477
461
  list_separator: str = LIST_SEPARATOR,
478
462
  pair_separator: str = PAIR_SEPARATOR,
@@ -508,9 +492,9 @@ def serialize_dataclass(
508
492
  )
509
493
 
510
494
 
511
- def parse_dataclass(
495
+ def parse_dataclass[T: Dataclass](
512
496
  text_or_mapping: str | StrStrMapping,
513
- cls: type[TDataclass],
497
+ cls: type[T],
514
498
  /,
515
499
  *,
516
500
  list_separator: str = LIST_SEPARATOR,
@@ -523,7 +507,7 @@ def parse_dataclass(
523
507
  case_sensitive: bool = False,
524
508
  allow_extra_keys: bool = False,
525
509
  extra_parsers: ParseObjectExtra | None = None,
526
- ) -> TDataclass:
510
+ ) -> T:
527
511
  """Construct a dataclass from a string or a mapping or strings."""
528
512
  match text_or_mapping:
529
513
  case str() as text:
@@ -597,9 +581,9 @@ def parse_dataclass(
597
581
  raise _ParseDataClassMissingValuesError(cls=cls, fields=error.fields) from None
598
582
 
599
583
 
600
- def _parse_dataclass_split_key_value_pairs(
584
+ def _parse_dataclass_split_key_value_pairs[T: Dataclass](
601
585
  text: str,
602
- cls: type[TDataclass],
586
+ cls: type[T],
603
587
  /,
604
588
  *,
605
589
  list_separator: str = LIST_SEPARATOR,
@@ -657,12 +641,12 @@ def _parse_dataclass_parse_text(
657
641
 
658
642
 
659
643
  @dataclass(kw_only=True, slots=True)
660
- class ParseDataClassError(Exception, Generic[TDataclass]):
661
- cls: type[TDataclass]
644
+ class ParseDataClassError[T: Dataclass](Exception):
645
+ cls: type[T]
662
646
 
663
647
 
664
648
  @dataclass(kw_only=True, slots=True)
665
- class _ParseDataClassSplitKeyValuePairsSplitError(ParseDataClassError[TDataclass]):
649
+ class _ParseDataClassSplitKeyValuePairsSplitError(ParseDataClassError):
666
650
  text: str
667
651
 
668
652
  @override
@@ -671,9 +655,7 @@ class _ParseDataClassSplitKeyValuePairsSplitError(ParseDataClassError[TDataclass
671
655
 
672
656
 
673
657
  @dataclass(kw_only=True, slots=True)
674
- class _ParseDataClassSplitKeyValuePairsDuplicateKeysError(
675
- ParseDataClassError[TDataclass]
676
- ):
658
+ class _ParseDataClassSplitKeyValuePairsDuplicateKeysError(ParseDataClassError):
677
659
  counts: Mapping[str, int]
678
660
 
679
661
  @override
@@ -682,7 +664,7 @@ class _ParseDataClassSplitKeyValuePairsDuplicateKeysError(
682
664
 
683
665
 
684
666
  @dataclass(kw_only=True, slots=True)
685
- class _ParseDataClassTextParseError(ParseDataClassError[TDataclass]):
667
+ class _ParseDataClassTextParseError(ParseDataClassError):
686
668
  field: _YieldFieldsClass[Any]
687
669
  text: str
688
670
 
@@ -692,7 +674,7 @@ class _ParseDataClassTextParseError(ParseDataClassError[TDataclass]):
692
674
 
693
675
 
694
676
  @dataclass(kw_only=True, slots=True)
695
- class _ParseDataClassTextExtraNonUniqueError(ParseDataClassError[TDataclass]):
677
+ class _ParseDataClassTextExtraNonUniqueError(ParseDataClassError):
696
678
  field: _YieldFieldsClass[Any]
697
679
  first: type[Any]
698
680
  second: type[Any]
@@ -703,9 +685,7 @@ class _ParseDataClassTextExtraNonUniqueError(ParseDataClassError[TDataclass]):
703
685
 
704
686
 
705
687
  @dataclass(kw_only=True, slots=True)
706
- class _ParseDataClassStrMappingToFieldMappingEmptyError(
707
- ParseDataClassError[TDataclass]
708
- ):
688
+ class _ParseDataClassStrMappingToFieldMappingEmptyError(ParseDataClassError):
709
689
  key: str
710
690
  head: bool = False
711
691
  case_sensitive: bool = False
@@ -720,9 +700,7 @@ class _ParseDataClassStrMappingToFieldMappingEmptyError(
720
700
 
721
701
 
722
702
  @dataclass(kw_only=True, slots=True)
723
- class _ParseDataClassStrMappingToFieldMappingNonUniqueError(
724
- ParseDataClassError[TDataclass]
725
- ):
703
+ class _ParseDataClassStrMappingToFieldMappingNonUniqueError(ParseDataClassError):
726
704
  key: str
727
705
  head: bool = False
728
706
  case_sensitive: bool = False
@@ -743,7 +721,7 @@ class _ParseDataClassStrMappingToFieldMappingNonUniqueError(
743
721
 
744
722
 
745
723
  @dataclass(kw_only=True, slots=True)
746
- class _ParseDataClassMissingValuesError(ParseDataClassError[TDataclass]):
724
+ class _ParseDataClassMissingValuesError(ParseDataClassError):
747
725
  fields: AbstractSet[str]
748
726
 
749
727
  @override
@@ -755,9 +733,9 @@ class _ParseDataClassMissingValuesError(ParseDataClassError[TDataclass]):
755
733
  ##
756
734
 
757
735
 
758
- def str_mapping_to_field_mapping(
759
- cls: type[TDataclass],
760
- mapping: Mapping[str, _T],
736
+ def str_mapping_to_field_mapping[T: Dataclass, U](
737
+ cls: type[T],
738
+ mapping: Mapping[str, U],
761
739
  /,
762
740
  *,
763
741
  fields: Iterable[_YieldFieldsClass[Any]] | None = None,
@@ -767,7 +745,7 @@ def str_mapping_to_field_mapping(
767
745
  head: bool = False,
768
746
  case_sensitive: bool = False,
769
747
  allow_extra: bool = False,
770
- ) -> Mapping[_YieldFieldsClass[Any], _T]:
748
+ ) -> Mapping[_YieldFieldsClass[Any], U]:
771
749
  """Convert a string-mapping into a field-mapping."""
772
750
  keys_to_fields: Mapping[str, _YieldFieldsClass[Any]] = {}
773
751
  for key in mapping:
@@ -800,8 +778,8 @@ def str_mapping_to_field_mapping(
800
778
 
801
779
 
802
780
  @dataclass(kw_only=True, slots=True)
803
- class StrMappingToFieldMappingError(Exception, Generic[TDataclass]):
804
- cls: type[TDataclass]
781
+ class StrMappingToFieldMappingError[T: Dataclass](Exception):
782
+ cls: type[T]
805
783
  key: str
806
784
  head: bool = False
807
785
  case_sensitive: bool = False
@@ -913,12 +891,12 @@ def yield_fields(
913
891
 
914
892
 
915
893
  @dataclass(order=True, unsafe_hash=True, kw_only=True, slots=True)
916
- class _YieldFieldsInstance(Generic[_T]):
894
+ class _YieldFieldsInstance[T]:
917
895
  name: str
918
- value: _T = field(hash=False)
896
+ value: T = field(hash=False)
919
897
  type_: Any = field(hash=False)
920
- default: _T | Sentinel = field(default=sentinel, hash=False)
921
- default_factory: Callable[[], _T] | Sentinel = field(default=sentinel, hash=False)
898
+ default: T | Sentinel = field(default=sentinel, hash=False)
899
+ default_factory: Callable[[], T] | Sentinel = field(default=sentinel, hash=False)
922
900
  repr: bool = True
923
901
  hash_: bool | None = None
924
902
  init: bool = True
@@ -926,12 +904,12 @@ class _YieldFieldsInstance(Generic[_T]):
926
904
  metadata: StrMapping = field(default_factory=dict, hash=False)
927
905
  kw_only: bool | Sentinel = sentinel
928
906
 
929
- def equals_default(
907
+ def equals_default[U](
930
908
  self,
931
909
  *,
932
910
  rel_tol: float | None = None,
933
911
  abs_tol: float | None = None,
934
- extra: Mapping[type[_U], Callable[[_U, _U], bool]] | None = None,
912
+ extra: Mapping[type[U], Callable[[U, U], bool]] | None = None,
935
913
  ) -> bool:
936
914
  """Check if the field value equals its default."""
937
915
  if isinstance(self.default, Sentinel) and isinstance(
@@ -954,14 +932,14 @@ class _YieldFieldsInstance(Generic[_T]):
954
932
  self.value, expected, rel_tol=rel_tol, abs_tol=abs_tol, extra=extra
955
933
  )
956
934
 
957
- def keep(
935
+ def keep[U](
958
936
  self,
959
937
  *,
960
938
  include: Iterable[str] | None = None,
961
939
  exclude: Iterable[str] | None = None,
962
940
  rel_tol: float | None = None,
963
941
  abs_tol: float | None = None,
964
- extra: Mapping[type[_U], Callable[[_U, _U], bool]] | None = None,
942
+ extra: Mapping[type[U], Callable[[U, U], bool]] | None = None,
965
943
  defaults: bool = False,
966
944
  ) -> bool:
967
945
  """Whether to include a field."""
@@ -974,11 +952,11 @@ class _YieldFieldsInstance(Generic[_T]):
974
952
 
975
953
 
976
954
  @dataclass(order=True, unsafe_hash=True, kw_only=True, slots=True)
977
- class _YieldFieldsClass(Generic[_T]):
955
+ class _YieldFieldsClass[T]:
978
956
  name: str
979
957
  type_: Any = field(hash=False)
980
- default: _T | Sentinel = field(default=sentinel, hash=False)
981
- default_factory: Callable[[], _T] | Sentinel = field(default=sentinel, hash=False)
958
+ default: T | Sentinel = field(default=sentinel, hash=False)
959
+ default_factory: Callable[[], T] | Sentinel = field(default=sentinel, hash=False)
982
960
  repr: bool = True
983
961
  hash_: bool | None = None
984
962
  init: bool = True
utilities/enum.py CHANGED
@@ -2,26 +2,29 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
4
  from enum import Enum, StrEnum
5
- from typing import Generic, Literal, assert_never, overload, override
5
+ from typing import TYPE_CHECKING, Literal, assert_never, overload, override
6
6
 
7
7
  from utilities.functions import ensure_str
8
8
  from utilities.iterables import OneStrEmptyError, OneStrNonUniqueError, one_str
9
- from utilities.types import EnumLike, TEnum
9
+
10
+ if TYPE_CHECKING:
11
+ from utilities.types import EnumLike
12
+
10
13
 
11
14
  ##
12
15
 
13
16
 
14
17
  @overload
15
- def ensure_enum(
16
- value: None, enum: type[TEnum], /, *, case_sensitive: bool = False
18
+ def ensure_enum[E: Enum](
19
+ value: None, enum: type[E], /, *, case_sensitive: bool = False
17
20
  ) -> None: ...
18
21
  @overload
19
- def ensure_enum(
20
- value: EnumLike[TEnum], enum: type[TEnum], /, *, case_sensitive: bool = False
21
- ) -> TEnum: ...
22
- def ensure_enum(
23
- value: EnumLike[TEnum] | None, enum: type[TEnum], /, *, case_sensitive: bool = False
24
- ) -> TEnum | None:
22
+ def ensure_enum[E: Enum](
23
+ value: EnumLike[E], enum: type[E], /, *, case_sensitive: bool = False
24
+ ) -> E: ...
25
+ def ensure_enum[E: Enum](
26
+ value: EnumLike[E] | None, enum: type[E], /, *, case_sensitive: bool = False
27
+ ) -> E | None:
25
28
  """Ensure the object is a member of the enum."""
26
29
  if value is None:
27
30
  return None
@@ -36,9 +39,9 @@ def ensure_enum(
36
39
 
37
40
 
38
41
  @dataclass(kw_only=True, slots=True)
39
- class EnsureEnumError(Exception, Generic[TEnum]):
40
- value: EnumLike[TEnum]
41
- enum: type[TEnum]
42
+ class EnsureEnumError[E: Enum](Exception):
43
+ value: EnumLike[E]
44
+ enum: type[E]
42
45
 
43
46
 
44
47
  @dataclass(kw_only=True, slots=True)
@@ -58,9 +61,9 @@ class _EnsureEnumParseError(EnsureEnumError):
58
61
  ##
59
62
 
60
63
 
61
- def parse_enum(
62
- value: str, enum: type[TEnum], /, *, case_sensitive: bool = False
63
- ) -> TEnum:
64
+ def parse_enum[E: Enum](
65
+ value: str, enum: type[E], /, *, case_sensitive: bool = False
66
+ ) -> E:
64
67
  """Parse a string into the enum."""
65
68
  by_name = _parse_enum_one(value, enum, "names", case_sensitive=case_sensitive)
66
69
  if not issubclass(enum, StrEnum):
@@ -99,14 +102,14 @@ def parse_enum(
99
102
  type _NamesOrValues = Literal["names", "values"]
100
103
 
101
104
 
102
- def _parse_enum_one(
105
+ def _parse_enum_one[E: Enum](
103
106
  value: str,
104
- enum: type[TEnum],
107
+ enum: type[E],
105
108
  names_or_values: _NamesOrValues,
106
109
  /,
107
110
  *,
108
111
  case_sensitive: bool = False,
109
- ) -> TEnum | None:
112
+ ) -> E | None:
110
113
  """Pair one aspect of the enums."""
111
114
  match names_or_values:
112
115
  case "names":
@@ -132,9 +135,9 @@ def _parse_enum_one(
132
135
 
133
136
 
134
137
  @dataclass(kw_only=True, slots=True)
135
- class ParseEnumError(Exception, Generic[TEnum]):
138
+ class ParseEnumError[E: Enum](Exception):
136
139
  value: str
137
- enum: type[TEnum]
140
+ enum: type[E]
138
141
 
139
142
 
140
143
  @dataclass(kw_only=True, slots=True)