telegrinder 0.2.1__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +22 -11
- telegrinder/bot/__init__.py +16 -4
- telegrinder/bot/cute_types/chat_member_updated.py +1 -1
- telegrinder/bot/cute_types/message.py +18 -11
- telegrinder/bot/dispatch/__init__.py +18 -2
- telegrinder/bot/dispatch/abc.py +1 -1
- telegrinder/bot/dispatch/context.py +2 -2
- telegrinder/bot/dispatch/dispatch.py +1 -1
- telegrinder/bot/dispatch/handler/__init__.py +17 -1
- telegrinder/bot/dispatch/handler/audio_reply.py +44 -0
- telegrinder/bot/dispatch/handler/base.py +57 -0
- telegrinder/bot/dispatch/handler/document_reply.py +44 -0
- telegrinder/bot/dispatch/handler/func.py +3 -3
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
- telegrinder/bot/dispatch/handler/message_reply.py +12 -35
- telegrinder/bot/dispatch/handler/photo_reply.py +44 -0
- telegrinder/bot/dispatch/handler/sticker_reply.py +37 -0
- telegrinder/bot/dispatch/handler/video_reply.py +44 -0
- telegrinder/bot/dispatch/process.py +2 -2
- telegrinder/bot/dispatch/return_manager/abc.py +11 -8
- telegrinder/bot/dispatch/return_manager/callback_query.py +2 -2
- telegrinder/bot/dispatch/return_manager/inline_query.py +2 -2
- telegrinder/bot/dispatch/return_manager/message.py +3 -3
- telegrinder/bot/dispatch/view/__init__.py +2 -1
- telegrinder/bot/dispatch/view/abc.py +2 -181
- telegrinder/bot/dispatch/view/base.py +200 -0
- telegrinder/bot/dispatch/view/callback_query.py +3 -3
- telegrinder/bot/dispatch/view/chat_join_request.py +2 -2
- telegrinder/bot/dispatch/view/chat_member.py +2 -3
- telegrinder/bot/dispatch/view/inline_query.py +2 -2
- telegrinder/bot/dispatch/view/message.py +5 -4
- telegrinder/bot/dispatch/view/raw.py +4 -3
- telegrinder/bot/dispatch/waiter_machine/machine.py +6 -7
- telegrinder/bot/dispatch/waiter_machine/middleware.py +0 -6
- telegrinder/bot/dispatch/waiter_machine/short_state.py +1 -1
- telegrinder/bot/polling/polling.py +5 -2
- telegrinder/bot/rules/__init__.py +3 -3
- telegrinder/bot/rules/abc.py +6 -5
- telegrinder/bot/rules/adapter/__init__.py +1 -1
- telegrinder/bot/rules/integer.py +1 -1
- telegrinder/bot/rules/is_from.py +19 -0
- telegrinder/bot/rules/state.py +9 -6
- telegrinder/bot/scenario/checkbox.py +3 -3
- telegrinder/bot/scenario/choice.py +2 -2
- telegrinder/client/aiohttp.py +5 -7
- telegrinder/model.py +1 -8
- telegrinder/modules.py +16 -25
- telegrinder/msgspec_utils.py +5 -5
- telegrinder/node/base.py +2 -2
- telegrinder/node/composer.py +5 -9
- telegrinder/node/container.py +6 -1
- telegrinder/node/polymorphic.py +7 -7
- telegrinder/node/rule.py +6 -4
- telegrinder/node/scope.py +3 -3
- telegrinder/node/source.py +4 -2
- telegrinder/node/tools/generator.py +7 -6
- telegrinder/rules.py +2 -2
- telegrinder/tools/__init__.py +10 -10
- telegrinder/tools/keyboard.py +6 -1
- telegrinder/tools/loop_wrapper/loop_wrapper.py +4 -5
- telegrinder/tools/magic.py +17 -19
- telegrinder/tools/state_storage/__init__.py +3 -3
- telegrinder/tools/state_storage/abc.py +12 -10
- telegrinder/tools/state_storage/memory.py +6 -3
- telegrinder/types/__init__.py +1 -0
- telegrinder/types/methods.py +10 -2
- telegrinder/types/objects.py +47 -5
- {telegrinder-0.2.1.dist-info → telegrinder-0.2.2.dist-info}/METADATA +3 -4
- {telegrinder-0.2.1.dist-info → telegrinder-0.2.2.dist-info}/RECORD +71 -63
- {telegrinder-0.2.1.dist-info → telegrinder-0.2.2.dist-info}/LICENSE +0 -0
- {telegrinder-0.2.1.dist-info → telegrinder-0.2.2.dist-info}/WHEEL +0 -0
telegrinder/modules.py
CHANGED
|
@@ -4,13 +4,6 @@ import typing
|
|
|
4
4
|
from choicelib import choice_in_order
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
@typing.runtime_checkable
|
|
8
|
-
class JSONModule(typing.Protocol):
|
|
9
|
-
def loads(self, s: str | bytes) -> typing.Any: ...
|
|
10
|
-
|
|
11
|
-
def dumps(self, o: typing.Any) -> str: ...
|
|
12
|
-
|
|
13
|
-
|
|
14
7
|
@typing.runtime_checkable
|
|
15
8
|
class LoggerModule(typing.Protocol):
|
|
16
9
|
def debug(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
@@ -25,25 +18,23 @@ class LoggerModule(typing.Protocol):
|
|
|
25
18
|
|
|
26
19
|
def exception(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
if typing.TYPE_CHECKING:
|
|
22
|
+
|
|
23
|
+
def set_level(
|
|
24
|
+
self,
|
|
25
|
+
level: typing.Literal[
|
|
26
|
+
"DEBUG",
|
|
27
|
+
"INFO",
|
|
28
|
+
"WARNING",
|
|
29
|
+
"ERROR",
|
|
30
|
+
"CRITICAL",
|
|
31
|
+
"EXCEPTION",
|
|
32
|
+
],
|
|
33
|
+
/,
|
|
34
|
+
) -> None: ...
|
|
39
35
|
|
|
40
36
|
|
|
41
37
|
logger: LoggerModule
|
|
42
|
-
json: JSONModule = choice_in_order(
|
|
43
|
-
["orjson", "ujson", "hyperjson"],
|
|
44
|
-
default="telegrinder.msgspec_json",
|
|
45
|
-
do_import=True,
|
|
46
|
-
)
|
|
47
38
|
logging_level = os.getenv("LOGGER_LEVEL", default="DEBUG").upper()
|
|
48
39
|
logging_module = choice_in_order(["loguru"], default="logging")
|
|
49
40
|
asyncio_module = choice_in_order(["uvloop"], default="asyncio")
|
|
@@ -227,7 +218,7 @@ if asyncio_module == "uvloop":
|
|
|
227
218
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) # type: ignore
|
|
228
219
|
|
|
229
220
|
|
|
230
|
-
def _set_logger_level(level):
|
|
221
|
+
def _set_logger_level(level, /):
|
|
231
222
|
level = level.upper()
|
|
232
223
|
if logging_module == "logging":
|
|
233
224
|
import logging
|
|
@@ -243,4 +234,4 @@ def _set_logger_level(level):
|
|
|
243
234
|
setattr(logger, "set_level", staticmethod(_set_logger_level)) # type: ignore
|
|
244
235
|
|
|
245
236
|
|
|
246
|
-
__all__ = ("
|
|
237
|
+
__all__ = ("LoggerModule", "logger")
|
telegrinder/msgspec_utils.py
CHANGED
|
@@ -29,7 +29,6 @@ else:
|
|
|
29
29
|
def __instancecheck__(cls, __instance: typing.Any) -> bool:
|
|
30
30
|
return isinstance(__instance, fntypes.option.Some | fntypes.option.Nothing)
|
|
31
31
|
|
|
32
|
-
|
|
33
32
|
class Option(typing.Generic[Value], metaclass=OptionMeta):
|
|
34
33
|
pass
|
|
35
34
|
|
|
@@ -63,9 +62,10 @@ def is_common_type(type_: typing.Any) -> typing.TypeGuard[type[typing.Any]]:
|
|
|
63
62
|
def type_check(obj: typing.Any, t: typing.Any) -> bool:
|
|
64
63
|
return (
|
|
65
64
|
isinstance(obj, t)
|
|
66
|
-
if isinstance(t, type)
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
if isinstance(t, type) and issubclass(t, msgspec.Struct)
|
|
66
|
+
else type(obj) in t
|
|
67
|
+
if isinstance(t, tuple)
|
|
68
|
+
else type(obj) is t
|
|
69
69
|
)
|
|
70
70
|
|
|
71
71
|
|
|
@@ -344,8 +344,8 @@ __all__ = (
|
|
|
344
344
|
"datetime",
|
|
345
345
|
"decoder",
|
|
346
346
|
"encoder",
|
|
347
|
-
"msgspec_convert",
|
|
348
347
|
"get_class_annotations",
|
|
349
348
|
"get_type_hints",
|
|
349
|
+
"msgspec_convert",
|
|
350
350
|
"msgspec_to_builtins",
|
|
351
351
|
)
|
telegrinder/node/base.py
CHANGED
|
@@ -12,8 +12,8 @@ from telegrinder.tools.magic import (
|
|
|
12
12
|
ComposeResult: typing.TypeAlias = typing.Awaitable[typing.Any] | typing.AsyncGenerator[typing.Any, None]
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def is_node(maybe_node:
|
|
16
|
-
maybe_node = typing.get_origin(maybe_node)
|
|
15
|
+
def is_node(maybe_node: typing.Any) -> typing.TypeGuard[type["Node"]]:
|
|
16
|
+
maybe_node = maybe_node if isinstance(maybe_node, type) else typing.get_origin(maybe_node)
|
|
17
17
|
return (
|
|
18
18
|
isinstance(maybe_node, type)
|
|
19
19
|
and issubclass(maybe_node, Node)
|
telegrinder/node/composer.py
CHANGED
|
@@ -4,7 +4,7 @@ import typing
|
|
|
4
4
|
from fntypes import Error, Ok, Result
|
|
5
5
|
from fntypes.error import UnwrapError
|
|
6
6
|
|
|
7
|
-
from telegrinder.api import API
|
|
7
|
+
from telegrinder.api.api import API
|
|
8
8
|
from telegrinder.bot.cute_types.update import Update, UpdateCute
|
|
9
9
|
from telegrinder.bot.dispatch.context import Context
|
|
10
10
|
from telegrinder.modules import logger
|
|
@@ -41,7 +41,7 @@ async def compose_node(
|
|
|
41
41
|
async def compose_nodes(
|
|
42
42
|
nodes: dict[str, type[Node]],
|
|
43
43
|
ctx: Context,
|
|
44
|
-
data: dict[type, typing.Any] | None = None,
|
|
44
|
+
data: dict[type[typing.Any], typing.Any] | None = None,
|
|
45
45
|
) -> Result["NodeCollection", ComposeError]:
|
|
46
46
|
logger.debug("Composing nodes: {!r}...", nodes)
|
|
47
47
|
|
|
@@ -67,11 +67,7 @@ async def compose_nodes(
|
|
|
67
67
|
local_nodes[node_t] = getattr(node_t, GLOBAL_VALUE_KEY)
|
|
68
68
|
continue
|
|
69
69
|
|
|
70
|
-
subnodes = {
|
|
71
|
-
k: session.value
|
|
72
|
-
for k, session in
|
|
73
|
-
(local_nodes | event_nodes).items()
|
|
74
|
-
}
|
|
70
|
+
subnodes = {k: session.value for k, session in (local_nodes | event_nodes).items()}
|
|
75
71
|
|
|
76
72
|
try:
|
|
77
73
|
local_nodes[node_t] = await compose_node(node_t, subnodes | data)
|
|
@@ -175,11 +171,11 @@ class Composition:
|
|
|
175
171
|
case Ok(col):
|
|
176
172
|
return col
|
|
177
173
|
case Error(err):
|
|
178
|
-
logger.debug(f"Composition failed with error: {err}")
|
|
174
|
+
logger.debug(f"Composition failed with error: {err!r}")
|
|
179
175
|
return None
|
|
180
176
|
|
|
181
177
|
async def __call__(self, **kwargs: typing.Any) -> typing.Any:
|
|
182
|
-
return await self.func(**magic_bundle(self.func, kwargs, start_idx=0, bundle_ctx=False))
|
|
178
|
+
return await self.func(**magic_bundle(self.func, kwargs, start_idx=0, bundle_ctx=False))
|
|
183
179
|
|
|
184
180
|
|
|
185
181
|
__all__ = ("Composition", "NodeCollection", "NodeSession", "compose_node", "compose_nodes")
|
telegrinder/node/container.py
CHANGED
|
@@ -12,7 +12,12 @@ class ContainerNode(Node):
|
|
|
12
12
|
|
|
13
13
|
@classmethod
|
|
14
14
|
def get_subnodes(cls) -> dict[str, type[Node]]:
|
|
15
|
-
|
|
15
|
+
subnodes = getattr(cls, "subnodes", None)
|
|
16
|
+
if subnodes is None:
|
|
17
|
+
subnodes_dct = {f"node_{i}": node_t for i, node_t in enumerate(cls.linked_nodes)}
|
|
18
|
+
setattr(cls, "subnodes", subnodes_dct)
|
|
19
|
+
return subnodes_dct
|
|
20
|
+
return subnodes
|
|
16
21
|
|
|
17
22
|
@classmethod
|
|
18
23
|
def link_nodes(cls, linked_nodes: list[type[Node]]) -> type["ContainerNode"]:
|
telegrinder/node/polymorphic.py
CHANGED
|
@@ -13,21 +13,21 @@ from telegrinder.tools.magic import get_impls, impl
|
|
|
13
13
|
class Polymorphic(Node):
|
|
14
14
|
@classmethod
|
|
15
15
|
async def compose(cls, update: UpdateNode, context: Context) -> typing.Any:
|
|
16
|
-
logger.debug(f"Composing polimorphic node {cls.__name__}")
|
|
16
|
+
logger.debug(f"Composing polimorphic node {cls.__name__!r}...")
|
|
17
17
|
scope = getattr(cls, "scope", None)
|
|
18
18
|
node_ctx = context.get_or_set(CONTEXT_STORE_NODES_KEY, {})
|
|
19
19
|
|
|
20
|
-
for i,
|
|
21
|
-
logger.debug("Checking impl {}",
|
|
22
|
-
composition = Composition(
|
|
20
|
+
for i, impl_ in enumerate(get_impls(cls)):
|
|
21
|
+
logger.debug("Checking impl {!r}...", impl_.__name__)
|
|
22
|
+
composition = Composition(impl_, True)
|
|
23
23
|
node_collection = await composition.compose_nodes(update, context)
|
|
24
24
|
if node_collection is None:
|
|
25
|
-
logger.debug("Impl {!r} composition failed",
|
|
25
|
+
logger.debug("Impl {!r} composition failed!", impl_.__name__)
|
|
26
26
|
continue
|
|
27
27
|
|
|
28
28
|
# To determine whether this is a right morph, all subnodes must be resolved
|
|
29
29
|
if scope is NodeScope.PER_EVENT and (cls, i) in node_ctx:
|
|
30
|
-
logger.debug("Morph is already cached as per_event node, using its value. Impl {!r} succeeded",
|
|
30
|
+
logger.debug("Morph is already cached as per_event node, using its value. Impl {!r} succeeded!", impl_.__name__)
|
|
31
31
|
res: NodeSession = node_ctx[(cls, i)]
|
|
32
32
|
await node_collection.close_all()
|
|
33
33
|
return res.value
|
|
@@ -40,7 +40,7 @@ class Polymorphic(Node):
|
|
|
40
40
|
node_ctx[(cls, i)] = NodeSession(cls, result, {})
|
|
41
41
|
|
|
42
42
|
await node_collection.close_all(with_value=result)
|
|
43
|
-
logger.debug("Impl {!r} succeeded with value {}",
|
|
43
|
+
logger.debug("Impl {!r} succeeded with value: {}", impl_.__name__, result)
|
|
44
44
|
return result
|
|
45
45
|
|
|
46
46
|
raise ComposeError("No implementation found.")
|
telegrinder/node/rule.py
CHANGED
|
@@ -11,7 +11,7 @@ if typing.TYPE_CHECKING:
|
|
|
11
11
|
from telegrinder.bot.rules.abc import ABCRule
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class RuleChain(dict[str, typing.Any]):
|
|
14
|
+
class RuleChain(dict[str, typing.Any], Node):
|
|
15
15
|
dataclass = dict
|
|
16
16
|
rules: tuple["ABCRule", ...] = ()
|
|
17
17
|
|
|
@@ -28,7 +28,6 @@ class RuleChain(dict[str, typing.Any]):
|
|
|
28
28
|
def __class_getitem__(cls, items: "ABCRule | tuple[ABCRule, ...]", /) -> typing.Self:
|
|
29
29
|
if not isinstance(items, tuple):
|
|
30
30
|
items = (items,)
|
|
31
|
-
assert all(isinstance(rule, "ABCRule") for rule in items), "All items must be instances of 'ABCRule'."
|
|
32
31
|
return cls(*items)
|
|
33
32
|
|
|
34
33
|
@staticmethod
|
|
@@ -37,6 +36,7 @@ class RuleChain(dict[str, typing.Any]):
|
|
|
37
36
|
|
|
38
37
|
@classmethod
|
|
39
38
|
async def compose(cls, update: UpdateNode) -> typing.Any:
|
|
39
|
+
# Hack to avoid circular import
|
|
40
40
|
globalns = globals()
|
|
41
41
|
if "check_rule" not in globalns:
|
|
42
42
|
globalns.update(
|
|
@@ -54,7 +54,9 @@ class RuleChain(dict[str, typing.Any]):
|
|
|
54
54
|
raise ComposeError(f"Rule {rule!r} failed!")
|
|
55
55
|
|
|
56
56
|
try:
|
|
57
|
-
|
|
57
|
+
if dataclasses.is_dataclass(cls.dataclass):
|
|
58
|
+
return cls.dataclass(**{k: ctx[k] for k in cls.__annotations__})
|
|
59
|
+
return cls.dataclass(**ctx)
|
|
58
60
|
except Exception as exc:
|
|
59
61
|
raise ComposeError(f"Dataclass validation error: {exc}")
|
|
60
62
|
|
|
@@ -63,7 +65,7 @@ class RuleChain(dict[str, typing.Any]):
|
|
|
63
65
|
return cls
|
|
64
66
|
|
|
65
67
|
@classmethod
|
|
66
|
-
def get_subnodes(cls) -> dict:
|
|
68
|
+
def get_subnodes(cls) -> dict[typing.Literal["update"], type[UpdateNode]]:
|
|
67
69
|
return {"update": UpdateNode}
|
|
68
70
|
|
|
69
71
|
@classmethod
|
telegrinder/node/scope.py
CHANGED
telegrinder/node/source.py
CHANGED
|
@@ -39,7 +39,9 @@ class Source(Polymorphic, DataNode):
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
@impl
|
|
42
|
-
async def compose_chat_join_request(
|
|
42
|
+
async def compose_chat_join_request(
|
|
43
|
+
cls, chat_join_request: EventNode[ChatJoinRequestCute]
|
|
44
|
+
) -> typing.Self:
|
|
43
45
|
return cls(
|
|
44
46
|
api=chat_join_request.ctx_api,
|
|
45
47
|
chat=chat_join_request.chat,
|
|
@@ -68,4 +70,4 @@ class UserSource(ScalarNode, User):
|
|
|
68
70
|
return source.from_user
|
|
69
71
|
|
|
70
72
|
|
|
71
|
-
__all__ = ("
|
|
73
|
+
__all__ = ("ChatSource", "Source", "UserSource")
|
|
@@ -20,13 +20,13 @@ def error_on_none(value: T | None) -> T:
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def generate_node(
|
|
23
|
-
subnodes: tuple[type[
|
|
24
|
-
func: typing.Callable[...,
|
|
23
|
+
subnodes: tuple[type[Node], ...],
|
|
24
|
+
func: typing.Callable[..., typing.Any],
|
|
25
25
|
casts: tuple[typing.Callable[[typing.Any], typing.Any], ...] = (cast_false_to_none, error_on_none),
|
|
26
|
-
) -> type[
|
|
27
|
-
async def compose(**kw
|
|
26
|
+
) -> type[Node]:
|
|
27
|
+
async def compose(cls, **kw) -> typing.Any:
|
|
28
28
|
args = await ContainerNode.compose(**kw)
|
|
29
|
-
result = func(*args)
|
|
29
|
+
result = func(*args) # type: ignore
|
|
30
30
|
if inspect.isawaitable(result):
|
|
31
31
|
result = await result
|
|
32
32
|
for cast in casts:
|
|
@@ -34,7 +34,8 @@ def generate_node(
|
|
|
34
34
|
return result
|
|
35
35
|
|
|
36
36
|
container = ContainerNode.link_nodes(list(subnodes))
|
|
37
|
-
|
|
37
|
+
compose.__annotations__ = container.get_subnodes()
|
|
38
|
+
return type("_ContainerNode", (container,), {"compose": classmethod(compose)})
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
__all__ = ("generate_node",)
|
telegrinder/rules.py
CHANGED
|
@@ -26,7 +26,6 @@ __all__ = (
|
|
|
26
26
|
"InlineQueryMarkup",
|
|
27
27
|
"InlineQueryRule",
|
|
28
28
|
"InlineQueryText",
|
|
29
|
-
"IsInteger",
|
|
30
29
|
"IntegerInRange",
|
|
31
30
|
"InviteLinkByCreator",
|
|
32
31
|
"InviteLinkName",
|
|
@@ -39,6 +38,7 @@ __all__ = (
|
|
|
39
38
|
"IsForward",
|
|
40
39
|
"IsForwardType",
|
|
41
40
|
"IsGroup",
|
|
41
|
+
"IsInteger",
|
|
42
42
|
"IsLanguageCode",
|
|
43
43
|
"IsPremium",
|
|
44
44
|
"IsPrivate",
|
|
@@ -50,11 +50,11 @@ __all__ = (
|
|
|
50
50
|
"Markup",
|
|
51
51
|
"MessageEntities",
|
|
52
52
|
"MessageRule",
|
|
53
|
+
"NodeRule",
|
|
53
54
|
"NotRule",
|
|
54
55
|
"OrRule",
|
|
55
56
|
"Regex",
|
|
56
57
|
"RuleEnum",
|
|
57
58
|
"StartCommand",
|
|
58
59
|
"Text",
|
|
59
|
-
"NodeRule",
|
|
60
60
|
)
|
telegrinder/tools/__init__.py
CHANGED
|
@@ -74,6 +74,7 @@ __all__ = (
|
|
|
74
74
|
"ABCGlobalContext",
|
|
75
75
|
"ABCI18n",
|
|
76
76
|
"ABCLoopWrapper",
|
|
77
|
+
"ABCStateStorage",
|
|
77
78
|
"ABCTranslator",
|
|
78
79
|
"ABCTranslatorMiddleware",
|
|
79
80
|
"AnyMarkup",
|
|
@@ -101,6 +102,7 @@ __all__ = (
|
|
|
101
102
|
"LimitedDict",
|
|
102
103
|
"Link",
|
|
103
104
|
"LoopWrapper",
|
|
105
|
+
"MemoryStateStorage",
|
|
104
106
|
"Mention",
|
|
105
107
|
"ParseMode",
|
|
106
108
|
"PreCode",
|
|
@@ -111,8 +113,14 @@ __all__ = (
|
|
|
111
113
|
"SpecialFormat",
|
|
112
114
|
"StartBotLink",
|
|
113
115
|
"StartGroupLink",
|
|
116
|
+
"StateData",
|
|
114
117
|
"TelegrinderContext",
|
|
115
118
|
"TgEmoji",
|
|
119
|
+
"block_quote",
|
|
120
|
+
"bold",
|
|
121
|
+
"channel_boost_link",
|
|
122
|
+
"code_inline",
|
|
123
|
+
"ctx_var",
|
|
116
124
|
"escape",
|
|
117
125
|
"get_channel_boost_link",
|
|
118
126
|
"get_invite_chat_link",
|
|
@@ -120,12 +128,14 @@ __all__ = (
|
|
|
120
128
|
"get_resolve_domain_link",
|
|
121
129
|
"get_start_bot_link",
|
|
122
130
|
"get_start_group_link",
|
|
131
|
+
"impl",
|
|
123
132
|
"invite_chat_link",
|
|
124
133
|
"italic",
|
|
125
134
|
"link",
|
|
126
135
|
"magic_bundle",
|
|
127
136
|
"mention",
|
|
128
137
|
"pre_code",
|
|
138
|
+
"resolve_arg_names",
|
|
129
139
|
"resolve_domain",
|
|
130
140
|
"spoiler",
|
|
131
141
|
"start_bot_link",
|
|
@@ -133,14 +143,4 @@ __all__ = (
|
|
|
133
143
|
"strike",
|
|
134
144
|
"tg_emoji",
|
|
135
145
|
"underline",
|
|
136
|
-
"bold",
|
|
137
|
-
"channel_boost_link",
|
|
138
|
-
"code_inline",
|
|
139
|
-
"ctx_var",
|
|
140
|
-
"block_quote",
|
|
141
|
-
"impl",
|
|
142
|
-
"resolve_arg_names",
|
|
143
|
-
"ABCStateStorage",
|
|
144
|
-
"MemoryStateStorage",
|
|
145
|
-
"StateData",
|
|
146
146
|
)
|
telegrinder/tools/keyboard.py
CHANGED
|
@@ -26,6 +26,11 @@ class KeyboardModel:
|
|
|
26
26
|
keyboard: list[list[DictStrAny]]
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
|
|
30
|
+
def copy_keyboard(keyboard: list[list[DictStrAny]]) -> list[list[DictStrAny]]:
|
|
31
|
+
return [row.copy() for row in keyboard]
|
|
32
|
+
|
|
33
|
+
|
|
29
34
|
class ABCMarkup(ABC, typing.Generic[ButtonT]):
|
|
30
35
|
BUTTON: type[ButtonT]
|
|
31
36
|
keyboard: list[list[DictStrAny]]
|
|
@@ -73,7 +78,7 @@ class ABCMarkup(ABC, typing.Generic[ButtonT]):
|
|
|
73
78
|
return copy_keyboard
|
|
74
79
|
|
|
75
80
|
def merge(self, other: typing.Self) -> typing.Self:
|
|
76
|
-
self.keyboard.extend(other.keyboard)
|
|
81
|
+
self.keyboard.extend(copy_keyboard(other.keyboard))
|
|
77
82
|
return self
|
|
78
83
|
|
|
79
84
|
|
|
@@ -44,17 +44,16 @@ class DelayedTask(typing.Generic[CoroFunc]):
|
|
|
44
44
|
def is_cancelled(self) -> bool:
|
|
45
45
|
return self._cancelled
|
|
46
46
|
|
|
47
|
-
async def __call__(self, *args, **kwargs) -> None:
|
|
47
|
+
async def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
48
48
|
while not self.is_cancelled:
|
|
49
49
|
await asyncio.sleep(self.seconds)
|
|
50
50
|
if self.is_cancelled:
|
|
51
51
|
break
|
|
52
52
|
try:
|
|
53
53
|
await self.handler(*args, **kwargs)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
break
|
|
54
|
+
finally:
|
|
55
|
+
if not self.repeat:
|
|
56
|
+
break
|
|
58
57
|
|
|
59
58
|
def cancel(self) -> None:
|
|
60
59
|
if not self._cancelled:
|
telegrinder/tools/magic.py
CHANGED
|
@@ -6,7 +6,7 @@ from functools import wraps
|
|
|
6
6
|
|
|
7
7
|
if typing.TYPE_CHECKING:
|
|
8
8
|
from telegrinder.bot.rules.abc import ABCRule
|
|
9
|
-
from telegrinder.node.
|
|
9
|
+
from telegrinder.node.polymorphic import Polymorphic
|
|
10
10
|
|
|
11
11
|
T = typing.TypeVar("T", bound=ABCRule)
|
|
12
12
|
F = typing.TypeVar(
|
|
@@ -23,7 +23,6 @@ IMPL_MARK: typing.Final[str] = "_is_impl"
|
|
|
23
23
|
|
|
24
24
|
def cache_magic_value(mark_key: str, /):
|
|
25
25
|
def inner(func: "F") -> "F":
|
|
26
|
-
|
|
27
26
|
@wraps(func)
|
|
28
27
|
def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
|
|
29
28
|
if mark_key not in args[0].__dict__:
|
|
@@ -31,6 +30,7 @@ def cache_magic_value(mark_key: str, /):
|
|
|
31
30
|
return args[0].__dict__[mark_key]
|
|
32
31
|
|
|
33
32
|
return wrapper # type: ignore
|
|
33
|
+
|
|
34
34
|
return inner
|
|
35
35
|
|
|
36
36
|
|
|
@@ -43,13 +43,13 @@ def get_default_args(func: FuncType) -> dict[str, typing.Any]:
|
|
|
43
43
|
fspec = inspect.getfullargspec(func)
|
|
44
44
|
if not fspec.defaults:
|
|
45
45
|
return {}
|
|
46
|
-
return dict(zip(fspec.args[-len(fspec.defaults):], fspec.defaults))
|
|
46
|
+
return dict(zip(fspec.args[-len(fspec.defaults) :], fspec.defaults))
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def get_annotations(func: FuncType, *, return_type: bool = False) -> dict[str, typing.Any]:
|
|
50
50
|
annotations = func.__annotations__
|
|
51
51
|
if not return_type and "return" in func.__annotations__:
|
|
52
|
-
|
|
52
|
+
annotations.pop("return")
|
|
53
53
|
return annotations
|
|
54
54
|
|
|
55
55
|
|
|
@@ -60,13 +60,11 @@ def to_str(s: str | enum.Enum) -> str:
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
@typing.overload
|
|
63
|
-
def magic_bundle(handler: FuncType, kw: dict[str, typing.Any]) -> dict[str, typing.Any]:
|
|
64
|
-
...
|
|
63
|
+
def magic_bundle(handler: FuncType, kw: dict[str, typing.Any]) -> dict[str, typing.Any]: ...
|
|
65
64
|
|
|
66
65
|
|
|
67
66
|
@typing.overload
|
|
68
|
-
def magic_bundle(handler: FuncType, kw: dict[enum.Enum, typing.Any]) -> dict[str, typing.Any]:
|
|
69
|
-
...
|
|
67
|
+
def magic_bundle(handler: FuncType, kw: dict[enum.Enum, typing.Any]) -> dict[str, typing.Any]: ...
|
|
70
68
|
|
|
71
69
|
|
|
72
70
|
@typing.overload
|
|
@@ -76,8 +74,7 @@ def magic_bundle(
|
|
|
76
74
|
*,
|
|
77
75
|
start_idx: int = 1,
|
|
78
76
|
bundle_ctx: bool = True,
|
|
79
|
-
) -> dict[str, typing.Any]:
|
|
80
|
-
...
|
|
77
|
+
) -> dict[str, typing.Any]: ...
|
|
81
78
|
|
|
82
79
|
|
|
83
80
|
@typing.overload
|
|
@@ -87,8 +84,7 @@ def magic_bundle(
|
|
|
87
84
|
*,
|
|
88
85
|
start_idx: int = 1,
|
|
89
86
|
bundle_ctx: bool = True,
|
|
90
|
-
) -> dict[str, typing.Any]:
|
|
91
|
-
...
|
|
87
|
+
) -> dict[str, typing.Any]: ...
|
|
92
88
|
|
|
93
89
|
|
|
94
90
|
@typing.overload
|
|
@@ -97,8 +93,7 @@ def magic_bundle(
|
|
|
97
93
|
kw: dict[type, typing.Any],
|
|
98
94
|
*,
|
|
99
95
|
typebundle: typing.Literal[True] = True,
|
|
100
|
-
) -> dict[str, typing.Any]:
|
|
101
|
-
...
|
|
96
|
+
) -> dict[str, typing.Any]: ...
|
|
102
97
|
|
|
103
98
|
|
|
104
99
|
def magic_bundle(
|
|
@@ -109,7 +104,6 @@ def magic_bundle(
|
|
|
109
104
|
bundle_ctx: bool = True,
|
|
110
105
|
typebundle: bool = False,
|
|
111
106
|
) -> dict[str, typing.Any]:
|
|
112
|
-
|
|
113
107
|
if typebundle:
|
|
114
108
|
types = get_annotations(handler, return_type=False)
|
|
115
109
|
bundle: dict[str, typing.Any] = {}
|
|
@@ -141,13 +135,17 @@ def impl(method: typing.Callable[..., typing.Any]):
|
|
|
141
135
|
return classmethod(method)
|
|
142
136
|
|
|
143
137
|
|
|
144
|
-
def get_impls(cls: type["
|
|
145
|
-
|
|
138
|
+
def get_impls(cls: type["Polymorphic"]) -> list[typing.Callable[..., typing.Any]]:
|
|
139
|
+
moprh_impls = getattr(cls, "__morph_impls__", None)
|
|
140
|
+
if moprh_impls is not None:
|
|
141
|
+
return moprh_impls
|
|
142
|
+
impls = [
|
|
146
143
|
func.__func__
|
|
147
144
|
for func in vars(cls).values()
|
|
148
145
|
if isinstance(func, classmethod) and getattr(func.__func__, IMPL_MARK, False)
|
|
149
146
|
]
|
|
150
|
-
|
|
147
|
+
setattr(cls, "__morph_impls__", impls)
|
|
148
|
+
return impls
|
|
151
149
|
|
|
152
150
|
|
|
153
151
|
__all__ = (
|
|
@@ -158,8 +156,8 @@ __all__ = (
|
|
|
158
156
|
"get_cached_translation",
|
|
159
157
|
"get_default_args",
|
|
160
158
|
"get_default_args",
|
|
161
|
-
"impl",
|
|
162
159
|
"get_impls",
|
|
160
|
+
"impl",
|
|
163
161
|
"magic_bundle",
|
|
164
162
|
"resolve_arg_names",
|
|
165
163
|
"to_str",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .abc import ABCStateStorage, StateData
|
|
2
|
-
from .memory import MemoryStateStorage
|
|
1
|
+
from telegrinder.tools.state_storage.abc import ABCStateStorage, StateData
|
|
2
|
+
from telegrinder.tools.state_storage.memory import MemoryStateStorage
|
|
3
3
|
|
|
4
|
-
__all__ = ("ABCStateStorage", "
|
|
4
|
+
__all__ = ("ABCStateStorage", "MemoryStateStorage", "StateData")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import abc
|
|
2
|
+
import enum
|
|
2
3
|
import typing
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
|
|
@@ -9,25 +10,26 @@ from telegrinder.bot.rules.state import State, StateMeta
|
|
|
9
10
|
Payload = typing.TypeVar("Payload")
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
@dataclass
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
13
14
|
class StateData(typing.Generic[Payload]):
|
|
14
|
-
key: str
|
|
15
|
+
key: str | enum.Enum
|
|
15
16
|
payload: Payload
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class ABCStateStorage(abc.ABC, typing.Generic[Payload]):
|
|
19
20
|
@abc.abstractmethod
|
|
20
|
-
async def get(self, user_id: int) -> Option[StateData[Payload]]:
|
|
21
|
-
...
|
|
21
|
+
async def get(self, user_id: int) -> Option[StateData[Payload]]: ...
|
|
22
22
|
|
|
23
23
|
@abc.abstractmethod
|
|
24
|
-
async def delete(self, user_id: int) -> None:
|
|
25
|
-
...
|
|
24
|
+
async def delete(self, user_id: int) -> None: ...
|
|
26
25
|
|
|
27
26
|
@abc.abstractmethod
|
|
28
|
-
async def set(self, user_id: int, key: str, payload: Payload) -> None:
|
|
29
|
-
|
|
27
|
+
async def set(self, user_id: int, key: str | enum.Enum, payload: Payload) -> None: ...
|
|
28
|
+
|
|
29
|
+
def State(self, key: str | StateMeta | enum.Enum = StateMeta.ANY, /) -> State[Payload]: # noqa: N802
|
|
30
|
+
"""Can be used as a shortcut to get a state rule dependant on current storage."""
|
|
30
31
|
|
|
31
|
-
def State(self, key: str | StateMeta = StateMeta.ANY) -> State: # noqa: N802
|
|
32
|
-
"""Can be used as a shortcut to get a state rule dependant on current storage"""
|
|
33
32
|
return State(storage=self, key=key)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__all__ = ("ABCStateStorage", "StateData")
|
|
@@ -2,13 +2,13 @@ import typing
|
|
|
2
2
|
|
|
3
3
|
from fntypes import Nothing, Option, Some
|
|
4
4
|
|
|
5
|
-
from .abc import ABCStateStorage, StateData
|
|
5
|
+
from telegrinder.tools.state_storage.abc import ABCStateStorage, StateData
|
|
6
6
|
|
|
7
|
-
Payload = dict[str, typing.Any]
|
|
7
|
+
Payload: typing.TypeAlias = dict[str, typing.Any]
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class MemoryStateStorage(ABCStateStorage[Payload]):
|
|
11
|
-
def __init__(self):
|
|
11
|
+
def __init__(self) -> None:
|
|
12
12
|
self.storage: dict[int, StateData[Payload]] = {}
|
|
13
13
|
|
|
14
14
|
async def get(self, user_id: int) -> Option[StateData[Payload]]:
|
|
@@ -20,3 +20,6 @@ class MemoryStateStorage(ABCStateStorage[Payload]):
|
|
|
20
20
|
|
|
21
21
|
async def delete(self, user_id: int) -> None:
|
|
22
22
|
self.storage.pop(user_id)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ("MemoryStateStorage",)
|