omdev 0.0.0.dev420__py3-none-any.whl → 0.0.0.dev421__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.
@@ -50,6 +50,126 @@ CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
50
50
  CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
51
51
 
52
52
 
53
+ ########################################
54
+ # ../../../omlish/lite/abstract.py
55
+
56
+
57
+ ##
58
+
59
+
60
+ _ABSTRACT_METHODS_ATTR = '__abstractmethods__'
61
+ _IS_ABSTRACT_METHOD_ATTR = '__isabstractmethod__'
62
+
63
+
64
+ def is_abstract_method(obj: ta.Any) -> bool:
65
+ return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
66
+
67
+
68
+ def update_abstracts(cls, *, force=False):
69
+ if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
70
+ # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
71
+ # implementation (especially during testing), and we want to handle both cases.
72
+ return cls
73
+
74
+ abstracts: ta.Set[str] = set()
75
+
76
+ for scls in cls.__bases__:
77
+ for name in getattr(scls, _ABSTRACT_METHODS_ATTR, ()):
78
+ value = getattr(cls, name, None)
79
+ if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
80
+ abstracts.add(name)
81
+
82
+ for name, value in cls.__dict__.items():
83
+ if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
84
+ abstracts.add(name)
85
+
86
+ setattr(cls, _ABSTRACT_METHODS_ATTR, frozenset(abstracts))
87
+ return cls
88
+
89
+
90
+ #
91
+
92
+
93
+ class AbstractTypeError(TypeError):
94
+ pass
95
+
96
+
97
+ _FORCE_ABSTRACT_ATTR = '__forceabstract__'
98
+
99
+
100
+ class Abstract:
101
+ """
102
+ Different from, but interoperable with, abc.ABC / abc.ABCMeta:
103
+
104
+ - This raises AbstractTypeError during class creation, not instance instantiation - unless Abstract or abc.ABC are
105
+ explicitly present in the class's direct bases.
106
+ - This will forbid instantiation of classes with Abstract in their direct bases even if there are no
107
+ abstractmethods left on the class.
108
+ - This is a mixin, not a metaclass.
109
+ - As it is not an ABCMeta, this does not support virtual base classes. As a result, operations like `isinstance`
110
+ and `issubclass` are ~7x faster.
111
+ - It additionally enforces a base class order of (Abstract, abc.ABC) to preemptively prevent common mro conflicts.
112
+
113
+ If not mixed-in with an ABCMeta, it will update __abstractmethods__ itself.
114
+ """
115
+
116
+ __slots__ = ()
117
+
118
+ __abstractmethods__: ta.ClassVar[ta.FrozenSet[str]] = frozenset()
119
+
120
+ #
121
+
122
+ def __forceabstract__(self):
123
+ raise TypeError
124
+
125
+ # This is done manually, rather than through @abc.abstractmethod, to mask it from static analysis.
126
+ setattr(__forceabstract__, _IS_ABSTRACT_METHOD_ATTR, True)
127
+
128
+ #
129
+
130
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
131
+ setattr(
132
+ cls,
133
+ _FORCE_ABSTRACT_ATTR,
134
+ getattr(Abstract, _FORCE_ABSTRACT_ATTR) if Abstract in cls.__bases__ else False,
135
+ )
136
+
137
+ super().__init_subclass__(**kwargs)
138
+
139
+ if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
140
+ ams = {a: cls for a, o in cls.__dict__.items() if is_abstract_method(o)}
141
+
142
+ seen = set(cls.__dict__)
143
+ for b in cls.__bases__:
144
+ ams.update({a: b for a in set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen}) # noqa
145
+ seen.update(dir(b))
146
+
147
+ if ams:
148
+ raise AbstractTypeError(
149
+ f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
150
+ ', '.join(sorted([
151
+ '.'.join([
152
+ *([m] if (m := getattr(c, '__module__')) else []),
153
+ getattr(c, '__qualname__', getattr(c, '__name__')),
154
+ a,
155
+ ])
156
+ for a, c in ams.items()
157
+ ])),
158
+ )
159
+
160
+ xbi = (Abstract, abc.ABC) # , ta.Generic ?
161
+ bis = [(cls.__bases__.index(b), b) for b in xbi if b in cls.__bases__]
162
+ if bis != sorted(bis):
163
+ raise TypeError(
164
+ f'Abstract subclass {cls.__name__} must have proper base class order of '
165
+ f'({", ".join(getattr(b, "__name__") for b in xbi)}), got: '
166
+ f'({", ".join(getattr(b, "__name__") for _, b in sorted(bis))})',
167
+ )
168
+
169
+ if not isinstance(cls, abc.ABCMeta):
170
+ update_abstracts(cls, force=True)
171
+
172
+
53
173
  ########################################
