omlish 0.0.0.dev5__py3-none-any.whl → 0.0.0.dev7__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 (163) hide show
  1. omlish/__about__.py +109 -5
  2. omlish/__init__.py +0 -8
  3. omlish/asyncs/__init__.py +9 -9
  4. omlish/asyncs/anyio.py +123 -19
  5. omlish/asyncs/asyncio.py +23 -0
  6. omlish/asyncs/asyncs.py +9 -6
  7. omlish/asyncs/bridge.py +316 -0
  8. omlish/asyncs/trio_asyncio.py +7 -3
  9. omlish/bootstrap.py +737 -0
  10. omlish/check.py +1 -1
  11. omlish/collections/__init__.py +5 -0
  12. omlish/collections/exceptions.py +2 -0
  13. omlish/collections/identity.py +7 -0
  14. omlish/collections/utils.py +38 -9
  15. omlish/configs/strings.py +96 -0
  16. omlish/dataclasses/__init__.py +16 -0
  17. omlish/dataclasses/impl/copy.py +30 -0
  18. omlish/dataclasses/impl/descriptors.py +95 -0
  19. omlish/dataclasses/impl/exceptions.py +6 -0
  20. omlish/dataclasses/impl/fields.py +24 -25
  21. omlish/dataclasses/impl/init.py +4 -2
  22. omlish/dataclasses/impl/main.py +2 -0
  23. omlish/dataclasses/impl/reflect.py +1 -1
  24. omlish/dataclasses/utils.py +67 -0
  25. omlish/{lang/datetimes.py → datetimes.py} +8 -4
  26. omlish/diag/__init__.py +4 -0
  27. omlish/diag/procfs.py +2 -2
  28. omlish/{testing → diag}/pydevd.py +35 -0
  29. omlish/diag/threads.py +131 -48
  30. omlish/dispatch/_dispatch2.py +65 -0
  31. omlish/dispatch/_dispatch3.py +104 -0
  32. omlish/docker.py +16 -1
  33. omlish/fnpairs.py +11 -4
  34. omlish/formats/__init__.py +0 -0
  35. omlish/{configs → formats}/dotenv.py +15 -24
  36. omlish/{json.py → formats/json.py} +2 -1
  37. omlish/formats/yaml.py +223 -0
  38. omlish/graphs/trees.py +1 -1
  39. omlish/http/asgi.py +2 -1
  40. omlish/http/collections.py +15 -0
  41. omlish/http/consts.py +22 -1
  42. omlish/http/sessions.py +10 -3
  43. omlish/inject/__init__.py +49 -17
  44. omlish/inject/binder.py +185 -5
  45. omlish/inject/bindings.py +3 -36
  46. omlish/inject/eagers.py +2 -8
  47. omlish/inject/elements.py +31 -10
  48. omlish/inject/exceptions.py +1 -1
  49. omlish/inject/impl/elements.py +37 -12
  50. omlish/inject/impl/injector.py +72 -25
  51. omlish/inject/impl/inspect.py +33 -5
  52. omlish/inject/impl/origins.py +77 -0
  53. omlish/inject/impl/{private.py → privates.py} +2 -2
  54. omlish/inject/impl/scopes.py +6 -2
  55. omlish/inject/injector.py +8 -4
  56. omlish/inject/inspect.py +18 -0
  57. omlish/inject/keys.py +8 -14
  58. omlish/inject/listeners.py +26 -0
  59. omlish/inject/managed.py +76 -10
  60. omlish/inject/multis.py +68 -18
  61. omlish/inject/origins.py +30 -0
  62. omlish/inject/overrides.py +5 -4
  63. omlish/inject/{private.py → privates.py} +6 -10
  64. omlish/inject/providers.py +12 -85
  65. omlish/inject/scopes.py +13 -6
  66. omlish/inject/types.py +3 -1
  67. omlish/inject/utils.py +18 -0
  68. omlish/iterators.py +69 -2
  69. omlish/lang/__init__.py +24 -9
  70. omlish/lang/cached.py +2 -2
  71. omlish/lang/classes/restrict.py +12 -1
  72. omlish/lang/classes/simple.py +18 -8
  73. omlish/lang/contextmanagers.py +13 -4
  74. omlish/lang/descriptors.py +132 -1
  75. omlish/lang/functions.py +8 -28
  76. omlish/lang/imports.py +67 -0
  77. omlish/lang/iterables.py +60 -1
  78. omlish/lang/maybes.py +3 -0
  79. omlish/lang/objects.py +38 -0
  80. omlish/lang/strings.py +25 -0
  81. omlish/lang/sys.py +9 -0
  82. omlish/lang/typing.py +42 -0
  83. omlish/lifecycles/__init__.py +34 -0
  84. omlish/lifecycles/abstract.py +43 -0
  85. omlish/lifecycles/base.py +51 -0
  86. omlish/lifecycles/contextmanagers.py +74 -0
  87. omlish/lifecycles/controller.py +116 -0
  88. omlish/lifecycles/manager.py +161 -0
  89. omlish/lifecycles/states.py +43 -0
  90. omlish/lifecycles/transitions.py +64 -0
  91. omlish/lite/__init__.py +1 -0
  92. omlish/lite/cached.py +18 -0
  93. omlish/lite/check.py +29 -0
  94. omlish/lite/contextmanagers.py +18 -0
  95. omlish/lite/json.py +30 -0
  96. omlish/lite/logs.py +52 -0
  97. omlish/lite/marshal.py +316 -0
  98. omlish/lite/reflect.py +49 -0
  99. omlish/lite/runtime.py +18 -0
  100. omlish/lite/secrets.py +19 -0
  101. omlish/lite/strings.py +25 -0
  102. omlish/lite/subprocesses.py +112 -0
  103. omlish/logs/configs.py +15 -2
  104. omlish/logs/formatters.py +7 -2
  105. omlish/marshal/__init__.py +32 -0
  106. omlish/marshal/any.py +5 -5
  107. omlish/marshal/base.py +27 -11
  108. omlish/marshal/base64.py +24 -9
  109. omlish/marshal/dataclasses.py +34 -28
  110. omlish/marshal/datetimes.py +74 -18
  111. omlish/marshal/enums.py +14 -8
  112. omlish/marshal/exceptions.py +11 -1
  113. omlish/marshal/factories.py +59 -74
  114. omlish/marshal/forbidden.py +35 -0
  115. omlish/marshal/global_.py +11 -4
  116. omlish/marshal/iterables.py +21 -24
  117. omlish/marshal/mappings.py +23 -26
  118. omlish/marshal/naming.py +4 -0
  119. omlish/marshal/numbers.py +51 -0
  120. omlish/marshal/objects.py +1 -0
  121. omlish/marshal/optionals.py +11 -12
  122. omlish/marshal/polymorphism.py +86 -21
  123. omlish/marshal/primitives.py +4 -5
  124. omlish/marshal/standard.py +13 -8
  125. omlish/marshal/uuids.py +4 -5
  126. omlish/matchfns.py +218 -0
  127. omlish/os.py +64 -0
  128. omlish/reflect/__init__.py +39 -0
  129. omlish/reflect/isinstance.py +38 -0
  130. omlish/reflect/ops.py +84 -0
  131. omlish/reflect/subst.py +110 -0
  132. omlish/reflect/types.py +275 -0
  133. omlish/secrets/__init__.py +23 -0
  134. omlish/secrets/crypto.py +132 -0
  135. omlish/secrets/marshal.py +70 -0
  136. omlish/secrets/openssl.py +207 -0
  137. omlish/secrets/passwords.py +120 -0
  138. omlish/secrets/secrets.py +299 -0
  139. omlish/secrets/subprocesses.py +42 -0
  140. omlish/sql/dbs.py +7 -6
  141. omlish/sql/duckdb.py +136 -0
  142. omlish/sql/exprs.py +12 -0
  143. omlish/sql/secrets.py +10 -0
  144. omlish/sql/sqlean.py +17 -0
  145. omlish/term.py +2 -2
  146. omlish/testing/pytest/__init__.py +3 -2
  147. omlish/testing/pytest/inject/harness.py +3 -3
  148. omlish/testing/pytest/marks.py +4 -7
  149. omlish/testing/pytest/plugins/__init__.py +1 -0
  150. omlish/testing/pytest/plugins/asyncs.py +136 -0
  151. omlish/testing/pytest/plugins/pydevd.py +1 -1
  152. omlish/testing/pytest/plugins/switches.py +54 -19
  153. omlish/text/glyphsplit.py +97 -0
  154. omlish-0.0.0.dev7.dist-info/METADATA +50 -0
  155. omlish-0.0.0.dev7.dist-info/RECORD +268 -0
  156. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
  157. omlish/reflect.py +0 -355
  158. omlish-0.0.0.dev5.dist-info/METADATA +0 -34
  159. omlish-0.0.0.dev5.dist-info/RECORD +0 -212
  160. /omlish/{asyncs/futures.py → concurrent.py} +0 -0
  161. /omlish/{configs → formats}/props.py +0 -0
  162. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
  163. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/top_level.txt +0 -0
