telegrinder 0.3.1__py3-none-any.whl → 0.3.2__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 telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +144 -144
- telegrinder/api/__init__.py +8 -8
- telegrinder/api/api.py +93 -93
- telegrinder/api/error.py +16 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +66 -66
- telegrinder/bot/bot.py +76 -76
- telegrinder/bot/cute_types/__init__.py +11 -11
- telegrinder/bot/cute_types/base.py +258 -234
- telegrinder/bot/cute_types/callback_query.py +382 -382
- telegrinder/bot/cute_types/chat_join_request.py +61 -61
- telegrinder/bot/cute_types/chat_member_updated.py +160 -160
- telegrinder/bot/cute_types/inline_query.py +53 -53
- telegrinder/bot/cute_types/message.py +2631 -2631
- telegrinder/bot/cute_types/update.py +75 -75
- telegrinder/bot/cute_types/utils.py +92 -92
- telegrinder/bot/dispatch/__init__.py +55 -55
- telegrinder/bot/dispatch/abc.py +77 -77
- telegrinder/bot/dispatch/context.py +92 -92
- telegrinder/bot/dispatch/dispatch.py +202 -201
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +24 -24
- telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
- telegrinder/bot/dispatch/handler/base.py +57 -57
- telegrinder/bot/dispatch/handler/document_reply.py +44 -44
- telegrinder/bot/dispatch/handler/func.py +128 -123
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -43
- telegrinder/bot/dispatch/handler/message_reply.py +36 -36
- telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
- telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
- telegrinder/bot/dispatch/handler/video_reply.py +44 -44
- telegrinder/bot/dispatch/middleware/__init__.py +3 -3
- telegrinder/bot/dispatch/middleware/abc.py +16 -16
- telegrinder/bot/dispatch/process.py +132 -132
- telegrinder/bot/dispatch/return_manager/__init__.py +13 -13
- telegrinder/bot/dispatch/return_manager/abc.py +108 -108
- telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
- telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
- telegrinder/bot/dispatch/return_manager/message.py +36 -36
- telegrinder/bot/dispatch/view/__init__.py +13 -13
- telegrinder/bot/dispatch/view/abc.py +41 -41
- telegrinder/bot/dispatch/view/base.py +200 -211
- telegrinder/bot/dispatch/view/box.py +129 -129
- telegrinder/bot/dispatch/view/callback_query.py +17 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +16 -16
- telegrinder/bot/dispatch/view/chat_member.py +39 -39
- telegrinder/bot/dispatch/view/inline_query.py +17 -17
- telegrinder/bot/dispatch/view/message.py +44 -44
- telegrinder/bot/dispatch/view/raw.py +114 -118
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +13 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +57 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +57 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +53 -53
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +168 -170
- telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +65 -65
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +131 -131
- telegrinder/bot/rules/__init__.py +62 -62
- telegrinder/bot/rules/abc.py +238 -238
- telegrinder/bot/rules/adapter/__init__.py +9 -9
- telegrinder/bot/rules/adapter/abc.py +29 -29
- telegrinder/bot/rules/adapter/errors.py +5 -5
- telegrinder/bot/rules/adapter/event.py +76 -76
- telegrinder/bot/rules/adapter/node.py +48 -48
- telegrinder/bot/rules/adapter/raw_update.py +30 -30
- telegrinder/bot/rules/callback_data.py +171 -171
- telegrinder/bot/rules/chat_join.py +48 -48
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +36 -36
- telegrinder/bot/rules/func.py +26 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/inline.py +60 -60
- telegrinder/bot/rules/integer.py +20 -20
- telegrinder/bot/rules/is_from.py +146 -146
- telegrinder/bot/rules/markup.py +43 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +17 -17
- telegrinder/bot/rules/message_entities.py +35 -35
- telegrinder/bot/rules/node.py +27 -27
- telegrinder/bot/rules/regex.py +37 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +37 -37
- telegrinder/bot/rules/text.py +33 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +19 -19
- telegrinder/bot/scenario/checkbox.py +167 -147
- telegrinder/bot/scenario/choice.py +46 -44
- telegrinder/client/__init__.py +4 -4
- telegrinder/client/abc.py +75 -75
- telegrinder/client/aiohttp.py +130 -130
- telegrinder/model.py +244 -244
- telegrinder/modules.py +237 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +410 -410
- telegrinder/node/__init__.py +20 -20
- telegrinder/node/attachment.py +92 -92
- telegrinder/node/base.py +143 -144
- telegrinder/node/callback_query.py +14 -14
- telegrinder/node/command.py +33 -33
- telegrinder/node/composer.py +196 -184
- telegrinder/node/container.py +27 -27
- telegrinder/node/event.py +71 -73
- telegrinder/node/me.py +16 -16
- telegrinder/node/message.py +14 -14
- telegrinder/node/polymorphic.py +48 -52
- telegrinder/node/rule.py +76 -76
- telegrinder/node/scope.py +38 -38
- telegrinder/node/source.py +71 -71
- telegrinder/node/text.py +21 -21
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +40 -40
- telegrinder/node/update.py +15 -15
- telegrinder/rules.py +0 -0
- telegrinder/tools/__init__.py +74 -74
- telegrinder/tools/buttons.py +79 -79
- telegrinder/tools/error_handler/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +33 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +193 -193
- telegrinder/tools/formatting/__init__.py +46 -46
- telegrinder/tools/formatting/html.py +308 -308
- telegrinder/tools/formatting/links.py +33 -33
- telegrinder/tools/formatting/spec_html_formats.py +111 -111
- telegrinder/tools/functional.py +12 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +412 -412
- telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
- telegrinder/tools/i18n/__init__.py +12 -12
- telegrinder/tools/i18n/abc.py +32 -32
- telegrinder/tools/i18n/middleware/__init__.py +3 -3
- telegrinder/tools/i18n/middleware/abc.py +25 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/kb_set/__init__.py +4 -4
- telegrinder/tools/kb_set/base.py +15 -15
- telegrinder/tools/kb_set/yaml.py +63 -63
- telegrinder/tools/keyboard.py +128 -128
- telegrinder/tools/limited_dict.py +37 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +15 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +216 -216
- telegrinder/tools/magic.py +168 -168
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +35 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/types/__init__.py +6 -6
- telegrinder/types/enums.py +672 -672
- telegrinder/types/methods.py +4633 -4633
- telegrinder/types/objects.py +6317 -6317
- telegrinder/verification_utils.py +32 -32
- {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/LICENSE +22 -22
- {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/METADATA +1 -1
- telegrinder-0.3.2.dist-info/RECORD +164 -0
- telegrinder-0.3.1.dist-info/RECORD +0 -164
- {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/WHEEL +0 -0
telegrinder/msgspec_utils.py
CHANGED
|
@@ -1,410 +1,410 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import typing
|
|
3
|
-
from contextlib import contextmanager
|
|
4
|
-
|
|
5
|
-
import fntypes.option
|
|
6
|
-
import fntypes.result
|
|
7
|
-
import msgspec
|
|
8
|
-
from fntypes.co import Error, Ok, Result, Variative
|
|
9
|
-
|
|
10
|
-
if typing.TYPE_CHECKING:
|
|
11
|
-
from datetime import datetime
|
|
12
|
-
|
|
13
|
-
from fntypes.option import Option
|
|
14
|
-
|
|
15
|
-
def get_class_annotations(obj: typing.Any) -> dict[str, type[typing.Any]]: ...
|
|
16
|
-
|
|
17
|
-
def get_type_hints(obj: typing.Any) -> dict[str, type[typing.Any]]: ...
|
|
18
|
-
|
|
19
|
-
else:
|
|
20
|
-
from datetime import datetime as dt
|
|
21
|
-
|
|
22
|
-
from msgspec._utils import get_class_annotations, get_type_hints
|
|
23
|
-
|
|
24
|
-
Value = typing.TypeVar("Value")
|
|
25
|
-
Err = typing.TypeVar("Err")
|
|
26
|
-
|
|
27
|
-
datetime = type("datetime", (dt,), {})
|
|
28
|
-
|
|
29
|
-
class OptionMeta(type):
|
|
30
|
-
def __instancecheck__(cls, __instance: typing.Any) -> bool:
|
|
31
|
-
return isinstance(__instance, fntypes.option.Some | fntypes.option.Nothing)
|
|
32
|
-
|
|
33
|
-
class Option(typing.Generic[Value], metaclass=OptionMeta):
|
|
34
|
-
pass
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
T = typing.TypeVar("T")
|
|
38
|
-
|
|
39
|
-
DecHook: typing.TypeAlias = typing.Callable[[type[T], typing.Any], typing.Any]
|
|
40
|
-
EncHook: typing.TypeAlias = typing.Callable[[T], typing.Any]
|
|
41
|
-
|
|
42
|
-
Nothing: typing.Final[fntypes.option.Nothing] = fntypes.option.Nothing()
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def get_origin(t: type[T]) -> type[T]:
|
|
46
|
-
return typing.cast(T, typing.get_origin(t)) or t
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def repr_type(t: typing.Any) -> str:
|
|
50
|
-
return getattr(t, "__name__", repr(get_origin(t)))
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def is_common_type(type_: typing.Any) -> typing.TypeGuard[type[typing.Any]]:
|
|
54
|
-
if not isinstance(type_, type):
|
|
55
|
-
return False
|
|
56
|
-
return (
|
|
57
|
-
type_ in (str, int, float, bool, None, Variative)
|
|
58
|
-
or issubclass(type_, msgspec.Struct)
|
|
59
|
-
or hasattr(type_, "__dataclass_fields__")
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def type_check(obj: typing.Any, t: typing.Any) -> bool:
|
|
64
|
-
return (
|
|
65
|
-
isinstance(obj, t)
|
|
66
|
-
if isinstance(t, type) and issubclass(t, msgspec.Struct)
|
|
67
|
-
else type(obj) in t
|
|
68
|
-
if isinstance(t, tuple)
|
|
69
|
-
else type(obj) is t
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def msgspec_convert(obj: typing.Any, t: type[T]) -> Result[T, str]:
|
|
74
|
-
try:
|
|
75
|
-
return Ok(decoder.convert(obj, type=t, strict=True))
|
|
76
|
-
except msgspec.ValidationError:
|
|
77
|
-
return Error(
|
|
78
|
-
"Expected object of type `{}`, got `{}`.".format(
|
|
79
|
-
repr_type(t),
|
|
80
|
-
repr_type(type(obj)),
|
|
81
|
-
)
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def msgspec_to_builtins(
|
|
86
|
-
obj: typing.Any,
|
|
87
|
-
*,
|
|
88
|
-
str_keys: bool = False,
|
|
89
|
-
builtin_types: typing.Iterable[type[typing.Any]] | None = None,
|
|
90
|
-
order: typing.Literal["deterministic", "sorted"] | None = None,
|
|
91
|
-
) -> fntypes.result.Result[typing.Any, msgspec.ValidationError]:
|
|
92
|
-
try:
|
|
93
|
-
return Ok(encoder.to_builtins(**locals()))
|
|
94
|
-
except msgspec.ValidationError as exc:
|
|
95
|
-
return Error(exc)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def option_dec_hook(tp: type[Option[typing.Any]], obj: typing.Any) -> Option[typing.Any]:
|
|
99
|
-
if obj is None:
|
|
100
|
-
return Nothing
|
|
101
|
-
|
|
102
|
-
(value_type,) = typing.get_args(tp) or (typing.Any,)
|
|
103
|
-
orig_value_type = typing.get_origin(value_type) or value_type
|
|
104
|
-
orig_obj = obj
|
|
105
|
-
|
|
106
|
-
if not isinstance(orig_obj, dict | list) and is_common_type(orig_value_type):
|
|
107
|
-
if orig_value_type is Variative:
|
|
108
|
-
obj = value_type(orig_obj) # type: ignore
|
|
109
|
-
orig_value_type = typing.get_args(value_type)
|
|
110
|
-
|
|
111
|
-
if not type_check(orig_obj, orig_value_type):
|
|
112
|
-
raise TypeError(f"Expected `{repr_type(orig_value_type)}`, got `{repr_type(type(orig_obj))}`.")
|
|
113
|
-
|
|
114
|
-
return fntypes.option.Some(obj)
|
|
115
|
-
|
|
116
|
-
return fntypes.option.Some(decoder.convert(orig_obj, type=value_type))
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
|
|
120
|
-
union_types = typing.get_args(tp)
|
|
121
|
-
|
|
122
|
-
if isinstance(obj, dict):
|
|
123
|
-
models_struct_fields: dict[type[msgspec.Struct], int] = {
|
|
124
|
-
m: sum(1 for k in obj if k in m.__struct_fields__)
|
|
125
|
-
for m in union_types
|
|
126
|
-
if issubclass(get_origin(m), msgspec.Struct)
|
|
127
|
-
}
|
|
128
|
-
union_types = tuple(t for t in union_types if t not in models_struct_fields)
|
|
129
|
-
reverse = False
|
|
130
|
-
|
|
131
|
-
if len(set(models_struct_fields.values())) != len(models_struct_fields.values()):
|
|
132
|
-
models_struct_fields = {m: len(m.__struct_fields__) for m in models_struct_fields}
|
|
133
|
-
reverse = True
|
|
134
|
-
|
|
135
|
-
union_types = (
|
|
136
|
-
*sorted(
|
|
137
|
-
models_struct_fields,
|
|
138
|
-
key=lambda k: models_struct_fields[k],
|
|
139
|
-
reverse=reverse,
|
|
140
|
-
),
|
|
141
|
-
*union_types,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
for t in union_types:
|
|
145
|
-
if not isinstance(obj, dict | list) and is_common_type(t) and type_check(obj, t):
|
|
146
|
-
return tp(obj)
|
|
147
|
-
match msgspec_convert(obj, t):
|
|
148
|
-
case Ok(value):
|
|
149
|
-
return tp(value)
|
|
150
|
-
|
|
151
|
-
raise TypeError(
|
|
152
|
-
"Object of type `{}` does not belong to types `{}`".format(
|
|
153
|
-
repr_type(obj.__class__),
|
|
154
|
-
" | ".join(map(repr_type, union_types)),
|
|
155
|
-
)
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
@typing.runtime_checkable
|
|
160
|
-
class DataclassInstance(typing.Protocol):
|
|
161
|
-
__dataclass_fields__: typing.ClassVar[dict[str, dataclasses.Field[typing.Any]]]
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
class Decoder:
|
|
165
|
-
"""Class `Decoder` for `msgspec` module with decode hook
|
|
166
|
-
for objects with the specified type.
|
|
167
|
-
|
|
168
|
-
```
|
|
169
|
-
import enum
|
|
170
|
-
|
|
171
|
-
from datetime import datetime as dt
|
|
172
|
-
|
|
173
|
-
class Digit(enum.IntEnum):
|
|
174
|
-
ONE = 1
|
|
175
|
-
TWO = 2
|
|
176
|
-
THREE = 3
|
|
177
|
-
|
|
178
|
-
decoder = Encoder()
|
|
179
|
-
decoder.dec_hooks[dt] = lambda t, timestamp: t.fromtimestamp(timestamp)
|
|
180
|
-
|
|
181
|
-
decoder.dec_hook(dt, 1713354732) #> datetime.datetime(2024, 4, 17, 14, 52, 12)
|
|
182
|
-
|
|
183
|
-
decoder.convert("123", type=int, strict=False) #> 123
|
|
184
|
-
decoder.convert(1, type=Digit) #> <Digit.ONE: 1>
|
|
185
|
-
|
|
186
|
-
decoder.decode(b'{"digit":3}', type=dict[str, Digit]) #> {'digit': <Digit.THREE: 3>}
|
|
187
|
-
```
|
|
188
|
-
"""
|
|
189
|
-
|
|
190
|
-
def __init__(self) -> None:
|
|
191
|
-
self.dec_hooks: dict[typing.Any, DecHook[typing.Any]] = {
|
|
192
|
-
Option: option_dec_hook,
|
|
193
|
-
Variative: variative_dec_hook,
|
|
194
|
-
datetime: lambda t, obj: t.fromtimestamp(obj),
|
|
195
|
-
fntypes.option.Some: option_dec_hook,
|
|
196
|
-
fntypes.option.Nothing: option_dec_hook,
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
def __repr__(self) -> str:
|
|
200
|
-
return "<{}: dec_hooks={!r}>".format(
|
|
201
|
-
self.__class__.__name__,
|
|
202
|
-
self.dec_hooks,
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
@typing.overload
|
|
206
|
-
def __call__(self, type: type[T]) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
|
|
207
|
-
|
|
208
|
-
@typing.overload
|
|
209
|
-
def __call__(self, type: typing.Any) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
|
|
210
|
-
|
|
211
|
-
@typing.overload
|
|
212
|
-
def __call__(
|
|
213
|
-
self,
|
|
214
|
-
type: type[T],
|
|
215
|
-
*,
|
|
216
|
-
strict: bool = True,
|
|
217
|
-
) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
|
|
218
|
-
|
|
219
|
-
@typing.overload
|
|
220
|
-
def __call__(
|
|
221
|
-
self,
|
|
222
|
-
type: typing.Any,
|
|
223
|
-
*,
|
|
224
|
-
strict: bool = True,
|
|
225
|
-
) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
|
|
226
|
-
|
|
227
|
-
@contextmanager
|
|
228
|
-
def __call__(self, type=object, *, strict=True):
|
|
229
|
-
"""Context manager returns the `msgspec.json.Decoder` object with the `dec_hook`."""
|
|
230
|
-
|
|
231
|
-
dec_obj = msgspec.json.Decoder(
|
|
232
|
-
type=typing.Any if type is object else type,
|
|
233
|
-
strict=strict,
|
|
234
|
-
dec_hook=self.dec_hook,
|
|
235
|
-
)
|
|
236
|
-
yield dec_obj
|
|
237
|
-
|
|
238
|
-
def add_dec_hook(self, t: T): # type: ignore
|
|
239
|
-
def decorator(func: DecHook[T]) -> DecHook[T]:
|
|
240
|
-
return self.dec_hooks.setdefault(get_origin(t), func) # type: ignore
|
|
241
|
-
|
|
242
|
-
return decorator
|
|
243
|
-
|
|
244
|
-
def dec_hook(self, tp: type[typing.Any], obj: object) -> object:
|
|
245
|
-
origin_type = t if isinstance((t := get_origin(tp)), type) else type(t)
|
|
246
|
-
if origin_type not in self.dec_hooks:
|
|
247
|
-
raise TypeError(
|
|
248
|
-
f"Unknown type `{repr_type(origin_type)}`. You can implement decode hook for this type."
|
|
249
|
-
)
|
|
250
|
-
return self.dec_hooks[origin_type](tp, obj)
|
|
251
|
-
|
|
252
|
-
def convert(
|
|
253
|
-
self,
|
|
254
|
-
obj: object,
|
|
255
|
-
*,
|
|
256
|
-
type: type[T] = dict,
|
|
257
|
-
strict: bool = True,
|
|
258
|
-
from_attributes: bool = False,
|
|
259
|
-
builtin_types: typing.Iterable[type[typing.Any]] | None = None,
|
|
260
|
-
str_keys: bool = False,
|
|
261
|
-
) -> T:
|
|
262
|
-
return msgspec.convert(
|
|
263
|
-
obj,
|
|
264
|
-
type,
|
|
265
|
-
strict=strict,
|
|
266
|
-
from_attributes=from_attributes,
|
|
267
|
-
dec_hook=self.dec_hook,
|
|
268
|
-
builtin_types=builtin_types,
|
|
269
|
-
str_keys=str_keys,
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
@typing.overload
|
|
273
|
-
def decode(self, buf: str | bytes) -> typing.Any: ...
|
|
274
|
-
|
|
275
|
-
@typing.overload
|
|
276
|
-
def decode(self, buf: str | bytes, *, type: type[T]) -> T: ...
|
|
277
|
-
|
|
278
|
-
@typing.overload
|
|
279
|
-
def decode(self, buf: str | bytes, *, type: typing.Any) -> typing.Any: ...
|
|
280
|
-
|
|
281
|
-
@typing.overload
|
|
282
|
-
def decode(
|
|
283
|
-
self,
|
|
284
|
-
buf: str | bytes,
|
|
285
|
-
*,
|
|
286
|
-
type: type[T],
|
|
287
|
-
strict: bool = True,
|
|
288
|
-
) -> T: ...
|
|
289
|
-
|
|
290
|
-
@typing.overload
|
|
291
|
-
def decode(
|
|
292
|
-
self,
|
|
293
|
-
buf: str | bytes,
|
|
294
|
-
*,
|
|
295
|
-
type: typing.Any,
|
|
296
|
-
strict: bool = True,
|
|
297
|
-
) -> typing.Any: ...
|
|
298
|
-
|
|
299
|
-
def decode(self, buf, *, type=object, strict=True):
|
|
300
|
-
return msgspec.json.decode(
|
|
301
|
-
buf,
|
|
302
|
-
type=typing.Any if type is object else type,
|
|
303
|
-
strict=strict,
|
|
304
|
-
dec_hook=self.dec_hook,
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
class Encoder:
|
|
309
|
-
"""Class `Encoder` for `msgspec` module with encode hooks for objects.
|
|
310
|
-
|
|
311
|
-
```
|
|
312
|
-
from datetime import datetime as dt
|
|
313
|
-
|
|
314
|
-
encoder = Encoder()
|
|
315
|
-
encoder.enc_hooks[dt] = lambda d: int(d.timestamp())
|
|
316
|
-
|
|
317
|
-
encoder.enc_hook(dt.now()) #> 1713354732
|
|
318
|
-
encoder.encode({'digit': Digit.ONE}) #> '{"digit":1}'
|
|
319
|
-
```
|
|
320
|
-
"""
|
|
321
|
-
|
|
322
|
-
def __init__(self) -> None:
|
|
323
|
-
self.enc_hooks: dict[typing.Any, EncHook[typing.Any]] = {
|
|
324
|
-
fntypes.option.Some: lambda opt: opt.value,
|
|
325
|
-
fntypes.option.Nothing: lambda _: None,
|
|
326
|
-
Variative: lambda variative: variative.v,
|
|
327
|
-
datetime: lambda date: int(date.timestamp()),
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
def __repr__(self) -> str:
|
|
331
|
-
return "<{}: enc_hooks={!r}>".format(
|
|
332
|
-
self.__class__.__name__,
|
|
333
|
-
self.enc_hooks,
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
@contextmanager
|
|
337
|
-
def __call__(
|
|
338
|
-
self,
|
|
339
|
-
*,
|
|
340
|
-
decimal_format: typing.Literal["string", "number"] = "string",
|
|
341
|
-
uuid_format: typing.Literal["canonical", "hex"] = "canonical",
|
|
342
|
-
order: typing.Literal[None, "deterministic", "sorted"] = None,
|
|
343
|
-
) -> typing.Generator[msgspec.json.Encoder, typing.Any, None]:
|
|
344
|
-
"""Context manager returns the `msgspec.json.Encoder` object with the `enc_hook`."""
|
|
345
|
-
|
|
346
|
-
enc_obj = msgspec.json.Encoder(enc_hook=self.enc_hook)
|
|
347
|
-
yield enc_obj
|
|
348
|
-
|
|
349
|
-
def add_dec_hook(self, t: type[T]):
|
|
350
|
-
def decorator(func: EncHook[T]) -> EncHook[T]:
|
|
351
|
-
encode_hook = self.enc_hooks.setdefault(get_origin(t), func)
|
|
352
|
-
return func if encode_hook is not func else encode_hook
|
|
353
|
-
|
|
354
|
-
return decorator
|
|
355
|
-
|
|
356
|
-
def enc_hook(self, obj: object) -> object:
|
|
357
|
-
origin_type = get_origin(obj.__class__)
|
|
358
|
-
if origin_type not in self.enc_hooks:
|
|
359
|
-
raise NotImplementedError(
|
|
360
|
-
f"Not implemented encode hook for object of type `{repr_type(origin_type)}`."
|
|
361
|
-
)
|
|
362
|
-
return self.enc_hooks[origin_type](obj)
|
|
363
|
-
|
|
364
|
-
@typing.overload
|
|
365
|
-
def encode(self, obj: typing.Any) -> str: ...
|
|
366
|
-
|
|
367
|
-
@typing.overload
|
|
368
|
-
def encode(self, obj: typing.Any, *, as_str: typing.Literal[True]) -> str: ...
|
|
369
|
-
|
|
370
|
-
@typing.overload
|
|
371
|
-
def encode(self, obj: typing.Any, *, as_str: typing.Literal[False]) -> bytes: ...
|
|
372
|
-
|
|
373
|
-
def encode(self, obj: typing.Any, *, as_str: bool = True) -> str | bytes:
|
|
374
|
-
buf = msgspec.json.encode(obj, enc_hook=self.enc_hook)
|
|
375
|
-
return buf.decode() if as_str else buf
|
|
376
|
-
|
|
377
|
-
def to_builtins(
|
|
378
|
-
self,
|
|
379
|
-
obj: typing.Any,
|
|
380
|
-
*,
|
|
381
|
-
str_keys: bool = False,
|
|
382
|
-
builtin_types: typing.Iterable[type[typing.Any]] | None = None,
|
|
383
|
-
order: typing.Literal["deterministic", "sorted"] | None = None,
|
|
384
|
-
) -> typing.Any:
|
|
385
|
-
return msgspec.to_builtins(
|
|
386
|
-
obj,
|
|
387
|
-
str_keys=str_keys,
|
|
388
|
-
builtin_types=builtin_types,
|
|
389
|
-
enc_hook=self.enc_hook,
|
|
390
|
-
order=order,
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
decoder: typing.Final[Decoder] = Decoder()
|
|
395
|
-
encoder: typing.Final[Encoder] = Encoder()
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
__all__ = (
|
|
399
|
-
"Decoder",
|
|
400
|
-
"Encoder",
|
|
401
|
-
"Nothing",
|
|
402
|
-
"Option",
|
|
403
|
-
"datetime",
|
|
404
|
-
"decoder",
|
|
405
|
-
"encoder",
|
|
406
|
-
"get_class_annotations",
|
|
407
|
-
"get_type_hints",
|
|
408
|
-
"msgspec_convert",
|
|
409
|
-
"msgspec_to_builtins",
|
|
410
|
-
)
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
import fntypes.option
|
|
6
|
+
import fntypes.result
|
|
7
|
+
import msgspec
|
|
8
|
+
from fntypes.co import Error, Ok, Result, Variative
|
|
9
|
+
|
|
10
|
+
if typing.TYPE_CHECKING:
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
from fntypes.option import Option
|
|
14
|
+
|
|
15
|
+
def get_class_annotations(obj: typing.Any) -> dict[str, type[typing.Any]]: ...
|
|
16
|
+
|
|
17
|
+
def get_type_hints(obj: typing.Any) -> dict[str, type[typing.Any]]: ...
|
|
18
|
+
|
|
19
|
+
else:
|
|
20
|
+
from datetime import datetime as dt
|
|
21
|
+
|
|
22
|
+
from msgspec._utils import get_class_annotations, get_type_hints
|
|
23
|
+
|
|
24
|
+
Value = typing.TypeVar("Value")
|
|
25
|
+
Err = typing.TypeVar("Err")
|
|
26
|
+
|
|
27
|
+
datetime = type("datetime", (dt,), {})
|
|
28
|
+
|
|
29
|
+
class OptionMeta(type):
|
|
30
|
+
def __instancecheck__(cls, __instance: typing.Any) -> bool:
|
|
31
|
+
return isinstance(__instance, fntypes.option.Some | fntypes.option.Nothing)
|
|
32
|
+
|
|
33
|
+
class Option(typing.Generic[Value], metaclass=OptionMeta):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
T = typing.TypeVar("T")
|
|
38
|
+
|
|
39
|
+
DecHook: typing.TypeAlias = typing.Callable[[type[T], typing.Any], typing.Any]
|
|
40
|
+
EncHook: typing.TypeAlias = typing.Callable[[T], typing.Any]
|
|
41
|
+
|
|
42
|
+
Nothing: typing.Final[fntypes.option.Nothing] = fntypes.option.Nothing()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_origin(t: type[T]) -> type[T]:
|
|
46
|
+
return typing.cast(T, typing.get_origin(t)) or t
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def repr_type(t: typing.Any) -> str:
|
|
50
|
+
return getattr(t, "__name__", repr(get_origin(t)))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_common_type(type_: typing.Any) -> typing.TypeGuard[type[typing.Any]]:
|
|
54
|
+
if not isinstance(type_, type):
|
|
55
|
+
return False
|
|
56
|
+
return (
|
|
57
|
+
type_ in (str, int, float, bool, None, Variative)
|
|
58
|
+
or issubclass(type_, msgspec.Struct)
|
|
59
|
+
or hasattr(type_, "__dataclass_fields__")
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def type_check(obj: typing.Any, t: typing.Any) -> bool:
|
|
64
|
+
return (
|
|
65
|
+
isinstance(obj, t)
|
|
66
|
+
if isinstance(t, type) and issubclass(t, msgspec.Struct)
|
|
67
|
+
else type(obj) in t
|
|
68
|
+
if isinstance(t, tuple)
|
|
69
|
+
else type(obj) is t
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def msgspec_convert(obj: typing.Any, t: type[T]) -> Result[T, str]:
|
|
74
|
+
try:
|
|
75
|
+
return Ok(decoder.convert(obj, type=t, strict=True))
|
|
76
|
+
except msgspec.ValidationError:
|
|
77
|
+
return Error(
|
|
78
|
+
"Expected object of type `{}`, got `{}`.".format(
|
|
79
|
+
repr_type(t),
|
|
80
|
+
repr_type(type(obj)),
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def msgspec_to_builtins(
|
|
86
|
+
obj: typing.Any,
|
|
87
|
+
*,
|
|
88
|
+
str_keys: bool = False,
|
|
89
|
+
builtin_types: typing.Iterable[type[typing.Any]] | None = None,
|
|
90
|
+
order: typing.Literal["deterministic", "sorted"] | None = None,
|
|
91
|
+
) -> fntypes.result.Result[typing.Any, msgspec.ValidationError]:
|
|
92
|
+
try:
|
|
93
|
+
return Ok(encoder.to_builtins(**locals()))
|
|
94
|
+
except msgspec.ValidationError as exc:
|
|
95
|
+
return Error(exc)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def option_dec_hook(tp: type[Option[typing.Any]], obj: typing.Any) -> Option[typing.Any]:
|
|
99
|
+
if obj is None:
|
|
100
|
+
return Nothing
|
|
101
|
+
|
|
102
|
+
(value_type,) = typing.get_args(tp) or (typing.Any,)
|
|
103
|
+
orig_value_type = typing.get_origin(value_type) or value_type
|
|
104
|
+
orig_obj = obj
|
|
105
|
+
|
|
106
|
+
if not isinstance(orig_obj, dict | list) and is_common_type(orig_value_type):
|
|
107
|
+
if orig_value_type is Variative:
|
|
108
|
+
obj = value_type(orig_obj) # type: ignore
|
|
109
|
+
orig_value_type = typing.get_args(value_type)
|
|
110
|
+
|
|
111
|
+
if not type_check(orig_obj, orig_value_type):
|
|
112
|
+
raise TypeError(f"Expected `{repr_type(orig_value_type)}`, got `{repr_type(type(orig_obj))}`.")
|
|
113
|
+
|
|
114
|
+
return fntypes.option.Some(obj)
|
|
115
|
+
|
|
116
|
+
return fntypes.option.Some(decoder.convert(orig_obj, type=value_type))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
|
|
120
|
+
union_types = typing.get_args(tp)
|
|
121
|
+
|
|
122
|
+
if isinstance(obj, dict):
|
|
123
|
+
models_struct_fields: dict[type[msgspec.Struct], int] = {
|
|
124
|
+
m: sum(1 for k in obj if k in m.__struct_fields__)
|
|
125
|
+
for m in union_types
|
|
126
|
+
if issubclass(get_origin(m), msgspec.Struct)
|
|
127
|
+
}
|
|
128
|
+
union_types = tuple(t for t in union_types if t not in models_struct_fields)
|
|
129
|
+
reverse = False
|
|
130
|
+
|
|
131
|
+
if len(set(models_struct_fields.values())) != len(models_struct_fields.values()):
|
|
132
|
+
models_struct_fields = {m: len(m.__struct_fields__) for m in models_struct_fields}
|
|
133
|
+
reverse = True
|
|
134
|
+
|
|
135
|
+
union_types = (
|
|
136
|
+
*sorted(
|
|
137
|
+
models_struct_fields,
|
|
138
|
+
key=lambda k: models_struct_fields[k],
|
|
139
|
+
reverse=reverse,
|
|
140
|
+
),
|
|
141
|
+
*union_types,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
for t in union_types:
|
|
145
|
+
if not isinstance(obj, dict | list) and is_common_type(t) and type_check(obj, t):
|
|
146
|
+
return tp(obj)
|
|
147
|
+
match msgspec_convert(obj, t):
|
|
148
|
+
case Ok(value):
|
|
149
|
+
return tp(value)
|
|
150
|
+
|
|
151
|
+
raise TypeError(
|
|
152
|
+
"Object of type `{}` does not belong to types `{}`".format(
|
|
153
|
+
repr_type(obj.__class__),
|
|
154
|
+
" | ".join(map(repr_type, union_types)),
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@typing.runtime_checkable
|
|
160
|
+
class DataclassInstance(typing.Protocol):
|
|
161
|
+
__dataclass_fields__: typing.ClassVar[dict[str, dataclasses.Field[typing.Any]]]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Decoder:
|
|
165
|
+
"""Class `Decoder` for `msgspec` module with decode hook
|
|
166
|
+
for objects with the specified type.
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
import enum
|
|
170
|
+
|
|
171
|
+
from datetime import datetime as dt
|
|
172
|
+
|
|
173
|
+
class Digit(enum.IntEnum):
|
|
174
|
+
ONE = 1
|
|
175
|
+
TWO = 2
|
|
176
|
+
THREE = 3
|
|
177
|
+
|
|
178
|
+
decoder = Encoder()
|
|
179
|
+
decoder.dec_hooks[dt] = lambda t, timestamp: t.fromtimestamp(timestamp)
|
|
180
|
+
|
|
181
|
+
decoder.dec_hook(dt, 1713354732) #> datetime.datetime(2024, 4, 17, 14, 52, 12)
|
|
182
|
+
|
|
183
|
+
decoder.convert("123", type=int, strict=False) #> 123
|
|
184
|
+
decoder.convert(1, type=Digit) #> <Digit.ONE: 1>
|
|
185
|
+
|
|
186
|
+
decoder.decode(b'{"digit":3}', type=dict[str, Digit]) #> {'digit': <Digit.THREE: 3>}
|
|
187
|
+
```
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
def __init__(self) -> None:
|
|
191
|
+
self.dec_hooks: dict[typing.Any, DecHook[typing.Any]] = {
|
|
192
|
+
Option: option_dec_hook,
|
|
193
|
+
Variative: variative_dec_hook,
|
|
194
|
+
datetime: lambda t, obj: t.fromtimestamp(obj),
|
|
195
|
+
fntypes.option.Some: option_dec_hook,
|
|
196
|
+
fntypes.option.Nothing: option_dec_hook,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
def __repr__(self) -> str:
|
|
200
|
+
return "<{}: dec_hooks={!r}>".format(
|
|
201
|
+
self.__class__.__name__,
|
|
202
|
+
self.dec_hooks,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
@typing.overload
|
|
206
|
+
def __call__(self, type: type[T]) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
|
|
207
|
+
|
|
208
|
+
@typing.overload
|
|
209
|
+
def __call__(self, type: typing.Any) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
|
|
210
|
+
|
|
211
|
+
@typing.overload
|
|
212
|
+
def __call__(
|
|
213
|
+
self,
|
|
214
|
+
type: type[T],
|
|
215
|
+
*,
|
|
216
|
+
strict: bool = True,
|
|
217
|
+
) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
|
|
218
|
+
|
|
219
|
+
@typing.overload
|
|
220
|
+
def __call__(
|
|
221
|
+
self,
|
|
222
|
+
type: typing.Any,
|
|
223
|
+
*,
|
|
224
|
+
strict: bool = True,
|
|
225
|
+
) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
|
|
226
|
+
|
|
227
|
+
@contextmanager
|
|
228
|
+
def __call__(self, type=object, *, strict=True):
|
|
229
|
+
"""Context manager returns the `msgspec.json.Decoder` object with the `dec_hook`."""
|
|
230
|
+
|
|
231
|
+
dec_obj = msgspec.json.Decoder(
|
|
232
|
+
type=typing.Any if type is object else type,
|
|
233
|
+
strict=strict,
|
|
234
|
+
dec_hook=self.dec_hook,
|
|
235
|
+
)
|
|
236
|
+
yield dec_obj
|
|
237
|
+
|
|
238
|
+
def add_dec_hook(self, t: T): # type: ignore
|
|
239
|
+
def decorator(func: DecHook[T]) -> DecHook[T]:
|
|
240
|
+
return self.dec_hooks.setdefault(get_origin(t), func) # type: ignore
|
|
241
|
+
|
|
242
|
+
return decorator
|
|
243
|
+
|
|
244
|
+
def dec_hook(self, tp: type[typing.Any], obj: object) -> object:
|
|
245
|
+
origin_type = t if isinstance((t := get_origin(tp)), type) else type(t)
|
|
246
|
+
if origin_type not in self.dec_hooks:
|
|
247
|
+
raise TypeError(
|
|
248
|
+
f"Unknown type `{repr_type(origin_type)}`. You can implement decode hook for this type."
|
|
249
|
+
)
|
|
250
|
+
return self.dec_hooks[origin_type](tp, obj)
|
|
251
|
+
|
|
252
|
+
def convert(
|
|
253
|
+
self,
|
|
254
|
+
obj: object,
|
|
255
|
+
*,
|
|
256
|
+
type: type[T] = dict,
|
|
257
|
+
strict: bool = True,
|
|
258
|
+
from_attributes: bool = False,
|
|
259
|
+
builtin_types: typing.Iterable[type[typing.Any]] | None = None,
|
|
260
|
+
str_keys: bool = False,
|
|
261
|
+
) -> T:
|
|
262
|
+
return msgspec.convert(
|
|
263
|
+
obj,
|
|
264
|
+
type,
|
|
265
|
+
strict=strict,
|
|
266
|
+
from_attributes=from_attributes,
|
|
267
|
+
dec_hook=self.dec_hook,
|
|
268
|
+
builtin_types=builtin_types,
|
|
269
|
+
str_keys=str_keys,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
@typing.overload
|
|
273
|
+
def decode(self, buf: str | bytes) -> typing.Any: ...
|
|
274
|
+
|
|
275
|
+
@typing.overload
|
|
276
|
+
def decode(self, buf: str | bytes, *, type: type[T]) -> T: ...
|
|
277
|
+
|
|
278
|
+
@typing.overload
|
|
279
|
+
def decode(self, buf: str | bytes, *, type: typing.Any) -> typing.Any: ...
|
|
280
|
+
|
|
281
|
+
@typing.overload
|
|
282
|
+
def decode(
|
|
283
|
+
self,
|
|
284
|
+
buf: str | bytes,
|
|
285
|
+
*,
|
|
286
|
+
type: type[T],
|
|
287
|
+
strict: bool = True,
|
|
288
|
+
) -> T: ...
|
|
289
|
+
|
|
290
|
+
@typing.overload
|
|
291
|
+
def decode(
|
|
292
|
+
self,
|
|
293
|
+
buf: str | bytes,
|
|
294
|
+
*,
|
|
295
|
+
type: typing.Any,
|
|
296
|
+
strict: bool = True,
|
|
297
|
+
) -> typing.Any: ...
|
|
298
|
+
|
|
299
|
+
def decode(self, buf, *, type=object, strict=True):
|
|
300
|
+
return msgspec.json.decode(
|
|
301
|
+
buf,
|
|
302
|
+
type=typing.Any if type is object else type,
|
|
303
|
+
strict=strict,
|
|
304
|
+
dec_hook=self.dec_hook,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class Encoder:
|
|
309
|
+
"""Class `Encoder` for `msgspec` module with encode hooks for objects.
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
from datetime import datetime as dt
|
|
313
|
+
|
|
314
|
+
encoder = Encoder()
|
|
315
|
+
encoder.enc_hooks[dt] = lambda d: int(d.timestamp())
|
|
316
|
+
|
|
317
|
+
encoder.enc_hook(dt.now()) #> 1713354732
|
|
318
|
+
encoder.encode({'digit': Digit.ONE}) #> '{"digit":1}'
|
|
319
|
+
```
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
def __init__(self) -> None:
|
|
323
|
+
self.enc_hooks: dict[typing.Any, EncHook[typing.Any]] = {
|
|
324
|
+
fntypes.option.Some: lambda opt: opt.value,
|
|
325
|
+
fntypes.option.Nothing: lambda _: None,
|
|
326
|
+
Variative: lambda variative: variative.v,
|
|
327
|
+
datetime: lambda date: int(date.timestamp()),
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
def __repr__(self) -> str:
|
|
331
|
+
return "<{}: enc_hooks={!r}>".format(
|
|
332
|
+
self.__class__.__name__,
|
|
333
|
+
self.enc_hooks,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
@contextmanager
|
|
337
|
+
def __call__(
|
|
338
|
+
self,
|
|
339
|
+
*,
|
|
340
|
+
decimal_format: typing.Literal["string", "number"] = "string",
|
|
341
|
+
uuid_format: typing.Literal["canonical", "hex"] = "canonical",
|
|
342
|
+
order: typing.Literal[None, "deterministic", "sorted"] = None,
|
|
343
|
+
) -> typing.Generator[msgspec.json.Encoder, typing.Any, None]:
|
|
344
|
+
"""Context manager returns the `msgspec.json.Encoder` object with the `enc_hook`."""
|
|
345
|
+
|
|
346
|
+
enc_obj = msgspec.json.Encoder(enc_hook=self.enc_hook)
|
|
347
|
+
yield enc_obj
|
|
348
|
+
|
|
349
|
+
def add_dec_hook(self, t: type[T]):
|
|
350
|
+
def decorator(func: EncHook[T]) -> EncHook[T]:
|
|
351
|
+
encode_hook = self.enc_hooks.setdefault(get_origin(t), func)
|
|
352
|
+
return func if encode_hook is not func else encode_hook
|
|
353
|
+
|
|
354
|
+
return decorator
|
|
355
|
+
|
|
356
|
+
def enc_hook(self, obj: object) -> object:
|
|
357
|
+
origin_type = get_origin(obj.__class__)
|
|
358
|
+
if origin_type not in self.enc_hooks:
|
|
359
|
+
raise NotImplementedError(
|
|
360
|
+
f"Not implemented encode hook for object of type `{repr_type(origin_type)}`."
|
|
361
|
+
)
|
|
362
|
+
return self.enc_hooks[origin_type](obj)
|
|
363
|
+
|
|
364
|
+
@typing.overload
|
|
365
|
+
def encode(self, obj: typing.Any) -> str: ...
|
|
366
|
+
|
|
367
|
+
@typing.overload
|
|
368
|
+
def encode(self, obj: typing.Any, *, as_str: typing.Literal[True]) -> str: ...
|
|
369
|
+
|
|
370
|
+
@typing.overload
|
|
371
|
+
def encode(self, obj: typing.Any, *, as_str: typing.Literal[False]) -> bytes: ...
|
|
372
|
+
|
|
373
|
+
def encode(self, obj: typing.Any, *, as_str: bool = True) -> str | bytes:
|
|
374
|
+
buf = msgspec.json.encode(obj, enc_hook=self.enc_hook)
|
|
375
|
+
return buf.decode() if as_str else buf
|
|
376
|
+
|
|
377
|
+
def to_builtins(
|
|
378
|
+
self,
|
|
379
|
+
obj: typing.Any,
|
|
380
|
+
*,
|
|
381
|
+
str_keys: bool = False,
|
|
382
|
+
builtin_types: typing.Iterable[type[typing.Any]] | None = None,
|
|
383
|
+
order: typing.Literal["deterministic", "sorted"] | None = None,
|
|
384
|
+
) -> typing.Any:
|
|
385
|
+
return msgspec.to_builtins(
|
|
386
|
+
obj,
|
|
387
|
+
str_keys=str_keys,
|
|
388
|
+
builtin_types=builtin_types,
|
|
389
|
+
enc_hook=self.enc_hook,
|
|
390
|
+
order=order,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
decoder: typing.Final[Decoder] = Decoder()
|
|
395
|
+
encoder: typing.Final[Encoder] = Encoder()
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
__all__ = (
|
|
399
|
+
"Decoder",
|
|
400
|
+
"Encoder",
|
|
401
|
+
"Nothing",
|
|
402
|
+
"Option",
|
|
403
|
+
"datetime",
|
|
404
|
+
"decoder",
|
|
405
|
+
"encoder",
|
|
406
|
+
"get_class_annotations",
|
|
407
|
+
"get_type_hints",
|
|
408
|
+
"msgspec_convert",
|
|
409
|
+
"msgspec_to_builtins",
|
|
410
|
+
)
|