54
174
  # ../../../omlish/lite/cached.py
55
175
 
@@ -598,6 +718,86 @@ class Checks:
598
718
  check = Checks()
599
719
 
600
720
 
721
+ ########################################
722
+ # ../../../omlish/lite/objects.py
723
+
724
+
725
+ ##
726
+
727
+
728
+ def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
729
+ seen = set()
730
+ todo = list(reversed(cls.__subclasses__()))
731
+ while todo:
732
+ cur = todo.pop()
733
+ if cur in seen:
734
+ continue
735
+ seen.add(cur)
736
+ yield cur
737
+ todo.extend(reversed(cur.__subclasses__()))
738
+
739
+
740
+ ##
741
+
742
+
743
+ def mro_owner_dict(
744
+ instance_cls: type,
745
+ owner_cls: ta.Optional[type] = None,
746
+ *,
747
+ bottom_up_key_order: bool = False,
748
+ sort_keys: bool = False,
749
+ ) -> ta.Mapping[str, ta.Tuple[type, ta.Any]]:
750
+ if owner_cls is None:
751
+ owner_cls = instance_cls
752
+
753
+ mro = instance_cls.__mro__[-2::-1]
754
+ try:
755
+ pos = mro.index(owner_cls)
756
+ except ValueError:
757
+ raise TypeError(f'Owner class {owner_cls} not in mro of instance class {instance_cls}') from None
758
+
759
+ dct: ta.Dict[str, ta.Tuple[type, ta.Any]] = {}
760
+ if not bottom_up_key_order:
761
+ for cur_cls in mro[:pos + 1][::-1]:
762
+ for k, v in cur_cls.__dict__.items():
763
+ if k not in dct:
764
+ dct[k] = (cur_cls, v)
765
+
766
+ else:
767
+ for cur_cls in mro[:pos + 1]:
768
+ dct.update({k: (cur_cls, v) for k, v in cur_cls.__dict__.items()})
769
+
770
+ if sort_keys:
771
+ dct = dict(sorted(dct.items(), key=lambda t: t[0]))
772
+
773
+ return dct
774
+
775
+
776
+ def mro_dict(
777
+ instance_cls: type,
778
+ owner_cls: ta.Optional[type] = None,
779
+ *,
780
+ bottom_up_key_order: bool = False,
781
+ sort_keys: bool = False,
782
+ ) -> ta.Mapping[str, ta.Any]:
783
+ return {
784
+ k: v
785
+ for k, (o, v) in mro_owner_dict(
786
+ instance_cls,
787
+ owner_cls,
788
+ bottom_up_key_order=bottom_up_key_order,
789
+ sort_keys=sort_keys,
790
+ ).items()
791
+ }
792
+
793
+
794
+ def dir_dict(o: ta.Any) -> ta.Dict[str, ta.Any]:
795
+ return {
796
+ a: getattr(o, a)
797
+ for a in dir(o)
798
+ }
799
+
800
+
601
801
  ########################################
602
802
  # ../../../omlish/lite/reflect.py
603
803
 
@@ -687,21 +887,6 @@ def get_literal_type_args(spec: ta.Any) -> ta.Iterable[ta.Any]:
687
887
  return spec.__args__
688
888
 
689
889
 
690
- ##
691
-
692
-
693
- def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
694
- seen = set()
695
- todo = list(reversed(cls.__subclasses__()))
696
- while todo:
697
- cur = todo.pop()
698
- if cur in seen:
699
- continue
700
- seen.add(cur)
701
- yield cur
702
- todo.extend(reversed(cur.__subclasses__()))
703
-
704
-
705
890
  ########################################
706
891
  # ../../../omlish/lite/strings.py
707
892
 
@@ -818,7 +1003,7 @@ class ObjMarshalOptions:
818
1003
  non_strict_fields: bool = False
819
1004
 
820
1005
 
821
- class ObjMarshaler(abc.ABC):
1006
+ class ObjMarshaler(Abstract):
822
1007
  @abc.abstractmethod
823
1008
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
824
1009
  raise NotImplementedError
