omlish 0.0.0.dev1__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.

Potentially problematic release.


This version of omlish might be problematic. Click here for more details.

Files changed (187) hide show
  1. omlish/__about__.py +7 -0
  2. omlish/__init__.py +0 -0
  3. omlish/argparse.py +223 -0
  4. omlish/asyncs/__init__.py +17 -0
  5. omlish/asyncs/anyio.py +23 -0
  6. omlish/asyncs/asyncio.py +19 -0
  7. omlish/asyncs/asyncs.py +76 -0
  8. omlish/asyncs/futures.py +179 -0
  9. omlish/asyncs/trio.py +11 -0
  10. omlish/c3.py +173 -0
  11. omlish/cached.py +9 -0
  12. omlish/check.py +231 -0
  13. omlish/collections/__init__.py +63 -0
  14. omlish/collections/_abc.py +156 -0
  15. omlish/collections/_io_abc.py +78 -0
  16. omlish/collections/cache/__init__.py +11 -0
  17. omlish/collections/cache/descriptor.py +188 -0
  18. omlish/collections/cache/impl.py +485 -0
  19. omlish/collections/cache/types.py +37 -0
  20. omlish/collections/coerce.py +337 -0
  21. omlish/collections/frozen.py +148 -0
  22. omlish/collections/identity.py +106 -0
  23. omlish/collections/indexed.py +75 -0
  24. omlish/collections/mappings.py +127 -0
  25. omlish/collections/ordered.py +81 -0
  26. omlish/collections/persistent.py +36 -0
  27. omlish/collections/skiplist.py +193 -0
  28. omlish/collections/sorted.py +126 -0
  29. omlish/collections/treap.py +228 -0
  30. omlish/collections/treapmap.py +144 -0
  31. omlish/collections/unmodifiable.py +174 -0
  32. omlish/collections/utils.py +110 -0
  33. omlish/configs/__init__.py +0 -0
  34. omlish/configs/flattening.py +147 -0
  35. omlish/configs/props.py +64 -0
  36. omlish/dataclasses/__init__.py +83 -0
  37. omlish/dataclasses/impl/__init__.py +6 -0
  38. omlish/dataclasses/impl/api.py +260 -0
  39. omlish/dataclasses/impl/as_.py +76 -0
  40. omlish/dataclasses/impl/exceptions.py +2 -0
  41. omlish/dataclasses/impl/fields.py +148 -0
  42. omlish/dataclasses/impl/frozen.py +55 -0
  43. omlish/dataclasses/impl/hashing.py +85 -0
  44. omlish/dataclasses/impl/init.py +173 -0
  45. omlish/dataclasses/impl/internals.py +118 -0
  46. omlish/dataclasses/impl/main.py +150 -0
  47. omlish/dataclasses/impl/metaclass.py +126 -0
  48. omlish/dataclasses/impl/metadata.py +74 -0
  49. omlish/dataclasses/impl/order.py +47 -0
  50. omlish/dataclasses/impl/params.py +150 -0
  51. omlish/dataclasses/impl/processing.py +16 -0
  52. omlish/dataclasses/impl/reflect.py +173 -0
  53. omlish/dataclasses/impl/replace.py +40 -0
  54. omlish/dataclasses/impl/repr.py +34 -0
  55. omlish/dataclasses/impl/simple.py +92 -0
  56. omlish/dataclasses/impl/slots.py +80 -0
  57. omlish/dataclasses/impl/utils.py +167 -0
  58. omlish/defs.py +193 -0
  59. omlish/dispatch/__init__.py +3 -0
  60. omlish/dispatch/dispatch.py +137 -0
  61. omlish/dispatch/functions.py +52 -0
  62. omlish/dispatch/methods.py +162 -0
  63. omlish/docker.py +149 -0
  64. omlish/dynamic.py +220 -0
  65. omlish/graphs/__init__.py +0 -0
  66. omlish/graphs/dot/__init__.py +19 -0
  67. omlish/graphs/dot/items.py +162 -0
  68. omlish/graphs/dot/rendering.py +147 -0
  69. omlish/graphs/dot/utils.py +30 -0
  70. omlish/graphs/trees.py +249 -0
  71. omlish/http/__init__.py +0 -0
  72. omlish/http/consts.py +20 -0
  73. omlish/http/wsgi.py +34 -0
  74. omlish/inject/__init__.py +85 -0
  75. omlish/inject/binder.py +12 -0
  76. omlish/inject/bindings.py +49 -0
  77. omlish/inject/eagers.py +21 -0
  78. omlish/inject/elements.py +43 -0
  79. omlish/inject/exceptions.py +49 -0
  80. omlish/inject/impl/__init__.py +0 -0
  81. omlish/inject/impl/bindings.py +19 -0
  82. omlish/inject/impl/elements.py +154 -0
  83. omlish/inject/impl/injector.py +182 -0
  84. omlish/inject/impl/inspect.py +98 -0
  85. omlish/inject/impl/private.py +109 -0
  86. omlish/inject/impl/providers.py +132 -0
  87. omlish/inject/impl/scopes.py +198 -0
  88. omlish/inject/injector.py +40 -0
  89. omlish/inject/inspect.py +14 -0
  90. omlish/inject/keys.py +43 -0
  91. omlish/inject/managed.py +24 -0
  92. omlish/inject/overrides.py +18 -0
  93. omlish/inject/private.py +29 -0
  94. omlish/inject/providers.py +111 -0
  95. omlish/inject/proxy.py +48 -0
  96. omlish/inject/scopes.py +84 -0
  97. omlish/inject/types.py +21 -0
  98. omlish/iterators.py +184 -0
  99. omlish/json.py +194 -0
  100. omlish/lang/__init__.py +112 -0
  101. omlish/lang/cached.py +267 -0
  102. omlish/lang/classes/__init__.py +24 -0
  103. omlish/lang/classes/abstract.py +74 -0
  104. omlish/lang/classes/restrict.py +137 -0
  105. omlish/lang/classes/simple.py +120 -0
  106. omlish/lang/classes/test/__init__.py +0 -0
  107. omlish/lang/classes/test/test_abstract.py +89 -0
  108. omlish/lang/classes/test/test_restrict.py +71 -0
  109. omlish/lang/classes/test/test_simple.py +58 -0
  110. omlish/lang/classes/test/test_virtual.py +72 -0
  111. omlish/lang/classes/virtual.py +130 -0
  112. omlish/lang/clsdct.py +67 -0
  113. omlish/lang/cmp.py +63 -0
  114. omlish/lang/contextmanagers.py +249 -0
  115. omlish/lang/datetimes.py +67 -0
  116. omlish/lang/descriptors.py +52 -0
  117. omlish/lang/functions.py +126 -0
  118. omlish/lang/imports.py +153 -0
  119. omlish/lang/iterables.py +54 -0
  120. omlish/lang/maybes.py +136 -0
  121. omlish/lang/objects.py +103 -0
  122. omlish/lang/resolving.py +50 -0
  123. omlish/lang/strings.py +128 -0
  124. omlish/lang/typing.py +92 -0
  125. omlish/libc.py +532 -0
  126. omlish/logs/__init__.py +9 -0
  127. omlish/logs/_abc.py +247 -0
  128. omlish/logs/configs.py +62 -0
  129. omlish/logs/filters.py +9 -0
  130. omlish/logs/formatters.py +67 -0
  131. omlish/logs/utils.py +20 -0
  132. omlish/marshal/__init__.py +52 -0
  133. omlish/marshal/any.py +25 -0
  134. omlish/marshal/base.py +201 -0
  135. omlish/marshal/base64.py +25 -0
  136. omlish/marshal/dataclasses.py +115 -0
  137. omlish/marshal/datetimes.py +90 -0
  138. omlish/marshal/enums.py +43 -0
  139. omlish/marshal/exceptions.py +7 -0
  140. omlish/marshal/factories.py +129 -0
  141. omlish/marshal/global_.py +33 -0
  142. omlish/marshal/iterables.py +57 -0
  143. omlish/marshal/mappings.py +66 -0
  144. omlish/marshal/naming.py +17 -0
  145. omlish/marshal/objects.py +106 -0
  146. omlish/marshal/optionals.py +49 -0
  147. omlish/marshal/polymorphism.py +147 -0
  148. omlish/marshal/primitives.py +43 -0
  149. omlish/marshal/registries.py +57 -0
  150. omlish/marshal/standard.py +80 -0
  151. omlish/marshal/utils.py +23 -0
  152. omlish/marshal/uuids.py +29 -0
  153. omlish/marshal/values.py +30 -0
  154. omlish/math.py +184 -0
  155. omlish/os.py +32 -0
  156. omlish/reflect.py +359 -0
  157. omlish/replserver/__init__.py +5 -0
  158. omlish/replserver/__main__.py +4 -0
  159. omlish/replserver/console.py +247 -0
  160. omlish/replserver/server.py +146 -0
  161. omlish/runmodule.py +28 -0
  162. omlish/stats.py +342 -0
  163. omlish/term.py +222 -0
  164. omlish/testing/__init__.py +7 -0
  165. omlish/testing/pydevd.py +225 -0
  166. omlish/testing/pytest/__init__.py +8 -0
  167. omlish/testing/pytest/helpers.py +35 -0
  168. omlish/testing/pytest/inject/__init__.py +1 -0
  169. omlish/testing/pytest/inject/harness.py +159 -0
  170. omlish/testing/pytest/plugins/__init__.py +20 -0
  171. omlish/testing/pytest/plugins/_registry.py +6 -0
  172. omlish/testing/pytest/plugins/logging.py +13 -0
  173. omlish/testing/pytest/plugins/pycharm.py +54 -0
  174. omlish/testing/pytest/plugins/repeat.py +19 -0
  175. omlish/testing/pytest/plugins/skips.py +32 -0
  176. omlish/testing/pytest/plugins/spacing.py +19 -0
  177. omlish/testing/pytest/plugins/switches.py +70 -0
  178. omlish/testing/testing.py +102 -0
  179. omlish/text/__init__.py +0 -0
  180. omlish/text/delimit.py +171 -0
  181. omlish/text/indent.py +50 -0
  182. omlish/text/parts.py +265 -0
  183. omlish-0.0.0.dev1.dist-info/LICENSE +21 -0
  184. omlish-0.0.0.dev1.dist-info/METADATA +17 -0
  185. omlish-0.0.0.dev1.dist-info/RECORD +187 -0
  186. omlish-0.0.0.dev1.dist-info/WHEEL +5 -0
  187. omlish-0.0.0.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,115 @@
