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
@@ -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 PhotoReplyHandler(BaseReplyHandler):
31
30
  **default_params,
32
31
  )
33
32
 
34
- async def run(self, _: API, event: MessageCute, __: Context) -> typing.Any:
35
- method = event.answer_photo if not self.as_reply else event.reply_photo
33
+ async def handle(self, message: MessageCute) -> None:
34
+ method = message.answer_photo if not self.as_reply else message.reply_photo
36
35
  await method(
37
36
  photo=self.photo,
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
@@ -29,8 +28,8 @@ class StickerReplyHandler(BaseReplyHandler):
29
28
  **default_params,
30
29
  )
31
30
 
32
- async def run(self, _: API, event: MessageCute, __: Context) -> typing.Any:
33
- method = event.answer_sticker if not self.as_reply else event.reply_sticker
31
+ async def handle(self, message: MessageCute) -> None:
32
+ method = message.answer_sticker if not self.as_reply else message.reply_sticker
34
33
  await method(sticker=self.sticker, emoji=self.emoji, **self.default_params)
35
34
 
36
35
 
@@ -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 VideoReplyHandler(BaseReplyHandler):
31
30
  **default_params,
32
31
  )
33
32
 
34
- async def run(self, _: API, event: MessageCute, __: Context) -> typing.Any:
35
- method = event.answer_video if not self.as_reply else event.reply_video
33
+ async def run(self, message: MessageCute) -> None:
34
+ method = message.answer_video if not self.as_reply else message.reply_video
36
35
  await method(
37
36
  video=self.video,
38
37
  parse_mode=self.parse_mode,
File without changes
@@ -1,95 +1,119 @@
1
- from __future__ import annotations
2
-
3
1
  from abc import ABC
2
+ from functools import cached_property
4
3
 
5
4
  import typing_extensions as typing
6
- from fntypes import Some
5
+ from fntypes.result import Error, Ok
7
6
 
8
- from telegrinder.api import API
7
+ from telegrinder.api.api import API
9
8
  from telegrinder.bot.cute_types.base import BaseCute
10
9
  from telegrinder.bot.dispatch.context import Context
11
- from telegrinder.model import Model
12
10
  from telegrinder.modules import logger
13
- from telegrinder.tools.adapter.abc import ABCAdapter, run_adapter
11
+ from telegrinder.node.base import IsNode, get_nodes
12
+ from telegrinder.node.composer import compose_nodes
13
+ from telegrinder.tools.aio import maybe_awaitable
14
14
  from telegrinder.tools.lifespan import Lifespan
15
+ from telegrinder.tools.magic.function import bundle
15
16
  from telegrinder.types.objects import Update
16
17
 
17
- ToEvent = typing.TypeVar("ToEvent", bound=Model, default=typing.Any)
18
+ type Event = Update | BaseCute[typing.Any]
19
+ type MiddlewareResult = bool | None | typing.Coroutine[typing.Any, typing.Any, bool | None]
18
20
 
19
21
 
20
- async def run_middleware[Event: Model, R: bool | None](
21
- method: typing.Callable[typing.Concatenate[Event, Context, ...], typing.Awaitable[R]],
22
- api: API[typing.Any],
23
- event: Event,
22
+ async def run_middleware(
23
+ method: typing.Callable[..., MiddlewareResult],
24
+ api: API,
25
+ event: Update,
24
26
  ctx: Context,
25
- raw_event: Update | None = None,
26
- adapter: "ABCAdapter[Update, Event] | None" = None,
27
- *args: typing.Any,
28
- **kwargs: typing.Any,
29
- ) -> R:
30
- if adapter is not None:
31
- if raw_event is None:
32
- raise RuntimeError("raw_event must be specified to apply adapter")
33
- match await run_adapter(adapter, api, raw_event, ctx):
34
- case Some(val):
35
- event = val
36
- case _:
37
- return False # type: ignore
38
-
39
- logger.debug("Running {}-middleware {!r}...", method.__name__, method.__qualname__.split(".")[0])
40
- return await method(event, ctx, *args, **kwargs) # type: ignore
41
-
42
-
43
- class ABCMiddleware[Event: Model | BaseCute](ABC):
44
- adapter: ABCAdapter[Update, Event] | None = None
27
+ required_nodes: typing.Mapping[str, IsNode] | None = None,
28
+ ) -> bool | None:
29
+ node_col = None
30
+ data = {API: api, Update: event, Context: ctx}
31
+
32
+ if required_nodes:
33
+ match await compose_nodes(required_nodes, ctx, data=data):
34
+ case Ok(value):
35
+ node_col = value
36
+ case Error(compose_error):
37
+ logger.debug(
38
+ "Cannot compose nodes for `{}`, error: {!r}",
39
+ method.__qualname__,
40
+ compose_error.message,
41
+ )
42
+ return False
43
+
44
+ try:
45
+ bundle_method = bundle(method, ctx | ({} if node_col is None else node_col.values))
46
+ bundle_method &= bundle(method, data, typebundle=True)
47
+ return await maybe_awaitable(bundle_method())
48
+ finally:
49
+ if node_col is not None:
50
+ await node_col.close_all()
51
+
45
52
 
53
+ class ABCMiddleware(ABC):
46
54
  def __repr__(self) -> str:
47
- name = f"middleware {self.__class__.__name__!r}:"
48
- has_pre = self.pre.__qualname__.split(".")[0] != "ABCMiddleware"
49
- has_post = self.post.__qualname__.split(".")[0] != "ABCMiddleware"
55
+ name = f"middleware {type(self).__name__!r}"
56
+ middleware_class = type(self)
50
57
 
51
- if has_post:
58
+ if middleware_class.post is not ABCMiddleware.post:
52
59
  name = "post-" + name
53
- if has_pre:
60
+
61
+ if middleware_class.pre is not ABCMiddleware.pre:
54
62
  name = "pre-" + name
55
63
 
56
- return "<{} with adapter={!r}>".format(name, self.adapter)
64
+ return f"<{name}>"
65
+
66
+ if typing.TYPE_CHECKING:
67
+
68
+ def pre(self, *args: typing.Any, **kwargs: typing.Any) -> MiddlewareResult: ...
69
+
70
+ def post(self, *args: typing.Any, **kwargs: typing.Any) -> MiddlewareResult: ...
57
71
 
58
- async def pre(self, event: Event, ctx: Context) -> bool: ...
72
+ else:
59
73
 
60
- async def post(self, event: Event, ctx: Context) -> None: ...
74
+ def pre(self, *args, **kwargs): ...
61
75
 
62
- @typing.overload
63
- def to_lifespan(self, event: Event, ctx: Context | None = None, *, api: API) -> Lifespan: ...
76
+ def post(self, *args, **kwargs): ...
64
77
 
65
- @typing.overload
66
- def to_lifespan(self, event: Event, ctx: Context | None = None) -> Lifespan: ...
78
+ @cached_property
79
+ def pre_required_nodes(self) -> dict[str, IsNode]:
80
+ return get_nodes(self.pre)
81
+
82
+ @cached_property
83
+ def post_required_nodes(self) -> dict[str, IsNode]:
84
+ return get_nodes(self.post)
67
85
 
68
86
  def to_lifespan(
69
87
  self,
70
88
  event: Event,
71
- ctx: Context | None = None,
89
+ ctx: Context,
90
+ *,
72
91
  api: API | None = None,
73
- **add_context: typing.Any,
74
92
  ) -> Lifespan:
93
+ if isinstance(event, BaseCute):
94
+ event, api = event.raw_update, event.api
95
+
75
96
  if api is None:
76
- if not isinstance(event, BaseCute):
77
- raise LookupError("Cannot get api, please pass as kwarg or provide BaseCute api-bound event")
78
- api = event.api
97
+ raise LookupError("Cannot get api, please pass as kwarg or provide BaseCute api-bound event.")
79
98
 
80
- ctx = ctx or Context()
81
- ctx |= add_context
82
99
  return Lifespan(
83
- startup_tasks=[run_middleware(self.pre, api, event, raw_event=None, ctx=ctx, adapter=None)],
100
+ startup_tasks=[
101
+ run_middleware(
102
+ self.pre,
103
+ api,
104
+ event,
105
+ ctx,
106
+ required_nodes=self.pre_required_nodes,
107
+ ),
108
+ ],
84
109
  shutdown_tasks=[
85
110
  run_middleware(
86
111
  self.post,
87
112
  api,
88
113
  event,
89
- raw_event=None,
90
- ctx=ctx,
91
- adapter=None,
92
- )
114
+ ctx,
115
+ required_nodes=self.post_required_nodes,
116
+ ),
93
117
  ],
94
118
  )