@@ -836,26 +1021,30 @@ class NopObjMarshaler(ObjMarshaler):
836
1021
  return o
837
1022
 
838
1023
 
839
- @dc.dataclass()
840
1024
  class ProxyObjMarshaler(ObjMarshaler):
841
- m: ta.Optional[ObjMarshaler] = None
1025
+ def __init__(self, m: ta.Optional[ObjMarshaler] = None) -> None:
1026
+ super().__init__()
1027
+
1028
+ self._m = m
842
1029
 
843
1030
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
844
- return check.not_none(self.m).marshal(o, ctx)
1031
+ return check.not_none(self._m).marshal(o, ctx)
845
1032
 
846
1033
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
847
- return check.not_none(self.m).unmarshal(o, ctx)
1034
+ return check.not_none(self._m).unmarshal(o, ctx)
848
1035
 
849
1036
 
850
- @dc.dataclass(frozen=True)
851
1037
  class CastObjMarshaler(ObjMarshaler):
852
- ty: type
1038
+ def __init__(self, ty: type) -> None:
1039
+ super().__init__()
1040
+
1041
+ self._ty = ty
853
1042
 
854
1043
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
855
1044
  return o
856
1045
 
857
1046
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
858
- return self.ty(o)
1047
+ return self._ty(o)
859
1048
 
860
1049
 
861
1050
  class DynamicObjMarshaler(ObjMarshaler):
@@ -866,121 +1055,151 @@ class DynamicObjMarshaler(ObjMarshaler):
866
1055
  return o
867
1056
 
868
1057
 
869
- @dc.dataclass(frozen=True)
870
1058
  class Base64ObjMarshaler(ObjMarshaler):
871
- ty: type
1059
+ def __init__(self, ty: type) -> None:
1060
+ super().__init__()
1061
+
1062
+ self._ty = ty
872
1063
 
873
1064
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
874
1065
  return base64.b64encode(o).decode('ascii')
875
1066
 
876
1067
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
877
- return self.ty(base64.b64decode(o))
1068
+ return self._ty(base64.b64decode(o))
878
1069
 
879
1070
 
880
- @dc.dataclass(frozen=True)
881
1071
  class BytesSwitchedObjMarshaler(ObjMarshaler):
882
- m: ObjMarshaler
1072
+ def __init__(self, m: ObjMarshaler) -> None:
1073
+ super().__init__()
1074
+
1075
+ self._m = m
883
1076
 
884
1077
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
885
1078
  if ctx.options.raw_bytes:
886
1079
  return o
887
- return self.m.marshal(o, ctx)
1080
+ return self._m.marshal(o, ctx)
888
1081
 
889
1082
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
890
1083
  if ctx.options.raw_bytes:
891
1084
  return o
892
- return self.m.unmarshal(o, ctx)
1085
+ return self._m.unmarshal(o, ctx)
893
1086
 
894
1087
 
895
- @dc.dataclass(frozen=True)
896
1088
  class EnumObjMarshaler(ObjMarshaler):
897
- ty: type
1089
+ def __init__(self, ty: type) -> None:
1090
+ super().__init__()
1091
+
1092
+ self._ty = ty
898
1093
 
899
1094
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
900
1095
  return o.name
901
1096
 
902
1097
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
903
- return self.ty.__members__[o] # type: ignore
1098
+ return self._ty.__members__[o] # type: ignore
904
1099
 
905
1100
 
906
- @dc.dataclass(frozen=True)
907
1101
  class OptionalObjMarshaler(ObjMarshaler):
908
- item: ObjMarshaler
1102
+ def __init__(self, item: ObjMarshaler) -> None:
1103
+ super().__init__()
1104
+
1105
+ self._item = item
909
1106
 
910
1107
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
911
1108
  if o is None:
912
1109
  return None
913
- return self.item.marshal(o, ctx)
1110
+ return self._item.marshal(o, ctx)
914
1111
 
915
1112
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
916
1113
  if o is None:
917
1114
  return None
918
- return self.item.unmarshal(o, ctx)
1115
+ return self._item.unmarshal(o, ctx)
919
1116
 
920
1117
 
921
- @dc.dataclass(frozen=True)
922
1118
  class PrimitiveUnionObjMarshaler(ObjMarshaler):
