telegrinder 0.3.4.post1__py3-none-any.whl → 0.4.0__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 (169) hide show
  1. telegrinder/__init__.py +30 -31
  2. telegrinder/api/__init__.py +2 -1
  3. telegrinder/api/api.py +28 -20
  4. telegrinder/api/error.py +8 -4
  5. telegrinder/api/response.py +2 -2
  6. telegrinder/api/token.py +2 -2
  7. telegrinder/bot/__init__.py +6 -0
  8. telegrinder/bot/bot.py +38 -31
  9. telegrinder/bot/cute_types/__init__.py +2 -0
  10. telegrinder/bot/cute_types/base.py +54 -128
  11. telegrinder/bot/cute_types/callback_query.py +76 -61
  12. telegrinder/bot/cute_types/chat_join_request.py +4 -3
  13. telegrinder/bot/cute_types/chat_member_updated.py +28 -31
  14. telegrinder/bot/cute_types/inline_query.py +5 -4
  15. telegrinder/bot/cute_types/message.py +555 -602
  16. telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
  17. telegrinder/bot/cute_types/update.py +20 -12
  18. telegrinder/bot/cute_types/utils.py +3 -36
  19. telegrinder/bot/dispatch/__init__.py +4 -0
  20. telegrinder/bot/dispatch/abc.py +8 -9
  21. telegrinder/bot/dispatch/context.py +5 -7
  22. telegrinder/bot/dispatch/dispatch.py +85 -33
  23. telegrinder/bot/dispatch/handler/abc.py +5 -6
  24. telegrinder/bot/dispatch/handler/audio_reply.py +2 -2
  25. telegrinder/bot/dispatch/handler/base.py +3 -3
  26. telegrinder/bot/dispatch/handler/document_reply.py +2 -2
  27. telegrinder/bot/dispatch/handler/func.py +36 -42
  28. telegrinder/bot/dispatch/handler/media_group_reply.py +5 -4
  29. telegrinder/bot/dispatch/handler/message_reply.py +2 -2
  30. telegrinder/bot/dispatch/handler/photo_reply.py +2 -2
  31. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -2
  32. telegrinder/bot/dispatch/handler/video_reply.py +2 -2
  33. telegrinder/bot/dispatch/middleware/abc.py +83 -8
  34. telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
  35. telegrinder/bot/dispatch/process.py +44 -50
  36. telegrinder/bot/dispatch/return_manager/__init__.py +2 -0
  37. telegrinder/bot/dispatch/return_manager/abc.py +6 -10
  38. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
  39. telegrinder/bot/dispatch/view/__init__.py +2 -0
  40. telegrinder/bot/dispatch/view/abc.py +10 -6
  41. telegrinder/bot/dispatch/view/base.py +81 -50
  42. telegrinder/bot/dispatch/view/box.py +20 -9
  43. telegrinder/bot/dispatch/view/callback_query.py +3 -4
  44. telegrinder/bot/dispatch/view/chat_join_request.py +2 -7
  45. telegrinder/bot/dispatch/view/chat_member.py +3 -5
  46. telegrinder/bot/dispatch/view/inline_query.py +3 -4
  47. telegrinder/bot/dispatch/view/message.py +3 -4
  48. telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
  49. telegrinder/bot/dispatch/view/raw.py +42 -40
  50. telegrinder/bot/dispatch/waiter_machine/actions.py +5 -4
  51. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +0 -0
  52. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +0 -0
  53. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +9 -7
  54. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  55. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +3 -2
  56. telegrinder/bot/dispatch/waiter_machine/machine.py +113 -34
  57. telegrinder/bot/dispatch/waiter_machine/middleware.py +15 -10
  58. telegrinder/bot/dispatch/waiter_machine/short_state.py +7 -18
  59. telegrinder/bot/polling/polling.py +62 -54
  60. telegrinder/bot/rules/__init__.py +24 -1
  61. telegrinder/bot/rules/abc.py +17 -10
  62. telegrinder/bot/rules/callback_data.py +20 -61
  63. telegrinder/bot/rules/chat_join.py +6 -4
  64. telegrinder/bot/rules/command.py +4 -4
  65. telegrinder/bot/rules/enum_text.py +1 -4
  66. telegrinder/bot/rules/func.py +5 -3
  67. telegrinder/bot/rules/fuzzy.py +1 -1
  68. telegrinder/bot/rules/id.py +24 -0
  69. telegrinder/bot/rules/inline.py +6 -4
  70. telegrinder/bot/rules/integer.py +2 -1
  71. telegrinder/bot/rules/logic.py +18 -0
  72. telegrinder/bot/rules/markup.py +5 -6
  73. telegrinder/bot/rules/message.py +2 -4
  74. telegrinder/bot/rules/message_entities.py +1 -3
  75. telegrinder/bot/rules/node.py +15 -9
  76. telegrinder/bot/rules/payload.py +81 -0
  77. telegrinder/bot/rules/payment_invoice.py +29 -0
  78. telegrinder/bot/rules/regex.py +5 -6
  79. telegrinder/bot/rules/state.py +1 -3
  80. telegrinder/bot/rules/text.py +10 -5
  81. telegrinder/bot/rules/update.py +0 -0
  82. telegrinder/bot/scenario/abc.py +2 -4
  83. telegrinder/bot/scenario/checkbox.py +12 -14
  84. telegrinder/bot/scenario/choice.py +6 -9
  85. telegrinder/client/__init__.py +9 -1
  86. telegrinder/client/abc.py +35 -10
  87. telegrinder/client/aiohttp.py +28 -24
  88. telegrinder/client/form_data.py +31 -0
  89. telegrinder/client/sonic.py +212 -0
  90. telegrinder/model.py +38 -145
  91. telegrinder/modules.py +3 -1
  92. telegrinder/msgspec_utils.py +136 -68
  93. telegrinder/node/__init__.py +74 -13
  94. telegrinder/node/attachment.py +92 -16
  95. telegrinder/node/base.py +196 -68
  96. telegrinder/node/callback_query.py +17 -16
  97. telegrinder/node/command.py +3 -2
  98. telegrinder/node/composer.py +40 -75
  99. telegrinder/node/container.py +13 -7
  100. telegrinder/node/either.py +82 -0
  101. telegrinder/node/event.py +20 -31
  102. telegrinder/node/file.py +51 -0
  103. telegrinder/node/me.py +4 -5
  104. telegrinder/node/payload.py +78 -0
  105. telegrinder/node/polymorphic.py +27 -8
  106. telegrinder/node/rule.py +2 -6
  107. telegrinder/node/scope.py +4 -6
  108. telegrinder/node/source.py +37 -21
  109. telegrinder/node/text.py +20 -8
  110. telegrinder/node/tools/generator.py +7 -11
  111. telegrinder/py.typed +0 -0
  112. telegrinder/rules.py +0 -61
  113. telegrinder/tools/__init__.py +97 -38
  114. telegrinder/tools/adapter/__init__.py +19 -0
  115. telegrinder/tools/adapter/abc.py +49 -0
  116. telegrinder/tools/adapter/dataclass.py +56 -0
  117. telegrinder/{bot/rules → tools}/adapter/event.py +8 -10
  118. telegrinder/{bot/rules → tools}/adapter/node.py +8 -10
  119. telegrinder/{bot/rules → tools}/adapter/raw_event.py +2 -2
  120. telegrinder/{bot/rules → tools}/adapter/raw_update.py +2 -2
  121. telegrinder/tools/buttons.py +52 -26
  122. telegrinder/tools/callback_data_serilization/__init__.py +5 -0
  123. telegrinder/tools/callback_data_serilization/abc.py +51 -0
  124. telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
  125. telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
  126. telegrinder/tools/error_handler/abc.py +4 -7
  127. telegrinder/tools/error_handler/error.py +0 -0
  128. telegrinder/tools/error_handler/error_handler.py +34 -48
  129. telegrinder/tools/formatting/__init__.py +57 -37
  130. telegrinder/tools/formatting/deep_links.py +541 -0
  131. telegrinder/tools/formatting/{html.py → html_formatter.py} +51 -79
  132. telegrinder/tools/formatting/spec_html_formats.py +14 -60
  133. telegrinder/tools/functional.py +1 -5
  134. telegrinder/tools/global_context/global_context.py +26 -51
  135. telegrinder/tools/global_context/telegrinder_ctx.py +3 -3
  136. telegrinder/tools/i18n/abc.py +0 -0
  137. telegrinder/tools/i18n/middleware/abc.py +3 -6
  138. telegrinder/tools/input_file_directory.py +30 -0
  139. telegrinder/tools/keyboard.py +9 -9
  140. telegrinder/tools/lifespan.py +105 -0
  141. telegrinder/tools/limited_dict.py +5 -10
  142. telegrinder/tools/loop_wrapper/abc.py +7 -2
  143. telegrinder/tools/loop_wrapper/loop_wrapper.py +40 -95
  144. telegrinder/tools/magic.py +184 -34
  145. telegrinder/tools/state_storage/__init__.py +0 -0
  146. telegrinder/tools/state_storage/abc.py +5 -9
  147. telegrinder/tools/state_storage/memory.py +1 -1
  148. telegrinder/tools/strings.py +13 -0
  149. telegrinder/types/__init__.py +8 -0
  150. telegrinder/types/enums.py +31 -21
  151. telegrinder/types/input_file.py +51 -0
  152. telegrinder/types/methods.py +531 -109
  153. telegrinder/types/objects.py +934 -826
  154. telegrinder/verification_utils.py +0 -2
  155. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +2 -2
  156. telegrinder-0.4.0.dist-info/METADATA +144 -0
  157. telegrinder-0.4.0.dist-info/RECORD +182 -0
  158. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
  159. telegrinder/bot/rules/adapter/__init__.py +0 -17
  160. telegrinder/bot/rules/adapter/abc.py +0 -31
  161. telegrinder/node/message.py +0 -14
  162. telegrinder/node/update.py +0 -15
  163. telegrinder/tools/formatting/links.py +0 -38
  164. telegrinder/tools/kb_set/__init__.py +0 -4
  165. telegrinder/tools/kb_set/base.py +0 -15
  166. telegrinder/tools/kb_set/yaml.py +0 -63
  167. telegrinder-0.3.4.post1.dist-info/METADATA +0 -110
  168. telegrinder-0.3.4.post1.dist-info/RECORD +0 -165
  169. /telegrinder/{bot/rules → tools}/adapter/errors.py +0 -0
