omlish 0.0.0.dev245__py3-none-any.whl → 0.0.0.dev247__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.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev245'
2
- __revision__ = 'c3979ac75a1b25b3c679bc0310afb6364c124c4c'
1
+ __version__ = '0.0.0.dev247'
2
+ __revision__ = '537438b3d967f194ec6ec7277d9e06205bac12b5'
3
3
 
4
4
 
5
5
  #
@@ -63,7 +63,6 @@ from .mappings import ( # noqa
63
63
  DynamicTypeMap,
64
64
  guarded_map_update,
65
65
  multikey_dict,
66
- yield_dict_init,
67
66
  )
68
67
 
69
68
  from .ordered import ( # noqa
@@ -3,7 +3,7 @@ import collections.abc
3
3
  import itertools
4
4
  import typing as ta
5
5
 
6
- from .mappings import yield_dict_init
6
+ from .. import lang
7
7
 
8
8
 
9
9
  T = ta.TypeVar('T')
@@ -27,7 +27,7 @@ class FrozenDict(ta.Mapping[K, V], Frozen):
27
27
  if len(args) > 1:
28
28
  raise TypeError(args)
29
29
  self._dct: dict[K, V] = {}
30
- self._dct.update(yield_dict_init(*args, **kwargs))
30
+ self._dct.update(lang.yield_dict_init(*args, **kwargs))
31
31
 
32
32
  @property
33
33
  def debug(self) -> ta.Mapping[K, V]:
@@ -8,7 +8,6 @@ import dataclasses as dc
8
8
  import typing as ta
9
9
 
10
10
  from .. import lang
11
- from .mappings import yield_dict_init
12
11
 
13
12
 
14
13
  K = ta.TypeVar('K')
@@ -64,7 +63,7 @@ class HashEqMap(ta.MutableMapping[K, V]):
64
63
  self._dct: dict[int, list[HashEqMap._Node[K, V]]] = {}
65
64
  self._len = 0
66
65
 
67
- for k, v in yield_dict_init(*args, **kwargs):
66
+ for k, v in lang.yield_dict_init(*args, **kwargs):
68
67
  self[k] = v
69
68
 
70
69
  def __len__(self) -> int:
@@ -4,7 +4,6 @@ import typing as ta
4
4
  import weakref
5
5
 
6
6
  from .. import lang
7
- from .mappings import yield_dict_init
8
7
 
9
8
 
10
9
  T = ta.TypeVar('T')
@@ -38,9 +37,12 @@ class IdentityKeyDict(ta.MutableMapping[K, V]):
38
37
  def __init__(self, *args, **kwargs) -> None:
39
38
  super().__init__()
40
39
  self._dict: dict[int, tuple[K, V]] = {}
41
- for k, v in yield_dict_init(*args, **kwargs):
40
+ for k, v in lang.yield_dict_init(*args, **kwargs):
42
41
  self[k] = v
43
42
 
43
+ def __reduce__(self):
44
+ return (type(self), (list(self.items()),))
45
+
44
46
  @property
45
47
  def debug(self) -> ta.Sequence[tuple[K, V]]:
46
48
  return list(self.items())
@@ -75,6 +77,9 @@ class IdentitySet(ta.MutableSet[T]):
75
77
  for item in init:
76
78
  self.add(item)
77
79
 
80
+ def __reduce__(self):
81
+ return (type(self), (list(self),))
82
+
78
83
  @property
79
84
  def debug(self) -> ta.Sequence[T]:
80
85
  return list(self)
@@ -1,4 +1,3 @@
1
- import collections.abc
2
1
  import typing as ta
3
2
  import weakref
4
3
 
@@ -33,21 +32,6 @@ def guarded_map_update(
33
32
  return dst
34
33
 
35
34
 
36
- def yield_dict_init(*args, **kwargs) -> ta.Iterable[tuple[ta.Any, ta.Any]]:
37
- if len(args) > 1:
38
- raise TypeError
39
- if args:
40
- [src] = args
41
- if isinstance(src, collections.abc.Mapping):
42
- for k in src:
43
- yield (k, src[k])
44
- else:
45
- for k, v in src:
46
- yield (k, v)
47
- for k, v in kwargs.items():
48
- yield (k, v)
49
-
50
-
51
35
  class TypeMap(ta.Generic[T]):
52
36
  def __init__(self, items: ta.Iterable[T] = ()) -> None:
53
37
  super().__init__()
@@ -2,7 +2,6 @@ import abc
2
2
  import typing as ta
3
3
 
4
4
  from ... import lang
5
- from ..mappings import yield_dict_init
6
5
 
7
6
 
8
7
  T = ta.TypeVar('T')
@@ -83,7 +82,7 @@ class SortedListDict(SortedMutableMapping[K, V]):
83
82
  def __init__(self, impl: SortedCollection, *args, **kwargs) -> None:
84
83
  super().__init__()
85
84
  self._impl = impl
86
- for k, v in yield_dict_init(*args, **kwargs):
85
+ for k, v in lang.yield_dict_init(*args, **kwargs):
87
86
  self[k] = v
88
87
 
89
88
  @property
@@ -1,3 +1,10 @@
1
+ """
2
+ TODO:
3
+ - jsonl pidfile
4
+ - time started
5
+ - code version / revision
6
+ - venv?
7
+ """
1
8
  import abc
2
9
  import threading
3
10
  import typing as ta
@@ -108,6 +108,9 @@ from .utils import ( # noqa
108
108
  update_fields,
109
109
  update_fields_metadata,
110
110
 
111
+ shallow_astuple,
112
+ shallow_asdict,
113
+
111
114
  deep_replace,
112
115
 
113
116
  iter_items,
@@ -32,6 +32,9 @@ def fields_dict(cls_or_instance: ta.Any) -> dict[str, dc.Field]:
32
32
  return {f.name: f for f in dc.fields(cls_or_instance)}
33
33
 
34
34
 
35
+ ##
36
+
37
+
35
38
  class field_modifier: # noqa
36
39
  def __init__(self, fn: ta.Callable[[dc.Field], dc.Field]) -> None:
37
40
  super().__init__()
@@ -112,6 +115,17 @@ def update_fields_metadata(
112
115
  ##
113
116
 
114
117
 
118
+ def shallow_astuple(o: ta.Any) -> tuple[ta.Any, ...]:
119
+ return tuple(getattr(o, f.name) for f in dc.fields(o))
120
+
121
+
122
+ def shallow_asdict(o: ta.Any) -> dict[str, ta.Any]:
123
+ return {f.name: getattr(o, f.name) for f in dc.fields(o)}
124
+
125
+
126
+ ##
127
+
128
+
115
129
  def deep_replace(o: T, *args: str | ta.Callable[[ta.Any], ta.Mapping[str, ta.Any]]) -> T:
116
130
  if not args:
117
131
  return o
omlish/http/handlers.py CHANGED
@@ -79,6 +79,22 @@ class LoggingHttpHandler(HttpHandler_):
79
79
  return resp
80
80
 
81
81
 
82
+ @dc.dataclass(frozen=True)
83
+ class ExceptionLoggingHttpHandler(HttpHandler_):
84
+ handler: HttpHandler
85
+ log: logging.Logger
86
+ message: ta.Union[str, ta.Callable[[HttpHandlerRequest, BaseException], str]] = 'Error in http handler'
87
+
88
+ def __call__(self, req: HttpHandlerRequest) -> HttpHandlerResponse:
89
+ try:
90
+ return self.handler(req)
91
+ except Exception as e: # noqa
92
+ if callable(msg := self.message):
93
+ msg = msg(req, e)
94
+ self.log.exception(msg)
95
+ raise
96
+
97
+
82
98
  ##
83
99
 
84
100
 
omlish/lang/__init__.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from .attrs import ( # noqa
2
2
  AttrOps,
3
+ DictAttrOps,
3
4
  STD_ATTR_OPS,
4
5
  StdAttrOps,
5
6
  TRANSIENT_ATTR_OPS,
@@ -76,6 +77,11 @@ from .cmp import ( # noqa
76
77
  cmp,
77
78
  )
78
79
 
80
+ from .collections import ( # noqa
81
+ merge_dicts,
82
+ yield_dict_init,
83
+ )
84
+
79
85
  from .contextmanagers import ( # noqa
80
86
  AsyncContextManager,
81
87
  ContextManaged,
omlish/lang/attrs.py CHANGED
@@ -47,6 +47,35 @@ STD_ATTR_OPS = StdAttrOps()
47
47
  ##
48
48
 
49
49
 
50
+ class DictAttrOps(AttrOps):
51
+ def __init__(self, dct: ta.MutableMapping[str, ta.Any] | None = None) -> None:
52
+ super().__init__()
53
+
54
+ if dct is None:
55
+ dct = {}
56
+ self._dct = dct
57
+
58
+ def getattr(self, obj: ta.Any, name: str, default: ta.Any = AttrOps.NOT_SET) -> ta.Any:
59
+ try:
60
+ return self._dct[name]
61
+ except KeyError:
62
+ if default is not AttrOps.NOT_SET:
63
+ return default
64
+ raise AttributeError(name) from None
65
+
66
+ def setattr(self, obj: ta.Any, name: str, value: ta.Any) -> None:
67
+ self._dct[name] = value
68
+
69
+ def delattr(self, obj: ta.Any, name: str) -> None:
70
+ try:
71
+ del self._dct[name]
72
+ except KeyError:
73
+ raise AttributeError(name) from None
74
+
75
+
76
+ ##
77
+
78
+
50
79
  class TransientDict(collections.abc.MutableMapping):
51
80
  def __init__(self) -> None:
52
81
  super().__init__()
@@ -6,6 +6,7 @@ TODO:
6
6
  - must support free functions (which have no instance nor owner)
7
7
  - 'staticmethod' or effective equiv - which must resolve to the shared instance
8
8
  - and must be transient?
9
+ - use __transient_dict__ to support common state nuking
9
10
  """
10
11
  import dataclasses as dc
11
12
  import functools
@@ -0,0 +1,50 @@
1
+ import collections.abc
2
+ import typing as ta
3
+
4
+
5
+ K = ta.TypeVar('K')
6
+ V = ta.TypeVar('V')
7
+
8
+
9
+ ##
10
+
11
+
12
+ def yield_dict_init(*args: ta.Any, **kwargs: ta.Any) -> ta.Iterable[tuple[ta.Any, ta.Any]]:
13
+ if len(args) > 1:
14
+ raise TypeError
15
+ if args:
16
+ [src] = args
17
+ if isinstance(src, collections.abc.Mapping):
18
+ for k in src:
19
+ yield (k, src[k])
20
+ else:
21
+ for k, v in src:
22
+ yield (k, v)
23
+ for k, v in kwargs.items():
24
+ yield (k, v)
25
+
26
+
27
+ def merge_dicts(
28
+ *dcts: ta.Mapping[K, V],
29
+ pair_fn: ta.Callable[[K, V], tuple[K, V] | None] | None = None,
30
+ conflict_fn: ta.Callable[[K, V, V], tuple[K, V] | None] | None = None,
31
+ ) -> dict[K, V]:
32
+ out: dict[K, V] = {}
33
+
34
+ for d in dcts:
35
+ for k_v in d.items():
36
+ if pair_fn is not None and (k_v := pair_fn(*k_v)) is None: # type: ignore[assignment]
37
+ continue
38
+
39
+ k, v = k_v
40
+ if k in out:
41
+ if conflict_fn is None:
42
+ raise KeyError(k)
43
+
44
+ if (k_v := conflict_fn(k, out[k], v)) is None: # type: ignore[assignment]
45
+ continue
46
+ k, v = k_v
47
+
48
+ out[k] = v
49
+
50
+ return out
@@ -28,6 +28,16 @@ from .base import ( # noqa
28
28
  ReflectOverride,
29
29
  )
30
30
 
31
+ from .composite.iterables import ( # noqa
32
+ IterableMarshaler,
33
+ IterableUnmarshaler,
34
+ )
35
+
36
+ from .composite.wrapped import ( # noqa
37
+ WrappedMarshaler,
38
+ WrappedUnmarshaler,
39
+ )
40
+
31
41
  from .exceptions import ( # noqa
32
42
  ForbiddenTypeError,
33
43
  MarshalError,
@@ -49,6 +59,14 @@ from .naming import ( # noqa
49
59
  translate_name,
50
60
  )
51
61
 
62
+ from .objects.dataclasses import ( # noqa
63
+ AbstractDataclassFactory,
64
+ DataclassMarshalerFactory,
65
+ DataclassUnmarshalerFactory,
66
+ get_dataclass_field_infos,
67
+ get_dataclass_metadata,
68
+ )
69
+
52
70
  from .objects.helpers import ( # noqa
53
71
  update_field_metadata,
54
72
  update_fields_metadata,
@@ -75,14 +93,18 @@ from .objects.unmarshal import ( # noqa
75
93
  )
76
94
 
77
95
  from .polymorphism.marshal import ( # noqa
96
+ PolymorphismMarshaler,
78
97
  PolymorphismMarshalerFactory,
79
98
  make_polymorphism_marshaler,
80
99
  )
81
100
 
82
101
  from .polymorphism.metadata import ( # noqa
102
+ FieldTypeTagging,
83
103
  Impl,
84
104
  Impls,
85
105
  Polymorphism,
106
+ TypeTagging,
107
+ WrapperTypeTagging,
86
108
  polymorphism_from_subclasses,
87
109
  )
88
110
 
@@ -97,6 +119,7 @@ from .polymorphism.unions import ( # noqa
97
119
  )
98
120
 
99
121
  from .polymorphism.unmarshal import ( # noqa
122
+ PolymorphismUnmarshaler,
100
123
  PolymorphismUnmarshalerFactory,
101
124
  make_polymorphism_unmarshaler,
102
125
  )
@@ -0,0 +1,26 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ..base import MarshalContext
5
+ from ..base import Marshaler
6
+ from ..base import UnmarshalContext
7
+ from ..base import Unmarshaler
8
+ from ..values import Value
9
+
10
+
11
+ @dc.dataclass(frozen=True)
12
+ class WrappedMarshaler(Marshaler):
13
+ wrapper: ta.Callable[[MarshalContext, ta.Any], ta.Any]
14
+ m: Marshaler
15
+
16
+ def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
17
+ return self.m.marshal(ctx, self.wrapper(ctx, o))
18
+
19
+
20
+ @dc.dataclass(frozen=True)
21
+ class WrappedUnmarshaler(Unmarshaler):
22
+ unwrapper: ta.Callable[[UnmarshalContext, ta.Any], ta.Any]
23
+ u: Unmarshaler
24
+
25
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
26
+ return self.unwrapper(ctx, self.u.unmarshal(ctx, v))
@@ -41,12 +41,16 @@ def get_dataclass_metadata(ty: type) -> ObjectMetadata:
41
41
  ) or ObjectMetadata()
42
42
 
43
43
 
44
- def get_field_infos(
44
+ def get_dataclass_field_infos(
45
45
  ty: type,
46
- opts: col.TypeMap[Option] = col.TypeMap(),
46
+ opts: col.TypeMap[Option] | None = None,
47
47
  ) -> FieldInfos:
48
+ if opts is None:
49
+ opts = col.TypeMap()
50
+
48
51
  dc_md = get_dataclass_metadata(ty)
49
52
  dc_naming = dc_md.field_naming or opts.get(Naming)
53
+ dc_rf = dc.reflect(ty)
50
54
 
51
55
  fi_defaults = {
52
56
  k: v
@@ -62,18 +66,24 @@ def get_field_infos(
62
66
  type_hints = ta.get_type_hints(ty)
63
67
 
64
68
  ret: list[FieldInfo] = []
65
- for field in dc.fields(ty):
69
+ for field in dc_rf.fields.values():
66
70
  if (f_naming := field.metadata.get(Naming, dc_naming)) is not None:
67
71
  um_name = translate_name(field.name, f_naming)
68
72
  else:
69
73
  um_name = field.name
70
74
 
75
+ f_ty: ta.Any
76
+ if (cpx := dc_rf.cls_params_extras) is not None and cpx.generic_init:
77
+ f_ty = dc_rf.generic_replaced_field_annotations[field.name]
78
+ else:
79
+ f_ty = type_hints[field.name]
80
+
71
81
  fi_kw = dict(fi_defaults)
72
82
  fo_kw = dict(fo_defaults)
73
83
 
74
84
  fi_kw.update(
75
85
  name=field.name,
76
- type=type_hints[field.name],
86
+ type=f_ty,
77
87
  metadata=FieldMetadata(),
78
88
 
79
89
  marshal_name=um_name,
@@ -158,7 +168,22 @@ def _make_field_obj(ctx, ty, obj, fac):
158
168
  ##
159
169
 
160
170
 
161
- class DataclassMarshalerFactory(MarshalerFactory):
171
+ class AbstractDataclassFactory(lang.Abstract):
172
+ def _get_metadata(self, ty: type) -> ObjectMetadata:
173
+ return get_dataclass_metadata(ty)
174
+
175
+ def _get_field_infos(
176
+ self,
177
+ ty: type,
178
+ opts: col.TypeMap[Option] | None = None,
179
+ ) -> FieldInfos:
180
+ return get_dataclass_field_infos(ty, opts)
181
+
182
+
183
+ ##
184
+
185
+
186
+ class DataclassMarshalerFactory(AbstractDataclassFactory, MarshalerFactory):
162
187
  def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
163
188
  return isinstance(rty, type) and dc.is_dataclass(rty) and not lang.is_abstract_class(rty)
164
189
 
@@ -167,8 +192,8 @@ class DataclassMarshalerFactory(MarshalerFactory):
167
192
  check.state(dc.is_dataclass(ty))
168
193
  check.state(not lang.is_abstract_class(ty))
169
194
 
170
- dc_md = get_dataclass_metadata(ty)
171
- fis = get_field_infos(ty, ctx.options)
195
+ dc_md = self._get_metadata(ty)
196
+ fis = self._get_field_infos(ty, ctx.options)
172
197
 
173
198
  fields = [
174
199
  (fi, _make_field_obj(ctx, fi.type, fi.metadata.marshaler, fi.metadata.marshaler_factory))
@@ -185,7 +210,7 @@ class DataclassMarshalerFactory(MarshalerFactory):
185
210
  ##
186
211
 
187
212
 
188
- class DataclassUnmarshalerFactory(UnmarshalerFactory):
213
+ class DataclassUnmarshalerFactory(AbstractDataclassFactory, UnmarshalerFactory):
189
214
  def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
190
215
  return isinstance(rty, type) and dc.is_dataclass(rty) and not lang.is_abstract_class(rty)
191
216
 
@@ -194,8 +219,8 @@ class DataclassUnmarshalerFactory(UnmarshalerFactory):
194
219
  check.state(dc.is_dataclass(ty))
195
220
  check.state(not lang.is_abstract_class(ty))
196
221
 
197
- dc_md = get_dataclass_metadata(ty)
198
- fis = get_field_infos(ty, ctx.options)
222
+ dc_md = self._get_metadata(ty)
223
+ fis = self._get_field_infos(ty, ctx.options)
199
224
 
200
225
  d: dict[str, tuple[FieldInfo, Unmarshaler]] = {}
201
226
  defaults: dict[str, ta.Any] = {}
@@ -213,7 +238,7 @@ class DataclassUnmarshalerFactory(UnmarshalerFactory):
213
238
  raise Exception(f'Embedded fields cannot have specials: {e_ty}')
214
239
 
215
240
  embeds[fi.name] = e_ty
216
- for e_fi in get_field_infos(e_ty, ctx.options):
241
+ for e_fi in self._get_field_infos(e_ty, ctx.options):
217
242
  e_ns = add_field(e_fi, prefixes=[p + ep for p in prefixes for ep in fi.unmarshal_names])
218
243
  embeds_by_unmarshal_name.update({e_f: (fi.name, e_fi.name) for e_f in e_ns})
219
244
  ret.extend(e_ns)
@@ -22,7 +22,7 @@ from .unmarshal import ObjectUnmarshaler
22
22
  ##
23
23
 
24
24
 
25
- def _is_named_tuple(rty: rfl.Type) -> bool:
25
+ def _is_namedtuple(rty: rfl.Type) -> bool:
26
26
  return (
27
27
  isinstance(rty, type) and
28
28
  issubclass(rty, tuple) and
@@ -30,11 +30,11 @@ def _is_named_tuple(rty: rfl.Type) -> bool:
30
30
  )
31
31
 
32
32
 
33
- def get_field_infos(
33
+ def get_namedtuple_field_infos(
34
34
  ty: type,
35
35
  opts: col.TypeMap[Option] = col.TypeMap(),
36
36
  ) -> FieldInfos:
37
- check.arg(_is_named_tuple(ty), ty)
37
+ check.arg(_is_namedtuple(ty), ty)
38
38
 
39
39
  sig = inspect.signature(ty)
40
40
 
@@ -57,14 +57,14 @@ def get_field_infos(
57
57
 
58
58
  class NamedtupleMarshalerFactory(MarshalerFactory):
59
59
  def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
60
- return _is_named_tuple(rty)
60
+ return _is_namedtuple(rty)
61
61
 
62
62
  def fn(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
63
- check.state(_is_named_tuple(rty))
63
+ check.state(_is_namedtuple(rty))
64
64
  ty = check.isinstance(rty, type)
65
65
  check.state(not lang.is_abstract_class(ty))
66
66
 
67
- fis = get_field_infos(ty, ctx.options)
67
+ fis = get_namedtuple_field_infos(ty, ctx.options)
68
68
 
69
69
  fields = [
70
70
  (fi, ctx.make(fi.type))
@@ -81,14 +81,14 @@ class NamedtupleMarshalerFactory(MarshalerFactory):
81
81
 
82
82
  class NamedtupleUnmarshalerFactory(UnmarshalerFactory):
83
83
  def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
84
- return _is_named_tuple(rty)
84
+ return _is_namedtuple(rty)
85
85
 
86
86
  def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
87
- check.state(_is_named_tuple(rty))
87
+ check.state(_is_namedtuple(rty))
88
88
  ty = check.isinstance(rty, type)
89
89
  check.state(not lang.is_abstract_class(ty))
90
90
 
91
- fis = get_field_infos(ty, ctx.options)
91
+ fis = get_namedtuple_field_infos(ty, ctx.options)
92
92
 
93
93
  d: dict[str, tuple[FieldInfo, Unmarshaler]] = {}
94
94
  defaults: dict[str, ta.Any] = {}
@@ -1,7 +1,9 @@
1
+ import abc
1
2
  import dataclasses as dc
2
3
  import typing as ta
3
4
 
4
5
  from ... import check
6
+ from ... import lang
5
7
  from ... import reflect as rfl
6
8
  from ..base import MarshalContext
7
9
  from ..base import Marshaler
@@ -17,20 +19,32 @@ from .metadata import WrapperTypeTagging
17
19
  ##
18
20
 
19
21
 
22
+ class PolymorphismMarshaler(Marshaler, lang.Abstract):
23
+ @abc.abstractmethod
24
+ def get_impls(self) -> ta.Mapping[type, tuple[str, Marshaler]]:
25
+ raise NotImplementedError
26
+
27
+
20
28
  @dc.dataclass(frozen=True)
21
- class WrapperPolymorphismMarshaler(Marshaler):
29
+ class WrapperPolymorphismMarshaler(PolymorphismMarshaler):
22
30
  m: ta.Mapping[type, tuple[str, Marshaler]]
23
31
 
32
+ def get_impls(self) -> ta.Mapping[type, tuple[str, Marshaler]]:
33
+ return self.m
34
+
24
35
  def marshal(self, ctx: MarshalContext, o: ta.Any | None) -> Value:
25
36
  tag, m = self.m[type(o)]
26
37
  return {tag: m.marshal(ctx, o)}
27
38
 
28
39
 
29
40
  @dc.dataclass(frozen=True)
30
- class FieldPolymorphismMarshaler(Marshaler):
41
+ class FieldPolymorphismMarshaler(PolymorphismMarshaler):
31
42
  m: ta.Mapping[type, tuple[str, Marshaler]]
32
43
  tf: str
33
44
 
45
+ def get_impls(self) -> ta.Mapping[type, tuple[str, Marshaler]]:
46
+ return self.m
47
+
34
48
  def marshal(self, ctx: MarshalContext, o: ta.Any | None) -> Value:
35
49
  tag, m = self.m[type(o)]
36
50
  return {self.tf: tag, **m.marshal(ctx, o)} # type: ignore
@@ -1,8 +1,10 @@
1
+ import abc
1
2
  import collections.abc
2
3
  import dataclasses as dc
3
4
  import typing as ta
4
5
 
5
6
  from ... import check
7
+ from ... import lang
6
8
  from ... import reflect as rfl
7
9
  from ..base import UnmarshalContext
8
10
  from ..base import Unmarshaler
@@ -18,10 +20,19 @@ from .metadata import WrapperTypeTagging
18
20
  ##
19
21
 
20
22
 
23
+ class PolymorphismUnmarshaler(Unmarshaler, lang.Abstract):
24
+ @abc.abstractmethod
25
+ def get_impls(self) -> ta.Mapping[str, Unmarshaler]:
26
+ raise NotImplementedError
27
+
28
+
21
29
  @dc.dataclass(frozen=True)
22
- class WrapperPolymorphismUnmarshaler(Unmarshaler):
30
+ class WrapperPolymorphismUnmarshaler(PolymorphismUnmarshaler):
23
31
  m: ta.Mapping[str, Unmarshaler]
24
32
 
33
+ def get_impls(self) -> ta.Mapping[str, Unmarshaler]:
34
+ return self.m
35
+
25
36
  def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any | None:
26
37
  ma = check.isinstance(v, collections.abc.Mapping)
27
38
  [(tag, iv)] = ma.items()
@@ -30,10 +41,13 @@ class WrapperPolymorphismUnmarshaler(Unmarshaler):
30
41
 
31
42
 
32
43
  @dc.dataclass(frozen=True)
33
- class FieldPolymorphismUnmarshaler(Unmarshaler):
44
+ class FieldPolymorphismUnmarshaler(PolymorphismUnmarshaler):
34
45
  m: ta.Mapping[str, Unmarshaler]
35
46
  tf: str
36
47
 
48
+ def get_impls(self) -> ta.Mapping[str, Unmarshaler]:
49
+ return self.m
50
+
37
51
  def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any | None:
38
52
  ma = dict(check.isinstance(v, collections.abc.Mapping))
39
53
  tag = ma.pop(self.tf)