telegrinder 0.3.4.post1__py3-none-any.whl → 0.4.1__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 +55 -129
- 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 +28 -9
- 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 +236 -60
- 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.1.dist-info}/LICENSE +2 -2
- telegrinder-0.4.1.dist-info/METADATA +143 -0
- telegrinder-0.4.1.dist-info/RECORD +182 -0
- {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.1.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,59 +1,106 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import dataclasses
|
|
1
5
|
import enum
|
|
2
|
-
import
|
|
3
|
-
import
|
|
6
|
+
import inspect
|
|
7
|
+
from collections import OrderedDict
|
|
4
8
|
from functools import wraps
|
|
5
9
|
|
|
10
|
+
import typing_extensions as typing
|
|
11
|
+
from fntypes import Result
|
|
12
|
+
|
|
13
|
+
from telegrinder.model import get_params
|
|
14
|
+
|
|
6
15
|
if typing.TYPE_CHECKING:
|
|
7
16
|
from telegrinder.bot.rules.abc import ABCRule
|
|
8
17
|
from telegrinder.node.polymorphic import Polymorphic
|
|
9
18
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Impl: typing.TypeAlias = type[classmethod]
|
|
17
|
-
FuncType: typing.TypeAlias = types.FunctionType | typing.Callable[..., typing.Any]
|
|
19
|
+
type Impl = type[classmethod]
|
|
20
|
+
type Function = typing.Callable[..., typing.Any]
|
|
21
|
+
type Executor[T] = typing.Callable[
|
|
22
|
+
[T, str, dict[str, typing.Any]],
|
|
23
|
+
typing.Awaitable[Result[typing.Any, typing.Any]],
|
|
24
|
+
]
|
|
18
25
|
|
|
19
26
|
TRANSLATIONS_KEY: typing.Final[str] = "_translations"
|
|
27
|
+
MORPH_IMPLEMENTATIONS_KEY = "__morph_implementations__"
|
|
20
28
|
IMPL_MARK: typing.Final[str] = "_is_impl"
|
|
29
|
+
CONTEXT_KEYS: typing.Final[tuple[str, ...]] = ("ctx", "context")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FuncParams(typing.TypedDict, total=True):
|
|
33
|
+
args: list[tuple[str, typing.Any | inspect.Parameter.empty]]
|
|
34
|
+
kwargs: list[tuple[str, typing.Any | inspect.Parameter.empty]]
|
|
35
|
+
var_args: typing.NotRequired[str]
|
|
36
|
+
var_kwargs: typing.NotRequired[str]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclasses.dataclass(slots=True, frozen=True)
|
|
40
|
+
class Shortcut[T]:
|
|
41
|
+
method_name: str
|
|
42
|
+
executor: Executor[T] | None = dataclasses.field(default=None, kw_only=True)
|
|
43
|
+
custom_params: set[str] = dataclasses.field(default_factory=lambda: set[str](), kw_only=True)
|
|
21
44
|
|
|
22
45
|
|
|
23
46
|
def cache_magic_value(mark_key: str, /):
|
|
24
|
-
def inner(func:
|
|
47
|
+
def inner[Func: typing.Callable[..., typing.Any]](func: Func) -> Func:
|
|
25
48
|
@wraps(func)
|
|
26
|
-
def wrapper(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
def wrapper(
|
|
50
|
+
f: Function | None = None,
|
|
51
|
+
/,
|
|
52
|
+
*args: typing.Any,
|
|
53
|
+
**kwargs: typing.Any,
|
|
54
|
+
) -> typing.Any:
|
|
55
|
+
if f is not None and mark_key not in f.__dict__:
|
|
56
|
+
f.__dict__[mark_key] = func(f, *args, **kwargs)
|
|
57
|
+
return f.__dict__[mark_key]
|
|
30
58
|
|
|
31
59
|
return wrapper # type: ignore
|
|
32
60
|
|
|
33
61
|
return inner
|
|
34
62
|
|
|
35
63
|
|
|
36
|
-
def resolve_arg_names(func:
|
|
64
|
+
def resolve_arg_names(func: Function, start_idx: int = 1) -> tuple[str, ...]:
|
|
37
65
|
return func.__code__.co_varnames[start_idx : func.__code__.co_argcount]
|
|
38
66
|
|
|
39
67
|
|
|
40
|
-
@cache_magic_value("
|
|
41
|
-
def get_default_args(func:
|
|
42
|
-
kwdefaults = func.__kwdefaults__
|
|
43
|
-
if kwdefaults:
|
|
44
|
-
return kwdefaults
|
|
45
|
-
|
|
68
|
+
@cache_magic_value("__default_arguments__")
|
|
69
|
+
def get_default_args(func: Function, /) -> dict[str, typing.Any]:
|
|
46
70
|
defaults = func.__defaults__
|
|
71
|
+
kwdefaults = {} if not func.__kwdefaults__ else func.__kwdefaults__.copy()
|
|
47
72
|
if not defaults:
|
|
48
|
-
return
|
|
73
|
+
return kwdefaults
|
|
74
|
+
return {
|
|
75
|
+
k: defaults[i] for i, k in enumerate(resolve_arg_names(func, start_idx=0)[-len(defaults) :])
|
|
76
|
+
} | kwdefaults
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@cache_magic_value("__function_parameters__")
|
|
80
|
+
def get_func_parameters(func: Function, /) -> FuncParams:
|
|
81
|
+
func_params: FuncParams = {"args": [], "kwargs": []}
|
|
82
|
+
|
|
83
|
+
for k, p in inspect.signature(func).parameters.items():
|
|
84
|
+
if k in ("self", "cls"):
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
match p.kind:
|
|
88
|
+
case p.POSITIONAL_OR_KEYWORD | p.POSITIONAL_ONLY:
|
|
89
|
+
func_params["args"].append((k, p.default))
|
|
90
|
+
case p.KEYWORD_ONLY:
|
|
91
|
+
func_params["kwargs"].append((k, p.default))
|
|
92
|
+
case p.VAR_POSITIONAL:
|
|
93
|
+
func_params["var_args"] = k
|
|
94
|
+
case p.VAR_KEYWORD:
|
|
95
|
+
func_params["var_kwargs"] = k
|
|
49
96
|
|
|
50
|
-
return
|
|
97
|
+
return func_params
|
|
51
98
|
|
|
52
99
|
|
|
53
|
-
def get_annotations(func:
|
|
54
|
-
annotations = func.__annotations__
|
|
55
|
-
if not return_type
|
|
56
|
-
annotations.pop("return")
|
|
100
|
+
def get_annotations(func: Function, *, return_type: bool = False) -> dict[str, typing.Any]:
|
|
101
|
+
annotations = func.__annotations__.copy()
|
|
102
|
+
if not return_type:
|
|
103
|
+
annotations.pop("return", None)
|
|
57
104
|
return annotations
|
|
58
105
|
|
|
59
106
|
|
|
@@ -64,70 +111,106 @@ def to_str(s: str | enum.Enum) -> str:
|
|
|
64
111
|
|
|
65
112
|
|
|
66
113
|
@typing.overload
|
|
67
|
-
def magic_bundle(
|
|
114
|
+
def magic_bundle(
|
|
115
|
+
function: Function,
|
|
116
|
+
kw: dict[str, typing.Any],
|
|
117
|
+
*,
|
|
118
|
+
start_idx: int = 1,
|
|
119
|
+
omit_defaults: bool = False,
|
|
120
|
+
) -> dict[str, typing.Any]: ...
|
|
68
121
|
|
|
69
122
|
|
|
70
123
|
@typing.overload
|
|
71
|
-
def magic_bundle(
|
|
124
|
+
def magic_bundle(
|
|
125
|
+
function: Function,
|
|
126
|
+
kw: dict[enum.Enum, typing.Any],
|
|
127
|
+
*,
|
|
128
|
+
start_idx: int = 1,
|
|
129
|
+
omit_defaults: bool = False,
|
|
130
|
+
) -> dict[str, typing.Any]: ...
|
|
72
131
|
|
|
73
132
|
|
|
74
133
|
@typing.overload
|
|
75
134
|
def magic_bundle(
|
|
76
|
-
|
|
135
|
+
function: Function,
|
|
77
136
|
kw: dict[str, typing.Any],
|
|
78
137
|
*,
|
|
79
138
|
start_idx: int = 1,
|
|
80
|
-
bundle_ctx: bool =
|
|
139
|
+
bundle_ctx: bool = False,
|
|
140
|
+
omit_defaults: bool = False,
|
|
81
141
|
) -> dict[str, typing.Any]: ...
|
|
82
142
|
|
|
83
143
|
|
|
84
144
|
@typing.overload
|
|
85
145
|
def magic_bundle(
|
|
86
|
-
|
|
146
|
+
function: Function,
|
|
87
147
|
kw: dict[enum.Enum, typing.Any],
|
|
88
148
|
*,
|
|
89
149
|
start_idx: int = 1,
|
|
90
|
-
bundle_ctx: bool =
|
|
150
|
+
bundle_ctx: bool = False,
|
|
151
|
+
omit_defaults: bool = False,
|
|
91
152
|
) -> dict[str, typing.Any]: ...
|
|
92
153
|
|
|
93
154
|
|
|
94
155
|
@typing.overload
|
|
95
156
|
def magic_bundle(
|
|
96
|
-
|
|
97
|
-
kw: dict[type, typing.Any],
|
|
157
|
+
function: Function,
|
|
158
|
+
kw: dict[type[typing.Any], typing.Any],
|
|
98
159
|
*,
|
|
160
|
+
start_idx: int = 1,
|
|
99
161
|
typebundle: typing.Literal[True] = True,
|
|
162
|
+
omit_defaults: bool = False,
|
|
100
163
|
) -> dict[str, typing.Any]: ...
|
|
101
164
|
|
|
102
165
|
|
|
103
166
|
def magic_bundle(
|
|
104
|
-
|
|
167
|
+
function: Function,
|
|
105
168
|
kw: dict[typing.Any, typing.Any],
|
|
106
169
|
*,
|
|
107
170
|
start_idx: int = 1,
|
|
108
|
-
bundle_ctx: bool =
|
|
171
|
+
bundle_ctx: bool = False,
|
|
109
172
|
typebundle: bool = False,
|
|
173
|
+
omit_defaults: bool = False,
|
|
110
174
|
) -> dict[str, typing.Any]:
|
|
175
|
+
# Bundle considering the function annotations
|
|
111
176
|
if typebundle:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if
|
|
122
|
-
|
|
177
|
+
return {name: kw[t] for name, t in get_annotations(function, return_type=False).items() if t in kw}
|
|
178
|
+
|
|
179
|
+
names = resolve_arg_names(function, start_idx=start_idx)
|
|
180
|
+
# Determine if the function is only have the **kwargs parameter, then bundle all kw
|
|
181
|
+
if "var_kwargs" in get_func_parameters(function) and not names:
|
|
182
|
+
return {to_str(k): v for k, v in kw.items()}
|
|
183
|
+
|
|
184
|
+
# Bundle considering the function parameters and defaults (if do not omit defaults)
|
|
185
|
+
defaults = {} if omit_defaults else get_default_args(function)
|
|
186
|
+
args = defaults | {n: v for k, v in kw.items() if (n := to_str(k)) in names}
|
|
187
|
+
|
|
188
|
+
# Bundle a context if context key specified in `names`
|
|
189
|
+
if bundle_ctx:
|
|
190
|
+
for key in CONTEXT_KEYS:
|
|
191
|
+
if key in names:
|
|
192
|
+
args[key] = kw
|
|
193
|
+
break
|
|
194
|
+
|
|
123
195
|
return args
|
|
124
196
|
|
|
125
197
|
|
|
126
|
-
def
|
|
198
|
+
def join_dicts[Key: typing.Hashable, Value](
|
|
199
|
+
left_dict: dict[Key, typing.Any],
|
|
200
|
+
right_dict: dict[typing.Any, Value],
|
|
201
|
+
) -> dict[Key, Value]:
|
|
202
|
+
return {key: right_dict[type_key] for key, type_key in left_dict.items()}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_cached_translation[Rule: ABCRule](rule: Rule, locale: str) -> Rule | None:
|
|
127
206
|
return getattr(rule, TRANSLATIONS_KEY, {}).get(locale)
|
|
128
207
|
|
|
129
208
|
|
|
130
|
-
def cache_translation
|
|
209
|
+
def cache_translation[Rule: ABCRule](
|
|
210
|
+
base_rule: Rule,
|
|
211
|
+
locale: str,
|
|
212
|
+
translated_rule: Rule,
|
|
213
|
+
) -> None:
|
|
131
214
|
translations = getattr(base_rule, TRANSLATIONS_KEY, {})
|
|
132
215
|
translations[locale] = translated_rule
|
|
133
216
|
setattr(base_rule, TRANSLATIONS_KEY, translations)
|
|
@@ -139,30 +222,123 @@ def impl(method: typing.Callable[..., typing.Any]):
|
|
|
139
222
|
return classmethod(method)
|
|
140
223
|
|
|
141
224
|
|
|
142
|
-
def
|
|
143
|
-
moprh_impls = getattr(cls,
|
|
225
|
+
def get_polymorphic_implementations(cls: type[Polymorphic], /) -> list[typing.Callable[..., typing.Any]]:
|
|
226
|
+
moprh_impls = getattr(cls, MORPH_IMPLEMENTATIONS_KEY, None)
|
|
144
227
|
if moprh_impls is not None:
|
|
145
228
|
return moprh_impls
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
229
|
+
|
|
230
|
+
impls = []
|
|
231
|
+
for cls_ in cls.mro():
|
|
232
|
+
impls += [
|
|
233
|
+
obj.__func__
|
|
234
|
+
for obj in vars(cls_).values()
|
|
235
|
+
if isinstance(obj, classmethod) and getattr(obj.__func__, IMPL_MARK, False)
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
setattr(cls, MORPH_IMPLEMENTATIONS_KEY, impls)
|
|
152
239
|
return impls
|
|
153
240
|
|
|
154
241
|
|
|
242
|
+
def shortcut[T](
|
|
243
|
+
method_name: str,
|
|
244
|
+
*,
|
|
245
|
+
executor: Executor[T] | None = None,
|
|
246
|
+
custom_params: set[str] | None = None,
|
|
247
|
+
):
|
|
248
|
+
"""Decorate a cute method as a shortcut."""
|
|
249
|
+
|
|
250
|
+
def wrapper[F: typing.Callable[..., typing.Any]](func: F) -> F:
|
|
251
|
+
@wraps(func)
|
|
252
|
+
async def inner(
|
|
253
|
+
self: T,
|
|
254
|
+
*args: typing.Any,
|
|
255
|
+
**kwargs: typing.Any,
|
|
256
|
+
) -> typing.Any:
|
|
257
|
+
if executor is None:
|
|
258
|
+
return await func(self, *args, **kwargs)
|
|
259
|
+
|
|
260
|
+
params: dict[str, typing.Any] = OrderedDict()
|
|
261
|
+
func_params = get_func_parameters(func)
|
|
262
|
+
|
|
263
|
+
for index, (arg, default) in enumerate(func_params["args"]):
|
|
264
|
+
if len(args) > index:
|
|
265
|
+
params[arg] = args[index]
|
|
266
|
+
elif default is not inspect.Parameter.empty:
|
|
267
|
+
params[arg] = default
|
|
268
|
+
|
|
269
|
+
if var_args := func_params.get("var_args"):
|
|
270
|
+
params[var_args] = args[len(func_params["args"]) :]
|
|
271
|
+
|
|
272
|
+
for kwarg, default in func_params["kwargs"]:
|
|
273
|
+
params[kwarg] = (
|
|
274
|
+
kwargs.pop(kwarg, default) if default is not inspect.Parameter.empty else kwargs.pop(kwarg)
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
if var_kwargs := func_params.get("var_kwargs"):
|
|
278
|
+
params[var_kwargs] = kwargs.copy()
|
|
279
|
+
|
|
280
|
+
return await executor(self, method_name, get_params(params))
|
|
281
|
+
|
|
282
|
+
inner.__shortcut__ = Shortcut( # type: ignore
|
|
283
|
+
method_name=method_name,
|
|
284
|
+
executor=executor,
|
|
285
|
+
custom_params=custom_params or set(),
|
|
286
|
+
)
|
|
287
|
+
return inner # type: ignore
|
|
288
|
+
|
|
289
|
+
return wrapper
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# Source code: https://github.com/facebookincubator/later/blob/main/later/task.py#L75
|
|
293
|
+
async def cancel_future(fut: asyncio.Future[typing.Any], /) -> None:
|
|
294
|
+
if fut.done():
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
fut.cancel()
|
|
298
|
+
exc: asyncio.CancelledError | None = None
|
|
299
|
+
|
|
300
|
+
while not fut.done():
|
|
301
|
+
shielded = asyncio.shield(fut)
|
|
302
|
+
try:
|
|
303
|
+
await asyncio.wait([shielded])
|
|
304
|
+
except asyncio.CancelledError as ex:
|
|
305
|
+
exc = ex
|
|
306
|
+
finally:
|
|
307
|
+
# Insure we handle the exception/value that may exist on the shielded task
|
|
308
|
+
# This will prevent errors logged to the asyncio logger
|
|
309
|
+
if shielded.done() and not shielded.cancelled() and not shielded.exception():
|
|
310
|
+
shielded.result()
|
|
311
|
+
|
|
312
|
+
if fut.cancelled():
|
|
313
|
+
if exc is None:
|
|
314
|
+
return
|
|
315
|
+
raise exc from None
|
|
316
|
+
|
|
317
|
+
ex = fut.exception()
|
|
318
|
+
if ex is not None:
|
|
319
|
+
raise ex from None
|
|
320
|
+
|
|
321
|
+
raise asyncio.InvalidStateError(
|
|
322
|
+
f"Task did not raise CancelledError on cancel: {fut!r} had result {fut.result()!r}",
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
155
326
|
__all__ = (
|
|
327
|
+
"Shortcut",
|
|
156
328
|
"TRANSLATIONS_KEY",
|
|
157
329
|
"cache_magic_value",
|
|
158
330
|
"cache_translation",
|
|
331
|
+
"cancel_future",
|
|
159
332
|
"get_annotations",
|
|
160
333
|
"get_cached_translation",
|
|
161
334
|
"get_default_args",
|
|
162
335
|
"get_default_args",
|
|
163
|
-
"
|
|
336
|
+
"get_func_parameters",
|
|
337
|
+
"get_polymorphic_implementations",
|
|
164
338
|
"impl",
|
|
339
|
+
"join_dicts",
|
|
165
340
|
"magic_bundle",
|
|
166
341
|
"resolve_arg_names",
|
|
342
|
+
"shortcut",
|
|
167
343
|
"to_str",
|
|
168
344
|
)
|
|
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",)
|