@@ -1,48 +1,40 @@
1
- import dataclasses
2
1
  import typing
3
2
  from contextlib import contextmanager
4
3
 
5
4
  import fntypes.option
6
5
  import fntypes.result
7
6
  import msgspec
8
- from fntypes.co import Error, Ok, Result, Variative
7
+ from fntypes.co import Error, Ok, Variative
9
8
 
10
9
  if typing.TYPE_CHECKING:
11
10
  from datetime import datetime
12
11
 
13
12
  from fntypes.option import Option
14
13
 
15
- def get_class_annotations(obj: typing.Any) -> dict[str, type[typing.Any]]: ...
14
+ def get_class_annotations(obj: typing.Any, /) -> dict[str, typing.Any]: ...
16
15
 
17
- def get_type_hints(obj: typing.Any) -> dict[str, type[typing.Any]]: ...
16
+ def get_type_hints(obj: typing.Any, /) -> dict[str, typing.Any]: ...
18
17
 
19
18
  else:
20
19
  from datetime import datetime as dt
21
20
 
22
21
  from msgspec._utils import get_class_annotations, get_type_hints
23
22
 
24
- Value = typing.TypeVar("Value")
25
- Err = typing.TypeVar("Err")
26
-
27
23
  datetime = type("datetime", (dt,), {})
