telegrinder 0.4.2__py3-none-any.whl → 0.5.1__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 +37 -55
  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 +98 -67
  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 +68 -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 +1782 -994
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.1.dist-info/METADATA +162 -0
  198. telegrinder-0.5.1.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/licenses/LICENSE +2 -2
  200. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
  201. telegrinder/bot/rules/id.py +0 -24
  202. telegrinder/bot/rules/message.py +0 -15
  203. telegrinder/client/sonic.py +0 -212
  204. telegrinder/msgspec_utils.py +0 -478
  205. telegrinder/tools/adapter/__init__.py +0 -19
  206. telegrinder/tools/adapter/abc.py +0 -49
  207. telegrinder/tools/adapter/dataclass.py +0 -56
  208. telegrinder/tools/adapter/errors.py +0 -5
  209. telegrinder/tools/adapter/event.py +0 -61
  210. telegrinder/tools/adapter/node.py +0 -46
  211. telegrinder/tools/adapter/raw_event.py +0 -27
  212. telegrinder/tools/adapter/raw_update.py +0 -30
  213. telegrinder/tools/callback_data_serilization/__init__.py +0 -5
  214. telegrinder/tools/error_handler/__init__.py +0 -10
  215. telegrinder/tools/error_handler/abc.py +0 -30
  216. telegrinder/tools/error_handler/error.py +0 -9
  217. telegrinder/tools/error_handler/error_handler.py +0 -179
  218. telegrinder/tools/formatting/spec_html_formats.py +0 -75
  219. telegrinder/tools/functional.py +0 -8
  220. telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
  221. telegrinder/tools/i18n/__init__.py +0 -12
  222. telegrinder/tools/i18n/abc.py +0 -32
  223. telegrinder/tools/i18n/middleware/__init__.py +0 -3
  224. telegrinder/tools/i18n/middleware/abc.py +0 -22
  225. telegrinder/tools/i18n/simple.py +0 -43
  226. telegrinder/tools/keyboard.py +0 -132
  227. telegrinder/tools/loop_wrapper/__init__.py +0 -4
  228. telegrinder/tools/loop_wrapper/abc.py +0 -20
  229. telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
  230. telegrinder/tools/magic.py +0 -344
  231. telegrinder-0.4.2.dist-info/METADATA +0 -151
  232. telegrinder-0.4.2.dist-info/RECORD +0 -182
  233. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/WHEEL +0 -0
@@ -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, bundle_ctx=True))
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
 
@@ -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