telegrinder 0.1.dev170__py3-none-any.whl → 0.1.dev171__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/api/abc.py +7 -1
- telegrinder/api/api.py +12 -3
- telegrinder/api/error.py +2 -1
- telegrinder/bot/bot.py +6 -1
- telegrinder/bot/cute_types/base.py +144 -17
- telegrinder/bot/cute_types/callback_query.py +6 -1
- telegrinder/bot/cute_types/chat_member_updated.py +1 -2
- telegrinder/bot/cute_types/message.py +23 -11
- telegrinder/bot/cute_types/update.py +48 -0
- telegrinder/bot/cute_types/utils.py +2 -465
- telegrinder/bot/dispatch/__init__.py +2 -3
- telegrinder/bot/dispatch/abc.py +6 -3
- telegrinder/bot/dispatch/context.py +6 -6
- telegrinder/bot/dispatch/dispatch.py +61 -23
- telegrinder/bot/dispatch/handler/abc.py +2 -2
- telegrinder/bot/dispatch/handler/func.py +36 -17
- telegrinder/bot/dispatch/handler/message_reply.py +2 -2
- telegrinder/bot/dispatch/middleware/abc.py +2 -2
- telegrinder/bot/dispatch/process.py +10 -10
- telegrinder/bot/dispatch/return_manager/abc.py +3 -3
- telegrinder/bot/dispatch/view/abc.py +12 -15
- telegrinder/bot/dispatch/view/box.py +73 -62
- telegrinder/bot/dispatch/view/message.py +11 -3
- telegrinder/bot/dispatch/view/raw.py +3 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +2 -2
- telegrinder/bot/dispatch/waiter_machine/middleware.py +1 -1
- telegrinder/bot/dispatch/waiter_machine/short_state.py +2 -1
- telegrinder/bot/polling/polling.py +3 -3
- telegrinder/bot/rules/abc.py +11 -7
- telegrinder/bot/rules/adapter/event.py +7 -4
- telegrinder/bot/rules/adapter/node.py +1 -1
- telegrinder/bot/rules/command.py +5 -7
- telegrinder/bot/rules/func.py +1 -1
- telegrinder/bot/rules/fuzzy.py +1 -1
- telegrinder/bot/rules/integer.py +1 -2
- telegrinder/bot/rules/markup.py +3 -3
- telegrinder/bot/rules/message_entities.py +1 -1
- telegrinder/bot/rules/node.py +2 -2
- telegrinder/bot/rules/regex.py +1 -1
- telegrinder/bot/rules/rule_enum.py +1 -1
- telegrinder/bot/scenario/checkbox.py +2 -2
- telegrinder/model.py +85 -46
- telegrinder/modules.py +3 -3
- telegrinder/msgspec_utils.py +33 -5
- telegrinder/node/__init__.py +20 -11
- telegrinder/node/attachment.py +19 -16
- telegrinder/node/base.py +120 -24
- telegrinder/node/callback_query.py +5 -9
- telegrinder/node/command.py +6 -2
- telegrinder/node/composer.py +82 -54
- telegrinder/node/container.py +4 -4
- telegrinder/node/event.py +59 -0
- telegrinder/node/me.py +3 -0
- telegrinder/node/message.py +6 -10
- telegrinder/node/polymorphic.py +11 -12
- telegrinder/node/rule.py +27 -5
- telegrinder/node/source.py +10 -11
- telegrinder/node/text.py +4 -4
- telegrinder/node/update.py +1 -2
- telegrinder/py.typed +0 -0
- telegrinder/tools/__init__.py +2 -2
- telegrinder/tools/buttons.py +5 -10
- telegrinder/tools/error_handler/error.py +2 -0
- telegrinder/tools/error_handler/error_handler.py +1 -1
- telegrinder/tools/formatting/spec_html_formats.py +10 -10
- telegrinder/tools/global_context/__init__.py +2 -2
- telegrinder/tools/global_context/global_context.py +2 -2
- telegrinder/tools/global_context/telegrinder_ctx.py +4 -4
- telegrinder/tools/keyboard.py +2 -2
- telegrinder/tools/loop_wrapper/loop_wrapper.py +39 -5
- telegrinder/tools/magic.py +48 -15
- telegrinder/types/enums.py +1 -0
- telegrinder/types/methods.py +14 -5
- telegrinder/types/objects.py +3 -0
- {telegrinder-0.1.dev170.dist-info → telegrinder-0.1.dev171.dist-info}/METADATA +2 -2
- telegrinder-0.1.dev171.dist-info/RECORD +145 -0
- telegrinder-0.1.dev170.dist-info/RECORD +0 -143
- {telegrinder-0.1.dev170.dist-info → telegrinder-0.1.dev171.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev170.dist-info → telegrinder-0.1.dev171.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
import msgspec
|
|
4
|
+
|
|
5
|
+
from telegrinder.bot.dispatch.context import Context
|
|
6
|
+
from telegrinder.msgspec_utils import DataclassInstance
|
|
7
|
+
from telegrinder.node.base import BaseNode, ComposeError, DataNode
|
|
8
|
+
from telegrinder.node.update import UpdateNode
|
|
9
|
+
|
|
10
|
+
if typing.TYPE_CHECKING:
|
|
11
|
+
Dataclass = typing.TypeVar("Dataclass", bound="DataclassType")
|
|
12
|
+
|
|
13
|
+
DataclassType: typing.TypeAlias = DataclassInstance | DataNode | msgspec.Struct | dict[str, typing.Any]
|
|
14
|
+
|
|
15
|
+
EVENT_NODE_KEY = "_event_node"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if typing.TYPE_CHECKING:
|
|
19
|
+
EventNode: typing.TypeAlias = typing.Annotated["Dataclass", ...]
|
|
20
|
+
|
|
21
|
+
else:
|
|
22
|
+
from telegrinder.msgspec_utils import decoder
|
|
23
|
+
|
|
24
|
+
class EventNode(BaseNode):
|
|
25
|
+
dataclass: type["DataclassType"]
|
|
26
|
+
|
|
27
|
+
def __new__(cls, dataclass: type["DataclassType"], /) -> type[typing.Self]:
|
|
28
|
+
namespace = dict(**cls.__dict__)
|
|
29
|
+
namespace.pop("__new__", None)
|
|
30
|
+
new_cls = type("EventNode", (cls,), {"dataclass": dataclass, **namespace})
|
|
31
|
+
return new_cls # type: ignore
|
|
32
|
+
|
|
33
|
+
def __class_getitem__(cls, dataclass: type["DataclassType"], /) -> type[typing.Self]:
|
|
34
|
+
return cls(dataclass)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
async def compose(cls, raw_update: UpdateNode, ctx: Context) -> "DataclassType":
|
|
38
|
+
dataclass_type = typing.get_origin(cls.dataclass) or cls.dataclass
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
if issubclass(dataclass_type, dict):
|
|
42
|
+
dataclass = cls.dataclass(**raw_update.incoming_update.to_full_dict())
|
|
43
|
+
|
|
44
|
+
elif issubclass(dataclass_type, msgspec.Struct | DataclassInstance):
|
|
45
|
+
dataclass = decoder.convert(
|
|
46
|
+
raw_update.incoming_update.to_full_dict(),
|
|
47
|
+
type=cls.dataclass,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
else:
|
|
51
|
+
dataclass = cls.dataclass(**raw_update.incoming_update.to_dict())
|
|
52
|
+
|
|
53
|
+
ctx[EVENT_NODE_KEY] = cls
|
|
54
|
+
return dataclass
|
|
55
|
+
except Exception:
|
|
56
|
+
raise ComposeError(f"Cannot parse update to {cls.dataclass.__name__!r}.")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
__all__ = ("EventNode",)
|
telegrinder/node/me.py
CHANGED
telegrinder/node/message.py
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
from telegrinder.bot.cute_types import MessageCute
|
|
2
|
-
|
|
3
|
-
from .
|
|
4
|
-
from .update import UpdateNode
|
|
1
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
2
|
+
from telegrinder.node.base import ComposeError, ScalarNode
|
|
3
|
+
from telegrinder.node.update import UpdateNode
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
class MessageNode(ScalarNode, MessageCute):
|
|
8
7
|
@classmethod
|
|
9
|
-
async def compose(cls, update: UpdateNode) ->
|
|
8
|
+
async def compose(cls, update: UpdateNode) -> MessageCute:
|
|
10
9
|
if not update.message:
|
|
11
|
-
raise ComposeError
|
|
12
|
-
return
|
|
13
|
-
**update.message.unwrap().to_dict(),
|
|
14
|
-
api=update.api,
|
|
15
|
-
)
|
|
10
|
+
raise ComposeError("Update is not a message.")
|
|
11
|
+
return update.message.unwrap()
|
|
16
12
|
|
|
17
13
|
|
|
18
14
|
__all__ = ("MessageNode",)
|
telegrinder/node/polymorphic.py
CHANGED
|
@@ -2,38 +2,37 @@ import inspect
|
|
|
2
2
|
import typing
|
|
3
3
|
|
|
4
4
|
from telegrinder.bot.dispatch.context import Context
|
|
5
|
-
from telegrinder.
|
|
5
|
+
from telegrinder.node.base import BaseNode, ComposeError
|
|
6
|
+
from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, Composition, NodeSession
|
|
7
|
+
from telegrinder.node.scope import NodeScope
|
|
8
|
+
from telegrinder.node.update import UpdateNode
|
|
9
|
+
from telegrinder.tools.magic import IMPL_MARK, get_impls_by_key, impl
|
|
6
10
|
|
|
7
|
-
from .base import ComposeError
|
|
8
|
-
from .composer import CONTEXT_STORE_NODES_KEY, Composition, NodeSession
|
|
9
|
-
from .scope import NodeScope
|
|
10
|
-
from .update import UpdateNode
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
class Polymorphic:
|
|
12
|
+
class Polymorphic(BaseNode):
|
|
14
13
|
@classmethod
|
|
15
14
|
async def compose(cls, update: UpdateNode, context: Context) -> typing.Any:
|
|
16
15
|
scope = getattr(cls, "scope", None)
|
|
17
16
|
node_ctx = context.get_or_set(CONTEXT_STORE_NODES_KEY, {})
|
|
18
17
|
|
|
19
|
-
for i, impl in enumerate(
|
|
20
|
-
composition = Composition(impl, True)
|
|
18
|
+
for i, impl in enumerate(get_impls_by_key(cls, IMPL_MARK).values()):
|
|
19
|
+
composition = Composition(impl, True, node_class=cls)
|
|
21
20
|
node_collection = await composition.compose_nodes(update, context)
|
|
22
21
|
if node_collection is None:
|
|
23
22
|
continue
|
|
24
23
|
|
|
25
24
|
# To determine whether this is a right morph, all subnodes must be resolved
|
|
26
25
|
if scope is NodeScope.PER_EVENT and (cls, i) in node_ctx:
|
|
27
|
-
|
|
26
|
+
res: NodeSession = node_ctx[(cls, i)]
|
|
28
27
|
await node_collection.close_all()
|
|
29
|
-
return
|
|
28
|
+
return res.value
|
|
30
29
|
|
|
31
30
|
result = composition.func(cls, **node_collection.values())
|
|
32
31
|
if inspect.isawaitable(result):
|
|
33
32
|
result = await result
|
|
34
33
|
|
|
35
34
|
if scope is NodeScope.PER_EVENT:
|
|
36
|
-
node_ctx[(cls, i)] = NodeSession(cls, result, {})
|
|
35
|
+
node_ctx[(cls, i)] = NodeSession(cls, result, {})
|
|
37
36
|
|
|
38
37
|
await node_collection.close_all(with_value=result)
|
|
39
38
|
return result
|
telegrinder/node/rule.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
+
import importlib
|
|
2
3
|
import typing
|
|
3
4
|
|
|
4
5
|
from telegrinder.bot.dispatch.context import Context
|
|
@@ -6,6 +7,7 @@ from telegrinder.node.base import ComposeError, Node
|
|
|
6
7
|
from telegrinder.node.update import UpdateNode
|
|
7
8
|
|
|
8
9
|
if typing.TYPE_CHECKING:
|
|
10
|
+
from telegrinder.bot.dispatch.process import check_rule
|
|
9
11
|
from telegrinder.bot.rules.abc import ABCRule
|
|
10
12
|
|
|
11
13
|
|
|
@@ -13,7 +15,9 @@ class RuleChain(dict[str, typing.Any]):
|
|
|
13
15
|
dataclass = dict
|
|
14
16
|
rules: tuple["ABCRule", ...] = ()
|
|
15
17
|
|
|
16
|
-
def __init_subclass__(cls) -> None:
|
|
18
|
+
def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
19
|
+
super().__init_subclass__(*args, **kwargs)
|
|
20
|
+
|
|
17
21
|
if cls.__name__ == "_RuleNode":
|
|
18
22
|
return
|
|
19
23
|
cls.dataclass = cls.generate_node_dataclass(cls)
|
|
@@ -21,7 +25,7 @@ class RuleChain(dict[str, typing.Any]):
|
|
|
21
25
|
def __new__(cls, *rules: "ABCRule") -> type[Node]:
|
|
22
26
|
return type("_RuleNode", (cls,), {"dataclass": dict, "rules": rules}) # type: ignore
|
|
23
27
|
|
|
24
|
-
def __class_getitem__(cls, items:
|
|
28
|
+
def __class_getitem__(cls, items: "ABCRule | tuple[ABCRule, ...]", /) -> typing.Self:
|
|
25
29
|
if not isinstance(items, tuple):
|
|
26
30
|
items = (items,)
|
|
27
31
|
assert all(isinstance(rule, "ABCRule") for rule in items), "All items must be instances of 'ABCRule'."
|
|
@@ -32,13 +36,23 @@ class RuleChain(dict[str, typing.Any]):
|
|
|
32
36
|
return dataclasses.dataclass(type(cls_.__name__, (object,), dict(cls_.__dict__)))
|
|
33
37
|
|
|
34
38
|
@classmethod
|
|
35
|
-
async def compose(cls, update: UpdateNode):
|
|
36
|
-
|
|
39
|
+
async def compose(cls, update: UpdateNode) -> typing.Any:
|
|
40
|
+
globalns = globals()
|
|
41
|
+
if "check_rule" not in globalns:
|
|
42
|
+
globalns.update(
|
|
43
|
+
{
|
|
44
|
+
"check_rule": getattr(
|
|
45
|
+
importlib.import_module("telegrinder.bot.dispatch.process"),
|
|
46
|
+
"check_rule",
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
)
|
|
37
50
|
|
|
38
51
|
ctx = Context()
|
|
39
52
|
for rule in cls.rules:
|
|
40
53
|
if not await check_rule(update.api, rule, update, ctx):
|
|
41
|
-
raise ComposeError
|
|
54
|
+
raise ComposeError(f"Rule {rule!r} failed!")
|
|
55
|
+
|
|
42
56
|
try:
|
|
43
57
|
return cls.dataclass(**ctx) # type: ignore
|
|
44
58
|
except Exception as exc:
|
|
@@ -52,6 +66,14 @@ class RuleChain(dict[str, typing.Any]):
|
|
|
52
66
|
def get_sub_nodes(cls) -> dict:
|
|
53
67
|
return {"update": UpdateNode}
|
|
54
68
|
|
|
69
|
+
@classmethod
|
|
70
|
+
def get_compose_annotations(cls) -> dict[str, typing.Any]:
|
|
71
|
+
return {}
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def get_node_impls(cls) -> dict[str, typing.Callable[..., typing.Any]]:
|
|
75
|
+
return {}
|
|
76
|
+
|
|
55
77
|
@classmethod
|
|
56
78
|
def is_generator(cls) -> typing.Literal[False]:
|
|
57
79
|
return False
|
telegrinder/node/source.py
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
import typing
|
|
3
3
|
|
|
4
|
-
from fntypes import Nothing, Option
|
|
4
|
+
from fntypes.option import Nothing, Option
|
|
5
5
|
|
|
6
|
-
from telegrinder.api import API
|
|
7
|
-
from telegrinder.
|
|
6
|
+
from telegrinder.api.api import API
|
|
7
|
+
from telegrinder.node.base import ComposeError, DataNode, ScalarNode
|
|
8
|
+
from telegrinder.node.callback_query import CallbackQueryNode
|
|
9
|
+
from telegrinder.node.message import MessageNode
|
|
10
|
+
from telegrinder.node.polymorphic import Polymorphic, impl
|
|
11
|
+
from telegrinder.types.objects import Chat, Message, User
|
|
8
12
|
|
|
9
|
-
from .base import ComposeError, DataNode, ScalarNode
|
|
10
|
-
from .callback_query import CallbackQueryNode
|
|
11
|
-
from .message import MessageNode
|
|
12
|
-
from .polymorphic import Polymorphic, impl
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
@dataclasses.dataclass(kw_only=True)
|
|
14
|
+
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
16
15
|
class Source(Polymorphic, DataNode):
|
|
17
16
|
api: API
|
|
18
17
|
chat: Chat
|
|
@@ -24,7 +23,7 @@ class Source(Polymorphic, DataNode):
|
|
|
24
23
|
return cls(
|
|
25
24
|
api=message.ctx_api,
|
|
26
25
|
chat=message.chat,
|
|
27
|
-
from_user=message.from_user,
|
|
26
|
+
from_user=message.from_.expect(ComposeError("MessageNode has no from_user")),
|
|
28
27
|
thread_id=message.message_thread_id,
|
|
29
28
|
)
|
|
30
29
|
|
|
@@ -32,7 +31,7 @@ class Source(Polymorphic, DataNode):
|
|
|
32
31
|
async def compose_callback_query(cls, callback_query: CallbackQueryNode) -> typing.Self:
|
|
33
32
|
return cls(
|
|
34
33
|
api=callback_query.ctx_api,
|
|
35
|
-
chat=callback_query.chat.expect(ComposeError),
|
|
34
|
+
chat=callback_query.chat.expect(ComposeError("CallbackQueryNode has no chat")),
|
|
36
35
|
from_user=callback_query.from_user,
|
|
37
36
|
thread_id=callback_query.message_thread_id,
|
|
38
37
|
)
|
telegrinder/node/text.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from .base import ComposeError, ScalarNode
|
|
2
|
-
from .message import MessageNode
|
|
1
|
+
from telegrinder.node.base import ComposeError, ScalarNode
|
|
2
|
+
from telegrinder.node.message import MessageNode
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Text(ScalarNode, str):
|
|
6
6
|
@classmethod
|
|
7
7
|
async def compose(cls, message: MessageNode) -> str:
|
|
8
8
|
if not message.text:
|
|
9
|
-
raise ComposeError("Message has no text")
|
|
9
|
+
raise ComposeError("Message has no text.")
|
|
10
10
|
return message.text.unwrap()
|
|
11
11
|
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ class TextInteger(ScalarNode, int):
|
|
|
14
14
|
@classmethod
|
|
15
15
|
async def compose(cls, text: Text) -> int:
|
|
16
16
|
if not text.isdigit():
|
|
17
|
-
raise ComposeError("Text is not digit")
|
|
17
|
+
raise ComposeError("Text is not digit.")
|
|
18
18
|
return int(text)
|
|
19
19
|
|
|
20
20
|
|
telegrinder/node/update.py
CHANGED
telegrinder/py.typed
ADDED
|
File without changes
|
telegrinder/tools/__init__.py
CHANGED
|
@@ -43,7 +43,7 @@ from .global_context import (
|
|
|
43
43
|
CtxVar,
|
|
44
44
|
GlobalContext,
|
|
45
45
|
GlobalCtxVar,
|
|
46
|
-
|
|
46
|
+
TelegrinderContext,
|
|
47
47
|
ctx_var,
|
|
48
48
|
)
|
|
49
49
|
from .i18n import (
|
|
@@ -110,7 +110,7 @@ __all__ = (
|
|
|
110
110
|
"SpecialFormat",
|
|
111
111
|
"StartBotLink",
|
|
112
112
|
"StartGroupLink",
|
|
113
|
-
"
|
|
113
|
+
"TelegrinderContext",
|
|
114
114
|
"TgEmoji",
|
|
115
115
|
"escape",
|
|
116
116
|
"get_channel_boost_link",
|
telegrinder/tools/buttons.py
CHANGED
|
@@ -3,25 +3,20 @@ import typing
|
|
|
3
3
|
|
|
4
4
|
import msgspec
|
|
5
5
|
|
|
6
|
-
from telegrinder.
|
|
7
|
-
from telegrinder.types import (
|
|
6
|
+
from telegrinder.msgspec_utils import DataclassInstance, encoder
|
|
7
|
+
from telegrinder.types.objects import (
|
|
8
8
|
CallbackGame,
|
|
9
9
|
KeyboardButtonPollType,
|
|
10
10
|
KeyboardButtonRequestChat,
|
|
11
11
|
KeyboardButtonRequestUsers,
|
|
12
|
+
LoginUrl,
|
|
12
13
|
SwitchInlineQueryChosenChat,
|
|
13
14
|
WebAppInfo,
|
|
14
15
|
)
|
|
15
|
-
from telegrinder.types.objects import LoginUrl
|
|
16
16
|
|
|
17
17
|
ButtonT = typing.TypeVar("ButtonT", bound="BaseButton")
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
@typing.runtime_checkable
|
|
21
|
-
class DataclassInstance(typing.Protocol):
|
|
22
|
-
__dataclass_fields__: typing.ClassVar[dict[str, dataclasses.Field[typing.Any]]]
|
|
23
|
-
|
|
24
|
-
|
|
25
20
|
@dataclasses.dataclass
|
|
26
21
|
class BaseButton:
|
|
27
22
|
def get_data(self) -> dict[str, typing.Any]:
|
|
@@ -44,7 +39,7 @@ class RowButtons(typing.Generic[ButtonT]):
|
|
|
44
39
|
return [b.get_data() for b in self.buttons]
|
|
45
40
|
|
|
46
41
|
|
|
47
|
-
@dataclasses.dataclass
|
|
42
|
+
@dataclasses.dataclass(slots=True)
|
|
48
43
|
class Button(BaseButton):
|
|
49
44
|
text: str
|
|
50
45
|
request_contact: bool = dataclasses.field(default=False, kw_only=True)
|
|
@@ -61,7 +56,7 @@ class Button(BaseButton):
|
|
|
61
56
|
web_app: dict[str, typing.Any] | WebAppInfo | None = dataclasses.field(default=None, kw_only=True)
|
|
62
57
|
|
|
63
58
|
|
|
64
|
-
@dataclasses.dataclass
|
|
59
|
+
@dataclasses.dataclass(slots=True)
|
|
65
60
|
class InlineButton(BaseButton):
|
|
66
61
|
text: str
|
|
67
62
|
url: str | None = dataclasses.field(default=None, kw_only=True)
|
|
@@ -16,7 +16,7 @@ ExceptionT = typing.TypeVar("ExceptionT", bound=BaseException, contravariant=Tru
|
|
|
16
16
|
FuncCatcher = typing.Callable[typing.Concatenate[ExceptionT, ...], typing.Awaitable[typing.Any]]
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
@dataclasses.dataclass(frozen=True, repr=False)
|
|
19
|
+
@dataclasses.dataclass(frozen=True, repr=False, slots=True)
|
|
20
20
|
class Catcher(typing.Generic[EventT]):
|
|
21
21
|
func: FuncCatcher[BaseException]
|
|
22
22
|
exceptions: list[type[BaseException] | BaseException] = dataclasses.field(
|
|
@@ -24,7 +24,7 @@ def is_spec_format(obj: typing.Any) -> typing.TypeGuard[SpecialFormat]:
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
@dataclasses.dataclass(repr=False)
|
|
27
|
+
@dataclasses.dataclass(repr=False, slots=True)
|
|
28
28
|
class BaseSpecFormat:
|
|
29
29
|
__formatter_name__: typing.ClassVar[str] = dataclasses.field(init=False, repr=False)
|
|
30
30
|
|
|
@@ -32,7 +32,7 @@ class BaseSpecFormat:
|
|
|
32
32
|
return f"<Special formatter {self.__class__.__name__!r} -> {self.__formatter_name__!r}>"
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
@dataclasses.dataclass(repr=False)
|
|
35
|
+
@dataclasses.dataclass(repr=False, slots=True)
|
|
36
36
|
class ChannelBoostLink(BaseSpecFormat):
|
|
37
37
|
__formatter_name__ = "channel_boost_link"
|
|
38
38
|
|
|
@@ -40,7 +40,7 @@ class ChannelBoostLink(BaseSpecFormat):
|
|
|
40
40
|
string: str | None = None
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
@dataclasses.dataclass(repr=False)
|
|
43
|
+
@dataclasses.dataclass(repr=False, slots=True)
|
|
44
44
|
class InviteChatLink(BaseSpecFormat):
|
|
45
45
|
__formatter_name__ = "invite_chat_link"
|
|
46
46
|
|
|
@@ -48,7 +48,7 @@ class InviteChatLink(BaseSpecFormat):
|
|
|
48
48
|
string: str | None = None
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
@dataclasses.dataclass(repr=False)
|
|
51
|
+
@dataclasses.dataclass(repr=False, slots=True)
|
|
52
52
|
class Mention(BaseSpecFormat):
|
|
53
53
|
__formatter_name__ = "mention"
|
|
54
54
|
|
|
@@ -56,7 +56,7 @@ class Mention(BaseSpecFormat):
|
|
|
56
56
|
user_id: int
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
@dataclasses.dataclass(repr=False)
|
|
59
|
+
@dataclasses.dataclass(repr=False, slots=True)
|
|
60
60
|
class Link(BaseSpecFormat):
|
|
61
61
|
__formatter_name__ = "link"
|
|
62
62
|
|
|
@@ -64,7 +64,7 @@ class Link(BaseSpecFormat):
|
|
|
64
64
|
string: str | None = None
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
@dataclasses.dataclass(repr=False)
|
|
67
|
+
@dataclasses.dataclass(repr=False, slots=True)
|
|
68
68
|
class PreCode(BaseSpecFormat):
|
|
69
69
|
__formatter_name__ = "pre_code"
|
|
70
70
|
|
|
@@ -72,7 +72,7 @@ class PreCode(BaseSpecFormat):
|
|
|
72
72
|
lang: str | ProgrammingLanguage | None = None
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
@dataclasses.dataclass(repr=False)
|
|
75
|
+
@dataclasses.dataclass(repr=False, slots=True)
|
|
76
76
|
class TgEmoji(BaseSpecFormat):
|
|
77
77
|
__formatter_name__ = "tg_emoji"
|
|
78
78
|
|
|
@@ -80,7 +80,7 @@ class TgEmoji(BaseSpecFormat):
|
|
|
80
80
|
emoji_id: int
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
@dataclasses.dataclass(repr=False)
|
|
83
|
+
@dataclasses.dataclass(repr=False, slots=True)
|
|
84
84
|
class StartBotLink(BaseSpecFormat):
|
|
85
85
|
__formatter_name__ = "start_bot_link"
|
|
86
86
|
|
|
@@ -89,7 +89,7 @@ class StartBotLink(BaseSpecFormat):
|
|
|
89
89
|
string: str | None
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
@dataclasses.dataclass(repr=False)
|
|
92
|
+
@dataclasses.dataclass(repr=False, slots=True)
|
|
93
93
|
class StartGroupLink(BaseSpecFormat):
|
|
94
94
|
__formatter_name__ = "start_group_link"
|
|
95
95
|
|
|
@@ -98,7 +98,7 @@ class StartGroupLink(BaseSpecFormat):
|
|
|
98
98
|
string: str | None = None
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
@dataclasses.dataclass(repr=False)
|
|
101
|
+
@dataclasses.dataclass(repr=False, slots=True)
|
|
102
102
|
class ResolveDomain(BaseSpecFormat):
|
|
103
103
|
__formatter_name__ = "resolve_domain"
|
|
104
104
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from .abc import ABCGlobalContext, CtxVar, GlobalCtxVar
|
|
2
2
|
from .global_context import GlobalContext, ctx_var
|
|
3
|
-
from .telegrinder_ctx import
|
|
3
|
+
from .telegrinder_ctx import TelegrinderContext
|
|
4
4
|
|
|
5
5
|
__all__ = (
|
|
6
6
|
"ABCGlobalContext",
|
|
7
7
|
"CtxVar",
|
|
8
8
|
"GlobalContext",
|
|
9
9
|
"GlobalCtxVar",
|
|
10
|
-
"
|
|
10
|
+
"TelegrinderContext",
|
|
11
11
|
"ctx_var",
|
|
12
12
|
)
|
|
@@ -81,7 +81,7 @@ def ctx_var(value: T, *, const: bool = False) -> T:
|
|
|
81
81
|
return typing.cast(T, CtxVar(value, const=const))
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
@dataclasses.dataclass(frozen=True, eq=False)
|
|
84
|
+
@dataclasses.dataclass(frozen=True, eq=False, slots=True)
|
|
85
85
|
class RootAttr:
|
|
86
86
|
name: str
|
|
87
87
|
can_be_read: bool = dataclasses.field(default=True, kw_only=True)
|
|
@@ -91,7 +91,7 @@ class RootAttr:
|
|
|
91
91
|
return self.name == __value
|
|
92
92
|
|
|
93
93
|
|
|
94
|
-
@dataclasses.dataclass(repr=False, frozen=True)
|
|
94
|
+
@dataclasses.dataclass(repr=False, frozen=True, slots=True)
|
|
95
95
|
class Storage:
|
|
96
96
|
_storage: dict[str, "GlobalContext"] = dataclasses.field(
|
|
97
97
|
default_factory=lambda: {},
|
|
@@ -5,14 +5,14 @@ import vbml
|
|
|
5
5
|
from telegrinder.tools.global_context import GlobalContext, ctx_var
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class
|
|
8
|
+
class TelegrinderContext(GlobalContext):
|
|
9
9
|
"""Basic type-hinted telegrinder context with context name `"telegrinder"`.
|
|
10
10
|
|
|
11
11
|
You can use this class or GlobalContext:
|
|
12
12
|
```
|
|
13
|
-
from telegrinder.tools.global_context import GlobalContext,
|
|
13
|
+
from telegrinder.tools.global_context import GlobalContext, TelegrinderContext
|
|
14
14
|
|
|
15
|
-
ctx1 =
|
|
15
|
+
ctx1 = TelegrinderContext()
|
|
16
16
|
ctx2 = GlobalContext("telegrinder") # same, but without the type-hints
|
|
17
17
|
assert ctx1 == ctx2 # ok
|
|
18
18
|
```"""
|
|
@@ -22,4 +22,4 @@ class TelegrinderCtx(GlobalContext):
|
|
|
22
22
|
vbml_patcher: typing.ClassVar[vbml.Patcher] = ctx_var(vbml.Patcher(), const=True)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
__all__ = ("
|
|
25
|
+
__all__ = ("TelegrinderContext",)
|
telegrinder/tools/keyboard.py
CHANGED
|
@@ -17,7 +17,7 @@ DictStrAny: typing.TypeAlias = dict[str, typing.Any]
|
|
|
17
17
|
AnyMarkup: typing.TypeAlias = InlineKeyboardMarkup | ReplyKeyboardMarkup
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
@dataclasses.dataclass(kw_only=True)
|
|
20
|
+
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
21
21
|
class KeyboardModel:
|
|
22
22
|
resize_keyboard: bool
|
|
23
23
|
one_time_keyboard: bool
|
|
@@ -77,7 +77,7 @@ class ABCMarkup(ABC, typing.Generic[ButtonT]):
|
|
|
77
77
|
return self
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
@dataclasses.dataclass(kw_only=True)
|
|
80
|
+
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
81
81
|
class Keyboard(ABCMarkup[Button], KeyboardModel):
|
|
82
82
|
BUTTON = Button
|
|
83
83
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import contextlib
|
|
3
3
|
import dataclasses
|
|
4
|
+
import datetime
|
|
4
5
|
import typing
|
|
5
6
|
|
|
6
7
|
from telegrinder.modules import logger
|
|
@@ -32,7 +33,7 @@ def to_coroutine_task(task: Task) -> CoroutineTask[typing.Any]:
|
|
|
32
33
|
return task
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
@dataclasses.dataclass
|
|
36
|
+
@dataclasses.dataclass(slots=True)
|
|
36
37
|
class DelayedTask(typing.Generic[CoroFunc]):
|
|
37
38
|
handler: CoroFunc
|
|
38
39
|
seconds: float
|
|
@@ -60,7 +61,7 @@ class DelayedTask(typing.Generic[CoroFunc]):
|
|
|
60
61
|
self._cancelled = True
|
|
61
62
|
|
|
62
63
|
|
|
63
|
-
@dataclasses.dataclass(kw_only=True)
|
|
64
|
+
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
64
65
|
class Lifespan:
|
|
65
66
|
startup_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
66
67
|
shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
@@ -86,10 +87,11 @@ class LoopWrapper(ABCLoopWrapper):
|
|
|
86
87
|
*,
|
|
87
88
|
tasks: list[CoroutineTask[typing.Any]] | None = None,
|
|
88
89
|
lifespan: Lifespan | None = None,
|
|
90
|
+
event_loop: asyncio.AbstractEventLoop | None = None,
|
|
89
91
|
) -> None:
|
|
90
92
|
self.tasks: list[CoroutineTask[typing.Any]] = tasks or []
|
|
91
93
|
self.lifespan = lifespan or Lifespan()
|
|
92
|
-
self._loop = asyncio.new_event_loop()
|
|
94
|
+
self._loop = event_loop or asyncio.new_event_loop()
|
|
93
95
|
|
|
94
96
|
def __repr__(self) -> str:
|
|
95
97
|
return "<{}: loop={!r} with tasks={!r}, lifespan={!r}>".format(
|
|
@@ -143,6 +145,10 @@ class LoopWrapper(ABCLoopWrapper):
|
|
|
143
145
|
with contextlib.suppress(asyncio.CancelledError):
|
|
144
146
|
self._loop.run_until_complete(task_to_cancel)
|
|
145
147
|
|
|
148
|
+
@typing.overload
|
|
149
|
+
def timer(self, *, seconds: datetime.timedelta) -> typing.Callable[..., typing.Any]: ...
|
|
150
|
+
|
|
151
|
+
@typing.overload
|
|
146
152
|
def timer(
|
|
147
153
|
self,
|
|
148
154
|
*,
|
|
@@ -150,7 +156,19 @@ class LoopWrapper(ABCLoopWrapper):
|
|
|
150
156
|
hours: int = 0,
|
|
151
157
|
minutes: int = 0,
|
|
152
158
|
seconds: float = 0,
|
|
153
|
-
):
|
|
159
|
+
) -> typing.Callable[..., typing.Any]: ...
|
|
160
|
+
|
|
161
|
+
def timer(
|
|
162
|
+
self,
|
|
163
|
+
*,
|
|
164
|
+
days: int = 0,
|
|
165
|
+
hours: int = 0,
|
|
166
|
+
minutes: int = 0,
|
|
167
|
+
seconds: float | datetime.timedelta = 0,
|
|
168
|
+
) -> typing.Callable[..., typing.Any]:
|
|
169
|
+
if isinstance(seconds, datetime.timedelta):
|
|
170
|
+
seconds = seconds.total_seconds()
|
|
171
|
+
|
|
154
172
|
seconds += minutes * 60
|
|
155
173
|
seconds += hours * 60 * 60
|
|
156
174
|
seconds += days * 24 * 60 * 60
|
|
@@ -161,6 +179,10 @@ class LoopWrapper(ABCLoopWrapper):
|
|
|
161
179
|
|
|
162
180
|
return decorator
|
|
163
181
|
|
|
182
|
+
@typing.overload
|
|
183
|
+
def interval(self, *, seconds: datetime.timedelta) -> typing.Callable[..., typing.Any]: ...
|
|
184
|
+
|
|
185
|
+
@typing.overload
|
|
164
186
|
def interval(
|
|
165
187
|
self,
|
|
166
188
|
*,
|
|
@@ -168,7 +190,19 @@ class LoopWrapper(ABCLoopWrapper):
|
|
|
168
190
|
hours: int = 0,
|
|
169
191
|
minutes: int = 0,
|
|
170
192
|
seconds: float = 0,
|
|
171
|
-
):
|
|
193
|
+
) -> typing.Callable[..., typing.Any]: ...
|
|
194
|
+
|
|
195
|
+
def interval(
|
|
196
|
+
self,
|
|
197
|
+
*,
|
|
198
|
+
days: int = 0,
|
|
199
|
+
hours: int = 0,
|
|
200
|
+
minutes: int = 0,
|
|
201
|
+
seconds: float | datetime.timedelta = 0,
|
|
202
|
+
) -> typing.Callable[..., typing.Any]:
|
|
203
|
+
if isinstance(seconds, datetime.timedelta):
|
|
204
|
+
seconds = seconds.total_seconds()
|
|
205
|
+
|
|
172
206
|
seconds += minutes * 60
|
|
173
207
|
seconds += hours * 60 * 60
|
|
174
208
|
seconds += days * 24 * 60 * 60
|