28
24
 
29
25
  class OptionMeta(type):
30
26
  def __instancecheck__(cls, __instance: typing.Any) -> bool:
31
- return isinstance(__instance, fntypes.option.Some | fntypes.option.Nothing)
27
+ return isinstance(__instance, (fntypes.option.Some | fntypes.option.Nothing, msgspec.UnsetType))
32
28
 
33
- class Option(typing.Generic[Value], metaclass=OptionMeta):
29
+ class Option[Value](metaclass=OptionMeta):
34
30
  pass
35
31
 
36
32
 
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]
33
+ type DecHook[T] = typing.Callable[typing.Concatenate[type[T], typing.Any, ...], typing.Any]
34
+ type EncHook[T] = typing.Callable[typing.Concatenate[T, ...], typing.Any]
41
35
 
42
- Nothing: typing.Final[fntypes.option.Nothing] = fntypes.option.Nothing()
43
36
 
44
-
45
- def get_origin(t: type[T]) -> type[T]:
37
+ def get_origin[T](t: type[T]) -> type[T]:
46
38
  return typing.cast(T, typing.get_origin(t)) or t
47
39
 
48
40
 
@@ -60,6 +52,14 @@ def is_common_type(type_: typing.Any) -> typing.TypeGuard[type[typing.Any]]:
60
52
  )
