telegrinder 0.3.4__py3-none-any.whl → 0.4.0__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 +148 -149
- telegrinder/api/__init__.py +9 -8
- telegrinder/api/api.py +101 -93
- telegrinder/api/error.py +20 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +72 -66
- telegrinder/bot/bot.py +83 -76
- telegrinder/bot/cute_types/__init__.py +19 -17
- telegrinder/bot/cute_types/base.py +184 -258
- telegrinder/bot/cute_types/callback_query.py +400 -385
- telegrinder/bot/cute_types/chat_join_request.py +62 -61
- telegrinder/bot/cute_types/chat_member_updated.py +157 -160
- telegrinder/bot/cute_types/inline_query.py +44 -43
- telegrinder/bot/cute_types/message.py +2590 -2637
- telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
- telegrinder/bot/cute_types/update.py +112 -104
- telegrinder/bot/cute_types/utils.py +62 -95
- telegrinder/bot/dispatch/__init__.py +59 -55
- telegrinder/bot/dispatch/abc.py +76 -77
- telegrinder/bot/dispatch/context.py +96 -98
- telegrinder/bot/dispatch/dispatch.py +254 -202
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +23 -24
- telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
- telegrinder/bot/dispatch/handler/base.py +57 -57
- telegrinder/bot/dispatch/handler/document_reply.py +44 -44
- telegrinder/bot/dispatch/handler/func.py +129 -135
- telegrinder/bot/dispatch/handler/media_group_reply.py +44 -43
- telegrinder/bot/dispatch/handler/message_reply.py +36 -36
- telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
- telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
- telegrinder/bot/dispatch/handler/video_reply.py +44 -44
- telegrinder/bot/dispatch/middleware/__init__.py +3 -3
- telegrinder/bot/dispatch/middleware/abc.py +97 -22
- telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
- telegrinder/bot/dispatch/process.py +151 -157
- telegrinder/bot/dispatch/return_manager/__init__.py +15 -13
- telegrinder/bot/dispatch/return_manager/abc.py +104 -108
- telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
- telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
- telegrinder/bot/dispatch/return_manager/message.py +36 -36
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -13
- telegrinder/bot/dispatch/view/abc.py +45 -41
- telegrinder/bot/dispatch/view/base.py +231 -200
- telegrinder/bot/dispatch/view/box.py +140 -129
- telegrinder/bot/dispatch/view/callback_query.py +16 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +11 -16
- telegrinder/bot/dispatch/view/chat_member.py +37 -39
- telegrinder/bot/dispatch/view/inline_query.py +16 -17
- telegrinder/bot/dispatch/view/message.py +43 -44
- telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
- telegrinder/bot/dispatch/view/raw.py +116 -114
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +14 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +59 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +20 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +251 -172
- telegrinder/bot/dispatch/waiter_machine/middleware.py +94 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +57 -68
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +139 -131
- telegrinder/bot/rules/__init__.py +85 -62
- telegrinder/bot/rules/abc.py +213 -206
- telegrinder/bot/rules/callback_data.py +122 -163
- telegrinder/bot/rules/chat_join.py +45 -43
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +33 -36
- telegrinder/bot/rules/func.py +28 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/id.py +24 -0
- telegrinder/bot/rules/inline.py +58 -56
- telegrinder/bot/rules/integer.py +21 -20
- telegrinder/bot/rules/is_from.py +127 -127
- telegrinder/bot/rules/logic.py +18 -0
- telegrinder/bot/rules/markup.py +42 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +15 -17
- telegrinder/bot/rules/message_entities.py +33 -35
- telegrinder/bot/rules/node.py +33 -27
- telegrinder/bot/rules/payload.py +81 -0
- telegrinder/bot/rules/payment_invoice.py +29 -0
- telegrinder/bot/rules/regex.py +36 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +35 -37
- telegrinder/bot/rules/text.py +38 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +17 -19
- telegrinder/bot/scenario/checkbox.py +174 -176
- telegrinder/bot/scenario/choice.py +48 -51
- telegrinder/client/__init__.py +12 -4
- telegrinder/client/abc.py +100 -75
- telegrinder/client/aiohttp.py +134 -130
- telegrinder/client/form_data.py +31 -0
- telegrinder/client/sonic.py +212 -0
- telegrinder/model.py +208 -315
- telegrinder/modules.py +239 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +478 -410
- telegrinder/node/__init__.py +86 -25
- telegrinder/node/attachment.py +163 -87
- telegrinder/node/base.py +288 -160
- telegrinder/node/callback_query.py +54 -53
- telegrinder/node/command.py +34 -33
- telegrinder/node/composer.py +163 -198
- telegrinder/node/container.py +33 -27
- telegrinder/node/either.py +82 -0
- telegrinder/node/event.py +54 -65
- telegrinder/node/file.py +51 -0
- telegrinder/node/me.py +15 -16
- telegrinder/node/payload.py +78 -0
- telegrinder/node/polymorphic.py +67 -48
- telegrinder/node/rule.py +72 -76
- telegrinder/node/scope.py +36 -38
- telegrinder/node/source.py +87 -71
- telegrinder/node/text.py +53 -41
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +36 -40
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -62
- telegrinder/tools/__init__.py +152 -93
- telegrinder/tools/adapter/__init__.py +19 -0
- telegrinder/tools/adapter/abc.py +49 -0
- telegrinder/tools/adapter/dataclass.py +56 -0
- telegrinder/{bot/rules → tools}/adapter/errors.py +5 -5
- telegrinder/{bot/rules → tools}/adapter/event.py +63 -65
- telegrinder/{bot/rules → tools}/adapter/node.py +46 -48
- telegrinder/{bot/rules → tools}/adapter/raw_event.py +27 -27
- telegrinder/{bot/rules → tools}/adapter/raw_update.py +30 -30
- telegrinder/tools/buttons.py +106 -80
- telegrinder/tools/callback_data_serilization/__init__.py +5 -0
- telegrinder/tools/callback_data_serilization/abc.py +51 -0
- telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
- telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
- telegrinder/tools/error_handler/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +30 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +179 -193
- telegrinder/tools/formatting/__init__.py +83 -63
- telegrinder/tools/formatting/deep_links.py +541 -0
- telegrinder/tools/formatting/{html.py → html_formatter.py} +266 -294
- telegrinder/tools/formatting/spec_html_formats.py +71 -117
- telegrinder/tools/functional.py +8 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +387 -412
- telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
- telegrinder/tools/i18n/__init__.py +7 -7
- telegrinder/tools/i18n/abc.py +30 -30
- telegrinder/tools/i18n/middleware/__init__.py +3 -3
- telegrinder/tools/i18n/middleware/abc.py +22 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard.py +128 -128
- telegrinder/tools/lifespan.py +105 -0
- telegrinder/tools/limited_dict.py +32 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +20 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +169 -224
- telegrinder/tools/magic.py +307 -157
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +31 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/tools/strings.py +13 -0
- telegrinder/types/__init__.py +268 -260
- telegrinder/types/enums.py +711 -701
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +5055 -4633
- telegrinder/types/objects.py +7058 -6950
- telegrinder/verification_utils.py +30 -32
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +22 -22
- telegrinder-0.4.0.dist-info/METADATA +144 -0
- telegrinder-0.4.0.dist-info/RECORD +182 -0
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
- telegrinder/bot/rules/adapter/__init__.py +0 -17
- telegrinder/bot/rules/adapter/abc.py +0 -31
- telegrinder/node/message.py +0 -14
- telegrinder/node/update.py +0 -15
- telegrinder/tools/formatting/links.py +0 -38
- telegrinder/tools/kb_set/__init__.py +0 -4
- telegrinder/tools/kb_set/base.py +0 -15
- telegrinder/tools/kb_set/yaml.py +0 -63
- telegrinder-0.3.4.dist-info/METADATA +0 -110
- telegrinder-0.3.4.dist-info/RECORD +0 -165
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
from fntypes.option import Option
|
|
2
|
-
|
|
3
|
-
from telegrinder.bot.
|
|
4
|
-
from telegrinder.bot.dispatch.
|
|
5
|
-
from telegrinder.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
from fntypes.option import Option
|
|
2
|
+
|
|
3
|
+
from telegrinder.bot.cute_types import BaseCute
|
|
4
|
+
from telegrinder.bot.dispatch.view import BaseStateView
|
|
5
|
+
from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import ECHO, Hasher
|
|
6
|
+
from telegrinder.tools.functional import from_optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StateViewHasher[Event: BaseCute](Hasher[Event, int]):
|
|
10
|
+
view: BaseStateView[Event]
|
|
11
|
+
|
|
12
|
+
def __init__(self, view: BaseStateView[Event]) -> None:
|
|
13
|
+
self.view = view
|
|
14
|
+
super().__init__(view.__class__, get_hash_from_data=ECHO)
|
|
15
|
+
|
|
16
|
+
def get_data_from_event(self, event: Event) -> Option[int]:
|
|
17
|
+
return from_optional(self.view.get_state_key(event))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ("StateViewHasher",)
|
|
@@ -1,172 +1,251 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import datetime
|
|
3
|
-
import typing
|
|
4
|
-
|
|
5
|
-
from telegrinder.bot.
|
|
6
|
-
from telegrinder.bot.dispatch.
|
|
7
|
-
from telegrinder.bot.dispatch.
|
|
8
|
-
from telegrinder.bot.dispatch.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
from telegrinder.
|
|
15
|
-
|
|
16
|
-
from .
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Storage:
|
|
22
|
-
Hasher[
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
hasher
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
) -> None:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
1
|
+
import asyncio
|
|
2
|
+
import datetime
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from telegrinder.bot.cute_types.base import BaseCute
|
|
6
|
+
from telegrinder.bot.dispatch.abc import ABCDispatch
|
|
7
|
+
from telegrinder.bot.dispatch.context import Context
|
|
8
|
+
from telegrinder.bot.dispatch.view.base import BaseStateView, BaseView
|
|
9
|
+
from telegrinder.bot.dispatch.waiter_machine.middleware import INITIATOR_CONTEXT_KEY, WaiterMiddleware
|
|
10
|
+
from telegrinder.bot.dispatch.waiter_machine.short_state import (
|
|
11
|
+
ShortState,
|
|
12
|
+
ShortStateContext,
|
|
13
|
+
)
|
|
14
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
15
|
+
from telegrinder.tools.lifespan import Lifespan
|
|
16
|
+
from telegrinder.tools.limited_dict import LimitedDict
|
|
17
|
+
|
|
18
|
+
from .actions import WaiterActions
|
|
19
|
+
from .hasher import Hasher, StateViewHasher
|
|
20
|
+
|
|
21
|
+
type Storage[Event: BaseCute, HasherData] = dict[
|
|
22
|
+
Hasher[Event, HasherData],
|
|
23
|
+
LimitedDict[typing.Hashable, ShortState[Event]],
|
|
24
|
+
]
|
|
25
|
+
type HasherWithData[Event: BaseCute, Data] = tuple[Hasher[Event, Data], Data]
|
|
26
|
+
|
|
27
|
+
WEEK: typing.Final[datetime.timedelta] = datetime.timedelta(days=7)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ContextUnpackProto[*Ts](typing.Protocol):
|
|
31
|
+
__name__: str
|
|
32
|
+
|
|
33
|
+
def __call__(self, context: Context, /) -> tuple[*Ts]: ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def unpack_to_context(context: Context) -> tuple[Context]:
|
|
37
|
+
return (context,)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def no_unpack(_: Context) -> tuple[()]:
|
|
41
|
+
return ()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class WaiterMachine:
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
dispatch: ABCDispatch | None = None,
|
|
48
|
+
*,
|
|
49
|
+
max_storage_size: int = 1000,
|
|
50
|
+
base_state_lifetime: datetime.timedelta = WEEK,
|
|
51
|
+
) -> None:
|
|
52
|
+
self.dispatch = dispatch
|
|
53
|
+
self.max_storage_size = max_storage_size
|
|
54
|
+
self.base_state_lifetime = base_state_lifetime
|
|
55
|
+
self.storage: Storage = {}
|
|
56
|
+
|
|
57
|
+
def __repr__(self) -> str:
|
|
58
|
+
return "<{}: with {} storage items and max_storage_size={}, base_state_lifetime={!r}>".format(
|
|
59
|
+
self.__class__.__name__,
|
|
60
|
+
len(self.storage),
|
|
61
|
+
self.max_storage_size,
|
|
62
|
+
self.base_state_lifetime,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def create_middleware[Event: BaseCute](self, view: BaseStateView[Event]) -> WaiterMiddleware[Event]:
|
|
66
|
+
hasher = StateViewHasher(view)
|
|
67
|
+
self.storage[hasher] = LimitedDict(maxlimit=self.max_storage_size)
|
|
68
|
+
return WaiterMiddleware(self, hasher)
|
|
69
|
+
|
|
70
|
+
async def drop_all(self) -> None:
|
|
71
|
+
"""Drops all waiters in storage."""
|
|
72
|
+
for hasher in self.storage.copy():
|
|
73
|
+
for ident, short_state in self.storage[hasher].items():
|
|
74
|
+
if short_state.context:
|
|
75
|
+
await self.drop(hasher, ident)
|
|
76
|
+
else:
|
|
77
|
+
await short_state.cancel()
|
|
78
|
+
|
|
79
|
+
del self.storage[hasher]
|
|
80
|
+
|
|
81
|
+
async def drop[Event: BaseCute, HasherData](
|
|
82
|
+
self,
|
|
83
|
+
hasher: Hasher[Event, HasherData],
|
|
84
|
+
data: HasherData,
|
|
85
|
+
**context: typing.Any,
|
|
86
|
+
) -> None:
|
|
87
|
+
if hasher not in self.storage:
|
|
88
|
+
raise LookupError("No record of hasher {!r} found.".format(hasher))
|
|
89
|
+
|
|
90
|
+
waiter_id: typing.Hashable = hasher.get_hash_from_data(data).expect(
|
|
91
|
+
RuntimeError("Couldn't create hash from data"),
|
|
92
|
+
)
|
|
93
|
+
short_state = self.storage[hasher].pop(waiter_id, None)
|
|
94
|
+
if short_state is None:
|
|
95
|
+
raise LookupError(
|
|
96
|
+
"Waiter with identificator {} is not found for hasher {!r}.".format(waiter_id, hasher)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if on_drop := short_state.actions.get("on_drop"):
|
|
100
|
+
on_drop(short_state, **context)
|
|
101
|
+
|
|
102
|
+
await short_state.cancel()
|
|
103
|
+
|
|
104
|
+
async def wait_from_event[Event: BaseCute](
|
|
105
|
+
self,
|
|
106
|
+
view: BaseStateView[Event],
|
|
107
|
+
event: Event,
|
|
108
|
+
*,
|
|
109
|
+
filter: ABCRule | None = None,
|
|
110
|
+
release: ABCRule | None = None,
|
|
111
|
+
lifetime: datetime.timedelta | float | None = None,
|
|
112
|
+
lifespan: Lifespan | None = None,
|
|
113
|
+
**actions: typing.Unpack[WaiterActions[Event]],
|
|
114
|
+
) -> ShortStateContext[Event]:
|
|
115
|
+
hasher = StateViewHasher(view)
|
|
116
|
+
return await self.wait(
|
|
117
|
+
hasher=hasher,
|
|
118
|
+
data=hasher.get_data_from_event(event).expect(
|
|
119
|
+
RuntimeError("Hasher couldn't create data from event."),
|
|
120
|
+
),
|
|
121
|
+
filter=filter,
|
|
122
|
+
release=release,
|
|
123
|
+
lifetime=lifetime,
|
|
124
|
+
lifespan=lifespan or Lifespan(),
|
|
125
|
+
**actions,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
async def wait[Event: BaseCute, HasherData](
|
|
129
|
+
self,
|
|
130
|
+
hasher: Hasher[Event, HasherData],
|
|
131
|
+
data: HasherData,
|
|
132
|
+
*,
|
|
133
|
+
filter: ABCRule | None = None,
|
|
134
|
+
release: ABCRule | None = None,
|
|
135
|
+
lifetime: datetime.timedelta | float | None = None,
|
|
136
|
+
lifespan: Lifespan | None = None,
|
|
137
|
+
**actions: typing.Unpack[WaiterActions[Event]],
|
|
138
|
+
) -> ShortStateContext[Event]:
|
|
139
|
+
if isinstance(lifetime, int | float):
|
|
140
|
+
lifetime = datetime.timedelta(seconds=lifetime)
|
|
141
|
+
|
|
142
|
+
lifespan = lifespan or Lifespan()
|
|
143
|
+
event = asyncio.Event()
|
|
144
|
+
short_state = ShortState[Event](
|
|
145
|
+
event,
|
|
146
|
+
actions,
|
|
147
|
+
release=release,
|
|
148
|
+
filter=filter,
|
|
149
|
+
lifetime=lifetime or self.base_state_lifetime,
|
|
150
|
+
)
|
|
151
|
+
waiter_hash = hasher.get_hash_from_data(data).expect(RuntimeError("Hasher couldn't create hash."))
|
|
152
|
+
|
|
153
|
+
if hasher not in self.storage:
|
|
154
|
+
if self.dispatch:
|
|
155
|
+
view: BaseView[Event] = self.dispatch.get_view(hasher.view_class).expect(
|
|
156
|
+
RuntimeError(f"View {hasher.view_class.__name__!r} is not defined in dispatch."),
|
|
157
|
+
)
|
|
158
|
+
view.middlewares.insert(0, WaiterMiddleware(self, hasher))
|
|
159
|
+
self.storage[hasher] = LimitedDict(maxlimit=self.max_storage_size)
|
|
160
|
+
|
|
161
|
+
if (deleted_short_state := self.storage[hasher].set(waiter_hash, short_state)) is not None:
|
|
162
|
+
await deleted_short_state.cancel()
|
|
163
|
+
|
|
164
|
+
async with lifespan:
|
|
165
|
+
await event.wait()
|
|
166
|
+
|
|
167
|
+
self.storage[hasher].pop(waiter_hash, None)
|
|
168
|
+
|
|
169
|
+
if short_state.context is None:
|
|
170
|
+
raise LookupError("No context in short_state.")
|
|
171
|
+
return short_state.context
|
|
172
|
+
|
|
173
|
+
async def wait_many[RestEvent: BaseCute[typing.Any], Data, *Ts](
|
|
174
|
+
self,
|
|
175
|
+
*hashers: HasherWithData[RestEvent, Data],
|
|
176
|
+
filter: ABCRule | None = None,
|
|
177
|
+
release: ABCRule | None = None,
|
|
178
|
+
lifetime: datetime.timedelta | float | None = None,
|
|
179
|
+
lifespan: Lifespan | None = None,
|
|
180
|
+
unpack: ContextUnpackProto[*Ts] = unpack_to_context,
|
|
181
|
+
**actions: typing.Unpack[WaiterActions[BaseCute[typing.Any]]],
|
|
182
|
+
) -> tuple[HasherWithData[RestEvent, Data], RestEvent, *Ts]:
|
|
183
|
+
if isinstance(lifetime, int | float):
|
|
184
|
+
lifetime = datetime.timedelta(seconds=lifetime)
|
|
185
|
+
|
|
186
|
+
lifespan = lifespan or Lifespan()
|
|
187
|
+
event = asyncio.Event()
|
|
188
|
+
short_state = ShortState(
|
|
189
|
+
event,
|
|
190
|
+
actions,
|
|
191
|
+
release=release,
|
|
192
|
+
filter=filter,
|
|
193
|
+
lifetime=lifetime or self.base_state_lifetime,
|
|
194
|
+
)
|
|
195
|
+
waiter_hashes: dict[Hasher[RestEvent, Data], typing.Hashable] = {}
|
|
196
|
+
|
|
197
|
+
for hasher, data in hashers:
|
|
198
|
+
waiter_hash = hasher.get_hash_from_data(data).expect(RuntimeError("Hasher couldn't create hash."))
|
|
199
|
+
|
|
200
|
+
if hasher not in self.storage:
|
|
201
|
+
if self.dispatch:
|
|
202
|
+
view = self.dispatch.get_view(hasher.view_class).expect(
|
|
203
|
+
RuntimeError(f"View {hasher.view_class.__name__!r} is not defined in dispatch."),
|
|
204
|
+
)
|
|
205
|
+
view.middlewares.insert(0, WaiterMiddleware(self, hasher))
|
|
206
|
+
self.storage[hasher] = LimitedDict(maxlimit=self.max_storage_size)
|
|
207
|
+
|
|
208
|
+
if (deleted_short_state := self.storage[hasher].set(waiter_hash, short_state)) is not None:
|
|
209
|
+
await deleted_short_state.cancel()
|
|
210
|
+
|
|
211
|
+
waiter_hashes[hasher] = waiter_hash
|
|
212
|
+
|
|
213
|
+
async with lifespan:
|
|
214
|
+
await event.wait()
|
|
215
|
+
|
|
216
|
+
if short_state.context is None:
|
|
217
|
+
raise LookupError("No context in short_state.")
|
|
218
|
+
|
|
219
|
+
initiator = short_state.context.context.get(INITIATOR_CONTEXT_KEY)
|
|
220
|
+
if initiator is None:
|
|
221
|
+
raise LookupError("Initiator not found in short_state context.")
|
|
222
|
+
|
|
223
|
+
for hasher, waiter_hash in waiter_hashes.items():
|
|
224
|
+
self.storage[hasher].pop(waiter_hash, None)
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
initiator,
|
|
228
|
+
short_state.context.event, # type: ignore
|
|
229
|
+
*unpack(short_state.context.context),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
async def clear_storage(self) -> None:
|
|
233
|
+
"""Clears storage."""
|
|
234
|
+
now = datetime.datetime.now()
|
|
235
|
+
|
|
236
|
+
for hasher in self.storage:
|
|
237
|
+
for ident, short_state in self.storage.get(hasher, {}).copy().items():
|
|
238
|
+
if short_state.expiration_date is not None and now > short_state.expiration_date:
|
|
239
|
+
await self.drop(hasher, data=ident, force=True)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
async def clear_wm_storage_worker(
|
|
243
|
+
wm: WaiterMachine,
|
|
244
|
+
interval_seconds: int = 60,
|
|
245
|
+
) -> typing.NoReturn:
|
|
246
|
+
while True:
|
|
247
|
+
await wm.clear_storage()
|
|
248
|
+
await asyncio.sleep(interval_seconds)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
__all__ = ("WaiterMachine",)
|
|
@@ -1,89 +1,94 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
from telegrinder.bot.cute_types.base import BaseCute
|
|
5
|
-
from telegrinder.bot.dispatch.context import Context
|
|
6
|
-
from telegrinder.bot.dispatch.handler.func import FuncHandler
|
|
7
|
-
from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
|
|
8
|
-
from telegrinder.bot.dispatch.process import check_rule
|
|
9
|
-
from telegrinder.bot.dispatch.waiter_machine.short_state import ShortStateContext
|
|
10
|
-
from telegrinder.modules import logger
|
|
11
|
-
|
|
12
|
-
from .hasher import Hasher
|
|
13
|
-
|
|
14
|
-
if typing.TYPE_CHECKING:
|
|
15
|
-
from .machine import WaiterMachine
|
|
16
|
-
from .short_state import ShortState
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
self.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
short_state
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
1
|
+
import datetime
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from telegrinder.bot.cute_types.base import BaseCute
|
|
5
|
+
from telegrinder.bot.dispatch.context import Context
|
|
6
|
+
from telegrinder.bot.dispatch.handler.func import FuncHandler
|
|
7
|
+
from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
|
|
8
|
+
from telegrinder.bot.dispatch.process import check_rule
|
|
9
|
+
from telegrinder.bot.dispatch.waiter_machine.short_state import ShortStateContext
|
|
10
|
+
from telegrinder.modules import logger
|
|
11
|
+
|
|
12
|
+
from .hasher import Hasher
|
|
13
|
+
|
|
14
|
+
if typing.TYPE_CHECKING:
|
|
15
|
+
from .machine import WaiterMachine
|
|
16
|
+
from .short_state import ShortState
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
INITIATOR_CONTEXT_KEY = "initiator"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WaiterMiddleware[Event: BaseCute](ABCMiddleware[Event]):
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
machine: "WaiterMachine",
|
|
26
|
+
hasher: Hasher,
|
|
27
|
+
) -> None:
|
|
28
|
+
self.machine = machine
|
|
29
|
+
self.hasher = hasher
|
|
30
|
+
|
|
31
|
+
async def pre(self, event: Event, ctx: Context) -> bool:
|
|
32
|
+
if self.hasher not in self.machine.storage:
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
key = self.hasher.get_hash_from_data_from_event(event)
|
|
36
|
+
if not key:
|
|
37
|
+
logger.info(f"Unable to get hash from event with hasher {self.hasher!r}")
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
short_state: "ShortState[Event] | None" = self.machine.storage[self.hasher].get(key.unwrap())
|
|
41
|
+
if not short_state:
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
preset_context = Context(short_state=short_state)
|
|
45
|
+
if short_state.context is not None:
|
|
46
|
+
preset_context.update(short_state.context.context)
|
|
47
|
+
|
|
48
|
+
# Run filter rule
|
|
49
|
+
if short_state.filter and not await check_rule(
|
|
50
|
+
event.ctx_api,
|
|
51
|
+
short_state.filter,
|
|
52
|
+
ctx.raw_update,
|
|
53
|
+
preset_context,
|
|
54
|
+
):
|
|
55
|
+
logger.debug("Filter rule {!r} failed", short_state.filter)
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
if short_state.expiration_date is not None and datetime.datetime.now() >= short_state.expiration_date:
|
|
59
|
+
await self.machine.drop(
|
|
60
|
+
self.hasher,
|
|
61
|
+
self.hasher.get_data_from_event(event).unwrap(),
|
|
62
|
+
**preset_context.copy(),
|
|
63
|
+
)
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
handler = FuncHandler(
|
|
67
|
+
self.pass_runtime,
|
|
68
|
+
[short_state.release] if short_state.release else [],
|
|
69
|
+
preset_context=preset_context,
|
|
70
|
+
)
|
|
71
|
+
handler.get_name_event_param = lambda event: "event" # FIXME: HOTFIX
|
|
72
|
+
result = await handler.check(event.ctx_api, ctx.raw_update, ctx)
|
|
73
|
+
|
|
74
|
+
if result is True:
|
|
75
|
+
await handler.run(event.api, event, ctx)
|
|
76
|
+
|
|
77
|
+
elif on_miss := short_state.actions.get("on_miss"): # noqa: SIM102
|
|
78
|
+
if await on_miss.check(event.ctx_api, ctx.raw_update, ctx):
|
|
79
|
+
await on_miss.run(event.ctx_api, event, ctx)
|
|
80
|
+
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
async def pass_runtime(
|
|
84
|
+
self,
|
|
85
|
+
event: Event,
|
|
86
|
+
short_state: "ShortState[Event]",
|
|
87
|
+
ctx: Context,
|
|
88
|
+
) -> None:
|
|
89
|
+
ctx.initiator = self.hasher
|
|
90
|
+
short_state.context = ShortStateContext(event, ctx)
|
|
91
|
+
short_state.event.set()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
__all__ = ("WaiterMiddleware",)
|