telegrinder 0.3.1__py3-none-any.whl → 0.3.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 +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 +11 -11
- telegrinder/bot/cute_types/base.py +258 -234
- telegrinder/bot/cute_types/callback_query.py +382 -382
- 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 +53 -53
- telegrinder/bot/cute_types/message.py +2631 -2631
- telegrinder/bot/cute_types/update.py +75 -75
- telegrinder/bot/cute_types/utils.py +92 -92
- telegrinder/bot/dispatch/__init__.py +55 -55
- telegrinder/bot/dispatch/abc.py +77 -77
- telegrinder/bot/dispatch/context.py +92 -92
- telegrinder/bot/dispatch/dispatch.py +202 -201
- 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 +128 -123
- 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 +16 -16
- telegrinder/bot/dispatch/process.py +132 -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 -211
- 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 -118
- 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 +57 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +57 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +53 -53
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +168 -170
- telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +65 -65
- 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 +238 -238
- telegrinder/bot/rules/adapter/__init__.py +9 -9
- telegrinder/bot/rules/adapter/abc.py +29 -29
- telegrinder/bot/rules/adapter/errors.py +5 -5
- telegrinder/bot/rules/adapter/event.py +76 -76
- telegrinder/bot/rules/adapter/node.py +48 -48
- telegrinder/bot/rules/adapter/raw_update.py +30 -30
- telegrinder/bot/rules/callback_data.py +171 -171
- telegrinder/bot/rules/chat_join.py +48 -48
- 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 +146 -146
- 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 +167 -147
- telegrinder/bot/scenario/choice.py +46 -44
- telegrinder/client/__init__.py +4 -4
- telegrinder/client/abc.py +75 -75
- telegrinder/client/aiohttp.py +130 -130
- telegrinder/model.py +244 -244
- telegrinder/modules.py +237 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +410 -410
- telegrinder/node/__init__.py +20 -20
- telegrinder/node/attachment.py +92 -92
- telegrinder/node/base.py +143 -144
- telegrinder/node/callback_query.py +14 -14
- telegrinder/node/command.py +33 -33
- telegrinder/node/composer.py +196 -184
- telegrinder/node/container.py +27 -27
- telegrinder/node/event.py +71 -73
- telegrinder/node/me.py +16 -16
- telegrinder/node/message.py +14 -14
- telegrinder/node/polymorphic.py +48 -52
- telegrinder/node/rule.py +76 -76
- telegrinder/node/scope.py +38 -38
- telegrinder/node/source.py +71 -71
- telegrinder/node/text.py +21 -21
- 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 +308 -308
- 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 +12 -12
- telegrinder/tools/i18n/abc.py +32 -32
- 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 +128 -128
- 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 +216 -216
- telegrinder/tools/magic.py +168 -168
- 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 +6 -6
- telegrinder/types/enums.py +672 -672
- telegrinder/types/methods.py +4633 -4633
- telegrinder/types/objects.py +6317 -6317
- telegrinder/verification_utils.py +32 -32
- {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/LICENSE +22 -22
- {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/METADATA +1 -1
- telegrinder-0.3.2.dist-info/RECORD +164 -0
- telegrinder-0.3.1.dist-info/RECORD +0 -164
- {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/WHEEL +0 -0
telegrinder/node/__init__.py
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
from .attachment import Attachment, Audio, Photo, Video
|
|
2
|
-
from .base import ComposeError, DataNode, Node, ScalarNode, is_node
|
|
3
|
-
from .callback_query import CallbackQueryNode
|
|
4
|
-
from .command import CommandInfo
|
|
5
|
-
from .composer import Composition, NodeCollection, NodeSession, compose_node, compose_nodes
|
|
6
|
-
from .container import ContainerNode
|
|
7
|
-
from .event import EventNode
|
|
8
|
-
from .me import Me
|
|
9
|
-
from .message import MessageNode
|
|
10
|
-
from .polymorphic import Polymorphic, impl
|
|
11
|
-
from .rule import RuleChain
|
|
12
|
-
from .scope import GLOBAL, PER_CALL, PER_EVENT, NodeScope, global_node, per_call, per_event
|
|
13
|
-
from .source import ChatSource, Source, UserSource
|
|
14
|
-
from .text import Text, TextInteger
|
|
15
|
-
from .tools import generate_node
|
|
16
|
-
from .update import UpdateNode
|
|
17
|
-
|
|
18
|
-
__all__ = (
|
|
1
|
+
from .attachment import Attachment, Audio, Photo, Video
|
|
2
|
+
from .base import ComposeError, DataNode, Node, ScalarNode, is_node
|
|
3
|
+
from .callback_query import CallbackQueryNode
|
|
4
|
+
from .command import CommandInfo
|
|
5
|
+
from .composer import Composition, NodeCollection, NodeSession, compose_node, compose_nodes
|
|
6
|
+
from .container import ContainerNode
|
|
7
|
+
from .event import EventNode
|
|
8
|
+
from .me import Me
|
|
9
|
+
from .message import MessageNode
|
|
10
|
+
from .polymorphic import Polymorphic, impl
|
|
11
|
+
from .rule import RuleChain
|
|
12
|
+
from .scope import GLOBAL, PER_CALL, PER_EVENT, NodeScope, global_node, per_call, per_event
|
|
13
|
+
from .source import ChatSource, Source, UserSource
|
|
14
|
+
from .text import Text, TextInteger
|
|
15
|
+
from .tools import generate_node
|
|
16
|
+
from .update import UpdateNode
|
|
17
|
+
|
|
18
|
+
__all__ = (
|
|
19
19
|
"Attachment",
|
|
20
20
|
"Audio",
|
|
21
21
|
"CallbackQueryNode",
|
|
@@ -52,5 +52,5 @@ __all__ = (
|
|
|
52
52
|
"impl",
|
|
53
53
|
"is_node",
|
|
54
54
|
"per_call",
|
|
55
|
-
"per_event",
|
|
56
|
-
)
|
|
55
|
+
"per_event",
|
|
56
|
+
)
|
telegrinder/node/attachment.py
CHANGED
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
from fntypes.co import Option, Some
|
|
5
|
-
from fntypes.option import Nothing
|
|
6
|
-
|
|
7
|
-
import telegrinder.types
|
|
8
|
-
from telegrinder.node.base import ComposeError, DataNode, ScalarNode
|
|
9
|
-
from telegrinder.node.message import MessageNode
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dataclasses.dataclass(slots=True)
|
|
13
|
-
class Attachment(DataNode):
|
|
14
|
-
attachment_type: typing.Literal["audio", "document", "photo", "poll", "video"]
|
|
15
|
-
audio: Option[telegrinder.types.Audio] = dataclasses.field(
|
|
16
|
-
default_factory=lambda: Nothing(),
|
|
17
|
-
kw_only=True,
|
|
18
|
-
)
|
|
19
|
-
document: Option[telegrinder.types.Document] = dataclasses.field(
|
|
20
|
-
default_factory=lambda: Nothing(),
|
|
21
|
-
kw_only=True,
|
|
22
|
-
)
|
|
23
|
-
photo: Option[list[telegrinder.types.PhotoSize]] = dataclasses.field(
|
|
24
|
-
default_factory=lambda: Nothing(),
|
|
25
|
-
kw_only=True,
|
|
26
|
-
)
|
|
27
|
-
poll: Option[telegrinder.types.Poll] = dataclasses.field(default_factory=lambda: Nothing(), kw_only=True)
|
|
28
|
-
video: Option[telegrinder.types.Video] = dataclasses.field(
|
|
29
|
-
default_factory=lambda: Nothing(),
|
|
30
|
-
kw_only=True,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
@classmethod
|
|
34
|
-
def compose(cls, message: MessageNode) -> "Attachment":
|
|
35
|
-
for attachment_type in ("audio", "document", "photo", "poll", "video"):
|
|
36
|
-
match getattr(message, attachment_type, Nothing()):
|
|
37
|
-
case Some(attachment):
|
|
38
|
-
return cls(attachment_type, **{attachment_type: Some(attachment)})
|
|
39
|
-
return cls.compose_error("No attachment found in message.")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@dataclasses.dataclass(slots=True)
|
|
43
|
-
class Photo(DataNode):
|
|
44
|
-
sizes: list[telegrinder.types.PhotoSize]
|
|
45
|
-
|
|
46
|
-
@classmethod
|
|
47
|
-
def compose(cls, attachment: Attachment) -> typing.Self:
|
|
48
|
-
if not attachment.photo:
|
|
49
|
-
raise ComposeError("Attachment is not a photo.")
|
|
50
|
-
return cls(attachment.photo.unwrap())
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class Video(ScalarNode, telegrinder.types.Video):
|
|
54
|
-
@classmethod
|
|
55
|
-
def compose(cls, attachment: Attachment) -> telegrinder.types.Video:
|
|
56
|
-
if not attachment.video:
|
|
57
|
-
raise ComposeError("Attachment is not a video.")
|
|
58
|
-
return attachment.video.unwrap()
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class Audio(ScalarNode, telegrinder.types.Audio):
|
|
62
|
-
@classmethod
|
|
63
|
-
def compose(cls, attachment: Attachment) -> telegrinder.types.Audio:
|
|
64
|
-
if not attachment.audio:
|
|
65
|
-
raise ComposeError("Attachment is not an audio.")
|
|
66
|
-
return attachment.audio.unwrap()
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class Document(ScalarNode, telegrinder.types.Document):
|
|
70
|
-
@classmethod
|
|
71
|
-
def compose(cls, attachment: Attachment) -> telegrinder.types.Document:
|
|
72
|
-
if not attachment.document:
|
|
73
|
-
raise ComposeError("Attachment is not a document.")
|
|
74
|
-
return attachment.document.unwrap()
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class Poll(ScalarNode, telegrinder.types.Poll):
|
|
78
|
-
@classmethod
|
|
79
|
-
def compose(cls, attachment: Attachment) -> telegrinder.types.Poll:
|
|
80
|
-
if not attachment.poll:
|
|
81
|
-
raise ComposeError("Attachment is not a poll.")
|
|
82
|
-
return attachment.poll.unwrap()
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
__all__ = (
|
|
86
|
-
"Attachment",
|
|
87
|
-
"Audio",
|
|
88
|
-
"Document",
|
|
89
|
-
"Photo",
|
|
90
|
-
"Poll",
|
|
91
|
-
"Video",
|
|
92
|
-
)
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from fntypes.co import Option, Some
|
|
5
|
+
from fntypes.option import Nothing
|
|
6
|
+
|
|
7
|
+
import telegrinder.types
|
|
8
|
+
from telegrinder.node.base import ComposeError, DataNode, ScalarNode
|
|
9
|
+
from telegrinder.node.message import MessageNode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclasses.dataclass(slots=True)
|
|
13
|
+
class Attachment(DataNode):
|
|
14
|
+
attachment_type: typing.Literal["audio", "document", "photo", "poll", "video"]
|
|
15
|
+
audio: Option[telegrinder.types.Audio] = dataclasses.field(
|
|
16
|
+
default_factory=lambda: Nothing(),
|
|
17
|
+
kw_only=True,
|
|
18
|
+
)
|
|
19
|
+
document: Option[telegrinder.types.Document] = dataclasses.field(
|
|
20
|
+
default_factory=lambda: Nothing(),
|
|
21
|
+
kw_only=True,
|
|
22
|
+
)
|
|
23
|
+
photo: Option[list[telegrinder.types.PhotoSize]] = dataclasses.field(
|
|
24
|
+
default_factory=lambda: Nothing(),
|
|
25
|
+
kw_only=True,
|
|
26
|
+
)
|
|
27
|
+
poll: Option[telegrinder.types.Poll] = dataclasses.field(default_factory=lambda: Nothing(), kw_only=True)
|
|
28
|
+
video: Option[telegrinder.types.Video] = dataclasses.field(
|
|
29
|
+
default_factory=lambda: Nothing(),
|
|
30
|
+
kw_only=True,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def compose(cls, message: MessageNode) -> "Attachment":
|
|
35
|
+
for attachment_type in ("audio", "document", "photo", "poll", "video"):
|
|
36
|
+
match getattr(message, attachment_type, Nothing()):
|
|
37
|
+
case Some(attachment):
|
|
38
|
+
return cls(attachment_type, **{attachment_type: Some(attachment)})
|
|
39
|
+
return cls.compose_error("No attachment found in message.")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclasses.dataclass(slots=True)
|
|
43
|
+
class Photo(DataNode):
|
|
44
|
+
sizes: list[telegrinder.types.PhotoSize]
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def compose(cls, attachment: Attachment) -> typing.Self:
|
|
48
|
+
if not attachment.photo:
|
|
49
|
+
raise ComposeError("Attachment is not a photo.")
|
|
50
|
+
return cls(attachment.photo.unwrap())
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Video(ScalarNode, telegrinder.types.Video):
|
|
54
|
+
@classmethod
|
|
55
|
+
def compose(cls, attachment: Attachment) -> telegrinder.types.Video:
|
|
56
|
+
if not attachment.video:
|
|
57
|
+
raise ComposeError("Attachment is not a video.")
|
|
58
|
+
return attachment.video.unwrap()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Audio(ScalarNode, telegrinder.types.Audio):
|
|
62
|
+
@classmethod
|
|
63
|
+
def compose(cls, attachment: Attachment) -> telegrinder.types.Audio:
|
|
64
|
+
if not attachment.audio:
|
|
65
|
+
raise ComposeError("Attachment is not an audio.")
|
|
66
|
+
return attachment.audio.unwrap()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Document(ScalarNode, telegrinder.types.Document):
|
|
70
|
+
@classmethod
|
|
71
|
+
def compose(cls, attachment: Attachment) -> telegrinder.types.Document:
|
|
72
|
+
if not attachment.document:
|
|
73
|
+
raise ComposeError("Attachment is not a document.")
|
|
74
|
+
return attachment.document.unwrap()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Poll(ScalarNode, telegrinder.types.Poll):
|
|
78
|
+
@classmethod
|
|
79
|
+
def compose(cls, attachment: Attachment) -> telegrinder.types.Poll:
|
|
80
|
+
if not attachment.poll:
|
|
81
|
+
raise ComposeError("Attachment is not a poll.")
|
|
82
|
+
return attachment.poll.unwrap()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
__all__ = (
|
|
86
|
+
"Attachment",
|
|
87
|
+
"Audio",
|
|
88
|
+
"Document",
|
|
89
|
+
"Photo",
|
|
90
|
+
"Poll",
|
|
91
|
+
"Video",
|
|
92
|
+
)
|
telegrinder/node/base.py
CHANGED
|
@@ -1,144 +1,143 @@
|
|
|
1
|
-
import abc
|
|
2
|
-
import inspect
|
|
3
|
-
import typing
|
|
4
|
-
from types import AsyncGeneratorType
|
|
5
|
-
|
|
6
|
-
from telegrinder.node.scope import NodeScope
|
|
7
|
-
from telegrinder.tools.magic import cache_magic_value, get_annotations
|
|
8
|
-
|
|
9
|
-
ComposeResult: typing.TypeAlias = (
|
|
10
|
-
typing.Awaitable[typing.Any] | typing.AsyncGenerator[typing.Any, None] | typing.Any
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def is_node(maybe_node: typing.Any) -> typing.TypeGuard[type["Node"]]:
|
|
15
|
-
maybe_node = maybe_node if isinstance(maybe_node, type) else typing.get_origin(maybe_node)
|
|
16
|
-
return (
|
|
17
|
-
isinstance(maybe_node, type)
|
|
18
|
-
and issubclass(maybe_node, Node)
|
|
19
|
-
or isinstance(maybe_node, Node)
|
|
20
|
-
or hasattr(maybe_node, "as_node")
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@cache_magic_value("__nodes__")
|
|
25
|
-
def get_nodes(function: typing.Callable[..., typing.Any]) -> dict[str, type["Node"]]:
|
|
26
|
-
return {k: v for k, v in get_annotations(function).items() if is_node(v)}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@cache_magic_value("__is_generator__")
|
|
30
|
-
def is_generator(
|
|
31
|
-
function: typing.Callable[..., typing.Any],
|
|
32
|
-
) -> typing.TypeGuard[AsyncGeneratorType[typing.Any, None]]:
|
|
33
|
-
return inspect.isasyncgenfunction(function)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def get_node_calc_lst(node: type["Node"]) -> list[type["Node"]]:
|
|
37
|
-
"""Returns flattened list of node types in ordering required to calculate given node.
|
|
38
|
-
Provides caching for passed node type"""
|
|
39
|
-
|
|
40
|
-
if calc_lst := getattr(node, "__nodes_calc_lst__", None):
|
|
41
|
-
return calc_lst
|
|
42
|
-
nodes_lst: list[type[
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@
|
|
85
|
-
@
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
|
|
144
|
-
)
|
|
1
|
+
import abc
|
|
2
|
+
import inspect
|
|
3
|
+
import typing
|
|
4
|
+
from types import AsyncGeneratorType
|
|
5
|
+
|
|
6
|
+
from telegrinder.node.scope import NodeScope
|
|
7
|
+
from telegrinder.tools.magic import cache_magic_value, get_annotations
|
|
8
|
+
|
|
9
|
+
ComposeResult: typing.TypeAlias = (
|
|
10
|
+
typing.Awaitable[typing.Any] | typing.AsyncGenerator[typing.Any, None] | typing.Any
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def is_node(maybe_node: typing.Any) -> typing.TypeGuard[type["Node"]]:
|
|
15
|
+
maybe_node = maybe_node if isinstance(maybe_node, type) else typing.get_origin(maybe_node)
|
|
16
|
+
return (
|
|
17
|
+
isinstance(maybe_node, type)
|
|
18
|
+
and issubclass(maybe_node, Node)
|
|
19
|
+
or isinstance(maybe_node, Node)
|
|
20
|
+
or hasattr(maybe_node, "as_node")
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@cache_magic_value("__nodes__")
|
|
25
|
+
def get_nodes(function: typing.Callable[..., typing.Any]) -> dict[str, type["Node"]]:
|
|
26
|
+
return {k: v for k, v in get_annotations(function).items() if is_node(v)}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@cache_magic_value("__is_generator__")
|
|
30
|
+
def is_generator(
|
|
31
|
+
function: typing.Callable[..., typing.Any],
|
|
32
|
+
) -> typing.TypeGuard[AsyncGeneratorType[typing.Any, None]]:
|
|
33
|
+
return inspect.isasyncgenfunction(function)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_node_calc_lst(node: type["Node"]) -> list[type["Node"]]:
|
|
37
|
+
"""Returns flattened list of node types in ordering required to calculate given node.
|
|
38
|
+
Provides caching for passed node type."""
|
|
39
|
+
|
|
40
|
+
if calc_lst := getattr(node, "__nodes_calc_lst__", None):
|
|
41
|
+
return calc_lst
|
|
42
|
+
nodes_lst: list[type[Node]] = []
|
|
43
|
+
for node_type in node.as_node().get_subnodes().values():
|
|
44
|
+
nodes_lst.extend(get_node_calc_lst(node_type))
|
|
45
|
+
calc_lst = [*nodes_lst, node]
|
|
46
|
+
setattr(node, "__nodes_calc_lst__", calc_lst)
|
|
47
|
+
return calc_lst
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ComposeError(BaseException):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Node(abc.ABC):
|
|
55
|
+
node: str = "node"
|
|
56
|
+
scope: NodeScope = NodeScope.PER_EVENT
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
@abc.abstractmethod
|
|
60
|
+
def compose(cls, *args, **kwargs) -> ComposeResult:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def compose_error(cls, error: str | None = None) -> typing.NoReturn:
|
|
65
|
+
raise ComposeError(error)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def get_subnodes(cls) -> dict[str, type["Node"]]:
|
|
69
|
+
return get_nodes(cls.compose)
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def as_node(cls) -> type[typing.Self]:
|
|
73
|
+
return cls
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def is_generator(cls) -> bool:
|
|
77
|
+
return is_generator(cls.compose)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class DataNode(Node, abc.ABC):
|
|
81
|
+
node = "data"
|
|
82
|
+
|
|
83
|
+
@typing.dataclass_transform()
|
|
84
|
+
@classmethod
|
|
85
|
+
@abc.abstractmethod
|
|
86
|
+
async def compose(cls, *args, **kwargs) -> ComposeResult:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ScalarNodeProto(Node, abc.ABC):
|
|
91
|
+
@classmethod
|
|
92
|
+
@abc.abstractmethod
|
|
93
|
+
async def compose(cls, *args, **kwargs) -> ComposeResult:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
SCALAR_NODE = type("ScalarNode", (), {"node": "scalar"})
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if typing.TYPE_CHECKING:
|
|
101
|
+
|
|
102
|
+
class ScalarNode(ScalarNodeProto, abc.ABC):
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
else:
|
|
106
|
+
|
|
107
|
+
def __init_subclass__(cls, *args, **kwargs): # noqa: N807
|
|
108
|
+
if any(issubclass(base, ScalarNodeProto) for base in cls.__bases__ if base is not ScalarNode):
|
|
109
|
+
raise RuntimeError("Scalar nodes do not support inheritance.")
|
|
110
|
+
|
|
111
|
+
def _as_node(cls, bases, dct):
|
|
112
|
+
if not hasattr(cls, "_scalar_node_type"):
|
|
113
|
+
dct.update(cls.__dict__)
|
|
114
|
+
scalar_node_type = type(cls.__name__, bases, dct)
|
|
115
|
+
setattr(cls, "_scalar_node_type", scalar_node_type)
|
|
116
|
+
return scalar_node_type
|
|
117
|
+
return getattr(cls, "_scalar_node_type")
|
|
118
|
+
|
|
119
|
+
def create_class(name, bases, dct):
|
|
120
|
+
return type(
|
|
121
|
+
"Scalar",
|
|
122
|
+
(SCALAR_NODE,),
|
|
123
|
+
{
|
|
124
|
+
"as_node": classmethod(lambda cls: _as_node(cls, bases, dct)),
|
|
125
|
+
"scope": Node.scope,
|
|
126
|
+
"__init_subclass__": __init_subclass__,
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
class ScalarNode(ScalarNodeProto, abc.ABC, metaclass=create_class):
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
__all__ = (
|
|
135
|
+
"ComposeError",
|
|
136
|
+
"DataNode",
|
|
137
|
+
"Node",
|
|
138
|
+
"SCALAR_NODE",
|
|
139
|
+
"ScalarNode",
|
|
140
|
+
"ScalarNodeProto",
|
|
141
|
+
"get_nodes",
|
|
142
|
+
"is_node",
|
|
143
|
+
)
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
2
|
-
from telegrinder.node.base import ComposeError, ScalarNode
|
|
3
|
-
from telegrinder.node.update import UpdateNode
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class CallbackQueryNode(ScalarNode, CallbackQueryCute):
|
|
7
|
-
@classmethod
|
|
8
|
-
def compose(cls, update: UpdateNode) -> CallbackQueryCute:
|
|
9
|
-
if not update.callback_query:
|
|
10
|
-
raise ComposeError("Update is not a callback_query.")
|
|
11
|
-
return update.callback_query.unwrap()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
__all__ = ("CallbackQueryNode",)
|
|
1
|
+
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
2
|
+
from telegrinder.node.base import ComposeError, ScalarNode
|
|
3
|
+
from telegrinder.node.update import UpdateNode
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CallbackQueryNode(ScalarNode, CallbackQueryCute):
|
|
7
|
+
@classmethod
|
|
8
|
+
def compose(cls, update: UpdateNode) -> CallbackQueryCute:
|
|
9
|
+
if not update.callback_query:
|
|
10
|
+
raise ComposeError("Update is not a callback_query.")
|
|
11
|
+
return update.callback_query.unwrap()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ("CallbackQueryNode",)
|
telegrinder/node/command.py
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
from dataclasses import dataclass, field
|
|
3
|
-
|
|
4
|
-
from fntypes.option import Nothing, Option, Some
|
|
5
|
-
|
|
6
|
-
from telegrinder.node.base import DataNode
|
|
7
|
-
from telegrinder.node.text import Text
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def single_split(s: str, separator: str) -> tuple[str, str]:
|
|
11
|
-
left, *right = s.split(separator, 1)
|
|
12
|
-
return left, (right[0] if right else "")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def cut_mention(text: str) -> tuple[str, Option[str]]:
|
|
16
|
-
left, right = single_split(text, "@")
|
|
17
|
-
return left, Some(right) if right else Nothing()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@dataclass(slots=True)
|
|
21
|
-
class CommandInfo(DataNode):
|
|
22
|
-
name: str
|
|
23
|
-
arguments: str
|
|
24
|
-
mention: Option[str] = field(default_factory=Nothing)
|
|
25
|
-
|
|
26
|
-
@classmethod
|
|
27
|
-
def compose(cls, text: Text) -> typing.Self:
|
|
28
|
-
name, arguments = single_split(text, separator=" ")
|
|
29
|
-
name, mention = cut_mention(name)
|
|
30
|
-
return cls(name, arguments, mention)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
__all__ = ("CommandInfo", "cut_mention", "single_split")
|
|
1
|
+
import typing
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from fntypes.option import Nothing, Option, Some
|
|
5
|
+
|
|
6
|
+
from telegrinder.node.base import DataNode
|
|
7
|
+
from telegrinder.node.text import Text
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def single_split(s: str, separator: str) -> tuple[str, str]:
|
|
11
|
+
left, *right = s.split(separator, 1)
|
|
12
|
+
return left, (right[0] if right else "")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def cut_mention(text: str) -> tuple[str, Option[str]]:
|
|
16
|
+
left, right = single_split(text, "@")
|
|
17
|
+
return left, Some(right) if right else Nothing()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(slots=True)
|
|
21
|
+
class CommandInfo(DataNode):
|
|
22
|
+
name: str
|
|
23
|
+
arguments: str
|
|
24
|
+
mention: Option[str] = field(default_factory=Nothing)
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def compose(cls, text: Text) -> typing.Self:
|
|
28
|
+
name, arguments = single_split(text, separator=" ")
|
|
29
|
+
name, mention = cut_mention(name)
|
|
30
|
+
return cls(name, arguments, mention)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__all__ = ("CommandInfo", "cut_mention", "single_split")
|