61
53
 
62
54
 
55
+ def struct_as_dict(struct: msgspec.Struct, /) -> dict[str, typing.Any]:
56
+ return {
57
+ k: v
58
+ for k, v in msgspec.structs.asdict(struct).items()
59
+ if not isinstance(v, msgspec.UnsetType | type(None) | fntypes.option.Nothing)
60
+ }
61
+
62
+
63
63
  def type_check(obj: typing.Any, t: typing.Any) -> bool:
64
64
  return (
65
65
  isinstance(obj, t)
@@ -70,7 +70,7 @@ def type_check(obj: typing.Any, t: typing.Any) -> bool:
70
70
  )
71
71
 
72
72
 
73
- def msgspec_convert(obj: typing.Any, t: type[T]) -> Result[T, str]:
73
+ def msgspec_convert[T](obj: typing.Any, t: type[T]) -> fntypes.result.Result[T, str]:
74
74
  try:
75
75
  return Ok(decoder.convert(obj, type=t, strict=True))
76
76
  except msgspec.ValidationError:
@@ -95,9 +95,15 @@ def msgspec_to_builtins(
95
95
  return Error(exc)
96
96
 
97
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
98
+ def option_dec_hook(
99
+ tp: type[Option[typing.Any]],
100
+ obj: typing.Any,
101
+ ) -> fntypes.option.Option[typing.Any] | msgspec.UnsetType:
102
+ if obj is msgspec.UNSET:
103
+ return obj
104
+
105
+ if obj is None or isinstance(obj, fntypes.option.Nothing):
106
+ return fntypes.option.Nothing()
101
107
 
102
108
  (value_type,) = typing.get_args(tp) or (typing.Any,)
103
109
  orig_value_type = typing.get_origin(value_type) or value_type
@@ -156,11 +162,6 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
156
162
  )
157
163
 
158
164
 
159
- @typing.runtime_checkable
160
- class DataclassInstance(typing.Protocol):
161
- __dataclass_fields__: typing.ClassVar[dict[str, dataclasses.Field[typing.Any]]]
162
-
163
-
164
165
  class Decoder:
