telegrinder 0.1.dev168__py3-none-any.whl → 0.1.dev170__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 (100) hide show
  1. telegrinder/__init__.py +9 -3
  2. telegrinder/bot/__init__.py +7 -5
  3. telegrinder/bot/cute_types/base.py +12 -14
  4. telegrinder/bot/cute_types/callback_query.py +54 -43
  5. telegrinder/bot/cute_types/chat_join_request.py +8 -7
  6. telegrinder/bot/cute_types/chat_member_updated.py +23 -17
  7. telegrinder/bot/cute_types/inline_query.py +1 -1
  8. telegrinder/bot/cute_types/message.py +331 -183
  9. telegrinder/bot/cute_types/update.py +4 -8
  10. telegrinder/bot/cute_types/utils.py +1 -5
  11. telegrinder/bot/dispatch/__init__.py +2 -3
  12. telegrinder/bot/dispatch/abc.py +4 -0
  13. telegrinder/bot/dispatch/context.py +9 -4
  14. telegrinder/bot/dispatch/dispatch.py +35 -33
  15. telegrinder/bot/dispatch/handler/func.py +34 -13
  16. telegrinder/bot/dispatch/handler/message_reply.py +6 -3
  17. telegrinder/bot/dispatch/middleware/abc.py +4 -4
  18. telegrinder/bot/dispatch/process.py +40 -13
  19. telegrinder/bot/dispatch/return_manager/abc.py +12 -12
  20. telegrinder/bot/dispatch/return_manager/callback_query.py +1 -3
  21. telegrinder/bot/dispatch/return_manager/inline_query.py +1 -3
  22. telegrinder/bot/dispatch/view/abc.py +37 -45
  23. telegrinder/bot/dispatch/view/box.py +66 -50
  24. telegrinder/bot/dispatch/view/message.py +1 -5
  25. telegrinder/bot/dispatch/view/raw.py +6 -6
  26. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -1
  27. telegrinder/bot/dispatch/waiter_machine/machine.py +77 -35
  28. telegrinder/bot/dispatch/waiter_machine/middleware.py +31 -7
  29. telegrinder/bot/dispatch/waiter_machine/short_state.py +17 -8
  30. telegrinder/bot/polling/polling.py +4 -4
  31. telegrinder/bot/rules/__init__.py +9 -6
  32. telegrinder/bot/rules/abc.py +99 -22
  33. telegrinder/bot/rules/adapter/__init__.py +4 -1
  34. telegrinder/bot/rules/adapter/abc.py +11 -6
  35. telegrinder/bot/rules/adapter/errors.py +1 -2
  36. telegrinder/bot/rules/adapter/event.py +14 -9
  37. telegrinder/bot/rules/adapter/node.py +42 -0
  38. telegrinder/bot/rules/callback_data.py +4 -4
  39. telegrinder/bot/rules/chat_join.py +3 -2
  40. telegrinder/bot/rules/command.py +26 -14
  41. telegrinder/bot/rules/enum_text.py +5 -5
  42. telegrinder/bot/rules/func.py +6 -6
  43. telegrinder/bot/rules/fuzzy.py +5 -7
  44. telegrinder/bot/rules/inline.py +4 -5
  45. telegrinder/bot/rules/integer.py +10 -8
  46. telegrinder/bot/rules/is_from.py +63 -91
  47. telegrinder/bot/rules/markup.py +5 -5
  48. telegrinder/bot/rules/mention.py +4 -4
  49. telegrinder/bot/rules/message.py +1 -1
  50. telegrinder/bot/rules/node.py +27 -0
  51. telegrinder/bot/rules/regex.py +5 -5
  52. telegrinder/bot/rules/rule_enum.py +4 -4
  53. telegrinder/bot/rules/start.py +5 -5
  54. telegrinder/bot/rules/text.py +9 -13
  55. telegrinder/bot/rules/update.py +4 -4
  56. telegrinder/bot/scenario/__init__.py +3 -3
  57. telegrinder/bot/scenario/choice.py +2 -3
  58. telegrinder/model.py +51 -16
  59. telegrinder/modules.py +14 -6
  60. telegrinder/msgspec_utils.py +67 -23
  61. telegrinder/node/__init__.py +26 -8
  62. telegrinder/node/attachment.py +13 -9
  63. telegrinder/node/base.py +27 -14
  64. telegrinder/node/callback_query.py +18 -0
  65. telegrinder/node/command.py +29 -0
  66. telegrinder/node/composer.py +119 -30
  67. telegrinder/node/me.py +14 -0
  68. telegrinder/node/message.py +2 -4
  69. telegrinder/node/polymorphic.py +44 -0
  70. telegrinder/node/rule.py +26 -22
  71. telegrinder/node/scope.py +36 -0
  72. telegrinder/node/source.py +37 -10
  73. telegrinder/node/text.py +11 -5
  74. telegrinder/node/tools/__init__.py +2 -2
  75. telegrinder/node/tools/generator.py +6 -6
  76. telegrinder/tools/__init__.py +8 -13
  77. telegrinder/tools/buttons.py +23 -17
  78. telegrinder/tools/error_handler/error_handler.py +11 -14
  79. telegrinder/tools/formatting/__init__.py +0 -6
  80. telegrinder/tools/formatting/html.py +10 -12
  81. telegrinder/tools/formatting/links.py +0 -5
  82. telegrinder/tools/formatting/spec_html_formats.py +0 -11
  83. telegrinder/tools/global_context/abc.py +1 -3
  84. telegrinder/tools/global_context/global_context.py +6 -16
  85. telegrinder/tools/i18n/simple.py +1 -3
  86. telegrinder/tools/kb_set/yaml.py +1 -2
  87. telegrinder/tools/keyboard.py +7 -8
  88. telegrinder/tools/limited_dict.py +1 -1
  89. telegrinder/tools/loop_wrapper/loop_wrapper.py +6 -5
  90. telegrinder/tools/magic.py +27 -5
  91. telegrinder/types/__init__.py +20 -0
  92. telegrinder/types/enums.py +37 -31
  93. telegrinder/types/methods.py +608 -327
  94. telegrinder/types/objects.py +1139 -716
  95. {telegrinder-0.1.dev168.dist-info → telegrinder-0.1.dev170.dist-info}/LICENSE +1 -1
  96. {telegrinder-0.1.dev168.dist-info → telegrinder-0.1.dev170.dist-info}/METADATA +6 -5
  97. telegrinder-0.1.dev170.dist-info/RECORD +143 -0
  98. telegrinder/bot/dispatch/composition.py +0 -88
  99. telegrinder-0.1.dev168.dist-info/RECORD +0 -137
  100. {telegrinder-0.1.dev168.dist-info → telegrinder-0.1.dev170.dist-info}/WHEEL +0 -0
