telegrinder 0.1.dev171__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +2 -2
- telegrinder/api/__init__.py +1 -2
- telegrinder/api/api.py +3 -3
- telegrinder/api/token.py +36 -0
- telegrinder/bot/__init__.py +12 -6
- telegrinder/bot/bot.py +12 -5
- telegrinder/bot/cute_types/__init__.py +7 -7
- telegrinder/bot/cute_types/base.py +7 -32
- telegrinder/bot/cute_types/callback_query.py +5 -6
- telegrinder/bot/cute_types/chat_join_request.py +4 -5
- telegrinder/bot/cute_types/chat_member_updated.py +3 -4
- telegrinder/bot/cute_types/inline_query.py +3 -4
- telegrinder/bot/cute_types/message.py +9 -10
- telegrinder/bot/cute_types/update.py +8 -9
- telegrinder/bot/cute_types/utils.py +1 -1
- telegrinder/bot/dispatch/__init__.py +9 -9
- telegrinder/bot/dispatch/abc.py +2 -2
- telegrinder/bot/dispatch/context.py +11 -2
- telegrinder/bot/dispatch/dispatch.py +18 -33
- telegrinder/bot/dispatch/handler/__init__.py +3 -3
- telegrinder/bot/dispatch/handler/abc.py +3 -3
- telegrinder/bot/dispatch/handler/func.py +17 -12
- telegrinder/bot/dispatch/handler/message_reply.py +6 -7
- telegrinder/bot/dispatch/middleware/__init__.py +1 -1
- telegrinder/bot/dispatch/process.py +30 -11
- telegrinder/bot/dispatch/return_manager/__init__.py +4 -4
- telegrinder/bot/dispatch/return_manager/callback_query.py +1 -2
- telegrinder/bot/dispatch/return_manager/inline_query.py +1 -2
- telegrinder/bot/dispatch/return_manager/message.py +1 -2
- telegrinder/bot/dispatch/view/__init__.py +8 -8
- telegrinder/bot/dispatch/view/abc.py +9 -4
- telegrinder/bot/dispatch/view/box.py +2 -2
- telegrinder/bot/dispatch/view/callback_query.py +1 -2
- telegrinder/bot/dispatch/view/chat_join_request.py +1 -2
- telegrinder/bot/dispatch/view/chat_member.py +16 -2
- telegrinder/bot/dispatch/view/inline_query.py +1 -2
- telegrinder/bot/dispatch/view/message.py +1 -2
- telegrinder/bot/dispatch/view/raw.py +8 -10
- telegrinder/bot/dispatch/waiter_machine/__init__.py +3 -3
- telegrinder/bot/dispatch/waiter_machine/machine.py +10 -6
- telegrinder/bot/dispatch/waiter_machine/short_state.py +2 -2
- telegrinder/bot/polling/abc.py +1 -1
- telegrinder/bot/polling/polling.py +3 -3
- telegrinder/bot/rules/__init__.py +20 -20
- telegrinder/bot/rules/abc.py +50 -40
- telegrinder/bot/rules/adapter/__init__.py +5 -5
- telegrinder/bot/rules/adapter/abc.py +6 -3
- telegrinder/bot/rules/adapter/errors.py +2 -1
- telegrinder/bot/rules/adapter/event.py +27 -15
- telegrinder/bot/rules/adapter/node.py +28 -22
- telegrinder/bot/rules/adapter/raw_update.py +13 -5
- telegrinder/bot/rules/callback_data.py +4 -4
- telegrinder/bot/rules/chat_join.py +4 -4
- telegrinder/bot/rules/func.py +1 -1
- telegrinder/bot/rules/inline.py +3 -3
- telegrinder/bot/rules/markup.py +3 -1
- telegrinder/bot/rules/message_entities.py +1 -1
- telegrinder/bot/rules/text.py +1 -2
- telegrinder/bot/rules/update.py +1 -2
- telegrinder/bot/scenario/abc.py +2 -2
- telegrinder/bot/scenario/checkbox.py +1 -2
- telegrinder/bot/scenario/choice.py +1 -2
- telegrinder/model.py +6 -1
- telegrinder/msgspec_utils.py +55 -55
- telegrinder/node/__init__.py +1 -3
- telegrinder/node/base.py +14 -86
- telegrinder/node/composer.py +71 -74
- telegrinder/node/container.py +3 -3
- telegrinder/node/event.py +40 -31
- telegrinder/node/polymorphic.py +12 -6
- telegrinder/node/rule.py +1 -9
- telegrinder/node/scope.py +9 -1
- telegrinder/node/source.py +11 -0
- telegrinder/node/update.py +6 -2
- telegrinder/rules.py +59 -0
- telegrinder/tools/error_handler/abc.py +2 -2
- telegrinder/tools/error_handler/error_handler.py +5 -5
- telegrinder/tools/global_context/global_context.py +1 -1
- telegrinder/tools/keyboard.py +1 -1
- telegrinder/tools/loop_wrapper/loop_wrapper.py +9 -9
- telegrinder/tools/magic.py +64 -19
- telegrinder/types/__init__.py +1 -0
- telegrinder/types/enums.py +1 -0
- telegrinder/types/methods.py +78 -11
- telegrinder/types/objects.py +46 -24
- telegrinder/verification_utils.py +1 -3
- {telegrinder-0.1.dev171.dist-info → telegrinder-0.2.0.dist-info}/METADATA +1 -1
- telegrinder-0.2.0.dist-info/RECORD +145 -0
- telegrinder/api/abc.py +0 -79
- telegrinder-0.1.dev171.dist-info/RECORD +0 -145
- {telegrinder-0.1.dev171.dist-info → telegrinder-0.2.0.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev171.dist-info → telegrinder-0.2.0.dist-info}/WHEEL +0 -0
telegrinder/node/event.py
CHANGED
|
@@ -2,9 +2,11 @@ import typing
|
|
|
2
2
|
|
|
3
3
|
import msgspec
|
|
4
4
|
|
|
5
|
+
from telegrinder.api import API
|
|
6
|
+
from telegrinder.bot.cute_types import BaseCute
|
|
5
7
|
from telegrinder.bot.dispatch.context import Context
|
|
6
8
|
from telegrinder.msgspec_utils import DataclassInstance
|
|
7
|
-
from telegrinder.node.base import
|
|
9
|
+
from telegrinder.node.base import ComposeError, DataNode, Node
|
|
8
10
|
from telegrinder.node.update import UpdateNode
|
|
9
11
|
|
|
10
12
|
if typing.TYPE_CHECKING:
|
|
@@ -15,45 +17,52 @@ if typing.TYPE_CHECKING:
|
|
|
15
17
|
EVENT_NODE_KEY = "_event_node"
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
EventNode: typing.TypeAlias = typing.Annotated["Dataclass", ...]
|
|
20
|
+
from telegrinder.msgspec_utils import decoder
|
|
20
21
|
|
|
21
|
-
else:
|
|
22
|
-
from telegrinder.msgspec_utils import decoder
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
class _EventNode(Node):
|
|
24
|
+
dataclass: type["DataclassType"]
|
|
25
|
+
|
|
26
|
+
def __new__(cls, dataclass: type["DataclassType"], /) -> type[typing.Self]:
|
|
27
|
+
namespace = dict(**cls.__dict__)
|
|
28
|
+
namespace.pop("__new__", None)
|
|
29
|
+
new_cls = type("EventNode", (cls,), {"dataclass": dataclass, **namespace})
|
|
30
|
+
return new_cls # type: ignore
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
namespace.pop("__new__", None)
|
|
30
|
-
new_cls = type("EventNode", (cls,), {"dataclass": dataclass, **namespace})
|
|
31
|
-
return new_cls # type: ignore
|
|
32
|
+
def __class_getitem__(cls, dataclass: type["DataclassType"], /) -> typing.Self:
|
|
33
|
+
return cls(dataclass)
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
@classmethod
|
|
36
|
+
async def compose(cls, raw_update: UpdateNode, ctx: Context, api: API) -> "DataclassType":
|
|
37
|
+
dataclass_type = typing.get_origin(cls.dataclass) or cls.dataclass
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
try:
|
|
40
|
+
if issubclass(dataclass_type, dict):
|
|
41
|
+
dataclass = cls.dataclass(**raw_update.incoming_update.to_full_dict())
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
dataclass = cls.dataclass(**raw_update.incoming_update.to_full_dict())
|
|
43
|
+
elif issubclass(dataclass_type, BaseCute):
|
|
44
|
+
dataclass = dataclass_type.from_update(raw_update.incoming_update, bound_api=api)
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
elif issubclass(dataclass_type, (msgspec.Struct, DataclassInstance)): # type: ignore
|
|
47
|
+
# FIXME: must be used with encode_name
|
|
48
|
+
dataclass = decoder.convert(
|
|
49
|
+
raw_update.incoming_update.to_full_dict(),
|
|
50
|
+
type=cls.dataclass,
|
|
51
|
+
)
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
else:
|
|
54
|
+
dataclass = cls.dataclass(**raw_update.incoming_update.to_dict())
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
ctx[EVENT_NODE_KEY] = cls
|
|
57
|
+
return dataclass
|
|
58
|
+
except Exception as exc:
|
|
59
|
+
raise ComposeError(f"Cannot parse update into {cls.dataclass.__name__!r}, error: {exc}")
|
|
57
60
|
|
|
58
61
|
|
|
62
|
+
if typing.TYPE_CHECKING:
|
|
63
|
+
EventNode: typing.TypeAlias = typing.Annotated["Dataclass", ...]
|
|
64
|
+
else:
|
|
65
|
+
class EventNode(_EventNode):
|
|
66
|
+
pass
|
|
67
|
+
|
|
59
68
|
__all__ = ("EventNode",)
|
telegrinder/node/polymorphic.py
CHANGED
|
@@ -2,32 +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.modules import logger
|
|
6
|
+
from telegrinder.node.base import ComposeError, Node
|
|
6
7
|
from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, Composition, NodeSession
|
|
7
8
|
from telegrinder.node.scope import NodeScope
|
|
8
9
|
from telegrinder.node.update import UpdateNode
|
|
9
|
-
from telegrinder.tools.magic import
|
|
10
|
+
from telegrinder.tools.magic import get_impls, impl
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
class Polymorphic(
|
|
13
|
+
class Polymorphic(Node):
|
|
13
14
|
@classmethod
|
|
14
15
|
async def compose(cls, update: UpdateNode, context: Context) -> typing.Any:
|
|
16
|
+
logger.debug(f"Composing polimorphic node {cls.__name__}")
|
|
15
17
|
scope = getattr(cls, "scope", None)
|
|
16
18
|
node_ctx = context.get_or_set(CONTEXT_STORE_NODES_KEY, {})
|
|
17
19
|
|
|
18
|
-
for i, impl in enumerate(
|
|
19
|
-
|
|
20
|
+
for i, impl in enumerate(get_impls(cls)):
|
|
21
|
+
logger.debug("Checking impl {}", impl.__name__)
|
|
22
|
+
composition = Composition(impl, True)
|
|
20
23
|
node_collection = await composition.compose_nodes(update, context)
|
|
21
24
|
if node_collection is None:
|
|
25
|
+
logger.debug("Impl {} composition failed", impl.__name__)
|
|
22
26
|
continue
|
|
23
27
|
|
|
24
28
|
# To determine whether this is a right morph, all subnodes must be resolved
|
|
25
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 {} succeeded", impl.__name__)
|
|
26
31
|
res: NodeSession = node_ctx[(cls, i)]
|
|
27
32
|
await node_collection.close_all()
|
|
28
33
|
return res.value
|
|
29
34
|
|
|
30
|
-
result = composition.func(cls, **node_collection.values
|
|
35
|
+
result = composition.func(cls, **node_collection.values)
|
|
31
36
|
if inspect.isawaitable(result):
|
|
32
37
|
result = await result
|
|
33
38
|
|
|
@@ -35,6 +40,7 @@ class Polymorphic(BaseNode):
|
|
|
35
40
|
node_ctx[(cls, i)] = NodeSession(cls, result, {})
|
|
36
41
|
|
|
37
42
|
await node_collection.close_all(with_value=result)
|
|
43
|
+
logger.debug("Impl {} succeeded with value {}", impl.__name__, result)
|
|
38
44
|
return result
|
|
39
45
|
|
|
40
46
|
raise ComposeError("No implementation found.")
|
telegrinder/node/rule.py
CHANGED
|
@@ -63,17 +63,9 @@ class RuleChain(dict[str, typing.Any]):
|
|
|
63
63
|
return cls
|
|
64
64
|
|
|
65
65
|
@classmethod
|
|
66
|
-
def
|
|
66
|
+
def get_subnodes(cls) -> dict:
|
|
67
67
|
return {"update": UpdateNode}
|
|
68
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
|
-
|
|
77
69
|
@classmethod
|
|
78
70
|
def is_generator(cls) -> typing.Literal[False]:
|
|
79
71
|
return False
|
telegrinder/node/scope.py
CHANGED
|
@@ -33,4 +33,12 @@ def global_node(node: T) -> T:
|
|
|
33
33
|
return node
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
__all__ = (
|
|
36
|
+
__all__ = (
|
|
37
|
+
"NodeScope",
|
|
38
|
+
"PER_EVENT",
|
|
39
|
+
"PER_CALL",
|
|
40
|
+
"per_call",
|
|
41
|
+
"per_event",
|
|
42
|
+
"global_node",
|
|
43
|
+
"GLOBAL",
|
|
44
|
+
)
|
telegrinder/node/source.py
CHANGED
|
@@ -4,8 +4,10 @@ import typing
|
|
|
4
4
|
from fntypes.option import Nothing, Option
|
|
5
5
|
|
|
6
6
|
from telegrinder.api.api import API
|
|
7
|
+
from telegrinder.bot.cute_types import ChatJoinRequestCute
|
|
7
8
|
from telegrinder.node.base import ComposeError, DataNode, ScalarNode
|
|
8
9
|
from telegrinder.node.callback_query import CallbackQueryNode
|
|
10
|
+
from telegrinder.node.event import EventNode
|
|
9
11
|
from telegrinder.node.message import MessageNode
|
|
10
12
|
from telegrinder.node.polymorphic import Polymorphic, impl
|
|
11
13
|
from telegrinder.types.objects import Chat, Message, User
|
|
@@ -36,6 +38,15 @@ class Source(Polymorphic, DataNode):
|
|
|
36
38
|
thread_id=callback_query.message_thread_id,
|
|
37
39
|
)
|
|
38
40
|
|
|
41
|
+
@impl
|
|
42
|
+
async def compose_chat_join_request(cls, chat_join_request: EventNode[ChatJoinRequestCute]) -> typing.Self:
|
|
43
|
+
return cls(
|
|
44
|
+
api=chat_join_request.ctx_api,
|
|
45
|
+
chat=chat_join_request.chat,
|
|
46
|
+
from_user=chat_join_request.from_user,
|
|
47
|
+
thread_id=Nothing(),
|
|
48
|
+
)
|
|
49
|
+
|
|
39
50
|
async def send(self, text: str) -> Message:
|
|
40
51
|
result = await self.api.send_message(
|
|
41
52
|
chat_id=self.chat.id,
|
telegrinder/node/update.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
from telegrinder.api import API
|
|
1
2
|
from telegrinder.bot.cute_types import UpdateCute
|
|
2
3
|
from telegrinder.node.base import ScalarNode
|
|
4
|
+
from telegrinder.types import Update
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
class UpdateNode(ScalarNode, UpdateCute):
|
|
6
8
|
@classmethod
|
|
7
|
-
async def compose(cls, update:
|
|
8
|
-
|
|
9
|
+
async def compose(cls, update: Update, api: API) -> UpdateCute:
|
|
10
|
+
if isinstance(update, UpdateCute):
|
|
11
|
+
return update
|
|
12
|
+
return UpdateCute.from_update(update, api)
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
__all__ = ("UpdateNode",)
|
telegrinder/rules.py
CHANGED
|
@@ -1 +1,60 @@
|
|
|
1
1
|
from .bot.rules import * # noqa: F403
|
|
2
|
+
|
|
3
|
+
__all__ = (
|
|
4
|
+
"ABCRule",
|
|
5
|
+
"AndRule",
|
|
6
|
+
"Argument",
|
|
7
|
+
"CallbackDataEq",
|
|
8
|
+
"CallbackDataJsonEq",
|
|
9
|
+
"CallbackDataJsonModel",
|
|
10
|
+
"CallbackDataMap",
|
|
11
|
+
"CallbackDataMarkup",
|
|
12
|
+
"CallbackQueryDataRule",
|
|
13
|
+
"CallbackQueryRule",
|
|
14
|
+
"ChatJoinRequestRule",
|
|
15
|
+
"Command",
|
|
16
|
+
"EnumTextRule",
|
|
17
|
+
"FuncRule",
|
|
18
|
+
"FuzzyText",
|
|
19
|
+
"HasData",
|
|
20
|
+
"HasEntities",
|
|
21
|
+
"HasInviteLink",
|
|
22
|
+
"HasLocation",
|
|
23
|
+
"HasMention",
|
|
24
|
+
"HasText",
|
|
25
|
+
"InlineQueryChatType",
|
|
26
|
+
"InlineQueryMarkup",
|
|
27
|
+
"InlineQueryRule",
|
|
28
|
+
"InlineQueryText",
|
|
29
|
+
"IsInteger",
|
|
30
|
+
"IntegerInRange",
|
|
31
|
+
"InviteLinkByCreator",
|
|
32
|
+
"InviteLinkName",
|
|
33
|
+
"IsBot",
|
|
34
|
+
"IsChat",
|
|
35
|
+
"IsChatId",
|
|
36
|
+
"IsDice",
|
|
37
|
+
"IsDiceEmoji",
|
|
38
|
+
"IsForum",
|
|
39
|
+
"IsForward",
|
|
40
|
+
"IsForwardType",
|
|
41
|
+
"IsGroup",
|
|
42
|
+
"IsLanguageCode",
|
|
43
|
+
"IsPremium",
|
|
44
|
+
"IsPrivate",
|
|
45
|
+
"IsReply",
|
|
46
|
+
"IsSuperGroup",
|
|
47
|
+
"IsUpdateType",
|
|
48
|
+
"IsUser",
|
|
49
|
+
"IsUserId",
|
|
50
|
+
"Markup",
|
|
51
|
+
"MessageEntities",
|
|
52
|
+
"MessageRule",
|
|
53
|
+
"NotRule",
|
|
54
|
+
"OrRule",
|
|
55
|
+
"Regex",
|
|
56
|
+
"RuleEnum",
|
|
57
|
+
"StartCommand",
|
|
58
|
+
"Text",
|
|
59
|
+
"NodeRule",
|
|
60
|
+
)
|
|
@@ -3,7 +3,7 @@ from abc import ABC, abstractmethod
|
|
|
3
3
|
|
|
4
4
|
from fntypes.result import Result
|
|
5
5
|
|
|
6
|
-
from telegrinder.api import
|
|
6
|
+
from telegrinder.api import API
|
|
7
7
|
from telegrinder.bot.dispatch.context import Context
|
|
8
8
|
|
|
9
9
|
EventT = typing.TypeVar("EventT")
|
|
@@ -24,7 +24,7 @@ class ABCErrorHandler(ABC, typing.Generic[EventT]):
|
|
|
24
24
|
self,
|
|
25
25
|
handler: Handler[EventT],
|
|
26
26
|
event: EventT,
|
|
27
|
-
api:
|
|
27
|
+
api: API,
|
|
28
28
|
ctx: Context,
|
|
29
29
|
) -> Result[typing.Any, typing.Any]:
|
|
30
30
|
"""Run error handler."""
|
|
@@ -3,7 +3,7 @@ import typing
|
|
|
3
3
|
|
|
4
4
|
from fntypes.result import Error, Ok, Result
|
|
5
5
|
|
|
6
|
-
from telegrinder.api import
|
|
6
|
+
from telegrinder.api import API
|
|
7
7
|
from telegrinder.bot.dispatch.context import Context
|
|
8
8
|
from telegrinder.modules import logger
|
|
9
9
|
from telegrinder.tools.magic import magic_bundle
|
|
@@ -39,7 +39,7 @@ class Catcher(typing.Generic[EventT]):
|
|
|
39
39
|
self,
|
|
40
40
|
handler: Handler[EventT],
|
|
41
41
|
event: EventT,
|
|
42
|
-
api:
|
|
42
|
+
api: API,
|
|
43
43
|
ctx: Context,
|
|
44
44
|
) -> Result[typing.Any, BaseException]:
|
|
45
45
|
try:
|
|
@@ -49,7 +49,7 @@ class Catcher(typing.Generic[EventT]):
|
|
|
49
49
|
|
|
50
50
|
async def process_exception(
|
|
51
51
|
self,
|
|
52
|
-
api:
|
|
52
|
+
api: API,
|
|
53
53
|
event: EventT,
|
|
54
54
|
ctx: Context,
|
|
55
55
|
exception: BaseException,
|
|
@@ -125,7 +125,7 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
125
125
|
self,
|
|
126
126
|
handler: Handler[EventT],
|
|
127
127
|
event: EventT,
|
|
128
|
-
api:
|
|
128
|
+
api: API,
|
|
129
129
|
ctx: Context,
|
|
130
130
|
) -> Result[typing.Any, BaseException]:
|
|
131
131
|
assert self.catcher is not None
|
|
@@ -159,7 +159,7 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
159
159
|
self,
|
|
160
160
|
handler: Handler[EventT],
|
|
161
161
|
event: EventT,
|
|
162
|
-
api:
|
|
162
|
+
api: API,
|
|
163
163
|
ctx: Context,
|
|
164
164
|
) -> Result[typing.Any, BaseException]:
|
|
165
165
|
if not self.catcher:
|
|
@@ -334,7 +334,7 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
334
334
|
def get(self, var_name, var_value_type=object): # type: ignore
|
|
335
335
|
"""Get context variable by name."""
|
|
336
336
|
|
|
337
|
-
var_value_type = typing.Any if var_value_type is object else
|
|
337
|
+
var_value_type = typing.Any if var_value_type is object else var_value_type
|
|
338
338
|
generic_types = typing.get_args(get_orig_class(self))
|
|
339
339
|
if generic_types and var_value_type is object:
|
|
340
340
|
var_value_type = generic_types[0]
|
telegrinder/tools/keyboard.py
CHANGED
|
@@ -94,7 +94,7 @@ class Keyboard(ABCMarkup[Button], KeyboardModel):
|
|
|
94
94
|
self.keyboard = [row for row in self.keyboard if row]
|
|
95
95
|
return {
|
|
96
96
|
k: v.unwrap() if v and isinstance(v, Some) else v
|
|
97
|
-
for k, v in self.
|
|
97
|
+
for k, v in dataclasses.asdict(self).items()
|
|
98
98
|
if type(v) not in (NoneType, Nothing)
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -61,23 +61,23 @@ class DelayedTask(typing.Generic[CoroFunc]):
|
|
|
61
61
|
self._cancelled = True
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
64
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
|
|
65
65
|
class Lifespan:
|
|
66
66
|
startup_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
67
67
|
shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
68
68
|
|
|
69
|
-
def on_startup(self,
|
|
70
|
-
self.startup_tasks.append(to_coroutine_task(
|
|
71
|
-
return
|
|
69
|
+
def on_startup(self, task: Task, /) -> Task:
|
|
70
|
+
self.startup_tasks.append(to_coroutine_task(task))
|
|
71
|
+
return task
|
|
72
72
|
|
|
73
|
-
def on_shutdown(self,
|
|
74
|
-
self.shutdown_tasks.append(to_coroutine_task(
|
|
75
|
-
return
|
|
73
|
+
def on_shutdown(self, task: Task, /) -> Task:
|
|
74
|
+
self.shutdown_tasks.append(to_coroutine_task(task))
|
|
75
|
+
return task
|
|
76
76
|
|
|
77
|
-
def start(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
77
|
+
def start(self, loop: asyncio.AbstractEventLoop, /) -> None:
|
|
78
78
|
run_tasks(self.startup_tasks, loop)
|
|
79
79
|
|
|
80
|
-
def shutdown(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
80
|
+
def shutdown(self, loop: asyncio.AbstractEventLoop, /) -> None:
|
|
81
81
|
run_tasks(self.shutdown_tasks, loop)
|
|
82
82
|
|
|
83
83
|
|
telegrinder/tools/magic.py
CHANGED
|
@@ -15,16 +15,15 @@ if typing.TYPE_CHECKING:
|
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
Impl: typing.TypeAlias = type[classmethod]
|
|
18
|
-
NodeImpl: typing.TypeAlias = Impl
|
|
19
18
|
FuncType: typing.TypeAlias = types.FunctionType | typing.Callable[..., typing.Any]
|
|
20
19
|
|
|
21
20
|
TRANSLATIONS_KEY: typing.Final[str] = "_translations"
|
|
22
21
|
IMPL_MARK: typing.Final[str] = "_is_impl"
|
|
23
|
-
NODE_IMPL_MARK: typing.Final[str] = "_is_node_impl"
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
def cache_magic_value(mark_key: str, /):
|
|
27
25
|
def inner(func: "F") -> "F":
|
|
26
|
+
|
|
28
27
|
@wraps(func)
|
|
29
28
|
def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
|
|
30
29
|
if mark_key not in args[0].__dict__:
|
|
@@ -32,7 +31,6 @@ def cache_magic_value(mark_key: str, /):
|
|
|
32
31
|
return args[0].__dict__[mark_key]
|
|
33
32
|
|
|
34
33
|
return wrapper # type: ignore
|
|
35
|
-
|
|
36
34
|
return inner
|
|
37
35
|
|
|
38
36
|
|
|
@@ -49,9 +47,10 @@ def get_default_args(func: FuncType) -> dict[str, typing.Any]:
|
|
|
49
47
|
|
|
50
48
|
|
|
51
49
|
def get_annotations(func: FuncType, *, return_type: bool = False) -> dict[str, typing.Any]:
|
|
50
|
+
annotations = func.__annotations__
|
|
52
51
|
if not return_type and "return" in func.__annotations__:
|
|
53
|
-
|
|
54
|
-
return
|
|
52
|
+
annotations.pop("return")
|
|
53
|
+
return annotations
|
|
55
54
|
|
|
56
55
|
|
|
57
56
|
def to_str(s: str | enum.Enum) -> str:
|
|
@@ -60,13 +59,64 @@ def to_str(s: str | enum.Enum) -> str:
|
|
|
60
59
|
return s
|
|
61
60
|
|
|
62
61
|
|
|
62
|
+
@typing.overload
|
|
63
|
+
def magic_bundle(handler: FuncType, kw: dict[str, typing.Any]) -> dict[str, typing.Any]:
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@typing.overload
|
|
68
|
+
def magic_bundle(handler: FuncType, kw: dict[enum.Enum, typing.Any]) -> dict[str, typing.Any]:
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@typing.overload
|
|
73
|
+
def magic_bundle(
|
|
74
|
+
handler: FuncType,
|
|
75
|
+
kw: dict[str, typing.Any],
|
|
76
|
+
*,
|
|
77
|
+
start_idx: int = 1,
|
|
78
|
+
bundle_ctx: bool = True,
|
|
79
|
+
) -> dict[str, typing.Any]:
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@typing.overload
|
|
84
|
+
def magic_bundle(
|
|
85
|
+
handler: FuncType,
|
|
86
|
+
kw: dict[enum.Enum, typing.Any],
|
|
87
|
+
*,
|
|
88
|
+
start_idx: int = 1,
|
|
89
|
+
bundle_ctx: bool = True,
|
|
90
|
+
) -> dict[str, typing.Any]:
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@typing.overload
|
|
63
95
|
def magic_bundle(
|
|
64
96
|
handler: FuncType,
|
|
65
|
-
kw: dict[
|
|
97
|
+
kw: dict[type, typing.Any],
|
|
98
|
+
*,
|
|
99
|
+
typebundle: typing.Literal[True] = True,
|
|
100
|
+
) -> dict[str, typing.Any]:
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def magic_bundle(
|
|
105
|
+
handler: FuncType,
|
|
106
|
+
kw: dict[typing.Any, typing.Any],
|
|
66
107
|
*,
|
|
67
108
|
start_idx: int = 1,
|
|
68
109
|
bundle_ctx: bool = True,
|
|
110
|
+
typebundle: bool = False,
|
|
69
111
|
) -> dict[str, typing.Any]:
|
|
112
|
+
|
|
113
|
+
if typebundle:
|
|
114
|
+
types = get_annotations(handler, return_type=False)
|
|
115
|
+
bundle: dict[str, typing.Any] = {}
|
|
116
|
+
for name, type in types.items():
|
|
117
|
+
bundle[name] = kw[type]
|
|
118
|
+
return bundle
|
|
119
|
+
|
|
70
120
|
names = resolve_arg_names(handler, start_idx=start_idx)
|
|
71
121
|
args = get_default_args(handler)
|
|
72
122
|
args.update({to_str(k): v for k, v in kw.items() if to_str(k) in names})
|
|
@@ -85,24 +135,19 @@ def cache_translation(base_rule: "T", locale: str, translated_rule: "T") -> None
|
|
|
85
135
|
setattr(base_rule, TRANSLATIONS_KEY, translations)
|
|
86
136
|
|
|
87
137
|
|
|
88
|
-
def get_impls_by_key(cls: type["Node"], mark_key: str) -> dict[str, typing.Callable[..., typing.Any]]:
|
|
89
|
-
return {
|
|
90
|
-
name: func.__func__
|
|
91
|
-
for name, func in vars(cls).items()
|
|
92
|
-
if isinstance(func, classmethod) and getattr(func.__func__, mark_key, False)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
138
|
@typing.cast(typing.Callable[..., Impl], lambda f: f)
|
|
97
139
|
def impl(method: typing.Callable[..., typing.Any]):
|
|
98
140
|
setattr(method, IMPL_MARK, True)
|
|
99
141
|
return classmethod(method)
|
|
100
142
|
|
|
101
143
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
144
|
+
def get_impls(cls: type["Node"]) -> list[typing.Callable[..., typing.Any]]:
|
|
145
|
+
return [
|
|
146
|
+
func.__func__
|
|
147
|
+
for func in vars(cls).values()
|
|
148
|
+
if isinstance(func, classmethod) and getattr(func.__func__, IMPL_MARK, False)
|
|
149
|
+
]
|
|
150
|
+
|
|
106
151
|
|
|
107
152
|
|
|
108
153
|
__all__ = (
|
|
@@ -114,8 +159,8 @@ __all__ = (
|
|
|
114
159
|
"get_default_args",
|
|
115
160
|
"get_default_args",
|
|
116
161
|
"impl",
|
|
162
|
+
"get_impls",
|
|
117
163
|
"magic_bundle",
|
|
118
|
-
"node_impl",
|
|
119
164
|
"resolve_arg_names",
|
|
120
165
|
"to_str",
|
|
121
166
|
)
|
telegrinder/types/__init__.py
CHANGED