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,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
@@ -2,97 +2,135 @@ import dataclasses
2
2
  import types
3
3
  import typing
4
4
  from abc import ABC, abstractmethod
5
+ from functools import cached_property
5
6
 
7
+ from fntypes.result import Error, Ok
8
+
9
+ from telegrinder.api.api import API
6
10
  from telegrinder.bot.dispatch.context import Context
7
- from telegrinder.model import Model
8
11
  from telegrinder.modules import logger
12
+ from telegrinder.node.base import IsNode, get_nodes
13
+ from telegrinder.node.composer import compose_nodes
14
+ from telegrinder.tools import fullname
15
+ from telegrinder.tools.aio import maybe_awaitable
16
+ from telegrinder.tools.magic.function import bundle
17
+ from telegrinder.types.objects import Update
18
+
19
+ type ManagerFunction = typing.Callable[..., typing.Any | typing.Awaitable[typing.Any]]
20
+
21
+
22
+ def _get_types(x: typing.Any, /) -> type[typing.Any] | tuple[typing.Any, ...]:
23
+ while True:
24
+ if isinstance(x, types.UnionType | typing._UnionGenericAlias): # type: ignore
25
+ return tuple(_get_types(x) for x in typing.get_args(x))
26
+
27
+ if isinstance(x, typing.TypeAliasType):
28
+ x = x.__value__
9
29
 
30
+ if isinstance(x, types.GenericAlias | typing._GenericAlias): # type: ignore
31
+ x = typing.get_origin(x)
10
32
 
11
- def get_union_types(t: types.UnionType | typing.Any) -> tuple[type[typing.Any], ...] | None:
12
- if type(t) in (types.UnionType, typing._UnionGenericAlias): # type: ignore
13
- return tuple(typing.get_origin(x) or x for x in typing.get_args(t))
14
- return None
33
+ if isinstance(x, type):
34
+ return x
15
35
 
16
36
 
17
- def register_manager(return_type: type[typing.Any] | types.UnionType):
18
- def wrapper(func: typing.Callable[..., typing.Awaitable[typing.Any]]):
19
- return Manager(get_union_types(return_type) or (return_type,), func) # type: ignore
37
+ def register_manager(return_type: typing.Any, /) -> typing.Callable[[ManagerFunction], "Manager"]:
38
+ def wrapper(function: ManagerFunction, /) -> "Manager":
39
+ function = function.__func__ if isinstance(function, classmethod | staticmethod) else function
40
+ types = _get_types(return_type)
41
+ return Manager((types,) if not isinstance(types, tuple) else types, function)
20
42
 
21
43
  return wrapper
22
44
 
23
45
 
24
- @dataclasses.dataclass(frozen=True, slots=True)
46
+ @dataclasses.dataclass
25
47
  class Manager:
26
- types: tuple[type, ...]
27
- callback: typing.Callable[..., typing.Awaitable[typing.Any]]
48
+ types: tuple[typing.Any, ...]
49
+ function: ManagerFunction
28
50
 
29
- async def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
30
- await self.callback(*args, **kwargs)
51
+ @cached_property
52
+ def required_nodes(self) -> dict[str, IsNode]:
53
+ return get_nodes(self.function, start_idx=1)
31
54
 
55
+ async def __call__(
56
+ self,
57
+ response: typing.Any,
58
+ update: Update,
59
+ api: API,
60
+ context: Context,
61
+ ) -> None:
62
+ data = {Update: update, API: api}
63
+ node_col = None
64
+
65
+ if self.required_nodes:
66
+ match await compose_nodes(self.required_nodes, context, data=data):
67
+ case Ok(value):
68
+ node_col = value
69
+ case Error(compose_error):
70
+ logger.debug(
71
+ "Cannot compose nodes for return manager `{}`, error {!r}",
72
+ fullname(self.function),
73
+ compose_error.message,
74
+ )
75
+ return None
76
+
77
+ temp_ctx = context.copy()
78
+ try:
79
+ bundle_function = bundle(self.function, {**data, Context: temp_ctx}, typebundle=True)
80
+ bundle_function &= bundle(
81
+ self.function,
82
+ context | ({} if node_col is None else node_col.values),
83
+ )
84
+ await maybe_awaitable(bundle_function(response))
85
+ finally:
86
+ context |= temp_ctx
87
+
88
+ if node_col is not None:
89
+ await node_col.close_all()
90
+
91
+
92
+ class ABCReturnManager(ABC):
93
+ @property
94
+ @abstractmethod
95
+ def managers(self) -> list[Manager]:
96
+ pass
32
97
 
33
- class ABCReturnManager[Event: Model](ABC):
34
98
  @abstractmethod
