telegrinder 0.1.dev167__py3-none-any.whl → 0.1.dev169__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 (101) 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 +55 -44
  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 +33 -30
  15. telegrinder/bot/dispatch/handler/func.py +33 -12
  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 +74 -31
  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 +86 -50
  28. telegrinder/bot/dispatch/waiter_machine/middleware.py +31 -7
  29. telegrinder/bot/dispatch/waiter_machine/short_state.py +26 -7
  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 +13 -15
  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/checkbox.py +5 -5
  58. telegrinder/bot/scenario/choice.py +5 -5
  59. telegrinder/model.py +49 -15
  60. telegrinder/modules.py +14 -6
  61. telegrinder/msgspec_utils.py +8 -17
  62. telegrinder/node/__init__.py +26 -8
  63. telegrinder/node/attachment.py +13 -9
  64. telegrinder/node/base.py +27 -14
  65. telegrinder/node/callback_query.py +18 -0
  66. telegrinder/node/command.py +29 -0
  67. telegrinder/node/composer.py +119 -30
  68. telegrinder/node/me.py +14 -0
  69. telegrinder/node/message.py +2 -4
  70. telegrinder/node/polymorphic.py +44 -0
  71. telegrinder/node/rule.py +26 -22
  72. telegrinder/node/scope.py +36 -0
  73. telegrinder/node/source.py +37 -10
  74. telegrinder/node/text.py +11 -5
  75. telegrinder/node/tools/__init__.py +2 -2
  76. telegrinder/node/tools/generator.py +6 -6
  77. telegrinder/tools/__init__.py +9 -14
  78. telegrinder/tools/buttons.py +23 -17
  79. telegrinder/tools/error_handler/error_handler.py +11 -14
  80. telegrinder/tools/formatting/__init__.py +0 -6
  81. telegrinder/tools/formatting/html.py +10 -12
  82. telegrinder/tools/formatting/links.py +0 -5
  83. telegrinder/tools/formatting/spec_html_formats.py +0 -11
  84. telegrinder/tools/global_context/abc.py +1 -3
  85. telegrinder/tools/global_context/global_context.py +6 -16
  86. telegrinder/tools/i18n/simple.py +1 -3
  87. telegrinder/tools/kb_set/yaml.py +1 -2
  88. telegrinder/tools/keyboard.py +7 -8
  89. telegrinder/tools/limited_dict.py +13 -3
  90. telegrinder/tools/loop_wrapper/loop_wrapper.py +6 -5
  91. telegrinder/tools/magic.py +27 -5
  92. telegrinder/types/__init__.py +20 -0
  93. telegrinder/types/enums.py +37 -31
  94. telegrinder/types/methods.py +613 -401
  95. telegrinder/types/objects.py +1151 -757
  96. {telegrinder-0.1.dev167.dist-info → telegrinder-0.1.dev169.dist-info}/LICENSE +1 -1
  97. {telegrinder-0.1.dev167.dist-info → telegrinder-0.1.dev169.dist-info}/METADATA +9 -8
  98. telegrinder-0.1.dev169.dist-info/RECORD +143 -0
  99. telegrinder/bot/dispatch/composition.py +0 -88
  100. telegrinder-0.1.dev167.dist-info/RECORD +0 -137
  101. {telegrinder-0.1.dev167.dist-info → telegrinder-0.1.dev169.dist-info}/WHEEL +0 -0
@@ -16,12 +16,12 @@ from telegrinder.msgspec_utils import Option
16
16
  from telegrinder.tools.error_handler.error_handler import ABCErrorHandler, ErrorHandler
17
17
  from telegrinder.types.objects import Update
18
18
 
19
- EventType = typing.TypeVar("EventType", bound=BaseCute)
19
+ Event = typing.TypeVar("Event", bound=BaseCute)
20
20
  ErrorHandlerT = typing.TypeVar("ErrorHandlerT", bound=ABCErrorHandler)
21
21
  MiddlewareT = typing.TypeVar("MiddlewareT", bound=ABCMiddleware)
