telegrinder 0.3.3.post1__py3-none-any.whl → 0.3.4.post1__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 +144 -144
- telegrinder/api/__init__.py +8 -8
- telegrinder/api/api.py +93 -93
- telegrinder/api/error.py +16 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +66 -66
- telegrinder/bot/bot.py +76 -76
- telegrinder/bot/cute_types/__init__.py +17 -17
- telegrinder/bot/cute_types/base.py +258 -258
- telegrinder/bot/cute_types/callback_query.py +385 -385
- telegrinder/bot/cute_types/chat_join_request.py +61 -61
- telegrinder/bot/cute_types/chat_member_updated.py +160 -160
- telegrinder/bot/cute_types/inline_query.py +43 -43
- telegrinder/bot/cute_types/message.py +2637 -2637
- telegrinder/bot/cute_types/update.py +104 -109
- telegrinder/bot/cute_types/utils.py +95 -95
- telegrinder/bot/dispatch/__init__.py +55 -55
- telegrinder/bot/dispatch/abc.py +77 -77
- telegrinder/bot/dispatch/context.py +98 -98
- telegrinder/bot/dispatch/dispatch.py +202 -202
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +24 -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 +135 -135
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -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 +22 -16
- telegrinder/bot/dispatch/process.py +157 -132
- telegrinder/bot/dispatch/return_manager/__init__.py +13 -13
- telegrinder/bot/dispatch/return_manager/abc.py +108 -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/view/__init__.py +13 -13
- telegrinder/bot/dispatch/view/abc.py +41 -41
- telegrinder/bot/dispatch/view/base.py +200 -200
- telegrinder/bot/dispatch/view/box.py +129 -129
- telegrinder/bot/dispatch/view/callback_query.py +17 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +16 -16
- telegrinder/bot/dispatch/view/chat_member.py +39 -39
- telegrinder/bot/dispatch/view/inline_query.py +17 -17
- telegrinder/bot/dispatch/view/message.py +44 -44
- telegrinder/bot/dispatch/view/raw.py +114 -114
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +13 -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 +57 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +172 -167
- telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +68 -68
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +131 -131
- telegrinder/bot/rules/__init__.py +62 -62
- telegrinder/bot/rules/abc.py +213 -213
- telegrinder/bot/rules/adapter/__init__.py +12 -9
- telegrinder/bot/rules/adapter/abc.py +31 -29
- telegrinder/bot/rules/adapter/errors.py +5 -5
- telegrinder/bot/rules/adapter/event.py +65 -67
- telegrinder/bot/rules/adapter/node.py +48 -48
- telegrinder/bot/rules/adapter/raw_event.py +27 -0
- telegrinder/bot/rules/adapter/raw_update.py +30 -30
- telegrinder/bot/rules/callback_data.py +170 -170
- telegrinder/bot/rules/chat_join.py +46 -46
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +36 -36
- telegrinder/bot/rules/func.py +26 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/inline.py +60 -60
- telegrinder/bot/rules/integer.py +20 -20
- telegrinder/bot/rules/is_from.py +127 -127
- telegrinder/bot/rules/markup.py +43 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +17 -17
- telegrinder/bot/rules/message_entities.py +35 -35
- telegrinder/bot/rules/node.py +27 -27
- telegrinder/bot/rules/regex.py +37 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +37 -37
- telegrinder/bot/rules/text.py +33 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +19 -19
- telegrinder/bot/scenario/checkbox.py +176 -167
- telegrinder/bot/scenario/choice.py +51 -46
- telegrinder/client/__init__.py +4 -4
- telegrinder/client/abc.py +75 -75
- telegrinder/client/aiohttp.py +130 -130
- telegrinder/model.py +320 -295
- telegrinder/modules.py +237 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +410 -410
- telegrinder/node/__init__.py +0 -0
- telegrinder/node/attachment.py +87 -87
- telegrinder/node/base.py +166 -166
- telegrinder/node/callback_query.py +53 -53
- telegrinder/node/command.py +33 -33
- telegrinder/node/composer.py +198 -198
- telegrinder/node/container.py +27 -27
- telegrinder/node/event.py +65 -65
- telegrinder/node/me.py +16 -16
- telegrinder/node/message.py +14 -14
- telegrinder/node/polymorphic.py +48 -48
- telegrinder/node/rule.py +76 -76
- telegrinder/node/scope.py +38 -38
- telegrinder/node/source.py +71 -71
- telegrinder/node/text.py +41 -41
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +40 -40
- telegrinder/node/update.py +15 -15
- telegrinder/rules.py +0 -0
- telegrinder/tools/__init__.py +74 -74
- telegrinder/tools/buttons.py +79 -79
- telegrinder/tools/error_handler/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +33 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +193 -193
- telegrinder/tools/formatting/__init__.py +46 -46
- telegrinder/tools/formatting/html.py +283 -283
- telegrinder/tools/formatting/links.py +33 -33
- telegrinder/tools/formatting/spec_html_formats.py +111 -111
- telegrinder/tools/functional.py +12 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +412 -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 +25 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/kb_set/__init__.py +4 -4
- telegrinder/tools/kb_set/base.py +15 -15
- telegrinder/tools/kb_set/yaml.py +63 -63
- telegrinder/tools/keyboard.py +132 -132
- telegrinder/tools/limited_dict.py +37 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +15 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +224 -224
- telegrinder/tools/magic.py +157 -157
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +35 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/types/__init__.py +260 -260
- telegrinder/types/enums.py +701 -701
- telegrinder/types/methods.py +4633 -4633
- telegrinder/types/objects.py +6950 -8561
- telegrinder/verification_utils.py +32 -32
- {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/LICENSE +22 -22
- {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/METADATA +1 -1
- telegrinder-0.3.4.post1.dist-info/RECORD +165 -0
- telegrinder-0.3.3.post1.dist-info/RECORD +0 -164
- {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/WHEEL +0 -0
telegrinder/bot/rules/abc.py
CHANGED
|
@@ -1,213 +1,213 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
from abc import ABC, abstractmethod
|
|
3
|
-
from functools import cached_property
|
|
4
|
-
|
|
5
|
-
import typing_extensions as typing
|
|
6
|
-
|
|
7
|
-
from telegrinder.bot.cute_types import MessageCute, UpdateCute
|
|
8
|
-
from telegrinder.bot.dispatch.context import Context
|
|
9
|
-
from telegrinder.bot.dispatch.process import check_rule
|
|
10
|
-
from telegrinder.bot.rules.adapter import ABCAdapter, RawUpdateAdapter
|
|
11
|
-
from telegrinder.bot.rules.adapter.node import Event
|
|
12
|
-
from telegrinder.node.base import Node, get_nodes, is_node
|
|
13
|
-
from telegrinder.tools.i18n.abc import ABCTranslator
|
|
14
|
-
from telegrinder.tools.magic import (
|
|
15
|
-
cache_translation,
|
|
16
|
-
get_annotations,
|
|
17
|
-
get_cached_translation,
|
|
18
|
-
get_default_args,
|
|
19
|
-
)
|
|
20
|
-
from telegrinder.types.objects import Update as UpdateObject
|
|
21
|
-
|
|
22
|
-
if typing.TYPE_CHECKING:
|
|
23
|
-
from telegrinder.node.composer import NodeCollection
|
|
24
|
-
|
|
25
|
-
AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any, contravariant=True)
|
|
26
|
-
|
|
27
|
-
CheckResult: typing.TypeAlias = bool | typing.Awaitable[bool]
|
|
28
|
-
Message: typing.TypeAlias = MessageCute
|
|
29
|
-
Update: typing.TypeAlias = UpdateCute
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def with_caching_translations(func: typing.Callable[..., typing.Any]):
|
|
33
|
-
"""Should be used as decorator for .translate method. Caches rule translations."""
|
|
34
|
-
|
|
35
|
-
async def wrapper(self: "ABCRule", translator: ABCTranslator):
|
|
36
|
-
if translation := get_cached_translation(self, translator.locale):
|
|
37
|
-
return translation
|
|
38
|
-
translation = await func(self, translator)
|
|
39
|
-
cache_translation(self, translator.locale, translation)
|
|
40
|
-
return translation
|
|
41
|
-
|
|
42
|
-
return wrapper
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
46
|
-
adapter: ABCAdapter[UpdateObject, AdaptTo]
|
|
47
|
-
requires: list["ABCRule"] = []
|
|
48
|
-
|
|
49
|
-
if typing.TYPE_CHECKING:
|
|
50
|
-
|
|
51
|
-
@abstractmethod
|
|
52
|
-
def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult:
|
|
53
|
-
pass
|
|
54
|
-
else:
|
|
55
|
-
adapter = RawUpdateAdapter()
|
|
56
|
-
|
|
57
|
-
@abstractmethod
|
|
58
|
-
def check(self, *args, **kwargs):
|
|
59
|
-
pass
|
|
60
|
-
|
|
61
|
-
def __init_subclass__(cls, requires: list["ABCRule"] | None = None) -> None:
|
|
62
|
-
"""Merges requirements from inherited classes and rule-specific requirements."""
|
|
63
|
-
|
|
64
|
-
requirements = []
|
|
65
|
-
for base in inspect.getmro(cls):
|
|
66
|
-
if issubclass(base, ABCRule) and base != cls:
|
|
67
|
-
requirements.extend(base.requires or ()) # type: ignore
|
|
68
|
-
|
|
69
|
-
requirements.extend(requires or ())
|
|
70
|
-
cls.requires = list(dict.fromkeys(requirements))
|
|
71
|
-
|
|
72
|
-
def __and__(self, other: "ABCRule") -> "AndRule":
|
|
73
|
-
"""And Rule.
|
|
74
|
-
|
|
75
|
-
```python
|
|
76
|
-
rule = HasText() & HasCaption()
|
|
77
|
-
rule #> AndRule(HasText(), HasCaption()) -> True if all rules in an AndRule are True, otherwise False.
|
|
78
|
-
```
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
return AndRule(self, other)
|
|
82
|
-
|
|
83
|
-
def __or__(self, other: "ABCRule") -> "OrRule":
|
|
84
|
-
"""Or Rule.
|
|
85
|
-
|
|
86
|
-
```python
|
|
87
|
-
rule = HasText() | HasCaption()
|
|
88
|
-
rule #> OrRule(HasText(), HasCaption()) -> True if any rule in an OrRule are True, otherwise False.
|
|
89
|
-
```
|
|
90
|
-
"""
|
|
91
|
-
|
|
92
|
-
return OrRule(self, other)
|
|
93
|
-
|
|
94
|
-
def __invert__(self) -> "NotRule":
|
|
95
|
-
"""Not Rule.
|
|
96
|
-
|
|
97
|
-
```python
|
|
98
|
-
rule = ~HasText()
|
|
99
|
-
rule # NotRule(HasText()) -> True if rule returned False, otherwise False.
|
|
100
|
-
```
|
|
101
|
-
"""
|
|
102
|
-
|
|
103
|
-
return NotRule(self)
|
|
104
|
-
|
|
105
|
-
def __repr__(self) -> str:
|
|
106
|
-
return "<{}: adapter={!r}>".format(
|
|
107
|
-
self.__class__.__name__,
|
|
108
|
-
self.adapter,
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
@cached_property
|
|
112
|
-
def required_nodes(self) -> dict[str, type[Node]]:
|
|
113
|
-
return get_nodes(self.check)
|
|
114
|
-
|
|
115
|
-
def as_optional(self) -> "ABCRule":
|
|
116
|
-
return self | Always()
|
|
117
|
-
|
|
118
|
-
def should_fail(self) -> "ABCRule":
|
|
119
|
-
return self & Never()
|
|
120
|
-
|
|
121
|
-
async def bounding_check(
|
|
122
|
-
self,
|
|
123
|
-
adapted_value: AdaptTo,
|
|
124
|
-
ctx: Context,
|
|
125
|
-
node_col: "NodeCollection | None" = None,
|
|
126
|
-
) -> bool:
|
|
127
|
-
bound_check_rule = self.check
|
|
128
|
-
kw = {}
|
|
129
|
-
node_col_values = node_col.values if node_col is not None else {}
|
|
130
|
-
temp_ctx = get_default_args(bound_check_rule) | ctx
|
|
131
|
-
|
|
132
|
-
for i, (k, v) in enumerate(get_annotations(bound_check_rule).items()):
|
|
133
|
-
if (isinstance(adapted_value, Event) and i == 0) or ( # First arg is Event
|
|
134
|
-
isinstance(v, type) and isinstance(adapted_value, v)
|
|
135
|
-
):
|
|
136
|
-
kw[k] = adapted_value if not isinstance(adapted_value, Event) else adapted_value.obj
|
|
137
|
-
elif is_node(v):
|
|
138
|
-
assert k in node_col_values, "Node is undefined, error while bounding."
|
|
139
|
-
kw[k] = node_col_values[k]
|
|
140
|
-
elif k in temp_ctx:
|
|
141
|
-
kw[k] = temp_ctx[k]
|
|
142
|
-
elif v is Context:
|
|
143
|
-
kw[k] = ctx
|
|
144
|
-
else:
|
|
145
|
-
raise LookupError(
|
|
146
|
-
f"Cannot bound {k!r} of type {v!r} to '{self.__class__.__qualname__}.check()', "
|
|
147
|
-
"because it cannot be resolved."
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
result = bound_check_rule(**kw) # type: ignore
|
|
151
|
-
if inspect.isawaitable(result):
|
|
152
|
-
result = await result
|
|
153
|
-
return result
|
|
154
|
-
|
|
155
|
-
async def translate(self, translator: ABCTranslator) -> typing.Self:
|
|
156
|
-
return self
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
class AndRule(ABCRule):
|
|
160
|
-
def __init__(self, *rules: ABCRule) -> None:
|
|
161
|
-
self.rules = rules
|
|
162
|
-
|
|
163
|
-
async def check(self, event: Update, ctx: Context) -> bool:
|
|
164
|
-
ctx_copy = ctx.copy()
|
|
165
|
-
for rule in self.rules:
|
|
166
|
-
if not await check_rule(event.ctx_api, rule, event, ctx_copy):
|
|
167
|
-
return False
|
|
168
|
-
ctx |= ctx_copy
|
|
169
|
-
return True
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
class OrRule(ABCRule):
|
|
173
|
-
def __init__(self, *rules: ABCRule) -> None:
|
|
174
|
-
self.rules = rules
|
|
175
|
-
|
|
176
|
-
async def check(self, event: Update, ctx: Context) -> bool:
|
|
177
|
-
for rule in self.rules:
|
|
178
|
-
ctx_copy = ctx.copy()
|
|
179
|
-
if await check_rule(event.ctx_api, rule, event, ctx_copy):
|
|
180
|
-
ctx |= ctx_copy
|
|
181
|
-
return True
|
|
182
|
-
return False
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
class NotRule(ABCRule):
|
|
186
|
-
def __init__(self, rule: ABCRule) -> None:
|
|
187
|
-
self.rule = rule
|
|
188
|
-
|
|
189
|
-
async def check(self, event: Update, ctx: Context) -> bool:
|
|
190
|
-
ctx_copy = ctx.copy()
|
|
191
|
-
return not await check_rule(event.ctx_api, self.rule, event, ctx_copy)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
class Never(ABCRule):
|
|
195
|
-
async def check(self) -> typing.Literal[False]:
|
|
196
|
-
return False
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
class Always(ABCRule):
|
|
200
|
-
async def check(self) -> typing.Literal[True]:
|
|
201
|
-
return True
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
__all__ = (
|
|
205
|
-
"ABCRule",
|
|
206
|
-
"Always",
|
|
207
|
-
"AndRule",
|
|
208
|
-
"CheckResult",
|
|
209
|
-
"Never",
|
|
210
|
-
"NotRule",
|
|
211
|
-
"OrRule",
|
|
212
|
-
"with_caching_translations",
|
|
213
|
-
)
|
|
1
|
+
import inspect
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
|
|
5
|
+
import typing_extensions as typing
|
|
6
|
+
|
|
7
|
+
from telegrinder.bot.cute_types import MessageCute, UpdateCute
|
|
8
|
+
from telegrinder.bot.dispatch.context import Context
|
|
9
|
+
from telegrinder.bot.dispatch.process import check_rule
|
|
10
|
+
from telegrinder.bot.rules.adapter import ABCAdapter, RawUpdateAdapter
|
|
11
|
+
from telegrinder.bot.rules.adapter.node import Event
|
|
12
|
+
from telegrinder.node.base import Node, get_nodes, is_node
|
|
13
|
+
from telegrinder.tools.i18n.abc import ABCTranslator
|
|
14
|
+
from telegrinder.tools.magic import (
|
|
15
|
+
cache_translation,
|
|
16
|
+
get_annotations,
|
|
17
|
+
get_cached_translation,
|
|
18
|
+
get_default_args,
|
|
19
|
+
)
|
|
20
|
+
from telegrinder.types.objects import Update as UpdateObject
|
|
21
|
+
|
|
22
|
+
if typing.TYPE_CHECKING:
|
|
23
|
+
from telegrinder.node.composer import NodeCollection
|
|
24
|
+
|
|
25
|
+
AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any, contravariant=True)
|
|
26
|
+
|
|
27
|
+
CheckResult: typing.TypeAlias = bool | typing.Awaitable[bool]
|
|
28
|
+
Message: typing.TypeAlias = MessageCute
|
|
29
|
+
Update: typing.TypeAlias = UpdateCute
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def with_caching_translations(func: typing.Callable[..., typing.Any]):
|
|
33
|
+
"""Should be used as decorator for .translate method. Caches rule translations."""
|
|
34
|
+
|
|
35
|
+
async def wrapper(self: "ABCRule", translator: ABCTranslator):
|
|
36
|
+
if translation := get_cached_translation(self, translator.locale):
|
|
37
|
+
return translation
|
|
38
|
+
translation = await func(self, translator)
|
|
39
|
+
cache_translation(self, translator.locale, translation)
|
|
40
|
+
return translation
|
|
41
|
+
|
|
42
|
+
return wrapper
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
46
|
+
adapter: ABCAdapter[UpdateObject, AdaptTo]
|
|
47
|
+
requires: list["ABCRule"] = []
|
|
48
|
+
|
|
49
|
+
if typing.TYPE_CHECKING:
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult:
|
|
53
|
+
pass
|
|
54
|
+
else:
|
|
55
|
+
adapter = RawUpdateAdapter()
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def check(self, *args, **kwargs):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
def __init_subclass__(cls, requires: list["ABCRule"] | None = None) -> None:
|
|
62
|
+
"""Merges requirements from inherited classes and rule-specific requirements."""
|
|
63
|
+
|
|
64
|
+
requirements = []
|
|
65
|
+
for base in inspect.getmro(cls):
|
|
66
|
+
if issubclass(base, ABCRule) and base != cls:
|
|
67
|
+
requirements.extend(base.requires or ()) # type: ignore
|
|
68
|
+
|
|
69
|
+
requirements.extend(requires or ())
|
|
70
|
+
cls.requires = list(dict.fromkeys(requirements))
|
|
71
|
+
|
|
72
|
+
def __and__(self, other: "ABCRule") -> "AndRule":
|
|
73
|
+
"""And Rule.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
rule = HasText() & HasCaption()
|
|
77
|
+
rule #> AndRule(HasText(), HasCaption()) -> True if all rules in an AndRule are True, otherwise False.
|
|
78
|
+
```
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
return AndRule(self, other)
|
|
82
|
+
|
|
83
|
+
def __or__(self, other: "ABCRule") -> "OrRule":
|
|
84
|
+
"""Or Rule.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
rule = HasText() | HasCaption()
|
|
88
|
+
rule #> OrRule(HasText(), HasCaption()) -> True if any rule in an OrRule are True, otherwise False.
|
|
89
|
+
```
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
return OrRule(self, other)
|
|
93
|
+
|
|
94
|
+
def __invert__(self) -> "NotRule":
|
|
95
|
+
"""Not Rule.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
rule = ~HasText()
|
|
99
|
+
rule # NotRule(HasText()) -> True if rule returned False, otherwise False.
|
|
100
|
+
```
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
return NotRule(self)
|
|
104
|
+
|
|
105
|
+
def __repr__(self) -> str:
|
|
106
|
+
return "<{}: adapter={!r}>".format(
|
|
107
|
+
self.__class__.__name__,
|
|
108
|
+
self.adapter,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@cached_property
|
|
112
|
+
def required_nodes(self) -> dict[str, type[Node]]:
|
|
113
|
+
return get_nodes(self.check)
|
|
114
|
+
|
|
115
|
+
def as_optional(self) -> "ABCRule":
|
|
116
|
+
return self | Always()
|
|
117
|
+
|
|
118
|
+
def should_fail(self) -> "ABCRule":
|
|
119
|
+
return self & Never()
|
|
120
|
+
|
|
121
|
+
async def bounding_check(
|
|
122
|
+
self,
|
|
123
|
+
adapted_value: AdaptTo,
|
|
124
|
+
ctx: Context,
|
|
125
|
+
node_col: "NodeCollection | None" = None,
|
|
126
|
+
) -> bool:
|
|
127
|
+
bound_check_rule = self.check
|
|
128
|
+
kw = {}
|
|
129
|
+
node_col_values = node_col.values if node_col is not None else {}
|
|
130
|
+
temp_ctx = get_default_args(bound_check_rule) | ctx
|
|
131
|
+
|
|
132
|
+
for i, (k, v) in enumerate(get_annotations(bound_check_rule).items()):
|
|
133
|
+
if (isinstance(adapted_value, Event) and i == 0) or ( # First arg is Event
|
|
134
|
+
isinstance(v, type) and isinstance(adapted_value, v)
|
|
135
|
+
):
|
|
136
|
+
kw[k] = adapted_value if not isinstance(adapted_value, Event) else adapted_value.obj
|
|
137
|
+
elif is_node(v):
|
|
138
|
+
assert k in node_col_values, "Node is undefined, error while bounding."
|
|
139
|
+
kw[k] = node_col_values[k]
|
|
140
|
+
elif k in temp_ctx:
|
|
141
|
+
kw[k] = temp_ctx[k]
|
|
142
|
+
elif v is Context:
|
|
143
|
+
kw[k] = ctx
|
|
144
|
+
else:
|
|
145
|
+
raise LookupError(
|
|
146
|
+
f"Cannot bound {k!r} of type {v!r} to '{self.__class__.__qualname__}.check()', "
|
|
147
|
+
"because it cannot be resolved."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
result = bound_check_rule(**kw) # type: ignore
|
|
151
|
+
if inspect.isawaitable(result):
|
|
152
|
+
result = await result
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
async def translate(self, translator: ABCTranslator) -> typing.Self:
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class AndRule(ABCRule):
|
|
160
|
+
def __init__(self, *rules: ABCRule) -> None:
|
|
161
|
+
self.rules = rules
|
|
162
|
+
|
|
163
|
+
async def check(self, event: Update, ctx: Context) -> bool:
|
|
164
|
+
ctx_copy = ctx.copy()
|
|
165
|
+
for rule in self.rules:
|
|
166
|
+
if not await check_rule(event.ctx_api, rule, event, ctx_copy):
|
|
167
|
+
return False
|
|
168
|
+
ctx |= ctx_copy
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class OrRule(ABCRule):
|
|
173
|
+
def __init__(self, *rules: ABCRule) -> None:
|
|
174
|
+
self.rules = rules
|
|
175
|
+
|
|
176
|
+
async def check(self, event: Update, ctx: Context) -> bool:
|
|
177
|
+
for rule in self.rules:
|
|
178
|
+
ctx_copy = ctx.copy()
|
|
179
|
+
if await check_rule(event.ctx_api, rule, event, ctx_copy):
|
|
180
|
+
ctx |= ctx_copy
|
|
181
|
+
return True
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class NotRule(ABCRule):
|
|
186
|
+
def __init__(self, rule: ABCRule) -> None:
|
|
187
|
+
self.rule = rule
|
|
188
|
+
|
|
189
|
+
async def check(self, event: Update, ctx: Context) -> bool:
|
|
190
|
+
ctx_copy = ctx.copy()
|
|
191
|
+
return not await check_rule(event.ctx_api, self.rule, event, ctx_copy)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Never(ABCRule):
|
|
195
|
+
async def check(self) -> typing.Literal[False]:
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class Always(ABCRule):
|
|
200
|
+
async def check(self) -> typing.Literal[True]:
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
__all__ = (
|
|
205
|
+
"ABCRule",
|
|
206
|
+
"Always",
|
|
207
|
+
"AndRule",
|
|
208
|
+
"CheckResult",
|
|
209
|
+
"Never",
|
|
210
|
+
"NotRule",
|
|
211
|
+
"OrRule",
|
|
212
|
+
"with_caching_translations",
|
|
213
|
+
)
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
|
|
2
|
-
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
3
|
-
from telegrinder.bot.rules.adapter.event import EventAdapter
|
|
4
|
-
from telegrinder.bot.rules.adapter.node import NodeAdapter
|
|
5
|
-
from telegrinder.bot.rules.adapter.
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
from telegrinder.bot.rules.adapter.abc import ABCAdapter, AdaptResult, Event
|
|
2
|
+
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
3
|
+
from telegrinder.bot.rules.adapter.event import EventAdapter
|
|
4
|
+
from telegrinder.bot.rules.adapter.node import NodeAdapter
|
|
5
|
+
from telegrinder.bot.rules.adapter.raw_event import RawEventAdapter
|
|
6
|
+
from telegrinder.bot.rules.adapter.raw_update import RawUpdateAdapter
|
|
7
|
+
|
|
8
|
+
__all__ = (
|
|
8
9
|
"ABCAdapter",
|
|
10
|
+
"AdaptResult",
|
|
9
11
|
"AdapterError",
|
|
10
12
|
"Event",
|
|
11
13
|
"EventAdapter",
|
|
12
14
|
"NodeAdapter",
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
+
"RawEventAdapter",
|
|
16
|
+
"RawUpdateAdapter",
|
|
17
|
+
)
|
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
import abc
|
|
2
|
-
import dataclasses
|
|
3
|
-
import typing
|
|
4
|
-
|
|
5
|
-
from fntypes.result import Result
|
|
6
|
-
|
|
7
|
-
from telegrinder.api.api import API
|
|
8
|
-
from telegrinder.bot.dispatch.context import Context
|
|
9
|
-
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
10
|
-
from telegrinder.model import Model
|
|
11
|
-
|
|
12
|
-
From = typing.TypeVar("From", bound=Model)
|
|
13
|
-
To = typing.TypeVar("To")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
import abc
|
|
2
|
+
import dataclasses
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from fntypes.result import Result
|
|
6
|
+
|
|
7
|
+
from telegrinder.api.api import API
|
|
8
|
+
from telegrinder.bot.dispatch.context import Context
|
|
9
|
+
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
10
|
+
from telegrinder.model import Model
|
|
11
|
+
|
|
12
|
+
From = typing.TypeVar("From", bound=Model)
|
|
13
|
+
To = typing.TypeVar("To")
|
|
14
|
+
|
|
15
|
+
AdaptResult: typing.TypeAlias = Result[To, AdapterError] | typing.Awaitable[Result[To, AdapterError]]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ABCAdapter(abc.ABC, typing.Generic[From, To]):
|
|
19
|
+
ADAPTED_VALUE_KEY: str | None = None
|
|
20
|
+
|
|
21
|
+
@abc.abstractmethod
|
|
22
|
+
def adapt(self, api: API, update: From, context: Context) -> AdaptResult[To]:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclasses.dataclass(slots=True)
|
|
27
|
+
class Event(typing.Generic[To]):
|
|
28
|
+
obj: To
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = ("ABCAdapter", "AdaptResult", "Event")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
class AdapterError(RuntimeError):
|
|
2
|
-
pass
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
__all__ = ("AdapterError",)
|
|
1
|
+
class AdapterError(RuntimeError):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
__all__ = ("AdapterError",)
|
|
@@ -1,67 +1,65 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
|
|
3
|
-
from fntypes.result import Error, Ok, Result
|
|
4
|
-
|
|
5
|
-
from telegrinder.api.api import API
|
|
6
|
-
from telegrinder.bot.cute_types.base import BaseCute
|
|
7
|
-
from telegrinder.bot.
|
|
8
|
-
from telegrinder.bot.
|
|
9
|
-
from telegrinder.bot.rules.adapter.
|
|
10
|
-
from telegrinder.bot.rules.adapter.
|
|
11
|
-
from telegrinder.
|
|
12
|
-
from telegrinder.types.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
self.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
raw_update_type
|
|
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
|
-
return Error(AdapterError(f"Update is not an {self.event!r}."))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
__all__ = ("EventAdapter",)
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from fntypes.result import Error, Ok, Result
|
|
4
|
+
|
|
5
|
+
from telegrinder.api.api import API
|
|
6
|
+
from telegrinder.bot.cute_types.base import BaseCute
|
|
7
|
+
from telegrinder.bot.cute_types.update import UpdateCute
|
|
8
|
+
from telegrinder.bot.dispatch.context import Context
|
|
9
|
+
from telegrinder.bot.rules.adapter.abc import ABCAdapter
|
|
10
|
+
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
11
|
+
from telegrinder.bot.rules.adapter.raw_update import RawUpdateAdapter
|
|
12
|
+
from telegrinder.types.enums import UpdateType
|
|
13
|
+
from telegrinder.types.objects import Model, Update
|
|
14
|
+
|
|
15
|
+
ToCute = typing.TypeVar("ToCute", bound=BaseCute)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EventAdapter(ABCAdapter[Update, ToCute]):
|
|
19
|
+
ADAPTED_VALUE_KEY: str = "_adapted_cute_event"
|
|
20
|
+
|
|
21
|
+
def __init__(self, event: UpdateType | type[Model], cute_model: type[ToCute]) -> None:
|
|
22
|
+
self.event = event
|
|
23
|
+
self.cute_model = cute_model
|
|
24
|
+
|
|
25
|
+
def __repr__(self) -> str:
|
|
26
|
+
raw_update_type = (
|
|
27
|
+
f"Update -> {self.event.__name__}"
|
|
28
|
+
if isinstance(self.event, type)
|
|
29
|
+
else f"Update.{self.event.value}"
|
|
30
|
+
)
|
|
31
|
+
return "<{}: adapt {} -> {}>".format(
|
|
32
|
+
self.__class__.__name__,
|
|
33
|
+
raw_update_type,
|
|
34
|
+
self.cute_model.__name__,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def get_event(self, update: UpdateCute) -> Model | None:
|
|
38
|
+
if isinstance(self.event, UpdateType) and self.event == update.update_type:
|
|
39
|
+
return update.incoming_update
|
|
40
|
+
|
|
41
|
+
if not isinstance(self.event, UpdateType) and (event := update.get_event(self.event)):
|
|
42
|
+
return event.unwrap()
|
|
43
|
+
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
def adapt(self, api: API, update: Update, context: Context) -> Result[ToCute, AdapterError]:
|
|
47
|
+
match RawUpdateAdapter().adapt(api, update, context):
|
|
48
|
+
case Ok(update_cute) if event := self.get_event(update_cute):
|
|
49
|
+
if self.ADAPTED_VALUE_KEY in context:
|
|
50
|
+
return Ok(context[self.ADAPTED_VALUE_KEY])
|
|
51
|
+
|
|
52
|
+
adapted = (
|
|
53
|
+
typing.cast(ToCute, event)
|
|
54
|
+
if isinstance(event, BaseCute)
|
|
55
|
+
else self.cute_model.from_update(event, bound_api=api)
|
|
56
|
+
)
|
|
57
|
+
context[self.ADAPTED_VALUE_KEY] = adapted
|
|
58
|
+
return Ok(adapted)
|
|
59
|
+
case Error(_) as err:
|
|
60
|
+
return err
|
|
61
|
+
case _:
|
|
62
|
+
return Error(AdapterError(f"Update is not an {self.event!r}."))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__all__ = ("EventAdapter",)
|