telegrinder 0.2.2__py3-none-any.whl → 0.3.0.post1__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 +24 -2
- telegrinder/bot/__init__.py +16 -0
- telegrinder/bot/cute_types/callback_query.py +60 -146
- telegrinder/bot/cute_types/chat_join_request.py +12 -16
- telegrinder/bot/cute_types/chat_member_updated.py +14 -104
- telegrinder/bot/cute_types/inline_query.py +5 -14
- telegrinder/bot/cute_types/message.py +605 -1227
- telegrinder/bot/dispatch/__init__.py +22 -1
- telegrinder/bot/dispatch/abc.py +7 -0
- telegrinder/bot/dispatch/dispatch.py +7 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +18 -0
- telegrinder/bot/dispatch/waiter_machine/actions.py +10 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +15 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +60 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +49 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +54 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +87 -99
- telegrinder/bot/dispatch/waiter_machine/middleware.py +23 -35
- telegrinder/bot/dispatch/waiter_machine/short_state.py +9 -9
- telegrinder/bot/scenario/checkbox.py +2 -2
- telegrinder/model.py +6 -4
- telegrinder/msgspec_json.py +1 -1
- telegrinder/msgspec_utils.py +51 -0
- telegrinder/node/event.py +2 -0
- telegrinder/tools/functional.py +9 -0
- telegrinder/tools/state_storage/memory.py +3 -3
- {telegrinder-0.2.2.dist-info → telegrinder-0.3.0.post1.dist-info}/METADATA +2 -2
- {telegrinder-0.2.2.dist-info → telegrinder-0.3.0.post1.dist-info}/RECORD +31 -24
- {telegrinder-0.2.2.dist-info → telegrinder-0.3.0.post1.dist-info}/LICENSE +0 -0
- {telegrinder-0.2.2.dist-info → telegrinder-0.3.0.post1.dist-info}/WHEEL +0 -0
|
@@ -36,7 +36,19 @@ from telegrinder.bot.dispatch.view import (
|
|
|
36
36
|
RawEventView,
|
|
37
37
|
ViewBox,
|
|
38
38
|
)
|
|
39
|
-
from telegrinder.bot.dispatch.waiter_machine import
|
|
39
|
+
from telegrinder.bot.dispatch.waiter_machine import (
|
|
40
|
+
CALLBACK_QUERY_FOR_MESSAGE,
|
|
41
|
+
CALLBACK_QUERY_FROM_CHAT,
|
|
42
|
+
CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE,
|
|
43
|
+
MESSAGE_FROM_USER,
|
|
44
|
+
MESSAGE_FROM_USER_IN_CHAT,
|
|
45
|
+
MESSAGE_IN_CHAT,
|
|
46
|
+
Hasher,
|
|
47
|
+
ShortState,
|
|
48
|
+
StateViewHasher,
|
|
49
|
+
WaiterMachine,
|
|
50
|
+
clear_wm_storage_worker,
|
|
51
|
+
)
|
|
40
52
|
|
|
41
53
|
__all__ = (
|
|
42
54
|
"ABCDispatch",
|
|
@@ -49,6 +61,9 @@ __all__ = (
|
|
|
49
61
|
"BaseReturnManager",
|
|
50
62
|
"BaseStateView",
|
|
51
63
|
"BaseView",
|
|
64
|
+
"CALLBACK_QUERY_FOR_MESSAGE",
|
|
65
|
+
"CALLBACK_QUERY_FROM_CHAT",
|
|
66
|
+
"CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE",
|
|
52
67
|
"CallbackQueryReturnManager",
|
|
53
68
|
"CallbackQueryView",
|
|
54
69
|
"ChatJoinRequestView",
|
|
@@ -57,8 +72,12 @@ __all__ = (
|
|
|
57
72
|
"Dispatch",
|
|
58
73
|
"DocumentReplyHandler",
|
|
59
74
|
"FuncHandler",
|
|
75
|
+
"Hasher",
|
|
60
76
|
"InlineQueryReturnManager",
|
|
61
77
|
"InlineQueryView",
|
|
78
|
+
"MESSAGE_FROM_USER",
|
|
79
|
+
"MESSAGE_FROM_USER_IN_CHAT",
|
|
80
|
+
"MESSAGE_IN_CHAT",
|
|
62
81
|
"Manager",
|
|
63
82
|
"MediaGroupReplyHandler",
|
|
64
83
|
"MessageReplyHandler",
|
|
@@ -67,6 +86,7 @@ __all__ = (
|
|
|
67
86
|
"PhotoReplyHandler",
|
|
68
87
|
"RawEventView",
|
|
69
88
|
"ShortState",
|
|
89
|
+
"StateViewHasher",
|
|
70
90
|
"StickerReplyHandler",
|
|
71
91
|
"TelegrinderContext",
|
|
72
92
|
"VideoReplyHandler",
|
|
@@ -74,6 +94,7 @@ __all__ = (
|
|
|
74
94
|
"WaiterMachine",
|
|
75
95
|
"check_rule",
|
|
76
96
|
"clear_wm_storage_worker",
|
|
97
|
+
"clear_wm_storage_worker",
|
|
77
98
|
"process_inner",
|
|
78
99
|
"register_manager",
|
|
79
100
|
)
|
telegrinder/bot/dispatch/abc.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
3
|
|
|
4
|
+
from fntypes import Option
|
|
5
|
+
|
|
4
6
|
from telegrinder.api.api import API
|
|
5
7
|
from telegrinder.tools.global_context.abc import ABCGlobalContext
|
|
6
8
|
from telegrinder.types.objects import Update
|
|
7
9
|
|
|
10
|
+
T = typing.TypeVar("T")
|
|
11
|
+
|
|
8
12
|
|
|
9
13
|
class ABCDispatch(ABC):
|
|
10
14
|
@property
|
|
@@ -24,5 +28,8 @@ class ABCDispatch(ABC):
|
|
|
24
28
|
for external in externals:
|
|
25
29
|
self.load(external)
|
|
26
30
|
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def get_view(self, of_type: type[T]) -> Option[T]: ...
|
|
33
|
+
|
|
27
34
|
|
|
28
35
|
__all__ = ("ABCDispatch",)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
import typing
|
|
3
3
|
|
|
4
|
+
from fntypes import Nothing, Option, Some
|
|
4
5
|
from vbml.patcher import Patcher
|
|
5
6
|
|
|
6
7
|
from telegrinder.api.api import API
|
|
@@ -188,6 +189,12 @@ class Dispatch(
|
|
|
188
189
|
view.load(view_external[name])
|
|
189
190
|
setattr(external, name, view)
|
|
190
191
|
|
|
192
|
+
def get_view(self, of_type: type[T]) -> Option[T]:
|
|
193
|
+
for view in self.get_views().values():
|
|
194
|
+
if isinstance(view, of_type):
|
|
195
|
+
return Some(view)
|
|
196
|
+
return Nothing()
|
|
197
|
+
|
|
191
198
|
__call__ = handle
|
|
192
199
|
|
|
193
200
|
|
|
@@ -1,9 +1,27 @@
|
|
|
1
|
+
from telegrinder.bot.dispatch.waiter_machine.hasher import (
|
|
2
|
+
CALLBACK_QUERY_FOR_MESSAGE,
|
|
3
|
+
CALLBACK_QUERY_FROM_CHAT,
|
|
4
|
+
CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE,
|
|
5
|
+
MESSAGE_FROM_USER,
|
|
6
|
+
MESSAGE_FROM_USER_IN_CHAT,
|
|
7
|
+
MESSAGE_IN_CHAT,
|
|
8
|
+
Hasher,
|
|
9
|
+
StateViewHasher,
|
|
10
|
+
)
|
|
1
11
|
from telegrinder.bot.dispatch.waiter_machine.machine import WaiterMachine, clear_wm_storage_worker
|
|
2
12
|
from telegrinder.bot.dispatch.waiter_machine.middleware import WaiterMiddleware
|
|
3
13
|
from telegrinder.bot.dispatch.waiter_machine.short_state import ShortState
|
|
4
14
|
|
|
5
15
|
__all__ = (
|
|
16
|
+
"CALLBACK_QUERY_FOR_MESSAGE",
|
|
17
|
+
"CALLBACK_QUERY_FROM_CHAT",
|
|
18
|
+
"CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE",
|
|
19
|
+
"Hasher",
|
|
20
|
+
"MESSAGE_FROM_USER",
|
|
21
|
+
"MESSAGE_FROM_USER_IN_CHAT",
|
|
22
|
+
"MESSAGE_IN_CHAT",
|
|
6
23
|
"ShortState",
|
|
24
|
+
"StateViewHasher",
|
|
7
25
|
"WaiterMachine",
|
|
8
26
|
"WaiterMiddleware",
|
|
9
27
|
"clear_wm_storage_worker",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from telegrinder.bot.dispatch.handler.abc import ABCHandler
|
|
4
|
+
|
|
5
|
+
from .short_state import EventModel, ShortState
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WaiterActions(typing.TypedDict, typing.Generic[EventModel]):
|
|
9
|
+
on_miss: typing.NotRequired[ABCHandler[EventModel]]
|
|
10
|
+
on_drop: typing.NotRequired[typing.Callable[[ShortState[EventModel]], None]]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .callback import CALLBACK_QUERY_FOR_MESSAGE, CALLBACK_QUERY_FROM_CHAT, CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE
|
|
2
|
+
from .hasher import Hasher
|
|
3
|
+
from .message import MESSAGE_FROM_USER, MESSAGE_FROM_USER_IN_CHAT, MESSAGE_IN_CHAT
|
|
4
|
+
from .state import StateViewHasher
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
"CALLBACK_QUERY_FOR_MESSAGE",
|
|
8
|
+
"CALLBACK_QUERY_FROM_CHAT",
|
|
9
|
+
"CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE",
|
|
10
|
+
"Hasher",
|
|
11
|
+
"MESSAGE_FROM_USER",
|
|
12
|
+
"MESSAGE_FROM_USER_IN_CHAT",
|
|
13
|
+
"MESSAGE_IN_CHAT",
|
|
14
|
+
"StateViewHasher",
|
|
15
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from fntypes import Some
|
|
2
|
+
|
|
3
|
+
from telegrinder.bot.cute_types import CallbackQueryCute as CallbackQuery
|
|
4
|
+
from telegrinder.bot.dispatch.view import CallbackQueryView
|
|
5
|
+
|
|
6
|
+
from .hasher import Hasher
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def from_chat_hash(chat_id: int) -> int:
|
|
10
|
+
return chat_id
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_chat_from_event(event: CallbackQuery) -> int | None:
|
|
14
|
+
return event.chat.and_then(lambda chat: Some(chat.id)).unwrap_or_none()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
CALLBACK_QUERY_FROM_CHAT = Hasher(
|
|
18
|
+
view=CallbackQueryView,
|
|
19
|
+
get_hash_from_data=from_chat_hash,
|
|
20
|
+
get_data_from_event=get_chat_from_event,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def for_message_hash(message_id: int) -> int:
|
|
25
|
+
return message_id
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_message_for_event(event: CallbackQuery) -> int | None:
|
|
29
|
+
return event.message_id.unwrap_or_none()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
CALLBACK_QUERY_FOR_MESSAGE = Hasher(
|
|
33
|
+
view=CallbackQueryView,
|
|
34
|
+
get_hash_from_data=for_message_hash,
|
|
35
|
+
get_data_from_event=get_message_for_event,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def for_message_in_chat(chat_and_message: tuple[int, int]) -> str:
|
|
40
|
+
return f"{chat_and_message[0]}_{chat_and_message[1]}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_chat_and_message_for_event(event: CallbackQuery) -> tuple[int, int] | None:
|
|
44
|
+
if not event.message_id or not event.chat:
|
|
45
|
+
return None
|
|
46
|
+
return event.chat.unwrap().id, event.message_id.unwrap()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE = Hasher(
|
|
50
|
+
view=CallbackQueryView,
|
|
51
|
+
get_hash_from_data=for_message_in_chat,
|
|
52
|
+
get_data_from_event=get_chat_and_message_for_event,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = (
|
|
57
|
+
"CALLBACK_QUERY_FOR_MESSAGE",
|
|
58
|
+
"CALLBACK_QUERY_FROM_CHAT",
|
|
59
|
+
"CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE",
|
|
60
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from fntypes import Option
|
|
4
|
+
|
|
5
|
+
from telegrinder.bot.cute_types import BaseCute
|
|
6
|
+
from telegrinder.bot.dispatch.view.base import BaseView
|
|
7
|
+
from telegrinder.tools.functional import from_optional
|
|
8
|
+
|
|
9
|
+
Event = typing.TypeVar("Event", bound=BaseCute)
|
|
10
|
+
Data = typing.TypeVar("Data")
|
|
11
|
+
|
|
12
|
+
ECHO = lambda x: x
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Hasher(typing.Generic[Event, Data]):
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
view: type[BaseView[Event]],
|
|
19
|
+
get_hash_from_data: typing.Callable[[Data], typing.Hashable | None] | None = None,
|
|
20
|
+
get_data_from_event: typing.Callable[[Event], Data | None] | None = None,
|
|
21
|
+
):
|
|
22
|
+
self.view = view
|
|
23
|
+
self._get_hash_from_data = get_hash_from_data
|
|
24
|
+
self._get_data_from_event = get_data_from_event
|
|
25
|
+
|
|
26
|
+
def get_name(self) -> str:
|
|
27
|
+
return f"{self.view.__class__.__name__}_{id(self)}"
|
|
28
|
+
|
|
29
|
+
def get_hash_from_data(self, data: Data) -> Option[typing.Hashable]:
|
|
30
|
+
if self._get_hash_from_data is None:
|
|
31
|
+
raise NotImplementedError
|
|
32
|
+
return from_optional(self._get_hash_from_data(data))
|
|
33
|
+
|
|
34
|
+
def get_data_from_event(self, event: Event) -> Option[Data]:
|
|
35
|
+
if not self._get_data_from_event:
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
return from_optional(self._get_data_from_event(event))
|
|
38
|
+
|
|
39
|
+
def get_hash_from_data_from_event(self, event: Event) -> Option[typing.Hashable]:
|
|
40
|
+
return self.get_data_from_event(event).and_then(self.get_hash_from_data) # type: ignore
|
|
41
|
+
|
|
42
|
+
def __hash__(self) -> int:
|
|
43
|
+
return hash(self.get_name())
|
|
44
|
+
|
|
45
|
+
def __repr__(self) -> str:
|
|
46
|
+
return f"<Hasher {self.get_name()}>"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
__all__ = ("Hasher",)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from telegrinder.bot.cute_types import MessageCute as Message
|
|
2
|
+
from telegrinder.bot.dispatch.view import MessageView
|
|
3
|
+
|
|
4
|
+
from .hasher import Hasher
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def from_chat_hash(chat_id: int) -> int:
|
|
8
|
+
return chat_id
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_chat_from_event(event: Message) -> int:
|
|
12
|
+
return event.chat.id
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
MESSAGE_IN_CHAT = Hasher(
|
|
16
|
+
view=MessageView, get_hash_from_data=from_chat_hash, get_data_from_event=get_chat_from_event
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def from_user_hash(from_id: int) -> int:
|
|
21
|
+
return from_id
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_user_from_event(event: Message) -> int:
|
|
25
|
+
return event.from_user.id
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
MESSAGE_FROM_USER = Hasher(
|
|
29
|
+
view=MessageView,
|
|
30
|
+
get_hash_from_data=from_user_hash,
|
|
31
|
+
get_data_from_event=get_user_from_event,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def from_user_in_chat_hash(chat_and_user: tuple[int, int]) -> str:
|
|
36
|
+
return f"{chat_and_user[0]}_{chat_and_user[1]}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_user_in_chat_from_event(event: Message) -> tuple[int, int]:
|
|
40
|
+
return event.chat.id, event.from_user.id
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
MESSAGE_FROM_USER_IN_CHAT = Hasher(
|
|
44
|
+
view=MessageView,
|
|
45
|
+
get_hash_from_data=from_user_in_chat_hash,
|
|
46
|
+
get_data_from_event=get_user_in_chat_from_event,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = (
|
|
51
|
+
"MESSAGE_FROM_USER",
|
|
52
|
+
"MESSAGE_FROM_USER_IN_CHAT",
|
|
53
|
+
"MESSAGE_IN_CHAT",
|
|
54
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from fntypes import Option
|
|
2
|
+
|
|
3
|
+
from telegrinder.bot.dispatch.view import BaseStateView
|
|
4
|
+
from telegrinder.tools.functional import from_optional
|
|
5
|
+
|
|
6
|
+
from .hasher import ECHO, Event, Hasher
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StateViewHasher(Hasher[Event, int]):
|
|
10
|
+
view: BaseStateView
|
|
11
|
+
|
|
12
|
+
def __init__(self, view: type[BaseStateView[Event]]):
|
|
13
|
+
super().__init__(view, get_hash_from_data=ECHO)
|
|
14
|
+
|
|
15
|
+
def get_data_from_event(self, event: Event) -> Option[int]:
|
|
16
|
+
return from_optional(self.view.get_state_key(event))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ("StateViewHasher",)
|
|
@@ -2,35 +2,42 @@ import asyncio
|
|
|
2
2
|
import datetime
|
|
3
3
|
import typing
|
|
4
4
|
|
|
5
|
-
from telegrinder.
|
|
6
|
-
from telegrinder.bot.dispatch.
|
|
7
|
-
from telegrinder.bot.dispatch.view.abc import ABCStateView
|
|
8
|
-
from telegrinder.bot.dispatch.view.base import BaseStateView
|
|
5
|
+
from telegrinder.bot.dispatch.abc import ABCDispatch
|
|
6
|
+
from telegrinder.bot.dispatch.view.base import BaseStateView, BaseView
|
|
9
7
|
from telegrinder.bot.dispatch.waiter_machine.middleware import WaiterMiddleware
|
|
10
8
|
from telegrinder.bot.dispatch.waiter_machine.short_state import (
|
|
11
|
-
Behaviour,
|
|
12
9
|
EventModel,
|
|
13
10
|
ShortState,
|
|
14
11
|
ShortStateContext,
|
|
15
12
|
)
|
|
16
13
|
from telegrinder.bot.rules.abc import ABCRule
|
|
17
14
|
from telegrinder.tools.limited_dict import LimitedDict
|
|
18
|
-
from telegrinder.types import Update
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
from .actions import WaiterActions
|
|
17
|
+
from .hasher import Hasher, StateViewHasher
|
|
22
18
|
|
|
23
19
|
T = typing.TypeVar("T")
|
|
20
|
+
HasherData = typing.TypeVar("HasherData")
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
Storage: typing.TypeAlias = dict[
|
|
22
|
+
|
|
23
|
+
Storage: typing.TypeAlias = dict[
|
|
24
|
+
Hasher[EventModel, HasherData], LimitedDict[typing.Hashable, ShortState[EventModel]]
|
|
25
|
+
]
|
|
27
26
|
|
|
28
27
|
WEEK: typing.Final[datetime.timedelta] = datetime.timedelta(days=7)
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
class WaiterMachine:
|
|
32
|
-
def __init__(
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
dispatch: ABCDispatch | None = None,
|
|
34
|
+
*,
|
|
35
|
+
max_storage_size: int = 1000,
|
|
36
|
+
base_state_lifetime: datetime.timedelta = WEEK,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.dispatch = dispatch
|
|
33
39
|
self.max_storage_size = max_storage_size
|
|
40
|
+
self.base_state_lifetime = base_state_lifetime
|
|
34
41
|
self.storage: Storage = {}
|
|
35
42
|
|
|
36
43
|
def __repr__(self) -> str:
|
|
@@ -46,142 +53,123 @@ class WaiterMachine:
|
|
|
46
53
|
|
|
47
54
|
async def drop(
|
|
48
55
|
self,
|
|
49
|
-
|
|
50
|
-
id:
|
|
51
|
-
event: EventModel,
|
|
52
|
-
update: Update,
|
|
56
|
+
hasher: Hasher[EventModel, HasherData],
|
|
57
|
+
id: HasherData,
|
|
53
58
|
**context: typing.Any,
|
|
54
59
|
) -> None:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
raise LookupError("No record of view {!r} found.".format(view_name))
|
|
60
|
+
if hasher not in self.storage:
|
|
61
|
+
raise LookupError("No record of hasher {!r} found.".format(hasher))
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
waiter_id: typing.Hashable = hasher.get_hash_from_data(id).expect(
|
|
64
|
+
RuntimeError("Couldn't create hash from data")
|
|
65
|
+
)
|
|
66
|
+
short_state = self.storage[hasher].pop(waiter_id, None)
|
|
60
67
|
if short_state is None:
|
|
61
|
-
raise LookupError(
|
|
68
|
+
raise LookupError(
|
|
69
|
+
"Waiter with identificator {} is not found for hasher {!r}".format(waiter_id, hasher)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if on_drop := short_state.actions.get("on_drop"):
|
|
73
|
+
on_drop(short_state, **context)
|
|
62
74
|
|
|
63
75
|
short_state.cancel()
|
|
64
|
-
await self.call_behaviour(
|
|
65
|
-
event,
|
|
66
|
-
update,
|
|
67
|
-
behaviour=short_state.on_drop_behaviour,
|
|
68
|
-
**context,
|
|
69
|
-
)
|
|
70
76
|
|
|
71
77
|
async def drop_all(self) -> None:
|
|
72
78
|
"""Drops all waiters in storage."""
|
|
73
79
|
|
|
74
|
-
for
|
|
75
|
-
for ident, short_state in self.storage[
|
|
80
|
+
for hasher in self.storage:
|
|
81
|
+
for ident, short_state in self.storage[hasher].items():
|
|
76
82
|
if short_state.context:
|
|
77
83
|
await self.drop(
|
|
78
|
-
|
|
84
|
+
hasher,
|
|
85
|
+
ident,
|
|
79
86
|
)
|
|
80
87
|
else:
|
|
81
88
|
short_state.cancel()
|
|
82
89
|
|
|
83
|
-
async def
|
|
90
|
+
async def wait_from_event(
|
|
84
91
|
self,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
view: BaseStateView[EventModel],
|
|
93
|
+
event: EventModel,
|
|
94
|
+
*,
|
|
95
|
+
filter: ABCRule | None = None,
|
|
96
|
+
release: ABCRule | None = None,
|
|
97
|
+
lifetime: datetime.timedelta | float | None = None,
|
|
98
|
+
**actions: typing.Unpack[WaiterActions[EventModel]],
|
|
92
99
|
) -> ShortStateContext[EventModel]:
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
hasher = StateViewHasher(view.__class__)
|
|
101
|
+
return await self.wait(
|
|
102
|
+
hasher=hasher,
|
|
103
|
+
data=hasher.get_data_from_event(event).expect(
|
|
104
|
+
RuntimeError("Hasher couldn't create data from event.")
|
|
105
|
+
),
|
|
106
|
+
filter=filter,
|
|
107
|
+
release=release,
|
|
108
|
+
lifetime=lifetime,
|
|
109
|
+
**actions,
|
|
110
|
+
)
|
|
95
111
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
async def wait(
|
|
113
|
+
self,
|
|
114
|
+
hasher: Hasher[EventModel, HasherData],
|
|
115
|
+
data: HasherData,
|
|
116
|
+
*,
|
|
117
|
+
filter: ABCRule | None = None,
|
|
118
|
+
release: ABCRule | None = None,
|
|
119
|
+
lifetime: datetime.timedelta | float | None = None,
|
|
120
|
+
**actions: typing.Unpack[WaiterActions[EventModel]],
|
|
121
|
+
) -> ShortStateContext[EventModel]:
|
|
122
|
+
if isinstance(lifetime, int | float):
|
|
123
|
+
lifetime = datetime.timedelta(seconds=lifetime)
|
|
101
124
|
|
|
102
|
-
view_name = state_view.__class__.__name__
|
|
103
125
|
event = asyncio.Event()
|
|
104
126
|
short_state = ShortState[EventModel](
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
event,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
default_behaviour=default,
|
|
111
|
-
on_drop_behaviour=on_drop,
|
|
112
|
-
exit_behaviour=exit,
|
|
127
|
+
filter=filter,
|
|
128
|
+
release=release,
|
|
129
|
+
event=event,
|
|
130
|
+
lifetime=lifetime or self.base_state_lifetime,
|
|
131
|
+
actions=actions,
|
|
113
132
|
)
|
|
133
|
+
waiter_hash = hasher.get_hash_from_data(data).expect(RuntimeError("Hasher couldn't create hash."))
|
|
114
134
|
|
|
115
|
-
if
|
|
116
|
-
|
|
117
|
-
|
|
135
|
+
if hasher not in self.storage:
|
|
136
|
+
if self.dispatch:
|
|
137
|
+
view: BaseView[EventModel] = self.dispatch.get_view(hasher.view).expect(
|
|
138
|
+
RuntimeError(f"View {hasher.view.__name__} is not defined in dispatch")
|
|
139
|
+
)
|
|
140
|
+
view.middlewares.insert(0, WaiterMiddleware(self, hasher))
|
|
141
|
+
self.storage[hasher] = LimitedDict(maxlimit=self.max_storage_size)
|
|
118
142
|
|
|
119
|
-
if (deleted_short_state := self.storage[
|
|
143
|
+
if (deleted_short_state := self.storage[hasher].set(waiter_hash, short_state)) is not None:
|
|
120
144
|
deleted_short_state.cancel()
|
|
121
145
|
|
|
122
146
|
await event.wait()
|
|
123
|
-
self.storage[
|
|
147
|
+
self.storage[hasher].pop(waiter_hash, None)
|
|
124
148
|
assert short_state.context is not None
|
|
125
149
|
return short_state.context
|
|
126
150
|
|
|
127
|
-
async def call_behaviour(
|
|
128
|
-
self,
|
|
129
|
-
event: EventModel,
|
|
130
|
-
update: Update,
|
|
131
|
-
behaviour: Behaviour[EventModel] | None = None,
|
|
132
|
-
**context: typing.Any,
|
|
133
|
-
) -> bool:
|
|
134
|
-
# TODO: support view as a behaviour
|
|
135
|
-
|
|
136
|
-
if behaviour is None:
|
|
137
|
-
return False
|
|
138
|
-
|
|
139
|
-
ctx = Context(**context)
|
|
140
|
-
if await behaviour.check(event.api, update, ctx):
|
|
141
|
-
await behaviour.run(event.api, event, ctx)
|
|
142
|
-
return True
|
|
143
|
-
|
|
144
|
-
return False
|
|
145
|
-
|
|
146
151
|
async def clear_storage(
|
|
147
152
|
self,
|
|
148
|
-
views: typing.Iterable[ABCStateView[EventModel]],
|
|
149
|
-
absolutely_dead_time: datetime.timedelta = WEEK,
|
|
150
153
|
) -> None:
|
|
151
|
-
"""Clears storage.
|
|
154
|
+
"""Clears storage."""
|
|
152
155
|
|
|
153
|
-
|
|
154
|
-
"""
|
|
155
|
-
|
|
156
|
-
for view in views:
|
|
157
|
-
view_name = view.__class__.__name__
|
|
156
|
+
for hasher in self.storage:
|
|
158
157
|
now = datetime.datetime.now()
|
|
159
|
-
for ident, short_state in self.storage.get(
|
|
158
|
+
for ident, short_state in self.storage.get(hasher, {}).copy().items():
|
|
160
159
|
if short_state.expiration_date is not None and now > short_state.expiration_date:
|
|
161
|
-
assert short_state.context # FIXME: why???
|
|
162
160
|
await self.drop(
|
|
163
|
-
|
|
161
|
+
hasher,
|
|
164
162
|
ident,
|
|
165
|
-
event=short_state.context.event,
|
|
166
|
-
update=short_state.context.context.raw_update,
|
|
167
163
|
force=True,
|
|
168
164
|
)
|
|
169
|
-
elif short_state.creation_date + absolutely_dead_time < now:
|
|
170
|
-
short_state.cancel()
|
|
171
|
-
del self.storage[view_name][short_state.key]
|
|
172
165
|
|
|
173
166
|
|
|
174
167
|
async def clear_wm_storage_worker(
|
|
175
168
|
wm: WaiterMachine,
|
|
176
|
-
dp: "Dispatch",
|
|
177
169
|
interval_seconds: int = 60,
|
|
178
|
-
absolutely_dead_time: datetime.timedelta = WEEK,
|
|
179
170
|
) -> typing.NoReturn:
|
|
180
171
|
while True:
|
|
181
|
-
await wm.clear_storage(
|
|
182
|
-
views=[view for view in dp.get_views().values() if isinstance(view, ABCStateView)],
|
|
183
|
-
absolutely_dead_time=absolutely_dead_time,
|
|
184
|
-
)
|
|
172
|
+
await wm.clear_storage()
|
|
185
173
|
await asyncio.sleep(interval_seconds)
|
|
186
174
|
|
|
187
175
|
|