telegrinder 0.1.dev20__py3-none-any.whl → 0.1.dev158__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 (132) hide show
  1. telegrinder/__init__.py +129 -22
  2. telegrinder/api/__init__.py +11 -2
  3. telegrinder/api/abc.py +25 -9
  4. telegrinder/api/api.py +29 -24
  5. telegrinder/api/error.py +14 -4
  6. telegrinder/api/response.py +11 -7
  7. telegrinder/bot/__init__.py +68 -7
  8. telegrinder/bot/bot.py +30 -24
  9. telegrinder/bot/cute_types/__init__.py +11 -1
  10. telegrinder/bot/cute_types/base.py +47 -0
  11. telegrinder/bot/cute_types/callback_query.py +64 -14
  12. telegrinder/bot/cute_types/inline_query.py +22 -16
  13. telegrinder/bot/cute_types/message.py +145 -53
  14. telegrinder/bot/cute_types/update.py +23 -0
  15. telegrinder/bot/dispatch/__init__.py +56 -3
  16. telegrinder/bot/dispatch/abc.py +9 -7
  17. telegrinder/bot/dispatch/composition.py +74 -0
  18. telegrinder/bot/dispatch/context.py +71 -0
  19. telegrinder/bot/dispatch/dispatch.py +86 -49
  20. telegrinder/bot/dispatch/handler/__init__.py +3 -0
  21. telegrinder/bot/dispatch/handler/abc.py +11 -5
  22. telegrinder/bot/dispatch/handler/func.py +41 -32
  23. telegrinder/bot/dispatch/handler/message_reply.py +46 -0
  24. telegrinder/bot/dispatch/middleware/__init__.py +2 -0
  25. telegrinder/bot/dispatch/middleware/abc.py +10 -4
  26. telegrinder/bot/dispatch/process.py +53 -49
  27. telegrinder/bot/dispatch/return_manager/__init__.py +19 -0
  28. telegrinder/bot/dispatch/return_manager/abc.py +95 -0
  29. telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
  30. telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
  31. telegrinder/bot/dispatch/return_manager/message.py +25 -0
  32. telegrinder/bot/dispatch/view/__init__.py +14 -2
  33. telegrinder/bot/dispatch/view/abc.py +121 -2
  34. telegrinder/bot/dispatch/view/box.py +38 -0
  35. telegrinder/bot/dispatch/view/callback_query.py +13 -39
  36. telegrinder/bot/dispatch/view/inline_query.py +11 -39
  37. telegrinder/bot/dispatch/view/message.py +11 -47
  38. telegrinder/bot/dispatch/waiter_machine/__init__.py +9 -0
  39. telegrinder/bot/dispatch/waiter_machine/machine.py +116 -0
  40. telegrinder/bot/dispatch/waiter_machine/middleware.py +76 -0
  41. telegrinder/bot/dispatch/waiter_machine/short_state.py +37 -0
  42. telegrinder/bot/polling/__init__.py +2 -0
  43. telegrinder/bot/polling/abc.py +11 -4
  44. telegrinder/bot/polling/polling.py +89 -40
  45. telegrinder/bot/rules/__init__.py +91 -5
  46. telegrinder/bot/rules/abc.py +81 -63
  47. telegrinder/bot/rules/adapter/__init__.py +11 -0
  48. telegrinder/bot/rules/adapter/abc.py +21 -0
  49. telegrinder/bot/rules/adapter/errors.py +5 -0
  50. telegrinder/bot/rules/adapter/event.py +43 -0
  51. telegrinder/bot/rules/adapter/raw_update.py +24 -0
  52. telegrinder/bot/rules/callback_data.py +159 -38
  53. telegrinder/bot/rules/command.py +116 -0
  54. telegrinder/bot/rules/enum_text.py +28 -0
  55. telegrinder/bot/rules/func.py +17 -17
  56. telegrinder/bot/rules/fuzzy.py +13 -10
  57. telegrinder/bot/rules/inline.py +61 -0
  58. telegrinder/bot/rules/integer.py +12 -7
  59. telegrinder/bot/rules/is_from.py +148 -7
  60. telegrinder/bot/rules/markup.py +21 -18
  61. telegrinder/bot/rules/mention.py +17 -0
  62. telegrinder/bot/rules/message_entities.py +33 -0
  63. telegrinder/bot/rules/regex.py +27 -19
  64. telegrinder/bot/rules/rule_enum.py +74 -0
  65. telegrinder/bot/rules/start.py +25 -13
  66. telegrinder/bot/rules/text.py +23 -14
  67. telegrinder/bot/scenario/__init__.py +2 -0
  68. telegrinder/bot/scenario/abc.py +12 -5
  69. telegrinder/bot/scenario/checkbox.py +48 -30
  70. telegrinder/bot/scenario/choice.py +16 -10
  71. telegrinder/client/__init__.py +2 -0
  72. telegrinder/client/abc.py +8 -21
  73. telegrinder/client/aiohttp.py +30 -21
  74. telegrinder/model.py +68 -37
  75. telegrinder/modules.py +189 -21
  76. telegrinder/msgspec_json.py +14 -0
  77. telegrinder/msgspec_utils.py +207 -0
  78. telegrinder/node/__init__.py +31 -0
  79. telegrinder/node/attachment.py +71 -0
  80. telegrinder/node/base.py +93 -0
  81. telegrinder/node/composer.py +71 -0
  82. telegrinder/node/container.py +22 -0
  83. telegrinder/node/message.py +18 -0
  84. telegrinder/node/rule.py +56 -0
  85. telegrinder/node/source.py +31 -0
  86. telegrinder/node/text.py +13 -0
  87. telegrinder/node/tools/__init__.py +3 -0
  88. telegrinder/node/tools/generator.py +40 -0
  89. telegrinder/node/update.py +12 -0
  90. telegrinder/rules.py +1 -1
  91. telegrinder/tools/__init__.py +165 -4
  92. telegrinder/tools/buttons.py +75 -51
  93. telegrinder/tools/error_handler/__init__.py +8 -0
  94. telegrinder/tools/error_handler/abc.py +30 -0
  95. telegrinder/tools/error_handler/error_handler.py +156 -0
  96. telegrinder/tools/formatting/__init__.py +81 -3
  97. telegrinder/tools/formatting/html.py +283 -37
  98. telegrinder/tools/formatting/links.py +32 -0
  99. telegrinder/tools/formatting/spec_html_formats.py +121 -0
  100. telegrinder/tools/global_context/__init__.py +12 -0
  101. telegrinder/tools/global_context/abc.py +66 -0
  102. telegrinder/tools/global_context/global_context.py +451 -0
  103. telegrinder/tools/global_context/telegrinder_ctx.py +25 -0
  104. telegrinder/tools/i18n/__init__.py +12 -0
  105. telegrinder/tools/i18n/base.py +31 -0
  106. telegrinder/tools/i18n/middleware/__init__.py +3 -0
  107. telegrinder/tools/i18n/middleware/base.py +26 -0
  108. telegrinder/tools/i18n/simple.py +48 -0
  109. telegrinder/tools/inline_query.py +684 -0
  110. telegrinder/tools/kb_set/__init__.py +2 -0
  111. telegrinder/tools/kb_set/base.py +3 -0
  112. telegrinder/tools/kb_set/yaml.py +28 -17
  113. telegrinder/tools/keyboard.py +84 -62
  114. telegrinder/tools/loop_wrapper/__init__.py +4 -0
  115. telegrinder/tools/loop_wrapper/abc.py +18 -0
  116. telegrinder/tools/loop_wrapper/loop_wrapper.py +132 -0
  117. telegrinder/tools/magic.py +48 -23
  118. telegrinder/tools/parse_mode.py +1 -2
  119. telegrinder/types/__init__.py +1 -0
  120. telegrinder/types/enums.py +651 -0
  121. telegrinder/types/methods.py +3920 -1251
  122. telegrinder/types/objects.py +4702 -1718
  123. {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev158.dist-info}/LICENSE +2 -1
  124. telegrinder-0.1.dev158.dist-info/METADATA +108 -0
  125. telegrinder-0.1.dev158.dist-info/RECORD +126 -0
  126. {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev158.dist-info}/WHEEL +1 -1
  127. telegrinder/bot/dispatch/waiter.py +0 -38
  128. telegrinder/result.py +0 -38
  129. telegrinder/tools/formatting/abc.py +0 -52
  130. telegrinder/tools/formatting/markdown.py +0 -57
  131. telegrinder-0.1.dev20.dist-info/METADATA +0 -22
  132. telegrinder-0.1.dev20.dist-info/RECORD +0 -71