923
- pt: ta.Tuple[type, ...]
924
- x: ta.Optional[ObjMarshaler] = None
1119
+ def __init__(
1120
+ self,
1121
+ pt: ta.Tuple[type, ...],
1122
+ x: ta.Optional[ObjMarshaler] = None,
1123
+ ) -> None:
1124
+ super().__init__()
1125
+
1126
+ self._pt = pt
1127
+ self._x = x
925
1128
 
926
1129
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
927
- if isinstance(o, self.pt):
1130
+ if isinstance(o, self._pt):
928
1131
  return o
929
- elif self.x is not None:
930
- return self.x.marshal(o, ctx)
1132
+ elif self._x is not None:
1133
+ return self._x.marshal(o, ctx)
931
1134
  else:
932
1135
  raise TypeError(o)
933
1136
 
934
1137
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
935
- if isinstance(o, self.pt):
1138
+ if isinstance(o, self._pt):
936
1139
  return o
937
- elif self.x is not None:
938
- return self.x.unmarshal(o, ctx)
1140
+ elif self._x is not None:
1141
+ return self._x.unmarshal(o, ctx)
939
1142
  else:
940
1143
  raise TypeError(o)
941
1144
 
942
1145
 
943
- @dc.dataclass(frozen=True)
944
1146
  class LiteralObjMarshaler(ObjMarshaler):
945
- item: ObjMarshaler
946
- vs: frozenset
1147
+ def __init__(
1148
+ self,
1149
+ item: ObjMarshaler,
1150
+ vs: frozenset,
1151
+ ) -> None:
1152
+ super().__init__()
1153
+
1154
+ self._item = item
1155
+ self._vs = vs
947
1156
 
948
1157
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
949
- return self.item.marshal(check.in_(o, self.vs), ctx)
1158
+ return self._item.marshal(check.in_(o, self._vs), ctx)
950
1159
 
951
1160
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
952
- return check.in_(self.item.unmarshal(o, ctx), self.vs)
1161
+ return check.in_(self._item.unmarshal(o, ctx), self._vs)
953
1162
 
954
1163
 
955
- @dc.dataclass(frozen=True)
956
1164
  class MappingObjMarshaler(ObjMarshaler):
957
- ty: type
958
- km: ObjMarshaler
959
- vm: ObjMarshaler
1165
+ def __init__(
1166
+ self,
1167
+ ty: type,
1168
+ km: ObjMarshaler,
1169
+ vm: ObjMarshaler,
1170
+ ) -> None:
1171
+ super().__init__()
1172
+
1173
+ self._ty = ty
1174
+ self._km = km
1175
+ self._vm = vm
960
1176
 
961
1177
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
962
- return {self.km.marshal(k, ctx): self.vm.marshal(v, ctx) for k, v in o.items()}
1178
+ return {self._km.marshal(k, ctx): self._vm.marshal(v, ctx) for k, v in o.items()}
963
1179
 
964
1180
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
965
- return self.ty((self.km.unmarshal(k, ctx), self.vm.unmarshal(v, ctx)) for k, v in o.items())
1181
+ return self._ty((self._km.unmarshal(k, ctx), self._vm.unmarshal(v, ctx)) for k, v in o.items())
966
1182
 
967
1183
 
968
- @dc.dataclass(frozen=True)
969
1184
  class IterableObjMarshaler(ObjMarshaler):
970
- ty: type
971
- item: ObjMarshaler
1185
+ def __init__(
1186
+ self,
1187
+ ty: type,
1188
+ item: ObjMarshaler,
1189
+ ) -> None:
1190
+ super().__init__()
1191
+
1192
+ self._ty = ty
1193
+ self._item = item
972
1194
 
973
1195
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
974
- return [self.item.marshal(e, ctx) for e in o]
1196
+ return [self._item.marshal(e, ctx) for e in o]
975
1197
 
976
1198
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
977
- return self.ty(self.item.unmarshal(e, ctx) for e in o)
1199
+ return self._ty(self._item.unmarshal(e, ctx) for e in o)
978
1200
 
979
1201
 
980
- @dc.dataclass(frozen=True)
981
1202
  class FieldsObjMarshaler(ObjMarshaler):
982
- ty: type
983
-
984
1203
  @dc.dataclass(frozen=True)
985
1204
  class Field:
986
1205
  att: str
@@ -989,31 +1208,43 @@ class FieldsObjMarshaler(ObjMarshaler):
989
1208
 
990
1209
  omit_if_none: bool = False
991
1210
 