1
+ import typing as ta
2
+
3
+ from .. import check
4
+ from .. import collections as col
5
+ from .. import dataclasses as dc
6
+ from .. import reflect as rfl
7
+ from .base import MarshalContext
8
+ from .base import Marshaler
9
+ from .base import MarshalerFactory
10
+ from .base import Option
11
+ from .base import UnmarshalContext
12
+ from .base import Unmarshaler
13
+ from .base import UnmarshalerFactory
14
+ from .naming import Naming
15
+ from .naming import translate_name
16
+ from .objects import FieldInfo
17
+ from .objects import FieldMetadata
18
+ from .objects import ObjectMarshaler
19
+ from .objects import ObjectMetadata
20
+ from .objects import ObjectUnmarshaler
21
+
22
+
23
+ ##
24
+
25
+
26
+ def get_dataclass_metadata(ty: type) -> ObjectMetadata:
27
+ return check.optional_single(
28
+ e
29
+ for e in dc.get_merged_metadata(ty).get(dc.UserMetadata, [])
30
+ if isinstance(e, ObjectMetadata)
31
+ ) or ObjectMetadata()
32
+
33
+
34
+ def get_field_infos(ty: type, opts: col.TypeMap[Option] = col.TypeMap()) -> ta.Sequence[FieldInfo]:
35
+ dc_md = get_dataclass_metadata(ty)
36
+ dc_naming = dc_md.field_naming or opts.get(Naming)
37
+
38
+ type_hints = ta.get_type_hints(ty)
39
+
40
+ ret: list[FieldInfo] = []
41
+ for field in dc.fields(ty):
42
+ if (f_naming := field.metadata.get(Naming, dc_naming)) is not None:
43
+ um_name = translate_name(field.name, f_naming)
44
+ else:
45
+ um_name = field.name
46
+
47
+ kw = dict(
48
+ name=field.name,
49
+ type=type_hints[field.name],
50
+ metadata=FieldMetadata(),
51
+
52
+ marshal_name=um_name,
53
+ unmarshal_names=[um_name],
54
+ )
55
+
56
+ if (fmd := field.metadata.get(FieldMetadata)) is not None:
57
+ kw.update(metadata=fmd)
58
+
59
+ if fmd.name is not None:
60
+ kw.update(
61
+ marshal_name=fmd.name,
62
+ unmarshal_names=col.unique([fmd.name, *(fmd.alts or ())]),
63
+ )
64
+
65
+ ret.append(FieldInfo(**kw))
66
+
67
+ return ret
68
+
69
+
70
+ def _make_field_obj(ctx, ty, obj, fac):
71
+ if obj is not None:
72
+ return obj
73
+ if fac is not None:
74
+ return fac(ctx, ty)
75
+ return ctx.make(ty)
76
+
77
+
78
+ ##
79
+
80
+
81
+ class DataclassMarshalerFactory(MarshalerFactory):
82
+ def __call__(self, ctx: MarshalContext, rty: rfl.Type) -> ta.Optional[Marshaler]:
83
+ if isinstance(rty, type) and dc.is_dataclass(rty):
84
+ dc_md = get_dataclass_metadata(rty)
85
+ fields = [
86
+ (fi, _make_field_obj(ctx, fi.type, fi.metadata.marshaler, fi.metadata.marshaler_factory))
87
+ for fi in get_field_infos(rty, ctx.options)
88
+ ]
89
+ return ObjectMarshaler(
90
+ fields,
91
+ unknown_field=dc_md.unknown_field,
92
+ )
93
+ return None
94
+
95
+
96
+ ##
97
+
98
+
99
+ class DataclassUnmarshalerFactory(UnmarshalerFactory):
100
+ def __call__(self, ctx: UnmarshalContext, rty: rfl.Type) -> ta.Optional[Unmarshaler]:
101
+ if isinstance(rty, type) and dc.is_dataclass(rty):
102
+ dc_md = get_dataclass_metadata(rty)
103
+ d: dict[str, tuple[FieldInfo, Unmarshaler]] = {}
104
+ for fi in get_field_infos(rty, ctx.options):
105
+ tup = (fi, _make_field_obj(ctx, fi.type, fi.metadata.unmarshaler, fi.metadata.unmarshaler_factory))
106
+ for un in fi.unmarshal_names:
107
+ if un in d:
108
+ raise KeyError(f'Duplicate fields for name {un!r}: {fi.name!r}, {d[un][0].name!r}')
109
+ d[un] = tup
110
+ return ObjectUnmarshaler(
111
+ rty,
112
+ d,
113
+ unknown_field=dc_md.unknown_field,
114
+ )
115
+ return None
@@ -0,0 +1,90 @@
1
+ import dataclasses as dc
2
+ import datetime
3
+ import typing as ta
4
+
5
+ from .. import check
6
+ from .base import MarshalContext
7
+ from .base import Marshaler
8
+ from .base import MarshalerFactory
9
+ from .base import UnmarshalContext
10
+ from .base import Unmarshaler
11
+ from .base import UnmarshalerFactory
12
+ from .factories import TypeMapFactory
13
+ from .values import Value
14
+
15
+
16
+ DATE_FORMATS: ta.Sequence[str] = [
17
+ '%Y-%m-%d',
18
+ ]
19
+
20
+ TIME_FORMATS: ta.Sequence[str] = [
21
+ ' '.join([tp, *tz])
22
+ for tp in [
23
+ '%H:%M:%S.%f',
24
+ '%H:%M:%S',
25
+ '%H:%M',
26
+ ]
27
+ for tz in ta.cast(list[list[str]], [
28
+ [],
29
+ ['%z'],
30
+ ['%Z'],
31
+ ['%z', '%Z'],
32
+ ['%Z', '%z'],
33
+ ])
34
+ ]
35
+
36
+ SEPS: ta.Sequence[str] = ['T', ' ']
37
+
38
+ DATETIME_FORMATS: ta.Sequence[str] = [
39
+ s.join([df, tf])
40
+ for s in SEPS
41
+ for df in DATE_FORMATS
42
+ for tf in TIME_FORMATS
43
+ ]
44
+
45
+
46
+ @dc.dataclass(frozen=True)
47
+ class DatetimeMarshaler(Marshaler):
48
+ fmt: str
49
+
50
+ def marshal(self, ctx: MarshalContext, o: datetime.datetime) -> Value:
51
+ return o.strftime(self.fmt)
52
+
53
+
54
+ @dc.dataclass(frozen=True)
55
+ class DatetimeUnmarshaler(Unmarshaler):
56
+ fmts: ta.Sequence[str]
57
+ try_iso: bool = False
58
+
59
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> datetime.datetime:
60
+ v = check.isinstance(v, str)
61
+
62
+ if self.try_iso:
63
+ try:
64
+ return datetime.datetime.fromisoformat(v)
65
+ except ValueError:
66
+ pass
67
+
68
+ for fmt in self.fmts:
69
+ try:
70
+ return datetime.datetime.strptime(v, fmt)
71
+ except ValueError:
72
+ pass
73
+
74
+ raise ValueError(v)
75
+
76
+
77
+ DATETIME_MARSHALER = DatetimeMarshaler(DATETIME_FORMATS[0])
78
+ DATETIME_UNMARSHALER = DatetimeUnmarshaler(DATETIME_FORMATS, try_iso=True)
79
+
80
+ DATETIME_MARSHALER_FACTORY: MarshalerFactory = TypeMapFactory({datetime.datetime: DATETIME_MARSHALER})
81
+ DATETIME_UNMARSHALER_FACTORY: UnmarshalerFactory = TypeMapFactory({datetime.datetime: DATETIME_UNMARSHALER})
82
+
83
+
84
+ class IsoDatetimeMarshalerUnmarshaler(Marshaler, Unmarshaler):
85
+
86
+ def marshal(self, ctx: MarshalContext, o: datetime.datetime) -> Value:
87
+ return o.isoformat()
88
+
89
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> datetime.datetime:
90
+ return datetime.datetime.fromisoformat(v) # type: ignore
@@ -0,0 +1,43 @@
1
+ import dataclasses as dc
2
+ import enum
3
+ import typing as ta
4
+
5
+ from .. import check
6
+ from .. import reflect as rfl
7
+ from .base import MarshalContext
8
+ from .base import Marshaler
9
+ from .base import MarshalerFactory
10
+ from .base import UnmarshalContext
11
+ from .base import Unmarshaler
12
+ from .base import UnmarshalerFactory
13
+ from .values import Value
14
+
15
+
16
+ @dc.dataclass(frozen=True)
17
+ class EnumMarshaler(Marshaler):
18
+ ty: type[enum.Enum]
19
+
20
+ def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
21
+ return o.name
22
+
23
+
24
+ class EnumMarshalerFactory(MarshalerFactory):
25
+ def __call__(self, ctx: MarshalContext, rty: rfl.Type) -> ta.Optional[Marshaler]:
26
+ if isinstance(rty, type) and issubclass(rty, enum.Enum):
27
+ return EnumMarshaler(rty)
28
+ return None
29
+
30
+
31
+ @dc.dataclass(frozen=True)
32
+ class EnumUnmarshaler(Unmarshaler):
33
+ ty: type[enum.Enum]
34
+
35
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
36
+ return self.ty[check.isinstance(v, str)]
37
+
38
+
39
+ class EnumUnmarshalerFactory(UnmarshalerFactory):
40
+ def __call__(self, ctx: UnmarshalContext, rty: rfl.Type) -> ta.Optional[Unmarshaler]:
41
+ if isinstance(rty, type) and issubclass(rty, enum.Enum):
42
+ return EnumUnmarshaler(rty)
43
+ return None
@@ -0,0 +1,7 @@
1
+ from .. import reflect as rfl
2
+
3
+
4
+ class UnhandledTypeException(Exception):
5
+ @property
6
+ def rty(self) -> rfl.Type:
7
+ return self.args[0]
@@ -0,0 +1,129 @@
1
+ import abc
2
+ import dataclasses as dc
3
+ import enum
4
+ import threading
5
+ import typing as ta
6
+
7
+ from .. import reflect as rfl
8
+
9
+
10
+ R = ta.TypeVar('R')
11
+ C = ta.TypeVar('C')
12
+ A = ta.TypeVar('A')
13
+
14
+
15
+ ##
16
+
17
+
18
+ class Factory(abc.ABC, ta.Generic[R, C, A]):
19
+ @abc.abstractmethod
20
+ def __call__(self, ctx: C, arg: A) -> ta.Optional[R]:
21
+ raise NotImplementedError
22
+
23
+
24
+ ##
25
+
26
+
27
+ @dc.dataclass(frozen=True)
28
+ class FuncFactory(ta.Generic[R, C, A]):
29
+ fn: ta.Callable[[C, A], ta.Optional[R]]
30
+
31
+ def __call__(self, ctx: C, arg: A) -> ta.Optional[R]:
32
+ return self.fn(ctx, arg)
33
+
34
+
35
+ ##
36
+
37
+
38
+ @dc.dataclass(frozen=True)
39
+ class TypeMapFactory(Factory[R, C, rfl.Type]):
40
+ m: ta.Mapping[rfl.Type, R] = dc.field(default_factory=dict)
41
+
42
+ def __call__(self, ctx: C, rty: rfl.Type) -> ta.Optional[R]:
43
+ return self.m.get(rty)
44
+
45
+
46
+ ##
47
+
48
+
49
+ class TypeCacheFactory(Factory[R, C, rfl.Type]):
50
+ def __init__(self, f: Factory[R, C, rfl.Type]) -> None:
51
+ super().__init__()
52
+ self._f = f
53
+ self._dct: dict[rfl.Type, ta.Optional[R]] = {}
54
+ self._mtx = threading.RLock()
55
+
56
+ def __call__(self, ctx: C, rty: rfl.Type) -> ta.Optional[R]:
57
+ try:
58
+ return self._dct[rty]
59
+ except KeyError:
60
+ pass
61
+ with self._mtx:
62
+ try:
63
+ return self._dct[rty]
64
+ except KeyError:
65
+ ret = self._dct[rty] = self._f(ctx, rty)
66
+ return ret
67
+
68
+
69
+ ##
70
+
71
+
72
+ class RecursiveTypeFactory(Factory[R, C, rfl.Type]):
73
+ def __init__(
74
+ self,
75
+ f: Factory[R, C, rfl.Type],
76
+ prx: ta.Callable[[], tuple[ta.Optional[R], ta.Callable[[ta.Optional[R]], None]]],
77
+ ) -> None:
78
+ super().__init__()
79
+ self._f = f
80
+ self._prx = prx
81
+ self._dct: dict[rfl.Type, ta.Optional[R]] = {}
82
+
83
+ def __call__(self, ctx: C, rty: rfl.Type) -> ta.Optional[R]:
84
+ try:
85
+ return self._dct[rty]
86
+ except KeyError:
87
+ pass
88
+ p, sp = self._prx()
89
+ self._dct[rty] = p
90
+ try:
91
+ r = self._f(ctx, rty)
92
+ sp(r)
93
+ return r
94
+ finally:
95
+ del self._dct[rty]
96
+
97
+
98
+ ##
99
+
100
+
101
+ class CompositeFactory(Factory[R, C, A]):
102
+ class Strategy(enum.Enum):
103
+ FIRST = enum.auto()
104
+ ONE = enum.auto()
105
+
106
+ def __init__(self, *fs: Factory[R, C, A], strategy: Strategy = Strategy.FIRST) -> None:
107
+ super().__init__()
108
+ self._fs = fs
109
+ self._st = strategy
110
+
111
+ def __call__(self, ctx: C, arg: A) -> ta.Optional[R]:
112
+ w: list[R] = []
113
+ for c in self._fs:
114
+ if (r := c(ctx, arg)) is None:
115
+ continue
116
+ if self._st is CompositeFactory.Strategy.FIRST:
117
+ return r
118
+ w.append(r)
119
+
120
+ if not w:
121
+ return None
122
+
123
+ if self._st is CompositeFactory.Strategy.ONE:
124
+ if len(w) == 1:
125
+ return w[0]
126
+
127
+ raise TypeError(f'multiple implementations: {arg} {w}')
128
+
129
+ raise TypeError(f'unknown composite strategy: {self._st}')
@@ -0,0 +1,33 @@
1
+ from .registries import Registry
2
+ from .standard import new_standard_marshaler_factory
3
+ from .base import MarshalContext
4
+ from .base import UnmarshalContext
5
+ from .standard import new_standard_unmarshaler_factory
6
+
7
+
8
+ ##
9
+
10
+
11
+ GLOBAL_REGISTRY = Registry()
12
+
13
+
14
+ ##
15
+
16
+
17
+ GLOBAL_MARSHALER_FACTORY = new_standard_marshaler_factory()
18
+
19
+
20
+ def marshal(obj, ty=None, **kwargs):
21
+ mc = MarshalContext(GLOBAL_REGISTRY, factory=GLOBAL_MARSHALER_FACTORY, **kwargs)
22
+ return mc.make(ty if ty is not None else type(obj)).marshal(mc, obj)
23
+
24
+
25
+ ##
26
+
27
+
28
+ GLOBAL_UNMARSHALER_FACTORY = new_standard_unmarshaler_factory()
29
+
30
+
31
+ def unmarshal(v, ty, **kwargs):
32
+ uc = UnmarshalContext(GLOBAL_REGISTRY, factory=GLOBAL_UNMARSHALER_FACTORY, **kwargs)
33
+ return uc.make(ty).unmarshal(uc, v)
@@ -0,0 +1,57 @@
1
+ import collections.abc
2
+ import dataclasses as dc
3
+ import functools
4
+ import typing as ta
5
+
6
+ from .. import check
7
+ from .. import reflect as rfl
8
+ from .base import MarshalContext
9
+ from .base import Marshaler
10
+ from .base import MarshalerFactory
11
+ from .base import UnmarshalContext
12
+ from .base import Unmarshaler
13
+ from .base import UnmarshalerFactory
14
+ from .values import Value
15
+
16
+
17
+ @dc.dataclass(frozen=True)
18
+ class IterableMarshaler(Marshaler):
19
+ e: Marshaler
20
+
21
+ def marshal(self, ctx: MarshalContext, o: ta.Iterable) -> Value:
22
+ return list(map(functools.partial(self.e.marshal, ctx), o))
23
+
24
+
25
+ class IterableMarshalerFactory(MarshalerFactory):
26
+ def __call__(self, ctx: MarshalContext, rty: rfl.Type) -> ta.Optional[Marshaler]:
27
+ if isinstance(rty, rfl.Generic) and issubclass(rty.cls, collections.abc.Iterable):
28
+ if (e := ctx.make(check.single(rty.args))) is None:
29
+ return None # type: ignore
30
+ return IterableMarshaler(e)
31
+ if isinstance(rty, type) and issubclass(rty, collections.abc.Iterable):
32
+ if (e := ctx.make(ta.Any)) is None:
33
+ return None # type: ignore
34
+ return IterableMarshaler(e)
35
+ return None
36
+
37
+
38
+ @dc.dataclass(frozen=True)
39
+ class IterableUnmarshaler(Unmarshaler):
40
+ ctor: ta.Callable[[ta.Iterable[ta.Any]], ta.Iterable]
41
+ e: Unmarshaler
42
+
43
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Iterable:
44
+ return self.ctor(map(functools.partial(self.e.unmarshal, ctx), check.isinstance(v, collections.abc.Iterable)))
45
+
46
+
47
+ class IterableUnmarshalerFactory(UnmarshalerFactory):
48
+ def __call__(self, ctx: UnmarshalContext, rty: rfl.Type) -> ta.Optional[Unmarshaler]:
49
+ if isinstance(rty, rfl.Generic) and issubclass(rty.cls, collections.abc.Iterable):
50
+ if (e := ctx.make(check.single(rty.args))) is None:
51
+ return None # type: ignore
52
+ return IterableUnmarshaler(rty.cls, e) # noqa
53
+ if isinstance(rty, type) and issubclass(rty, collections.abc.Iterable):
54
+ if (e := ctx.make(ta.Any)) is None:
55
+ return None # type: ignore
56
+ return IterableUnmarshaler(rty, e) # noqa
57
+ return None
@@ -0,0 +1,66 @@
1
+ import collections.abc
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+ from .. import check
6
+ from .. import reflect as rfl
7
+ from .base import MarshalContext
8
+ from .base import Marshaler
9
+ from .base import MarshalerFactory
10
+ from .base import UnmarshalContext
11
+ from .base import Unmarshaler
12
+ from .base import UnmarshalerFactory
13
+ from .values import Value
14
+
15
+
16
+ @dc.dataclass(frozen=True)
17
+ class MappingMarshaler(Marshaler):
18
+ ke: Marshaler
19
+ ve: Marshaler
20
+
21
+ def marshal(self, ctx: MarshalContext, o: ta.Mapping) -> Value:
22
+ return {
23
+ self.ke.marshal(ctx, uk): self.ve.marshal(ctx, uv)
24
+ for uk, uv in check.isinstance(o, collections.abc.Mapping).items()
25
+ }
26
+
27
+
28
+ class MappingMarshalerFactory(MarshalerFactory):
29
+ def __call__(self, ctx: MarshalContext, rty: rfl.Type) -> ta.Optional[Marshaler]:
30
+ if isinstance(rty, rfl.Generic) and issubclass(rty.cls, collections.abc.Mapping):
31
+ kt, vt = rty.args
32
+ if (ke := ctx.make(kt)) is None or (ve := ctx.make(vt)) is None:
33
+ return None # type: ignore
34
+ return MappingMarshaler(ke, ve)
35
+ if isinstance(rty, type) and issubclass(rty, collections.abc.Mapping):
36
+ if (e := ctx.make(ta.Any)) is None:
37
+ return None # type: ignore
38
+ return MappingMarshaler(e, e)
39
+ return None
40
+
41
+
42
+ @dc.dataclass(frozen=True)
43
+ class MappingUnmarshaler(Unmarshaler):
44
+ ctor: ta.Callable[[ta.Mapping[ta.Any, ta.Any]], ta.Mapping]
45
+ ke: Unmarshaler
46
+ ve: Unmarshaler
47
+
48
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Mapping:
49
+ dct: dict = {}
50
+ for mk, mv in check.isinstance(v, collections.abc.Mapping).items():
51
+ dct[self.ke.unmarshal(ctx, mk)] = self.ve.unmarshal(ctx, mv) # type: ignore
52
+ return self.ctor(dct)
53
+
54
+
55
+ class MappingUnmarshalerFactory(UnmarshalerFactory):
56
+ def __call__(self, ctx: UnmarshalContext, rty: rfl.Type) -> ta.Optional[Unmarshaler]:
57
+ if isinstance(rty, rfl.Generic) and issubclass(rty.cls, collections.abc.Mapping):
58
+ kt, vt = rty.args
59
+ if (ke := ctx.make(kt)) is None or (ve := ctx.make(vt)) is None:
60
+ return None # type: ignore
61
+ return MappingUnmarshaler(rty.cls, ke, ve)
62
+ if isinstance(rty, type) and issubclass(rty, collections.abc.Mapping):
63
+ if (e := ctx.make(ta.Any)) is None:
64
+ return None # type: ignore
65
+ return MappingUnmarshaler(rty, e, e)
66
+ return None
@@ -0,0 +1,17 @@
1
+ import enum
2
+
3
+ from .. import lang
4
+ from .base import Option
5
+
6
+
7
+ class Naming(Option, enum.Enum):
8
+ SNAKE = 'snake'
9
+ CAMEL = 'camel'
10
+
11
+
12
+ def translate_name(n: str, e: Naming) -> str:
13
+ if e is Naming.SNAKE:
14
+ return lang.snake_case(n)
15
+ if e is Naming.CAMEL:
16
+ return lang.camel_case(n)
17
+ raise ValueError(e)
@@ -0,0 +1,106 @@
1
+ """
2
+ TODO:
3
+ - cfg naming
4
+ - adapters for dataclasses / namedtuples / user objects (as confitured)
5
+ """
6
+ import collections.abc
7
+ import typing as ta
8
+
9
+ from .. import check
10
+ from .. import dataclasses as dc
11
+ from .base import MarshalContext
12
+ from .base import Marshaler
13
+ from .base import MarshalerFactory
14
+ from .base import UnmarshalContext
15
+ from .base import Unmarshaler
16
+ from .base import UnmarshalerFactory
17
+ from .naming import Naming
18
+ from .values import Value
19
+
20
+
21
+ ##
22
+
23
+
24
+ @dc.dataclass(frozen=True)
25
+ class ObjectMetadata:
26
+ field_naming: Naming | None = None
27
+
28
+ unknown_field: str | None = None
29
+
30
+
31
+ @dc.dataclass(frozen=True)
32
+ class FieldMetadata:
33
+ name: str | None = None
34
+ alts: ta.Iterable[str] | None = None
35
+
36
+ marshaler: Marshaler | None = None
37
+ marshaler_factory: MarshalerFactory | None = None
38
+
39
+ unmarshaler: Unmarshaler | None = None
40
+ unmarshaler_factory: UnmarshalerFactory | None = None
41
+
42
+
43
+ ##
44
+
45
+
46
+ @dc.dataclass(frozen=True)
47
+ class FieldInfo:
48
+ name: str
49
+ type: ta.Any
50
+ metadata: FieldMetadata
51
+
52
+ marshal_name: str
53
+ unmarshal_names: ta.Sequence[str]
54
+
55
+
56
+ ##
57
+
58
+
59
+ @dc.dataclass(frozen=True)
60
+ class ObjectMarshaler(Marshaler):
61
+ fields: ta.Sequence[tuple[FieldInfo, Marshaler]]
62
+ unknown_field: str | None = None
63
+
64
+ def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
65
+ ret = {
66
+ fi.marshal_name: m.marshal(ctx, getattr(o, fi.name))
67
+ for fi, m in self.fields
68
+ }
69
+ if self.unknown_field is not None:
70
+ if (ukf := getattr(o, self.unknown_field)):
71
+ if (dks := set(ret) & set(ukf)):
72
+ raise KeyError(f'Unknown field keys duplicate fields: {dks!r}')
73
+ ret.update(ukf) # FIXME: marshal?
74
+ return ret
75
+
76
+
77
+ ##
78
+
79
+
80
+ @dc.dataclass(frozen=True)
81
+ class ObjectUnmarshaler(Unmarshaler):
82
+ cls: type
83
+ fields_by_unmarshal_name: ta.Mapping[str, tuple[FieldInfo, Unmarshaler]]
84
+ unknown_field: str | None = None
85
+
86
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
87
+ ma = check.isinstance(v, collections.abc.Mapping)
88
+ u: ta.Any
89
+ kw: dict[str, ta.Any] = {}
90
+ ukf: dict[str, ta.Any] | None = None
91
+ if self.unknown_field is not None:
92
+ kw[self.unknown_field] = ukf = {}
93
+ for k, mv in ma.items():
94
+ ks = check.isinstance(k, str)
95
+ try:
96
+ fi, u = self.fields_by_unmarshal_name[ks]
97
+ except KeyError:
98
+ if ukf is not None:
99
+ ukf[ks] = mv # FIXME: unmarshal?
100
+ continue
101
+ else:
102
+ raise
103
+ if fi.name in kw:
104
+ raise KeyError(f'Duplicate keys for field {fi.name!r}: {ks!r}')
105
+ kw[fi.name] = u.unmarshal(ctx, mv)
106
+ return self.cls(**kw)