95
119
 
@@ -1,50 +1,36 @@
1
- import inspect
2
1
  import typing
3
2
  from contextlib import contextmanager
4
3
 
5
- from telegrinder.api import API
6
- from telegrinder.bot.cute_types.update import UpdateCute
4
+ from telegrinder.api.api import API
7
5
  from telegrinder.bot.dispatch.context import Context
8
6
  from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
9
7
  from telegrinder.bot.rules.abc import ABCRule, check_rule
10
- from telegrinder.node import IsNode, compose_nodes
11
- from telegrinder.tools.adapter.abc import ABCAdapter
12
- from telegrinder.tools.adapter.raw_update import RawUpdateAdapter
8
+ from telegrinder.node.base import IsNode
9
+ from telegrinder.node.composer import compose_nodes
13
10
  from telegrinder.types import Update
11
+ from telegrinder.types.objects import Update
14
12
 
15
13
 
16
14
  class GlobalMiddleware(ABCMiddleware):
17
- adapter = RawUpdateAdapter()
18
-
19
- def __init__(self):
15
+ def __init__(self) -> None:
20
16
  self.filters: set[ABCRule] = set()
21
- self.source_filters: dict[ABCAdapter | IsNode, dict[typing.Any, ABCRule]] = {}
17
+ self.source_filters: dict[IsNode, dict[typing.Any, ABCRule]] = {}
22
18
 
