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.
- telegrinder/__init__.py +9 -3
- telegrinder/bot/__init__.py +7 -5
- telegrinder/bot/cute_types/base.py +12 -14
- telegrinder/bot/cute_types/callback_query.py +55 -44
- telegrinder/bot/cute_types/chat_join_request.py +8 -7
- telegrinder/bot/cute_types/chat_member_updated.py +23 -17
- telegrinder/bot/cute_types/inline_query.py +1 -1
- telegrinder/bot/cute_types/message.py +331 -183
- telegrinder/bot/cute_types/update.py +4 -8
- telegrinder/bot/cute_types/utils.py +1 -5
- telegrinder/bot/dispatch/__init__.py +2 -3
- telegrinder/bot/dispatch/abc.py +4 -0
- telegrinder/bot/dispatch/context.py +9 -4
- telegrinder/bot/dispatch/dispatch.py +33 -30
- telegrinder/bot/dispatch/handler/func.py +33 -12
- telegrinder/bot/dispatch/handler/message_reply.py +6 -3
- telegrinder/bot/dispatch/middleware/abc.py +4 -4
- telegrinder/bot/dispatch/process.py +40 -13
- telegrinder/bot/dispatch/return_manager/abc.py +12 -12
- telegrinder/bot/dispatch/return_manager/callback_query.py +1 -3
- telegrinder/bot/dispatch/return_manager/inline_query.py +1 -3
- telegrinder/bot/dispatch/view/abc.py +74 -31
- telegrinder/bot/dispatch/view/box.py +66 -50
- telegrinder/bot/dispatch/view/message.py +1 -5
- telegrinder/bot/dispatch/view/raw.py +6 -6
- telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -1
- telegrinder/bot/dispatch/waiter_machine/machine.py +86 -50
- telegrinder/bot/dispatch/waiter_machine/middleware.py +31 -7
- telegrinder/bot/dispatch/waiter_machine/short_state.py +26 -7
- telegrinder/bot/polling/polling.py +4 -4
- telegrinder/bot/rules/__init__.py +9 -6
- telegrinder/bot/rules/abc.py +99 -22
- telegrinder/bot/rules/adapter/__init__.py +4 -1
- telegrinder/bot/rules/adapter/abc.py +11 -6
- telegrinder/bot/rules/adapter/errors.py +1 -2
- telegrinder/bot/rules/adapter/event.py +14 -9
- telegrinder/bot/rules/adapter/node.py +42 -0
- telegrinder/bot/rules/callback_data.py +13 -15
- telegrinder/bot/rules/chat_join.py +3 -2
- telegrinder/bot/rules/command.py +26 -14
- telegrinder/bot/rules/enum_text.py +5 -5
- telegrinder/bot/rules/func.py +6 -6
- telegrinder/bot/rules/fuzzy.py +5 -7
- telegrinder/bot/rules/inline.py +4 -5
- telegrinder/bot/rules/integer.py +10 -8
- telegrinder/bot/rules/is_from.py +63 -91
- telegrinder/bot/rules/markup.py +5 -5
- telegrinder/bot/rules/mention.py +4 -4
- telegrinder/bot/rules/message.py +1 -1
- telegrinder/bot/rules/node.py +27 -0
- telegrinder/bot/rules/regex.py +5 -5
- telegrinder/bot/rules/rule_enum.py +4 -4
- telegrinder/bot/rules/start.py +5 -5
- telegrinder/bot/rules/text.py +9 -13
- telegrinder/bot/rules/update.py +4 -4
- telegrinder/bot/scenario/__init__.py +3 -3
- telegrinder/bot/scenario/checkbox.py +5 -5
- telegrinder/bot/scenario/choice.py +5 -5
- telegrinder/model.py +49 -15
- telegrinder/modules.py +14 -6
- telegrinder/msgspec_utils.py +8 -17
- telegrinder/node/__init__.py +26 -8
- telegrinder/node/attachment.py +13 -9
- telegrinder/node/base.py +27 -14
- telegrinder/node/callback_query.py +18 -0
- telegrinder/node/command.py +29 -0
- telegrinder/node/composer.py +119 -30
- telegrinder/node/me.py +14 -0
- telegrinder/node/message.py +2 -4
- telegrinder/node/polymorphic.py +44 -0
- telegrinder/node/rule.py +26 -22
- telegrinder/node/scope.py +36 -0
- telegrinder/node/source.py +37 -10
- telegrinder/node/text.py +11 -5
- telegrinder/node/tools/__init__.py +2 -2
- telegrinder/node/tools/generator.py +6 -6
- telegrinder/tools/__init__.py +9 -14
- telegrinder/tools/buttons.py +23 -17
- telegrinder/tools/error_handler/error_handler.py +11 -14
- telegrinder/tools/formatting/__init__.py +0 -6
- telegrinder/tools/formatting/html.py +10 -12
- telegrinder/tools/formatting/links.py +0 -5
- telegrinder/tools/formatting/spec_html_formats.py +0 -11
- telegrinder/tools/global_context/abc.py +1 -3
- telegrinder/tools/global_context/global_context.py +6 -16
- telegrinder/tools/i18n/simple.py +1 -3
- telegrinder/tools/kb_set/yaml.py +1 -2
- telegrinder/tools/keyboard.py +7 -8
- telegrinder/tools/limited_dict.py +13 -3
- telegrinder/tools/loop_wrapper/loop_wrapper.py +6 -5
- telegrinder/tools/magic.py +27 -5
- telegrinder/types/__init__.py +20 -0
- telegrinder/types/enums.py +37 -31
- telegrinder/types/methods.py +613 -401
- telegrinder/types/objects.py +1151 -757
- {telegrinder-0.1.dev167.dist-info → telegrinder-0.1.dev169.dist-info}/LICENSE +1 -1
- {telegrinder-0.1.dev167.dist-info → telegrinder-0.1.dev169.dist-info}/METADATA +9 -8
- telegrinder-0.1.dev169.dist-info/RECORD +143 -0
- telegrinder/bot/dispatch/composition.py +0 -88
- telegrinder-0.1.dev167.dist-info/RECORD +0 -137
- {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
|
-
|
|
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[
|
|
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[
|
|
49
|
+
class ABCStateView(ABCView, typing.Generic[Event]):
|
|
50
50
|
@abstractmethod
|
|
51
|
-
def get_state_key(self, event:
|
|
51
|
+
def get_state_key(self, event: Event) -> int | None:
|
|
52
52
|
pass
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
class BaseView(ABCView, typing.Generic[
|
|
56
|
-
auto_rules: list[ABCRule
|
|
57
|
-
handlers: list[ABCHandler[
|
|
58
|
-
middlewares: list[ABCMiddleware[
|
|
59
|
-
return_manager: ABCReturnManager[
|
|
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[
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
126
|
+
*rules: ABCRule,
|
|
82
127
|
) -> typing.Callable[
|
|
83
|
-
[FuncType[
|
|
84
|
-
FuncHandler[
|
|
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
|
|
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
|
|
143
|
+
*rules: ABCRule,
|
|
101
144
|
error_handler: typing.Literal[None] = None,
|
|
102
145
|
is_blocking: bool = True,
|
|
103
146
|
) -> typing.Callable[
|
|
104
|
-
[FuncType[
|
|
105
|
-
FuncHandler[
|
|
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
|
|
153
|
+
*rules: ABCRule,
|
|
111
154
|
error_handler: ABCErrorHandler | None = None,
|
|
112
155
|
is_blocking: bool = True,
|
|
113
156
|
):
|
|
114
|
-
def wrapper(func: FuncType[
|
|
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[
|
|
209
|
+
class BaseStateView(ABCStateView[Event], BaseView[Event], ABC, typing.Generic[Event]):
|
|
167
210
|
@abstractmethod
|
|
168
|
-
def get_state_key(self, event:
|
|
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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
CallbackQueryView,
|
|
32
|
+
ChatJoinRequestView,
|
|
33
|
+
ChatMemberView,
|
|
34
|
+
InlineQueryView,
|
|
35
|
+
MessageView,
|
|
36
|
+
RawEventView,
|
|
34
37
|
],
|
|
35
38
|
):
|
|
36
|
-
callback_query:
|
|
37
|
-
default_factory=lambda: typing.cast(
|
|
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:
|
|
40
|
-
default_factory=lambda: typing.cast(
|
|
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:
|
|
51
|
+
chat_member: ChatMemberView = dataclasses.field(
|
|
43
52
|
default_factory=lambda: typing.cast(
|
|
44
|
-
|
|
53
|
+
ChatMemberView,
|
|
54
|
+
chat_member.ChatMemberView(update_type=UpdateType.CHAT_MEMBER),
|
|
45
55
|
),
|
|
46
56
|
)
|
|
47
|
-
my_chat_member:
|
|
57
|
+
my_chat_member: ChatMemberView = dataclasses.field(
|
|
48
58
|
default_factory=lambda: typing.cast(
|
|
49
|
-
|
|
59
|
+
ChatMemberView,
|
|
60
|
+
chat_member.ChatMemberView(update_type=UpdateType.MY_CHAT_MEMBER),
|
|
50
61
|
),
|
|
51
62
|
)
|
|
52
|
-
inline_query:
|
|
53
|
-
default_factory=lambda: typing.cast(
|
|
63
|
+
inline_query: InlineQueryView = dataclasses.field(
|
|
64
|
+
default_factory=lambda: typing.cast(InlineQueryView, inline_query.InlineQueryView()),
|
|
54
65
|
)
|
|
55
|
-
message:
|
|
66
|
+
message: MessageView = dataclasses.field(
|
|
56
67
|
default_factory=lambda: typing.cast(
|
|
57
|
-
|
|
68
|
+
MessageView,
|
|
69
|
+
message.MessageView(update_type=UpdateType.MESSAGE),
|
|
58
70
|
),
|
|
59
71
|
)
|
|
60
|
-
business_message:
|
|
72
|
+
business_message: MessageView = dataclasses.field(
|
|
61
73
|
default_factory=lambda: typing.cast(
|
|
62
|
-
|
|
74
|
+
MessageView,
|
|
75
|
+
message.MessageView(update_type=UpdateType.BUSINESS_MESSAGE),
|
|
63
76
|
),
|
|
64
77
|
)
|
|
65
|
-
channel_post:
|
|
78
|
+
channel_post: MessageView = dataclasses.field(
|
|
66
79
|
default_factory=lambda: typing.cast(
|
|
67
|
-
|
|
80
|
+
MessageView,
|
|
81
|
+
message.MessageView(update_type=UpdateType.CHANNEL_POST),
|
|
68
82
|
),
|
|
69
83
|
)
|
|
70
|
-
edited_message:
|
|
84
|
+
edited_message: MessageView = dataclasses.field(
|
|
71
85
|
default_factory=lambda: typing.cast(
|
|
72
|
-
|
|
86
|
+
MessageView,
|
|
87
|
+
message.MessageView(update_type=UpdateType.EDITED_MESSAGE),
|
|
73
88
|
),
|
|
74
89
|
)
|
|
75
|
-
edited_business_message:
|
|
90
|
+
edited_business_message: MessageView = dataclasses.field(
|
|
76
91
|
default_factory=lambda: typing.cast(
|
|
77
|
-
|
|
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:
|
|
96
|
+
edited_channel_post: MessageView = dataclasses.field(
|
|
82
97
|
default_factory=lambda: typing.cast(
|
|
83
|
-
|
|
98
|
+
MessageView,
|
|
99
|
+
message.MessageView(update_type=UpdateType.EDITED_CHANNEL_POST),
|
|
84
100
|
),
|
|
85
101
|
)
|
|
86
|
-
any_message:
|
|
87
|
-
default_factory=lambda: typing.cast(
|
|
102
|
+
any_message: MessageView = dataclasses.field(
|
|
103
|
+
default_factory=lambda: typing.cast(MessageView, message.MessageView()),
|
|
88
104
|
)
|
|
89
|
-
chat_member_updated:
|
|
90
|
-
default_factory=lambda: typing.cast(
|
|
105
|
+
chat_member_updated: ChatMemberView = dataclasses.field(
|
|
106
|
+
default_factory=lambda: typing.cast(ChatMemberView, chat_member.ChatMemberView()),
|
|
91
107
|
)
|
|
92
|
-
raw_event:
|
|
93
|
-
default_factory=lambda: typing.cast(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 "<{}:
|
|
32
|
+
return "<{}: max_storage_size={}, {}>".format(
|
|
29
33
|
self.__class__.__name__,
|
|
30
|
-
self.
|
|
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
|
-
|
|
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
|
|
72
|
-
default: Behaviour = None,
|
|
73
|
-
on_drop: Behaviour = None,
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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]
|
|
105
|
-
|
|
103
|
+
if (deleted_short_state := self.storage[view_name].set(key, short_state)) is not None:
|
|
104
|
+
deleted_short_state.cancel()
|
|
106
105
|
|
|
107
|
-
|
|
106
|
+
await event.wait()
|
|
108
107
|
self.storage[view_name].pop(key, None)
|
|
109
|
-
|
|
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:
|
|
114
|
+
event: EventModel,
|
|
115
115
|
update: Update,
|
|
116
116
|
behaviour: Behaviour[EventModel] | None = None,
|
|
117
117
|
**context: typing.Any,
|
|
118
|
-
) ->
|
|
119
|
-
# TODO: support
|
|
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",)
|