165
166
  """Class `Decoder` for `msgspec` module with decode hook
166
167
  for objects with the specified type.
@@ -175,7 +176,7 @@ class Decoder:
175
176
  TWO = 2
176
177
  THREE = 3
177
178
 
178
- decoder = Encoder()
179
+ decoder = Decoder()
179
180
  decoder.dec_hooks[dt] = lambda t, timestamp: t.fromtimestamp(timestamp)
180
181
 
181
182
  decoder.dec_hook(dt, 1713354732) #> datetime.datetime(2024, 4, 17, 14, 52, 12)
@@ -203,17 +204,26 @@ class Decoder:
203
204
  )
204
205
 
205
206
  @typing.overload
206
- def __call__(self, type: type[T]) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
207
+ def __call__[T](
208
+ self,
209
+ type: type[T],
210
+ context: dict[str, typing.Any] | None = None,
211
+ ) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
207
212
 
208
213
  @typing.overload
209
- def __call__(self, type: typing.Any) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
214
+ def __call__(
215
+ self,
216
+ type: typing.Any,
217
+ context: dict[str, typing.Any] | None = None,
218
+ ) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
210
219
 
211
220
  @typing.overload
212
- def __call__(
221
+ def __call__[T](
213
222
  self,
214
223
  type: type[T],
215
224
  *,
216
225
  strict: bool = True,
226
+ context: dict[str, typing.Any] | None = None,
217
227
  ) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
218
228
 
219
229
  @typing.overload
@@ -222,34 +232,41 @@ class Decoder:
222
232
  type: typing.Any,
223
233
  *,
224
234
  strict: bool = True,
235
+ context: dict[str, typing.Any] | None = None,
225
236
  ) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
226
237
 
227
238
  @contextmanager
228
- def __call__(self, type=object, *, strict=True):
229
- """Context manager returns the `msgspec.json.Decoder` object with the `dec_hook`."""
230
-
239
+ def __call__(self, type=object, *, strict=True, context=None):
240
+ """Context manager returns an `msgspec.json.Decoder` object with the `dec_hook`."""
231
241
  dec_obj = msgspec.json.Decoder(
232
242
  type=typing.Any if type is object else type,
233
243
  strict=strict,
234
- dec_hook=self.dec_hook,
244
+ dec_hook=self.dec_hook(context),
235
245
  )
236
246
  yield dec_obj
237
247
 
238
- def add_dec_hook(self, t: type[T]): # type: ignore
248
+ def add_dec_hook[T](self, t: type[T], /):
239
249
  def decorator(func: DecHook[T]) -> DecHook[T]:
240
- return self.dec_hooks.setdefault(get_origin(t), func) # type: ignore
250
+ return self.dec_hooks.setdefault(get_origin(t), func)
241
251
 
242
252
  return decorator
243
253
 
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)
254
+ def dec_hook(self, context: dict[str, typing.Any] | None = None):
255
+ from telegrinder.tools.magic import magic_bundle
256
+
257
+ def inner(tp: type[typing.Any], obj: object) -> typing.Any:
258
+ origin_type = t if isinstance((t := get_origin(tp)), type) else type(t)
259
+ if origin_type not in self.dec_hooks:
260
+ raise TypeError(
261
+ f"Unknown type `{repr_type(origin_type)}`. You can implement decode hook for this type."
262
+ )
263
+ dec_hook_func = self.dec_hooks[origin_type]
264
+ kwargs = magic_bundle(dec_hook_func, context or {}, start_idx=2, bundle_ctx=False)
265
+ return dec_hook_func(tp, obj, **kwargs)
266
+
267
+ return inner
251
268
 
252
- def convert(
269
+ def convert[T](
253
270
  self,
254
271
  obj: object,
255
272
  *,
@@ -258,33 +275,52 @@ class Decoder:
258
275
  from_attributes: bool = False,
259
276
  builtin_types: typing.Iterable[type[typing.Any]] | None = None,
260
277
  str_keys: bool = False,
278
+ context: dict[str, typing.Any] | None = None,
261
279
  ) -> T:
262
280
  return msgspec.convert(
263
281
  obj,
264
282
  type,
265
283
  strict=strict,
266
284
  from_attributes=from_attributes,
267
- dec_hook=self.dec_hook,
285
+ dec_hook=self.dec_hook(context),
268
286
  builtin_types=builtin_types,
269
287
  str_keys=str_keys,
270
288
  )
271
289
 
272
290
  @typing.overload
273
- def decode(self, buf: str | bytes) -> typing.Any: ...
291
+ def decode(
292
+ self,
293
+ buf: str | bytes,
294
+ *,
295
+ context: dict[str, typing.Any] | None = None,
296
+ ) -> typing.Any: ...
274
297
 
275
298
  @typing.overload
276
- def decode(self, buf: str | bytes, *, type: type[T]) -> T: ...
299
+ def decode[T](
300
+ self,
301
+ buf: str | bytes,
302
+ *,
303
+ type: type[T],
304
+ context: dict[str, typing.Any] | None = None,
305
+ ) -> T: ...
277
306
 
278
307
  @typing.overload
279
- def decode(self, buf: str | bytes, *, type: typing.Any) -> typing.Any: ...
308
+ def decode(
309
+ self,
310
+ buf: str | bytes,
311
+ *,
312
+ type: typing.Any,
313
+ context: dict[str, typing.Any] | None = None,
314
+ ) -> typing.Any: ...
280
315
 
281
316
  @typing.overload
282
- def decode(
317
+ def decode[T](
283
318
  self,
284
319
  buf: str | bytes,
285
320
  *,
286
321
  type: type[T],
287
322
  strict: bool = True,
323
+ context: dict[str, typing.Any] | None = None,
288
324
  ) -> T: ...
289
325
 
290
326
  @typing.overload
@@ -294,14 +330,15 @@ class Decoder:
294
330
  *,
295
331
  type: typing.Any,
296
332
  strict: bool = True,
333
+ context: dict[str, typing.Any] | None = None,
297
334
  ) -> typing.Any: ...
298
335
 
299
- def decode(self, buf, *, type=object, strict=True):
336
+ def decode(self, buf, *, type=object, strict=True, context=None):
300
337
  return msgspec.json.decode(
301
338
  buf,
302
339
  type=typing.Any if type is object else type,
303
340
  strict=strict,
304
- dec_hook=self.dec_hook,
341
+ dec_hook=self.dec_hook(context),
305
342
  )
306
343
 
307
344
 
@@ -340,38 +377,68 @@ class Encoder:
340
377
  decimal_format: typing.Literal["string", "number"] = "string",
341
378
  uuid_format: typing.Literal["canonical", "hex"] = "canonical",
342
379
  order: typing.Literal[None, "deterministic", "sorted"] = None,
380
+ context: dict[str, typing.Any] | None = None,
343
381
  ) -> 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)
382
+ """Context manager returns an `msgspec.json.Encoder` object with the `enc_hook`."""
383
+ enc_obj = msgspec.json.Encoder(enc_hook=self.enc_hook(context))
347
384
  yield enc_obj
348
385
 
349
- def add_enc_hook(self, t: type[T]):
386
+ def add_enc_hook[T](self, t: type[T], /):
350
387
  def decorator(func: EncHook[T]) -> EncHook[T]:
351
388
  encode_hook = self.enc_hooks.setdefault(get_origin(t), func)
352
389
  return func if encode_hook is not func else encode_hook
353
390
 
354
391
  return decorator
355
392
 
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)
393
+ def enc_hook(self, context: dict[str, typing.Any] | None = None):
394
+ from telegrinder.tools.magic import magic_bundle
395
+
396
+ def inner(obj: typing.Any) -> typing.Any:
397
+ origin_type = get_origin(obj.__class__)
398
+ if origin_type not in self.enc_hooks:
399
+ raise NotImplementedError(
400
+ f"Not implemented encode hook for object of type `{repr_type(origin_type)}`.",
401
+ )
402
+ enc_hook_func = self.enc_hooks[origin_type]
403
+ kwargs = magic_bundle(enc_hook_func, context or {}, start_idx=1, bundle_ctx=False)
404
+ return enc_hook_func(obj, **kwargs)
405
+
406
+ return inner
363
407
 
364
408
  @typing.overload
365
- def encode(self, obj: typing.Any) -> str: ...
409
+ def encode(
410
+ self,
411
+ obj: typing.Any,
412
+ *,
413
+ context: dict[str, typing.Any] | None = None,
414
+ ) -> str: ...
366
415
 
367
416
  @typing.overload
368
- def encode(self, obj: typing.Any, *, as_str: typing.Literal[True]) -> str: ...
417
+ def encode(
418
+ self,
419
+ obj: typing.Any,
420
+ *,
421
+ as_str: typing.Literal[True],
422
+ context: dict[str, typing.Any] | None = None,
423
+ ) -> str: ...
369
424
 
370
425
  @typing.overload
371
- def encode(self, obj: typing.Any, *, as_str: typing.Literal[False]) -> bytes: ...
426
+ def encode(
427
+ self,
428
+ obj: typing.Any,
429
+ *,
430
+ as_str: typing.Literal[False],
431
+ context: dict[str, typing.Any] | None = None,
432
+ ) -> bytes: ...
372
433
 
373
- def encode(self, obj: typing.Any, *, as_str: bool = True) -> str | bytes:
374
- buf = msgspec.json.encode(obj, enc_hook=self.enc_hook)
434
+ def encode(
435
+ self,
436
+ obj: typing.Any,
437
+ *,
438
+ as_str: bool = True,
439
+ context: dict[str, typing.Any] | None = None,
440
+ ) -> str | bytes:
441
+ buf = msgspec.json.encode(obj, enc_hook=self.enc_hook(context))
375
442
  return buf.decode() if as_str else buf
376
443
 
377
444
  def to_builtins(
@@ -381,12 +448,13 @@ class Encoder:
381
448
  str_keys: bool = False,
382
449
  builtin_types: typing.Iterable[type[typing.Any]] | None = None,
383
450
  order: typing.Literal["deterministic", "sorted"] | None = None,
451
+ context: dict[str, typing.Any] | None = None,
384
452
  ) -> typing.Any:
385
453
  return msgspec.to_builtins(
386
454
  obj,
387
455
  str_keys=str_keys,
388
456
  builtin_types=builtin_types,
389
- enc_hook=self.enc_hook,
457
+ enc_hook=self.enc_hook(context),
390
458
  order=order,
391
459
  )
392
460
 
@@ -398,7 +466,6 @@ encoder: typing.Final[Encoder] = Encoder()
398
466
  __all__ = (
399
467
  "Decoder",
400
468
  "Encoder",
401
- "Nothing",
402
469
  "Option",
403
470
  "datetime",
404
471
  "decoder",
@@ -407,4 +474,5 @@ __all__ = (
407
474
  "get_type_hints",
408
475
  "msgspec_convert",
409
476
  "msgspec_to_builtins",
477
+ "struct_as_dict",
410
478
  )
@@ -1,54 +1,113 @@
1
- from .attachment import Attachment, Audio, Photo, Video
2
- from .base import ComposeError, DataNode, Name, Node, ScalarNode, is_node
3
- from .callback_query import CallbackQueryData, CallbackQueryNode, Field
1
+ from .attachment import (
2
+ Animation,
3
+ Attachment,
4
+ Audio,
5
+ Document,
6
+ Photo,
7
+ SuccessfulPayment,
8
+ Video,
9
+ VideoNote,
10
+ Voice,
11
+ )
12
+ from .base import (
13
+ Composable,
14
+ ComposeError,
15
+ DataNode,
16
+ FactoryNode,
17
+ GlobalNode,
18
+ IsNode,
19
+ Name,
20
+ Node,
21
+ NodeComposeFunction,
22
+ NodeImpersonation,
23
+ NodeProto,
24
+ NodeType,
25
+ as_node,
26
+ is_node,
27
+ scalar_node,
28
+ unwrap_node,
29
+ )
30
+ from .callback_query import (
31
+ CallbackQueryData,
32
+ CallbackQueryDataJson,
33
+ Field,
34
+ )
4
35
  from .command import CommandInfo
5
- from .composer import Composition, NodeCollection, NodeSession, compose_node, compose_nodes
36
+ from .composer import NodeCollection, NodeSession, compose_node, compose_nodes
6
37
  from .container import ContainerNode
38
+ from .either import Either, Optional
7
39
  from .event import EventNode
40
+ from .file import File, FileId
8
41
  from .me import Me
9
- from .message import MessageNode
42
+ from .payload import Payload, PayloadData, PayloadSerializer
10
43
  from .polymorphic import Polymorphic, impl
11
44
  from .rule import RuleChain
12
- from .scope import GLOBAL, PER_CALL, PER_EVENT, NodeScope, global_node, per_call, per_event
13
- from .source import ChatSource, Source, UserSource
45
+ from .scope import (
46
+ GLOBAL,
47
+ PER_CALL,
48
+ PER_EVENT,
49
+ NodeScope,
50
+ global_node,
51
+ per_call,
52
+ per_event,
53
+ )
54
+ from .source import ChatSource, Source, UserId, UserSource
14
55
  from .text import Text, TextInteger, TextLiteral
15
56
  from .tools import generate_node
16
- from .update import UpdateNode
17
57
 
18
58
  __all__ = (
59
+ "Animation",
19
60
  "Attachment",
20
61
  "Audio",
21
62
  "CallbackQueryData",
22
- "CallbackQueryNode",
63
+ "CallbackQueryDataJson",
23
64
  "ChatSource",
24
65
  "CommandInfo",
66
+ "Composable",
25
67
  "ComposeError",
26
- "Composition",
27
68
  "ContainerNode",
28
69
  "DataNode",
70
+ "Document",
71
+ "Either",
29
72
  "EventNode",
73
+ "FactoryNode",
74
+ "Field",
30
75
  "Field",
76
+ "File",
77
+ "FileId",
31
78
  "GLOBAL",
79
+ "GlobalNode",
80
+ "IsNode",
32
81
  "Me",
33
- "MessageNode",
34
82
  "Name",
35
83
  "Node",
36
84
  "NodeCollection",
85
+ "NodeComposeFunction",
86
+ "NodeImpersonation",
87
+ "NodeProto",
37
88
  "NodeScope",
38
89
  "NodeSession",
90
+ "NodeType",
91
+ "Optional",
39
92
  "PER_CALL",
40
93
  "PER_EVENT",
94
+ "Payload",
95
+ "PayloadData",
96
+ "PayloadSerializer",
41
97
  "Photo",
42
98
  "Polymorphic",
43
99
  "RuleChain",
44
- "ScalarNode",
45
100
  "Source",
101
+ "SuccessfulPayment",
46
102
  "Text",
47
103
  "TextInteger",
48
104
  "TextLiteral",
49
- "UpdateNode",
105
+ "UserId",
50
106
  "UserSource",
51
107
  "Video",
108
+ "VideoNote",
109
+ "Voice",
110
+ "as_node",
52
111
  "compose_node",
53
112
  "compose_nodes",
54
113
  "generate_node",
@@ -57,4 +116,6 @@ __all__ = (
57
116
  "is_node",
58
117
  "per_call",
59
118
  "per_event",
119
+ "scalar_node",
120
+ "unwrap_node",
60
121
  )