omlish/marshal/base.py CHANGED
@@ -2,6 +2,7 @@
2
2
  TODO:
3
3
  - mappings
4
4
  - redacted
5
+ - strongly typed MarshalerFactory base class?
5
6
  - strongly typed Composite/Cached Marshaler/Unmarshaler factories - footgun
6
7
  - streaming? Start/EndObject, etc..
7
8
 
@@ -80,10 +81,12 @@ import typing as ta
80
81
  from .. import check
81
82
  from .. import collections as col
82
83
  from .. import lang
84
+ from .. import matchfns as mfs
83
85
  from .. import reflect as rfl
84
86
  from .exceptions import UnhandledTypeError
85
- from .factories import Factory
86
87
  from .factories import RecursiveTypeFactory
88
+ from .factories import TypeCacheFactory
89
+ from .factories import TypeMapFactory
87
90
  from .registries import Registry
88
91
  from .registries import RegistryItem
89
92
  from .utils import _Proxy
@@ -105,8 +108,19 @@ class Unmarshaler(lang.Abstract):
105
108
  raise NotImplementedError
106
109
 
107
110
 
108
- MarshalerFactory = Factory[Marshaler, 'MarshalContext', rfl.Type]
109
- UnmarshalerFactory = Factory[Unmarshaler, 'UnmarshalContext', rfl.Type]
111
+ MarshalerFactory: ta.TypeAlias = mfs.MatchFn[['MarshalContext', rfl.Type], Marshaler]
112
+ UnmarshalerFactory: ta.TypeAlias = mfs.MatchFn[['UnmarshalContext', rfl.Type], Unmarshaler]
113
+
114
+ MarshalerFactoryMatchClass: ta.TypeAlias = mfs.MatchFnClass[['MarshalContext', rfl.Type], Marshaler]
115
+ UnmarshalerFactoryMatchClass: ta.TypeAlias = mfs.MatchFnClass[['UnmarshalContext', rfl.Type], Unmarshaler]
116
+
117
+ #
118
+
119
+ TypeMapMarshalerFactory: ta.TypeAlias = TypeMapFactory['MarshalContext', Marshaler]
120
+ TypeMapUnmarshalerFactory: ta.TypeAlias = TypeMapFactory['UnmarshalContext', Unmarshaler]
121
+
122
+ TypeCacheMarshalerFactory: ta.TypeAlias = TypeCacheFactory['MarshalContext', Marshaler]
123
+ TypeCacheUnmarshalerFactory: ta.TypeAlias = TypeCacheFactory['UnmarshalContext', Unmarshaler]
110
124
 
