telegrinder 0.4.2__py3-none-any.whl → 0.5.1__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 +37 -55
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +6 -4
- telegrinder/api/api.py +100 -26
- telegrinder/api/error.py +42 -8
- telegrinder/api/response.py +4 -1
- telegrinder/api/token.py +2 -2
- telegrinder/bot/__init__.py +9 -25
- telegrinder/bot/bot.py +31 -25
- telegrinder/bot/cute_types/__init__.py +0 -0
- telegrinder/bot/cute_types/base.py +103 -61
- telegrinder/bot/cute_types/callback_query.py +447 -400
- telegrinder/bot/cute_types/chat_join_request.py +59 -62
- telegrinder/bot/cute_types/chat_member_updated.py +154 -157
- telegrinder/bot/cute_types/inline_query.py +41 -44
- telegrinder/bot/cute_types/message.py +98 -67
- telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
- telegrinder/bot/cute_types/update.py +1 -8
- telegrinder/bot/cute_types/utils.py +1 -1
- telegrinder/bot/dispatch/__init__.py +10 -15
- telegrinder/bot/dispatch/abc.py +12 -11
- telegrinder/bot/dispatch/action.py +104 -0
- telegrinder/bot/dispatch/context.py +32 -26
- telegrinder/bot/dispatch/dispatch.py +61 -134
- telegrinder/bot/dispatch/handler/__init__.py +2 -0
- telegrinder/bot/dispatch/handler/abc.py +10 -8
- telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
- telegrinder/bot/dispatch/handler/base.py +10 -33
- telegrinder/bot/dispatch/handler/document_reply.py +2 -3
- telegrinder/bot/dispatch/handler/func.py +55 -87
- telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
- telegrinder/bot/dispatch/handler/message_reply.py +2 -3
- telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
- telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
- telegrinder/bot/dispatch/handler/video_reply.py +2 -3
- telegrinder/bot/dispatch/middleware/__init__.py +0 -0
- telegrinder/bot/dispatch/middleware/abc.py +79 -55
- telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
- telegrinder/bot/dispatch/process.py +84 -105
- telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
- telegrinder/bot/dispatch/return_manager/abc.py +102 -65
- telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
- telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
- telegrinder/bot/dispatch/return_manager/message.py +8 -10
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
- telegrinder/bot/dispatch/view/__init__.py +4 -4
- telegrinder/bot/dispatch/view/abc.py +6 -16
- telegrinder/bot/dispatch/view/base.py +54 -178
- telegrinder/bot/dispatch/view/box.py +19 -18
- telegrinder/bot/dispatch/view/callback_query.py +4 -8
- telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
- telegrinder/bot/dispatch/view/chat_member.py +5 -25
- telegrinder/bot/dispatch/view/error.py +9 -0
- telegrinder/bot/dispatch/view/inline_query.py +4 -8
- telegrinder/bot/dispatch/view/message.py +5 -25
- telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
- telegrinder/bot/dispatch/view/raw.py +3 -109
- telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
- telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
- telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
- telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
- telegrinder/bot/polling/__init__.py +0 -0
- telegrinder/bot/polling/abc.py +0 -0
- telegrinder/bot/polling/polling.py +209 -88
- telegrinder/bot/rules/__init__.py +3 -16
- telegrinder/bot/rules/abc.py +42 -122
- telegrinder/bot/rules/callback_data.py +29 -49
- telegrinder/bot/rules/chat_join.py +5 -23
- telegrinder/bot/rules/command.py +8 -4
- telegrinder/bot/rules/enum_text.py +3 -4
- telegrinder/bot/rules/func.py +7 -14
- telegrinder/bot/rules/fuzzy.py +3 -4
- telegrinder/bot/rules/inline.py +8 -20
- telegrinder/bot/rules/integer.py +2 -3
- telegrinder/bot/rules/is_from.py +12 -11
- telegrinder/bot/rules/logic.py +11 -5
- telegrinder/bot/rules/markup.py +22 -14
- telegrinder/bot/rules/mention.py +8 -7
- telegrinder/bot/rules/message_entities.py +8 -4
- telegrinder/bot/rules/node.py +23 -12
- telegrinder/bot/rules/payload.py +5 -4
- telegrinder/bot/rules/payment_invoice.py +6 -21
- telegrinder/bot/rules/regex.py +2 -4
- telegrinder/bot/rules/rule_enum.py +8 -7
- telegrinder/bot/rules/start.py +5 -6
- telegrinder/bot/rules/state.py +1 -1
- telegrinder/bot/rules/text.py +4 -15
- telegrinder/bot/rules/update.py +3 -4
- telegrinder/bot/scenario/__init__.py +0 -0
- telegrinder/bot/scenario/abc.py +6 -5
- telegrinder/bot/scenario/checkbox.py +1 -1
- telegrinder/bot/scenario/choice.py +30 -39
- telegrinder/client/__init__.py +3 -5
- telegrinder/client/abc.py +11 -6
- telegrinder/client/aiohttp.py +141 -27
- telegrinder/client/form_data.py +1 -1
- telegrinder/model.py +61 -89
- telegrinder/modules.py +325 -102
- telegrinder/msgspec_utils/__init__.py +40 -0
- telegrinder/msgspec_utils/abc.py +18 -0
- telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
- telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
- telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +389 -0
- telegrinder/msgspec_utils/encoder.py +206 -0
- telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
- telegrinder/msgspec_utils/tools.py +75 -0
- telegrinder/node/__init__.py +24 -7
- telegrinder/node/attachment.py +1 -0
- telegrinder/node/base.py +154 -72
- telegrinder/node/callback_query.py +5 -5
- telegrinder/node/collection.py +39 -0
- telegrinder/node/command.py +1 -2
- telegrinder/node/composer.py +121 -72
- telegrinder/node/container.py +11 -8
- telegrinder/node/context.py +48 -0
- telegrinder/node/either.py +27 -40
- telegrinder/node/error.py +41 -0
- telegrinder/node/event.py +37 -11
- telegrinder/node/exceptions.py +7 -0
- telegrinder/node/file.py +0 -0
- telegrinder/node/i18n.py +108 -0
- telegrinder/node/me.py +3 -2
- telegrinder/node/payload.py +1 -1
- telegrinder/node/polymorphic.py +63 -28
- telegrinder/node/reply_message.py +12 -0
- telegrinder/node/rule.py +6 -13
- telegrinder/node/scope.py +14 -5
- telegrinder/node/session.py +53 -0
- telegrinder/node/source.py +41 -9
- telegrinder/node/text.py +1 -2
- telegrinder/node/tools/__init__.py +0 -0
- telegrinder/node/tools/generator.py +3 -5
- telegrinder/node/utility.py +16 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +0 -0
- telegrinder/tools/__init__.py +48 -88
- telegrinder/tools/aio.py +103 -0
- telegrinder/tools/callback_data_serialization/__init__.py +5 -0
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +2 -18
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
- telegrinder/tools/formatting/deep_links/parsing.py +90 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html_formatter.py +18 -45
- telegrinder/tools/fullname.py +83 -0
- telegrinder/tools/global_context/__init__.py +4 -3
- telegrinder/tools/global_context/abc.py +17 -14
- telegrinder/tools/global_context/builtin_context.py +39 -0
- telegrinder/tools/global_context/global_context.py +138 -39
- telegrinder/tools/input_file_directory.py +0 -0
- telegrinder/tools/keyboard/__init__.py +39 -0
- telegrinder/tools/keyboard/abc.py +159 -0
- telegrinder/tools/keyboard/base.py +77 -0
- telegrinder/tools/keyboard/buttons/__init__.py +14 -0
- telegrinder/tools/keyboard/buttons/base.py +18 -0
- telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
- telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
- telegrinder/tools/keyboard/buttons/tools.py +18 -0
- telegrinder/tools/keyboard/data.py +20 -0
- telegrinder/tools/keyboard/keyboard.py +131 -0
- telegrinder/tools/keyboard/static_keyboard.py +83 -0
- telegrinder/tools/lifespan.py +87 -51
- telegrinder/tools/limited_dict.py +4 -1
- telegrinder/tools/loop_wrapper.py +332 -0
- telegrinder/tools/magic/__init__.py +32 -0
- telegrinder/tools/magic/annotations.py +165 -0
- telegrinder/tools/magic/dictionary.py +20 -0
- telegrinder/tools/magic/function.py +246 -0
- telegrinder/tools/magic/shortcut.py +111 -0
- telegrinder/tools/parse_mode.py +9 -3
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_storage/__init__.py +0 -0
- telegrinder/tools/state_storage/abc.py +6 -1
- telegrinder/tools/state_storage/memory.py +1 -1
- telegrinder/tools/strings.py +0 -0
- telegrinder/types/__init__.py +307 -268
- telegrinder/types/enums.py +68 -37
- telegrinder/types/input_file.py +3 -3
- telegrinder/types/methods.py +5699 -5055
- telegrinder/types/methods_utils.py +62 -0
- telegrinder/types/objects.py +1782 -994
- telegrinder/verification_utils.py +3 -1
- telegrinder-0.5.1.dist-info/METADATA +162 -0
- telegrinder-0.5.1.dist-info/RECORD +200 -0
- {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/licenses/LICENSE +2 -2
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
- telegrinder/bot/rules/id.py +0 -24
- telegrinder/bot/rules/message.py +0 -15
- telegrinder/client/sonic.py +0 -212
- telegrinder/msgspec_utils.py +0 -478
- telegrinder/tools/adapter/__init__.py +0 -19
- telegrinder/tools/adapter/abc.py +0 -49
- telegrinder/tools/adapter/dataclass.py +0 -56
- telegrinder/tools/adapter/errors.py +0 -5
- telegrinder/tools/adapter/event.py +0 -61
- telegrinder/tools/adapter/node.py +0 -46
- telegrinder/tools/adapter/raw_event.py +0 -27
- telegrinder/tools/adapter/raw_update.py +0 -30
- telegrinder/tools/callback_data_serilization/__init__.py +0 -5
- telegrinder/tools/error_handler/__init__.py +0 -10
- telegrinder/tools/error_handler/abc.py +0 -30
- telegrinder/tools/error_handler/error.py +0 -9
- telegrinder/tools/error_handler/error_handler.py +0 -179
- telegrinder/tools/formatting/spec_html_formats.py +0 -75
- telegrinder/tools/functional.py +0 -8
- telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
- telegrinder/tools/i18n/__init__.py +0 -12
- telegrinder/tools/i18n/abc.py +0 -32
- telegrinder/tools/i18n/middleware/__init__.py +0 -3
- telegrinder/tools/i18n/middleware/abc.py +0 -22
- telegrinder/tools/i18n/simple.py +0 -43
- telegrinder/tools/keyboard.py +0 -132
- telegrinder/tools/loop_wrapper/__init__.py +0 -4
- telegrinder/tools/loop_wrapper/abc.py +0 -20
- telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
- telegrinder/tools/magic.py +0 -344
- telegrinder-0.4.2.dist-info/METADATA +0 -151
- telegrinder-0.4.2.dist-info/RECORD +0 -182
- {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import types
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
import msgspec
|
|
5
|
+
from fntypes.co import Nothing, Variative
|
|
6
|
+
|
|
7
|
+
if typing.TYPE_CHECKING:
|
|
8
|
+
from telegrinder.tools.fullname import fullname
|
|
9
|
+
from telegrinder.tools.magic.function import bundle
|
|
10
|
+
|
|
11
|
+
def get_class_annotations(obj: typing.Any, /) -> dict[str, typing.Any]: ...
|
|
12
|
+
|
|
13
|
+
def get_type_hints(obj: typing.Any, /) -> dict[str, typing.Any]: ...
|
|
14
|
+
|
|
15
|
+
else:
|
|
16
|
+
from msgspec._utils import get_class_annotations, get_type_hints
|
|
17
|
+
|
|
18
|
+
def bundle(*args, **kwargs):
|
|
19
|
+
from telegrinder.tools.magic.function import bundle
|
|
20
|
+
|
|
21
|
+
return bundle(*args, **kwargs)
|
|
22
|
+
|
|
23
|
+
def fullname(*args, **kwargs):
|
|
24
|
+
from telegrinder.tools.fullname import fullname
|
|
25
|
+
|
|
26
|
+
return fullname(*args, **kwargs)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_COMMON_TYPES = frozenset((str, int, float, bool, None, Variative))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_origin[T](t: T, /) -> type[T]:
|
|
33
|
+
t_ = typing.get_origin(t) or t
|
|
34
|
+
t_ = type(t_) if not isinstance(t_, type) else t_
|
|
35
|
+
return typing.cast("type[T]", t_)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def is_common_type[T](t: T, /) -> typing.TypeGuard[type[T]]:
|
|
39
|
+
if not isinstance(t, type):
|
|
40
|
+
return False
|
|
41
|
+
return t in _COMMON_TYPES or issubclass(t, msgspec.Struct) or hasattr(t, "__dataclass_fields__")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def struct_asdict(
|
|
45
|
+
struct: msgspec.Struct,
|
|
46
|
+
/,
|
|
47
|
+
*,
|
|
48
|
+
exclude_unset: bool = True,
|
|
49
|
+
unset_as_nothing: bool = False,
|
|
50
|
+
) -> dict[str, typing.Any]:
|
|
51
|
+
return {
|
|
52
|
+
k: v if not unset_as_nothing else Nothing() if v is msgspec.UNSET else v
|
|
53
|
+
for k, v in msgspec.structs.asdict(struct).items()
|
|
54
|
+
if not (exclude_unset and isinstance(v, msgspec.UnsetType | types.NoneType | Nothing))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def type_check(obj: typing.Any, t: typing.Any) -> bool:
|
|
59
|
+
return (
|
|
60
|
+
isinstance(obj, t)
|
|
61
|
+
if isinstance(t, type) and issubclass(t, msgspec.Struct)
|
|
62
|
+
else type(obj) in t
|
|
63
|
+
if isinstance(t, tuple)
|
|
64
|
+
else type(obj) is t
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
__all__ = (
|
|
69
|
+
"get_class_annotations",
|
|
70
|
+
"get_origin",
|
|
71
|
+
"get_type_hints",
|
|
72
|
+
"is_common_type",
|
|
73
|
+
"struct_asdict",
|
|
74
|
+
"type_check",
|
|
75
|
+
)
|
telegrinder/node/__init__.py
CHANGED
|
@@ -19,7 +19,7 @@ from .base import (
|
|
|
19
19
|
Name,
|
|
20
20
|
Node,
|
|
21
21
|
NodeComposeFunction,
|
|
22
|
-
|
|
22
|
+
NodeConvertable,
|
|
23
23
|
NodeProto,
|
|
24
24
|
NodeType,
|
|
25
25
|
as_node,
|
|
@@ -32,15 +32,20 @@ from .callback_query import (
|
|
|
32
32
|
CallbackQueryDataJson,
|
|
33
33
|
Field,
|
|
34
34
|
)
|
|
35
|
+
from .collection import Collection
|
|
35
36
|
from .command import CommandInfo
|
|
36
37
|
from .composer import NodeCollection, NodeSession, compose_node, compose_nodes
|
|
37
38
|
from .container import ContainerNode
|
|
39
|
+
from .context import NODE_CONTEXT, NodeGlobalContext
|
|
38
40
|
from .either import Either, Optional
|
|
41
|
+
from .error import Error
|
|
39
42
|
from .event import EventNode
|
|
40
43
|
from .file import File, FileId
|
|
44
|
+
from .i18n import ABCTranslator, KeySeparator
|
|
41
45
|
from .me import Me
|
|
42
46
|
from .payload import Payload, PayloadData, PayloadSerializer
|
|
43
47
|
from .polymorphic import Polymorphic, impl
|
|
48
|
+
from .reply_message import ReplyMessage
|
|
44
49
|
from .rule import RuleChain
|
|
45
50
|
from .scope import (
|
|
46
51
|
GLOBAL,
|
|
@@ -51,17 +56,26 @@ from .scope import (
|
|
|
51
56
|
per_call,
|
|
52
57
|
per_event,
|
|
53
58
|
)
|
|
54
|
-
from .source import ChatSource, Source, UserId, UserSource
|
|
55
|
-
from .text import Text, TextInteger, TextLiteral
|
|
59
|
+
from .source import ChatId, ChatSource, Locale, Source, UserId, UserSource
|
|
60
|
+
from .text import Caption, Text, TextInteger, TextLiteral
|
|
56
61
|
from .tools import generate_node
|
|
62
|
+
from .utility import TypeArgs
|
|
57
63
|
|
|
58
64
|
__all__ = (
|
|
65
|
+
"GLOBAL",
|
|
66
|
+
"NODE_CONTEXT",
|
|
67
|
+
"PER_CALL",
|
|
68
|
+
"PER_EVENT",
|
|
69
|
+
"ABCTranslator",
|
|
59
70
|
"Animation",
|
|
60
71
|
"Attachment",
|
|
61
72
|
"Audio",
|
|
62
73
|
"CallbackQueryData",
|
|
63
74
|
"CallbackQueryDataJson",
|
|
75
|
+
"Caption",
|
|
76
|
+
"ChatId",
|
|
64
77
|
"ChatSource",
|
|
78
|
+
"Collection",
|
|
65
79
|
"CommandInfo",
|
|
66
80
|
"Composable",
|
|
67
81
|
"ComposeError",
|
|
@@ -69,39 +83,42 @@ __all__ = (
|
|
|
69
83
|
"DataNode",
|
|
70
84
|
"Document",
|
|
71
85
|
"Either",
|
|
86
|
+
"Error",
|
|
72
87
|
"EventNode",
|
|
73
88
|
"FactoryNode",
|
|
74
89
|
"Field",
|
|
75
90
|
"Field",
|
|
76
91
|
"File",
|
|
77
92
|
"FileId",
|
|
78
|
-
"GLOBAL",
|
|
79
93
|
"GlobalNode",
|
|
80
94
|
"IsNode",
|
|
95
|
+
"KeySeparator",
|
|
96
|
+
"Locale",
|
|
81
97
|
"Me",
|
|
82
98
|
"Name",
|
|
83
99
|
"Node",
|
|
84
100
|
"NodeCollection",
|
|
85
101
|
"NodeComposeFunction",
|
|
86
|
-
"
|
|
102
|
+
"NodeConvertable",
|
|
103
|
+
"NodeGlobalContext",
|
|
87
104
|
"NodeProto",
|
|
88
105
|
"NodeScope",
|
|
89
106
|
"NodeSession",
|
|
90
107
|
"NodeType",
|
|
91
108
|
"Optional",
|
|
92
|
-
"PER_CALL",
|
|
93
|
-
"PER_EVENT",
|
|
94
109
|
"Payload",
|
|
95
110
|
"PayloadData",
|
|
96
111
|
"PayloadSerializer",
|
|
97
112
|
"Photo",
|
|
98
113
|
"Polymorphic",
|
|
114
|
+
"ReplyMessage",
|
|
99
115
|
"RuleChain",
|
|
100
116
|
"Source",
|
|
101
117
|
"SuccessfulPayment",
|
|
102
118
|
"Text",
|
|
103
119
|
"TextInteger",
|
|
104
120
|
"TextLiteral",
|
|
121
|
+
"TypeArgs",
|
|
105
122
|
"UserId",
|
|
106
123
|
"UserSource",
|
|
107
124
|
"Video",
|
telegrinder/node/attachment.py
CHANGED
telegrinder/node/base.py
CHANGED
|
@@ -3,12 +3,19 @@ from __future__ import annotations
|
|
|
3
3
|
import abc
|
|
4
4
|
import inspect
|
|
5
5
|
from collections import deque
|
|
6
|
-
from
|
|
6
|
+
from functools import reduce
|
|
7
|
+
from itertools import islice
|
|
8
|
+
from types import CodeType, NoneType, UnionType, resolve_bases
|
|
7
9
|
|
|
8
10
|
import typing_extensions as typing
|
|
9
11
|
|
|
12
|
+
from telegrinder.node.context import NODE_CONTEXT
|
|
13
|
+
from telegrinder.node.exceptions import ComposeError
|
|
10
14
|
from telegrinder.node.scope import NodeScope
|
|
11
|
-
from telegrinder.
|
|
15
|
+
from telegrinder.node.session import NodeSession
|
|
16
|
+
from telegrinder.tools.aio import Generator
|
|
17
|
+
from telegrinder.tools.fullname import fullname
|
|
18
|
+
from telegrinder.tools.magic.function import function_context, get_func_annotations
|
|
12
19
|
from telegrinder.tools.strings import to_pascal_case
|
|
13
20
|
|
|
14
21
|
if typing.TYPE_CHECKING:
|
|
@@ -16,94 +23,138 @@ if typing.TYPE_CHECKING:
|
|
|
16
23
|
else:
|
|
17
24
|
|
|
18
25
|
def generate_node(*args, **kwargs):
|
|
19
|
-
|
|
20
|
-
if "__generate_node" not in globalns:
|
|
21
|
-
import telegrinder.node.tools.generator
|
|
26
|
+
from telegrinder.node.tools.generator import generate_node
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return globals()["__generate_node"](*args, **kwargs)
|
|
28
|
+
return generate_node(*args, **kwargs)
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
type NodeType = Node | NodeProto[typing.Any]
|
|
29
32
|
type IsNode = NodeType | type[NodeType]
|
|
33
|
+
type AnyNode = IsNode | NodeConvertable
|
|
30
34
|
|
|
31
35
|
T = typing.TypeVar("T", default=typing.Any)
|
|
32
36
|
|
|
33
|
-
ComposeResult: typing.TypeAlias = T | typing.Awaitable[T] |
|
|
37
|
+
ComposeResult: typing.TypeAlias = T | typing.Awaitable[T] | Generator[T, typing.Any, typing.Any]
|
|
38
|
+
|
|
39
|
+
_NODEFAULT: typing.Final[object] = object()
|
|
40
|
+
_NONE_TYPES: typing.Final[set[typing.Any]] = {None, NoneType}
|
|
41
|
+
_UNION_TYPES: typing.Final[set[typing.Any]] = {typing.Union, UnionType}
|
|
42
|
+
UNWRAPPED_NODE_KEY: typing.Final[str] = "__unwrapped_node__"
|
|
43
|
+
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
def is_node(maybe_node: typing.Any, /) -> typing.TypeIs[AnyNode]:
|
|
46
|
+
return hasattr(maybe_node, "as_node") or is_node_type(maybe_node)
|
|
36
47
|
|
|
37
48
|
|
|
38
49
|
@typing.overload
|
|
39
|
-
def
|
|
50
|
+
def as_node(maybe_node: typing.Any, /) -> IsNode: ...
|
|
40
51
|
|
|
41
52
|
|
|
42
53
|
@typing.overload
|
|
43
|
-
def
|
|
54
|
+
def as_node(
|
|
55
|
+
maybe_node: typing.Any,
|
|
56
|
+
/,
|
|
57
|
+
*,
|
|
58
|
+
raise_exception: typing.Literal[False],
|
|
59
|
+
) -> IsNode | None: ...
|
|
60
|
+
|
|
44
61
|
|
|
62
|
+
def union_as_node(union: UnionType, /) -> IsNode | None:
|
|
63
|
+
from telegrinder.node.either import _Either
|
|
45
64
|
|
|
46
|
-
|
|
47
|
-
if
|
|
48
|
-
|
|
49
|
-
if not isinstance(maybe_node, type):
|
|
50
|
-
maybe_node = typing.get_origin(maybe_node) or maybe_node
|
|
65
|
+
args = typing.get_args(union)
|
|
66
|
+
if not args:
|
|
67
|
+
return None
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
plain, opt = [t for t in args if t not in _NONE_TYPES], any(t in _NONE_TYPES for t in args)
|
|
70
|
+
if not plain:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
nodes = typing.cast("IsNode | tuple[IsNode, ...] | None", as_node(*plain, raise_exception=False))
|
|
74
|
+
if nodes is None:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
nodes = (nodes,) if not isinstance(nodes, tuple) else nodes
|
|
78
|
+
node = reduce(
|
|
79
|
+
lambda left, right: _Either[left, right],
|
|
80
|
+
islice(nodes, 1, None),
|
|
81
|
+
nodes[0], # type: ignore
|
|
58
82
|
)
|
|
83
|
+
return _Either[node] if opt else node
|
|
59
84
|
|
|
60
85
|
|
|
61
86
|
@typing.overload
|
|
62
|
-
def as_node(
|
|
87
|
+
def as_node(*maybe_nodes: typing.Any) -> tuple[IsNode, ...]: ...
|
|
63
88
|
|
|
64
89
|
|
|
65
90
|
@typing.overload
|
|
66
|
-
def as_node(
|
|
91
|
+
def as_node(
|
|
92
|
+
*maybe_nodes: typing.Any,
|
|
93
|
+
raise_exception: typing.Literal[False],
|
|
94
|
+
) -> tuple[IsNode, ...] | None: ...
|
|
67
95
|
|
|
68
96
|
|
|
69
|
-
|
|
70
|
-
|
|
97
|
+
def as_node(
|
|
98
|
+
*maybe_nodes: typing.Any,
|
|
99
|
+
raise_exception: bool = True,
|
|
100
|
+
) -> IsNode | tuple[IsNode, ...] | None:
|
|
101
|
+
nodes = []
|
|
71
102
|
|
|
103
|
+
for maybe_node in maybe_nodes:
|
|
104
|
+
if isinstance(maybe_node, typing.TypeAliasType):
|
|
105
|
+
maybe_node = maybe_node.__value__
|
|
106
|
+
|
|
107
|
+
if (typing.get_origin(maybe_node) or maybe_node) in _UNION_TYPES and (
|
|
108
|
+
maybe_node := union_as_node(union := maybe_node)
|
|
109
|
+
) is None:
|
|
110
|
+
if not raise_exception:
|
|
111
|
+
return None
|
|
112
|
+
raise TypeError(f"Union `{union!r}` doesn't contain all types of Node.")
|
|
113
|
+
|
|
114
|
+
if not is_node_type(orig := typing.get_origin(maybe_node) or maybe_node):
|
|
115
|
+
if not hasattr(orig, "as_node"):
|
|
116
|
+
if not raise_exception:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
raise TypeError(
|
|
120
|
+
f"{'Type of' if isinstance(maybe_node, type) else 'Object of type'} "
|
|
121
|
+
f"{fullname(maybe_node)!r} cannot be resolved as Node.",
|
|
122
|
+
)
|
|
72
123
|
|
|
73
|
-
|
|
74
|
-
def as_node(*maybe_nodes: typing.Any) -> tuple[NodeType, ...]: ...
|
|
124
|
+
maybe_node = orig.as_node()
|
|
75
125
|
|
|
126
|
+
nodes.append(maybe_node)
|
|
76
127
|
|
|
77
|
-
|
|
78
|
-
def as_node(*maybe_nodes: type[typing.Any] | typing.Any) -> tuple[IsNode, ...]: ...
|
|
128
|
+
return nodes[0] if len(nodes) == 1 else tuple(nodes)
|
|
79
129
|
|
|
80
130
|
|
|
81
|
-
def
|
|
82
|
-
|
|
83
|
-
if not is_node(maybe_node):
|
|
84
|
-
is_type = isinstance(maybe_node, type)
|
|
85
|
-
raise LookupError(
|
|
86
|
-
f"{'Type of' if is_type else 'Object of type'} "
|
|
87
|
-
f"{maybe_node.__name__ if is_type else maybe_node.__class__.__name__!r} "
|
|
88
|
-
"cannot be resolved as Node."
|
|
89
|
-
)
|
|
90
|
-
return maybe_nodes[0] if len(maybe_nodes) == 1 else maybe_nodes
|
|
131
|
+
def is_node_type(obj: typing.Any, /) -> typing.TypeIs[IsNode]:
|
|
132
|
+
return isinstance(obj, Node | NodeProto) or (isinstance(obj, type) and issubclass(obj, Node | NodeProto))
|
|
91
133
|
|
|
92
134
|
|
|
93
|
-
@
|
|
94
|
-
def get_nodes(
|
|
95
|
-
|
|
135
|
+
@function_context("nodes")
|
|
136
|
+
def get_nodes(
|
|
137
|
+
function: typing.Callable[..., typing.Any],
|
|
138
|
+
/,
|
|
139
|
+
*,
|
|
140
|
+
start_idx: int = 0,
|
|
141
|
+
) -> dict[str, IsNode]:
|
|
142
|
+
return {
|
|
143
|
+
k: node
|
|
144
|
+
for index, (k, v) in enumerate(get_func_annotations(function).items())
|
|
145
|
+
if (node := as_node(v, raise_exception=False)) is not None and index >= start_idx
|
|
146
|
+
}
|
|
96
147
|
|
|
97
148
|
|
|
98
|
-
@
|
|
149
|
+
@function_context("is_generator")
|
|
99
150
|
def is_generator(
|
|
100
151
|
function: typing.Callable[..., typing.Any],
|
|
101
152
|
/,
|
|
102
|
-
) -> typing.TypeGuard[
|
|
103
|
-
return inspect.isasyncgenfunction(function)
|
|
153
|
+
) -> typing.TypeGuard[Generator[typing.Any, typing.Any, typing.Any]]:
|
|
154
|
+
return inspect.isgeneratorfunction(function) or inspect.isasyncgenfunction(function)
|
|
104
155
|
|
|
105
156
|
|
|
106
|
-
def unwrap_node(node:
|
|
157
|
+
def unwrap_node(node: IsNode, /) -> tuple[IsNode, ...]:
|
|
107
158
|
"""Unwrap node as flattened tuple of node types in ordering required to calculate given node.
|
|
108
159
|
|
|
109
160
|
Provides caching for passed node type.
|
|
@@ -111,34 +162,42 @@ def unwrap_node(node: type[NodeType], /) -> tuple[type[NodeType], ...]:
|
|
|
111
162
|
if (unwrapped := getattr(node, UNWRAPPED_NODE_KEY, None)) is not None:
|
|
112
163
|
return unwrapped
|
|
113
164
|
|
|
114
|
-
stack = deque([(node, node.get_subnodes().values())])
|
|
115
|
-
visited = list[
|
|
165
|
+
stack = deque([(node, node.get_subnodes().values(), [node])])
|
|
166
|
+
visited = list[IsNode]()
|
|
116
167
|
|
|
117
168
|
while stack:
|
|
118
|
-
parent, child_nodes = stack.pop()
|
|
169
|
+
parent, child_nodes, path = stack.pop()
|
|
170
|
+
dependencies = set(child_nodes)
|
|
171
|
+
|
|
172
|
+
if parent in dependencies:
|
|
173
|
+
raise ComposeError(f"Node `{fullname(parent)}` refers to itself in dependency tree.")
|
|
119
174
|
|
|
120
175
|
if parent not in visited:
|
|
121
176
|
visited.insert(0, parent)
|
|
122
177
|
|
|
123
178
|
for child in child_nodes:
|
|
124
|
-
|
|
179
|
+
subnodes = child.get_subnodes().values()
|
|
180
|
+
dependencies.update(subnodes)
|
|
181
|
+
if child in path:
|
|
182
|
+
raise ComposeError(
|
|
183
|
+
f"Cannot resolve node `{fullname(node)}` due to circular dependency "
|
|
184
|
+
f"({' -> '.join(fullname(n) for n in path[path.index(child) :] + [child])} <...>)",
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
stack.append((child, subnodes, path + [child]))
|
|
125
188
|
|
|
126
189
|
unwrapped = tuple(visited)
|
|
127
190
|
setattr(node, UNWRAPPED_NODE_KEY, unwrapped)
|
|
128
191
|
return unwrapped
|
|
129
192
|
|
|
130
193
|
|
|
131
|
-
class ComposeError(BaseException):
|
|
132
|
-
pass
|
|
133
|
-
|
|
134
|
-
|
|
135
194
|
@typing.runtime_checkable
|
|
136
195
|
class Composable[R](typing.Protocol):
|
|
137
196
|
@classmethod
|
|
138
197
|
def compose(cls, *args: typing.Any, **kwargs: typing.Any) -> ComposeResult[R]: ...
|
|
139
198
|
|
|
140
199
|
|
|
141
|
-
class
|
|
200
|
+
class NodeConvertable(typing.Protocol):
|
|
142
201
|
@classmethod
|
|
143
202
|
def as_node(cls) -> type[NodeProto[typing.Any]]: ...
|
|
144
203
|
|
|
@@ -151,9 +210,9 @@ class NodeComposeFunction[R](typing.Protocol):
|
|
|
151
210
|
|
|
152
211
|
|
|
153
212
|
@typing.runtime_checkable
|
|
154
|
-
class NodeProto[R](Composable[R],
|
|
213
|
+
class NodeProto[R](Composable[R], typing.Protocol):
|
|
155
214
|
@classmethod
|
|
156
|
-
def get_subnodes(cls) -> dict[str,
|
|
215
|
+
def get_subnodes(cls) -> dict[str, IsNode]: ...
|
|
157
216
|
|
|
158
217
|
@classmethod
|
|
159
218
|
def is_generator(cls) -> bool: ...
|
|
@@ -169,13 +228,9 @@ class Node(abc.ABC):
|
|
|
169
228
|
pass
|
|
170
229
|
|
|
171
230
|
@classmethod
|
|
172
|
-
def get_subnodes(cls) -> dict[str,
|
|
231
|
+
def get_subnodes(cls) -> dict[str, IsNode]:
|
|
173
232
|
return get_nodes(cls.compose)
|
|
174
233
|
|
|
175
|
-
@classmethod
|
|
176
|
-
def as_node(cls) -> type[typing.Self]:
|
|
177
|
-
return cls
|
|
178
|
-
|
|
179
234
|
@classmethod
|
|
180
235
|
def is_generator(cls) -> bool:
|
|
181
236
|
return is_generator(cls.compose)
|
|
@@ -194,17 +249,30 @@ class scalar_node[T]: # noqa: N801
|
|
|
194
249
|
/,
|
|
195
250
|
*,
|
|
196
251
|
scope: NodeScope,
|
|
197
|
-
) -> typing.Callable[[NodeComposeFunction[Composable[T]]
|
|
252
|
+
) -> typing.Callable[[NodeComposeFunction[Composable[T]]], type[T]]: ...
|
|
253
|
+
|
|
254
|
+
@typing.overload
|
|
255
|
+
def __new__(
|
|
256
|
+
cls,
|
|
257
|
+
/,
|
|
258
|
+
*,
|
|
259
|
+
scope: NodeScope,
|
|
260
|
+
) -> typing.Callable[[NodeComposeFunction[T]], type[T]]: ...
|
|
198
261
|
|
|
199
|
-
def __new__(cls, x=None, /, *, scope=
|
|
262
|
+
def __new__(cls, x=None, /, *, scope=_NODEFAULT) -> typing.Any:
|
|
200
263
|
def inner(node_or_func, /) -> typing.Any:
|
|
201
|
-
namespace = {"node": "scalar", "
|
|
264
|
+
namespace = {"node": "scalar", "__module__": node_or_func.__module__}
|
|
202
265
|
|
|
203
266
|
if isinstance(node_or_func, type):
|
|
204
267
|
bases: list[type[typing.Any]] = [node_or_func]
|
|
205
268
|
node_bases = resolve_bases(node_or_func.__bases__)
|
|
269
|
+
|
|
206
270
|
if not any(issubclass(base, Node) for base in node_bases if isinstance(base, type)):
|
|
207
271
|
bases.append(Node)
|
|
272
|
+
namespace["scope"] = NodeScope.PER_EVENT if scope is _NODEFAULT else scope
|
|
273
|
+
elif scope is not _NODEFAULT:
|
|
274
|
+
namespace["scope"] = scope
|
|
275
|
+
|
|
208
276
|
return type(node_or_func.__name__, tuple(bases), namespace)
|
|
209
277
|
else:
|
|
210
278
|
base_node = generate_node(
|
|
@@ -247,12 +315,18 @@ class Name:
|
|
|
247
315
|
def compose(cls) -> str: ...
|
|
248
316
|
|
|
249
317
|
|
|
318
|
+
@scalar_node
|
|
319
|
+
class NodeClass:
|
|
320
|
+
@classmethod
|
|
321
|
+
def compose(cls) -> type[Node]: ...
|
|
322
|
+
|
|
323
|
+
|
|
250
324
|
class GlobalNode[Value](Node):
|
|
251
325
|
scope = NodeScope.GLOBAL
|
|
252
326
|
|
|
253
327
|
@classmethod
|
|
254
328
|
def set(cls, value: Value, /) -> None:
|
|
255
|
-
|
|
329
|
+
NODE_CONTEXT.global_session[cls] = NodeSession(cls, value)
|
|
256
330
|
|
|
257
331
|
@typing.overload
|
|
258
332
|
@classmethod
|
|
@@ -266,12 +340,18 @@ class GlobalNode[Value](Node):
|
|
|
266
340
|
def get(cls, **kwargs: typing.Any) -> typing.Any:
|
|
267
341
|
sentinel = object()
|
|
268
342
|
default = kwargs.pop("default", sentinel)
|
|
269
|
-
|
|
343
|
+
|
|
344
|
+
if default is not sentinel and cls not in NODE_CONTEXT.global_sessions:
|
|
345
|
+
return default
|
|
346
|
+
|
|
347
|
+
if (session := NODE_CONTEXT.global_sessions.get(cls)) is None and default is sentinel:
|
|
348
|
+
raise ValueError(f"Node `{fullname(cls)}` has no global value.")
|
|
349
|
+
|
|
350
|
+
return session.value if session is not None else default
|
|
270
351
|
|
|
271
352
|
@classmethod
|
|
272
353
|
def unset(cls) -> None:
|
|
273
|
-
|
|
274
|
-
delattr(cls, "_value")
|
|
354
|
+
NODE_CONTEXT.global_sessions.pop(cls, None)
|
|
275
355
|
|
|
276
356
|
|
|
277
357
|
__all__ = (
|
|
@@ -283,12 +363,14 @@ __all__ = (
|
|
|
283
363
|
"IsNode",
|
|
284
364
|
"Name",
|
|
285
365
|
"Node",
|
|
286
|
-
"
|
|
366
|
+
"NodeClass",
|
|
367
|
+
"NodeConvertable",
|
|
287
368
|
"NodeProto",
|
|
288
369
|
"NodeType",
|
|
289
370
|
"as_node",
|
|
290
371
|
"get_nodes",
|
|
291
372
|
"is_node",
|
|
373
|
+
"is_node_type",
|
|
292
374
|
"scalar_node",
|
|
293
375
|
"unwrap_node",
|
|
294
376
|
)
|
|
@@ -3,7 +3,7 @@ import typing
|
|
|
3
3
|
from fntypes.result import Error, Ok
|
|
4
4
|
|
|
5
5
|
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
6
|
-
from telegrinder.msgspec_utils import
|
|
6
|
+
from telegrinder.msgspec_utils import convert
|
|
7
7
|
from telegrinder.node.base import ComposeError, FactoryNode, Name, scalar_node
|
|
8
8
|
|
|
9
9
|
|
|
@@ -24,15 +24,15 @@ class CallbackQueryDataJson:
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class _Field(FactoryNode):
|
|
27
|
-
field_type:
|
|
27
|
+
field_type: typing.Any
|
|
28
28
|
|
|
29
|
-
def __class_getitem__(cls, field_type:
|
|
29
|
+
def __class_getitem__(cls, field_type: typing.Any, /) -> typing.Self:
|
|
30
30
|
return cls(field_type=field_type)
|
|
31
31
|
|
|
32
32
|
@classmethod
|
|
33
33
|
def compose(cls, callback_query_data: CallbackQueryDataJson, data_name: Name) -> typing.Any:
|
|
34
34
|
if data := callback_query_data.get(data_name):
|
|
35
|
-
match
|
|
35
|
+
match convert(data, cls.field_type):
|
|
36
36
|
case Ok(value):
|
|
37
37
|
return value
|
|
38
38
|
case Error(err):
|
|
@@ -42,7 +42,7 @@ class _Field(FactoryNode):
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
if typing.TYPE_CHECKING:
|
|
45
|
-
type Field[FieldType] =
|
|
45
|
+
type Field[FieldType] = FieldType
|
|
46
46
|
else:
|
|
47
47
|
Field = _Field
|
|
48
48
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
import msgspec
|
|
5
|
+
|
|
6
|
+
from telegrinder.node.base import IsNode, Node, as_node
|
|
7
|
+
from telegrinder.tools.magic.annotations import Annotations
|
|
8
|
+
from telegrinder.tools.magic.dictionary import extract
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Collection(Node):
|
|
12
|
+
__subnodes__: typing.ClassVar[dict[str, IsNode] | None] = None
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def get_subnodes(cls) -> dict[str, IsNode]:
|
|
16
|
+
if cls.__subnodes__ is None:
|
|
17
|
+
cls.__subnodes__ = {
|
|
18
|
+
name: node
|
|
19
|
+
for name, annotation in Annotations(obj=cls).get(cache=True, exclude_forward_refs=True).items()
|
|
20
|
+
if (node := as_node(annotation, raise_exception=False)) is not None
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return cls.__subnodes__
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def compose(cls, **kwargs: typing.Any) -> typing.Self:
|
|
27
|
+
nodes = extract(cls.__subnodes__ or (), kwargs)
|
|
28
|
+
|
|
29
|
+
if dataclasses.is_dataclass(cls) or issubclass(cls, msgspec.Struct):
|
|
30
|
+
return cls(**nodes)
|
|
31
|
+
|
|
32
|
+
instance = cls()
|
|
33
|
+
for name, value in nodes.items():
|
|
34
|
+
setattr(instance, name, value)
|
|
35
|
+
|
|
36
|
+
return instance
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = ("Collection",)
|
telegrinder/node/command.py
CHANGED
|
@@ -4,7 +4,6 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
from fntypes.option import Nothing, Option, Some
|
|
5
5
|
|
|
6
6
|
from telegrinder.node.base import DataNode
|
|
7
|
-
from telegrinder.node.either import Either
|
|
8
7
|
from telegrinder.node.text import Caption, Text
|
|
9
8
|
|
|
10
9
|
|
|
@@ -25,7 +24,7 @@ class CommandInfo(DataNode):
|
|
|
25
24
|
mention: Option[str] = field(default_factory=Nothing)
|
|
26
25
|
|
|
27
26
|
@classmethod
|
|
28
|
-
def compose(cls, text:
|
|
27
|
+
def compose(cls, text: Text | Caption) -> typing.Self:
|
|
29
28
|
name, arguments = single_split(text, separator=" ")
|
|
30
29
|
name, mention = cut_mention(name)
|
|
31
30
|
return cls(name, arguments, mention)
|