telegrinder 0.1.dev164__py3-none-any.whl → 0.1.dev166__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 +58 -0
- telegrinder/api/abc.py +1 -1
- telegrinder/api/api.py +8 -6
- telegrinder/api/error.py +2 -3
- telegrinder/bot/__init__.py +14 -0
- telegrinder/bot/bot.py +13 -2
- telegrinder/bot/cute_types/__init__.py +4 -0
- telegrinder/bot/cute_types/base.py +22 -13
- telegrinder/bot/cute_types/chat_join_request.py +63 -0
- telegrinder/bot/cute_types/chat_member_updated.py +244 -0
- telegrinder/bot/cute_types/message.py +33 -6
- telegrinder/bot/cute_types/update.py +5 -4
- telegrinder/bot/cute_types/utils.py +39 -17
- telegrinder/bot/dispatch/__init__.py +9 -1
- telegrinder/bot/dispatch/composition.py +10 -8
- telegrinder/bot/dispatch/context.py +9 -10
- telegrinder/bot/dispatch/dispatch.py +29 -19
- telegrinder/bot/dispatch/handler/abc.py +1 -0
- telegrinder/bot/dispatch/handler/func.py +29 -6
- telegrinder/bot/dispatch/handler/message_reply.py +2 -3
- telegrinder/bot/dispatch/middleware/abc.py +2 -4
- telegrinder/bot/dispatch/process.py +4 -3
- telegrinder/bot/dispatch/return_manager/__init__.py +1 -1
- telegrinder/bot/dispatch/return_manager/abc.py +39 -21
- telegrinder/bot/dispatch/return_manager/callback_query.py +4 -2
- telegrinder/bot/dispatch/return_manager/inline_query.py +4 -2
- telegrinder/bot/dispatch/return_manager/message.py +12 -6
- telegrinder/bot/dispatch/view/__init__.py +8 -2
- telegrinder/bot/dispatch/view/abc.py +26 -20
- telegrinder/bot/dispatch/view/box.py +72 -1
- telegrinder/bot/dispatch/view/callback_query.py +1 -3
- telegrinder/bot/dispatch/view/chat_join_request.py +17 -0
- telegrinder/bot/dispatch/view/chat_member.py +26 -0
- telegrinder/bot/dispatch/view/message.py +23 -1
- telegrinder/bot/dispatch/view/raw.py +116 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -1
- telegrinder/bot/dispatch/waiter_machine/machine.py +73 -19
- telegrinder/bot/dispatch/waiter_machine/middleware.py +3 -3
- telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -3
- telegrinder/bot/polling/polling.py +17 -1
- telegrinder/bot/rules/__init__.py +20 -12
- telegrinder/bot/rules/abc.py +0 -9
- telegrinder/bot/rules/adapter/event.py +31 -22
- telegrinder/bot/rules/callback_data.py +15 -20
- telegrinder/bot/rules/chat_join.py +47 -0
- telegrinder/bot/rules/enum_text.py +7 -2
- telegrinder/bot/rules/inline.py +3 -3
- telegrinder/bot/rules/is_from.py +36 -50
- telegrinder/bot/rules/markup.py +1 -1
- telegrinder/bot/rules/message.py +17 -0
- telegrinder/bot/rules/message_entities.py +1 -1
- telegrinder/bot/rules/start.py +6 -4
- telegrinder/bot/rules/text.py +2 -1
- telegrinder/bot/rules/update.py +16 -0
- telegrinder/bot/scenario/checkbox.py +9 -7
- telegrinder/client/aiohttp.py +4 -4
- telegrinder/model.py +43 -19
- telegrinder/modules.py +16 -32
- telegrinder/msgspec_utils.py +48 -36
- telegrinder/node/__init__.py +12 -12
- telegrinder/node/attachment.py +15 -5
- telegrinder/node/base.py +24 -16
- telegrinder/node/composer.py +8 -6
- telegrinder/node/container.py +1 -1
- telegrinder/node/rule.py +4 -4
- telegrinder/node/source.py +4 -2
- telegrinder/node/tools/generator.py +1 -1
- telegrinder/tools/__init__.py +2 -1
- telegrinder/tools/error_handler/abc.py +4 -3
- telegrinder/tools/error_handler/error_handler.py +22 -16
- telegrinder/tools/formatting/html.py +20 -18
- telegrinder/tools/formatting/links.py +12 -6
- telegrinder/tools/formatting/spec_html_formats.py +5 -6
- telegrinder/tools/global_context/abc.py +5 -1
- telegrinder/tools/global_context/global_context.py +2 -2
- telegrinder/tools/global_context/telegrinder_ctx.py +1 -1
- telegrinder/tools/i18n/base.py +4 -3
- telegrinder/tools/i18n/simple.py +1 -3
- telegrinder/tools/keyboard.py +1 -1
- telegrinder/tools/loop_wrapper/__init__.py +2 -2
- telegrinder/tools/loop_wrapper/abc.py +1 -4
- telegrinder/tools/loop_wrapper/loop_wrapper.py +90 -36
- telegrinder/tools/magic.py +1 -1
- telegrinder/types/__init__.py +206 -0
- telegrinder/types/enums.py +34 -0
- telegrinder/types/methods.py +52 -47
- telegrinder/types/objects.py +531 -88
- telegrinder/verification_utils.py +33 -0
- {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/METADATA +1 -1
- telegrinder-0.1.dev166.dist-info/RECORD +136 -0
- telegrinder-0.1.dev164.dist-info/RECORD +0 -127
- {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/WHEEL +0 -0
telegrinder/msgspec_utils.py
CHANGED
|
@@ -15,15 +15,14 @@ else:
|
|
|
15
15
|
|
|
16
16
|
datetime = type("datetime", (dt,), {})
|
|
17
17
|
|
|
18
|
-
|
|
19
18
|
class OptionMeta(type):
|
|
20
19
|
def __instancecheck__(cls, __instance: typing.Any) -> bool:
|
|
21
20
|
return isinstance(__instance, fntypes.option.Some | fntypes.option.Nothing)
|
|
22
21
|
|
|
23
|
-
|
|
24
22
|
class Option(typing.Generic[Value], metaclass=OptionMeta):
|
|
25
23
|
pass
|
|
26
24
|
|
|
25
|
+
|
|
27
26
|
T = typing.TypeVar("T")
|
|
28
27
|
Ts = typing.TypeVarTuple("Ts")
|
|
29
28
|
|
|
@@ -51,14 +50,13 @@ def msgspec_convert(obj: typing.Any, t: type[T]) -> Result[T, msgspec.Validation
|
|
|
51
50
|
def option_dec_hook(tp: type[Option[typing.Any]], obj: typing.Any) -> Option[typing.Any]:
|
|
52
51
|
if obj is None:
|
|
53
52
|
return Nothing
|
|
54
|
-
|
|
55
|
-
value_type: typing.Any | type[typing.Any] = typing.Any if not generic_args else generic_args[0]
|
|
53
|
+
(value_type,) = typing.get_args(tp) or (typing.Any,)
|
|
56
54
|
return msgspec_convert({"value": obj}, fntypes.option.Some[value_type]).unwrap()
|
|
57
55
|
|
|
58
56
|
|
|
59
57
|
def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
|
|
60
58
|
union_types = typing.get_args(tp)
|
|
61
|
-
|
|
59
|
+
|
|
62
60
|
if isinstance(obj, dict):
|
|
63
61
|
struct_fields_match_sums: dict[type[msgspec.Struct], int] = {
|
|
64
62
|
m: sum(1 for k in obj if k in m.__struct_fields__)
|
|
@@ -68,15 +66,23 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
|
|
|
68
66
|
union_types = tuple(t for t in union_types if t not in struct_fields_match_sums)
|
|
69
67
|
reverse = False
|
|
70
68
|
|
|
71
|
-
if len(set(struct_fields_match_sums.values())) != len(
|
|
72
|
-
struct_fields_match_sums
|
|
69
|
+
if len(set(struct_fields_match_sums.values())) != len(
|
|
70
|
+
struct_fields_match_sums.values()
|
|
71
|
+
):
|
|
72
|
+
struct_fields_match_sums = {
|
|
73
|
+
m: len(m.__struct_fields__) for m in struct_fields_match_sums
|
|
74
|
+
}
|
|
73
75
|
reverse = True
|
|
74
76
|
|
|
75
77
|
union_types = (
|
|
76
|
-
*sorted(
|
|
78
|
+
*sorted(
|
|
79
|
+
struct_fields_match_sums,
|
|
80
|
+
key=lambda k: struct_fields_match_sums[k],
|
|
81
|
+
reverse=reverse,
|
|
82
|
+
),
|
|
77
83
|
*union_types,
|
|
78
84
|
)
|
|
79
|
-
|
|
85
|
+
|
|
80
86
|
for t in union_types:
|
|
81
87
|
match msgspec_convert(obj, t):
|
|
82
88
|
case Ok(value):
|
|
@@ -93,7 +99,7 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
|
|
|
93
99
|
class Decoder:
|
|
94
100
|
"""Class `Decoder` for `msgspec` module with decode hook
|
|
95
101
|
for objects with the specified type.
|
|
96
|
-
|
|
102
|
+
|
|
97
103
|
```
|
|
98
104
|
import enum
|
|
99
105
|
|
|
@@ -124,12 +130,18 @@ class Decoder:
|
|
|
124
130
|
datetime: lambda t, obj: t.fromtimestamp(obj),
|
|
125
131
|
}
|
|
126
132
|
|
|
133
|
+
def __repr__(self) -> str:
|
|
134
|
+
return "<{}: dec_hooks={!r}>".format(
|
|
135
|
+
self.__class__.__name__,
|
|
136
|
+
self.dec_hooks,
|
|
137
|
+
)
|
|
138
|
+
|
|
127
139
|
def add_dec_hook(self, t: T): # type: ignore
|
|
128
140
|
def decorator(func: DecHook[T]) -> DecHook[T]:
|
|
129
141
|
return self.dec_hooks.setdefault(get_origin(t), func) # type: ignore
|
|
130
|
-
|
|
142
|
+
|
|
131
143
|
return decorator
|
|
132
|
-
|
|
144
|
+
|
|
133
145
|
def dec_hook(self, tp: type[typing.Any], obj: object) -> object:
|
|
134
146
|
origin_type = t if isinstance((t := get_origin(tp)), type) else type(t)
|
|
135
147
|
if origin_type not in self.dec_hooks:
|
|
@@ -138,7 +150,7 @@ class Decoder:
|
|
|
138
150
|
"You can implement decode hook for this type."
|
|
139
151
|
)
|
|
140
152
|
return self.dec_hooks[origin_type](tp, obj)
|
|
141
|
-
|
|
153
|
+
|
|
142
154
|
def convert(
|
|
143
155
|
self,
|
|
144
156
|
obj: object,
|
|
@@ -158,14 +170,12 @@ class Decoder:
|
|
|
158
170
|
builtin_types=builtin_types,
|
|
159
171
|
str_keys=str_keys,
|
|
160
172
|
)
|
|
161
|
-
|
|
173
|
+
|
|
162
174
|
@typing.overload
|
|
163
|
-
def decode(self, buf: str | bytes) -> typing.Any:
|
|
164
|
-
|
|
165
|
-
|
|
175
|
+
def decode(self, buf: str | bytes) -> typing.Any: ...
|
|
176
|
+
|
|
166
177
|
@typing.overload
|
|
167
|
-
def decode(self, buf: str | bytes, *, strict: bool = True) -> typing.Any:
|
|
168
|
-
...
|
|
178
|
+
def decode(self, buf: str | bytes, *, strict: bool = True) -> typing.Any: ...
|
|
169
179
|
|
|
170
180
|
@typing.overload
|
|
171
181
|
def decode(
|
|
@@ -174,8 +184,7 @@ class Decoder:
|
|
|
174
184
|
*,
|
|
175
185
|
type: type[T],
|
|
176
186
|
strict: bool = True,
|
|
177
|
-
) -> T:
|
|
178
|
-
...
|
|
187
|
+
) -> T: ...
|
|
179
188
|
|
|
180
189
|
def decode(
|
|
181
190
|
self,
|
|
@@ -194,7 +203,7 @@ class Decoder:
|
|
|
194
203
|
|
|
195
204
|
class Encoder:
|
|
196
205
|
"""Class `Encoder` for `msgspec` module with encode hooks for objects.
|
|
197
|
-
|
|
206
|
+
|
|
198
207
|
```
|
|
199
208
|
from datetime import datetime as dt
|
|
200
209
|
|
|
@@ -216,13 +225,19 @@ class Encoder:
|
|
|
216
225
|
datetime: lambda date: int(date.timestamp()),
|
|
217
226
|
}
|
|
218
227
|
|
|
228
|
+
def __repr__(self) -> str:
|
|
229
|
+
return "<{}: enc_hooks={!r}>".format(
|
|
230
|
+
self.__class__.__name__,
|
|
231
|
+
self.enc_hooks,
|
|
232
|
+
)
|
|
233
|
+
|
|
219
234
|
def add_dec_hook(self, t: type[T]):
|
|
220
235
|
def decorator(func: EncHook[T]) -> EncHook[T]:
|
|
221
236
|
encode_hook = self.enc_hooks.setdefault(get_origin(t), func)
|
|
222
237
|
return func if encode_hook is not func else encode_hook
|
|
223
|
-
|
|
238
|
+
|
|
224
239
|
return decorator
|
|
225
|
-
|
|
240
|
+
|
|
226
241
|
def enc_hook(self, obj: object) -> object:
|
|
227
242
|
origin_type = get_origin(obj.__class__)
|
|
228
243
|
if origin_type not in self.enc_hooks:
|
|
@@ -233,16 +248,13 @@ class Encoder:
|
|
|
233
248
|
return self.enc_hooks[origin_type](obj)
|
|
234
249
|
|
|
235
250
|
@typing.overload
|
|
236
|
-
def encode(self, obj: typing.Any) -> str:
|
|
237
|
-
|
|
238
|
-
|
|
251
|
+
def encode(self, obj: typing.Any) -> str: ...
|
|
252
|
+
|
|
239
253
|
@typing.overload
|
|
240
|
-
def encode(self, obj: typing.Any, *, as_str: typing.Literal[True]) -> str:
|
|
241
|
-
...
|
|
254
|
+
def encode(self, obj: typing.Any, *, as_str: typing.Literal[True]) -> str: ...
|
|
242
255
|
|
|
243
256
|
@typing.overload
|
|
244
|
-
def encode(self, obj: typing.Any, *, as_str: typing.Literal[False]) -> bytes:
|
|
245
|
-
...
|
|
257
|
+
def encode(self, obj: typing.Any, *, as_str: typing.Literal[False]) -> bytes: ...
|
|
246
258
|
|
|
247
259
|
def encode(self, obj: typing.Any, *, as_str: bool = True) -> str | bytes:
|
|
248
260
|
buf = msgspec.json.encode(obj, enc_hook=self.enc_hook)
|
|
@@ -256,14 +268,14 @@ encoder: typing.Final[Encoder] = Encoder()
|
|
|
256
268
|
__all__ = (
|
|
257
269
|
"Decoder",
|
|
258
270
|
"Encoder",
|
|
259
|
-
"Option",
|
|
260
271
|
"Nothing",
|
|
272
|
+
"Option",
|
|
273
|
+
"datetime",
|
|
274
|
+
"decoder",
|
|
275
|
+
"encoder",
|
|
261
276
|
"get_origin",
|
|
262
|
-
"repr_type",
|
|
263
277
|
"msgspec_convert",
|
|
264
278
|
"option_dec_hook",
|
|
279
|
+
"repr_type",
|
|
265
280
|
"variative_dec_hook",
|
|
266
|
-
"datetime",
|
|
267
|
-
"decoder",
|
|
268
|
-
"encoder",
|
|
269
281
|
)
|
telegrinder/node/__init__.py
CHANGED
|
@@ -10,22 +10,22 @@ from .tools import generate
|
|
|
10
10
|
from .update import UpdateNode
|
|
11
11
|
|
|
12
12
|
__all__ = (
|
|
13
|
-
"Node",
|
|
14
|
-
"DataNode",
|
|
15
|
-
"ScalarNode",
|
|
16
13
|
"Attachment",
|
|
17
|
-
"Photo",
|
|
18
|
-
"Video",
|
|
19
|
-
"Text",
|
|
20
14
|
"Audio",
|
|
21
|
-
"UpdateNode",
|
|
22
|
-
"compose_node",
|
|
23
15
|
"ComposeError",
|
|
16
|
+
"ContainerNode",
|
|
17
|
+
"DataNode",
|
|
24
18
|
"MessageNode",
|
|
25
|
-
"
|
|
26
|
-
"NodeSession",
|
|
19
|
+
"Node",
|
|
27
20
|
"NodeCollection",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
21
|
+
"NodeSession",
|
|
22
|
+
"Photo",
|
|
30
23
|
"RuleContext",
|
|
24
|
+
"ScalarNode",
|
|
25
|
+
"Source",
|
|
26
|
+
"Text",
|
|
27
|
+
"UpdateNode",
|
|
28
|
+
"Video",
|
|
29
|
+
"compose_node",
|
|
30
|
+
"generate",
|
|
31
31
|
)
|
telegrinder/node/attachment.py
CHANGED
|
@@ -14,11 +14,21 @@ from .message import MessageNode
|
|
|
14
14
|
class Attachment(DataNode):
|
|
15
15
|
attachment_type: typing.Literal["audio", "document", "photo", "poll", "video"]
|
|
16
16
|
_: dataclasses.KW_ONLY
|
|
17
|
-
audio: Option[telegrinder.types.Audio] = dataclasses.field(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
audio: Option[telegrinder.types.Audio] = dataclasses.field(
|
|
18
|
+
default_factory=lambda: Nothing()
|
|
19
|
+
)
|
|
20
|
+
document: Option[telegrinder.types.Document] = dataclasses.field(
|
|
21
|
+
default_factory=lambda: Nothing()
|
|
22
|
+
)
|
|
23
|
+
photo: Option[list[telegrinder.types.PhotoSize]] = dataclasses.field(
|
|
24
|
+
default_factory=lambda: Nothing()
|
|
25
|
+
)
|
|
26
|
+
poll: Option[telegrinder.types.Poll] = dataclasses.field(
|
|
27
|
+
default_factory=lambda: Nothing()
|
|
28
|
+
)
|
|
29
|
+
video: Option[telegrinder.types.Video] = dataclasses.field(
|
|
30
|
+
default_factory=lambda: Nothing()
|
|
31
|
+
)
|
|
22
32
|
|
|
23
33
|
@classmethod
|
|
24
34
|
async def compose(cls, message: MessageNode) -> "Attachment":
|
telegrinder/node/base.py
CHANGED
|
@@ -2,11 +2,13 @@ import abc
|
|
|
2
2
|
import inspect
|
|
3
3
|
import typing
|
|
4
4
|
|
|
5
|
-
ComposeResult: typing.TypeAlias =
|
|
5
|
+
ComposeResult: typing.TypeAlias = (
|
|
6
|
+
typing.Coroutine[typing.Any, typing.Any, typing.Any]
|
|
7
|
+
| typing.AsyncGenerator[typing.Any, None]
|
|
8
|
+
)
|
|
6
9
|
|
|
7
10
|
|
|
8
|
-
class ComposeError(BaseException):
|
|
9
|
-
...
|
|
11
|
+
class ComposeError(BaseException): ...
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class Node(abc.ABC):
|
|
@@ -14,7 +16,9 @@ class Node(abc.ABC):
|
|
|
14
16
|
|
|
15
17
|
@classmethod
|
|
16
18
|
@abc.abstractmethod
|
|
17
|
-
def compose(
|
|
19
|
+
def compose(
|
|
20
|
+
cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
|
|
21
|
+
) -> ComposeResult:
|
|
18
22
|
pass
|
|
19
23
|
|
|
20
24
|
@classmethod
|
|
@@ -24,7 +28,7 @@ class Node(abc.ABC):
|
|
|
24
28
|
@classmethod
|
|
25
29
|
def get_sub_nodes(cls) -> dict[str, type[typing.Self]]:
|
|
26
30
|
parameters = inspect.signature(cls.compose).parameters
|
|
27
|
-
|
|
31
|
+
|
|
28
32
|
sub_nodes = {}
|
|
29
33
|
for name, param in parameters.items():
|
|
30
34
|
if param.annotation is inspect._empty:
|
|
@@ -32,11 +36,11 @@ class Node(abc.ABC):
|
|
|
32
36
|
node = param.annotation
|
|
33
37
|
sub_nodes[name] = node
|
|
34
38
|
return sub_nodes
|
|
35
|
-
|
|
39
|
+
|
|
36
40
|
@classmethod
|
|
37
41
|
def as_node(cls) -> type[typing.Self]:
|
|
38
42
|
return cls
|
|
39
|
-
|
|
43
|
+
|
|
40
44
|
@classmethod
|
|
41
45
|
def is_generator(cls) -> bool:
|
|
42
46
|
return inspect.isasyncgenfunction(cls.compose)
|
|
@@ -48,14 +52,18 @@ class DataNode(Node, abc.ABC):
|
|
|
48
52
|
@typing.dataclass_transform()
|
|
49
53
|
@classmethod
|
|
50
54
|
@abc.abstractmethod
|
|
51
|
-
async def compose(
|
|
55
|
+
async def compose(
|
|
56
|
+
cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
|
|
57
|
+
) -> ComposeResult:
|
|
52
58
|
pass
|
|
53
59
|
|
|
54
60
|
|
|
55
61
|
class ScalarNodeProto(Node, abc.ABC):
|
|
56
62
|
@classmethod
|
|
57
63
|
@abc.abstractmethod
|
|
58
|
-
async def compose(
|
|
64
|
+
async def compose(
|
|
65
|
+
cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
|
|
66
|
+
) -> ComposeResult:
|
|
59
67
|
pass
|
|
60
68
|
|
|
61
69
|
|
|
@@ -64,9 +72,9 @@ SCALAR_NODE = type("ScalarNode", (), {"node": "scalar"})
|
|
|
64
72
|
|
|
65
73
|
if typing.TYPE_CHECKING:
|
|
66
74
|
|
|
67
|
-
class ScalarNode(ScalarNodeProto, abc.ABC):
|
|
75
|
+
class ScalarNode(ScalarNodeProto, abc.ABC):
|
|
68
76
|
pass
|
|
69
|
-
|
|
77
|
+
|
|
70
78
|
else:
|
|
71
79
|
|
|
72
80
|
def create_node(cls, bases, dct):
|
|
@@ -75,8 +83,8 @@ else:
|
|
|
75
83
|
|
|
76
84
|
def create_class(name, bases, dct):
|
|
77
85
|
return type(
|
|
78
|
-
"Scalar",
|
|
79
|
-
(SCALAR_NODE,),
|
|
86
|
+
"Scalar",
|
|
87
|
+
(SCALAR_NODE,),
|
|
80
88
|
{"as_node": classmethod(lambda cls: create_node(cls, bases, dct))},
|
|
81
89
|
)
|
|
82
90
|
|
|
@@ -85,9 +93,9 @@ else:
|
|
|
85
93
|
|
|
86
94
|
|
|
87
95
|
__all__ = (
|
|
88
|
-
"
|
|
89
|
-
"SCALAR_NODE",
|
|
96
|
+
"ComposeError",
|
|
90
97
|
"DataNode",
|
|
91
98
|
"Node",
|
|
92
|
-
"
|
|
99
|
+
"SCALAR_NODE",
|
|
100
|
+
"ScalarNode",
|
|
93
101
|
)
|
telegrinder/node/composer.py
CHANGED
|
@@ -6,7 +6,7 @@ from telegrinder.node import Node
|
|
|
6
6
|
|
|
7
7
|
class NodeSession:
|
|
8
8
|
def __init__(
|
|
9
|
-
self,
|
|
9
|
+
self,
|
|
10
10
|
value: typing.Any,
|
|
11
11
|
subnodes: dict[str, typing.Self],
|
|
12
12
|
generator: typing.AsyncGenerator[typing.Any, None] | None = None,
|
|
@@ -14,11 +14,11 @@ class NodeSession:
|
|
|
14
14
|
self.value = value
|
|
15
15
|
self.subnodes = subnodes
|
|
16
16
|
self.generator = generator
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
async def close(self, with_value: typing.Any | None = None) -> None:
|
|
19
19
|
for subnode in self.subnodes.values():
|
|
20
20
|
await subnode.close()
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
if self.generator is None:
|
|
23
23
|
return
|
|
24
24
|
try:
|
|
@@ -27,7 +27,9 @@ class NodeSession:
|
|
|
27
27
|
self.generator = None
|
|
28
28
|
|
|
29
29
|
def __repr__(self) -> str:
|
|
30
|
-
return f"<
|
|
30
|
+
return f"<{self.__class__.__name__}: {self.value}" + (
|
|
31
|
+
"ACTIVE>" if self.generator else ">"
|
|
32
|
+
)
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
class NodeCollection:
|
|
@@ -36,7 +38,7 @@ class NodeCollection:
|
|
|
36
38
|
|
|
37
39
|
def values(self) -> dict[str, typing.Any]:
|
|
38
40
|
return {name: session.value for name, session in self.sessions.items()}
|
|
39
|
-
|
|
41
|
+
|
|
40
42
|
async def close_all(self, with_value: typing.Any | None = None) -> None:
|
|
41
43
|
for session in self.sessions.values():
|
|
42
44
|
await session.close(with_value)
|
|
@@ -64,7 +66,7 @@ async def compose_node(
|
|
|
64
66
|
else:
|
|
65
67
|
generator = None
|
|
66
68
|
value = await _node.compose(**context.values()) # type: ignore
|
|
67
|
-
|
|
69
|
+
|
|
68
70
|
return NodeSession(value, context.sessions, generator)
|
|
69
71
|
|
|
70
72
|
|
telegrinder/node/container.py
CHANGED
|
@@ -13,7 +13,7 @@ class ContainerNode(Node):
|
|
|
13
13
|
@classmethod
|
|
14
14
|
def get_sub_nodes(cls) -> dict[str, type["Node"]]:
|
|
15
15
|
return {f"node_{i}": node_t for i, node_t in enumerate(cls.linked_nodes)}
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
@classmethod
|
|
18
18
|
def link_nodes(cls, linked_nodes: list[type[Node]]) -> type["ContainerNode"]:
|
|
19
19
|
return type("_ContainerNode", (cls,), {"linked_nodes": linked_nodes})
|
telegrinder/node/rule.py
CHANGED
|
@@ -26,7 +26,7 @@ class RuleContext(dict):
|
|
|
26
26
|
@classmethod
|
|
27
27
|
def as_node(cls) -> type[typing.Self]:
|
|
28
28
|
return cls
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
@classmethod
|
|
31
31
|
def get_sub_nodes(cls) -> dict:
|
|
32
32
|
return {"update": UpdateNode}
|
|
@@ -37,16 +37,16 @@ class RuleContext(dict):
|
|
|
37
37
|
|
|
38
38
|
def __new__(cls, *rules: ABCRule) -> type[Node]:
|
|
39
39
|
return type("_RuleNode", (cls,), {"dataclass": dict, "rules": rules}) # type: ignore
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
def __class_getitem__(cls, item: tuple[ABCRule, ...]) -> typing.Self:
|
|
42
42
|
if not isinstance(item, tuple):
|
|
43
43
|
item = (item,)
|
|
44
44
|
return cls(*item)
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
@staticmethod
|
|
47
47
|
def generate_dataclass(cls_: type["RuleContext"]): # noqa: ANN205
|
|
48
48
|
return dataclasses.dataclass(type(cls_.__name__, (object,), dict(cls_.__dict__)))
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
def __init_subclass__(cls) -> None:
|
|
51
51
|
if cls.__name__ == "_RuleNode":
|
|
52
52
|
return
|
telegrinder/node/source.py
CHANGED
|
@@ -21,9 +21,11 @@ class Source(DataNode):
|
|
|
21
21
|
chat=message.chat,
|
|
22
22
|
thread_id=message.message_thread_id.unwrap_or_none(),
|
|
23
23
|
)
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
async def send(self, text: str) -> Message:
|
|
26
|
-
result = await self.api.send_message(
|
|
26
|
+
result = await self.api.send_message(
|
|
27
|
+
self.chat.id, message_thread_id=self.thread_id, text=text
|
|
28
|
+
)
|
|
27
29
|
return result.unwrap()
|
|
28
30
|
|
|
29
31
|
|
|
@@ -20,7 +20,7 @@ def error_on_none(value: T | None) -> T:
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def generate(
|
|
23
|
-
subnodes: tuple[type[Node], ...],
|
|
23
|
+
subnodes: tuple[type[Node], ...],
|
|
24
24
|
func: typing.Callable[..., typing.Any],
|
|
25
25
|
casts: tuple[typing.Callable, ...] = (cast_false_to_none, error_on_none),
|
|
26
26
|
) -> type[ContainerNode]:
|
telegrinder/tools/__init__.py
CHANGED
|
@@ -66,7 +66,7 @@ from .keyboard import (
|
|
|
66
66
|
Keyboard,
|
|
67
67
|
RowButtons,
|
|
68
68
|
)
|
|
69
|
-
from .loop_wrapper import ABCLoopWrapper, DelayedTask, LoopWrapper
|
|
69
|
+
from .loop_wrapper import ABCLoopWrapper, DelayedTask, Lifespan, LoopWrapper
|
|
70
70
|
from .magic import magic_bundle, resolve_arg_names
|
|
71
71
|
from .parse_mode import ParseMode
|
|
72
72
|
|
|
@@ -98,6 +98,7 @@ __all__ = (
|
|
|
98
98
|
"Keyboard",
|
|
99
99
|
"KeyboardSetBase",
|
|
100
100
|
"KeyboardSetYAML",
|
|
101
|
+
"Lifespan",
|
|
101
102
|
"Link",
|
|
102
103
|
"LoopWrapper",
|
|
103
104
|
"Mention",
|
|
@@ -4,10 +4,9 @@ from abc import ABC, abstractmethod
|
|
|
4
4
|
from fntypes.result import Result
|
|
5
5
|
|
|
6
6
|
from telegrinder.api import ABCAPI
|
|
7
|
-
from telegrinder.bot.cute_types import BaseCute
|
|
8
7
|
from telegrinder.bot.dispatch.context import Context
|
|
9
8
|
|
|
10
|
-
EventT = typing.TypeVar("EventT"
|
|
9
|
+
EventT = typing.TypeVar("EventT")
|
|
11
10
|
Handler = typing.Callable[typing.Concatenate[EventT, ...], typing.Awaitable[typing.Any]]
|
|
12
11
|
|
|
13
12
|
|
|
@@ -17,7 +16,9 @@ class ABCErrorHandler(ABC, typing.Generic[EventT]):
|
|
|
17
16
|
self,
|
|
18
17
|
*args: typing.Any,
|
|
19
18
|
**kwargs: typing.Any,
|
|
20
|
-
) -> typing.Callable[
|
|
19
|
+
) -> typing.Callable[
|
|
20
|
+
[typing.Callable[..., typing.Any]], typing.Callable[..., typing.Any]
|
|
21
|
+
]:
|
|
21
22
|
"""Decorator for registering callback as an error handler."""
|
|
22
23
|
|
|
23
24
|
@abstractmethod
|
|
@@ -13,7 +13,9 @@ from .error import CatcherError
|
|
|
13
13
|
|
|
14
14
|
F = typing.TypeVar("F", bound="FuncCatcher")
|
|
15
15
|
ExceptionT = typing.TypeVar("ExceptionT", bound=BaseException, contravariant=True)
|
|
16
|
-
FuncCatcher = typing.Callable[
|
|
16
|
+
FuncCatcher = typing.Callable[
|
|
17
|
+
typing.Concatenate[ExceptionT, ...], typing.Awaitable[typing.Any]
|
|
18
|
+
]
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
@dataclasses.dataclass(frozen=True, repr=False)
|
|
@@ -71,7 +73,7 @@ class Catcher(typing.Generic[EventT]):
|
|
|
71
73
|
)
|
|
72
74
|
logger.debug("Failed to match exception {!r}!", exception.__class__.__name__)
|
|
73
75
|
return Error(exception)
|
|
74
|
-
|
|
76
|
+
|
|
75
77
|
def match_exception(self, exception: BaseException) -> bool:
|
|
76
78
|
for exc in self.exceptions:
|
|
77
79
|
if isinstance(exc, type) and type(exception) is exc:
|
|
@@ -84,10 +86,11 @@ class Catcher(typing.Generic[EventT]):
|
|
|
84
86
|
class ErrorHandler(ABCErrorHandler[EventT]):
|
|
85
87
|
def __init__(self, catcher: Catcher[EventT] | None = None, /) -> None:
|
|
86
88
|
self.catcher = catcher
|
|
87
|
-
|
|
89
|
+
|
|
88
90
|
def __repr__(self) -> str:
|
|
89
91
|
return (
|
|
90
|
-
"<
|
|
92
|
+
"<{}: exceptions_handled=[{}], catcher={!r}>".format(
|
|
93
|
+
self.__class__.__name__,
|
|
91
94
|
", ".join(
|
|
92
95
|
e.__name__ if isinstance(e, type) else repr(e)
|
|
93
96
|
for e in self.catcher.exceptions
|
|
@@ -95,7 +98,7 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
95
98
|
self.catcher,
|
|
96
99
|
)
|
|
97
100
|
if self.catcher is not None
|
|
98
|
-
else "<
|
|
101
|
+
else "<{}: No catcher>".format(self.__class__.__name__)
|
|
99
102
|
)
|
|
100
103
|
|
|
101
104
|
def __call__(
|
|
@@ -121,8 +124,9 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
121
124
|
ignore_errors=ignore_errors,
|
|
122
125
|
)
|
|
123
126
|
return func
|
|
127
|
+
|
|
124
128
|
return decorator
|
|
125
|
-
|
|
129
|
+
|
|
126
130
|
async def process(
|
|
127
131
|
self,
|
|
128
132
|
handler: Handler[EventT],
|
|
@@ -135,23 +139,25 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
135
139
|
try:
|
|
136
140
|
return await self.catcher(handler, event, api, ctx)
|
|
137
141
|
except BaseException as exc:
|
|
138
|
-
return Error(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
return Error(
|
|
143
|
+
CatcherError(
|
|
144
|
+
exc,
|
|
145
|
+
"Exception {!r} was occurred during the running catcher {!r}.".format(
|
|
146
|
+
repr(exc), self.catcher.func.__name__
|
|
147
|
+
),
|
|
142
148
|
)
|
|
143
|
-
)
|
|
144
|
-
|
|
149
|
+
)
|
|
150
|
+
|
|
145
151
|
def process_catcher_error(self, error: CatcherError) -> Result[None, str]:
|
|
146
152
|
assert self.catcher is not None
|
|
147
|
-
|
|
153
|
+
|
|
148
154
|
if self.catcher.raise_exception:
|
|
149
155
|
raise error.exc from None
|
|
150
156
|
if not self.catcher.ignore_errors:
|
|
151
157
|
return Error(error.error)
|
|
152
158
|
if self.catcher.logging:
|
|
153
159
|
logger.error(error.error)
|
|
154
|
-
|
|
160
|
+
|
|
155
161
|
return Ok(None)
|
|
156
162
|
|
|
157
163
|
async def run(
|
|
@@ -163,13 +169,13 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
163
169
|
) -> Result[typing.Any, typing.Any]:
|
|
164
170
|
if not self.catcher:
|
|
165
171
|
return Ok(await handler(event, **magic_bundle(handler, ctx))) # type: ignore
|
|
166
|
-
|
|
172
|
+
|
|
167
173
|
match await self.process(handler, event, api, ctx):
|
|
168
174
|
case Ok(_) as ok:
|
|
169
175
|
return ok
|
|
170
176
|
case Error(exc) as err:
|
|
171
177
|
if isinstance(exc, CatcherError):
|
|
172
|
-
return self.process_catcher_error(exc)
|
|
178
|
+
return self.process_catcher_error(exc)
|
|
173
179
|
if self.catcher.ignore_errors:
|
|
174
180
|
return Ok(None)
|
|
175
181
|
return err
|