992
- fs: ta.Sequence[Field]
993
-
994
- non_strict: bool = False
995
-
996
- #
1211
+ def __init__(
1212
+ self,
1213
+ ty: type,
1214
+ fs: ta.Sequence[Field],
1215
+ *,
1216
+ non_strict: bool = False,
1217
+ ) -> None:
1218
+ super().__init__()
997
1219
 
998
- _fs_by_att: ta.ClassVar[ta.Mapping[str, Field]]
999
- _fs_by_key: ta.ClassVar[ta.Mapping[str, Field]]
1220
+ self._ty = ty
1221
+ self._fs = fs
1222
+ self._non_strict = non_strict
1000
1223
 
1001
- def __post_init__(self) -> None:
1002
1224
  fs_by_att: dict = {}
1003
1225
  fs_by_key: dict = {}
1004
- for f in self.fs:
1226
+ for f in self._fs:
1005
1227
  check.not_in(check.non_empty_str(f.att), fs_by_att)
1006
1228
  check.not_in(check.non_empty_str(f.key), fs_by_key)
1007
1229
  fs_by_att[f.att] = f
1008
1230
  fs_by_key[f.key] = f
1009
- self.__dict__['_fs_by_att'] = fs_by_att
1010
- self.__dict__['_fs_by_key'] = fs_by_key
1231
+
1232
+ self._fs_by_att: ta.Mapping[str, FieldsObjMarshaler.Field] = fs_by_att
1233
+ self._fs_by_key: ta.Mapping[str, FieldsObjMarshaler.Field] = fs_by_key
1234
+
1235
+ @property
1236
+ def ty(self) -> type:
1237
+ return self._ty
1238
+
1239
+ @property
1240
+ def fs(self) -> ta.Sequence[Field]:
1241
+ return self._fs
1011
1242
 
1012
1243
  #
1013
1244
 
1014
1245
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1015
1246
  d = {}
1016
- for f in self.fs:
1247
+ for f in self._fs:
1017
1248
  mv = f.m.marshal(getattr(o, f.att), ctx)
1018
1249
  if mv is None and f.omit_if_none:
1019
1250
  continue
@@ -1024,34 +1255,46 @@ class FieldsObjMarshaler(ObjMarshaler):
1024
1255
  kw = {}
1025
1256
  for k, v in o.items():
1026
1257
  if (f := self._fs_by_key.get(k)) is None:
1027
- if not (self.non_strict or ctx.options.non_strict_fields):
1258
+ if not (self._non_strict or ctx.options.non_strict_fields):
1028
1259
  raise KeyError(k)
1029
1260
  continue
1030
1261
  kw[f.att] = f.m.unmarshal(v, ctx)
1031
- return self.ty(**kw)
1262
+ return self._ty(**kw)
1032
1263
 
1033
1264
 
1034
- @dc.dataclass(frozen=True)
1035
1265
  class SingleFieldObjMarshaler(ObjMarshaler):
1036
- ty: type
1037
- fld: str
1266
+ def __init__(
1267
+ self,
1268
+ ty: type,
1269
+ fld: str,
1270
+ ) -> None:
1271
+ super().__init__()
1272
+
1273
+ self._ty = ty
1274
+ self._fld = fld
1038
1275
 
1039
1276
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1040
- return getattr(o, self.fld)
1277
+ return getattr(o, self._fld)
1041
1278
 
1042
1279
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1043
- return self.ty(**{self.fld: o})
1280
+ return self._ty(**{self._fld: o})
1044
1281
 
1045
1282
 
1046
- @dc.dataclass(frozen=True)
1047
1283
  class PolymorphicObjMarshaler(ObjMarshaler):
1048
1284
  class Impl(ta.NamedTuple):
1049
1285
  ty: type
1050
1286
  tag: str
1051
1287
  m: ObjMarshaler
1052
1288
 
1053
- impls_by_ty: ta.Mapping[type, Impl]
1054
- impls_by_tag: ta.Mapping[str, Impl]
1289
+ def __init__(
1290
+ self,
1291
+ impls_by_ty: ta.Mapping[type, Impl],
1292
+ impls_by_tag: ta.Mapping[str, Impl],
1293
+ ) -> None:
1294
+ super().__init__()
1295
+
1296
+ self._impls_by_ty = impls_by_ty
1297
+ self._impls_by_tag = impls_by_tag
1055
1298
 
1056
1299
  @classmethod
1057
1300
  def of(cls, impls: ta.Iterable[Impl]) -> 'PolymorphicObjMarshaler':