111
125
 
112
126
  ##
@@ -150,9 +164,10 @@ class MarshalContext(BaseContext, lang.Final):
150
164
 
151
165
  def make(self, o: ta.Any) -> Marshaler:
152
166
  rty = rfl.type_(o)
153
- if (m := check.not_none(self.factory)(self, rty)) is not None: # noqa
154
- return m
155
- raise UnhandledTypeError(rty)
167
+ try:
168
+ return check.not_none(self.factory)(self, rty) # type: ignore
169
+ except mfs.MatchGuardError:
170
+ raise UnhandledTypeError(rty) # noqa
156
171
 
157
172
 
158
173
  @dc.dataclass(frozen=True)
@@ -161,9 +176,10 @@ class UnmarshalContext(BaseContext, lang.Final):
161
176
 
162
177
  def make(self, o: ta.Any) -> Unmarshaler:
163
178
  rty = rfl.type_(o)
164
- if (m := check.not_none(self.factory)(self, rty)) is not None: # noqa
165
- return m
166
- raise UnhandledTypeError(rty)
179
+ try:
180
+ return check.not_none(self.factory)(self, rty) # type: ignore
181
+ except mfs.MatchGuardError:
182
+ raise UnhandledTypeError(rty) # noqa
167
183
 
168
184
 
169
185
  ##
@@ -174,7 +190,7 @@ class _ProxyMarshaler(_Proxy[Marshaler], Marshaler):
174
190
  return self._obj.marshal(ctx, o)
175
191
 
176
192
 
177
- class RecursiveMarshalerFactory(RecursiveTypeFactory[Marshaler, MarshalContext], lang.Final):
193
+ class RecursiveMarshalerFactory(RecursiveTypeFactory[MarshalContext, Marshaler], lang.Final):
178
194
  def __init__(self, f: MarshalerFactory) -> None:
179
195
  super().__init__(f, _ProxyMarshaler._new) # noqa
180
196
 
@@ -184,7 +200,7 @@ class _ProxyUnmarshaler(_Proxy[Unmarshaler], Unmarshaler):
184
200
  return self._obj.unmarshal(ctx, v)
185
201
 
186
202
 
187
- class RecursiveUnmarshalerFactory(RecursiveTypeFactory[Unmarshaler, UnmarshalContext], lang.Final):
203
+ class RecursiveUnmarshalerFactory(RecursiveTypeFactory[UnmarshalContext, Unmarshaler], lang.Final):
188
204
  def __init__(self, f: UnmarshalerFactory) -> None:
189
205
  super().__init__(f, _ProxyUnmarshaler._new) # noqa
190
206
 
omlish/marshal/base64.py CHANGED
@@ -1,25 +1,40 @@
1
1
  import base64
2
+ import typing as ta
2
3
 
3
4
  from .. import check
5
+ from .. import dataclasses as dc
4
6
  from .base import MarshalContext
5
7
  from .base import Marshaler
6
- from .base import MarshalerFactory
8
+ from .base import TypeMapMarshalerFactory
9
+ from .base import TypeMapUnmarshalerFactory
7
10
  from .base import UnmarshalContext
8
11
  from .base import Unmarshaler
9
- from .base import UnmarshalerFactory
10
- from .factories import TypeMapFactory
11
12
  from .values import Value
12
13
 
13
14
 
14
- class Base64MarshalerUnmarshaler(Marshaler, Unmarshaler):
15
+ T = ta.TypeVar('T')
16
+
17
+
18
+ @dc.dataclass(frozen=True)
19
+ class Base64MarshalerUnmarshaler(Marshaler, Unmarshaler, ta.Generic[T]):
20
+ ty: type[T]
21
+
15
22
  def marshal(self, ctx: MarshalContext, o: bytes) -> str:
16
23
  return base64.b64encode(o).decode()
17
24
 
18
- def unmarshal(self, ctx: UnmarshalContext, v: Value) -> bytes:
19
- return base64.b64decode(check.isinstance(v, str).encode())
25
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> T:
26
+ return self.ty(base64.b64decode(check.isinstance(v, str).encode())) # type: ignore
27
+
20
28
 
29
+ BASE64_TYPES = (
30
+ bytes,
31
+ bytearray,
32
+ )
21
33
 
22
- BASE64_MARSHALER_UNMARSHALER = Base64MarshalerUnmarshaler()
34
+ BASE64_MARSHALER_FACTORY = TypeMapMarshalerFactory({
35
+ ty: Base64MarshalerUnmarshaler(ty) for ty in BASE64_TYPES
36
+ })
23
37
 
24
- BASE64_MARSHALER_FACTORY: MarshalerFactory = TypeMapFactory({bytes: BASE64_MARSHALER_UNMARSHALER})
25
- BASE64_UNMARSHALER_FACTORY: UnmarshalerFactory = TypeMapFactory({bytes: BASE64_MARSHALER_UNMARSHALER})
38
+ BASE64_UNMARSHALER_FACTORY = TypeMapUnmarshalerFactory({
39
+ ty: Base64MarshalerUnmarshaler(ty) for ty in BASE64_TYPES
40
+ })
@@ -79,37 +79,43 @@ def _make_field_obj(ctx, ty, obj, fac):
79
79
 
80
80
 
81
81
  class DataclassMarshalerFactory(MarshalerFactory):