22
22
 
23
23
  FuncType: typing.TypeAlias = typing.Callable[
24
- typing.Concatenate[EventType, ...],
24
+ typing.Concatenate[Event, ...],
25
25
  typing.Coroutine[typing.Any, typing.Any, typing.Any],
26
26
  ]
27
27
 
@@ -46,20 +46,24 @@ class ABCView(ABC):
46
46
  pass
47
47
 
48
48
 
49
- class ABCStateView(ABCView, typing.Generic[EventType]):
49
+ class ABCStateView(ABCView, typing.Generic[Event]):
50
50
  @abstractmethod
51
- def get_state_key(self, event: EventType) -> int | None:
51
+ def get_state_key(self, event: Event) -> int | None:
52
52
  pass
53
53
 
54
54
 
55
- class BaseView(ABCView, typing.Generic[EventType]):
56
- auto_rules: list[ABCRule[EventType]]
57
- handlers: list[ABCHandler[EventType]]
58
- middlewares: list[ABCMiddleware[EventType]]
59
- return_manager: ABCReturnManager[EventType] | None
55
+ class BaseView(ABCView, typing.Generic[Event]):
56
+ auto_rules: list[ABCRule]
57
+ handlers: list[ABCHandler[Event]]
58
+ middlewares: list[ABCMiddleware[Event]]
59
+ return_manager: ABCReturnManager[Event] | None = None
60
+
61
+ @staticmethod
62
+ def get_raw_event(update: Update) -> Option[Model]:
63
+ return getattr(update, update.update_type.value)
60
64
 
61
65
  @classmethod
62
- def get_event_type(cls) -> Option[type[EventType]]:
66
+ def get_event_type(cls) -> Option[type[Event]]:
63
67
  for base in cls.__dict__.get("__orig_bases__", ()):
64
68
  if issubclass(typing.get_origin(base) or base, ABCView):
65
69
  for generic_type in typing.get_args(base):
@@ -67,51 +71,90 @@ class BaseView(ABCView, typing.Generic[EventType]):
67
71
  return Some(generic_type)
68
72
  return Nothing()
69
73
 
74
+ @typing.overload
70
75
  @classmethod
71
- def get_raw_event(cls, update: Update) -> Option[Model]:
72
- match update.update_type:
73
- case Some(update_type):
74
- return getattr(update, update_type.value)
75
- case _:
76
- return Nothing()
76
+ def to_handler(
77
+ cls,
78
+ *rules: ABCRule,
79
+ ) -> typing.Callable[
80
+ [FuncType[Event]],
81
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
82
+ ]: ...
83
+
84
+ @typing.overload
85
+ @classmethod
86
+ def to_handler(
87
+ cls,
88
+ *rules: ABCRule,
89
+ error_handler: ErrorHandlerT,
90
+ is_blocking: bool = True,
91
+ ) -> typing.Callable[[FuncType[Event]], FuncHandler[Event, FuncType[Event], ErrorHandlerT]]: ...
92
+
93
+ @typing.overload
94
+ @classmethod
95
+ def to_handler(
96
+ cls,
97
+ *rules: ABCRule,
98
+ error_handler: typing.Literal[None] = None,
99
+ is_blocking: bool = True,
100
+ ) -> typing.Callable[
101
+ [FuncType[Event]],
102
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
103
+ ]: ...
104
+
105
+ @classmethod
106
+ def to_handler( # type: ignore
107
+ cls,
108
+ *rules: ABCRule,
109
+ error_handler: ABCErrorHandler | None = None,
110
+ is_blocking: bool = True,
111
+ ):
112
+ def wrapper(func: FuncType[Event]):
113
+ return FuncHandler(
114
+ func,
115
+ list(rules),
116
+ is_blocking=is_blocking,
117
+ dataclass=None,
118
+ error_handler=error_handler or ErrorHandler(),
119
+ )
120
+
121
+ return wrapper
77
122
 
78
123
  @typing.overload