@@ -1061,24 +1304,29 @@ class PolymorphicObjMarshaler(ObjMarshaler):
1061
1304
  )
1062
1305
 
1063
1306
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1064
- impl = self.impls_by_ty[type(o)]
1307
+ impl = self._impls_by_ty[type(o)]
1065
1308
  return {impl.tag: impl.m.marshal(o, ctx)}
1066
1309
 
1067
1310
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1068
1311
  [(t, v)] = o.items()
1069
- impl = self.impls_by_tag[t]
1312
+ impl = self._impls_by_tag[t]
1070
1313
  return impl.m.unmarshal(v, ctx)
1071
1314
 
1072
1315
 
1073
- @dc.dataclass(frozen=True)
1074
1316
  class DatetimeObjMarshaler(ObjMarshaler):
1075
- ty: type
1317
+ def __init__(
1318
+ self,
1319
+ ty: type,
1320
+ ) -> None:
1321
+ super().__init__()
1322
+
1323
+ self._ty = ty
1076
1324
 
1077
1325
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1078
1326
  return o.isoformat()
1079
1327
 
1080
1328
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
1081
- return self.ty.fromisoformat(o) # type: ignore
1329
+ return self._ty.fromisoformat(o) # type: ignore
1082
1330
 
1083
1331
 
1084
1332
  class DecimalObjMarshaler(ObjMarshaler):
@@ -1186,7 +1434,78 @@ class OBJ_MARSHALER_OMIT_IF_NONE(ObjMarshalerFieldMetadata): # noqa
1186
1434
  ##
1187
1435
 
1188
1436
 