35
- async def run(self, response: typing.Any, event: Event, ctx: Context) -> None:
99
+ async def run(self, response: typing.Any, api: API, update: Update, context: Context) -> None:
36
100
  pass
37
101
 
38
102
 
39
- class BaseReturnManager[Event: Model](ABCReturnManager[Event]):
103
+ class BaseReturnManager(ABCReturnManager):
40
104
  def __repr__(self) -> str:
41
- return "<{}: {}>".format(
42
- self.__class__.__name__,
43
- ", ".join(x.callback.__name__ + "=" + repr(x) for x in self.managers),
44
- )
105
+ return "<{}: {}>".format(fullname(self), self.managers)
45
106
 
46
- @property
107
+ @cached_property
47
108
  def managers(self) -> list[Manager]:
48
- managers = self.__dict__.get("managers")
49
- if managers is not None:
50
- return managers
51
- managers_lst = [
109
+ return [
52
110
  manager
53
- for manager in (vars(BaseReturnManager) | vars(self.__class__)).values()
111
+ for manager in (vars(BaseReturnManager) | vars(type(self))).values()
54
112
  if isinstance(manager, Manager)
55
113
  ]
56
- self.__dict__["managers"] = managers_lst
57
- return managers_lst
58
-
59
- @register_manager(Context)
60
- @staticmethod
61
- async def ctx_manager(value: Context, event: Event, ctx: Context) -> None:
62
- """Basic manager for returning context from handler."""
63
- ctx.update(value)
64
114
 
65
- async def run(self, response: typing.Any, event: Event, ctx: Context) -> None:
66
- logger.debug("Run return manager for response: {!r}", response)
67
- for manager in self.managers:
68
- if typing.Any in manager.types or any(type(response) is x for x in manager.types):
69
- logger.debug("Run manager {!r}...", manager.callback.__name__)
70
- await manager(response, event, ctx)
71
-
72
- @typing.overload
73
- def register_manager[T](
115
+ async def run(
74
116
  self,
75
- return_type: type[T],
76
- ) -> typing.Callable[[typing.Callable[[T, Event, Context], typing.Awaitable[typing.Any]]], Manager]: ...
77
-
78
- @typing.overload
79
- def register_manager[T](
80
- self,
81
- return_type: tuple[type[T], ...],
82
- ) -> typing.Callable[
83
- [typing.Callable[[tuple[T, ...], Event, Context], typing.Awaitable[typing.Any]]],
84
- Manager,
85
- ]: ...
86
-
87
- def register_manager[T](
88
- self,
89
- return_type: type[T] | tuple[type[T], ...],
90
- ) -> typing.Callable[
91
- [typing.Callable[[T | tuple[T, ...], Event, Context], typing.Awaitable[typing.Any]]],
92
- Manager,
93
- ]:
94
- def wrapper(func: typing.Callable[[T, Event, Context], typing.Awaitable]) -> Manager:
95
- manager = Manager(get_union_types(return_type) or (return_type,), func) # type: ignore
117
+ response: typing.Any,
118
+ api: API,
119
+ update: Update,
120
+ context: Context,
121
+ ) -> None:
122
+ for manager in self.managers:
123
+ if typing.Any in manager.types or type(response) in manager.types:
124
+ logger.debug(
125
+ "Running manager `{}` for response `{!r}`",
126
+ fullname(manager.function),
127
+ response,
128
+ )
129
+ await manager(response, update, api, context)
130
+
131
+ def register_manager(self, return_type: typing.Any, /) -> typing.Callable[[ManagerFunction], Manager]:
132
+ def wrapper(function: ManagerFunction, /) -> Manager:
133
+ manager = register_manager(return_type)(function)
96
134
  self.managers.append(manager)
97
135
  return manager
98
136
 
@@ -103,6 +141,5 @@ __all__ = (
103
141
  "ABCReturnManager",
104
142
  "BaseReturnManager",
105
143
  "Manager",
106
- "get_union_types",
107
144
  "register_manager",
108
145
  )
@@ -1,19 +1,18 @@
1
1
  import typing
2
2
 
3
3
  from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
4
- from telegrinder.bot.dispatch.context import Context
5
4
  from telegrinder.bot.dispatch.return_manager.abc import BaseReturnManager, register_manager
6
5
 
7
6
 
8
- class CallbackQueryReturnManager(BaseReturnManager[CallbackQueryCute]):
7
+ class CallbackQueryReturnManager(BaseReturnManager):
9
8
  @register_manager(str)
10
9
  @staticmethod
11
- async def str_manager(value: str, event: CallbackQueryCute, ctx: Context) -> None:
10
+ async def str_manager(value: str, event: CallbackQueryCute) -> None:
12
11
  await event.answer(value)
13
12
 
14
- @register_manager(dict[str, typing.Any])
13
+ @register_manager(dict)
15
14
  @staticmethod
16
- async def dict_manager(value: dict[str, typing.Any], event: CallbackQueryCute, ctx: Context) -> None:
15
+ async def dict_manager(value: dict[str, typing.Any], event: CallbackQueryCute) -> None:
17
16
  await event.answer(**value)
18
17
 
19
18
 
@@ -1,14 +1,13 @@
1
1
  import typing
2
2
 
3
3
  from telegrinder.bot.cute_types.inline_query import InlineQueryCute
4
- from telegrinder.bot.dispatch.context import Context
5
4
  from telegrinder.bot.dispatch.return_manager.abc import BaseReturnManager, register_manager
6
5
 
7
6
 
8
- class InlineQueryReturnManager(BaseReturnManager[InlineQueryCute]):
9
- @register_manager(dict[str, typing.Any])
7
+ class InlineQueryReturnManager(BaseReturnManager):
8
+ @register_manager(dict)
10
9
  @staticmethod
11
- async def dict_manager(value: dict[str, typing.Any], event: InlineQueryCute, ctx: Context) -> None:
10
+ async def dict_manager(value: dict[str, typing.Any], event: InlineQueryCute) -> None:
12
11
  await event.answer(**value)
13
12
 
14
13
 
@@ -1,35 +1,33 @@
1
1
  import typing
2
2
 
3
3
  from telegrinder.bot.cute_types.message import MessageCute
4
- from telegrinder.bot.dispatch.context import Context
5
4
  from telegrinder.bot.dispatch.return_manager.abc import BaseReturnManager, register_manager
6
5
  from telegrinder.tools.formatting import HTMLFormatter
7
6
 
8
7
 
9
- class MessageReturnManager(BaseReturnManager[MessageCute]):
8
+ class MessageReturnManager(BaseReturnManager):
10
9
  @register_manager(str)
11
10
  @staticmethod
12
- async def str_manager(value: str, event: MessageCute, ctx: Context) -> None:
11
+ async def str_manager(value: str, event: MessageCute) -> None:
13
12
  await event.answer(value)
14
13
 
15
- @register_manager(list[str] | tuple[str, ...])
14
+ @register_manager(list | tuple)
16
15
  @staticmethod
17
16
  async def seq_manager(
18
- value: list[str] | tuple[str, ...],
17
+ value: list[typing.Any] | tuple[typing.Any, ...],
19
18
  event: MessageCute,
20
- ctx: Context,
21
19
  ) -> None:
22
20
  for message in value:
23
- await event.answer(message)
21
+ await event.answer(str(message))
24
22
 
25
- @register_manager(dict[str, typing.Any])
23
+ @register_manager(dict)
26
24
  @staticmethod
27
- async def dict_manager(value: dict[str, typing.Any], event: MessageCute, ctx: Context) -> None:
25
+ async def dict_manager(value: dict[str, typing.Any], event: MessageCute) -> None:
28
26
  await event.answer(**value)
29
27
 
30
28
  @register_manager(HTMLFormatter)
31
29
  @staticmethod
32
- async def htmlformatter_manager(value: HTMLFormatter, event: MessageCute, ctx: Context) -> None:
30
+ async def htmlformatter_manager(value: HTMLFormatter, event: MessageCute) -> None:
33
31
  await event.answer(value, parse_mode=HTMLFormatter.PARSE_MODE)
34
32
 
35
33
 
@@ -1,19 +1,18 @@
1
1
  import typing
2
2
 
3
3
  from telegrinder.bot.cute_types.pre_checkout_query import PreCheckoutQueryCute
4
- from telegrinder.bot.dispatch.context import Context
5
4
  from telegrinder.bot.dispatch.return_manager.abc import BaseReturnManager, register_manager
6
5
 
7
6
 
8
- class PreCheckoutQueryManager(BaseReturnManager[PreCheckoutQueryCute]):
7
+ class PreCheckoutQueryManager(BaseReturnManager):
9
8
  @register_manager(bool)
10
9
  @staticmethod
11
- async def bool_manager(value: bool, event: PreCheckoutQueryCute, ctx: Context) -> None:
10
+ async def bool_manager(value: bool, event: PreCheckoutQueryCute) -> None:
12
11
  await event.answer(value)
13
12
 
14
- @register_manager(dict[str, typing.Any])
13
+ @register_manager(dict)
15
14
  @staticmethod
16
- async def dict_manager(value: dict[str, typing.Any], event: PreCheckoutQueryCute, ctx: Context) -> None:
15
+ async def dict_manager(value: dict[str, typing.Any], event: PreCheckoutQueryCute) -> None:
17
16
  await event.answer(**value)
18
17
 
19
18