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
@@ -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,55 +71,45 @@ class BaseView(ABCView, typing.Generic[EventType]):
67
71
  return Some(generic_type)
68
72
  return Nothing()
69
73
 
70
- @staticmethod
71
- def get_raw_event(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()
77
-
78
74
  @typing.overload
79
75
  @classmethod
80
76
  def to_handler(
81
77
  cls,
82
- *rules: ABCRule[EventType],
78
+ *rules: ABCRule,
83
79
  ) -> typing.Callable[
84
- [FuncType[EventType]],
85
- FuncHandler[EventType, FuncType[EventType], ErrorHandler[EventType]],
80
+ [FuncType[Event]],
81
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
86
82
  ]: ...
87
83
 
88
84
  @typing.overload
89
85
  @classmethod
90
86
  def to_handler(
91
87
  cls,
92
- *rules: ABCRule[EventType],
88
+ *rules: ABCRule,
93
89
  error_handler: ErrorHandlerT,
94
90
  is_blocking: bool = True,
95
- ) -> typing.Callable[
96
- [FuncType[EventType]], FuncHandler[EventType, FuncType[EventType], ErrorHandlerT]
97
- ]: ...
91
+ ) -> typing.Callable[[FuncType[Event]], FuncHandler[Event, FuncType[Event], ErrorHandlerT]]: ...
98
92
 
99
93
  @typing.overload
100
94
  @classmethod
101
95
  def to_handler(
102
96
  cls,
103
- *rules: ABCRule[EventType],
97
+ *rules: ABCRule,
104
98
  error_handler: typing.Literal[None] = None,
105
99
  is_blocking: bool = True,
106
100
  ) -> typing.Callable[
107
- [FuncType[EventType]],
108
- FuncHandler[EventType, FuncType[EventType], ErrorHandler[EventType]],
101
+ [FuncType[Event]],
102
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
109
103
  ]: ...
110
104
 
111
105
  @classmethod
112
106
  def to_handler( # type: ignore
113
107
  cls,
114
- *rules: ABCRule[EventType],
108
+ *rules: ABCRule,
115
109
  error_handler: ABCErrorHandler | None = None,
116
110
  is_blocking: bool = True,
117
111
  ):
118
- def wrapper(func: FuncType[EventType]):
112
+ def wrapper(func: FuncType[Event]):
119
113
  return FuncHandler(
120
114
  func,
121
115
  list(rules),
@@ -129,40 +123,38 @@ class BaseView(ABCView, typing.Generic[EventType]):
129
123
  @typing.overload
130
124
  def __call__(
131
125
  self,
132
- *rules: ABCRule[EventType],
126
+ *rules: ABCRule,
133
127
  ) -> typing.Callable[
134
- [FuncType[EventType]],
135
- FuncHandler[EventType, FuncType[EventType], ErrorHandler[EventType]],
128
+ [FuncType[Event]],
129
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
136
130
  ]: ...
137
131
 
138
132
  @typing.overload
139
- def __call__(
133
+ def __call__( # type: ignore
140
134
  self,
141
- *rules: ABCRule[EventType],
135
+ *rules: ABCRule,
142
136
  error_handler: ErrorHandlerT,
143
137
  is_blocking: bool = True,
144
- ) -> typing.Callable[
145
- [FuncType[EventType]], FuncHandler[EventType, FuncType[EventType], ErrorHandlerT]
146
- ]: ...
138
+ ) -> typing.Callable[[FuncType[Event]], FuncHandler[Event, FuncType[Event], ErrorHandlerT]]: ...
147
139
 
148
140
  @typing.overload
149
141
  def __call__(
150
142
  self,
151
- *rules: ABCRule[EventType],
143
+ *rules: ABCRule,
152
144
  error_handler: typing.Literal[None] = None,
153
145
  is_blocking: bool = True,
154
146
  ) -> typing.Callable[
155
- [FuncType[EventType]],
156
- FuncHandler[EventType, FuncType[EventType], ErrorHandler[EventType]],
147
+ [FuncType[Event]],
148
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
157
149
  ]: ...
158
150
 
159
151
  def __call__( # type: ignore
160
152
  self,
161
- *rules: ABCRule[EventType],
153
+ *rules: ABCRule,
162
154
  error_handler: ABCErrorHandler | None = None,
163
155
  is_blocking: bool = True,
164
156
  ):
165
- def wrapper(func: FuncType[EventType]):
157
+ def wrapper(func: FuncType[Event]):
166
158
  func_handler = FuncHandler(
167
159
  func,
168
160
  [*self.auto_rules, *rules],
@@ -214,9 +206,9 @@ class BaseView(ABCView, typing.Generic[EventType]):
214
206
  self.middlewares.extend(external.middlewares)
215
207
 
216
208
 
217
- class BaseStateView(ABCStateView[EventType], BaseView[EventType], ABC, typing.Generic[EventType]):
209
+ class BaseStateView(ABCStateView[Event], BaseView[Event], ABC, typing.Generic[Event]):
218
210
  @abstractmethod
219
- def get_state_key(self, event: EventType) -> int | None:
211
+ def get_state_key(self, event: Event) -> int | None:
220
212
  pass
221
213
 
222
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,36 +4,46 @@ 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:
@@ -43,16 +53,12 @@ class WaiterMachine:
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
- )
56
+ raise LookupError("Waiter with identificator {} is not found for view {!r}".format(id, view_name))
51
57
 
52
58
  short_state.cancel()
53
59
  await self.call_behaviour(
54
60
  state_view,
55
- short_state.event,
61
+ event,
56
62
  update,
57
63
  behaviour=short_state.on_drop_behaviour,
58
64
  **context,
@@ -62,27 +68,24 @@ class WaiterMachine:
62
68
  self,
63
69
  state_view: "BaseStateView[EventModel]",
64
70
  linked: EventModel | tuple[ABCAPI, Identificator],
65
- *rules: ABCRule[EventModel],
71
+ *rules: ABCRule,
66
72
  default: Behaviour[EventModel] | None = None,
67
73
  on_drop: Behaviour[EventModel] | None = None,
74
+ exit: Behaviour[EventModel] | None = None,
68
75
  expiration: datetime.timedelta | float | None = None,
69
- ) -> tuple[EventModel, Context]:
76
+ ) -> ShortStateContext[EventModel]:
70
77
  if isinstance(expiration, int | float):