1189
- class ObjMarshalerManager:
1437
+ class ObjMarshalerManager(Abstract):
1438
+ @abc.abstractmethod
1439
+ def make_obj_marshaler(
1440
+ self,
1441
+ ty: ta.Any,
1442
+ rec: ta.Callable[[ta.Any], ObjMarshaler],
1443
+ *,
1444
+ non_strict_fields: bool = False,
1445
+ ) -> ObjMarshaler:
1446
+ raise NotImplementedError
1447
+
1448
+ @abc.abstractmethod
1449
+ def set_obj_marshaler(
1450
+ self,
1451
+ ty: ta.Any,
1452
+ m: ObjMarshaler,
1453
+ *,
1454
+ override: bool = False,
1455
+ ) -> None:
1456
+ raise NotImplementedError
1457
+
1458
+ @abc.abstractmethod
1459
+ def get_obj_marshaler(
1460
+ self,
1461
+ ty: ta.Any,
1462
+ *,
1463
+ no_cache: bool = False,
1464
+ **kwargs: ta.Any,
1465
+ ) -> ObjMarshaler:
1466
+ raise NotImplementedError
1467
+
1468
+ @abc.abstractmethod
1469
+ def make_context(self, opts: ta.Optional[ObjMarshalOptions]) -> 'ObjMarshalContext':
1470
+ raise NotImplementedError
1471
+
1472
+ #
1473
+
1474
+ def marshal_obj(
1475
+ self,
1476
+ o: ta.Any,
1477
+ ty: ta.Any = None,
1478
+ opts: ta.Optional[ObjMarshalOptions] = None,
1479
+ ) -> ta.Any:
1480
+ m = self.get_obj_marshaler(ty if ty is not None else type(o))
1481
+ return m.marshal(o, self.make_context(opts))
1482
+
1483
+ def unmarshal_obj(
1484
+ self,
1485
+ o: ta.Any,
1486
+ ty: ta.Union[ta.Type[T], ta.Any],
1487
+ opts: ta.Optional[ObjMarshalOptions] = None,
1488
+ ) -> T:
1489
+ m = self.get_obj_marshaler(ty)
1490
+ return m.unmarshal(o, self.make_context(opts))
1491
+
1492
+ def roundtrip_obj(
1493
+ self,
1494
+ o: ta.Any,
1495
+ ty: ta.Any = None,
1496
+ opts: ta.Optional[ObjMarshalOptions] = None,
1497
+ ) -> ta.Any:
1498
+ if ty is None:
1499
+ ty = type(o)
1500
+ m: ta.Any = self.marshal_obj(o, ty, opts)
1501
+ u: ta.Any = self.unmarshal_obj(m, ty, opts)
1502
+ return u
1503
+
1504
+
1505
+ #
1506
+
1507
+
1508
+ class ObjMarshalerManagerImpl(ObjMarshalerManager):
1190
1509
  def __init__(
1191
1510
  self,
1192
1511
  *,
@@ -1213,6 +1532,12 @@ class ObjMarshalerManager:
1213
1532
 
1214
1533
  #
1215
1534
 
1535
+ @classmethod
1536
+ def _is_abstract(cls, ty: type) -> bool:
1537
+ return abc.ABC in ty.__bases__ or Abstract in ty.__bases__
1538
+
1539
+ #
1540
+
1216
1541
  def make_obj_marshaler(
1217
1542
  self,
1218
1543
  ty: ta.Any,
@@ -1224,12 +1549,12 @@ class ObjMarshalerManager:
1224
1549
  if (reg := self._registered_obj_marshalers.get(ty)) is not None:
1225
1550
  return reg
1226
1551
 
1227
- if abc.ABC in ty.__bases__:
1552
+ if self._is_abstract(ty):
1228
1553
  tn = ty.__name__
1229
1554
  impls: ta.List[ta.Tuple[type, str]] = [ # type: ignore[var-annotated]
1230
1555
  (ity, ity.__name__)
1231
1556
  for ity in deep_subclasses(ty)
1232
- if abc.ABC not in ity.__bases__
1557
+ if not self._is_abstract(ity)
1233
1558
  ]
1234
1559
 
1235
1560
  if all(itn.endswith(tn) for _, itn in impls):
@@ -1395,49 +1720,24 @@ class ObjMarshalerManager:
1395
1720
  m = self.make_obj_marshaler(ty, rec, **kwargs)
1396
1721
  finally:
1397
1722
  del self._proxies[ty]
1398
- p.m = m
1723
+ p._m = m # noqa
1399
1724
 
1400
1725
  if not no_cache:
1401
1726
  self._obj_marshalers[ty] = m
1402
1727
  return m
1403
1728
 
1404
- #
1405
-
1406
- def _make_context(self, opts: ta.Optional[ObjMarshalOptions]) -> 'ObjMarshalContext':
1729
+ def make_context(self, opts: ta.Optional[ObjMarshalOptions]) -> 'ObjMarshalContext':
1407
1730
  return ObjMarshalContext(
1408
1731
  options=opts or self._default_options,
1409
1732
  manager=self,
1410
1733
  )
1411
1734
 
1412
- def marshal_obj(
1413
- self,
1414
- o: ta.Any,
1415
- ty: ta.Any = None,
1416
- opts: ta.Optional[ObjMarshalOptions] = None,
1417
- ) -> ta.Any:
1418
- m = self.get_obj_marshaler(ty if ty is not None else type(o))
1419
- return m.marshal(o, self._make_context(opts))
1420
1735
 
1421
- def unmarshal_obj(
1422
- self,
1423
- o: ta.Any,
1424
- ty: ta.Union[ta.Type[T], ta.Any],
1425
- opts: ta.Optional[ObjMarshalOptions] = None,
1426
- ) -> T:
1427
- m = self.get_obj_marshaler(ty)
1428
- return m.unmarshal(o, self._make_context(opts))
1736
+ def new_obj_marshaler_manager(**kwargs: ta.Any) -> ObjMarshalerManager:
1737
+ return ObjMarshalerManagerImpl(**kwargs)
1429
1738
 
1430
- def roundtrip_obj(
1431
- self,
1432
- o: ta.Any,
1433
- ty: ta.Any = None,
1434
- opts: ta.Optional[ObjMarshalOptions] = None,
1435
- ) -> ta.Any:
1436
- if ty is None:
1437
- ty = type(o)
1438
- m: ta.Any = self.marshal_obj(o, ty, opts)
1439
- u: ta.Any = self.unmarshal_obj(m, ty, opts)
1440
- return u
1739
+
1740
+ ##
1441
1741
 
1442
1742
 
1443
1743
  @dc.dataclass(frozen=True)
@@ -1449,7 +1749,7 @@ class ObjMarshalContext:
1449
1749
  ##
1450
1750
 
1451
1751
 
1452
- OBJ_MARSHALER_MANAGER = ObjMarshalerManager()
1752
+ OBJ_MARSHALER_MANAGER = new_obj_marshaler_manager()
1453
1753
 
1454
1754
  set_obj_marshaler = OBJ_MARSHALER_MANAGER.set_obj_marshaler
1455
1755
  get_obj_marshaler = OBJ_MARSHALER_MANAGER.get_obj_marshaler