@@ -1,68 +1,76 @@
1
1
  import asyncio
2
+ import dataclasses
3
+ import typing
4
+
5
+ from vbml.patcher import Patcher
2
6
 
3
- from .abc import ABCDispatch
4
- from telegrinder.bot.rules import ABCRule
5
- from .handler import ABCHandler, FuncHandler
6
- from telegrinder.types import Update
7
7
  from telegrinder.api.abc import ABCAPI
8
+ from telegrinder.bot.cute_types.base import BaseCute
9
+ from telegrinder.bot.rules import ABCRule
8
10
  from telegrinder.modules import logger
9
- from .view import ABCView, MessageView, CallbackQueryView, InlineQueryView
10
- import typing
11
+ from telegrinder.tools.global_context import TelegrinderCtx
12
+ from telegrinder.types import Update
13
+
14
+ from .abc import ABCDispatch
15
+ from .handler import ABCHandler, FuncHandler
16
+ from .handler.func import ErrorHandlerT
17
+ from .view.box import CallbackQueryViewT, InlineQueryViewT, MessageViewT, ViewBox
11
18
 
12
19
  T = typing.TypeVar("T")
13
20
 
21
+ Event = typing.TypeVar("Event", bound=BaseCute)
22
+ R = typing.TypeVar("R")
23
+ P = typing.ParamSpec("P")
24
+
14
25
  DEFAULT_DATACLASS = Update