79
124
  def __call__(
80
125
  self,
81
- *rules: ABCRule[EventType],
126
+ *rules: ABCRule,
82
127
  ) -> typing.Callable[
83
- [FuncType[EventType]],
84
- FuncHandler[EventType, FuncType[EventType], ErrorHandler[EventType]],
128
+ [FuncType[Event]],
129
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
85
130
  ]: ...
86
131
 
87
132
  @typing.overload
88
- def __call__(
133
+ def __call__( # type: ignore
89
134
  self,
90
- *rules: ABCRule[EventType],
135
+ *rules: ABCRule,
91
136
  error_handler: ErrorHandlerT,
92
137
  is_blocking: bool = True,
93
- ) -> typing.Callable[
94
- [FuncType[EventType]], FuncHandler[EventType, FuncType[EventType], ErrorHandlerT]
95
- ]: ...
138
+ ) -> typing.Callable[[FuncType[Event]], FuncHandler[Event, FuncType[Event], ErrorHandlerT]]: ...
96
139
 
97
140
  @typing.overload
98
141
  def __call__(
99
142
  self,
100
- *rules: ABCRule[EventType],
143
+ *rules: ABCRule,
101
144
  error_handler: typing.Literal[None] = None,
102
145
  is_blocking: bool = True,
103
146
  ) -> typing.Callable[
104
- [FuncType[EventType]],
105
- FuncHandler[EventType, FuncType[EventType], ErrorHandler[EventType]],
147
+ [FuncType[Event]],
148
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
106
149
  ]: ...
107
150
 
108
151
  def __call__( # type: ignore
109
152
  self,
110
- *rules: ABCRule[EventType],
153
+ *rules: ABCRule,
111
154
  error_handler: ABCErrorHandler | None = None,
112
155
  is_blocking: bool = True,
113
156
  ):
