telegrinder 0.3.3.post1__py3-none-any.whl → 0.3.4.post1__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.

Files changed (165) hide show
  1. telegrinder/__init__.py +144 -144
  2. telegrinder/api/__init__.py +8 -8
  3. telegrinder/api/api.py +93 -93
  4. telegrinder/api/error.py +16 -16
  5. telegrinder/api/response.py +20 -20
  6. telegrinder/api/token.py +36 -36
  7. telegrinder/bot/__init__.py +66 -66
  8. telegrinder/bot/bot.py +76 -76
  9. telegrinder/bot/cute_types/__init__.py +17 -17
  10. telegrinder/bot/cute_types/base.py +258 -258
  11. telegrinder/bot/cute_types/callback_query.py +385 -385
  12. telegrinder/bot/cute_types/chat_join_request.py +61 -61
  13. telegrinder/bot/cute_types/chat_member_updated.py +160 -160
  14. telegrinder/bot/cute_types/inline_query.py +43 -43
  15. telegrinder/bot/cute_types/message.py +2637 -2637
  16. telegrinder/bot/cute_types/update.py +104 -109
  17. telegrinder/bot/cute_types/utils.py +95 -95
  18. telegrinder/bot/dispatch/__init__.py +55 -55
  19. telegrinder/bot/dispatch/abc.py +77 -77
  20. telegrinder/bot/dispatch/context.py +98 -98
  21. telegrinder/bot/dispatch/dispatch.py +202 -202
  22. telegrinder/bot/dispatch/handler/__init__.py +13 -13
  23. telegrinder/bot/dispatch/handler/abc.py +24 -24
  24. telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
  25. telegrinder/bot/dispatch/handler/base.py +57 -57
  26. telegrinder/bot/dispatch/handler/document_reply.py +44 -44
  27. telegrinder/bot/dispatch/handler/func.py +135 -135
  28. telegrinder/bot/dispatch/handler/media_group_reply.py +43 -43
  29. telegrinder/bot/dispatch/handler/message_reply.py +36 -36
  30. telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
  31. telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
  32. telegrinder/bot/dispatch/handler/video_reply.py +44 -44
  33. telegrinder/bot/dispatch/middleware/__init__.py +3 -3
  34. telegrinder/bot/dispatch/middleware/abc.py +22 -16
  35. telegrinder/bot/dispatch/process.py +157 -132
  36. telegrinder/bot/dispatch/return_manager/__init__.py +13 -13
  37. telegrinder/bot/dispatch/return_manager/abc.py +108 -108
  38. telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
  39. telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
  40. telegrinder/bot/dispatch/return_manager/message.py +36 -36
  41. telegrinder/bot/dispatch/view/__init__.py +13 -13
  42. telegrinder/bot/dispatch/view/abc.py +41 -41
  43. telegrinder/bot/dispatch/view/base.py +200 -200
  44. telegrinder/bot/dispatch/view/box.py +129 -129
  45. telegrinder/bot/dispatch/view/callback_query.py +17 -17
  46. telegrinder/bot/dispatch/view/chat_join_request.py +16 -16
  47. telegrinder/bot/dispatch/view/chat_member.py +39 -39
  48. telegrinder/bot/dispatch/view/inline_query.py +17 -17
  49. telegrinder/bot/dispatch/view/message.py +44 -44
  50. telegrinder/bot/dispatch/view/raw.py +114 -114
  51. telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
  52. telegrinder/bot/dispatch/waiter_machine/actions.py +13 -13
  53. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
  54. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
  55. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +57 -57
  56. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
  57. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
  58. telegrinder/bot/dispatch/waiter_machine/machine.py +172 -167
  59. telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
  60. telegrinder/bot/dispatch/waiter_machine/short_state.py +68 -68
  61. telegrinder/bot/polling/__init__.py +4 -4
  62. telegrinder/bot/polling/abc.py +25 -25
  63. telegrinder/bot/polling/polling.py +131 -131
  64. telegrinder/bot/rules/__init__.py +62 -62
  65. telegrinder/bot/rules/abc.py +213 -213
  66. telegrinder/bot/rules/adapter/__init__.py +12 -9
  67. telegrinder/bot/rules/adapter/abc.py +31 -29
  68. telegrinder/bot/rules/adapter/errors.py +5 -5
  69. telegrinder/bot/rules/adapter/event.py +65 -67
  70. telegrinder/bot/rules/adapter/node.py +48 -48
  71. telegrinder/bot/rules/adapter/raw_event.py +27 -0
  72. telegrinder/bot/rules/adapter/raw_update.py +30 -30
  73. telegrinder/bot/rules/callback_data.py +170 -170
  74. telegrinder/bot/rules/chat_join.py +46 -46
  75. telegrinder/bot/rules/command.py +126 -126
  76. telegrinder/bot/rules/enum_text.py +36 -36
  77. telegrinder/bot/rules/func.py +26 -26
  78. telegrinder/bot/rules/fuzzy.py +24 -24
  79. telegrinder/bot/rules/inline.py +60 -60
  80. telegrinder/bot/rules/integer.py +20 -20
  81. telegrinder/bot/rules/is_from.py +127 -127
  82. telegrinder/bot/rules/markup.py +43 -43
  83. telegrinder/bot/rules/mention.py +14 -14
  84. telegrinder/bot/rules/message.py +17 -17
  85. telegrinder/bot/rules/message_entities.py +35 -35
  86. telegrinder/bot/rules/node.py +27 -27
  87. telegrinder/bot/rules/regex.py +37 -37
  88. telegrinder/bot/rules/rule_enum.py +72 -72
  89. telegrinder/bot/rules/start.py +42 -42
  90. telegrinder/bot/rules/state.py +37 -37
  91. telegrinder/bot/rules/text.py +33 -33
  92. telegrinder/bot/rules/update.py +15 -15
  93. telegrinder/bot/scenario/__init__.py +5 -5
  94. telegrinder/bot/scenario/abc.py +19 -19
  95. telegrinder/bot/scenario/checkbox.py +176 -167
  96. telegrinder/bot/scenario/choice.py +51 -46
  97. telegrinder/client/__init__.py +4 -4
  98. telegrinder/client/abc.py +75 -75
  99. telegrinder/client/aiohttp.py +130 -130
  100. telegrinder/model.py +320 -295
  101. telegrinder/modules.py +237 -237
  102. telegrinder/msgspec_json.py +14 -14
  103. telegrinder/msgspec_utils.py +410 -410
  104. telegrinder/node/__init__.py +0 -0
  105. telegrinder/node/attachment.py +87 -87
  106. telegrinder/node/base.py +166 -166
  107. telegrinder/node/callback_query.py +53 -53
  108. telegrinder/node/command.py +33 -33
  109. telegrinder/node/composer.py +198 -198
  110. telegrinder/node/container.py +27 -27
  111. telegrinder/node/event.py +65 -65
  112. telegrinder/node/me.py +16 -16
  113. telegrinder/node/message.py +14 -14
  114. telegrinder/node/polymorphic.py +48 -48
  115. telegrinder/node/rule.py +76 -76
  116. telegrinder/node/scope.py +38 -38
  117. telegrinder/node/source.py +71 -71
  118. telegrinder/node/text.py +41 -41
  119. telegrinder/node/tools/__init__.py +3 -3
  120. telegrinder/node/tools/generator.py +40 -40
  121. telegrinder/node/update.py +15 -15
  122. telegrinder/rules.py +0 -0
  123. telegrinder/tools/__init__.py +74 -74
  124. telegrinder/tools/buttons.py +79 -79
  125. telegrinder/tools/error_handler/__init__.py +7 -7
  126. telegrinder/tools/error_handler/abc.py +33 -33
  127. telegrinder/tools/error_handler/error.py +9 -9
  128. telegrinder/tools/error_handler/error_handler.py +193 -193
  129. telegrinder/tools/formatting/__init__.py +46 -46
  130. telegrinder/tools/formatting/html.py +283 -283
  131. telegrinder/tools/formatting/links.py +33 -33
  132. telegrinder/tools/formatting/spec_html_formats.py +111 -111
  133. telegrinder/tools/functional.py +12 -12
  134. telegrinder/tools/global_context/__init__.py +7 -7
  135. telegrinder/tools/global_context/abc.py +63 -63
  136. telegrinder/tools/global_context/global_context.py +412 -412
  137. telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
  138. telegrinder/tools/i18n/__init__.py +7 -7
  139. telegrinder/tools/i18n/abc.py +30 -30
  140. telegrinder/tools/i18n/middleware/__init__.py +3 -3
  141. telegrinder/tools/i18n/middleware/abc.py +25 -25
  142. telegrinder/tools/i18n/simple.py +43 -43
  143. telegrinder/tools/kb_set/__init__.py +4 -4
  144. telegrinder/tools/kb_set/base.py +15 -15
  145. telegrinder/tools/kb_set/yaml.py +63 -63
  146. telegrinder/tools/keyboard.py +132 -132
  147. telegrinder/tools/limited_dict.py +37 -37
  148. telegrinder/tools/loop_wrapper/__init__.py +4 -4
  149. telegrinder/tools/loop_wrapper/abc.py +15 -15
  150. telegrinder/tools/loop_wrapper/loop_wrapper.py +224 -224
  151. telegrinder/tools/magic.py +157 -157
  152. telegrinder/tools/parse_mode.py +6 -6
  153. telegrinder/tools/state_storage/__init__.py +4 -4
  154. telegrinder/tools/state_storage/abc.py +35 -35
  155. telegrinder/tools/state_storage/memory.py +25 -25
  156. telegrinder/types/__init__.py +260 -260
  157. telegrinder/types/enums.py +701 -701
  158. telegrinder/types/methods.py +4633 -4633
  159. telegrinder/types/objects.py +6950 -8561
  160. telegrinder/verification_utils.py +32 -32
  161. {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/LICENSE +22 -22
  162. {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/METADATA +1 -1
  163. telegrinder-0.3.4.post1.dist-info/RECORD +165 -0
  164. telegrinder-0.3.3.post1.dist-info/RECORD +0 -164
  165. {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/WHEEL +0 -0
@@ -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: type[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_enc_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 or isinstance(obj, fntypes.Nothing):
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: type[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_enc_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
+ )