@@ -15,16 +15,12 @@ class UpdateCute(BaseCute[Update], Update, kw_only=True):
15
15
  api: ABCAPI
16
16
 
17
17
  @property
18
- def incoming_update(self) -> Option[Model]:
19
- return getattr(
20
- self,
21
- self.update_type.expect("Update object has no incoming update.").value,
22
- )
18
+ def incoming_update(self) -> Model:
19
+ return getattr(self, self.update_type.value).unwrap()
23
20
 
24
21
  def get_event(self, event_model: type[ModelT]) -> Option[ModelT]:
25
- match self.incoming_update:
26
- case Some(event) if isinstance(event, event_model):
27
- return Some(event)
22
+ if isinstance(self.incoming_update, event_model):
23
+ return Some(self.incoming_update)
28
24
  return Nothing()
29
25
 
30
26
 
@@ -78,11 +78,7 @@ def compose_reactions(
78
78
  (
79
79
  ReactionTypeEmoji("emoji", emoji)
80
80
  if isinstance(emoji, ReactionEmoji)
81
- else (
82
- ReactionTypeEmoji("emoji", ReactionEmoji(emoji))
83
- if isinstance(emoji, str)
84
- else emoji
85
- )
81
+ else (ReactionTypeEmoji("emoji", ReactionEmoji(emoji)) if isinstance(emoji, str) else emoji)
86
82
  )
87
83
  for emoji in ([reactions] if isinstance(reactions, str) else reactions)
88
84
  ]
@@ -1,5 +1,4 @@
1
1
  from .abc import ABCDispatch
2
- from .composition import CompositionDispatch
3
2
  from .context import Context
4
3
  from .dispatch import ABCRule, Dispatch, TelegrinderCtx
5
4
  from .handler import ABCHandler, FuncHandler, MessageReplyHandler
@@ -27,7 +26,7 @@ from .view import (
27
26
  RawEventView,
28
27
  ViewBox,
29
28
  )
30
- from .waiter_machine import ShortState, WaiterMachine
29
+ from .waiter_machine import ShortState, WaiterMachine, clear_wm_storage_worker
31
30
 