82
- def __call__(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler | None:
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
82
+ def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
83
+ return isinstance(rty, type) and dc.is_dataclass(rty)
84
+
85
+ def fn(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
86
+ ty = check.isinstance(rty, type)
87
+ check.state(dc.is_dataclass(ty))
88
+ dc_md = get_dataclass_metadata(ty)
89
+ fields = [
90
+ (fi, _make_field_obj(ctx, fi.type, fi.metadata.marshaler, fi.metadata.marshaler_factory))
91
+ for fi in get_field_infos(ty, ctx.options)
92
+ ]
93
+ return ObjectMarshaler(
94
+ fields,
95
+ unknown_field=dc_md.unknown_field,
96
+ )
94
97
 
95
98
 
96
99
  ##
97
100
 
98
101
 
99
102
  class DataclassUnmarshalerFactory(UnmarshalerFactory):
100
- def __call__(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler | None:
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
103
+ def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
104
+ return isinstance(rty, type) and dc.is_dataclass(rty)
105
+
106
+ def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
107
+ ty = check.isinstance(rty, type)
108
+ check.state(dc.is_dataclass(ty))
109
+ dc_md = get_dataclass_metadata(ty)
110
+ d: dict[str, tuple[FieldInfo, Unmarshaler]] = {}
111
+ for fi in get_field_infos(ty, ctx.options):
112
+ tup = (fi, _make_field_obj(ctx, fi.type, fi.metadata.unmarshaler, fi.metadata.unmarshaler_factory))
113
+ for un in fi.unmarshal_names:
114
+ if un in d:
115
+ raise KeyError(f'Duplicate fields for name {un!r}: {fi.name!r}, {d[un][0].name!r}')
116
+ d[un] = tup
117
+ return ObjectUnmarshaler(
118
+ ty,
119
+ d,
120
+ unknown_field=dc_md.unknown_field,
121
+ )
@@ -3,16 +3,28 @@ import datetime
3
3
  import typing as ta
4
4
 
5
5
  from .. import check
6
+ from .. import datetimes as dts
6
7
  from .base import MarshalContext
7
8
  from .base import Marshaler
8
- from .base import MarshalerFactory
9
+ from .base import TypeMapMarshalerFactory
10
+ from .base import TypeMapUnmarshalerFactory
9
11
  from .base import UnmarshalContext
10
12
  from .base import Unmarshaler
11
- from .base import UnmarshalerFactory
12
- from .factories import TypeMapFactory
13
13
  from .values import Value
14
14
 
15
15
 
16
+ DatetimeLikeT = ta.TypeVar('DatetimeLikeT', bound=datetime.datetime | datetime.date | datetime.time)
17
+
18
+ _DATETIME_LIKES = (
19
+ datetime.datetime,
20
+ datetime.date,
21
+ datetime.time,
22
+ )
23
+
24
+
25
+ ##
26
+
27
+
16
28
  DATE_FORMATS: ta.Sequence[str] = [
17
29
  '%Y-%m-%d',
18
30
  ]
@@ -43,48 +55,92 @@ DATETIME_FORMATS: ta.Sequence[str] = [
43
55
  ]
44
56
 
45
57
 
58
+ ##
59
+
60
+
46
61
  @dc.dataclass(frozen=True)
47
- class DatetimeMarshaler(Marshaler):
62
+ class DatetimeMarshaler(Marshaler, ta.Generic[DatetimeLikeT]):
63
+ cls: type[DatetimeLikeT]
48
64
  fmt: str
49
65
 
50
- def marshal(self, ctx: MarshalContext, o: datetime.datetime) -> Value:
66
+ def marshal(self, ctx: MarshalContext, o: DatetimeLikeT) -> Value:
51
67
  return o.strftime(self.fmt)
52
68
 
53
69
 
70
+ _ZERO_DATE = datetime.datetime.now().strptime('', '').date() # noqa
71
+ _ZERO_TIME = datetime.time(0)
72
+
73
+
54
74
  @dc.dataclass(frozen=True)
55
- class DatetimeUnmarshaler(Unmarshaler):
75
+ class DatetimeUnmarshaler(Unmarshaler, ta.Generic[DatetimeLikeT]):
76
+ cls: type[DatetimeLikeT]
56
77
  fmts: ta.Sequence[str]
57
78
  try_iso: bool = False
58
79
 
59
- def unmarshal(self, ctx: UnmarshalContext, v: Value) -> datetime.datetime:
80
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> DatetimeLikeT:
60
81
  v = check.isinstance(v, str)
61
82
 
62
83
  if self.try_iso:
63
84
  try:
64
- return datetime.datetime.fromisoformat(v)
85
+ return self.cls.fromisoformat(v) # type: ignore
65
86
  except ValueError:
66
87
  pass
67
88
 
68
89
  for fmt in self.fmts:
69
90
  try:
70
- return datetime.datetime.strptime(v, fmt) # FIXME: timezone # noqa
91
+ dt = datetime.datetime.strptime(v, fmt) # FIXME: timezone # noqa
71
92
  except ValueError:
72
93
  pass
94
+ else:
95
+ if self.cls is datetime.datetime:
96
+ return dt # type: ignore
97
+ elif self.cls is datetime.date:
98
+ if dt.time() != _ZERO_TIME:
99
+ raise ValueError(dt)
100
+ return dt.date() # type: ignore
101
+ elif self.cls is datetime.time:
102
+ if dt.date() != _ZERO_DATE:
103
+ raise ValueError(dt)
104
+ return dt.time() # type: ignore
105
+ else:
106
+ raise TypeError(self.cls)
73
107
 
74
108
  raise ValueError(v)
75
109
 
76
110
 
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})
111
+ ##
82
112
 
83
113
 
84
- class IsoDatetimeMarshalerUnmarshaler(Marshaler, Unmarshaler):
114
+ class IsoDatetimeMarshalerUnmarshaler(Marshaler, Unmarshaler, ta.Generic[DatetimeLikeT]):
115
+ cls: type[DatetimeLikeT]
85
116
 
86
- def marshal(self, ctx: MarshalContext, o: datetime.datetime) -> Value:
117
+ def marshal(self, ctx: MarshalContext, o: DatetimeLikeT) -> Value:
87
118
  return o.isoformat()
88
119
 
89
- def unmarshal(self, ctx: UnmarshalContext, v: Value) -> datetime.datetime:
90
- return datetime.datetime.fromisoformat(v) # type: ignore
120
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> DatetimeLikeT:
121
+ return self.cls.fromisoformat(v) # type: ignore
122
+
123
+
124
+ ##
125
+
126
+
127
+ class TimedeltaMarshalerUnmarshaler(Marshaler, Unmarshaler):
128
+ def marshal(self, ctx: MarshalContext, o: datetime.timedelta) -> Value:
129
+ return str(o)
130
+
131
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
132
+ return dts.parse_timedelta(check.isinstance(v, str))
133
+
134
+
135
+ ##
136
+
137
+
138
+ DATETIME_MARSHALER_FACTORY = TypeMapMarshalerFactory({
139
+ **{cls: DatetimeMarshaler(cls, DATETIME_FORMATS[0]) for cls in _DATETIME_LIKES},
140
+ datetime.timedelta: TimedeltaMarshalerUnmarshaler(),
141
+ })
142
+
143
+ DATETIME_UNMARSHALER_FACTORY = TypeMapUnmarshalerFactory({
144
+ **{cls: DatetimeUnmarshaler(cls, DATETIME_FORMATS, try_iso=True) for cls in _DATETIME_LIKES},
145
+ datetime.timedelta: TimedeltaMarshalerUnmarshaler(),
146
+ })
omlish/marshal/enums.py CHANGED
@@ -22,10 +22,13 @@ class EnumMarshaler(Marshaler):
22
22
 
23
23
 
24
24
  class EnumMarshalerFactory(MarshalerFactory):
25
- def __call__(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler | None:
26
- if isinstance(rty, type) and issubclass(rty, enum.Enum):
27
- return EnumMarshaler(rty)
28
- return None
25
+ def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
26
+ return isinstance(rty, type) and issubclass(rty, enum.Enum)
27
+
28
+ def fn(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
29
+ ty = check.isinstance(rty, type)
30
+ check.state(issubclass(ty, enum.Enum))
31
+ return EnumMarshaler(ty)
29
32
 
30
33
 
31
34
  @dc.dataclass(frozen=True)
@@ -37,7 +40,10 @@ class EnumUnmarshaler(Unmarshaler):
37
40
 
38
41
 
39
42
  class EnumUnmarshalerFactory(UnmarshalerFactory):
40
- def __call__(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler | None:
41
- if isinstance(rty, type) and issubclass(rty, enum.Enum):
42
- return EnumUnmarshaler(rty)
43
- return None
43
+ def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
44
+ return isinstance(rty, type) and issubclass(rty, enum.Enum)
45
+
46
+ def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
47
+ ty = check.isinstance(rty, type)
48
+ check.state(issubclass(ty, enum.Enum))
49
+ return EnumUnmarshaler(ty)
@@ -1,7 +1,17 @@
1
1
  from .. import reflect as rfl
2
2
 
3
3
 
4
- class UnhandledTypeError(Exception):
4
+ class MarshalError(Exception):
5
+ pass
6
+
7
+
8
+ class UnhandledTypeError(MarshalError):
9
+ @property
10
+ def rty(self) -> rfl.Type:
11
+ return self.args[0]
12
+
13
+
14
+ class ForbiddenTypeError(MarshalError):
5
15
  @property
6
16
  def rty(self) -> rfl.Type:
7
17
  return self.args[0]
@@ -1,86 +1,105 @@
1
- import abc
2
1
  import dataclasses as dc
3
- import enum
4
2
  import threading
5
3
  import typing as ta
6
4
 
5
+ from .. import check
6
+ from .. import matchfns as mfs
7
7
  from .. import reflect as rfl
8
8
 
9
9
 
10
10
  R = ta.TypeVar('R')
11
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) -> R | None:
21
- raise NotImplementedError
22
12
 
23
13
 
24
14
  ##
25
15
 
26
16
 
27
17
  @dc.dataclass(frozen=True)
28
- class FuncFactory(ta.Generic[R, C, A]):
29
- fn: ta.Callable[[C, A], R | None]
30
-
31
- def __call__(self, ctx: C, arg: A) -> R | None:
32
- return self.fn(ctx, arg)
33
-
34
-
35
- ##
18
+ class TypeMapFactory(mfs.MatchFn[[C, rfl.Type], R]):
19
+ m: ta.Mapping[rfl.Type, R] = dc.field(default_factory=dict)
36
20
 
21
+ def __post_init__(self) -> None:
22
+ for k in self.m:
23
+ if not isinstance(k, rfl.TYPES):
24
+ raise TypeError(k)
37
25
 
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)
26
+ def guard(self, ctx: C, rty: rfl.Type) -> bool:
27
+ check.isinstance(rty, rfl.TYPES)
28
+ return rty in self.m
41
29
 
42
- def __call__(self, ctx: C, rty: rfl.Type) -> R | None:
43
- return self.m.get(rty)
30
+ def fn(self, ctx: C, rty: rfl.Type) -> R:
31
+ check.isinstance(rty, rfl.TYPES)
32
+ try:
33
+ return self.m[rty]
34
+ except KeyError:
35
+ raise mfs.MatchGuardError(ctx, rty) # noqa
44
36
 
45
37
 
46
38
  ##
47
39
 
48
40
 
49
- class TypeCacheFactory(Factory[R, C, rfl.Type]):
50
- def __init__(self, f: Factory[R, C, rfl.Type]) -> None:
41
+ class TypeCacheFactory(mfs.MatchFn[[C, rfl.Type], R]):
42
+ def __init__(self, f: mfs.MatchFn[[C, rfl.Type], R]) -> None:
51
43
  super().__init__()
52
44
  self._f = f
53
45
  self._dct: dict[rfl.Type, R | None] = {}
54
46
  self._mtx = threading.RLock()
55
47
 
56
- def __call__(self, ctx: C, rty: rfl.Type) -> R | None:
57
- try:
58
- return self._dct[rty]
59
- except KeyError:
60
- pass
48
+ def guard(self, ctx: C, rty: rfl.Type) -> bool:
49
+ check.isinstance(rty, rfl.TYPES)
61
50
  with self._mtx:
62
51
  try:
63
- return self._dct[rty]
52
+ e = self._dct[rty]
64
53
  except KeyError:
65
- ret = self._dct[rty] = self._f(ctx, rty)
66
- return ret
54
+ if self._f.guard(ctx, rty):
55
+ return True
56
+ else:
57
+ self._dct[rty] = None
58
+ return False
59
+ else:
60
+ return e is not None
61
+
62
+ def fn(self, ctx: C, rty: rfl.Type) -> R:
63
+ check.isinstance(rty, rfl.TYPES)
64
+ with self._mtx:
65
+ try:
66
+ e = self._dct[rty]
67
+ except KeyError:
68
+ try:
69
+ ret = self._f(ctx, rty)
70
+ except mfs.MatchGuardError:
71
+ self._dct[rty] = None
72
+ raise
73
+ else:
74
+ self._dct[rty] = ret
75
+ return ret
76
+ else:
77
+ if e is None:
78
+ raise mfs.MatchGuardError(ctx, rty)
79
+ else:
80
+ return e
67
81
 
68
82
 
69
83
  ##
70
84
 
71
85
 
72
- class RecursiveTypeFactory(Factory[R, C, rfl.Type]):
86
+ class RecursiveTypeFactory(mfs.MatchFn[[C, rfl.Type], R]):
73
87
  def __init__(
74
88
  self,
75
- f: Factory[R, C, rfl.Type],
76
- prx: ta.Callable[[], tuple[R | None, ta.Callable[[R | None], None]]],
89
+ f: mfs.MatchFn[[C, rfl.Type], R],
90
+ prx: ta.Callable[[], tuple[R, ta.Callable[[R], None]]],
77
91
  ) -> None:
78
92
  super().__init__()
79
93
  self._f = f
80
94
  self._prx = prx
81
- self._dct: dict[rfl.Type, R | None] = {}
95
+ self._dct: dict[rfl.Type, R] = {}
96
+
97
+ def guard(self, ctx: C, rty: rfl.Type) -> bool:
98
+ check.isinstance(rty, rfl.TYPES)
99
+ return self._f.guard(ctx, rty)
82
100
 
83
- def __call__(self, ctx: C, rty: rfl.Type) -> R | None:
101
+ def fn(self, ctx: C, rty: rfl.Type) -> R:
102
+ check.isinstance(rty, rfl.TYPES)
84
103
  try:
85
104
  return self._dct[rty]
86
105
  except KeyError:
@@ -93,37 +112,3 @@ class RecursiveTypeFactory(Factory[R, C, rfl.Type]):
93
112
  return r
94
113
  finally:
95
114
  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) -> R | None:
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,35 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from .. import matchfns as mfs
5
+ from .. import reflect as rfl
6
+ from .base import MarshalContext
7
+ from .base import Marshaler
8
+ from .base import UnmarshalContext
9
+ from .base import Unmarshaler
10
+ from .exceptions import ForbiddenTypeError
11
+
12
+
13
+ C = ta.TypeVar('C')
14
+ R = ta.TypeVar('R')
15
+
16
+
17
+ @dc.dataclass(frozen=True)
18
+ class ForbiddenTypeFactory(mfs.MatchFn[[C, rfl.Type], R]):
19
+ rtys: ta.AbstractSet[rfl.Type]
20
+
21
+ def guard(self, ctx: C, rty: rfl.Type) -> bool:
22
+ return rty in self.rtys
23
+
24
+ def fn(self, ctx: C, rty: rfl.Type) -> R:
25
+ raise ForbiddenTypeError(rty)
26
+
27
+
28
+ @dc.dataclass(frozen=True)
29
+ class ForbiddenTypeMarshalerFactory(ForbiddenTypeFactory[MarshalContext, Marshaler]):
30
+ pass
31
+
32
+
33
+ @dc.dataclass(frozen=True)
34
+ class ForbiddenTypeUnmarshalerFactory(ForbiddenTypeFactory[UnmarshalContext, Unmarshaler]):
35
+ pass
omlish/marshal/global_.py CHANGED
@@ -1,7 +1,10 @@
1
1
  import typing as ta
2
2
 
3
+ from .. import lang
3
4
  from .base import MarshalContext
5
+ from .base import MarshalerFactory
4
6
  from .base import UnmarshalContext
7
+ from .base import UnmarshalerFactory
5
8
  from .registries import Registry
6
9
  from .standard import new_standard_marshaler_factory
7
10
  from .standard import new_standard_unmarshaler_factory
@@ -20,20 +23,24 @@ GLOBAL_REGISTRY = Registry()
20
23
  ##
21
24
 
22
25
 
23
- GLOBAL_MARSHALER_FACTORY = new_standard_marshaler_factory()
26
+ @lang.cached_function(lock=True)
27
+ def global_marshaler_factory() -> MarshalerFactory:
28
+ return new_standard_marshaler_factory()
24
29
 
25
30
 
26
31
  def marshal(obj: ta.Any, ty: type | None = None, **kwargs: ta.Any) -> Value:
27
- mc = MarshalContext(GLOBAL_REGISTRY, factory=GLOBAL_MARSHALER_FACTORY, **kwargs)
32
+ mc = MarshalContext(GLOBAL_REGISTRY, factory=global_marshaler_factory(), **kwargs)
28
33
  return mc.make(ty if ty is not None else type(obj)).marshal(mc, obj)
29
34
 
30
35
 
31
36
  ##
32
37
 
33
38
 
34
- GLOBAL_UNMARSHALER_FACTORY = new_standard_unmarshaler_factory()
39
+ @lang.cached_function(lock=True)
40
+ def global_unmarshaler_factory() -> UnmarshalerFactory:
41
+ return new_standard_unmarshaler_factory()
35
42
 
36
43
 
37
44
  def unmarshal(v: Value, ty: type[T], **kwargs: ta.Any) -> T:
38
- uc = UnmarshalContext(GLOBAL_REGISTRY, factory=GLOBAL_UNMARSHALER_FACTORY, **kwargs)
45
+ uc = UnmarshalContext(GLOBAL_REGISTRY, factory=global_unmarshaler_factory(), **kwargs)
39
46
  return uc.make(ty).unmarshal(uc, v)