telegrinder 0.3.0.post2__py3-none-any.whl → 0.3.2__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 +144 -144
- telegrinder/api/__init__.py +8 -8
- telegrinder/api/api.py +93 -93
- telegrinder/api/error.py +16 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +66 -66
- telegrinder/bot/bot.py +76 -76
- telegrinder/bot/cute_types/__init__.py +11 -11
- telegrinder/bot/cute_types/base.py +258 -234
- telegrinder/bot/cute_types/callback_query.py +382 -382
- telegrinder/bot/cute_types/chat_join_request.py +61 -61
- telegrinder/bot/cute_types/chat_member_updated.py +160 -160
- telegrinder/bot/cute_types/inline_query.py +53 -53
- telegrinder/bot/cute_types/message.py +2631 -2631
- telegrinder/bot/cute_types/update.py +75 -75
- telegrinder/bot/cute_types/utils.py +92 -92
- telegrinder/bot/dispatch/__init__.py +55 -55
- telegrinder/bot/dispatch/abc.py +77 -77
- telegrinder/bot/dispatch/context.py +92 -92
- telegrinder/bot/dispatch/dispatch.py +202 -201
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +24 -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 +128 -123
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -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 +16 -16
- telegrinder/bot/dispatch/process.py +132 -132
- telegrinder/bot/dispatch/return_manager/__init__.py +13 -13
- telegrinder/bot/dispatch/return_manager/abc.py +108 -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/view/__init__.py +13 -13
- telegrinder/bot/dispatch/view/abc.py +41 -41
- telegrinder/bot/dispatch/view/base.py +200 -211
- telegrinder/bot/dispatch/view/box.py +129 -129
- telegrinder/bot/dispatch/view/callback_query.py +17 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +16 -16
- telegrinder/bot/dispatch/view/chat_member.py +39 -39
- telegrinder/bot/dispatch/view/inline_query.py +17 -17
- telegrinder/bot/dispatch/view/message.py +44 -44
- telegrinder/bot/dispatch/view/raw.py +114 -118
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +13 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +57 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +57 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +53 -53
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +168 -170
- telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +65 -65
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +131 -131
- telegrinder/bot/rules/__init__.py +62 -62
- telegrinder/bot/rules/abc.py +238 -233
- telegrinder/bot/rules/adapter/__init__.py +9 -9
- telegrinder/bot/rules/adapter/abc.py +29 -29
- telegrinder/bot/rules/adapter/errors.py +5 -5
- telegrinder/bot/rules/adapter/event.py +76 -76
- telegrinder/bot/rules/adapter/node.py +48 -48
- telegrinder/bot/rules/adapter/raw_update.py +30 -30
- telegrinder/bot/rules/callback_data.py +171 -171
- telegrinder/bot/rules/chat_join.py +48 -48
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +36 -33
- telegrinder/bot/rules/func.py +26 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/inline.py +60 -60
- telegrinder/bot/rules/integer.py +20 -20
- telegrinder/bot/rules/is_from.py +146 -146
- telegrinder/bot/rules/markup.py +43 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +17 -17
- telegrinder/bot/rules/message_entities.py +35 -35
- telegrinder/bot/rules/node.py +27 -27
- telegrinder/bot/rules/regex.py +37 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +37 -37
- telegrinder/bot/rules/text.py +33 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +19 -19
- telegrinder/bot/scenario/checkbox.py +167 -139
- telegrinder/bot/scenario/choice.py +46 -44
- telegrinder/client/__init__.py +4 -4
- telegrinder/client/abc.py +75 -75
- telegrinder/client/aiohttp.py +130 -130
- telegrinder/model.py +244 -244
- telegrinder/modules.py +237 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +410 -410
- telegrinder/node/__init__.py +20 -20
- telegrinder/node/attachment.py +92 -92
- telegrinder/node/base.py +143 -144
- telegrinder/node/callback_query.py +14 -14
- telegrinder/node/command.py +33 -33
- telegrinder/node/composer.py +196 -184
- telegrinder/node/container.py +27 -27
- telegrinder/node/event.py +71 -73
- telegrinder/node/me.py +16 -16
- telegrinder/node/message.py +14 -14
- telegrinder/node/polymorphic.py +48 -52
- telegrinder/node/rule.py +76 -76
- telegrinder/node/scope.py +38 -38
- telegrinder/node/source.py +71 -71
- telegrinder/node/text.py +21 -21
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +40 -40
- telegrinder/node/update.py +15 -15
- telegrinder/rules.py +0 -0
- telegrinder/tools/__init__.py +74 -74
- telegrinder/tools/buttons.py +79 -79
- telegrinder/tools/error_handler/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +33 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +193 -193
- telegrinder/tools/formatting/__init__.py +46 -46
- telegrinder/tools/formatting/html.py +308 -308
- telegrinder/tools/formatting/links.py +33 -33
- telegrinder/tools/formatting/spec_html_formats.py +111 -111
- telegrinder/tools/functional.py +12 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +412 -412
- telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
- telegrinder/tools/i18n/__init__.py +12 -12
- telegrinder/tools/i18n/abc.py +32 -32
- telegrinder/tools/i18n/middleware/__init__.py +3 -3
- telegrinder/tools/i18n/middleware/abc.py +25 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/kb_set/__init__.py +4 -4
- telegrinder/tools/kb_set/base.py +15 -15
- telegrinder/tools/kb_set/yaml.py +63 -63
- telegrinder/tools/keyboard.py +128 -128
- telegrinder/tools/limited_dict.py +37 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +15 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +216 -216
- telegrinder/tools/magic.py +168 -164
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +35 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/types/__init__.py +6 -6
- telegrinder/types/enums.py +672 -672
- telegrinder/types/methods.py +4633 -4633
- telegrinder/types/objects.py +6317 -6317
- telegrinder/verification_utils.py +32 -32
- {telegrinder-0.3.0.post2.dist-info → telegrinder-0.3.2.dist-info}/LICENSE +22 -22
- {telegrinder-0.3.0.post2.dist-info → telegrinder-0.3.2.dist-info}/METADATA +1 -1
- telegrinder-0.3.2.dist-info/RECORD +164 -0
- telegrinder-0.3.0.post2.dist-info/RECORD +0 -164
- {telegrinder-0.3.0.post2.dist-info → telegrinder-0.3.2.dist-info}/WHEEL +0 -0
telegrinder/node/composer.py
CHANGED
|
@@ -1,184 +1,196 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import inspect
|
|
3
|
-
import typing
|
|
4
|
-
|
|
5
|
-
from fntypes.error import UnwrapError
|
|
6
|
-
from fntypes.result import Error, Ok, Result
|
|
7
|
-
|
|
8
|
-
from telegrinder.api.api import API
|
|
9
|
-
from telegrinder.bot.cute_types.update import Update, UpdateCute
|
|
10
|
-
from telegrinder.bot.dispatch.context import Context
|
|
11
|
-
from telegrinder.modules import logger
|
|
12
|
-
from telegrinder.node.base import (
|
|
13
|
-
ComposeError,
|
|
14
|
-
Node,
|
|
15
|
-
NodeScope,
|
|
16
|
-
get_node_calc_lst,
|
|
17
|
-
get_nodes,
|
|
18
|
-
)
|
|
19
|
-
from telegrinder.tools.magic import magic_bundle
|
|
20
|
-
|
|
21
|
-
CONTEXT_STORE_NODES_KEY = "_node_ctx"
|
|
22
|
-
GLOBAL_VALUE_KEY = "_value"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
async def compose_node(
|
|
26
|
-
_node: type[Node],
|
|
27
|
-
linked: dict[type, typing.Any],
|
|
28
|
-
) -> "NodeSession":
|
|
29
|
-
node = _node.as_node()
|
|
30
|
-
kwargs = magic_bundle(node.compose, linked, typebundle=True)
|
|
31
|
-
|
|
32
|
-
if node.is_generator():
|
|
33
|
-
generator = typing.cast(typing.AsyncGenerator[typing.Any, None], node.compose(**kwargs))
|
|
34
|
-
value = await generator.asend(None)
|
|
35
|
-
else:
|
|
36
|
-
generator = None
|
|
37
|
-
value = typing.cast(typing.Awaitable[typing.Any] | typing.Any, node.compose(**kwargs))
|
|
38
|
-
if inspect.isawaitable(value):
|
|
39
|
-
value = await value
|
|
40
|
-
|
|
41
|
-
return NodeSession(_node, value, {}, generator)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
async def compose_nodes(
|
|
45
|
-
nodes: dict[str, type[Node]],
|
|
46
|
-
ctx: Context,
|
|
47
|
-
data: dict[type[typing.Any], typing.Any] | None = None,
|
|
48
|
-
) -> Result["NodeCollection", ComposeError]:
|
|
49
|
-
logger.debug("Composing nodes: {!r}...", nodes)
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
1
|
+
import dataclasses
|
|
2
|
+
import inspect
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from fntypes.error import UnwrapError
|
|
6
|
+
from fntypes.result import Error, Ok, Result
|
|
7
|
+
|
|
8
|
+
from telegrinder.api.api import API
|
|
9
|
+
from telegrinder.bot.cute_types.update import Update, UpdateCute
|
|
10
|
+
from telegrinder.bot.dispatch.context import Context
|
|
11
|
+
from telegrinder.modules import logger
|
|
12
|
+
from telegrinder.node.base import (
|
|
13
|
+
ComposeError,
|
|
14
|
+
Node,
|
|
15
|
+
NodeScope,
|
|
16
|
+
get_node_calc_lst,
|
|
17
|
+
get_nodes,
|
|
18
|
+
)
|
|
19
|
+
from telegrinder.tools.magic import magic_bundle
|
|
20
|
+
|
|
21
|
+
CONTEXT_STORE_NODES_KEY = "_node_ctx"
|
|
22
|
+
GLOBAL_VALUE_KEY = "_value"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def compose_node(
|
|
26
|
+
_node: type[Node],
|
|
27
|
+
linked: dict[type, typing.Any],
|
|
28
|
+
) -> "NodeSession":
|
|
29
|
+
node = _node.as_node()
|
|
30
|
+
kwargs = magic_bundle(node.compose, linked, typebundle=True)
|
|
31
|
+
|
|
32
|
+
if node.is_generator():
|
|
33
|
+
generator = typing.cast(typing.AsyncGenerator[typing.Any, None], node.compose(**kwargs))
|
|
34
|
+
value = await generator.asend(None)
|
|
35
|
+
else:
|
|
36
|
+
generator = None
|
|
37
|
+
value = typing.cast(typing.Awaitable[typing.Any] | typing.Any, node.compose(**kwargs))
|
|
38
|
+
if inspect.isawaitable(value):
|
|
39
|
+
value = await value
|
|
40
|
+
|
|
41
|
+
return NodeSession(_node, value, {}, generator)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def compose_nodes(
|
|
45
|
+
nodes: dict[str, type[Node]],
|
|
46
|
+
ctx: Context,
|
|
47
|
+
data: dict[type[typing.Any], typing.Any] | None = None,
|
|
48
|
+
) -> Result["NodeCollection", ComposeError]:
|
|
49
|
+
logger.debug("Composing nodes: {!r}...", nodes)
|
|
50
|
+
|
|
51
|
+
local_nodes: dict[type[Node], NodeSession]
|
|
52
|
+
data = {Context: ctx} | (data or {})
|
|
53
|
+
parent_nodes: dict[type[Node], NodeSession] = {}
|
|
54
|
+
event_nodes: dict[type[Node], NodeSession] = ctx.get_or_set(CONTEXT_STORE_NODES_KEY, {})
|
|
55
|
+
# TODO: optimize flattened list calculation via caching key = tuple of node types
|
|
56
|
+
calculation_nodes: dict[type[Node], tuple[type[Node], ...]] = {
|
|
57
|
+
node_t: tuple(get_node_calc_lst(node_t)) for node_t in nodes.values()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for parent_node, linked_nodes in calculation_nodes.items():
|
|
61
|
+
local_nodes = {}
|
|
62
|
+
subnodes = {}
|
|
63
|
+
|
|
64
|
+
for node_t in linked_nodes:
|
|
65
|
+
scope = getattr(node_t, "scope", None)
|
|
66
|
+
|
|
67
|
+
if scope is NodeScope.PER_EVENT and node_t in event_nodes:
|
|
68
|
+
local_nodes[node_t] = event_nodes[node_t]
|
|
69
|
+
continue
|
|
70
|
+
elif scope is NodeScope.GLOBAL and hasattr(node_t, GLOBAL_VALUE_KEY):
|
|
71
|
+
local_nodes[node_t] = getattr(node_t, GLOBAL_VALUE_KEY)
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
subnodes |= {
|
|
75
|
+
k: session.value for k, session in (local_nodes | event_nodes).items() if k not in subnodes
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
local_nodes[node_t] = await compose_node(node_t, subnodes | data)
|
|
80
|
+
except (ComposeError, UnwrapError) as exc:
|
|
81
|
+
for t, local_node in local_nodes.items():
|
|
82
|
+
if t.scope is NodeScope.PER_CALL:
|
|
83
|
+
await local_node.close()
|
|
84
|
+
return Error(ComposeError(f"Cannot compose {node_t}. Error: {exc}"))
|
|
85
|
+
|
|
86
|
+
if scope is NodeScope.PER_EVENT:
|
|
87
|
+
event_nodes[node_t] = local_nodes[node_t]
|
|
88
|
+
elif scope is NodeScope.GLOBAL:
|
|
89
|
+
setattr(node_t, GLOBAL_VALUE_KEY, local_nodes[node_t])
|
|
90
|
+
|
|
91
|
+
parent_nodes[parent_node] = local_nodes[parent_node]
|
|
92
|
+
|
|
93
|
+
node_sessions = {k: parent_nodes[t] for k, t in nodes.items()}
|
|
94
|
+
return Ok(NodeCollection(node_sessions))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclasses.dataclass(slots=True, repr=False)
|
|
98
|
+
class NodeSession:
|
|
99
|
+
node_type: type[Node] | None
|
|
100
|
+
value: typing.Any
|
|
101
|
+
subnodes: dict[str, typing.Self]
|
|
102
|
+
generator: typing.AsyncGenerator[typing.Any, typing.Any | None] | None = None
|
|
103
|
+
|
|
104
|
+
def __repr__(self) -> str:
|
|
105
|
+
return f"<{self.__class__.__name__}: {self.value!r}" + (" (ACTIVE)>" if self.generator else ">")
|
|
106
|
+
|
|
107
|
+
async def close(
|
|
108
|
+
self,
|
|
109
|
+
with_value: typing.Any | None = None,
|
|
110
|
+
scopes: tuple[NodeScope, ...] = (NodeScope.PER_CALL,),
|
|
111
|
+
) -> None:
|
|
112
|
+
if self.node_type and getattr(self.node_type, "scope", None) not in scopes:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
for subnode in self.subnodes.values():
|
|
116
|
+
await subnode.close(scopes=scopes)
|
|
117
|
+
|
|
118
|
+
if self.generator is None:
|
|
119
|
+
return
|
|
120
|
+
try:
|
|
121
|
+
logger.debug("Closing session for node {!r}...", self.node_type)
|
|
122
|
+
await self.generator.asend(with_value)
|
|
123
|
+
except StopAsyncIteration:
|
|
124
|
+
self.generator = None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class NodeCollection:
|
|
128
|
+
__slots__ = ("sessions", "_values")
|
|
129
|
+
|
|
130
|
+
def __init__(self, sessions: dict[str, NodeSession]) -> None:
|
|
131
|
+
self.sessions = sessions
|
|
132
|
+
self._values: dict[str, typing.Any] = {}
|
|
133
|
+
|
|
134
|
+
def __repr__(self) -> str:
|
|
135
|
+
return "<{}: sessions={!r}>".format(self.__class__.__name__, self.sessions)
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def values(self) -> dict[str, typing.Any]:
|
|
139
|
+
if self._values.keys() == self.sessions.keys():
|
|
140
|
+
return self._values
|
|
141
|
+
|
|
142
|
+
for name, session in self.sessions.items():
|
|
143
|
+
if name not in self._values:
|
|
144
|
+
self._values[name] = session.value
|
|
145
|
+
|
|
146
|
+
return self._values
|
|
147
|
+
|
|
148
|
+
async def close_all(
|
|
149
|
+
self,
|
|
150
|
+
with_value: typing.Any | None = None,
|
|
151
|
+
scopes: tuple[NodeScope, ...] = (NodeScope.PER_CALL,),
|
|
152
|
+
) -> None:
|
|
153
|
+
for session in self.sessions.values():
|
|
154
|
+
await session.close(with_value, scopes=scopes)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclasses.dataclass(slots=True, repr=False)
|
|
158
|
+
class Composition:
|
|
159
|
+
func: typing.Callable[..., typing.Any]
|
|
160
|
+
is_blocking: bool
|
|
161
|
+
nodes: dict[str, type[Node]] = dataclasses.field(init=False)
|
|
162
|
+
|
|
163
|
+
def __post_init__(self) -> None:
|
|
164
|
+
self.nodes = get_nodes(self.func)
|
|
165
|
+
|
|
166
|
+
def __repr__(self) -> str:
|
|
167
|
+
return "<{}: for function={!r} with nodes={!r}>".format(
|
|
168
|
+
("blocking " if self.is_blocking else "") + self.__class__.__name__,
|
|
169
|
+
self.func.__qualname__,
|
|
170
|
+
self.nodes,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
async def compose_nodes(
|
|
174
|
+
self,
|
|
175
|
+
update: UpdateCute,
|
|
176
|
+
context: Context,
|
|
177
|
+
) -> NodeCollection | None:
|
|
178
|
+
match await compose_nodes(
|
|
179
|
+
nodes=self.nodes,
|
|
180
|
+
ctx=context,
|
|
181
|
+
data={Update: update, API: update.api},
|
|
182
|
+
):
|
|
183
|
+
case Ok(col):
|
|
184
|
+
return col
|
|
185
|
+
case Error(err):
|
|
186
|
+
logger.debug(f"Composition failed with error: {err!r}")
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
async def __call__(self, node_cls: type[Node], **kwargs: typing.Any) -> typing.Any:
|
|
190
|
+
result = self.func(node_cls, **magic_bundle(self.func, kwargs, start_idx=0, bundle_ctx=False))
|
|
191
|
+
if inspect.isawaitable(result):
|
|
192
|
+
result = await result
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
__all__ = ("Composition", "NodeCollection", "NodeSession", "compose_node", "compose_nodes")
|
telegrinder/node/container.py
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
|
|
3
|
-
from telegrinder.node.base import Node
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class ContainerNode(Node):
|
|
7
|
-
linked_nodes: typing.ClassVar[list[type[Node]]]
|
|
8
|
-
|
|
9
|
-
@classmethod
|
|
10
|
-
def compose(cls, **kw) -> tuple[Node, ...]:
|
|
11
|
-
return tuple(t[1] for t in sorted(kw.items(), key=lambda t: t[0]))
|
|
12
|
-
|
|
13
|
-
@classmethod
|
|
14
|
-
def get_subnodes(cls) -> dict[str, type[Node]]:
|
|
15
|
-
subnodes = getattr(cls, "subnodes", None)
|
|
16
|
-
if subnodes is None:
|
|
17
|
-
subnodes_dct = {f"node_{i}": node_t for i, node_t in enumerate(cls.linked_nodes)}
|
|
18
|
-
setattr(cls, "subnodes", subnodes_dct)
|
|
19
|
-
return subnodes_dct
|
|
20
|
-
return subnodes
|
|
21
|
-
|
|
22
|
-
@classmethod
|
|
23
|
-
def link_nodes(cls, linked_nodes: list[type[Node]]) -> type["ContainerNode"]:
|
|
24
|
-
return type("_ContainerNode", (cls,), {"linked_nodes": linked_nodes})
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
__all__ = ("ContainerNode",)
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from telegrinder.node.base import Node
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ContainerNode(Node):
|
|
7
|
+
linked_nodes: typing.ClassVar[list[type[Node]]]
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
def compose(cls, **kw) -> tuple[Node, ...]:
|
|
11
|
+
return tuple(t[1] for t in sorted(kw.items(), key=lambda t: t[0]))
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def get_subnodes(cls) -> dict[str, type[Node]]:
|
|
15
|
+
subnodes = getattr(cls, "subnodes", None)
|
|
16
|
+
if subnodes is None:
|
|
17
|
+
subnodes_dct = {f"node_{i}": node_t for i, node_t in enumerate(cls.linked_nodes)}
|
|
18
|
+
setattr(cls, "subnodes", subnodes_dct)
|
|
19
|
+
return subnodes_dct
|
|
20
|
+
return subnodes
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def link_nodes(cls, linked_nodes: list[type[Node]]) -> type["ContainerNode"]:
|
|
24
|
+
return type("_ContainerNode", (cls,), {"linked_nodes": linked_nodes})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = ("ContainerNode",)
|
telegrinder/node/event.py
CHANGED
|
@@ -1,73 +1,71 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
import msgspec
|
|
5
|
-
|
|
6
|
-
from telegrinder.api import API
|
|
7
|
-
from telegrinder.bot.cute_types import BaseCute
|
|
8
|
-
from telegrinder.bot.dispatch.context import Context
|
|
9
|
-
from telegrinder.msgspec_utils import DataclassInstance, decoder
|
|
10
|
-
from telegrinder.node.base import ComposeError, Node
|
|
11
|
-
from telegrinder.node.update import UpdateNode
|
|
12
|
-
|
|
13
|
-
if typing.TYPE_CHECKING:
|
|
14
|
-
Dataclass = typing.TypeVar("Dataclass", bound="DataclassType")
|
|
15
|
-
|
|
16
|
-
DataclassType: typing.TypeAlias = DataclassInstance | msgspec.Struct | dict[str, typing.Any]
|
|
17
|
-
|
|
18
|
-
EVENT_NODE_KEY = "_event_node"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class _EventNode(Node):
|
|
22
|
-
dataclass: type["DataclassType"]
|
|
23
|
-
|
|
24
|
-
def __new__(cls, dataclass: type["DataclassType"], /) -> type[typing.Self]:
|
|
25
|
-
namespace = dict(**cls.__dict__)
|
|
26
|
-
namespace.pop("__new__", None)
|
|
27
|
-
new_cls = type("EventNode", (cls,), {"dataclass": dataclass, **namespace})
|
|
28
|
-
return new_cls # type: ignore
|
|
29
|
-
|
|
30
|
-
def __class_getitem__(cls, dataclass: type["DataclassType"], /) -> typing.Self:
|
|
31
|
-
return cls(dataclass)
|
|
32
|
-
|
|
33
|
-
@classmethod
|
|
34
|
-
def compose(cls, raw_update: UpdateNode, ctx: Context, api: API) -> "DataclassType":
|
|
35
|
-
dataclass_type = typing.get_origin(cls.dataclass) or cls.dataclass
|
|
36
|
-
|
|
37
|
-
try:
|
|
38
|
-
if issubclass(dataclass_type,
|
|
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
|
-
__all__ = ("EventNode",)
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
import msgspec
|
|
5
|
+
|
|
6
|
+
from telegrinder.api.api import API
|
|
7
|
+
from telegrinder.bot.cute_types import BaseCute
|
|
8
|
+
from telegrinder.bot.dispatch.context import Context
|
|
9
|
+
from telegrinder.msgspec_utils import DataclassInstance, decoder
|
|
10
|
+
from telegrinder.node.base import ComposeError, Node
|
|
11
|
+
from telegrinder.node.update import UpdateNode
|
|
12
|
+
|
|
13
|
+
if typing.TYPE_CHECKING:
|
|
14
|
+
Dataclass = typing.TypeVar("Dataclass", bound="DataclassType")
|
|
15
|
+
|
|
16
|
+
DataclassType: typing.TypeAlias = DataclassInstance | msgspec.Struct | dict[str, typing.Any]
|
|
17
|
+
|
|
18
|
+
EVENT_NODE_KEY = "_event_node"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class _EventNode(Node):
|
|
22
|
+
dataclass: type["DataclassType"]
|
|
23
|
+
|
|
24
|
+
def __new__(cls, dataclass: type["DataclassType"], /) -> type[typing.Self]:
|
|
25
|
+
namespace = dict(**cls.__dict__)
|
|
26
|
+
namespace.pop("__new__", None)
|
|
27
|
+
new_cls = type("EventNode", (cls,), {"dataclass": dataclass, **namespace})
|
|
28
|
+
return new_cls # type: ignore
|
|
29
|
+
|
|
30
|
+
def __class_getitem__(cls, dataclass: type["DataclassType"], /) -> typing.Self:
|
|
31
|
+
return cls(dataclass)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def compose(cls, raw_update: UpdateNode, ctx: Context, api: API) -> "DataclassType":
|
|
35
|
+
dataclass_type = typing.get_origin(cls.dataclass) or cls.dataclass
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
if issubclass(dataclass_type, BaseCute):
|
|
39
|
+
if isinstance(raw_update.incoming_update, dataclass_type):
|
|
40
|
+
dataclass = raw_update.incoming_update
|
|
41
|
+
else:
|
|
42
|
+
dataclass = dataclass_type.from_update(raw_update.incoming_update, bound_api=api)
|
|
43
|
+
|
|
44
|
+
elif issubclass(dataclass_type, msgspec.Struct | dict) or dataclasses.is_dataclass(
|
|
45
|
+
dataclass_type
|
|
46
|
+
):
|
|
47
|
+
dataclass = decoder.convert(
|
|
48
|
+
raw_update.incoming_update.to_full_dict(),
|
|
49
|
+
type=cls.dataclass,
|
|
50
|
+
str_keys=True,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
else:
|
|
54
|
+
dataclass = cls.dataclass(**raw_update.incoming_update.to_dict())
|
|
55
|
+
|
|
56
|
+
ctx[EVENT_NODE_KEY] = cls
|
|
57
|
+
return dataclass
|
|
58
|
+
except Exception as exc:
|
|
59
|
+
raise ComposeError(f"Cannot parse update into {cls.dataclass.__name__!r}, error: {str(exc)!r}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if typing.TYPE_CHECKING:
|
|
63
|
+
EventNode: typing.TypeAlias = typing.Annotated["Dataclass", ...]
|
|
64
|
+
|
|
65
|
+
else:
|
|
66
|
+
|
|
67
|
+
class EventNode(_EventNode):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
__all__ = ("EventNode",)
|
telegrinder/node/me.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
from telegrinder.api.api import API
|
|
2
|
-
from telegrinder.node.base import ComposeError, ScalarNode
|
|
3
|
-
from telegrinder.node.scope import GLOBAL
|
|
4
|
-
from telegrinder.types.objects import User
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Me(ScalarNode, User):
|
|
8
|
-
scope = GLOBAL
|
|
9
|
-
|
|
10
|
-
@classmethod
|
|
11
|
-
async def compose(cls, api: API) -> User:
|
|
12
|
-
me = await api.get_me()
|
|
13
|
-
return me.expect(ComposeError("Can't complete get_me request"))
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
__all__ = ("Me",)
|
|
1
|
+
from telegrinder.api.api import API
|
|
2
|
+
from telegrinder.node.base import ComposeError, ScalarNode
|
|
3
|
+
from telegrinder.node.scope import GLOBAL
|
|
4
|
+
from telegrinder.types.objects import User
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Me(ScalarNode, User):
|
|
8
|
+
scope = GLOBAL
|
|
9
|
+
|
|
10
|
+
@classmethod
|
|
11
|
+
async def compose(cls, api: API) -> User:
|
|
12
|
+
me = await api.get_me()
|
|
13
|
+
return me.expect(ComposeError("Can't complete get_me request"))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ("Me",)
|
telegrinder/node/message.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
from telegrinder.bot.cute_types.message import MessageCute
|
|
2
|
-
from telegrinder.node.base import ComposeError, ScalarNode
|
|
3
|
-
from telegrinder.node.update import UpdateNode
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class MessageNode(ScalarNode, MessageCute):
|
|
7
|
-
@classmethod
|
|
8
|
-
def compose(cls, update: UpdateNode) -> MessageCute:
|
|
9
|
-
if not update.message:
|
|
10
|
-
raise ComposeError("Update is not a message.")
|
|
11
|
-
return update.message.unwrap()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
__all__ = ("MessageNode",)
|
|
1
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
2
|
+
from telegrinder.node.base import ComposeError, ScalarNode
|
|
3
|
+
from telegrinder.node.update import UpdateNode
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MessageNode(ScalarNode, MessageCute):
|
|
7
|
+
@classmethod
|
|
8
|
+
def compose(cls, update: UpdateNode) -> MessageCute:
|
|
9
|
+
if not update.message:
|
|
10
|
+
raise ComposeError("Update is not a message.")
|
|
11
|
+
return update.message.unwrap()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ("MessageNode",)
|