15
26
 
16
27
 
17
- class Dispatch(ABCDispatch):
18
- def __init__(self):
19
- self.default_handlers: typing.List[ABCHandler] = []
20
- self.message = MessageView()
21
- self.callback_query = CallbackQueryView()
22
- self.inline_query = InlineQueryView()
23
- self.views = ["message", "callback_query", "inline_query"]
28
+ @dataclasses.dataclass(repr=False, kw_only=True)
29
+ class Dispatch(
30
+ ABCDispatch,
31
+ ViewBox[CallbackQueryViewT, InlineQueryViewT, MessageViewT],
32
+ ):
33
+ global_context: TelegrinderCtx = dataclasses.field(
34
+ init=False,
35
+ default_factory=lambda: TelegrinderCtx(),
36
+ )
37
+ default_handlers: list[ABCHandler] = dataclasses.field(
38
+ init=False,
39
+ default_factory=lambda: [],
40
+ )
41
+
42
+ def __repr__(self) -> str:
43
+ return "Dispatch(%s)" % ", ".join(
44
+ f"{k}={v!r}" for k, v in self.__dict__.items()
45
+ )
46
+
47
+ @property
48
+ def patcher(self) -> Patcher:
49
+ """Alias `patcher` to get `vbml.Patcher` from the global context"""
50
+ return self.global_context.vbml_patcher
24
51
 
25
52
  def handle(
26
53
  self,
27
54
  *rules: ABCRule,
28
55
  is_blocking: bool = True,
29
- dataclass: typing.Any = DEFAULT_DATACLASS,
56
+ dataclass: type[typing.Any] = DEFAULT_DATACLASS,
57
+ error_handler: ErrorHandlerT | None = None,
30
58
  ):
31
59
  def wrapper(func: typing.Callable):