114
- def wrapper(func: FuncType[EventType]):
157
+ def wrapper(func: FuncType[Event]):
115
158
  func_handler = FuncHandler(
116
159
  func,
117
160
  [*self.auto_rules, *rules],
@@ -163,9 +206,9 @@ class BaseView(ABCView, typing.Generic[EventType]):
163
206
  self.middlewares.extend(external.middlewares)
164
207
 
165
208
 
166
- class BaseStateView(ABCStateView[EventType], BaseView[EventType], ABC, typing.Generic[EventType]):
209
+ class BaseStateView(ABCStateView[Event], BaseView[Event], ABC, typing.Generic[Event]):
167
210
  @abstractmethod
168
- def get_state_key(self, event: EventType) -> int | None:
211
+ def get_state_key(self, event: Event) -> int | None:
169
212
  pass
170
213
 
171
214
 
@@ -2,95 +2,111 @@ import dataclasses
2
2
 
3
3
  import typing_extensions as typing
4
4
 
5
+ from telegrinder.bot.dispatch.view import (
6
+ callback_query,
7
+ chat_join_request,
8
+ chat_member,
9
+ inline_query,
10
+ message,
11
+ raw,
12
+ )
13
+ from telegrinder.bot.dispatch.view.abc import ABCView
5
14
  from telegrinder.types.enums import UpdateType
6
15
 
7
- from .abc import ABCView
8
- from .callback_query import CallbackQueryView
9
- from .chat_join_request import ChatJoinRequestView
10
- from .chat_member import ChatMemberView
11
- from .inline_query import InlineQueryView
12
- from .message import MessageView
13
- from .raw import RawEventView
14
-
15
- CallbackQueryViewT = typing.TypeVar("CallbackQueryViewT", bound=ABCView, default=CallbackQueryView)
16
- ChatJoinRequestViewT = typing.TypeVar(
17
- "ChatJoinRequestViewT", bound=ABCView, default=ChatJoinRequestView
16
+ CallbackQueryView = typing.TypeVar(
17
+ "CallbackQueryView", bound=ABCView, default=callback_query.CallbackQueryView
18
18
  )
19
- ChatMemberViewT = typing.TypeVar("ChatMemberViewT", bound=ABCView, default=ChatMemberView)
20
- InlineQueryViewT = typing.TypeVar("InlineQueryViewT", bound=ABCView, default=InlineQueryView)
21
- MessageViewT = typing.TypeVar("MessageViewT", bound=ABCView, default=MessageView)
22
- RawEventViewT = typing.TypeVar("RawEventViewT", bound=ABCView, default=RawEventView)
19
+ ChatJoinRequestView = typing.TypeVar(
20
+ "ChatJoinRequestView", bound=ABCView, default=chat_join_request.ChatJoinRequestView
21
+ )
22
+ ChatMemberView = typing.TypeVar("ChatMemberView", bound=ABCView, default=chat_member.ChatMemberView)
23
+ InlineQueryView = typing.TypeVar("InlineQueryView", bound=ABCView, default=inline_query.InlineQueryView)
24
+ MessageView = typing.TypeVar("MessageView", bound=ABCView, default=message.MessageView)
25
+ RawEventView = typing.TypeVar("RawEventView", bound=ABCView, default=raw.RawEventView)
23
26
 
24
27
 
25
28
  @dataclasses.dataclass(kw_only=True)
26
29
  class ViewBox(
27
30
  typing.Generic[
28
- CallbackQueryViewT,
29
- ChatJoinRequestViewT,
30
- ChatMemberViewT,
31
- InlineQueryViewT,
32
- MessageViewT,
33
- RawEventViewT,
31
+ CallbackQueryView,
32
+ ChatJoinRequestView,
33
+ ChatMemberView,
34
+ InlineQueryView,
35
+ MessageView,
36
+ RawEventView,
34
37
  ],
35
38
  ):
36
- callback_query: CallbackQueryViewT = dataclasses.field(
37
- default_factory=lambda: typing.cast(CallbackQueryViewT, CallbackQueryView()),
39
+ callback_query: CallbackQueryView = dataclasses.field(
40
+ default_factory=lambda: typing.cast(
41
+ CallbackQueryView,
42
+ callback_query.CallbackQueryView(),
43
+ ),
38
44
  )
39
- chat_join_request: ChatJoinRequestViewT = dataclasses.field(
40
- default_factory=lambda: typing.cast(ChatJoinRequestViewT, ChatJoinRequestView()),
45
+ chat_join_request: ChatJoinRequestView = dataclasses.field(
46
+ default_factory=lambda: typing.cast(
47
+ ChatJoinRequestView,
48
+ chat_join_request.ChatJoinRequestView(),
49
+ ),
41
50
  )
42
- chat_member: ChatMemberViewT = dataclasses.field(
51
+ chat_member: ChatMemberView = dataclasses.field(
43
52
  default_factory=lambda: typing.cast(
44
- ChatMemberViewT, ChatMemberView(update_type=UpdateType.CHAT_MEMBER)
53
+ ChatMemberView,
54
+ chat_member.ChatMemberView(update_type=UpdateType.CHAT_MEMBER),
45
55
  ),
46
56
  )
47
- my_chat_member: ChatMemberViewT = dataclasses.field(
57
+ my_chat_member: ChatMemberView = dataclasses.field(
48
58
  default_factory=lambda: typing.cast(
49
- ChatMemberViewT, ChatMemberView(update_type=UpdateType.MY_CHAT_MEMBER)
59
+ ChatMemberView,
60
+ chat_member.ChatMemberView(update_type=UpdateType.MY_CHAT_MEMBER),
50
61
  ),
51
62
  )
52
- inline_query: InlineQueryViewT = dataclasses.field(
53
- default_factory=lambda: typing.cast(InlineQueryViewT, InlineQueryView()),
63
+ inline_query: InlineQueryView = dataclasses.field(
64
+ default_factory=lambda: typing.cast(InlineQueryView, inline_query.InlineQueryView()),
54
65
  )
55
- message: MessageViewT = dataclasses.field(
66
+ message: MessageView = dataclasses.field(
56
67
  default_factory=lambda: typing.cast(
57
- MessageViewT, MessageView(update_type=UpdateType.MESSAGE)
68
+ MessageView,
69
+ message.MessageView(update_type=UpdateType.MESSAGE),
58
70
  ),
59
71
  )
60
- business_message: MessageViewT = dataclasses.field(
72
+ business_message: MessageView = dataclasses.field(
61
73
  default_factory=lambda: typing.cast(
62
- MessageViewT, MessageView(update_type=UpdateType.BUSINESS_MESSAGE)
74
+ MessageView,
75
+ message.MessageView(update_type=UpdateType.BUSINESS_MESSAGE),
63
76
  ),
64
77
  )
65
- channel_post: MessageViewT = dataclasses.field(
78
+ channel_post: MessageView = dataclasses.field(
66
79
  default_factory=lambda: typing.cast(
67
- MessageViewT, MessageView(update_type=UpdateType.CHANNEL_POST)
80
+ MessageView,
81
+ message.MessageView(update_type=UpdateType.CHANNEL_POST),
68
82
  ),
69
83
  )
70
- edited_message: MessageViewT = dataclasses.field(
84
+ edited_message: MessageView = dataclasses.field(
71
85
  default_factory=lambda: typing.cast(
72
- MessageViewT, MessageView(update_type=UpdateType.EDITED_MESSAGE)
86
+ MessageView,
87
+ message.MessageView(update_type=UpdateType.EDITED_MESSAGE),
73
88
  ),
74
89
  )
75
- edited_business_message: MessageViewT = dataclasses.field(
90
+ edited_business_message: MessageView = dataclasses.field(
76
91
  default_factory=lambda: typing.cast(
77
- MessageViewT,
78
- MessageView(update_type=UpdateType.EDITED_BUSINESS_MESSAGE),
92
+ MessageView,
93
+ message.MessageView(update_type=UpdateType.EDITED_BUSINESS_MESSAGE),
79
94
  ),
80
95
  )
81
- edited_channel_post: MessageViewT = dataclasses.field(
96
+ edited_channel_post: MessageView = dataclasses.field(
82
97
  default_factory=lambda: typing.cast(
83
- MessageViewT, MessageView(update_type=UpdateType.EDITED_CHANNEL_POST)
98
+ MessageView,
99
+ message.MessageView(update_type=UpdateType.EDITED_CHANNEL_POST),
84
100
  ),
85
101
  )
86
- any_message: MessageViewT = dataclasses.field(
87
- default_factory=lambda: typing.cast(MessageViewT, MessageView()),
102
+ any_message: MessageView = dataclasses.field(
103
+ default_factory=lambda: typing.cast(MessageView, message.MessageView()),
88
104
  )
89
- chat_member_updated: ChatMemberViewT = dataclasses.field(
90
- default_factory=lambda: typing.cast(ChatMemberViewT, ChatMemberView()),
105
+ chat_member_updated: ChatMemberView = dataclasses.field(
106
+ default_factory=lambda: typing.cast(ChatMemberView, chat_member.ChatMemberView()),
91
107
  )
92
- raw_event: RawEventViewT = dataclasses.field(
93
- default_factory=lambda: typing.cast(RawEventViewT, RawEventView()),
108
+ raw_event: RawEventView = dataclasses.field(
109
+ default_factory=lambda: typing.cast(RawEventView, raw.RawEventView()),
94
110
  )
95
111
 
96
112
  def get_views(self) -> dict[str, ABCView]:
@@ -30,11 +30,7 @@ class MessageView(BaseStateView[MessageCute]):
30
30
  async def check(self, event: Update) -> bool:
31
31
  if not await super().check(event):
32
32
  return False
33
- return (
34
- True
35
- if self.update_type is None
36
- else self.update_type == event.update_type.unwrap_or_none()
37
- )
33
+ return True if self.update_type is None else self.update_type == event.update_type
38
34
 
39
35
 
40
36
  __all__ = ("MessageView",)
@@ -29,7 +29,7 @@ class RawEventView(BaseView[UpdateCute]):
29
29
  def __call__(
30
30
  self,
31
31
  update_type: UpdateType,
32
- *rules: ABCRule[UpdateCute],
32
+ *rules: ABCRule,
33
33
  ) -> typing.Callable[
34
34
  [FuncType[UpdateCute]],
35
35
  FuncHandler[UpdateCute, FuncType[UpdateCute], ErrorHandler[UpdateCute]],
@@ -39,7 +39,7 @@ class RawEventView(BaseView[UpdateCute]):
39
39
  def __call__(
40
40
  self,
41
41
  update_type: UpdateType,
42
- *rules: ABCRule[UpdateCute],
42
+ *rules: ABCRule,
43
43
  dataclass: type[T],
44
44
  ) -> typing.Callable[[FuncType[T]], FuncHandler[UpdateCute, FuncType[T], ErrorHandler[T]]]: ...
45
45
 
@@ -47,7 +47,7 @@ class RawEventView(BaseView[UpdateCute]):
47
47
  def __call__(
48
48
  self,
49
49
  update_type: UpdateType,
50
- *rules: ABCRule[UpdateCute],
50
+ *rules: ABCRule,
51
51
  error_handler: ErrorHandlerT,
52
52
  ) -> typing.Callable[
53
53
  [FuncType[UpdateCute]],
@@ -58,7 +58,7 @@ class RawEventView(BaseView[UpdateCute]):
58
58
  def __call__(
59
59
  self,
60
60
  update_type: UpdateType,
61
- *rules: ABCRule[UpdateCute],
61
+ *rules: ABCRule,
62
62
  dataclass: type[T],
63
63
  error_handler: ErrorHandlerT,
64
64
  is_blocking: bool = True,
@@ -68,7 +68,7 @@ class RawEventView(BaseView[UpdateCute]):
68
68
  def __call__(
69
69
  self,
70
70
  update_type: UpdateType,
71
- *rules: ABCRule[UpdateCute],
71
+ *rules: ABCRule,
72
72
  dataclass: typing.Literal[None] = None,
73
73
  error_handler: typing.Literal[None] = None,
74
74
  is_blocking: bool = True,
@@ -80,7 +80,7 @@ class RawEventView(BaseView[UpdateCute]):
80
80
  def __call__( # type: ignore
81
81
  self,
82
82
  update_type: UpdateType,
83
- *rules: ABCRule[typing.Any],
83
+ *rules: ABCRule,
84
84
  dataclass: type[typing.Any] | None = None,
85
85
  error_handler: ABCErrorHandler | None = None,
86
86
  is_blocking: bool = True,
@@ -1,4 +1,4 @@
1
- from .machine import WaiterMachine
1
+ from .machine import WaiterMachine, clear_wm_storage_worker
2
2
  from .middleware import WaiterMiddleware
3
3
  from .short_state import ShortState
4
4
 
@@ -6,4 +6,5 @@ __all__ = (
6
6
  "ShortState",
7
7
  "WaiterMachine",
8
8
  "WaiterMiddleware",
9
+ "clear_wm_storage_worker",
9
10
  )
@@ -4,61 +4,61 @@ import typing
4
4
 
5
5
  from telegrinder.api.abc import ABCAPI
6
6
  from telegrinder.bot.dispatch.context import Context
7
+ from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
7
8
  from telegrinder.bot.rules.abc import ABCRule
8
9
  from telegrinder.tools.limited_dict import LimitedDict
9
10
  from telegrinder.types import Update
10
11
 
11
12
  from .middleware import WaiterMiddleware
12
- from .short_state import Behaviour, EventModel, ShortState
13
+ from .short_state import Behaviour, EventModel, ShortState, ShortStateContext
13
14
 
14
15
  if typing.TYPE_CHECKING:
15
- from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
16
+ from telegrinder.bot.dispatch import Dispatch
16
17
 
17
18
  T = typing.TypeVar("T")
18
19
 
19
20
  Identificator: typing.TypeAlias = str | int
20
21
  Storage: typing.TypeAlias = dict[str, LimitedDict[Identificator, ShortState[EventModel]]]
21
22
 
23
+ WEEK: typing.Final[datetime.timedelta] = datetime.timedelta(days=7)
24
+
22
25
 
23
26
  class WaiterMachine:
24
- def __init__(self) -> None:
27
+ def __init__(self, *, max_storage_size: int = 1000) -> None:
28
+ self.max_storage_size = max_storage_size
25
29
  self.storage: Storage = {}
26
30
 
27
31
  def __repr__(self) -> str:
28
- return "<{}: storage={!r}>".format(
32
+ return "<{}: max_storage_size={}, {}>".format(
29
33
  self.__class__.__name__,
30
- self.storage,
34
+ self.max_storage_size,
35
+ ", ".join(
36
+ f"{view_name}: {len(self.storage[view_name].values())} shortstates"
37
+ for view_name in self.storage
38
+ )
39
+ or "empty",
31
40
  )
32
41
 
33
42
  async def drop(
34
43
  self,
35
44
  state_view: "ABCStateView[EventModel]",
36
45
  id: Identificator,
46
+ event: EventModel,
37
47
  update: Update,
38
48
  **context: typing.Any,
39
49
  ) -> None:
40
50
  view_name = state_view.__class__.__name__
41
51
  if view_name not in self.storage:
42
- raise LookupError("No record of view {!r} found".format(view_name))
52
+ raise LookupError("No record of view {!r} found.".format(view_name))
43
53
 
44
54
  short_state = self.storage[view_name].pop(id, None)
45
55
  if short_state is None:
46
- raise LookupError(
47
- "Waiter with identificator {} is not found for view {!r}".format(
48
- id, view_name
49
- )
50
- )
51
-
52
- waiters = typing.cast(
53
- typing.Iterable[asyncio.Future[typing.Any]],
54
- short_state.event._waiters, # type: ignore
55
- )
56
- for future in waiters:
57
- future.cancel()
56
+ raise LookupError("Waiter with identificator {} is not found for view {!r}".format(id, view_name))
58
57
 
58
+ short_state.cancel()
59
59
  await self.call_behaviour(
60
60
  state_view,
61
- short_state.event,
61
+ event,
62
62
  update,
63
63
  behaviour=short_state.on_drop_behaviour,
64
64
  **context,
@@ -68,26 +68,24 @@ class WaiterMachine:
68
68
  self,
69
69
  state_view: "BaseStateView[EventModel]",
70
70
  linked: EventModel | tuple[ABCAPI, Identificator],
71
- *rules: ABCRule[EventModel],
72
- default: Behaviour = None,
73
- on_drop: Behaviour = None,
74
- expiration: datetime.timedelta | int | None = None,
75
- ) -> tuple[EventModel, Context]:
71
+ *rules: ABCRule,
72
+ default: Behaviour[EventModel] | None = None,
73
+ on_drop: Behaviour[EventModel] | None = None,
74
+ exit: Behaviour[EventModel] | None = None,
75
+ expiration: datetime.timedelta | float | None = None,
76
+ ) -> ShortStateContext[EventModel]:
76
77
  if isinstance(expiration, int | float):
77
78
  expiration = datetime.timedelta(seconds=expiration)
78
79
 
79
80
  api: ABCAPI
80
81
  key: Identificator
81
- api, key = (
82
- linked
83
- if isinstance(linked, tuple)
84
- else (linked.ctx_api, state_view.get_state_key(linked))
85
- ) # type: ignore
82
+ api, key = linked if isinstance(linked, tuple) else (linked.ctx_api, state_view.get_state_key(linked)) # type: ignore
86
83
  if not key:
87
84
  raise RuntimeError("Unable to get state key.")
88
85
 
86
+ view_name = state_view.__class__.__name__
89
87
  event = asyncio.Event()
90
- short_state = ShortState(
88
+ short_state = ShortState[EventModel](
91
89
  key,
92
90
  api,
93
91
  event,
@@ -95,43 +93,81 @@ class WaiterMachine:
95
93
  expiration=expiration,
96
94
  default_behaviour=default,
97
95
  on_drop_behaviour=on_drop,
96
+ exit_behaviour=exit,
98
97
  )
99
- view_name = state_view.__class__.__name__
98
+
100
99
  if view_name not in self.storage:
101
100
  state_view.middlewares.insert(0, WaiterMiddleware(self, state_view))
102
- self.storage[view_name] = LimitedDict()
101
+ self.storage[view_name] = LimitedDict(maxlimit=self.max_storage_size)
103
102
 
104
- self.storage[view_name][key] = short_state
105
- await event.wait()
103
+ if (deleted_short_state := self.storage[view_name].set(key, short_state)) is not None:
104
+ deleted_short_state.cancel()
106
105
 
107
- e, ctx = getattr(event, "context")
106
+ await event.wait()
108
107
  self.storage[view_name].pop(key, None)
109
- return e, ctx
108
+ assert short_state.context is not None
109
+ return short_state.context
110
110
 
111
111
  async def call_behaviour(
112
112
  self,
113
113
  view: "ABCStateView[EventModel]",
114
- event: asyncio.Event | EventModel,
114
+ event: EventModel,
115
115
  update: Update,
116
116
  behaviour: Behaviour[EventModel] | None = None,
117
117
  **context: typing.Any,
118
- ) -> None:
119
- # TODO: support param view as a behaviour
120
-
121
- ctx = Context(**context)
122
-
123
- if isinstance(event, asyncio.Event):
124
- event, preset_ctx = typing.cast(
125
- tuple[EventModel, Context],
126
- getattr(event, "context"),
127
- )
128
- ctx.update(preset_ctx)
118
+ ) -> bool:
119
+ # TODO: support view as a behaviour
129
120
 
130
121
  if behaviour is None:
131
- return
122
+ return False
132
123
 
124
+ ctx = Context(**context)
133
125
  if await behaviour.check(event.api, update, ctx):
134
126
  await behaviour.run(event, ctx)
135
-
127
+ return True
128
+
129
+ return False
130
+
131
+ async def clear_storage(
132
+ self,
133
+ views: typing.Iterable[ABCStateView[EventModel]],
134
+ absolutely_dead_time: datetime.timedelta = WEEK,
135
+ ):
136
+ """Clears storage.
137
+
138
+ :param absolutely_dead_time: timedelta when state can be forgotten.
139
+ """
140
+
141
+ for view in views:
142
+ view_name = view.__class__.__name__
143
+ now = datetime.datetime.now()
144
+ for ident, short_state in self.storage.get(view_name, {}).copy().items():
145
+ if short_state.expiration_date is not None and now > short_state.expiration_date:
146
+ assert short_state.context
147
+ await self.drop(
148
+ view,
149
+ ident,
150
+ event=short_state.context.event,
151
+ update=short_state.context.context.raw_update,
152
+ force=True,
153
+ )
154
+ elif short_state.creation_date + absolutely_dead_time < now:
155
+ short_state.cancel()
156
+ del self.storage[view_name][short_state.key]
157
+
158
+
159
+ async def clear_wm_storage_worker(
160
+ wm: WaiterMachine,
161
+ dp: "Dispatch",
162
+ interval_seconds: int = 60,
163
+ absolutely_dead_time: datetime.timedelta = WEEK,
164
+ ) -> typing.NoReturn:
165
+ while True:
166
+ await wm.clear_storage(
167
+ views=[view for view in dp.get_views().values() if isinstance(view, ABCStateView)],
168
+ absolutely_dead_time=absolutely_dead_time,
169
+ )
170
+ await asyncio.sleep(interval_seconds)
171
+
136
172
 
137
173
  __all__ = ("WaiterMachine",)