telegrinder 0.4.1__py3-none-any.whl → 0.5.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 (233) hide show
  1. telegrinder/__init__.py +38 -56
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +6 -4
  4. telegrinder/api/api.py +100 -26
  5. telegrinder/api/error.py +42 -8
  6. telegrinder/api/response.py +4 -1
  7. telegrinder/api/token.py +2 -2
  8. telegrinder/bot/__init__.py +9 -25
  9. telegrinder/bot/bot.py +31 -25
  10. telegrinder/bot/cute_types/__init__.py +0 -0
  11. telegrinder/bot/cute_types/base.py +103 -61
  12. telegrinder/bot/cute_types/callback_query.py +447 -400
  13. telegrinder/bot/cute_types/chat_join_request.py +59 -62
  14. telegrinder/bot/cute_types/chat_member_updated.py +154 -157
  15. telegrinder/bot/cute_types/inline_query.py +41 -44
  16. telegrinder/bot/cute_types/message.py +2621 -2590
  17. telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
  18. telegrinder/bot/cute_types/update.py +1 -8
  19. telegrinder/bot/cute_types/utils.py +1 -1
  20. telegrinder/bot/dispatch/__init__.py +10 -15
  21. telegrinder/bot/dispatch/abc.py +12 -11
  22. telegrinder/bot/dispatch/action.py +104 -0
  23. telegrinder/bot/dispatch/context.py +32 -26
  24. telegrinder/bot/dispatch/dispatch.py +61 -134
  25. telegrinder/bot/dispatch/handler/__init__.py +2 -0
  26. telegrinder/bot/dispatch/handler/abc.py +10 -8
  27. telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
  28. telegrinder/bot/dispatch/handler/base.py +10 -33
  29. telegrinder/bot/dispatch/handler/document_reply.py +2 -3
  30. telegrinder/bot/dispatch/handler/func.py +55 -87
  31. telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
  32. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  33. telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
  34. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
  35. telegrinder/bot/dispatch/handler/video_reply.py +2 -3
  36. telegrinder/bot/dispatch/middleware/__init__.py +0 -0
  37. telegrinder/bot/dispatch/middleware/abc.py +79 -55
  38. telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
  39. telegrinder/bot/dispatch/process.py +84 -105
  40. telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
  41. telegrinder/bot/dispatch/return_manager/abc.py +102 -65
  42. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
  43. telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
  44. telegrinder/bot/dispatch/return_manager/message.py +8 -10
  45. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
  46. telegrinder/bot/dispatch/view/__init__.py +4 -4
  47. telegrinder/bot/dispatch/view/abc.py +6 -16
  48. telegrinder/bot/dispatch/view/base.py +54 -178
  49. telegrinder/bot/dispatch/view/box.py +19 -18
  50. telegrinder/bot/dispatch/view/callback_query.py +4 -8
  51. telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
  52. telegrinder/bot/dispatch/view/chat_member.py +5 -25
  53. telegrinder/bot/dispatch/view/error.py +9 -0
  54. telegrinder/bot/dispatch/view/inline_query.py +4 -8
  55. telegrinder/bot/dispatch/view/message.py +5 -25
  56. telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
  57. telegrinder/bot/dispatch/view/raw.py +3 -109
  58. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
  59. telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
  60. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
  61. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
  62. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
  63. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  64. telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
  65. telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
  66. telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
  67. telegrinder/bot/polling/__init__.py +0 -0
  68. telegrinder/bot/polling/abc.py +0 -0
  69. telegrinder/bot/polling/polling.py +209 -88
  70. telegrinder/bot/rules/__init__.py +3 -16
  71. telegrinder/bot/rules/abc.py +42 -122
  72. telegrinder/bot/rules/callback_data.py +29 -49
  73. telegrinder/bot/rules/chat_join.py +5 -23
  74. telegrinder/bot/rules/command.py +8 -4
  75. telegrinder/bot/rules/enum_text.py +3 -4
  76. telegrinder/bot/rules/func.py +7 -14
  77. telegrinder/bot/rules/fuzzy.py +3 -4
  78. telegrinder/bot/rules/inline.py +8 -20
  79. telegrinder/bot/rules/integer.py +2 -3
  80. telegrinder/bot/rules/is_from.py +12 -11
  81. telegrinder/bot/rules/logic.py +11 -5
  82. telegrinder/bot/rules/markup.py +22 -14
  83. telegrinder/bot/rules/mention.py +8 -7
  84. telegrinder/bot/rules/message_entities.py +8 -4
  85. telegrinder/bot/rules/node.py +23 -12
  86. telegrinder/bot/rules/payload.py +5 -4
  87. telegrinder/bot/rules/payment_invoice.py +6 -21
  88. telegrinder/bot/rules/regex.py +2 -4
  89. telegrinder/bot/rules/rule_enum.py +8 -7
  90. telegrinder/bot/rules/start.py +5 -6
  91. telegrinder/bot/rules/state.py +1 -1
  92. telegrinder/bot/rules/text.py +4 -15
  93. telegrinder/bot/rules/update.py +3 -4
  94. telegrinder/bot/scenario/__init__.py +0 -0
  95. telegrinder/bot/scenario/abc.py +6 -5
  96. telegrinder/bot/scenario/checkbox.py +1 -1
  97. telegrinder/bot/scenario/choice.py +30 -39
  98. telegrinder/client/__init__.py +3 -5
  99. telegrinder/client/abc.py +11 -6
  100. telegrinder/client/aiohttp.py +141 -27
  101. telegrinder/client/form_data.py +1 -1
  102. telegrinder/model.py +61 -89
  103. telegrinder/modules.py +325 -102
  104. telegrinder/msgspec_utils/__init__.py +40 -0
  105. telegrinder/msgspec_utils/abc.py +18 -0
  106. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  107. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  108. telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
  109. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  110. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  111. telegrinder/msgspec_utils/decoder.py +389 -0
  112. telegrinder/msgspec_utils/encoder.py +206 -0
  113. telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
  114. telegrinder/msgspec_utils/tools.py +75 -0
  115. telegrinder/node/__init__.py +24 -7
  116. telegrinder/node/attachment.py +1 -0
  117. telegrinder/node/base.py +154 -72
  118. telegrinder/node/callback_query.py +5 -5
  119. telegrinder/node/collection.py +39 -0
  120. telegrinder/node/command.py +1 -2
  121. telegrinder/node/composer.py +121 -72
  122. telegrinder/node/container.py +11 -8
  123. telegrinder/node/context.py +48 -0
  124. telegrinder/node/either.py +27 -40
  125. telegrinder/node/error.py +41 -0
  126. telegrinder/node/event.py +37 -11
  127. telegrinder/node/exceptions.py +7 -0
  128. telegrinder/node/file.py +0 -0
  129. telegrinder/node/i18n.py +108 -0
  130. telegrinder/node/me.py +3 -2
  131. telegrinder/node/payload.py +1 -1
  132. telegrinder/node/polymorphic.py +63 -28
  133. telegrinder/node/reply_message.py +12 -0
  134. telegrinder/node/rule.py +6 -13
  135. telegrinder/node/scope.py +14 -5
  136. telegrinder/node/session.py +53 -0
  137. telegrinder/node/source.py +41 -9
  138. telegrinder/node/text.py +1 -2
  139. telegrinder/node/tools/__init__.py +0 -0
  140. telegrinder/node/tools/generator.py +3 -5
  141. telegrinder/node/utility.py +16 -0
  142. telegrinder/py.typed +0 -0
  143. telegrinder/rules.py +0 -0
  144. telegrinder/tools/__init__.py +48 -88
  145. telegrinder/tools/aio.py +103 -0
  146. telegrinder/tools/callback_data_serialization/__init__.py +5 -0
  147. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
  148. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
  149. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
  150. telegrinder/tools/final.py +21 -0
  151. telegrinder/tools/formatting/__init__.py +2 -18
  152. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  153. telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
  154. telegrinder/tools/formatting/deep_links/parsing.py +90 -0
  155. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  156. telegrinder/tools/formatting/html_formatter.py +18 -45
  157. telegrinder/tools/fullname.py +83 -0
  158. telegrinder/tools/global_context/__init__.py +4 -3
  159. telegrinder/tools/global_context/abc.py +17 -14
  160. telegrinder/tools/global_context/builtin_context.py +39 -0
  161. telegrinder/tools/global_context/global_context.py +138 -39
  162. telegrinder/tools/input_file_directory.py +0 -0
  163. telegrinder/tools/keyboard/__init__.py +39 -0
  164. telegrinder/tools/keyboard/abc.py +159 -0
  165. telegrinder/tools/keyboard/base.py +77 -0
  166. telegrinder/tools/keyboard/buttons/__init__.py +14 -0
  167. telegrinder/tools/keyboard/buttons/base.py +18 -0
  168. telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
  169. telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
  170. telegrinder/tools/keyboard/buttons/tools.py +18 -0
  171. telegrinder/tools/keyboard/data.py +20 -0
  172. telegrinder/tools/keyboard/keyboard.py +131 -0
  173. telegrinder/tools/keyboard/static_keyboard.py +83 -0
  174. telegrinder/tools/lifespan.py +87 -51
  175. telegrinder/tools/limited_dict.py +4 -1
  176. telegrinder/tools/loop_wrapper.py +332 -0
  177. telegrinder/tools/magic/__init__.py +32 -0
  178. telegrinder/tools/magic/annotations.py +165 -0
  179. telegrinder/tools/magic/dictionary.py +20 -0
  180. telegrinder/tools/magic/function.py +246 -0
  181. telegrinder/tools/magic/shortcut.py +111 -0
  182. telegrinder/tools/parse_mode.py +9 -3
  183. telegrinder/tools/singleton/__init__.py +4 -0
  184. telegrinder/tools/singleton/abc.py +14 -0
  185. telegrinder/tools/singleton/singleton.py +18 -0
  186. telegrinder/tools/state_storage/__init__.py +0 -0
  187. telegrinder/tools/state_storage/abc.py +6 -1
  188. telegrinder/tools/state_storage/memory.py +1 -1
  189. telegrinder/tools/strings.py +0 -0
  190. telegrinder/types/__init__.py +307 -268
  191. telegrinder/types/enums.py +64 -37
  192. telegrinder/types/input_file.py +3 -3
  193. telegrinder/types/methods.py +5699 -5055
  194. telegrinder/types/methods_utils.py +62 -0
  195. telegrinder/types/objects.py +7846 -7058
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.0.dist-info/METADATA +162 -0
  198. telegrinder-0.5.0.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.1.dist-info → telegrinder-0.5.0.dist-info}/WHEEL +1 -1
  200. {telegrinder-0.4.1.dist-info → telegrinder-0.5.0.dist-info/licenses}/LICENSE +2 -2
  201. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
  202. telegrinder/bot/rules/id.py +0 -24
  203. telegrinder/bot/rules/message.py +0 -15
  204. telegrinder/client/sonic.py +0 -212
  205. telegrinder/msgspec_utils.py +0 -478
  206. telegrinder/tools/adapter/__init__.py +0 -19
  207. telegrinder/tools/adapter/abc.py +0 -49
  208. telegrinder/tools/adapter/dataclass.py +0 -56
  209. telegrinder/tools/adapter/errors.py +0 -5
  210. telegrinder/tools/adapter/event.py +0 -63
  211. telegrinder/tools/adapter/node.py +0 -46
  212. telegrinder/tools/adapter/raw_event.py +0 -27
  213. telegrinder/tools/adapter/raw_update.py +0 -30
  214. telegrinder/tools/callback_data_serilization/__init__.py +0 -5
  215. telegrinder/tools/error_handler/__init__.py +0 -10
  216. telegrinder/tools/error_handler/abc.py +0 -30
  217. telegrinder/tools/error_handler/error.py +0 -9
  218. telegrinder/tools/error_handler/error_handler.py +0 -179
  219. telegrinder/tools/formatting/spec_html_formats.py +0 -75
  220. telegrinder/tools/functional.py +0 -8
  221. telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
  222. telegrinder/tools/i18n/__init__.py +0 -12
  223. telegrinder/tools/i18n/abc.py +0 -32
  224. telegrinder/tools/i18n/middleware/__init__.py +0 -3
  225. telegrinder/tools/i18n/middleware/abc.py +0 -22
  226. telegrinder/tools/i18n/simple.py +0 -43
  227. telegrinder/tools/keyboard.py +0 -132
  228. telegrinder/tools/loop_wrapper/__init__.py +0 -4
  229. telegrinder/tools/loop_wrapper/abc.py +0 -20
  230. telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
  231. telegrinder/tools/magic.py +0 -344
  232. telegrinder-0.4.1.dist-info/METADATA +0 -143
  233. telegrinder-0.4.1.dist-info/RECORD +0 -182