23
- async def pre(self, event: UpdateCute, ctx: Context) -> bool:
19
+ async def pre(self, event: Update, api: API, ctx: Context) -> bool:
24
20
  for filter in self.filters:
25
- if not await check_rule(event.api, filter, event, ctx):
21
+ if not await check_rule(api, filter, event, ctx):
26
22
  return False
27
23
 
28
- # Simple implication.... Grouped by source categories
24
+ # Simple implication. Grouped by source categories
29
25
  for source, identifiers in self.source_filters.items():
30
- if isinstance(source, ABCAdapter):
31
- result = source.adapt(event.api, event, ctx)
32
- if inspect.isawaitable(result):
33
- result = await result
34
-
35
- result = result.unwrap_or_none()
36
- if result is None:
37
- return True
38
-
26
+ result = await compose_nodes({"value": source}, ctx, {Update: event, API: api})
27
+ if result := result.unwrap():
28
+ result = result.values["value"]
39
29
  else:
40
- result = await compose_nodes({"value": source}, ctx, {Update: event, API: event.api})
41
- if result := result.unwrap():
42
- result = result.values["value"]
43
- else:
44
- return True
30
+ return True
45
31
 
46
32
  if result in identifiers:
47
- return await check_rule(event.api, identifiers[result], event, ctx)
33
+ return await check_rule(api, identifiers[result], event, ctx)
48
34
 
49
35
  return True
50
36
 
@@ -52,8 +38,8 @@ class GlobalMiddleware(ABCMiddleware):
52
38
  def apply_filters(
53
39
  self,
54
40
  *filters: ABCRule,
55
- source_filter: tuple[ABCAdapter | IsNode, typing.Any, ABCRule] | None = None,
56
- ):
41
+ source_filter: tuple[IsNode, typing.Any, ABCRule] | None = None,
42
+ ) -> typing.Generator[None, typing.Any, None]:
57
43
  if source_filter is not None:
58
44
  self.source_filters.setdefault(source_filter[0], {})
59
45
  self.source_filters[source_filter[0]].update({source_filter[1]: source_filter[2]})
@@ -62,9 +48,8 @@ class GlobalMiddleware(ABCMiddleware):
62
48
  yield
63
49
  self.filters.difference_update(filters)
64
50
 
65
- if source_filter is not None: # noqa: SIM102
66
- if identifiers := self.source_filters.get(source_filter[0]):
67
- identifiers.pop(source_filter[1], None)
51
+ if source_filter is not None and (identifiers := self.source_filters.get(source_filter[0])):
52
+ identifiers.pop(source_filter[1], None)
68
53
 
69
54
 
70
55
  __all__ = ("GlobalMiddleware",)
@@ -1,151 +1,130 @@
1
+ from __future__ import annotations
2
+
1
3
  import typing
2
4
 
3
- from fntypes.option import Nothing, Some
5
+ from fntypes.result import Error, Ok
4
6
 
