telegrinder 0.1.dev165__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 +22 -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 +5 -3
- 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 +34 -7
- 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 +33 -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 +4 -2
- 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/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 +33 -19
- telegrinder/modules.py +16 -32
- telegrinder/msgspec_utils.py +37 -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 +1 -1
- telegrinder/tools/error_handler/abc.py +4 -3
- telegrinder/tools/error_handler/error_handler.py +22 -16
- telegrinder/tools/formatting/html.py +15 -7
- telegrinder/tools/formatting/spec_html_formats.py +1 -1
- telegrinder/tools/global_context/abc.py +5 -1
- 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/loop_wrapper.py +24 -16
- 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 +3 -1
- {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev166.dist-info}/METADATA +1 -1
- telegrinder-0.1.dev166.dist-info/RECORD +136 -0
- telegrinder-0.1.dev165.dist-info/RECORD +0 -128
- {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev166.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev165.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,13 +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
|
-
value_type, = typing.get_args(tp) or (typing.Any,)
|
|
53
|
+
(value_type,) = typing.get_args(tp) or (typing.Any,)
|
|
55
54
|
return msgspec_convert({"value": obj}, fntypes.option.Some[value_type]).unwrap()
|
|
56
55
|
|
|
57
56
|
|
|
58
57
|
def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
|
|
59
58
|
union_types = typing.get_args(tp)
|
|
60
|
-
|
|
59
|
+
|
|
61
60
|
if isinstance(obj, dict):
|
|
62
61
|
struct_fields_match_sums: dict[type[msgspec.Struct], int] = {
|
|
63
62
|
m: sum(1 for k in obj if k in m.__struct_fields__)
|
|
@@ -67,15 +66,23 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
|
|
|
67
66
|
union_types = tuple(t for t in union_types if t not in struct_fields_match_sums)
|
|
68
67
|
reverse = False
|
|
69
68
|
|
|
70
|
-
if len(set(struct_fields_match_sums.values())) != len(
|
|
71
|
-
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
|
+
}
|
|
72
75
|
reverse = True
|
|
73
76
|
|
|
74
77
|
union_types = (
|
|
75
|
-
*sorted(
|
|
78
|
+
*sorted(
|
|
79
|
+
struct_fields_match_sums,
|
|
80
|
+
key=lambda k: struct_fields_match_sums[k],
|
|
81
|
+
reverse=reverse,
|
|
82
|
+
),
|
|
76
83
|
*union_types,
|
|
77
84
|
)
|
|
78
|
-
|
|
85
|
+
|
|
79
86
|
for t in union_types:
|
|
80
87
|
match msgspec_convert(obj, t):
|
|
81
88
|
case Ok(value):
|
|
@@ -92,7 +99,7 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
|
|
|
92
99
|
class Decoder:
|
|
93
100
|
"""Class `Decoder` for `msgspec` module with decode hook
|
|
94
101
|
for objects with the specified type.
|
|
95
|
-
|
|
102
|
+
|
|
96
103
|
```
|
|
97
104
|
import enum
|
|
98
105
|
|
|
@@ -122,7 +129,7 @@ class Decoder:
|
|
|
122
129
|
Variative: variative_dec_hook,
|
|
123
130
|
datetime: lambda t, obj: t.fromtimestamp(obj),
|
|
124
131
|
}
|
|
125
|
-
|
|
132
|
+
|
|
126
133
|
def __repr__(self) -> str:
|
|
127
134
|
return "<{}: dec_hooks={!r}>".format(
|
|
128
135
|
self.__class__.__name__,
|
|
@@ -132,9 +139,9 @@ class Decoder:
|
|
|
132
139
|
def add_dec_hook(self, t: T): # type: ignore
|
|
133
140
|
def decorator(func: DecHook[T]) -> DecHook[T]:
|
|
134
141
|
return self.dec_hooks.setdefault(get_origin(t), func) # type: ignore
|
|
135
|
-
|
|
142
|
+
|
|
136
143
|
return decorator
|
|
137
|
-
|
|
144
|
+
|
|
138
145
|
def dec_hook(self, tp: type[typing.Any], obj: object) -> object:
|
|
139
146
|
origin_type = t if isinstance((t := get_origin(tp)), type) else type(t)
|
|
140
147
|
if origin_type not in self.dec_hooks:
|
|
@@ -143,7 +150,7 @@ class Decoder:
|
|
|
143
150
|
"You can implement decode hook for this type."
|
|
144
151
|
)
|
|
145
152
|
return self.dec_hooks[origin_type](tp, obj)
|
|
146
|
-
|
|
153
|
+
|
|
147
154
|
def convert(
|
|
148
155
|
self,
|
|
149
156
|
obj: object,
|
|
@@ -163,14 +170,12 @@ class Decoder:
|
|
|
163
170
|
builtin_types=builtin_types,
|
|
164
171
|
str_keys=str_keys,
|
|
165
172
|
)
|
|
166
|
-
|
|
173
|
+
|
|
167
174
|
@typing.overload
|
|
168
|
-
def decode(self, buf: str | bytes) -> typing.Any:
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
def decode(self, buf: str | bytes) -> typing.Any: ...
|
|
176
|
+
|
|
171
177
|
@typing.overload
|
|
172
|
-
def decode(self, buf: str | bytes, *, strict: bool = True) -> typing.Any:
|
|
173
|
-
...
|
|
178
|
+
def decode(self, buf: str | bytes, *, strict: bool = True) -> typing.Any: ...
|
|
174
179
|
|
|
175
180
|
@typing.overload
|
|
176
181
|
def decode(
|
|
@@ -179,8 +184,7 @@ class Decoder:
|
|
|
179
184
|
*,
|
|
180
185
|
type: type[T],
|
|
181
186
|
strict: bool = True,
|
|
182
|
-
) -> T:
|
|
183
|
-
...
|
|
187
|
+
) -> T: ...
|
|
184
188
|
|
|
185
189
|
def decode(
|
|
186
190
|
self,
|
|
@@ -199,7 +203,7 @@ class Decoder:
|
|
|
199
203
|
|
|
200
204
|
class Encoder:
|
|
201
205
|
"""Class `Encoder` for `msgspec` module with encode hooks for objects.
|
|
202
|
-
|
|
206
|
+
|
|
203
207
|
```
|
|
204
208
|
from datetime import datetime as dt
|
|
205
209
|
|
|
@@ -231,9 +235,9 @@ class Encoder:
|
|
|
231
235
|
def decorator(func: EncHook[T]) -> EncHook[T]:
|
|
232
236
|
encode_hook = self.enc_hooks.setdefault(get_origin(t), func)
|
|
233
237
|
return func if encode_hook is not func else encode_hook
|
|
234
|
-
|
|
238
|
+
|
|
235
239
|
return decorator
|
|
236
|
-
|
|
240
|
+
|
|
237
241
|
def enc_hook(self, obj: object) -> object:
|
|
238
242
|
origin_type = get_origin(obj.__class__)
|
|
239
243
|
if origin_type not in self.enc_hooks:
|
|
@@ -244,16 +248,13 @@ class Encoder:
|
|
|
244
248
|
return self.enc_hooks[origin_type](obj)
|
|
245
249
|
|
|
246
250
|
@typing.overload
|
|
247
|
-
def encode(self, obj: typing.Any) -> str:
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
def encode(self, obj: typing.Any) -> str: ...
|
|
252
|
+
|
|
250
253
|
@typing.overload
|
|
251
|
-
def encode(self, obj: typing.Any, *, as_str: typing.Literal[True]) -> str:
|
|
252
|
-
...
|
|
254
|
+
def encode(self, obj: typing.Any, *, as_str: typing.Literal[True]) -> str: ...
|
|
253
255
|
|
|
254
256
|
@typing.overload
|
|
255
|
-
def encode(self, obj: typing.Any, *, as_str: typing.Literal[False]) -> bytes:
|
|
256
|
-
...
|
|
257
|
+
def encode(self, obj: typing.Any, *, as_str: typing.Literal[False]) -> bytes: ...
|
|
257
258
|
|
|
258
259
|
def encode(self, obj: typing.Any, *, as_str: bool = True) -> str | bytes:
|
|
259
260
|
buf = msgspec.json.encode(obj, enc_hook=self.enc_hook)
|
|
@@ -267,14 +268,14 @@ encoder: typing.Final[Encoder] = Encoder()
|
|
|
267
268
|
__all__ = (
|
|
268
269
|
"Decoder",
|
|
269
270
|
"Encoder",
|
|
270
|
-
"Option",
|
|
271
271
|
"Nothing",
|
|
272
|
+
"Option",
|
|
273
|
+
"datetime",
|
|
274
|
+
"decoder",
|
|
275
|
+
"encoder",
|
|
272
276
|
"get_origin",
|
|
273
|
-
"repr_type",
|
|
274
277
|
"msgspec_convert",
|
|
275
278
|
"option_dec_hook",
|
|
279
|
+
"repr_type",
|
|
276
280
|
"variative_dec_hook",
|
|
277
|
-
"datetime",
|
|
278
|
-
"decoder",
|
|
279
|
-
"encoder",
|
|
280
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"<{self.__class__.__name__}: {self.value}" + (
|
|
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
|
@@ -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
|
|
@@ -49,7 +49,9 @@ class StringFormatter(string.Formatter):
|
|
|
49
49
|
)
|
|
50
50
|
return fmt
|
|
51
51
|
|
|
52
|
-
def get_spec_formatter(
|
|
52
|
+
def get_spec_formatter(
|
|
53
|
+
self, value: SpecialFormat
|
|
54
|
+
) -> typing.Callable[..., "TagFormat"]:
|
|
53
55
|
return globals()[value.__formatter_name__]
|
|
54
56
|
|
|
55
57
|
def check_formats(self, value: typing.Any, fmts: list[str]) -> "TagFormat":
|
|
@@ -78,11 +80,15 @@ class StringFormatter(string.Formatter):
|
|
|
78
80
|
with suppress(ValueError):
|
|
79
81
|
return HTMLFormatter(
|
|
80
82
|
format(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
(
|
|
84
|
+
value.formatting()
|
|
85
|
+
if isinstance(value, TagFormat)
|
|
86
|
+
else (
|
|
87
|
+
self.get_spec_formatter(value)(**value.__dict__).formatting()
|
|
88
|
+
if is_spec_format(value)
|
|
89
|
+
else value
|
|
90
|
+
)
|
|
91
|
+
),
|
|
86
92
|
fmt,
|
|
87
93
|
)
|
|
88
94
|
)
|
|
@@ -235,7 +241,9 @@ def start_bot_link(bot_id: str | int, data: str, string: str | None = None) -> T
|
|
|
235
241
|
return link(get_start_bot_link(bot_id, data), string)
|
|
236
242
|
|
|
237
243
|
|
|
238
|
-
def start_group_link(
|
|
244
|
+
def start_group_link(
|
|
245
|
+
bot_id: str | int, data: str, string: str | None = None
|
|
246
|
+
) -> TagFormat:
|
|
239
247
|
return link(get_start_group_link(bot_id, data), string)
|
|
240
248
|
|
|
241
249
|
|
|
@@ -35,7 +35,11 @@ class GlobalCtxVar(typing.Generic[T]):
|
|
|
35
35
|
|
|
36
36
|
@classmethod
|
|
37
37
|
def collect(cls, name: str, ctx_value: T | CtxVariable[T]) -> typing.Self:
|
|
38
|
-
ctx_value =
|
|
38
|
+
ctx_value = (
|
|
39
|
+
CtxVar(ctx_value)
|
|
40
|
+
if not isinstance(ctx_value, CtxVar | GlobalCtxVar)
|
|
41
|
+
else ctx_value
|
|
42
|
+
)
|
|
39
43
|
params = ctx_value.__dict__
|
|
40
44
|
params["name"] = name
|
|
41
45
|
return cls(**params)
|