@@ -3,13 +3,13 @@ from __future__ import annotations
3
3
  import dataclasses
4
4
 
5
5
  import typing_extensions as typing
6
- from fntypes import Nothing, Option, Some
7
- from vbml.patcher import Patcher
6
+ from fntypes.option import Nothing, Option, Some
7
+ from vbml.patcher.abc import ABCPatcher
8
8
 
9
- from telegrinder.api.api import API, HTTPClient
9
+ from telegrinder.api.api import API
10
10
  from telegrinder.bot.dispatch.abc import ABCDispatch
11
11
  from telegrinder.bot.dispatch.context import Context
12
- from telegrinder.bot.dispatch.handler.func import ErrorHandlerT, Func, FuncHandler
12
+ from telegrinder.bot.dispatch.handler.func import FuncHandler, Function
13
13
  from telegrinder.bot.dispatch.middleware.abc import run_middleware
14
14
  from telegrinder.bot.dispatch.middleware.global_middleware import GlobalMiddleware
15
15
  from telegrinder.bot.dispatch.view.abc import ABCView
@@ -17,6 +17,7 @@ from telegrinder.bot.dispatch.view.box import (
17
17
  CallbackQueryView,
18
18
  ChatJoinRequestView,
19
19
  ChatMemberView,
20
+ ErrorView,
20
21
  InlineQueryView,
21
22
  MessageView,
22
23
  PreCheckoutQueryView,
@@ -24,23 +25,20 @@ from telegrinder.bot.dispatch.view.box import (
24
25
  ViewBox,
25
26
  )
26
27
  from telegrinder.modules import logger
27
- from telegrinder.tools.error_handler.error_handler import ErrorHandler
28
+ from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, NodeScope
28
29
  from telegrinder.tools.global_context import TelegrinderContext
29
- from telegrinder.types.enums import UpdateType
30
30
  from telegrinder.types.objects import Update
31
31
 
32
32
  if typing.TYPE_CHECKING:
33
33
  from telegrinder.bot.cute_types.base import BaseCute
34
- from telegrinder.bot.cute_types.update import UpdateCute
35
34
  from telegrinder.bot.rules.abc import ABCRule
35
+ from telegrinder.node.composer import Composer
36
36
 
37
37
  T = typing.TypeVar("T", default=typing.Any)
38
38
  R = typing.TypeVar("R", covariant=True, default=typing.Any)
39
39
  Event = typing.TypeVar("Event", bound="BaseCute")
40
40
  P = typing.ParamSpec("P", default=...)
41
41
 
42
- DEFAULT_DATACLASS: typing.Final[type[Update]] = Update
43
-
44
42
 
45
43
  @dataclasses.dataclass(repr=False, kw_only=True)
46
44
  class Dispatch(
@@ -53,9 +51,9 @@ class Dispatch(
53
51
  MessageView,
54
52
  PreCheckoutQueryView,
55
53
  RawEventView,
54
+ ErrorView,
56
55
  ],
57
56
  typing.Generic[
58
- HTTPClient,
59
57
  CallbackQueryView,
60
58
  ChatJoinRequestView,
61
59
  ChatMemberView,
@@ -63,9 +61,10 @@ class Dispatch(
63
61
  MessageView,
64
62
  PreCheckoutQueryView,
65
63
  RawEventView,
64
+ ErrorView,
66
65
  ],
67
66
  ):
68
- _global_context: TelegrinderContext = dataclasses.field(
67
+ global_context: TelegrinderContext = dataclasses.field(
69
68
  init=False,
70
69
  default_factory=TelegrinderContext,
71
70
  )
@@ -77,156 +76,82 @@ class Dispatch(
77
76
  return "Dispatch(%s)" % ", ".join(f"{k}={v!r}" for k, v in self.get_views().items())
78
77
 
79
78
  @property
80
- def global_context(self) -> TelegrinderContext:
81
- return self._global_context
82
-
83
- @property
84
- def patcher(self) -> Patcher:
79
+ def patcher(self) -> ABCPatcher:
85
80
  """Alias `patcher` to get `vbml.Patcher` from the global context."""
86
81
  return self.global_context.vbml_patcher
87
82
 
88
- @typing.overload
89
- def handle(
90
- self,
91
- *rules: "ABCRule",
92
- final: bool = True,
93
- ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandler[UpdateCute]]]: ...
94
-
95
- @typing.overload
96
- def handle(
97
- self,
98
- *rules: "ABCRule",
99
- dataclass: type[T],
100
- final: bool = True,
101
- ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandler[T]]]: ...
102
-
103
- @typing.overload
104
- def handle(
105
- self,
106
- *rules: "ABCRule",
107
- update_type: UpdateType,
108
- final: bool = True,
109
- ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandler[UpdateCute]]]: ...
110
-
111
- @typing.overload
112
- def handle(
113
- self,
114
- *rules: "ABCRule",
115
- dataclass: type[T],
116
- update_type: UpdateType,
117
- final: bool = True,
118
- ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandler[T]]]: ...
119
-
120
- @typing.overload
121
- def handle(
122
- self,
123
- *rules: "ABCRule",
124
- error_handler: ErrorHandlerT,
125
- final: bool = True,
126
- ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandlerT]]: ...
127
-
128
- @typing.overload
129
- def handle(
130
- self,
131
- *rules: "ABCRule",
132
- update_type: UpdateType,
133
- error_handler: ErrorHandlerT,
134
- final: bool = True,
135
- ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandlerT]]: ...
136
-
137
- @typing.overload
138
- def handle(
139
- self,
140
- *rules: "ABCRule",
141
- dataclass: type[T],
142
- error_handler: ErrorHandlerT,
143
- final: bool = True,
144
- ) -> typing.Callable[[Func[P, R]], FuncHandler[T, Func[P, R], ErrorHandlerT]]: ...
145
-
146
- @typing.overload
147
- def handle(
148
- self,
149
- *rules: "ABCRule",
150
- dataclass: type[T],
151
- update_type: UpdateType,
152
- error_handler: ErrorHandlerT,
153
- final: bool = True,
154
- ) -> typing.Callable[[Func[P, R]], FuncHandler[T, Func[P, R], ErrorHandlerT]]: ...
155
-
156
- @typing.overload
157
- def handle(
158
- self,
159
- *rules: "ABCRule",
160
- update_type: UpdateType | None = None,
161
- dataclass: type[T] = DEFAULT_DATACLASS,
162
- error_handler: typing.Literal[None] = None,
163
- final: bool = True,
164
- ) -> typing.Callable[[Func[P, R]], FuncHandler[T, Func[P, R], ErrorHandler[T]]]: ...
165
-
166
- def handle(
167
- self,
168
- *rules: "ABCRule",
169
- update_type: UpdateType | None = None,
170
- dataclass: type[typing.Any] = DEFAULT_DATACLASS,
171
- error_handler: ErrorHandlerT | None = None,
172
- final: bool = True,
173
- ) -> typing.Callable[..., typing.Any]:
174
- def wrapper(func):
175
- handler = FuncHandler(
176
- func,
177
- list(rules),
178
- final=final,
179
- dataclass=dataclass,
180
- error_handler=error_handler or ErrorHandler(),
181
- update_type=update_type,
83
+ @property
84
+ def composer(self) -> Composer:
85
+ """Alias `composer` to get `telegrinder.node.composer.Composer` from the global context."""
86
+ return self.global_context.composer.unwrap()
87
+
88
+ def handle[T: Function](self, *rules: ABCRule, final: bool = True) -> typing.Callable[[T], T]:
89
+ def wrapper(func: T, /) -> T:
90
+ self.raw_event.handlers.append(
91
+ FuncHandler(
92
+ function=func,
93
+ rules=list(rules),
94
+ final=final,
95
+ ),
182
96
  )
183
- self.raw_event.handlers.append(handler)
184
- return handler
97
+ return func
185
98
 
186
99
  return wrapper
187
100
 
188
- async def feed(self, event: Update, api: API[HTTPClient]) -> bool:
189
- logger.debug(
190
- "Processing update (update_id={}, update_type={!r})",
191
- event.update_id,
192
- event.update_type.name,
193
- )
194
- context = Context(raw_update=event)
101
+ async def feed(self, event: Update, api: API) -> bool:
102
+ logger.info("New Update(id={}, type={!r})", event.update_id, event.update_type)
103
+ processed = False
104
+ context = Context().add_update_cute(event, api)
105
+ start_time = self.global_context.loop_wrapper.loop.time()
195
106
 
196
107
  if (
197
108
  await run_middleware(
198
109
  self.global_middleware.pre,
199
110
  api,
200
- event, # type: ignore
201
- raw_event=event,
202
- ctx=context,
203
- adapter=self.global_middleware.adapter,
111
+ event,
112
+ context,
113
+ required_nodes=self.global_middleware.pre_required_nodes,
204
114
  )
205
115
  is False
206
116
  ):
207
- return False
117
+ return processed
208
118
 
209
119
  for view in self.get_views().values():
210
120
  if await view.check(event):
211
121
  logger.debug(
212
- "Update (update_id={}, update_type={!r}) matched view {!r}.",
122
+ "Processing update (id={}, type={!r}) with view {!r} by bot (id={})",
213
123
  event.update_id,
214
- event.update_type.name,
124
+ event.update_type,
215
125
  view,
126
+ api.id,
216
127
  )
217
- if await view.process(event, api, context):
218
- return True
128
+
129
+ try:
130
+ if await view.process(event, api, context):
131
+ processed = True
132
+ break
133
+ except BaseException as exception:
134
+ if not await self.error.process(event, api, context.add_exception_update(exception)):
135
+ raise exception
136
+ finally:
137
+ for session in context.get(CONTEXT_STORE_NODES_KEY, {}).values():
138
+ await session.close(scopes=(NodeScope.PER_EVENT,))
219
139
 
220
140
  await run_middleware(
221
141
  self.global_middleware.post,
222
142
  api,
223
143
  event,
224
- raw_event=event,
225
- ctx=context,
226
- adapter=self.global_middleware.adapter,
144
+ context,
145
+ required_nodes=self.global_middleware.post_required_nodes,
227
146
  )
228
-
229
- return False
147
+ logger.debug(
148
+ "Update (id={}, type={!r}) processed in {} ms by bot (id={})",
149
+ event.update_id,
150
+ event.update_type,
151
+ int((self.global_context.loop_wrapper.loop.time() - start_time) * 1000),
152
+ api.id,
153
+ )
154
+ return processed
230
155
 
231
156
  def load(self, external: typing.Self) -> None:
232
157
  views_external = external.get_views()
@@ -246,7 +171,9 @@ class Dispatch(
246
171
 
247
172
  def get_views(self) -> dict[str, ABCView]:
248
173
  """Get all views."""
249
- return {name: view for name, view in self.__dict__.items() if isinstance(view, ABCView)}
174
+ return {
175
+ name: view for name, view in self.__dict__.items() if isinstance(view, ABCView) and name != "error"
176
+ }
250
177
 
251
178
  __call__ = handle
252
179
 
@@ -1,5 +1,6 @@
1
1
  from telegrinder.bot.dispatch.handler.abc import ABCHandler
2
2
  from telegrinder.bot.dispatch.handler.audio_reply import AudioReplyHandler
3
+ from telegrinder.bot.dispatch.handler.base import BaseReplyHandler
3
4
  from telegrinder.bot.dispatch.handler.document_reply import DocumentReplyHandler
4
5
  from telegrinder.bot.dispatch.handler.func import FuncHandler
5
6
  from telegrinder.bot.dispatch.handler.media_group_reply import MediaGroupReplyHandler
@@ -11,6 +12,7 @@ from telegrinder.bot.dispatch.handler.video_reply import VideoReplyHandler
11
12
  __all__ = (
12
13
  "ABCHandler",
13
14
  "AudioReplyHandler",
15
+ "BaseReplyHandler",
14
16
  "DocumentReplyHandler",
15
17
  "FuncHandler",
16
18
  "MediaGroupReplyHandler",
@@ -1,22 +1,24 @@
1
1
  import typing
2
2
  from abc import ABC, abstractmethod
3
3
 
4
+ from fntypes.result import Result
5
+
4
6
  from telegrinder.api import API
5
7
  from telegrinder.bot.dispatch.context import Context
6
- from telegrinder.tools.adapter.abc import ABCAdapter
7
8
  from telegrinder.types.objects import Update
8
9
 
9
10
 
10
- class ABCHandler[Event](ABC):
11
+ class ABCHandler(ABC):
11
12
  final: bool
12
- adapter: ABCAdapter[Update, Event] | None = None
13
-
14
- @abstractmethod
15
- async def check(self, api: API, event: Update, ctx: Context | None = None) -> bool:
16
- pass
17
13
 
18
14
  @abstractmethod
19
- async def run(self, api: API, event: Event, ctx: Context) -> typing.Any:
15
+ async def run(
16
+ self,
17
+ api: API,
18
+ event: Update,
19
+ context: Context,
20
+ check: bool = True,
21
+ ) -> Result[typing.Any, typing.Any]:
20
22
  pass
21
23
 
22
24
 
@@ -1,6 +1,5 @@
1
1
  import typing
2
2
 
3
- from telegrinder.api.api import API
4
3
  from telegrinder.bot.cute_types.message import MessageCute
5
4
  from telegrinder.bot.dispatch.context import Context
6
5
  from telegrinder.bot.dispatch.handler.base import BaseReplyHandler
@@ -31,8 +30,8 @@ class AudioReplyHandler(BaseReplyHandler):
31
30
  **default_params,
32
31
  )
33
32
 
34
- async def run(self, _: API, event: MessageCute, __: Context) -> typing.Any:
35
- method = event.answer_audio if not self.as_reply else event.reply_audio
33
+ async def handle(self, message: MessageCute) -> None:
34
+ method = message.answer_audio if not self.as_reply else message.reply_audio
36
35
  await method(
37
36
  audio=self.audio,
38
37
  parse_mode=self.parse_mode,
@@ -1,24 +1,14 @@
1
1
  import abc
2
2
  import typing
3
3
 
4
- from fntypes.result import Result
5
-
6
- from telegrinder.api.api import API
7
- from telegrinder.api.error import APIError
8
- from telegrinder.bot.cute_types.message import MessageCute
9
4
  from telegrinder.bot.dispatch.context import Context
10
- from telegrinder.bot.dispatch.handler.abc import ABCHandler
11
- from telegrinder.bot.dispatch.process import check_rule
5
+ from telegrinder.bot.dispatch.handler.func import FuncHandler
12
6
  from telegrinder.bot.rules.abc import ABCRule
13
- from telegrinder.modules import logger
14
- from telegrinder.types.objects import Update
15
7
 
16
- type APIMethod = typing.Callable[
17
- typing.Concatenate[MessageCute, ...], typing.Awaitable[Result[typing.Any, APIError]]
18
- ]
19
8
 
9
+ class BaseReplyHandler(FuncHandler, abc.ABC):
10
+ final: bool
20
11
 
21
- class BaseReplyHandler(ABCHandler[MessageCute], abc.ABC):
22
12
  def __init__(
23
13
  self,
24
14
  *rules: ABCRule,
@@ -27,30 +17,17 @@ class BaseReplyHandler(ABCHandler[MessageCute], abc.ABC):
27
17
  preset_context: Context | None = None,
28
18
  **default_params: typing.Any,
29
19
  ) -> None:
30
- self.rules = list(rules)
31
20
  self.as_reply = as_reply
32
- self.final = final
33
21
  self.default_params = default_params
34
- self.preset_context = preset_context or Context()
35
-
36
- def __repr__(self) -> str:
37
- return f"<{self.__class__.__qualname__}>"
38
-
39
- async def check(self, api: API, event: Update, ctx: Context | None = None) -> bool:
40
- ctx = Context(raw_update=event) if ctx is None else ctx
41
- temp_ctx = ctx.copy()
42
- temp_ctx |= self.preset_context
43
-
44
- for rule in self.rules:
45
- if not await check_rule(api, rule, event, ctx):
46
- logger.debug("Rule {!r} failed!", rule)
47
- return False
48
-
49
- ctx |= temp_ctx
50
- return True
22
+ super().__init__(
23
+ function=self.handle,
24
+ rules=list(rules),
25
+ final=final,
26
+ preset_context=preset_context or Context(),
27
+ )
51
28
 
52
29
  @abc.abstractmethod
53
- async def run(self, api: API, event: MessageCute, ctx: Context) -> typing.Any:
30
+ async def handle(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
54
31
  pass
55
32
 
56
33
 
@@ -1,6 +1,5 @@
1
1
  import typing
2
2
 
3
- from telegrinder.api.api import API
4
3
  from telegrinder.bot.cute_types.message import MessageCute
5
4
  from telegrinder.bot.dispatch.context import Context
6
5
  from telegrinder.bot.dispatch.handler.base import BaseReplyHandler
@@ -31,8 +30,8 @@ class DocumentReplyHandler(BaseReplyHandler):
31
30
  **default_params,
32
31
  )
33
32
 
34
- async def run(self, _: API, event: MessageCute, __: Context) -> typing.Any:
35
- method = event.answer_document if not self.as_reply else event.reply_document
33
+ async def handle(self, message: MessageCute) -> None:
34
+ method = message.answer_document if not self.as_reply else message.reply_document
36
35
  await method(
37
36
  document=self.document,
38
37
  parse_mode=self.parse_mode,
@@ -1,128 +1,96 @@
1
+ from __future__ import annotations
2
+
1
3
  import dataclasses
4
+ import typing
5
+ from collections import deque
2
6
  from functools import cached_property
3
7
 
4
- import typing_extensions as typing
8
+ from fntypes.result import Error, Ok, Result
5
9
 
6
10
  from telegrinder.api.api import API
7
11
  from telegrinder.bot.dispatch.context import Context
12
+ from telegrinder.bot.dispatch.handler.abc import ABCHandler
8
13
  from telegrinder.bot.dispatch.process import check_rule
9
14
  from telegrinder.modules import logger
10
- from telegrinder.node.base import NodeType, get_nodes
11
- from telegrinder.node.composer import NodeCollection, compose_nodes
12
- from telegrinder.tools.adapter.abc import ABCAdapter
13
- from telegrinder.tools.adapter.dataclass import DataclassAdapter
14
- from telegrinder.tools.error_handler import ABCErrorHandler, ErrorHandler
15
- from telegrinder.tools.magic import get_annotations, magic_bundle
16
- from telegrinder.types.enums import UpdateType
15
+ from telegrinder.node.base import IsNode, get_nodes
16
+ from telegrinder.node.composer import compose_nodes
17
+ from telegrinder.tools.fullname import fullname
18
+ from telegrinder.tools.magic.function import bundle
17
19
  from telegrinder.types.objects import Update
18
20
 
19
- from .abc import ABCHandler
20
-
21
21
  if typing.TYPE_CHECKING:
22
22
  from telegrinder.bot.rules.abc import ABCRule
23
- from telegrinder.node.composer import NodeCollection
24
23
 
25
- Function = typing.TypeVar("Function", bound="Func[..., typing.Any]")
26
- Event = typing.TypeVar("Event")
27
- ErrorHandlerT = typing.TypeVar("ErrorHandlerT", bound=ABCErrorHandler, default=ErrorHandler)
28
-
29
- type Func[**Rest, Result] = typing.Callable[Rest, typing.Coroutine[typing.Any, typing.Any, Result]]
24
+ type Function = typing.Callable[..., typing.Coroutine[typing.Any, typing.Any, typing.Any]]
30
25
 
31
26
 
32
27
  @dataclasses.dataclass(repr=False, slots=True)
33
- class FuncHandler(ABCHandler[Event], typing.Generic[Event, Function, ErrorHandlerT]):
34
- function: Function
35
- rules: list["ABCRule"]
36
- adapter: ABCAdapter[Update, Event] | None = dataclasses.field(default=None, kw_only=True)
28
+ class FuncHandler[T: Function](ABCHandler):
29
+ function: T
30
+ rules: dataclasses.InitVar[typing.Iterable[ABCRule] | None] = None
37
31
  final: bool = dataclasses.field(default=True, kw_only=True)
38
- dataclass: type[typing.Any] | None = dataclasses.field(default=dict[str, typing.Any], kw_only=True)
39
- error_handler: ErrorHandlerT = dataclasses.field(
40
- default_factory=lambda: typing.cast(ErrorHandlerT, ErrorHandler()),
41
- kw_only=True,
42
- )
43
32
  preset_context: Context = dataclasses.field(default_factory=lambda: Context(), kw_only=True)
44
- update_type: UpdateType | None = dataclasses.field(default=None, kw_only=True)
45
-
46
- def __post_init__(self) -> None:
47
- self.dataclass = typing.get_origin(self.dataclass) or self.dataclass
48
33
 
49
- if self.dataclass is not None and self.adapter is None:
50
- self.adapter = DataclassAdapter(self.dataclass, self.update_type)
34
+ def __post_init__(self, rules: typing.Iterable[ABCRule] | None) -> None:
35
+ self.check_rules = deque(rules or ())
51
36
 
52
37
  @property
53
38
  def __call__(self) -> Function:
54
39
  return self.function
55
40
 
56
41
  def __repr__(self) -> str:
57
- return "<{}: {}={!r} with rules={!r}, dataclass={!r}, error_handler={!r}>".format(
58
- self.__class__.__name__,
59
- "final function" if self.final else "function",
60
- self.function.__qualname__,
61
- self.rules,
62
- self.dataclass,
63
- self.error_handler,
64
- )
42
+ return fullname(self.function)
65
43
 
66
44
  @cached_property
67
- def required_nodes(self) -> dict[str, type[NodeType]]:
45
+ def required_nodes(self) -> dict[str, IsNode]:
68
46
  return get_nodes(self.function)
69
47
 
70
- def get_name_event_param(self, event: Event) -> str | None:
71
- event_class = self.dataclass or event.__class__
72
- for k, v in get_annotations(self.function).items():
73
- if isinstance(v := typing.get_origin(v) or v, type) and v is event_class:
74
- self.func_event_param = k
75
- return k
76
- return None
77
-
78
- async def check(self, api: API, event: Update, ctx: Context | None = None) -> bool:
79
- if self.update_type is not None and self.update_type != event.update_type:
80
- return False
81
-
82
- logger.debug("Checking handler {!r}...", self)
83
- ctx = Context(raw_update=event) if ctx is None else ctx
84
- temp_ctx = ctx.copy()
48
+ async def run(
49
+ self,
50
+ api: API,
51
+ event: Update,
52
+ context: Context,
53
+ check: bool = True,
54
+ ) -> Result[typing.Any, str]:
55
+ logger.debug("Checking rules and composing nodes for handler `{!r}`...", self)
56
+
57
+ temp_ctx = context.copy()
85
58
  temp_ctx |= self.preset_context.copy()
86
- update = event
87
59
 
88
- for rule in self.rules:
89
- if not await check_rule(api, rule, update, temp_ctx):
90
- logger.debug("Rule {!r} failed!", rule)
91
- return False
60
+ if check:
61
+ for rule in self.check_rules:
62
+ if not await check_rule(api, rule, event, temp_ctx):
63
+ return Error(f"Rule {rule!r} failed.")
92
64
 
93
- nodes = self.required_nodes
65
+ context |= temp_ctx
66
+ data = {Update: event, API: api}
94
67
  node_col = None
95
- if nodes:
96
- result = await compose_nodes(nodes, ctx, data={Update: update, API: api})
97
- if not result:
98
- logger.debug(f"Cannot compose nodes for handler, error: {str(result.error)}")
99
- return False
100
-
101
- node_col = result.value
102
- temp_ctx |= node_col.values
103
68
 
104
- logger.debug("All checks passed for handler.")
105
- temp_ctx["node_col"] = node_col
106
- ctx |= temp_ctx
107
- return True
69
+ if self.required_nodes:
70
+ match await compose_nodes(self.required_nodes, context, data=data):
71
+ case Ok(value):
72
+ node_col = value
73
+ case Error(compose_error):
74
+ return Error(f"Cannot compose nodes for handler `{self}`, error: {compose_error.message}")
108
75
 
109
- async def run(
110
- self,
111
- api: API,
112
- event: Event,
113
- ctx: Context,
114
- node_col: "NodeCollection | None" = None,
115
- ) -> typing.Any:
116
- logger.debug(f"Running handler {self!r}...")
76
+ logger.debug("All good, running handler `{!r}`", self)
117
77
 
78
+ temp_ctx = context.copy()
118
79
  try:
119
- if event_param := self.get_name_event_param(event):
120
- ctx = Context(**{event_param: event, **ctx})
121
- return await self(**magic_bundle(self.function, ctx, start_idx=0))
122
- except BaseException as exception:
123
- return await self.error_handler.run(exception, event, api, ctx)
80
+ bundle_function = bundle(
81
+ self.function,
82
+ {**data, Context: temp_ctx},
83
+ typebundle=True,
84
+ start_idx=0,
85
+ )
86
+ bundle_function &= bundle(
87
+ self.function, context | ({} if node_col is None else node_col.values), start_idx=0
88
+ )
89
+ return Ok(await bundle_function())
124
90
  finally:
125
- if node_col := ctx.node_col:
91
+ context |= temp_ctx
92
+
93
+ if node_col is not None:
126
94
  await node_col.close_all()
127
95
 
128
96
 
@@ -1,6 +1,5 @@
1
1
  import typing
2
2
 
3
- from telegrinder.api.api import API
4
3
  from telegrinder.bot.cute_types.message import MessageCute
5
4
  from telegrinder.bot.dispatch.context import Context
6
5
  from telegrinder.bot.dispatch.handler.base import BaseReplyHandler
@@ -31,8 +30,8 @@ class MediaGroupReplyHandler(BaseReplyHandler):
31
30
  **default_params,
32
31
  )
33
32
 
34
- async def run(self, _: API, event: MessageCute, __: Context) -> typing.Any:
35
- method = event.answer_media_group if not self.as_reply else event.reply_media_group
33
+ async def handle(self, message: MessageCute) -> None:
34
+ method = message.answer_media_group if not self.as_reply else message.reply_media_group
36
35
  await method(
37
36
  media=self.media,
38
37
  parse_mode=self.parse_mode,
@@ -1,6 +1,5 @@
1
1
  import typing
2
2
 
3
- from telegrinder.api.api import API
4
3
  from telegrinder.bot.cute_types.message import MessageCute
5
4
  from telegrinder.bot.dispatch.context import Context
6
5
  from telegrinder.bot.dispatch.handler.base import BaseReplyHandler
@@ -28,8 +27,8 @@ class MessageReplyHandler(BaseReplyHandler):
28
27
  **default_params,
29
28
  )
30
29
 
31
- async def run(self, _: API, event: MessageCute, __: Context) -> typing.Any:
32
- method = event.answer if not self.as_reply else event.reply
30
+ async def handle(self, message: MessageCute) -> None:
31
+ method = message.answer if not self.as_reply else message.reply
33
32
  await method(text=self.text, parse_mode=self.parse_mode, **self.default_params)
34
33
 
35
34