telegrinder 0.3.4.post1__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 +30 -31
- telegrinder/api/__init__.py +2 -1
- telegrinder/api/api.py +28 -20
- telegrinder/api/error.py +8 -4
- telegrinder/api/response.py +2 -2
- telegrinder/api/token.py +2 -2
- telegrinder/bot/__init__.py +6 -0
- telegrinder/bot/bot.py +38 -31
- telegrinder/bot/cute_types/__init__.py +2 -0
- telegrinder/bot/cute_types/base.py +54 -128
- telegrinder/bot/cute_types/callback_query.py +76 -61
- telegrinder/bot/cute_types/chat_join_request.py +4 -3
- telegrinder/bot/cute_types/chat_member_updated.py +28 -31
- telegrinder/bot/cute_types/inline_query.py +5 -4
- telegrinder/bot/cute_types/message.py +555 -602
- telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
- telegrinder/bot/cute_types/update.py +20 -12
- telegrinder/bot/cute_types/utils.py +3 -36
- telegrinder/bot/dispatch/__init__.py +4 -0
- telegrinder/bot/dispatch/abc.py +8 -9
- telegrinder/bot/dispatch/context.py +5 -7
- telegrinder/bot/dispatch/dispatch.py +85 -33
- telegrinder/bot/dispatch/handler/abc.py +5 -6
- telegrinder/bot/dispatch/handler/audio_reply.py +2 -2
- telegrinder/bot/dispatch/handler/base.py +3 -3
- telegrinder/bot/dispatch/handler/document_reply.py +2 -2
- telegrinder/bot/dispatch/handler/func.py +36 -42
- telegrinder/bot/dispatch/handler/media_group_reply.py +5 -4
- telegrinder/bot/dispatch/handler/message_reply.py +2 -2
- telegrinder/bot/dispatch/handler/photo_reply.py +2 -2
- telegrinder/bot/dispatch/handler/sticker_reply.py +2 -2
- telegrinder/bot/dispatch/handler/video_reply.py +2 -2
- telegrinder/bot/dispatch/middleware/abc.py +83 -8
- telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
- telegrinder/bot/dispatch/process.py +44 -50
- telegrinder/bot/dispatch/return_manager/__init__.py +2 -0
- telegrinder/bot/dispatch/return_manager/abc.py +6 -10
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
- telegrinder/bot/dispatch/view/__init__.py +2 -0
- telegrinder/bot/dispatch/view/abc.py +10 -6
- telegrinder/bot/dispatch/view/base.py +81 -50
- telegrinder/bot/dispatch/view/box.py +20 -9
- telegrinder/bot/dispatch/view/callback_query.py +3 -4
- telegrinder/bot/dispatch/view/chat_join_request.py +2 -7
- telegrinder/bot/dispatch/view/chat_member.py +3 -5
- telegrinder/bot/dispatch/view/inline_query.py +3 -4
- telegrinder/bot/dispatch/view/message.py +3 -4
- telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
- telegrinder/bot/dispatch/view/raw.py +42 -40
- telegrinder/bot/dispatch/waiter_machine/actions.py +5 -4
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +9 -7
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +3 -2
- telegrinder/bot/dispatch/waiter_machine/machine.py +113 -34
- telegrinder/bot/dispatch/waiter_machine/middleware.py +15 -10
- telegrinder/bot/dispatch/waiter_machine/short_state.py +7 -18
- telegrinder/bot/polling/polling.py +62 -54
- telegrinder/bot/rules/__init__.py +24 -1
- telegrinder/bot/rules/abc.py +17 -10
- telegrinder/bot/rules/callback_data.py +20 -61
- telegrinder/bot/rules/chat_join.py +6 -4
- telegrinder/bot/rules/command.py +4 -4
- telegrinder/bot/rules/enum_text.py +1 -4
- telegrinder/bot/rules/func.py +5 -3
- telegrinder/bot/rules/fuzzy.py +1 -1
- telegrinder/bot/rules/id.py +24 -0
- telegrinder/bot/rules/inline.py +6 -4
- telegrinder/bot/rules/integer.py +2 -1
- telegrinder/bot/rules/logic.py +18 -0
- telegrinder/bot/rules/markup.py +5 -6
- telegrinder/bot/rules/message.py +2 -4
- telegrinder/bot/rules/message_entities.py +1 -3
- telegrinder/bot/rules/node.py +15 -9
- telegrinder/bot/rules/payload.py +81 -0
- telegrinder/bot/rules/payment_invoice.py +29 -0
- telegrinder/bot/rules/regex.py +5 -6
- telegrinder/bot/rules/state.py +1 -3
- telegrinder/bot/rules/text.py +10 -5
- telegrinder/bot/rules/update.py +0 -0
- telegrinder/bot/scenario/abc.py +2 -4
- telegrinder/bot/scenario/checkbox.py +12 -14
- telegrinder/bot/scenario/choice.py +6 -9
- telegrinder/client/__init__.py +9 -1
- telegrinder/client/abc.py +35 -10
- telegrinder/client/aiohttp.py +28 -24
- telegrinder/client/form_data.py +31 -0
- telegrinder/client/sonic.py +212 -0
- telegrinder/model.py +38 -145
- telegrinder/modules.py +3 -1
- telegrinder/msgspec_utils.py +136 -68
- telegrinder/node/__init__.py +74 -13
- telegrinder/node/attachment.py +92 -16
- telegrinder/node/base.py +196 -68
- telegrinder/node/callback_query.py +17 -16
- telegrinder/node/command.py +3 -2
- telegrinder/node/composer.py +40 -75
- telegrinder/node/container.py +13 -7
- telegrinder/node/either.py +82 -0
- telegrinder/node/event.py +20 -31
- telegrinder/node/file.py +51 -0
- telegrinder/node/me.py +4 -5
- telegrinder/node/payload.py +78 -0
- telegrinder/node/polymorphic.py +27 -8
- telegrinder/node/rule.py +2 -6
- telegrinder/node/scope.py +4 -6
- telegrinder/node/source.py +37 -21
- telegrinder/node/text.py +20 -8
- telegrinder/node/tools/generator.py +7 -11
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +0 -61
- telegrinder/tools/__init__.py +97 -38
- 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/event.py +8 -10
- telegrinder/{bot/rules → tools}/adapter/node.py +8 -10
- telegrinder/{bot/rules → tools}/adapter/raw_event.py +2 -2
- telegrinder/{bot/rules → tools}/adapter/raw_update.py +2 -2
- telegrinder/tools/buttons.py +52 -26
- 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/abc.py +4 -7
- telegrinder/tools/error_handler/error.py +0 -0
- telegrinder/tools/error_handler/error_handler.py +34 -48
- telegrinder/tools/formatting/__init__.py +57 -37
- telegrinder/tools/formatting/deep_links.py +541 -0
- telegrinder/tools/formatting/{html.py → html_formatter.py} +51 -79
- telegrinder/tools/formatting/spec_html_formats.py +14 -60
- telegrinder/tools/functional.py +1 -5
- telegrinder/tools/global_context/global_context.py +26 -51
- telegrinder/tools/global_context/telegrinder_ctx.py +3 -3
- telegrinder/tools/i18n/abc.py +0 -0
- telegrinder/tools/i18n/middleware/abc.py +3 -6
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard.py +9 -9
- telegrinder/tools/lifespan.py +105 -0
- telegrinder/tools/limited_dict.py +5 -10
- telegrinder/tools/loop_wrapper/abc.py +7 -2
- telegrinder/tools/loop_wrapper/loop_wrapper.py +40 -95
- telegrinder/tools/magic.py +184 -34
- telegrinder/tools/state_storage/__init__.py +0 -0
- telegrinder/tools/state_storage/abc.py +5 -9
- telegrinder/tools/state_storage/memory.py +1 -1
- telegrinder/tools/strings.py +13 -0
- telegrinder/types/__init__.py +8 -0
- telegrinder/types/enums.py +31 -21
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +531 -109
- telegrinder/types/objects.py +934 -826
- telegrinder/verification_utils.py +0 -2
- {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +2 -2
- telegrinder-0.4.0.dist-info/METADATA +144 -0
- telegrinder-0.4.0.dist-info/RECORD +182 -0
- {telegrinder-0.3.4.post1.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.post1.dist-info/METADATA +0 -110
- telegrinder-0.3.4.post1.dist-info/RECORD +0 -165
- /telegrinder/{bot/rules → tools}/adapter/errors.py +0 -0
telegrinder/tools/magic.py
CHANGED
|
@@ -1,27 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import dataclasses
|
|
1
5
|
import enum
|
|
6
|
+
import inspect
|
|
2
7
|
import types
|
|
3
8
|
import typing
|
|
9
|
+
from collections import OrderedDict
|
|
4
10
|
from functools import wraps
|
|
5
11
|
|
|
12
|
+
from fntypes import Result
|
|
13
|
+
|
|
14
|
+
from telegrinder.model import get_params
|
|
15
|
+
|
|
6
16
|
if typing.TYPE_CHECKING:
|
|
7
17
|
from telegrinder.bot.rules.abc import ABCRule
|
|
8
18
|
from telegrinder.node.polymorphic import Polymorphic
|
|
9
19
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"F",
|
|
13
|
-
bound=typing.Callable[typing.Concatenate[typing.Callable[..., typing.Any], ...], typing.Any],
|
|
14
|
-
)
|
|
20
|
+
type Impl = type[classmethod]
|
|
21
|
+
type FuncType = types.FunctionType | typing.Callable[..., typing.Any]
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
type Executor[T] = typing.Callable[
|
|
24
|
+
[T, str, dict[str, typing.Any]],
|
|
25
|
+
typing.Awaitable[Result[typing.Any, typing.Any]],
|
|
26
|
+
]
|
|
18
27
|
|
|
19
28
|
TRANSLATIONS_KEY: typing.Final[str] = "_translations"
|
|
20
29
|
IMPL_MARK: typing.Final[str] = "_is_impl"
|
|
21
30
|
|
|
22
31
|
|
|
32
|
+
@dataclasses.dataclass(slots=True, frozen=True)
|
|
33
|
+
class Shortcut[T]:
|
|
34
|
+
method_name: str
|
|
35
|
+
executor: Executor[T] | None = dataclasses.field(default=None, kw_only=True)
|
|
36
|
+
custom_params: set[str] = dataclasses.field(default_factory=lambda: set(), kw_only=True)
|
|
37
|
+
|
|
38
|
+
|
|
23
39
|
def cache_magic_value(mark_key: str, /):
|
|
24
|
-
def inner(func:
|
|
40
|
+
def inner[Func: typing.Callable[..., typing.Any]](func: Func) -> Func:
|
|
25
41
|
@wraps(func)
|
|
26
42
|
def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
|
|
27
43
|
if mark_key not in args[0].__dict__:
|
|
@@ -50,10 +66,31 @@ def get_default_args(func: FuncType) -> dict[str, typing.Any]:
|
|
|
50
66
|
return {k: defaults[i] for i, k in enumerate(resolve_arg_names(func, start_idx=0)[-len(defaults) :])}
|
|
51
67
|
|
|
52
68
|
|
|
69
|
+
@cache_magic_value("__func_parameters__")
|
|
70
|
+
def get_func_parameters(func: FuncType, /) -> FuncParams:
|
|
71
|
+
func_params: FuncParams = {"args": [], "kwargs": []}
|
|
72
|
+
|
|
73
|
+
for k, p in inspect.signature(func).parameters.items():
|
|
74
|
+
if k in ("self", "cls"):
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
match p.kind:
|
|
78
|
+
case p.POSITIONAL_OR_KEYWORD | p.POSITIONAL_ONLY:
|
|
79
|
+
func_params["args"].append((k, p.default))
|
|
80
|
+
case p.KEYWORD_ONLY:
|
|
81
|
+
func_params["kwargs"].append((k, p.default))
|
|
82
|
+
case p.VAR_POSITIONAL:
|
|
83
|
+
func_params["var_args"] = k
|
|
84
|
+
case p.VAR_KEYWORD:
|
|
85
|
+
func_params["var_kwargs"] = k
|
|
86
|
+
|
|
87
|
+
return func_params
|
|
88
|
+
|
|
89
|
+
|
|
53
90
|
def get_annotations(func: FuncType, *, return_type: bool = False) -> dict[str, typing.Any]:
|
|
54
91
|
annotations = func.__annotations__
|
|
55
|
-
if not return_type
|
|
56
|
-
annotations.pop("return")
|
|
92
|
+
if not return_type:
|
|
93
|
+
annotations.pop("return", None)
|
|
57
94
|
return annotations
|
|
58
95
|
|
|
59
96
|
|
|
@@ -64,16 +101,16 @@ def to_str(s: str | enum.Enum) -> str:
|
|
|
64
101
|
|
|
65
102
|
|
|
66
103
|
@typing.overload
|
|
67
|
-
def magic_bundle(
|
|
104
|
+
def magic_bundle(function: FuncType, kw: dict[str, typing.Any]) -> dict[str, typing.Any]: ...
|
|
68
105
|
|
|
69
106
|
|
|
70
107
|
@typing.overload
|
|
71
|
-
def magic_bundle(
|
|
108
|
+
def magic_bundle(function: FuncType, kw: dict[enum.Enum, typing.Any]) -> dict[str, typing.Any]: ...
|
|
72
109
|
|
|
73
110
|
|
|
74
111
|
@typing.overload
|
|
75
112
|
def magic_bundle(
|
|
76
|
-
|
|
113
|
+
function: FuncType,
|
|
77
114
|
kw: dict[str, typing.Any],
|
|
78
115
|
*,
|
|
79
116
|
start_idx: int = 1,
|
|
@@ -83,7 +120,7 @@ def magic_bundle(
|
|
|
83
120
|
|
|
84
121
|
@typing.overload
|
|
85
122
|
def magic_bundle(
|
|
86
|
-
|
|
123
|
+
function: FuncType,
|
|
87
124
|
kw: dict[enum.Enum, typing.Any],
|
|
88
125
|
*,
|
|
89
126
|
start_idx: int = 1,
|
|
@@ -93,41 +130,54 @@ def magic_bundle(
|
|
|
93
130
|
|
|
94
131
|
@typing.overload
|
|
95
132
|
def magic_bundle(
|
|
96
|
-
|
|
97
|
-
kw: dict[type, typing.Any],
|
|
133
|
+
function: FuncType,
|
|
134
|
+
kw: dict[type[typing.Any], typing.Any],
|
|
98
135
|
*,
|
|
99
136
|
typebundle: typing.Literal[True] = True,
|
|
100
137
|
) -> dict[str, typing.Any]: ...
|
|
101
138
|
|
|
102
139
|
|
|
103
140
|
def magic_bundle(
|
|
104
|
-
|
|
141
|
+
function: FuncType,
|
|
105
142
|
kw: dict[typing.Any, typing.Any],
|
|
106
143
|
*,
|
|
107
144
|
start_idx: int = 1,
|
|
108
145
|
bundle_ctx: bool = True,
|
|
109
146
|
typebundle: bool = False,
|
|
110
147
|
) -> dict[str, typing.Any]:
|
|
148
|
+
# Bundle considering the function annotations
|
|
111
149
|
if typebundle:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
args
|
|
150
|
+
return {name: kw[t] for name, t in get_annotations(function, return_type=False).items() if t in kw}
|
|
151
|
+
|
|
152
|
+
names = resolve_arg_names(function, start_idx=start_idx)
|
|
153
|
+
# Determine if the function is only have the **kwargs parameter, then bundle all kw
|
|
154
|
+
if "var_kwargs" in get_func_parameters(function) and not names:
|
|
155
|
+
return {to_str(k): v for k, v in kw.items()}
|
|
156
|
+
|
|
157
|
+
# Bundle considering the function parameters and defaults
|
|
158
|
+
args = get_default_args(function) | {n: v for k, v in kw.items() if (n := to_str(k)) in names}
|
|
121
159
|
if "ctx" in names and bundle_ctx:
|
|
122
160
|
args["ctx"] = kw
|
|
161
|
+
|
|
123
162
|
return args
|
|
124
163
|
|
|
125
164
|
|
|
126
|
-
def
|
|
165
|
+
def join_dicts[Key: typing.Hashable, Value](
|
|
166
|
+
left_dict: dict[Key, typing.Any],
|
|
167
|
+
right_dict: dict[typing.Any, Value],
|
|
168
|
+
) -> dict[Key, Value]:
|
|
169
|
+
return {key: right_dict[type_key] for key, type_key in left_dict.items()}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_cached_translation[Rule: ABCRule](rule: Rule, locale: str) -> Rule | None:
|
|
127
173
|
return getattr(rule, TRANSLATIONS_KEY, {}).get(locale)
|
|
128
174
|
|
|
129
175
|
|
|
130
|
-
def cache_translation
|
|
176
|
+
def cache_translation[Rule: ABCRule](
|
|
177
|
+
base_rule: Rule,
|
|
178
|
+
locale: str,
|
|
179
|
+
translated_rule: Rule,
|
|
180
|
+
) -> None:
|
|
131
181
|
translations = getattr(base_rule, TRANSLATIONS_KEY, {})
|
|
132
182
|
translations[locale] = translated_rule
|
|
133
183
|
setattr(base_rule, TRANSLATIONS_KEY, translations)
|
|
@@ -139,30 +189,130 @@ def impl(method: typing.Callable[..., typing.Any]):
|
|
|
139
189
|
return classmethod(method)
|
|
140
190
|
|
|
141
191
|
|
|
142
|
-
def get_impls(cls: type[
|
|
192
|
+
def get_impls(cls: type[Polymorphic]) -> list[typing.Callable[..., typing.Any]]:
|
|
143
193
|
moprh_impls = getattr(cls, "__morph_impls__", None)
|
|
144
194
|
if moprh_impls is not None:
|
|
145
195
|
return moprh_impls
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
196
|
+
|
|
197
|
+
impls = []
|
|
198
|
+
for cls_ in cls.mro():
|
|
199
|
+
impls += [
|
|
200
|
+
func.__func__
|
|
201
|
+
for func in vars(cls_).values()
|
|
202
|
+
if isinstance(func, classmethod) and getattr(func.__func__, IMPL_MARK, False)
|
|
203
|
+
]
|
|
204
|
+
|
|
151
205
|
setattr(cls, "__morph_impls__", impls)
|
|
152
206
|
return impls
|
|
153
207
|
|
|
154
208
|
|
|
209
|
+
class FuncParams(typing.TypedDict, total=True):
|
|
210
|
+
args: list[tuple[str, typing.Any | inspect.Parameter.empty]]
|
|
211
|
+
kwargs: list[tuple[str, typing.Any | inspect.Parameter.empty]]
|
|
212
|
+
var_args: typing.NotRequired[str]
|
|
213
|
+
var_kwargs: typing.NotRequired[str]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def shortcut[T](
|
|
217
|
+
method_name: str,
|
|
218
|
+
*,
|
|
219
|
+
executor: Executor[T] | None = None,
|
|
220
|
+
custom_params: set[str] | None = None,
|
|
221
|
+
):
|
|
222
|
+
"""Decorate a cute method as a shortcut."""
|
|
223
|
+
|
|
224
|
+
def wrapper[F: typing.Callable[..., typing.Any]](func: F) -> F:
|
|
225
|
+
@wraps(func)
|
|
226
|
+
async def inner(
|
|
227
|
+
self: T,
|
|
228
|
+
*args: typing.Any,
|
|
229
|
+
**kwargs: typing.Any,
|
|
230
|
+
) -> typing.Any:
|
|
231
|
+
if executor is None:
|
|
232
|
+
return await func(self, *args, **kwargs)
|
|
233
|
+
|
|
234
|
+
params: dict[str, typing.Any] = OrderedDict()
|
|
235
|
+
func_params = get_func_parameters(func)
|
|
236
|
+
|
|
237
|
+
for index, (arg, default) in enumerate(func_params["args"]):
|
|
238
|
+
if len(args) > index:
|
|
239
|
+
params[arg] = args[index]
|
|
240
|
+
elif default is not inspect.Parameter.empty:
|
|
241
|
+
params[arg] = default
|
|
242
|
+
|
|
243
|
+
if var_args := func_params.get("var_args"):
|
|
244
|
+
params[var_args] = args[len(func_params["args"]) :]
|
|
245
|
+
|
|
246
|
+
for kwarg, default in func_params["kwargs"]:
|
|
247
|
+
params[kwarg] = (
|
|
248
|
+
kwargs.pop(kwarg, default) if default is not inspect.Parameter.empty else kwargs.pop(kwarg)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if var_kwargs := func_params.get("var_kwargs"):
|
|
252
|
+
params[var_kwargs] = kwargs.copy()
|
|
253
|
+
|
|
254
|
+
return await executor(self, method_name, get_params(params))
|
|
255
|
+
|
|
256
|
+
inner.__shortcut__ = Shortcut( # type: ignore
|
|
257
|
+
method_name=method_name,
|
|
258
|
+
executor=executor,
|
|
259
|
+
custom_params=custom_params or set(),
|
|
260
|
+
)
|
|
261
|
+
return inner # type: ignore
|
|
262
|
+
|
|
263
|
+
return wrapper
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# Source code: https://github.com/facebookincubator/later/blob/main/later/task.py#L75
|
|
267
|
+
async def cancel_future(fut: asyncio.Future[typing.Any], /) -> None:
|
|
268
|
+
if fut.done():
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
fut.cancel()
|
|
272
|
+
exc: asyncio.CancelledError | None = None
|
|
273
|
+
|
|
274
|
+
while not fut.done():
|
|
275
|
+
shielded = asyncio.shield(fut)
|
|
276
|
+
try:
|
|
277
|
+
await asyncio.wait([shielded])
|
|
278
|
+
except asyncio.CancelledError as ex:
|
|
279
|
+
exc = ex
|
|
280
|
+
finally:
|
|
281
|
+
# Insure we handle the exception/value that may exist on the shielded task
|
|
282
|
+
# This will prevent errors logged to the asyncio logger
|
|
283
|
+
if shielded.done() and not shielded.cancelled() and not shielded.exception():
|
|
284
|
+
shielded.result()
|
|
285
|
+
|
|
286
|
+
if fut.cancelled():
|
|
287
|
+
if exc is None:
|
|
288
|
+
return
|
|
289
|
+
raise exc from None
|
|
290
|
+
|
|
291
|
+
ex = fut.exception()
|
|
292
|
+
if ex is not None:
|
|
293
|
+
raise ex from None
|
|
294
|
+
|
|
295
|
+
raise asyncio.InvalidStateError(
|
|
296
|
+
f"Task did not raise CancelledError on cancel: {fut!r} had result {fut.result()!r}",
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
155
300
|
__all__ = (
|
|
301
|
+
"Shortcut",
|
|
156
302
|
"TRANSLATIONS_KEY",
|
|
157
303
|
"cache_magic_value",
|
|
158
304
|
"cache_translation",
|
|
305
|
+
"cancel_future",
|
|
159
306
|
"get_annotations",
|
|
160
307
|
"get_cached_translation",
|
|
161
308
|
"get_default_args",
|
|
162
309
|
"get_default_args",
|
|
310
|
+
"get_func_parameters",
|
|
163
311
|
"get_impls",
|
|
164
312
|
"impl",
|
|
313
|
+
"join_dicts",
|
|
165
314
|
"magic_bundle",
|
|
166
315
|
"resolve_arg_names",
|
|
316
|
+
"shortcut",
|
|
167
317
|
"to_str",
|
|
168
318
|
)
|
|
File without changes
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
import abc
|
|
2
|
+
import dataclasses
|
|
2
3
|
import enum
|
|
3
|
-
import typing
|
|
4
|
-
from dataclasses import dataclass
|
|
5
4
|
|
|
6
|
-
from fntypes import Option
|
|
5
|
+
from fntypes.option import Option
|
|
7
6
|
|
|
8
7
|
from telegrinder.bot.rules.state import State, StateMeta
|
|
9
8
|
|
|
10
|
-
Payload = typing.TypeVar("Payload")
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class StateData(typing.Generic[Payload]):
|
|
10
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
11
|
+
class StateData[Payload]:
|
|
15
12
|
key: str | enum.Enum
|
|
16
13
|
payload: Payload
|
|
17
14
|
|
|
18
15
|
|
|
19
|
-
class ABCStateStorage(abc.ABC
|
|
16
|
+
class ABCStateStorage[Payload](abc.ABC):
|
|
20
17
|
@abc.abstractmethod
|
|
21
18
|
async def get(self, user_id: int) -> Option[StateData[Payload]]: ...
|
|
22
19
|
|
|
@@ -28,7 +25,6 @@ class ABCStateStorage(abc.ABC, typing.Generic[Payload]):
|
|
|
28
25
|
|
|
29
26
|
def State(self, key: str | StateMeta | enum.Enum = StateMeta.ANY, /) -> State[Payload]: # noqa: N802
|
|
30
27
|
"""Can be used as a shortcut to get a state rule dependant on current storage."""
|
|
31
|
-
|
|
32
28
|
return State(storage=self, key=key)
|
|
33
29
|
|
|
34
30
|
|
|
@@ -5,7 +5,7 @@ from fntypes.option import Option
|
|
|
5
5
|
from telegrinder.tools.functional import from_optional
|
|
6
6
|
from telegrinder.tools.state_storage.abc import ABCStateStorage, StateData
|
|
7
7
|
|
|
8
|
-
Payload
|
|
8
|
+
type Payload = dict[str, typing.Any]
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class MemoryStateStorage(ABCStateStorage[Payload]):
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
PATTERN = re.compile(r"[^ a-z A-Z 0-9 \s]")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def to_pascal_case(string: str, /) -> str:
|
|
7
|
+
return "".join(
|
|
8
|
+
"".join(char.capitalize() for char in sub_str)
|
|
9
|
+
for sub_str in (char.split() for char in PATTERN.split(string))
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = ("to_pascal_case",)
|
telegrinder/types/__init__.py
CHANGED
|
@@ -2,6 +2,7 @@ from telegrinder.types.enums import *
|
|
|
2
2
|
from telegrinder.types.objects import *
|
|
3
3
|
|
|
4
4
|
__all__ = (
|
|
5
|
+
"AffiliateInfo",
|
|
5
6
|
"Animation",
|
|
6
7
|
"Audio",
|
|
7
8
|
"BackgroundFill",
|
|
@@ -68,6 +69,7 @@ __all__ = (
|
|
|
68
69
|
"ChosenInlineResult",
|
|
69
70
|
"Contact",
|
|
70
71
|
"ContentType",
|
|
72
|
+
"CopyTextButton",
|
|
71
73
|
"Currency",
|
|
72
74
|
"DefaultAccentColor",
|
|
73
75
|
"Dice",
|
|
@@ -88,6 +90,8 @@ __all__ = (
|
|
|
88
90
|
"GameHighScore",
|
|
89
91
|
"GeneralForumTopicHidden",
|
|
90
92
|
"GeneralForumTopicUnhidden",
|
|
93
|
+
"Gift",
|
|
94
|
+
"Gifts",
|
|
91
95
|
"Giveaway",
|
|
92
96
|
"GiveawayCompleted",
|
|
93
97
|
"GiveawayCreated",
|
|
@@ -199,6 +203,7 @@ __all__ = (
|
|
|
199
203
|
"PollOption",
|
|
200
204
|
"PollType",
|
|
201
205
|
"PreCheckoutQuery",
|
|
206
|
+
"PreparedInlineMessage",
|
|
202
207
|
"ProgrammingLanguage",
|
|
203
208
|
"ProximityAlertTriggered",
|
|
204
209
|
"ReactionCount",
|
|
@@ -235,9 +240,12 @@ __all__ = (
|
|
|
235
240
|
"TextQuote",
|
|
236
241
|
"TopicIconColor",
|
|
237
242
|
"TransactionPartner",
|
|
243
|
+
"TransactionPartnerAffiliateProgram",
|
|
244
|
+
"TransactionPartnerChat",
|
|
238
245
|
"TransactionPartnerFragment",
|
|
239
246
|
"TransactionPartnerOther",
|
|
240
247
|
"TransactionPartnerTelegramAds",
|
|
248
|
+
"TransactionPartnerTelegramApi",
|
|
241
249
|
"TransactionPartnerUser",
|
|
242
250
|
"Update",
|
|
243
251
|
"UpdateType",
|
telegrinder/types/enums.py
CHANGED
|
@@ -52,7 +52,8 @@ class ChatAction(str, enum.Enum):
|
|
|
52
52
|
- find_location for location data,
|
|
53
53
|
- record_video_note or upload_video_note for video notes.
|
|
54
54
|
|
|
55
|
-
Docs: https://core.telegram.org/bots/api#sendchataction
|
|
55
|
+
Docs: https://core.telegram.org/bots/api#sendchataction
|
|
56
|
+
"""
|
|
56
57
|
|
|
57
58
|
TYPING = "typing"
|
|
58
59
|
UPLOAD_PHOTO = "upload_photo"
|
|
@@ -77,7 +78,8 @@ class ReactionEmoji(str, enum.Enum):
|
|
|
77
78
|
`✍`, `🤗`, `🫡`, `🎅`, `🎄`, `☃`, `💅`, `🤪`, `🗿`, `🆒`, `💘`, `🙉`, `🦄`, `😘`, `💊`,
|
|
78
79
|
`🙊`, `😎`, `👾`, `🤷♂`, `🤷`, `🤷♀`, `😡`.
|
|
79
80
|
|
|
80
|
-
Docs: https://core.telegram.org/bots/api#reactiontypeemoji
|
|
81
|
+
Docs: https://core.telegram.org/bots/api#reactiontypeemoji
|
|
82
|
+
"""
|
|
81
83
|
|
|
82
84
|
THUMBS_UP = "👍"
|
|
83
85
|
THUMBS_DOWN = "👎"
|
|
@@ -194,7 +196,8 @@ class TopicIconColor(int, enum.Enum):
|
|
|
194
196
|
|
|
195
197
|
class ChatBoostSourceType(str, enum.Enum):
|
|
196
198
|
"""Type of ChatBoostSourceType
|
|
197
|
-
Docs: https://core.telegram.org/bots/api#chatboostsource
|
|
199
|
+
Docs: https://core.telegram.org/bots/api#chatboostsource
|
|
200
|
+
"""
|
|
198
201
|
|
|
199
202
|
PREMIUM = "premium"
|
|
200
203
|
GIFT_CODE = "gift_code"
|
|
@@ -263,7 +266,8 @@ class ContentType(str, enum.Enum):
|
|
|
263
266
|
|
|
264
267
|
class Currency(str, enum.Enum):
|
|
265
268
|
"""Type of Currency.
|
|
266
|
-
Docs: https://core.telegram.org/bots/payments#supported-currencies
|
|
269
|
+
Docs: https://core.telegram.org/bots/payments#supported-currencies
|
|
270
|
+
"""
|
|
267
271
|
|
|
268
272
|
AED = "AED"
|
|
269
273
|
AFN = "AFN"
|
|
@@ -357,7 +361,8 @@ class Currency(str, enum.Enum):
|
|
|
357
361
|
|
|
358
362
|
class InlineQueryResultType(str, enum.Enum):
|
|
359
363
|
"""Type of InlineQueryResultType.
|
|
360
|
-
Docs: https://core.telegram.org/bots/api#inlinequeryresult
|
|
364
|
+
Docs: https://core.telegram.org/bots/api#inlinequeryresult
|
|
365
|
+
"""
|
|
361
366
|
|
|
362
367
|
AUDIO = "audio"
|
|
363
368
|
DOCUMENT = "document"
|
|
@@ -425,7 +430,8 @@ class UpdateType(str, enum.Enum):
|
|
|
425
430
|
|
|
426
431
|
class BotCommandScopeType(str, enum.Enum):
|
|
427
432
|
"""Type of BotCommandScope.
|
|
428
|
-
Represents the scope to which bot commands are applied.
|
|
433
|
+
Represents the scope to which bot commands are applied.
|
|
434
|
+
"""
|
|
429
435
|
|
|
430
436
|
DEFAULT = "default"
|
|
431
437
|
ALL_PRIVATE_CHATS = "all_private_chats"
|
|
@@ -469,17 +475,17 @@ class DiceEmoji(str, enum.Enum):
|
|
|
469
475
|
|
|
470
476
|
|
|
471
477
|
class MessageEntityType(str, enum.Enum):
|
|
472
|
-
"""Type of the entity.
|
|
473
|
-
|
|
474
|
-
(
|
|
475
|
-
|
|
476
|
-
`
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
(for
|
|
481
|
-
`custom_emoji` (for inline custom emoji stickers)
|
|
482
|
-
|
|
478
|
+
"""Type of the entity. Currently, can be `mention` (@username), `hashtag`
|
|
479
|
+
(#hashtag or #hashtag@chatusername), `cashtag` ($USD or $USD@chatusername),
|
|
480
|
+
`bot_command` (/start@jobs_bot), `url` (https://telegram.org), `email`
|
|
481
|
+
(do-not-reply@telegram.org), `phone_number` (+1-212-555-0123),
|
|
482
|
+
`bold` (bold text), `italic` (italic text), `underline` (underlined
|
|
483
|
+
text), `strikethrough` (strikethrough text), `spoiler` (spoiler message),
|
|
484
|
+
`blockquote` (block quotation), `expandable_blockquote` (collapsed-by-default
|
|
485
|
+
block quotation), `code` (monowidth string), `pre` (monowidth block),
|
|
486
|
+
`text_link` (for clickable text URLs), `text_mention` (for users without
|
|
487
|
+
usernames), `custom_emoji` (for inline custom emoji stickers).
|
|
488
|
+
"""
|
|
483
489
|
|
|
484
490
|
MENTION = "mention"
|
|
485
491
|
HASHTAG = "hashtag"
|
|
@@ -512,7 +518,8 @@ class PollType(str, enum.Enum):
|
|
|
512
518
|
class StickerType(str, enum.Enum):
|
|
513
519
|
"""Type of the sticker, currently one of `regular`, `mask`, `custom_emoji`.
|
|
514
520
|
The type of the sticker is independent from its format, which is determined
|
|
515
|
-
by the fields `is_animated` and `is_video`.
|
|
521
|
+
by the fields `is_animated` and `is_video`.
|
|
522
|
+
"""
|
|
516
523
|
|
|
517
524
|
REGULAR = "regular"
|
|
518
525
|
MASK = "mask"
|
|
@@ -521,7 +528,8 @@ class StickerType(str, enum.Enum):
|
|
|
521
528
|
|
|
522
529
|
class MessageOriginType(str, enum.Enum):
|
|
523
530
|
"""Type of MessageOriginType
|
|
524
|
-
Docs: https://core.telegram.org/bots/api#messageorigin
|
|
531
|
+
Docs: https://core.telegram.org/bots/api#messageorigin
|
|
532
|
+
"""
|
|
525
533
|
|
|
526
534
|
USER = "user"
|
|
527
535
|
HIDDEN_USER = "hidden_user"
|
|
@@ -539,7 +547,8 @@ class StickerSetStickerType(str, enum.Enum):
|
|
|
539
547
|
|
|
540
548
|
class MaskPositionPoint(str, enum.Enum):
|
|
541
549
|
"""The part of the face relative to which the mask should be placed. One of `forehead`,
|
|
542
|
-
`eyes`, `mouth`, or `chin`.
|
|
550
|
+
`eyes`, `mouth`, or `chin`.
|
|
551
|
+
"""
|
|
543
552
|
|
|
544
553
|
FOREHEAD = "forehead"
|
|
545
554
|
EYES = "eyes"
|
|
@@ -552,7 +561,8 @@ class InlineQueryChatType(str, enum.Enum):
|
|
|
552
561
|
either `sender` for a private chat with the inline query sender, `private`,
|
|
553
562
|
`group`, `supergroup`, or `channel`. The chat type should be always known
|
|
554
563
|
for requests sent from official clients and most third-party clients,
|
|
555
|
-
unless the request was sent from a secret chat.
|
|
564
|
+
unless the request was sent from a secret chat.
|
|
565
|
+
"""
|
|
556
566
|
|
|
557
567
|
SENDER = "sender"
|
|
558
568
|
PRIVATE = "private"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import secrets
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from telegrinder.msgspec_utils import encoder
|
|
6
|
+
|
|
7
|
+
type Files = dict[str, tuple[str, bytes]]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InputFile:
|
|
11
|
+
"""Object `InputFile`, see the [documentation](https://core.telegram.org/bots/api#inputfile).
|
|
12
|
+
|
|
13
|
+
This object represents the contents of a file to be uploaded. Must be posted using `multipart/form-data` in the usual way that files are uploaded via the browser.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__slots__ = ("filename", "data")
|
|
17
|
+
|
|
18
|
+
filename: str
|
|
19
|
+
"""File name."""
|
|
20
|
+
|
|
21
|
+
data: bytes
|
|
22
|
+
"""Bytes of file."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, filename: str, data: bytes) -> None:
|
|
25
|
+
self.filename = filename
|
|
26
|
+
self.data = data
|
|
27
|
+
|
|
28
|
+
def __repr__(self) -> str:
|
|
29
|
+
return "{}(filename={!r}, data={!r})".format(
|
|
30
|
+
self.__class__.__name__,
|
|
31
|
+
self.filename,
|
|
32
|
+
(self.data[:30] + b"...") if len(self.data) > 30 else self.data,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_path(cls, path: str | pathlib.Path, /) -> typing.Self:
|
|
37
|
+
path = pathlib.Path(path)
|
|
38
|
+
return cls(path.name, path.read_bytes())
|
|
39
|
+
|
|
40
|
+
def _to_multipart(self, files: Files, /) -> str:
|
|
41
|
+
attach_name = secrets.token_urlsafe(16)
|
|
42
|
+
files[attach_name] = (self.filename, self.data)
|
|
43
|
+
return f"attach://{attach_name}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@encoder.add_enc_hook(InputFile)
|
|
47
|
+
def encode_inputfile(inputfile: InputFile, files: Files) -> str:
|
|
48
|
+
return inputfile._to_multipart(files)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = ("InputFile",)
|