32
31
  __all__ = (
33
32
  "ABCDispatch",
@@ -44,7 +43,6 @@ __all__ = (
44
43
  "CallbackQueryView",
45
44
  "ChatJoinRequestView",
46
45
  "ChatMemberView",
47
- "CompositionDispatch",
48
46
  "Context",
49
47
  "Dispatch",
50
48
  "FuncHandler",
@@ -62,4 +60,5 @@ __all__ = (
62
60
  "check_rule",
63
61
  "process_inner",
64
62
  "register_manager",
63
+ "clear_wm_storage_worker",
65
64
  )
@@ -17,5 +17,9 @@ class ABCDispatch(ABC):
17
17
  def load(self, external: typing.Self) -> None:
18
18
  pass
19
19
 
20
+ def load_many(self, *externals: typing.Self) -> None:
21
+ for external in externals:
22
+ self.load(external)
23
+
20
24
 
21
25
  __all__ = ("ABCDispatch",)
@@ -15,7 +15,7 @@ class Context(dict[str, AnyValue]):
15
15
  """Context class for rules and middlewares.
16
16
  ```
17
17
  class MyRule(ABCRule[T]):
18
- adapter: ABCAdapter[Update, T] = RawUpdateAdapter()
18
+ adapter = RawUpdateAdapter()
19
19
 
20
20
  async def check(self, event: T, ctx: Context) -> bool:
21
21
  ctx.set("value", (await event.ctx_api.get_me()).unwrap())
@@ -28,17 +28,17 @@ class Context(dict[str, AnyValue]):
28
28
  def __init__(self, **kwargs: AnyValue) -> None:
29
29
  cls_vars = vars(self.__class__)
30
30
  defaults = {}
31
+
31
32
  for k in self.__class__.__annotations__:
32
33
  if k in cls_vars:
33
34
  defaults[k] = cls_vars[k]
34
35
  delattr(self.__class__, k)
36
+
35
37
  dict.__init__(self, **defaults | kwargs)
36
38
 
37
39
  @recursive_repr()
38
40
  def __repr__(self) -> str:
39
- return "{}({})".format(
40
- self.__class__.__name__, ", ".join(f"{k}={v!r}" for k, v in self.items())
41
- )
41
+ return "{}({})".format(self.__class__.__name__, ", ".join(f"{k}={v!r}" for k, v in self.items()))
42
42
 
43
43
  def __setitem__(self, __key: Key, __value: AnyValue) -> None:
44
44
  dict.__setitem__(self, self.key_to_str(__key), __value)
@@ -71,6 +71,11 @@ class Context(dict[str, AnyValue]):
71
71
  def get(self, key: Key, default: T | None = None) -> T | AnyValue:
72
72
  return dict.get(self, key, default)
73
73
 
74
+ def get_or_set(self, key: Key, default: T) -> T:
75
+ if key not in self:
76
+ self.set(key, default)
77
+ return self.get(key)
78
+
74
79
  def delete(self, key: Key) -> None:
75
80
  del self[key]
76
81
 
@@ -6,6 +6,7 @@ from vbml.patcher import Patcher
6
6
 
7
7
  from telegrinder.api.abc import ABCAPI
8
8
  from telegrinder.bot.cute_types.base import BaseCute
9
+ from telegrinder.bot.cute_types.update import UpdateCute
9
10
  from telegrinder.bot.dispatch.context import Context
10
11
  from telegrinder.bot.rules import ABCRule
11
12
  from telegrinder.modules import logger
@@ -17,21 +18,19 @@ from .abc import ABCDispatch
17
18
  from .handler import ABCHandler, FuncHandler
18
19
  from .handler.func import ErrorHandlerT
19
20
  from .view.box import (
20
- CallbackQueryViewT,
21
- ChatJoinRequestViewT,
22
- ChatMemberViewT,
23
- InlineQueryViewT,
24
- MessageViewT,
25
- RawEventViewT,
21
+ CallbackQueryView,
22
+ ChatJoinRequestView,
23
+ ChatMemberView,
24
+ InlineQueryView,
25
+ MessageView,
26
+ RawEventView,
26
27
  ViewBox,
27
28
  )
28
29
 
29
30
  T = typing.TypeVar("T")
30
31
  R = typing.TypeVar("R")
31
32
  P = typing.ParamSpec("P")
32
- Handler = typing.Callable[
33
- typing.Concatenate[T, ...], typing.Coroutine[typing.Any, typing.Any, typing.Any]
34
- ]
33
+ Handler = typing.Callable[typing.Concatenate[T, ...], typing.Coroutine[typing.Any, typing.Any, typing.Any]]
35
34
  Event = typing.TypeVar("Event", bound=BaseCute)
36
35
 
37
36
  DEFAULT_DATACLASS: typing.Final[type[Update]] = Update
@@ -41,12 +40,12 @@ DEFAULT_DATACLASS: typing.Final[type[Update]] = Update
41
40
  class Dispatch(
42
41
  ABCDispatch,
43
42
  ViewBox[
44
- CallbackQueryViewT,
45
- ChatJoinRequestViewT,
46
- ChatMemberViewT,
47
- InlineQueryViewT,
48
- MessageViewT,
49
- RawEventViewT,
43
+ CallbackQueryView,
44
+ ChatJoinRequestView,
45
+ ChatMemberView,
46
+ InlineQueryView,
47
+ MessageView,
48
+ RawEventView,
50
49
  ],
51
50
  ):
52
51
  global_context: TelegrinderCtx = dataclasses.field(
@@ -63,55 +62,56 @@ class Dispatch(
63
62
 
64
63
  @property
65
64
  def patcher(self) -> Patcher:
66
- """Alias `patcher` to get `vbml.Patcher` from the global context"""
65
+ """Alias `patcher` to get `vbml.Patcher` from the global context."""
66
+
67
67
  return self.global_context.vbml_patcher
68
68
 
69
69
  @typing.overload
70
70
  def handle(
71
71
  self,
72
- *rules: ABCRule[Event],
73
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
72
+ *rules: ABCRule,
73
+ ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandler]]: ...
74
74
 
75
75
  @typing.overload
76
76
  def handle(
77
77
  self,
78
- *rules: ABCRule[Event],
78
+ *rules: ABCRule,
79
79
  is_blocking: bool = True,
80
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
80
+ ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandler]]: ...
81
81
 
82
82
  @typing.overload
83
83
  def handle(
84
84
  self,
85
- *rules: ABCRule[Event],
85
+ *rules: ABCRule,
86
86
  dataclass: type[T],
87
87
  is_blocking: bool = True,
88
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
88
+ ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandler]]: ...
89
89
 
90
90
  @typing.overload
91
91
  def handle( # type: ignore
92
92
  self,
93
- *rules: ABCRule[Event],
93
+ *rules: ABCRule,
94
94
  error_handler: ErrorHandlerT,
95
95
  is_blocking: bool = True,
96
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandlerT]]: ...
96
+ ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandlerT]]: ...
97
97
 
98
98
  @typing.overload
99
99
  def handle(
100
100
  self,
101
- *rules: ABCRule[Event],
101
+ *rules: ABCRule,
102
102
  dataclass: type[T],
103
103
  error_handler: ErrorHandlerT,
104
104
  is_blocking: bool = True,
105
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandlerT]]: ...
105
+ ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandlerT]]: ...
106
106
 
107
107
  @typing.overload
108
108
  def handle(
109
109
  self,
110
- *rules: ABCRule[Event],
110
+ *rules: ABCRule,
111
111
  dataclass: type[T] = DEFAULT_DATACLASS,
112
112
  error_handler: typing.Literal[None] = None,
113
113
  is_blocking: bool = True,
114
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
114
+ ) -> typing.Callable[[Handler[T]], FuncHandler[UpdateCute, Handler[T], ErrorHandler]]: ...
115
115
 
116
116
  def handle( # type: ignore
117
117
  self,
@@ -135,20 +135,20 @@ class Dispatch(
135
135
 
136
136
  async def feed(self, event: Update, api: ABCAPI) -> bool:
137
137
  logger.debug("Processing update (update_id={})", event.update_id)
138
- loop = asyncio.get_running_loop()
139
- loop.create_task(self.raw_event.process(event, api))
140
-
138
+ await self.raw_event.process(event, api)
139
+
141
140
  for view in self.get_views().values():
142
141
  if await view.check(event):
143
142
  logger.debug(
144
- "Update (update_id={}) matched view {!r}",
143
+ "Update (update_id={}) matched view {!r}.",
145
144
  event.update_id,
146
145
  view.__class__.__name__,
147
146
  )
148
- loop.create_task(view.process(event, api))
147
+ await view.process(event, api)
149
148
  return True
150
149
 
151
150
  ctx = Context()
151
+ loop = asyncio.get_running_loop()
152
152
  found = False
153
153
  for handler in self.default_handlers:
154
154
  if await handler.check(api, event, ctx):
@@ -165,5 +165,7 @@ class Dispatch(
165
165
  view.load(view_external[name])
166
166
  setattr(external, name, view)
167
167
 
168
+ __call__ = handle
169
+
168
170
 
169
171
  __all__ = ("Dispatch",)
@@ -3,11 +3,13 @@ import dataclasses
3
3
  import typing_extensions as typing
4
4
 
5
5
  from telegrinder.api.abc import ABCAPI
6
- from telegrinder.bot.cute_types import BaseCute
6
+ from telegrinder.bot.cute_types import BaseCute, UpdateCute
7
7
  from telegrinder.bot.dispatch.context import Context
8
8
  from telegrinder.bot.dispatch.process import check_rule
9
9
  from telegrinder.modules import logger
10
+ from telegrinder.node import Node, compose_nodes, is_node
10
11
  from telegrinder.tools.error_handler import ABCErrorHandler, ErrorHandler
12
+ from telegrinder.tools.magic import get_annotations
11
13
  from telegrinder.types import Update
12
14
  from telegrinder.types.enums import UpdateType
13
15
 
@@ -20,22 +22,22 @@ F = typing.TypeVar(
20
22
  "F",
21
23
  bound=typing.Callable[typing.Concatenate[typing.Any, ...], typing.Awaitable[typing.Any]],
22
24
  )
23
- EventT = typing.TypeVar("EventT", bound=BaseCute)
25
+ Event = typing.TypeVar("Event", bound=BaseCute)
24
26
  ErrorHandlerT = typing.TypeVar("ErrorHandlerT", bound=ABCErrorHandler, default=ErrorHandler)
25
27
 
26
28
 
27
29
  @dataclasses.dataclass(repr=False)
28
- class FuncHandler(ABCHandler[EventT], typing.Generic[EventT, F, ErrorHandlerT]):
30
+ class FuncHandler(ABCHandler[Event], typing.Generic[Event, F, ErrorHandlerT]):
29
31
  func: F
30
- rules: list["ABCRule[EventT]"]
31
- _: dataclasses.KW_ONLY
32
- is_blocking: bool = dataclasses.field(default=True)
33
- dataclass: type[typing.Any] | None = dataclasses.field(default=dict)
32
+ rules: list["ABCRule"]
33
+ is_blocking: bool = dataclasses.field(default=True, kw_only=True)
34
+ dataclass: type[typing.Any] | None = dataclasses.field(default=dict, kw_only=True)
34
35
  error_handler: ErrorHandlerT = dataclasses.field(
35
36
  default_factory=lambda: typing.cast(ErrorHandlerT, ErrorHandler()),
37
+ kw_only=True,
36
38
  )
37
- preset_context: Context = dataclasses.field(default_factory=lambda: Context())
38
- update_type: UpdateType | None = dataclasses.field(default=None)
39
+ preset_context: Context = dataclasses.field(default_factory=lambda: Context(), kw_only=True)
40
+ update_type: UpdateType | None = dataclasses.field(default=None, kw_only=True)
39
41
 
40
42
  def __repr__(self) -> str:
41
43
  return "<{}: {}={!r} with rules={!r}, dataclass={!r}, error_handler={!r}>".format(
@@ -46,24 +48,39 @@ class FuncHandler(ABCHandler[EventT], typing.Generic[EventT, F, ErrorHandlerT]):
46
48
  self.dataclass,
47
49
  self.error_handler,
48
50
  )
49
-
51
+
52
+ def get_required_nodes(self) -> dict[str, type[Node]]:
53
+ return {k: v for k, v in get_annotations(self.func).items() if is_node(v)}
54
+
50
55
  async def check(self, api: ABCAPI, event: Update, ctx: Context | None = None) -> bool:
51
- if self.update_type is not None and self.update_type != event.update_type.unwrap_or_none():
56
+ if self.update_type is not None and self.update_type != event.update_type:
52
57
  return False
58
+
53
59
  ctx = ctx or Context()
54
60
  temp_ctx = ctx.copy()
55
61
  temp_ctx |= self.preset_context
56
62
 
63
+ nodes = self.get_required_nodes()
64
+ node_col = None
65
+
66
+ if nodes:
67
+ node_col = await compose_nodes(nodes, UpdateCute.from_update(event, api), ctx)
68
+ if node_col is None:
69
+ return False
70
+ temp_ctx |= node_col.values()
71
+
57
72
  for rule in self.rules:
58
73
  if not await check_rule(api, rule, event, temp_ctx):
59
74
  logger.debug("Rule {!r} failed!", rule)
60
75
  return False
61
76
 
77
+ temp_ctx["node_col"] = node_col
62
78
  ctx |= temp_ctx
63
79
  return True
64
80
 
65
- async def run(self, event: EventT, ctx: Context) -> typing.Any:
81
+ async def run(self, event: Event, ctx: Context) -> typing.Any:
66
82
  api = event.api
83
+
67
84
  if self.dataclass is not None:
68
85
  if self.update_type is not None:
69
86
  update = event.to_dict()[self.update_type.value].unwrap()
@@ -74,7 +91,11 @@ class FuncHandler(ABCHandler[EventT], typing.Generic[EventT, F, ErrorHandlerT]):
74
91
  )
75
92
  else:
76
93
  event = self.dataclass(**event.to_dict())
77
- return (await self.error_handler.run(self.func, event, api, ctx)).unwrap()
94
+
95
+ result = (await self.error_handler.run(self.func, event, api, ctx)).unwrap()
96
+ if node_col := ctx.node_col:
97
+ await node_col.close_all()
98
+ return result
78
99
 
79
100
 
80
101
  __all__ = ("FuncHandler",)
@@ -6,7 +6,6 @@ from telegrinder.bot.dispatch.context import Context
6
6
  from telegrinder.bot.dispatch.process import check_rule
7
7
  from telegrinder.bot.rules.abc import ABCRule
8
8
  from telegrinder.modules import logger
9
- from telegrinder.msgspec_utils import Nothing
10
9
  from telegrinder.types.objects import ReplyParameters, Update
11
10
 
12
11
  from .abc import ABCHandler
@@ -16,15 +15,18 @@ class MessageReplyHandler(ABCHandler[MessageCute]):
16
15
  def __init__(
17
16
  self,
18
17
  text: str,
19
- *rules: ABCRule[MessageCute],
18
+ *rules: ABCRule,
20
19
  is_blocking: bool = True,
21
20
  as_reply: bool = False,
21
+ preset_context: Context | None = None,
22
+ **default_params: typing.Any,
22
23
  ) -> None:
23
24
  self.text = text
24
25
  self.rules = list(rules)
25
26
  self.as_reply = as_reply
26
27
  self.is_blocking = is_blocking
27
- self.preset_context = Context()
28
+ self.default_params = default_params
29
+ self.preset_context = preset_context or Context()
28
30
 
29
31
  def __repr__(self) -> str:
30
32
  return "<{}: with rules={!r}, {}: {!r}>".format(
@@ -51,6 +53,7 @@ class MessageReplyHandler(ABCHandler[MessageCute]):
51
53
  await event.answer(
52
54
  text=self.text,
53
55
  reply_parameters=ReplyParameters(event.message_id) if self.as_reply else None,
56
+ **self.default_params,
54
57
  )
55
58
 
56
59
 
@@ -4,13 +4,13 @@ from abc import ABC
4
4
  from telegrinder.bot.cute_types.base import BaseCute
5
5
  from telegrinder.bot.dispatch.context import Context
6
6
 
7
- T = typing.TypeVar("T", bound=BaseCute)
7
+ Event = typing.TypeVar("Event", bound=BaseCute)
8
8
 
9
9
 
10
- class ABCMiddleware(ABC, typing.Generic[T]):
11
- async def pre(self, event: T, ctx: Context) -> bool: ...
10
+ class ABCMiddleware(ABC, typing.Generic[Event]):
11
+ async def pre(self, event: Event, ctx: Context) -> bool: ...
12
12
 
13
- async def post(self, event: T, responses: list[typing.Any], ctx: Context) -> None: ...
13
+ async def post(self, event: Event, responses: list[typing.Any], ctx: Context) -> None: ...
14
14
 
15
15
 
16
16
  __all__ = ("ABCMiddleware",)
@@ -1,11 +1,13 @@
1
1
  import typing
2
2
 
3
- from fntypes.result import Error
3
+ from fntypes.result import Error, Ok
4
4
 
5
5
  from telegrinder.api.abc import ABCAPI
6
6
  from telegrinder.bot.cute_types import BaseCute
7
+ from telegrinder.bot.cute_types.update import UpdateCute
7
8
  from telegrinder.bot.dispatch.context import Context
8
9
  from telegrinder.modules import logger
10
+ from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, NodeScope, compose_nodes
9
11
  from telegrinder.tools.i18n.base import I18nEnum
10
12
  from telegrinder.types import Update
11
13
 
@@ -16,19 +18,20 @@ if typing.TYPE_CHECKING:
16
18
  from telegrinder.bot.dispatch.handler.abc import ABCHandler
17
19
  from telegrinder.bot.rules.abc import ABCRule
18
20
 
19
- T = typing.TypeVar("T", bound=BaseCute)
21
+ Event = typing.TypeVar("Event", bound=BaseCute)
20
22
  _: typing.TypeAlias = typing.Any
21
23
 
22
24
 
23
25
  async def process_inner(
24
- event: T,
26
+ event: Event,
25
27
  raw_event: Update,
26
- middlewares: list[ABCMiddleware[T]],
27
- handlers: list["ABCHandler[T]"],
28
- return_manager: ABCReturnManager[T] | None = None,
28
+ middlewares: list[ABCMiddleware[Event]],
29
+ handlers: list["ABCHandler[Event]"],
30
+ return_manager: ABCReturnManager[Event] | None = None,
29
31
  ) -> bool:
30
32
  logger.debug("Processing {!r}...", event.__class__.__name__)
31
33
  ctx = Context(raw_update=raw_event)
34
+ ctx[CONTEXT_STORE_NODES_KEY] = {} # for per-event shared nodes
32
35
 
33
36
  for middleware in middlewares:
34
37
  if await middleware.pre(event, ctx) is False:
@@ -42,45 +45,69 @@ async def process_inner(
42
45
  if await handler.check(event.api, raw_event, ctx):
43
46
  found = True
44
47
  response = await handler.run(event, ctx)
48
+ logger.debug("Handler {!r} returned: {!r}", handler, response)
45
49
  responses.append(response)
46
50
  if return_manager is not None:
47
51
  await return_manager.run(response, event, ctx)
48
52
  if handler.is_blocking:
49
53
  break
50
- ctx = ctx_copy
54
+
55
+ ctx = ctx_copy
51
56
 
52
57
  for middleware in middlewares:
53
58
  await middleware.post(event, responses, ctx)
54
59
 
60
+ for session in ctx.get(CONTEXT_STORE_NODES_KEY, {}).values():
61
+ await session.close(scopes=(NodeScope.PER_EVENT,))
62
+
55
63
  return found
56
64
 
57
65
 
58
66
  async def check_rule(
59
67
  api: ABCAPI,
60
- rule: "ABCRule[T]",
68
+ rule: "ABCRule",
61
69
  update: Update,
62
70
  ctx: Context,
63
71
  ) -> bool:
64
72
  """Checks requirements, adapts update.
65
73
  Returns check result."""
66
74
 
67
- cute_model = await rule.adapter.adapt(api, update)
68
- match cute_model:
75
+ # Running adapter
76
+ match await rule.adapter.adapt(api, update):
77
+ case Ok(value):
78
+ adapted_value = value
69
79
  case Error(err):
70
80
  logger.debug("Adapter failed with error message: {!r}", str(err))
71
81
  return False
72
82
 
83
+ # Running subrules to fetch requirements
73
84
  ctx_copy = ctx.copy()
74
85
  for requirement in rule.requires:
75
86
  if not await check_rule(api, requirement, update, ctx_copy):
76
87
  return False
77
88
 
78
- ctx |= ctx_copy
79
-
89
+ # Translating translatable rules
80
90
  if I18nEnum.I18N in ctx:
81
91
  rule = await rule.translate(ctx.get(I18nEnum.I18N))
82
92
 
83
- return await rule.check(cute_model.unwrap(), ctx)
93
+ ctx |= ctx_copy
94
+
95
+ # Composing required nodes
96
+ nodes = rule.get_required_nodes()
97
+ node_col = None
98
+ if nodes:
99
+ node_col = await compose_nodes(nodes, UpdateCute.from_update(update, api), ctx)
100
+ if node_col is None:
101
+ return False
102
+
103
+ # Running check
104
+ result = await rule.bounding_check(adapted_value, ctx, node_col)
105
+
106
+ # Closing node sessions if there are any
107
+ if node_col is not None:
108
+ await node_col.close_all()
109
+
110
+ return result
84
111
 
85
112
 
86
113
  __all__ = ("check_rule", "process_inner")
@@ -8,7 +8,7 @@ from telegrinder.bot.dispatch.context import Context
8
8
  from telegrinder.modules import logger
9
9
 
10
10
  T = typing.TypeVar("T")
11
- EventT = typing.TypeVar("EventT", bound=BaseCute, contravariant=True)
11
+ Event = typing.TypeVar("Event", bound=BaseCute, contravariant=True)
12
12
 
13
13
 
14
14
  def get_union_types(t: types.UnionType) -> tuple[type, ...] | None:
@@ -36,13 +36,13 @@ class Manager:
36
36
  logger.exception(ex)
37
37
 
38
38
 
39
- class ABCReturnManager(ABC, typing.Generic[EventT]):
39
+ class ABCReturnManager(ABC, typing.Generic[Event]):
40
40
  @abstractmethod
41
- async def run(self, response: typing.Any, event: EventT, ctx: Context) -> None:
41
+ async def run(self, response: typing.Any, event: Event, ctx: Context) -> None:
42
42
  pass
43
43
 
44
44
 
45
- class BaseReturnManager(ABCReturnManager[EventT]):
45
+ class BaseReturnManager(ABCReturnManager[Event]):
46
46
  def __repr__(self) -> str:
47
47
  return "<{}: {}>".format(
48
48
  self.__class__.__name__,
@@ -59,29 +59,29 @@ class BaseReturnManager(ABCReturnManager[EventT]):
59
59
 
60
60
  @register_manager(Context)
61
61
  @staticmethod
62
- async def ctx_manager(value: Context, event: EventT, ctx: Context) -> None:
62
+ async def ctx_manager(value: Context, event: Event, ctx: Context) -> None:
63
63
  """Basic manager for returning context from handler."""
64
64
 
65
65
  ctx.update(value)
66
66
 
67
- async def run(self, response: typing.Any, event: EventT, ctx: Context) -> None:
67
+ async def run(self, response: typing.Any, event: Event, ctx: Context) -> None:
68
+ logger.debug("Run return manager for response: {!r}", response)
68
69
  for manager in self.managers:
69
70
  if typing.Any in manager.types or any(type(response) is x for x in manager.types):
71
+ logger.debug("Run manager {!r}...", manager.callback.__name__)
70
72
  await manager(response, event, ctx)
71
73
 
72
74
  @typing.overload
73
75
  def register_manager(
74
76
  self, return_type: type[T]
75
- ) -> typing.Callable[
76
- [typing.Callable[[T, EventT, Context], typing.Awaitable[typing.Any]]], Manager
77
- ]: ...
77
+ ) -> typing.Callable[[typing.Callable[[T, Event, Context], typing.Awaitable[typing.Any]]], Manager]: ...
78
78
 
79
79
  @typing.overload
80
80
  def register_manager(
81
81
  self,
82
82
  return_type: tuple[type[T], ...],
83
83
  ) -> typing.Callable[
84
- [typing.Callable[[tuple[T, ...], EventT, Context], typing.Awaitable[typing.Any]]],
84
+ [typing.Callable[[tuple[T, ...], Event, Context], typing.Awaitable[typing.Any]]],
85
85
  Manager,
86
86
  ]: ...
87
87
 
@@ -89,10 +89,10 @@ class BaseReturnManager(ABCReturnManager[EventT]):
89
89
  self,
90
90
  return_type: type[T] | tuple[type[T], ...],
91
91
  ) -> typing.Callable[
92
- [typing.Callable[[T | tuple[T, ...], EventT, Context], typing.Awaitable[typing.Any]]],
92
+ [typing.Callable[[T | tuple[T, ...], Event, Context], typing.Awaitable[typing.Any]]],
93
93
  Manager,
94
94
  ]:
95
- def wrapper(func: typing.Callable[[T, EventT, Context], typing.Awaitable]) -> Manager:
95
+ def wrapper(func: typing.Callable[[T, Event, Context], typing.Awaitable]) -> Manager:
96
96
  manager = Manager(get_union_types(return_type) or (return_type,), func) # type: ignore
97
97
  setattr(self.__class__, func.__name__, manager)
98
98
  return manager
@@ -14,9 +14,7 @@ class CallbackQueryReturnManager(BaseReturnManager[CallbackQueryCute]):
14
14
 
15
15
  @register_manager(dict)
16
16
  @staticmethod
17
- async def dict_manager(
18
- value: dict[str, typing.Any], event: CallbackQueryCute, ctx: Context
19
- ) -> None:
17
+ async def dict_manager(value: dict[str, typing.Any], event: CallbackQueryCute, ctx: Context) -> None:
20
18
  await event.answer(**value)
21
19
 
22
20
 
@@ -9,9 +9,7 @@ from .abc import BaseReturnManager, register_manager
9
9
  class InlineQueryReturnManager(BaseReturnManager[InlineQueryCute]):
10
10
  @register_manager(dict)
11
11
  @staticmethod
12
- async def dict_manager(
13
- value: dict[str, typing.Any], event: InlineQueryCute, ctx: Context
14
- ) -> None:
12
+ async def dict_manager(value: dict[str, typing.Any], event: InlineQueryCute, ctx: Context) -> None:
15
13
  await event.answer(**value)
16
14
 
17
15