5
7
  from telegrinder.api.api import API
6
- from telegrinder.bot.cute_types.update import UpdateCute
7
8
  from telegrinder.bot.dispatch.context import Context
8
9
  from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware, run_middleware
9
- from telegrinder.bot.dispatch.return_manager.abc import ABCReturnManager
10
- from telegrinder.model import Model
11
10
  from telegrinder.modules import logger
12
- from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, NodeScope, compose_nodes
13
- from telegrinder.tools.adapter.abc import run_adapter
14
- from telegrinder.tools.i18n.abc import I18nEnum
11
+ from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, compose_nodes
12
+ from telegrinder.tools.aio import maybe_awaitable
13
+ from telegrinder.tools.fullname import fullname
14
+ from telegrinder.tools.magic.function import bundle
15
15
  from telegrinder.types.objects import Update
16
16
 
17
17
  if typing.TYPE_CHECKING:
18
- from telegrinder.bot.dispatch.handler.abc import ABCHandler
18
+ from telegrinder.bot.dispatch.view.base import BaseView
19
19
  from telegrinder.bot.rules.abc import ABCRule
20
20
 
21
21
 
22
- async def process_inner[Event: Model](
22
+ async def process_inner(
23
23
  api: API,
24
- event: Event,
25
- raw_event: Update,
24
+ event: Update,
26
25
  ctx: Context,
27
- middlewares: list[ABCMiddleware[Event]],
28
- handlers: list["ABCHandler[Event]"],
29
- return_manager: ABCReturnManager[Event] | None = None,
26
+ view: BaseView,
30
27
  ) -> bool:
31
- logger.debug("Processing {!r}...", event.__class__.__name__)
32
28
  ctx[CONTEXT_STORE_NODES_KEY] = {} # For per-event shared nodes
33
29
 
34
- logger.debug("Run pre middlewares...")
35
- for m in middlewares:
36
- result = await run_middleware(m.pre, api, event, raw_event=raw_event, ctx=ctx, adapter=m.adapter)
37
- logger.debug("Middleware {!r} returned: {!r}", m, result)
38
- if result is False:
30
+ for m in view.middlewares:
31
+ if (
32
+ type(m).pre is not ABCMiddleware.pre
33
+ and await run_middleware(m.pre, api, event, ctx, required_nodes=m.pre_required_nodes) is False
34
+ ):
35
+ logger.info(
36
+ "Update(id={}, type={!r}) processed with view `{}`. Pre-middleware `{}` raised failure.",
37
+ event.update_id,
38
+ event.update_type,
39
+ type(view).__name__,
40
+ fullname(m),
41
+ )
39
42
  return False
40
43
 
41
- found = False
42
- responses = []
44
+ found_handlers = []
45
+ responses = list[typing.Any]()
43
46
  ctx_copy = ctx.copy()
44
47
 
45
- for handler in handlers:
46
- adapted_event = event
47
-
48
- if await handler.check(api, raw_event, ctx):
49
- if handler.adapter is not None:
50
- match await run_adapter(handler.adapter, api, raw_event, ctx):
51
- case Some(value):
52
- adapted_event = value
53
- case Nothing():
54
- continue
55
-
56
- found = True
57
- logger.debug("Handler {!r} matched, run...", handler)
58
- response = await handler.run(api, adapted_event, ctx)
59
- logger.debug("Handler {!r} returned: {!r}", handler, response)
60
- responses.append(response)
61
-
62
- if return_manager is not None:
63
- await return_manager.run(response, event, ctx)
64
- if handler.final:
65
- break
66
-
67
- ctx = ctx_copy
68
-
69
- logger.debug("Run post middlewares...")
70
- ctx.set("responses", responses)
71
-
72
- for m in middlewares:
73
- await run_middleware(
74
- m.post,
75
- api,
76
- event,
77
- raw_event=raw_event,
78
- ctx=ctx,
79
- adapter=m.adapter,
80
- )
81
-
82
- for session in ctx.get(CONTEXT_STORE_NODES_KEY, {}).values():
83
- await session.close(scopes=(NodeScope.PER_EVENT,))
84
-
85
- logger.debug(
86
- "{} handlers, returns {!r}",
87
- "No found" if not found else "Found",
88
- found,
48
+ for handler in view.handlers:
49
+ match await handler.run(api, event, ctx):
50
+ case Ok(response):
51
+ found_handlers.append(handler)
52
+ responses.append(response)
53
+
54
+ if view.return_manager is not None:
55
+ await view.return_manager.run(response, api, event, ctx)
56
+
57
+ if handler.final is True:
58
+ break
59
+ case Error(error):
60
+ logger.debug(error)
61
+
62
+ ctx = ctx_copy
63
+ ctx.responses = responses
64
+
65
+ for m in view.middlewares:
66
+ if type(m).post is not ABCMiddleware.post:
67
+ await run_middleware(
68
+ m.post,
69
+ api,
70
+ event,
71
+ ctx,
72
+ required_nodes=m.post_required_nodes,
73
+ )
74
+
75
+ logger.info(
76
+ "Update(id={}, type={!r}) processed with view `{}`. {}",
77
+ event.update_id,
78
+ event.update_type,
79
+ type(view).__name__,
80
+ "No found corresponded handlers."
81
+ if not found_handlers
82
+ else f"Handler{'s' if len(found_handlers) > 1 else ''}: {', '.join(f'`{x!r}`' for x in found_handlers)}",
89
83
  )
90
- return found
84
+ return bool(found_handlers)
91
85
 
92
86
 
93
87
  async def check_rule(
94
88
  api: API,
95
- rule: "ABCRule",
89
+ rule: ABCRule,
96
90
  update: Update,
97
91
  ctx: Context,
98
92
  ) -> bool:
99
93
  """Checks requirements, adapts update.
100
94
  Returns check result.
101
95
  """
102
- update_cute = None if not isinstance(update, UpdateCute) else update
103
-
104
- # Running adapter
105
- match await run_adapter(rule.adapter, api, update, ctx):
106
- case Some(val):
107
- adapted_value = val
108
- case Nothing():
109
- return False
110
-
111
- # Preparing update
112
- if isinstance(adapted_value, UpdateCute):
113
- update_cute = adapted_value
114
- elif isinstance(adapted_val := ctx.get(rule.adapter.ADAPTED_VALUE_KEY or ""), UpdateCute):
115
- update_cute = adapted_val
116
- else:
117
- update_cute = UpdateCute.from_update(update, bound_api=api)
118
-
119
96
  # Running subrules to fetch requirements
120
97
  ctx_copy = ctx.copy()
121
98
  for requirement in rule.requires:
122
- if not await check_rule(api, requirement, update_cute, ctx_copy):
99
+ if not await check_rule(api, requirement, update, ctx_copy):
123
100
  return False
124
101
 
125
- # Translating translatable rules
126
- if I18nEnum.I18N in ctx:
127
- rule = await rule.translate(ctx[I18nEnum.I18N])
128
-
129
102
  ctx |= ctx_copy
103
+ node_col = None
104
+ data = {Update: update, API: api, Context: ctx}
130
105
 
131
106
  # Composing required nodes
132
- nodes = rule.required_nodes
133
- node_col = None
134
- if nodes:
135
- result = await compose_nodes(nodes, ctx, data={Update: update, API: api})
136
- if not result:
137
- logger.debug(f"Cannot compose nodes for rule, error: {str(result.error)}")
138
- return False
139
- node_col = result.value
107
+ if rule.required_nodes:
108
+ match await compose_nodes(rule.required_nodes, ctx, data=data):
109
+ case Ok(value):
110
+ node_col = value
111
+ case Error(compose_error):
112
+ logger.debug(
113
+ "Cannot compose nodes for rule `{!r}`, error: {!r}",
114
+ rule,
115
+ compose_error.message,
116
+ )
117
+ return False
140
118
 
141
119
  # Running check
142
- result = await rule.bounding_check(ctx, adapted_value=adapted_value, node_col=node_col)
143
-
144
- # Closing node sessions if there are any
145
- if node_col is not None:
146
- await node_col.close_all()
147
-
148
- return result
120
+ try:
121
+ bundle_check = bundle(rule.check, data, typebundle=True)
122
+ bundle_check &= bundle(rule.check, ctx | ({} if node_col is None else node_col.values))
123
+ return await maybe_awaitable(bundle_check())
124
+ finally:
125
+ # Closing node sessions if there are any
126
+ if node_col is not None:
127
+ await node_col.close_all()
149
128
 
150
129
 
151
130
  __all__ = ("check_rule", "process_inner")
File without changes