71
78
  expiration = datetime.timedelta(seconds=expiration)
72
79
 
73
80
  api: ABCAPI
74
81
  key: Identificator
75
- api, key = (
76
- linked
77
- if isinstance(linked, tuple)
78
- else (linked.ctx_api, state_view.get_state_key(linked))
79
- ) # type: ignore
82
+ api, key = linked if isinstance(linked, tuple) else (linked.ctx_api, state_view.get_state_key(linked)) # type: ignore
80
83
  if not key:
81
84
  raise RuntimeError("Unable to get state key.")
82
85
 
83
86
  view_name = state_view.__class__.__name__
84
87
  event = asyncio.Event()
85
- short_state = ShortState(
88
+ short_state = ShortState[EventModel](
86
89
  key,
87
90
  api,
88
91
  event,
@@ -90,42 +93,81 @@ class WaiterMachine:
90
93
  expiration=expiration,
91
94
  default_behaviour=default,
92
95
  on_drop_behaviour=on_drop,
96
+ exit_behaviour=exit,
93
97
  )
94
-
98
+
95
99
  if view_name not in self.storage:
96
100
  state_view.middlewares.insert(0, WaiterMiddleware(self, state_view))
97
- self.storage[view_name] = LimitedDict()
101
+ self.storage[view_name] = LimitedDict(maxlimit=self.max_storage_size)
98
102
 
99
103
  if (deleted_short_state := self.storage[view_name].set(key, short_state)) is not None:
100
104
  deleted_short_state.cancel()
101
-
105
+
102
106
  await event.wait()
103
107
  self.storage[view_name].pop(key, None)
104
- return getattr(event, "context")
108
+ assert short_state.context is not None
109
+ return short_state.context
105
110
 
106
111
  async def call_behaviour(
107
112
  self,
108
113
  view: "ABCStateView[EventModel]",
109
- event: asyncio.Event | EventModel,
114
+ event: EventModel,
110
115
  update: Update,
111
116
  behaviour: Behaviour[EventModel] | None = None,
112
117
  **context: typing.Any,
113
- ) -> None:
114
- # TODO: support param view as a behaviour
118
+ ) -> bool:
119
+ # TODO: support view as a behaviour
115
120
 
116
121
  if behaviour is None:
117
- return
122
+ return False
118
123
 
119
124
  ctx = Context(**context)
120
- if isinstance(event, asyncio.Event):
121
- event, preset_ctx = typing.cast(
122
- tuple[EventModel, Context],
123
- getattr(event, "context"),
124
- )
125
- ctx.update(preset_ctx)
126
-
127
125
  if await behaviour.check(event.api, update, ctx):
128
126
  await behaviour.run(event, ctx)
129
-
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
+
130
172
 
131
173
  __all__ = ("WaiterMachine",)