telegrinder 0.3.4__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +148 -149
- telegrinder/api/__init__.py +9 -8
- telegrinder/api/api.py +101 -93
- telegrinder/api/error.py +20 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +72 -66
- telegrinder/bot/bot.py +83 -76
- telegrinder/bot/cute_types/__init__.py +19 -17
- telegrinder/bot/cute_types/base.py +184 -258
- telegrinder/bot/cute_types/callback_query.py +400 -385
- telegrinder/bot/cute_types/chat_join_request.py +62 -61
- telegrinder/bot/cute_types/chat_member_updated.py +157 -160
- telegrinder/bot/cute_types/inline_query.py +44 -43
- telegrinder/bot/cute_types/message.py +2590 -2637
- telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
- telegrinder/bot/cute_types/update.py +112 -104
- telegrinder/bot/cute_types/utils.py +62 -95
- telegrinder/bot/dispatch/__init__.py +59 -55
- telegrinder/bot/dispatch/abc.py +76 -77
- telegrinder/bot/dispatch/context.py +96 -98
- telegrinder/bot/dispatch/dispatch.py +254 -202
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +23 -24
- telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
- telegrinder/bot/dispatch/handler/base.py +57 -57
- telegrinder/bot/dispatch/handler/document_reply.py +44 -44
- telegrinder/bot/dispatch/handler/func.py +129 -135
- telegrinder/bot/dispatch/handler/media_group_reply.py +44 -43
- telegrinder/bot/dispatch/handler/message_reply.py +36 -36
- telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
- telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
- telegrinder/bot/dispatch/handler/video_reply.py +44 -44
- telegrinder/bot/dispatch/middleware/__init__.py +3 -3
- telegrinder/bot/dispatch/middleware/abc.py +97 -22
- telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
- telegrinder/bot/dispatch/process.py +151 -157
- telegrinder/bot/dispatch/return_manager/__init__.py +15 -13
- telegrinder/bot/dispatch/return_manager/abc.py +104 -108
- telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
- telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
- telegrinder/bot/dispatch/return_manager/message.py +36 -36
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -13
- telegrinder/bot/dispatch/view/abc.py +45 -41
- telegrinder/bot/dispatch/view/base.py +231 -200
- telegrinder/bot/dispatch/view/box.py +140 -129
- telegrinder/bot/dispatch/view/callback_query.py +16 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +11 -16
- telegrinder/bot/dispatch/view/chat_member.py +37 -39
- telegrinder/bot/dispatch/view/inline_query.py +16 -17
- telegrinder/bot/dispatch/view/message.py +43 -44
- telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
- telegrinder/bot/dispatch/view/raw.py +116 -114
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +14 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +59 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +20 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +251 -172
- telegrinder/bot/dispatch/waiter_machine/middleware.py +94 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +57 -68
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +139 -131
- telegrinder/bot/rules/__init__.py +85 -62
- telegrinder/bot/rules/abc.py +213 -206
- telegrinder/bot/rules/callback_data.py +122 -163
- telegrinder/bot/rules/chat_join.py +45 -43
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +33 -36
- telegrinder/bot/rules/func.py +28 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/id.py +24 -0
- telegrinder/bot/rules/inline.py +58 -56
- telegrinder/bot/rules/integer.py +21 -20
- telegrinder/bot/rules/is_from.py +127 -127
- telegrinder/bot/rules/logic.py +18 -0
- telegrinder/bot/rules/markup.py +42 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +15 -17
- telegrinder/bot/rules/message_entities.py +33 -35
- telegrinder/bot/rules/node.py +33 -27
- telegrinder/bot/rules/payload.py +81 -0
- telegrinder/bot/rules/payment_invoice.py +29 -0
- telegrinder/bot/rules/regex.py +36 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +35 -37
- telegrinder/bot/rules/text.py +38 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +17 -19
- telegrinder/bot/scenario/checkbox.py +174 -176
- telegrinder/bot/scenario/choice.py +48 -51
- telegrinder/client/__init__.py +12 -4
- telegrinder/client/abc.py +100 -75
- telegrinder/client/aiohttp.py +134 -130
- telegrinder/client/form_data.py +31 -0
- telegrinder/client/sonic.py +212 -0
- telegrinder/model.py +208 -315
- telegrinder/modules.py +239 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +478 -410
- telegrinder/node/__init__.py +86 -25
- telegrinder/node/attachment.py +163 -87
- telegrinder/node/base.py +288 -160
- telegrinder/node/callback_query.py +54 -53
- telegrinder/node/command.py +34 -33
- telegrinder/node/composer.py +163 -198
- telegrinder/node/container.py +33 -27
- telegrinder/node/either.py +82 -0
- telegrinder/node/event.py +54 -65
- telegrinder/node/file.py +51 -0
- telegrinder/node/me.py +15 -16
- telegrinder/node/payload.py +78 -0
- telegrinder/node/polymorphic.py +67 -48
- telegrinder/node/rule.py +72 -76
- telegrinder/node/scope.py +36 -38
- telegrinder/node/source.py +87 -71
- telegrinder/node/text.py +53 -41
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +36 -40
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -62
- telegrinder/tools/__init__.py +152 -93
- telegrinder/tools/adapter/__init__.py +19 -0
- telegrinder/tools/adapter/abc.py +49 -0
- telegrinder/tools/adapter/dataclass.py +56 -0
- telegrinder/{bot/rules → tools}/adapter/errors.py +5 -5
- telegrinder/{bot/rules → tools}/adapter/event.py +63 -65
- telegrinder/{bot/rules → tools}/adapter/node.py +46 -48
- telegrinder/{bot/rules → tools}/adapter/raw_event.py +27 -27
- telegrinder/{bot/rules → tools}/adapter/raw_update.py +30 -30
- telegrinder/tools/buttons.py +106 -80
- telegrinder/tools/callback_data_serilization/__init__.py +5 -0
- telegrinder/tools/callback_data_serilization/abc.py +51 -0
- telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
- telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
- telegrinder/tools/error_handler/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +30 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +179 -193
- telegrinder/tools/formatting/__init__.py +83 -63
- telegrinder/tools/formatting/deep_links.py +541 -0
- telegrinder/tools/formatting/{html.py → html_formatter.py} +266 -294
- telegrinder/tools/formatting/spec_html_formats.py +71 -117
- telegrinder/tools/functional.py +8 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +387 -412
- telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
- telegrinder/tools/i18n/__init__.py +7 -7
- telegrinder/tools/i18n/abc.py +30 -30
- telegrinder/tools/i18n/middleware/__init__.py +3 -3
- telegrinder/tools/i18n/middleware/abc.py +22 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard.py +128 -128
- telegrinder/tools/lifespan.py +105 -0
- telegrinder/tools/limited_dict.py +32 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +20 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +169 -224
- telegrinder/tools/magic.py +307 -157
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +31 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/tools/strings.py +13 -0
- telegrinder/types/__init__.py +268 -260
- telegrinder/types/enums.py +711 -701
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +5055 -4633
- telegrinder/types/objects.py +7058 -6950
- telegrinder/verification_utils.py +30 -32
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +22 -22
- telegrinder-0.4.0.dist-info/METADATA +144 -0
- telegrinder-0.4.0.dist-info/RECORD +182 -0
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
- telegrinder/bot/rules/adapter/__init__.py +0 -17
- telegrinder/bot/rules/adapter/abc.py +0 -31
- telegrinder/node/message.py +0 -14
- telegrinder/node/update.py +0 -15
- telegrinder/tools/formatting/links.py +0 -38
- telegrinder/tools/kb_set/__init__.py +0 -4
- telegrinder/tools/kb_set/base.py +0 -15
- telegrinder/tools/kb_set/yaml.py +0 -63
- telegrinder-0.3.4.dist-info/METADATA +0 -110
- telegrinder-0.3.4.dist-info/RECORD +0 -165
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import binascii
|
|
3
|
+
import dataclasses
|
|
4
|
+
import typing
|
|
5
|
+
from collections import deque
|
|
6
|
+
from contextlib import suppress
|
|
7
|
+
from functools import cached_property
|
|
8
|
+
|
|
9
|
+
import msgspec
|
|
10
|
+
from fntypes.result import Error, Ok, Result
|
|
11
|
+
|
|
12
|
+
from telegrinder.msgspec_utils import decoder, encoder, get_class_annotations
|
|
13
|
+
|
|
14
|
+
from .abc import ABCDataSerializer, ModelType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
18
|
+
class ModelParser[Model: ModelType]:
|
|
19
|
+
model_type: type[Model]
|
|
20
|
+
|
|
21
|
+
def _is_model(self, obj: typing.Any, /) -> typing.TypeGuard[ModelType]:
|
|
22
|
+
return dataclasses.is_dataclass(obj) or isinstance(obj, msgspec.Struct)
|
|
23
|
+
|
|
24
|
+
def _model_to_dict(self, model: ModelType) -> dict[str, typing.Any]:
|
|
25
|
+
if dataclasses.is_dataclass(model):
|
|
26
|
+
return dataclasses.asdict(model)
|
|
27
|
+
return msgspec.structs.asdict(model) # type: ignore
|
|
28
|
+
|
|
29
|
+
def _is_union(self, inspected_type: msgspec.inspect.Type, /) -> typing.TypeGuard[msgspec.inspect.UnionType]:
|
|
30
|
+
return isinstance(inspected_type, msgspec.inspect.UnionType)
|
|
31
|
+
|
|
32
|
+
def _is_model_type(
|
|
33
|
+
self,
|
|
34
|
+
inspected_type: msgspec.inspect.Type,
|
|
35
|
+
/,
|
|
36
|
+
) -> typing.TypeGuard[msgspec.inspect.DataclassType | msgspec.inspect.StructType]:
|
|
37
|
+
return isinstance(inspected_type, msgspec.inspect.DataclassType | msgspec.inspect.StructType)
|
|
38
|
+
|
|
39
|
+
def _is_iter_of_model(
|
|
40
|
+
self, inspected_type: msgspec.inspect.Type, /
|
|
41
|
+
) -> typing.TypeGuard[msgspec.inspect.ListType]:
|
|
42
|
+
return isinstance(
|
|
43
|
+
inspected_type,
|
|
44
|
+
msgspec.inspect.ListType | msgspec.inspect.SetType | msgspec.inspect.FrozenSetType,
|
|
45
|
+
) and self._is_model_type(inspected_type.item_type)
|
|
46
|
+
|
|
47
|
+
def _validate_annotation(self, annotation: typing.Any, /) -> tuple[type[ModelType], bool] | None:
|
|
48
|
+
is_iter_of_model = False
|
|
49
|
+
type_args: tuple[msgspec.inspect.Type, ...] | None = None
|
|
50
|
+
inspected_type = msgspec.inspect.type_info(annotation)
|
|
51
|
+
|
|
52
|
+
if self._is_union(inspected_type):
|
|
53
|
+
type_args = inspected_type.types
|
|
54
|
+
elif self._is_iter_of_model(inspected_type):
|
|
55
|
+
type_args = (inspected_type.item_type,)
|
|
56
|
+
is_iter_of_model = True
|
|
57
|
+
elif self._is_model_type(inspected_type):
|
|
58
|
+
type_args = (inspected_type,)
|
|
59
|
+
|
|
60
|
+
if type_args is not None:
|
|
61
|
+
for arg in type_args:
|
|
62
|
+
if self._is_union(arg):
|
|
63
|
+
type_args += arg.types
|
|
64
|
+
if self._is_model_type(arg):
|
|
65
|
+
return (arg.cls, is_iter_of_model)
|
|
66
|
+
if self._is_iter_of_model(arg):
|
|
67
|
+
return (arg.item_type.cls, True) # type: ignore
|
|
68
|
+
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
def parse(self, model: Model) -> list[typing.Any]:
|
|
72
|
+
"""Returns a parsed model as linked list."""
|
|
73
|
+
linked: list[typing.Any] = []
|
|
74
|
+
stack: list[typing.Any] = [(list(self._model_to_dict(model).values()), linked)]
|
|
75
|
+
|
|
76
|
+
while stack:
|
|
77
|
+
current_obj, current = stack.pop()
|
|
78
|
+
|
|
79
|
+
for item in current_obj:
|
|
80
|
+
if self._is_model(item):
|
|
81
|
+
item = self._model_to_dict(item)
|
|
82
|
+
if isinstance(item, dict):
|
|
83
|
+
new_list = []
|
|
84
|
+
current.append(new_list)
|
|
85
|
+
stack.append((list(item.values()), new_list))
|
|
86
|
+
elif isinstance(item, list | tuple):
|
|
87
|
+
new_list = []
|
|
88
|
+
current.append(new_list)
|
|
89
|
+
stack.append((item, new_list))
|
|
90
|
+
else:
|
|
91
|
+
current.append(item)
|
|
92
|
+
|
|
93
|
+
return encoder.to_builtins(linked)
|
|
94
|
+
|
|
95
|
+
def compose(self, linked: list[typing.Any]) -> dict[str, typing.Any]:
|
|
96
|
+
"""Compose linked list to dictionary based on the model class annotations `(without validation)`."""
|
|
97
|
+
root_converted_data: dict[str, typing.Any] = {}
|
|
98
|
+
stack: deque[typing.Any] = deque([(linked, self.model_type, root_converted_data)])
|
|
99
|
+
|
|
100
|
+
while stack:
|
|
101
|
+
current_data, current_model, converted_data = stack.pop()
|
|
102
|
+
|
|
103
|
+
for index, (field, annotation) in enumerate(get_class_annotations(current_model).items()):
|
|
104
|
+
obj, model_type, is_iter_of_model = current_data[index], None, False
|
|
105
|
+
|
|
106
|
+
if isinstance(obj, list) and (validated := self._validate_annotation(annotation)):
|
|
107
|
+
model_type, is_iter_of_model = validated
|
|
108
|
+
|
|
109
|
+
if model_type is not None:
|
|
110
|
+
if is_iter_of_model:
|
|
111
|
+
converted_data[field] = []
|
|
112
|
+
for item in obj:
|
|
113
|
+
new_converted_data = {}
|
|
114
|
+
converted_data[field].append(new_converted_data)
|
|
115
|
+
stack.append((item, model_type, new_converted_data))
|
|
116
|
+
else:
|
|
117
|
+
new_converted_data = {}
|
|
118
|
+
converted_data[field] = new_converted_data
|
|
119
|
+
stack.append((obj, model_type, new_converted_data))
|
|
120
|
+
else:
|
|
121
|
+
converted_data[field] = obj
|
|
122
|
+
|
|
123
|
+
return root_converted_data
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class MsgPackSerializer[Model: ModelType](ABCDataSerializer[Model]):
|
|
127
|
+
@typing.overload
|
|
128
|
+
def __init__(self, model_t: type[Model], /) -> None: ...
|
|
129
|
+
|
|
130
|
+
@typing.overload
|
|
131
|
+
def __init__(self, model_t: type[Model], /, *, ident_key: str | None = ...) -> None: ...
|
|
132
|
+
|
|
133
|
+
def __init__(self, model_t: type[Model], /, *, ident_key: str | None = None) -> None:
|
|
134
|
+
self.model_t = model_t
|
|
135
|
+
self.ident_key: str | None = ident_key or getattr(model_t, "__key__", None)
|
|
136
|
+
self._model_parser = ModelParser(model_t)
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def serialize_from_model(cls, model: Model, *, ident_key: str | None = None) -> str:
|
|
140
|
+
return cls(model.__class__, ident_key=ident_key).serialize(model)
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def deserialize_to_json(cls, serialized_data: str, model_t: type[Model]) -> Result[Model, str]:
|
|
144
|
+
return cls(model_t).deserialize(serialized_data)
|
|
145
|
+
|
|
146
|
+
@cached_property
|
|
147
|
+
def key(self) -> bytes:
|
|
148
|
+
if self.ident_key:
|
|
149
|
+
return msgspec.msgpack.encode(super().key)
|
|
150
|
+
return b""
|
|
151
|
+
|
|
152
|
+
def serialize(self, data: Model) -> str:
|
|
153
|
+
return base64.urlsafe_b64encode(
|
|
154
|
+
self.key + msgspec.msgpack.encode(self._model_parser.parse(data), enc_hook=encoder.enc_hook),
|
|
155
|
+
).decode()
|
|
156
|
+
|
|
157
|
+
def deserialize(self, serialized_data: str) -> Result[Model, str]:
|
|
158
|
+
with suppress(msgspec.DecodeError, msgspec.ValidationError, binascii.Error):
|
|
159
|
+
ser_data = base64.urlsafe_b64decode(serialized_data)
|
|
160
|
+
if self.ident_key and not ser_data.startswith(self.key):
|
|
161
|
+
return Error("Data is not corresponding to key.")
|
|
162
|
+
|
|
163
|
+
data: list[typing.Any] = msgspec.msgpack.decode(
|
|
164
|
+
ser_data.removeprefix(self.key),
|
|
165
|
+
dec_hook=decoder.dec_hook(),
|
|
166
|
+
)
|
|
167
|
+
return Ok(decoder.convert(self._model_parser.compose(data), type=self.model_t))
|
|
168
|
+
|
|
169
|
+
return Error("Incorrect data.")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
__all__ = ("MsgPackSerializer",)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
from .abc import ABCErrorHandler
|
|
2
|
-
from .error import CatcherError
|
|
3
|
-
from .error_handler import Catcher, ErrorHandler
|
|
4
|
-
|
|
5
|
-
__all__ = (
|
|
1
|
+
from .abc import ABCErrorHandler
|
|
2
|
+
from .error import CatcherError
|
|
3
|
+
from .error_handler import Catcher, ErrorHandler
|
|
4
|
+
|
|
5
|
+
__all__ = (
|
|
6
6
|
"ABCErrorHandler",
|
|
7
7
|
"Catcher",
|
|
8
8
|
"CatcherError",
|
|
9
|
-
"ErrorHandler",
|
|
10
|
-
)
|
|
9
|
+
"ErrorHandler",
|
|
10
|
+
)
|
|
@@ -1,33 +1,30 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
from abc import ABC, abstractmethod
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
__all__ = ("ABCErrorHandler",)
|
|
1
|
+
import typing
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
from telegrinder.api import API
|
|
5
|
+
from telegrinder.bot.dispatch.context import Context
|
|
6
|
+
|
|
7
|
+
type Handler = typing.Callable[..., typing.Awaitable[typing.Any]]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ABCErrorHandler[Event](ABC):
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def __call__(
|
|
13
|
+
self,
|
|
14
|
+
*args: typing.Any,
|
|
15
|
+
**kwargs: typing.Any,
|
|
16
|
+
) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Callable[..., typing.Any]]:
|
|
17
|
+
"""Decorator for registering callback as a catcher for the error handler."""
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
async def run(
|
|
21
|
+
self,
|
|
22
|
+
exception: BaseException,
|
|
23
|
+
event: Event,
|
|
24
|
+
api: API,
|
|
25
|
+
ctx: Context,
|
|
26
|
+
) -> typing.Any:
|
|
27
|
+
"""Run the error handler."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
__all__ = ("ABCErrorHandler",)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
class CatcherError(BaseException):
|
|
2
|
-
__slots__ = ("exc", "message")
|
|
3
|
-
|
|
4
|
-
def __init__(self, exc: BaseException, message: str) -> None:
|
|
5
|
-
self.exc = exc
|
|
6
|
-
self.message = message
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
__all__ = ("CatcherError",)
|
|
1
|
+
class CatcherError(BaseException):
|
|
2
|
+
__slots__ = ("exc", "message")
|
|
3
|
+
|
|
4
|
+
def __init__(self, exc: BaseException, message: str) -> None:
|
|
5
|
+
self.exc = exc
|
|
6
|
+
self.message = message
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
__all__ = ("CatcherError",)
|
|
@@ -1,193 +1,179 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
from fntypes.result import Error, Ok, Result
|
|
5
|
-
|
|
6
|
-
from telegrinder.api.api import API
|
|
7
|
-
from telegrinder.bot.dispatch.context import Context
|
|
8
|
-
from telegrinder.modules import logger
|
|
9
|
-
from telegrinder.
|
|
10
|
-
from telegrinder.tools.error_handler.
|
|
11
|
-
from telegrinder.tools.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def
|
|
92
|
-
self
|
|
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
|
-
return
|
|
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
|
-
"Catcher {!r} returned: {!r}",
|
|
181
|
-
self.catcher.func.__name__,
|
|
182
|
-
value,
|
|
183
|
-
)
|
|
184
|
-
return ok
|
|
185
|
-
case Error(exc) as err:
|
|
186
|
-
if isinstance(exc, CatcherError):
|
|
187
|
-
return self._process_catcher_error(exc)
|
|
188
|
-
if self.catcher.ignore_errors:
|
|
189
|
-
return Ok(None)
|
|
190
|
-
return err
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
__all__ = ("Catcher", "ErrorHandler")
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from fntypes.result import Error, Ok, Result
|
|
5
|
+
|
|
6
|
+
from telegrinder.api.api import API
|
|
7
|
+
from telegrinder.bot.dispatch.context import Context
|
|
8
|
+
from telegrinder.modules import logger
|
|
9
|
+
from telegrinder.tools.error_handler.abc import ABCErrorHandler
|
|
10
|
+
from telegrinder.tools.error_handler.error import CatcherError
|
|
11
|
+
from telegrinder.tools.magic import magic_bundle
|
|
12
|
+
|
|
13
|
+
type FuncCatcher[Exc: BaseException] = typing.Callable[
|
|
14
|
+
typing.Concatenate[Exc, ...],
|
|
15
|
+
typing.Awaitable[typing.Any],
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclasses.dataclass(frozen=True, repr=False, slots=True)
|
|
20
|
+
class Catcher[Event]:
|
|
21
|
+
func: FuncCatcher[BaseException]
|
|
22
|
+
exceptions: list[type[BaseException] | BaseException] = dataclasses.field(
|
|
23
|
+
default_factory=lambda: [],
|
|
24
|
+
kw_only=True,
|
|
25
|
+
)
|
|
26
|
+
logging: bool = dataclasses.field(default=False, kw_only=True)
|
|
27
|
+
raise_exception: bool = dataclasses.field(default=False, kw_only=True)
|
|
28
|
+
ignore_errors: bool = dataclasses.field(default=False, kw_only=True)
|
|
29
|
+
|
|
30
|
+
def __repr__(self) -> str:
|
|
31
|
+
return "<Catcher: function={!r}, logging={}, raise_exception={}, ignore_errors={}>".format(
|
|
32
|
+
self.func.__name__,
|
|
33
|
+
self.logging,
|
|
34
|
+
self.raise_exception,
|
|
35
|
+
self.ignore_errors,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
async def __call__(
|
|
39
|
+
self,
|
|
40
|
+
exception: BaseException,
|
|
41
|
+
event: Event,
|
|
42
|
+
api: API,
|
|
43
|
+
ctx: Context,
|
|
44
|
+
) -> Result[typing.Any, BaseException]:
|
|
45
|
+
return await self.run(api, event, ctx, exception)
|
|
46
|
+
|
|
47
|
+
def match_exception(self, exception: BaseException) -> bool:
|
|
48
|
+
for exc in self.exceptions:
|
|
49
|
+
if isinstance(exc, type) and type(exception) is exc:
|
|
50
|
+
return True
|
|
51
|
+
if isinstance(exc, object) and type(exception) is type(exc):
|
|
52
|
+
return True if not exc.args else exc.args == exception.args
|
|
53
|
+
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
async def run(
|
|
57
|
+
self,
|
|
58
|
+
api: API,
|
|
59
|
+
event: Event,
|
|
60
|
+
ctx: Context,
|
|
61
|
+
exception: BaseException,
|
|
62
|
+
) -> Result[typing.Any, BaseException]:
|
|
63
|
+
if self.match_exception(exception):
|
|
64
|
+
logger.debug(
|
|
65
|
+
"Error handler caught an exception {!r}, running catcher {!r}...".format(
|
|
66
|
+
exception,
|
|
67
|
+
self.func.__name__,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
return Ok(await self.func(exception, **magic_bundle(self.func, {"event": event, "api": api} | ctx)))
|
|
71
|
+
|
|
72
|
+
logger.debug("Failed to match exception {!r}.", exception.__class__.__name__)
|
|
73
|
+
return Error(exception)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ErrorHandler[Event](ABCErrorHandler[Event]):
|
|
77
|
+
def __init__(self, catcher: Catcher[Event] | None = None, /) -> None:
|
|
78
|
+
self.catcher = catcher
|
|
79
|
+
|
|
80
|
+
def __repr__(self) -> str:
|
|
81
|
+
return (
|
|
82
|
+
"<{}: exceptions=[{}], catcher={!r}>".format(
|
|
83
|
+
self.__class__.__name__,
|
|
84
|
+
", ".join(e.__name__ if isinstance(e, type) else repr(e) for e in self.catcher.exceptions),
|
|
85
|
+
self.catcher,
|
|
86
|
+
)
|
|
87
|
+
if self.catcher is not None
|
|
88
|
+
else "<{}()>".format(self.__class__.__name__)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def __call__(
|
|
92
|
+
self,
|
|
93
|
+
*exceptions: type[BaseException] | BaseException,
|
|
94
|
+
logging: bool = False,
|
|
95
|
+
raise_exception: bool = False,
|
|
96
|
+
ignore_errors: bool = False,
|
|
97
|
+
):
|
|
98
|
+
"""Register the catcher.
|
|
99
|
+
|
|
100
|
+
:param logging: Logging the result of the catcher at the level `DEBUG`.
|
|
101
|
+
:param raise_exception: Raise an exception if the catcher has not started.
|
|
102
|
+
:param ignore_errors: Ignore errors that may occur.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def decorator[Func: FuncCatcher](catcher: Func, /) -> Func:
|
|
106
|
+
if not self.catcher:
|
|
107
|
+
self.catcher = Catcher(
|
|
108
|
+
catcher,
|
|
109
|
+
exceptions=list(exceptions),
|
|
110
|
+
logging=logging,
|
|
111
|
+
raise_exception=raise_exception,
|
|
112
|
+
ignore_errors=ignore_errors,
|
|
113
|
+
)
|
|
114
|
+
return catcher
|
|
115
|
+
|
|
116
|
+
return decorator
|
|
117
|
+
|
|
118
|
+
def _process_catcher_error(self, error: CatcherError) -> Result[None, BaseException]:
|
|
119
|
+
assert self.catcher is not None
|
|
120
|
+
|
|
121
|
+
if self.catcher.raise_exception:
|
|
122
|
+
raise error.exc from None
|
|
123
|
+
if self.catcher.logging:
|
|
124
|
+
logger.error(error.message)
|
|
125
|
+
if not self.catcher.ignore_errors:
|
|
126
|
+
return Error(error.exc)
|
|
127
|
+
|
|
128
|
+
return Ok(None)
|
|
129
|
+
|
|
130
|
+
async def suppress(
|
|
131
|
+
self,
|
|
132
|
+
exception: BaseException,
|
|
133
|
+
event: Event,
|
|
134
|
+
api: API,
|
|
135
|
+
ctx: Context,
|
|
136
|
+
) -> Result[typing.Any, BaseException]:
|
|
137
|
+
assert self.catcher is not None
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
return await self.catcher(exception, event, api, ctx)
|
|
141
|
+
except BaseException as exc:
|
|
142
|
+
return Error(
|
|
143
|
+
CatcherError(
|
|
144
|
+
exc,
|
|
145
|
+
"{!r} was occurred during the running catcher {!r}.".format(
|
|
146
|
+
exc,
|
|
147
|
+
self.catcher.func.__name__,
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
async def run(
|
|
153
|
+
self,
|
|
154
|
+
exception: BaseException,
|
|
155
|
+
event: Event,
|
|
156
|
+
api: API,
|
|
157
|
+
ctx: Context,
|
|
158
|
+
) -> typing.Any:
|
|
159
|
+
if not self.catcher:
|
|
160
|
+
raise exception from None
|
|
161
|
+
|
|
162
|
+
match await self.suppress(exception, event, api, ctx):
|
|
163
|
+
case Ok(value):
|
|
164
|
+
if self.catcher.logging:
|
|
165
|
+
logger.debug(
|
|
166
|
+
"Catcher {!r} returned: {!r}",
|
|
167
|
+
self.catcher.func.__name__,
|
|
168
|
+
value,
|
|
169
|
+
)
|
|
170
|
+
return value
|
|
171
|
+
case Error(exc):
|
|
172
|
+
if isinstance(exc, CatcherError):
|
|
173
|
+
return self._process_catcher_error(exc).unwrap()
|
|
174
|
+
if self.catcher.ignore_errors:
|
|
175
|
+
return None
|
|
176
|
+
raise exc from None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
__all__ = ("Catcher", "ErrorHandler")
|