32
- self.default_handlers.append(
33
- FuncHandler(func, list(rules), is_blocking, dataclass)
60
+ handler = FuncHandler(
61
+ func, list(rules), is_blocking, dataclass, error_handler,
34
62
  )
35
- return func
63
+ self.default_handlers.append(handler)
64
+ return handler
36
65
 
37
66
  return wrapper
38
67
 
39
- def get_views(self) -> typing.Iterator[ABCView]:
40
- for view_name in self.views:
41
- view = getattr(self, view_name)
42
- assert view, f"View {view_name} is undefined in dispatch"
43
- yield view
44
-
45
- def get_view(self, view_t: typing.Type[T], name: str) -> typing.Optional[T]:
46
- if name not in self.views:
47
- return None
48
- view = getattr(self, name)
49
- assert isinstance(view, view_t)
50
- return view # type: ignore
51
-
52
- def load(self, external: "Dispatch"):
53
- for view_name in self.views:
54
- view = getattr(self, view_name)
55
- assert view, f"View {view_name} is undefined in dispatch"
56
- view_external = getattr(external, view_name)
57
- assert view_external, f"View {view_name} is undefined in external dispatch"
58
- view.load(view_external)
59
-
60
68
  async def feed(self, event: Update, api: ABCAPI) -> bool:
61
- logger.debug("processing update (update_id={})", event.update_id)
62
- for view in self.get_views():
69
+ logger.debug("Processing update (update_id={})", event.update_id)
70
+ for view in self.get_views().values():
63
71
  if await view.check(event):
64
72
  logger.debug(
65
- "update {} matched view {}",
73
+ "Update (update_id={}) matched view {!r}",
66
74
  event.update_id,
67
75
  view.__class__.__name__,
68
76
  )
@@ -70,18 +78,47 @@ class Dispatch(ABCDispatch):
70
78
  return True
71
79
 
72
80
  loop = asyncio.get_running_loop()
73
- assert loop, "No running loop"
74
-
75
81
  found = False
76
82
  for handler in self.default_handlers:
77
- result = await handler.check(api, event)
78
- if result:
83
+ if await handler.check(api, event):
79
84
  found = True
80
85
  loop.create_task(handler.run(event))
81
86
  if handler.is_blocking:
82
- return True
87
+ break
83
88
  return found
84
89
 
85
- def mount(self, view_t: typing.Type["ABCView"], name: str):
86
- self.views.append(name)
87
- setattr(self, name, view_t)
90
+ def load(self, external: typing.Self):
91
+ view_external = external.get_views()
92
+ for name, view in self.get_views().items():
93
+ assert (
94
+ name in view_external
95
+ ), f"View {name!r} is undefined in external dispatch."
96
+ view.load(view_external[name])
97
+ setattr(external, name, view)
98
+
99
+ @classmethod
100
+ def to_handler(
101
+ cls,
102
+ *rules: ABCRule[BaseCute],
103
+ is_blocking: bool = True,
104
+ error_handler: ErrorHandlerT | None = None,
105
+ ):
106
+ def wrapper(
107
+ func: typing.Callable[typing.Concatenate[T, P], typing.Awaitable[R]]
108
+ ) -> FuncHandler[
109
+ BaseCute,
110
+ typing.Callable[typing.Concatenate[T, P], typing.Awaitable[R]],
111
+ ErrorHandlerT,
112
+ ]:
113
+ return FuncHandler(
114
+ func,
115
+ list(rules),
116
+ is_blocking=is_blocking,
117
+ dataclass=None,
118
+ error_handler=error_handler,
119
+ )
120
+
121
+ return wrapper
122
+
123
+
124
+ __all__ = ("Dispatch",)
@@ -1,2 +1,5 @@
1
1
  from .abc import ABCHandler
2
2
  from .func import FuncHandler
3
+ from .message_reply import MessageReplyHandler
4
+
5
+ __all__ = ("ABCHandler", "FuncHandler", "MessageReplyHandler")
@@ -1,19 +1,25 @@
1
+ import typing
1
2
  from abc import ABC, abstractmethod
2
- from telegrinder.types import Update
3
+
3
4
  from telegrinder.api.abc import ABCAPI
4
- import typing
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.model import Model
7
+ from telegrinder.types import Update
5
8
 
6
- T = typing.TypeVar("T")
9
+ T = typing.TypeVar("T", bound=Model)
7
10
 
8
11
 
9
12
  class ABCHandler(ABC, typing.Generic[T]):
10
13
  is_blocking: bool
11
- ctx: dict
14
+ ctx: Context
12
15
 
13
16
  @abstractmethod
14
17
  async def run(self, event: T) -> typing.Any:
15
18
  pass
16
19
 
17
20
  @abstractmethod
18
- async def check(self, api: ABCAPI, event: Update) -> bool:
21
+ async def check(self, api: ABCAPI, event: Update, ctx: Context | None = None) -> bool:
19
22
  pass
23
+
24
+
25
+ __all__ = ("ABCHandler",)
@@ -1,49 +1,58 @@
1
- import typing
2
- import types
3
- from telegrinder.bot.rules import ABCRule
4
- from telegrinder.tools import magic_bundle
5
- from telegrinder.types import Update
1
+ import typing_extensions as typing
2
+
6
3
  from telegrinder.api.abc import ABCAPI
7
- from .abc import ABCHandler
4
+ from telegrinder.bot.cute_types import BaseCute
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.dispatch.process import check_rule
8
7
  from telegrinder.modules import logger
8
+ from telegrinder.tools.error_handler import ABCErrorHandler, ErrorHandler
9
+ from telegrinder.types import Update
10
+
11
+ from .abc import ABCHandler
12
+
13
+ if typing.TYPE_CHECKING:
14
+ from telegrinder.bot.rules import ABCRule
9
15
 
10
- T = typing.TypeVar("T")
16
+ F = typing.TypeVar("F", bound=typing.Callable[typing.Concatenate[typing.Any, ...], typing.Awaitable])
17
+ EventT = typing.TypeVar("EventT", bound=BaseCute)
18
+ ErrorHandlerT = typing.TypeVar("ErrorHandlerT", bound=ABCErrorHandler, default=ErrorHandler)
11
19
 
12
20
 
13
- class FuncHandler(ABCHandler, typing.Generic[T]):
21
+ class FuncHandler(ABCHandler[EventT], typing.Generic[EventT, F, ErrorHandlerT]):
14
22
  def __init__(
15
23
  self,
16
- func: typing.Union[types.FunctionType, typing.Callable],
17
- rules: typing.List[ABCRule],
24
+ func: F,
25
+ rules: list["ABCRule[EventT]"],
18
26
  is_blocking: bool = True,
19
- dataclass: typing.Optional[typing.Any] = dict,
27
+ dataclass: type[typing.Any] | None = dict,
28
+ error_handler: ErrorHandlerT | None = None,
20
29
  ):
21
30
  self.func = func
22
31
  self.is_blocking = is_blocking
23
32
  self.rules = rules
24
33
  self.dataclass = dataclass
25
- self.ctx = {}
26
-
27
- async def check(self, api: ABCAPI, event: Update) -> bool:
28
- self.ctx = {}
34
+ self.error_handler: ErrorHandlerT = error_handler or ErrorHandler() # type: ignore
35
+ self.ctx = Context()
36
+
37
+ @property
38
+ def on_error(self):
39
+ return self.error_handler.catch
40
+
41
+ async def check(self, api: ABCAPI, event: Update, ctx: Context | None = None) -> bool:
42
+ ctx = ctx or Context()
43
+ preset_ctx = self.ctx.copy()
44
+ self.ctx |= ctx
29
45
  for rule in self.rules:
30
- e = event
31
- if rule.__event__ is None:
32
- pass
33
- else:
34
- event_dict = event.to_dict()
35
- if event_dict.get(rule.__event__.name) is None:
36
- continue
37
- e = rule.__event__.dataclass(
38
- **event_dict[rule.__event__.name].to_dict(),
39
- api=api,
40
- )
41
- if not await rule.run_check(e, self.ctx):
42
- logger.debug("Rule {} failed", rule)
46
+ if not await check_rule(api, rule, event, self.ctx):
47
+ logger.debug("Rule {!r} failed!", rule)
48
+ self.ctx = preset_ctx
43
49
  return False
44
50
  return True
45
51
 
46
- async def run(self, event: T) -> typing.Any:
47
- if self.dataclass:
48
- event = self.dataclass(**event)
49
- return await self.func(event, **magic_bundle(self.func, self.ctx))
52
+ async def run(self, event: EventT) -> typing.Any:
53
+ if self.dataclass is not None:
54
+ event = self.dataclass(**event.to_dict())
55
+ return (await self.error_handler.run(self.func, event, event.api, self.ctx)).unwrap()
56
+
57
+
58
+ __all__ = ("FuncHandler",)
@@ -0,0 +1,46 @@
1
+ import typing
2
+
3
+ from telegrinder.api.abc import ABCAPI
4
+ from telegrinder.bot.cute_types import MessageCute
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.dispatch.process import check_rule
7
+ from telegrinder.bot.rules.abc import ABCRule
8
+ from telegrinder.modules import logger
9
+ from telegrinder.msgspec_utils import Nothing
10
+ from telegrinder.types.objects import Update
11
+
12
+ from .abc import ABCHandler
13
+
14
+
15
+ class MessageReplyHandler(ABCHandler[MessageCute]):
16
+ def __init__(
17
+ self,
18
+ text: str,
19
+ *rules: ABCRule[MessageCute],
20
+ as_reply: bool = False,
21
+ is_blocking: bool = True,
22
+ ):
23
+ self.text = text
24
+ self.rules = list(rules)
25
+ self.as_reply = as_reply
26
+ self.is_blocking = is_blocking
27
+ self.dataclass = MessageCute
28
+ self.ctx = Context()
29
+
30
+ async def check(self, api: ABCAPI, event: Update, ctx: Context | None = None) -> bool:
31
+ ctx = ctx or Context()
32
+ self.ctx |= ctx
33
+ for rule in self.rules:
34
+ if not await check_rule(api, rule, event, self.ctx):
35
+ logger.debug("Rule {!r} failed!", rule)
36
+ return False
37
+ return True
38
+
39
+ async def run(self, event: MessageCute) -> typing.Any:
40
+ await event.answer(
41
+ text=self.text,
42
+ reply_to_message_id=(event.message_id if self.as_reply else Nothing),
43
+ )
44
+
45
+
46
+ __all__ = ("MessageReplyHandler",)
@@ -1 +1,3 @@
1
1
  from .abc import ABCMiddleware
2
+
3
+ __all__ = ("ABCMiddleware",)
@@ -1,12 +1,18 @@
1
- from abc import ABC
2
1
  import typing
2
+ from abc import ABC
3
+
4
+ from telegrinder.bot.cute_types.base import BaseCute
5
+ from telegrinder.bot.dispatch.context import Context
3
6
 
4
- T = typing.TypeVar("T")
7
+ T = typing.TypeVar("T", bound=BaseCute)
5
8
 
6
9
 
7
10
  class ABCMiddleware(ABC, typing.Generic[T]):
8
- async def pre(self, event: T, ctx: dict) -> bool:
11
+ async def pre(self, event: T, ctx: Context) -> bool:
9
12
  ...
10
13
 
11
- async def post(self, event: T, responses: typing.List[typing.Any], ctx: dict):
14
+ async def post(self, event: T, responses: list[typing.Any], ctx: Context):
12
15
  ...
16
+
17
+
18
+ __all__ = ("ABCMiddleware",)
@@ -1,61 +1,34 @@
1
1
  import typing
2
2
 
3
- from .waiter import Waiter
4
- from .middleware.abc import ABCMiddleware
5
- from .handler.abc import ABCHandler
6
- from telegrinder.types import Update
7
- from telegrinder.modules import logger
8
-
9
- T = typing.TypeVar("T")
10
- E = typing.TypeVar("E")
11
-
12
-
13
- async def process_waiters(
14
- waiters: typing.Dict[T, Waiter],
15
- key: T,
16
- event: typing.Optional[E],
17
- raw_event: dict,
18
- str_handler: typing.Callable,
19
- ) -> bool:
20
- if key not in waiters:
21
- return False
22
-
23
- logger.debug(
24
- "update {} found in waiter (key={})", event.__class__.__name__, str(key)
25
- )
3
+ from fntypes.result import Error
26
4
 
27
- waiter = waiters[key]
28
- ctx = {}
5
+ from telegrinder.api.abc import ABCAPI
6
+ from telegrinder.bot.cute_types import BaseCute
7
+ from telegrinder.bot.dispatch.context import Context
8
+ from telegrinder.modules import logger
9
+ from telegrinder.tools.i18n.base import I18nEnum
10
+ from telegrinder.types import Update
29
11
 
30
- for rule in waiter.rules:
31
- chk_event = event
32
- if rule.__event__ is None:
33
- chk_event = raw_event
34
- if not await rule.run_check(chk_event, ctx):
35
- if not waiter.default:
36
- return True
37
- elif isinstance(waiter.default, str):
38
- await str_handler(waiter.default)
39
- else:
40
- await waiter.default(event)
41
- return True
12
+ from .middleware.abc import ABCMiddleware
13
+ from .return_manager.abc import ABCReturnManager
42
14
 
43
- logger.debug("waiter set as ready")
15
+ if typing.TYPE_CHECKING:
16
+ from telegrinder.bot.dispatch.handler.abc import ABCHandler
17
+ from telegrinder.bot.rules.abc import ABCRule
44
18
 
45
- waiters.pop(key)
46
- setattr(waiter.event, "e", (event, ctx))
47
- waiter.event.set()
48
- return True
19
+ T = typing.TypeVar("T", bound=BaseCute)
20
+ _ = typing.Any
49
21
 
50
22
 
51
23
  async def process_inner(
52
24
  event: T,
53
25
  raw_event: Update,
54
- middlewares: typing.List[ABCMiddleware[T]],
55
- handlers: typing.List[ABCHandler[T]],
26
+ middlewares: list[ABCMiddleware[T]],
27
+ handlers: list["ABCHandler[T]"],
28
+ return_manager: ABCReturnManager[T],
56
29
  ) -> bool:
57
- logger.debug("processing {}", event.__class__.__name__)
58
- ctx = {}
30
+ logger.debug("Processing {!r}...", event.__class__.__name__)
31
+ ctx = Context(raw_update=raw_event)
59
32
 
60
33
  for middleware in middlewares:
61
34
  if await middleware.pre(event, ctx) is False:
@@ -64,12 +37,12 @@ async def process_inner(
64
37
  found = False
65
38
  responses = []
66
39
  for handler in handlers:
67
- result = await handler.check(event.api, raw_event)
68
- if result:
69
- handler.ctx.update(ctx)
40
+ if await handler.check(event.api, raw_event, ctx):
70
41
  found = True
42
+ handler.ctx |= ctx
71
43
  response = await handler.run(event)
72
44
  responses.append(response)
45
+ await return_manager.run(response, event, ctx)
73
46
  if handler.is_blocking:
74
47
  break
75
48
 
@@ -77,3 +50,34 @@ async def process_inner(
77
50
  await middleware.post(event, responses, ctx)
78
51
 
79
52
  return found
53
+
54
+
55
+ async def check_rule(
56
+ api: ABCAPI,
57
+ rule: "ABCRule",
58
+ update: Update,
59
+ ctx: Context,
60
+ ) -> bool:
61
+ """Checks requirements, adapts update.
62
+ Returns check result."""
63
+
64
+ cute_model = await rule.adapter.adapt(api, update)
65
+ match cute_model:
66
+ case Error(err):
67
+ logger.debug("Adapter failed with error message: {!r}", str(err))
68
+ return False
69
+
70
+ ctx_copy = ctx.copy()
71
+ for requirement in rule.requires:
72
+ if not await check_rule(api, requirement, update, ctx_copy):
73
+ return False
74
+
75
+ ctx |= ctx_copy
76
+
77
+ if I18nEnum.I18N in ctx:
78
+ rule = await rule.translate(ctx.get(I18nEnum.I18N))
79
+
80
+ return await rule.check(cute_model.unwrap(), ctx)
81
+
82
+
83
+ __all__ = ("check_rule", "process_inner")
@@ -0,0 +1,19 @@
1
+ from .abc import (
2
+ ABCReturnManager,
3
+ BaseReturnManager,
4
+ Manager,
5
+ register_manager,
6
+ )
7
+ from .callback_query import CallbackQueryReturnManager
8
+ from .inline_query import InlineQueryReturnManager
9
+ from .message import MessageReturnManager
10
+
11
+ __all__ = (
12
+ "ABCReturnManager",
13
+ "BaseReturnManager",
14
+ "CallbackQueryReturnManager",
15
+ "InlineQueryReturnManager",
16
+ "Manager",
17
+ "MessageReturnManager",
18
+ "register_manager",
19
+ )
@@ -0,0 +1,95 @@
1
+ import dataclasses
2
+ import types
3
+ import typing
4
+ from abc import ABC, abstractmethod
5
+
6
+ from telegrinder.bot.cute_types import BaseCute
7
+ from telegrinder.bot.dispatch.context import Context
8
+ from telegrinder.modules import logger
9
+
10
+ T = typing.TypeVar("T")
11
+ EventT = typing.TypeVar("EventT", bound=BaseCute, contravariant=True)
12
+
13
+
14
+ def get_union_types(t: types.UnionType) -> tuple[type, ...] | None:
15
+ if type(t) in (types.UnionType, typing._UnionGenericAlias): # type: ignore
16
+ return tuple(typing.get_origin(x) or x for x in typing.get_args(t))
17
+ return None
18
+
19
+
20
+ def register_manager(return_type: type | types.UnionType):
21
+ def wrapper(func: typing.Callable[..., typing.Awaitable]):
22
+ return Manager(get_union_types(return_type) or (return_type,), func) # type: ignore
23
+
24
+ return wrapper
25
+
26
+
27
+ @dataclasses.dataclass(frozen=True)
28
+ class Manager:
29
+ types: tuple[type, ...]
30
+ callback: typing.Callable[..., typing.Awaitable]
31
+
32
+ async def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
33
+ try:
34
+ await self.callback(*args, **kwargs)
35
+ except BaseException as ex:
36
+ logger.exception(ex)
37
+
38
+
39
+ class ABCReturnManager(ABC, typing.Generic[EventT]):
40
+ @abstractmethod
41
+ async def run(self, response: typing.Any, event: EventT, ctx: Context) -> None:
42
+ pass
43
+
44
+
45
+ class BaseReturnManager(ABCReturnManager[EventT]):
46
+ @property
47
+ def managers(self) -> list[Manager]:
48
+ return [
49
+ manager
50
+ for manager in (vars(BaseReturnManager) | vars(self.__class__)).values()
51
+ if isinstance(manager, Manager)
52
+ ]
53
+
54
+ @register_manager(Context)
55
+ @staticmethod
56
+ async def ctx_manager(value: Context, event: EventT, ctx: Context) -> None:
57
+ """Basic manager for returning context from handler."""
58
+
59
+ ctx.update(value)
60
+
61
+ async def run(self, response: typing.Any, event: EventT, ctx: Context) -> None:
62
+ for manager in self.managers:
63
+ if typing.Any in manager.types or any(type(response) is x for x in manager.types):
64
+ await manager(response, event, ctx)
65
+
66
+ @typing.overload
67
+ def register_manager(self, return_type: type[T]) -> typing.Callable[
68
+ [typing.Callable[[T, EventT, Context], typing.Awaitable]], Manager
69
+ ]:
70
+ ...
71
+
72
+ @typing.overload
73
+ def register_manager(self, return_type: tuple[type[T], ...]) -> typing.Callable[
74
+ [typing.Callable[[tuple[T, ...], EventT, Context], typing.Awaitable]], Manager
75
+ ]:
76
+ ...
77
+
78
+ def register_manager(self, return_type: type[T] | tuple[type[T], ...]) -> typing.Callable[
79
+ [typing.Callable[[T | tuple[T, ...], EventT, Context], typing.Awaitable]], Manager
80
+ ]:
81
+ def wrapper(func: typing.Callable[[T, EventT, Context], typing.Awaitable]) -> Manager:
82
+ manager = Manager(get_union_types(return_type) or (return_type,), func) # type: ignore
83
+ setattr(self.__class__, func.__name__, manager)
84
+ return manager
85
+
86
+ return wrapper
87
+
88
+
89
+ __all__ = (
90
+ "ABCReturnManager",
91
+ "BaseReturnManager",
92
+ "Manager",
93
+ "register_manager",
94
+ "get_union_types",
95
+ )
@@ -0,0 +1,19 @@
1
+ from telegrinder.bot.cute_types import CallbackQueryCute
2
+ from telegrinder.bot.dispatch.context import Context
3
+
4
+ from .abc import BaseReturnManager, register_manager
5
+
6
+
7
+ class CallbackQueryReturnManager(BaseReturnManager[CallbackQueryCute]):
8
+ @register_manager(str)
9
+ @staticmethod
10
+ async def str_manager(value: str, event: CallbackQueryCute, ctx: Context) -> None:
11
+ await event.answer(value)
12
+
13
+ @register_manager(dict)
14
+ @staticmethod
15
+ async def dict_manager(value: dict, event: CallbackQueryCute, ctx: Context) -> None:
16
+ await event.answer(**value)
17
+
18
+
19
+ __all__ = ("CallbackQueryReturnManager",)
@@ -0,0 +1,14 @@
1
+ from telegrinder.bot.cute_types import InlineQueryCute
2
+ from telegrinder.bot.dispatch.context import Context
3
+
4
+ from .abc import BaseReturnManager, register_manager
5
+
6
+
7
+ class InlineQueryReturnManager(BaseReturnManager[InlineQueryCute]):
8
+ @register_manager(dict)
9
+ @staticmethod
10
+ async def dict_manager(value: dict, event: InlineQueryCute, ctx: Context) -> None:
11
+ await event.answer(**value)
12
+
13
+
14
+ __all__ = ("InlineQueryReturnManager",)