telegrinder 1.0.0rc1__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.
- telegrinder/__init__.py +258 -0
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +15 -0
- telegrinder/api/api.py +175 -0
- telegrinder/api/error.py +50 -0
- telegrinder/api/response.py +23 -0
- telegrinder/api/token.py +30 -0
- telegrinder/api/validators.py +30 -0
- telegrinder/bot/__init__.py +144 -0
- telegrinder/bot/bot.py +70 -0
- telegrinder/bot/cute_types/__init__.py +41 -0
- telegrinder/bot/cute_types/base.py +228 -0
- telegrinder/bot/cute_types/base.pyi +49 -0
- telegrinder/bot/cute_types/business_connection.py +9 -0
- telegrinder/bot/cute_types/business_messages_deleted.py +9 -0
- telegrinder/bot/cute_types/callback_query.py +248 -0
- telegrinder/bot/cute_types/chat_boost_removed.py +9 -0
- telegrinder/bot/cute_types/chat_boost_updated.py +9 -0
- telegrinder/bot/cute_types/chat_join_request.py +59 -0
- telegrinder/bot/cute_types/chat_member_updated.py +158 -0
- telegrinder/bot/cute_types/chosen_inline_result.py +11 -0
- telegrinder/bot/cute_types/inline_query.py +41 -0
- telegrinder/bot/cute_types/message.py +2809 -0
- telegrinder/bot/cute_types/message_reaction_count_updated.py +9 -0
- telegrinder/bot/cute_types/message_reaction_updated.py +9 -0
- telegrinder/bot/cute_types/paid_media_purchased.py +11 -0
- telegrinder/bot/cute_types/poll.py +9 -0
- telegrinder/bot/cute_types/poll_answer.py +9 -0
- telegrinder/bot/cute_types/pre_checkout_query.py +36 -0
- telegrinder/bot/cute_types/shipping_query.py +11 -0
- telegrinder/bot/cute_types/update.py +209 -0
- telegrinder/bot/cute_types/utils.py +141 -0
- telegrinder/bot/dispatch/__init__.py +99 -0
- telegrinder/bot/dispatch/abc.py +74 -0
- telegrinder/bot/dispatch/action.py +99 -0
- telegrinder/bot/dispatch/context.py +162 -0
- telegrinder/bot/dispatch/dispatch.py +362 -0
- telegrinder/bot/dispatch/handler/__init__.py +23 -0
- telegrinder/bot/dispatch/handler/abc.py +25 -0
- telegrinder/bot/dispatch/handler/audio_reply.py +43 -0
- telegrinder/bot/dispatch/handler/base.py +34 -0
- telegrinder/bot/dispatch/handler/document_reply.py +43 -0
- telegrinder/bot/dispatch/handler/func.py +73 -0
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
- telegrinder/bot/dispatch/handler/message_reply.py +35 -0
- telegrinder/bot/dispatch/handler/photo_reply.py +43 -0
- telegrinder/bot/dispatch/handler/sticker_reply.py +36 -0
- telegrinder/bot/dispatch/handler/video_reply.py +43 -0
- telegrinder/bot/dispatch/middleware/__init__.py +13 -0
- telegrinder/bot/dispatch/middleware/abc.py +112 -0
- telegrinder/bot/dispatch/middleware/box.py +32 -0
- telegrinder/bot/dispatch/middleware/filter.py +88 -0
- telegrinder/bot/dispatch/middleware/media_group.py +69 -0
- telegrinder/bot/dispatch/process.py +93 -0
- telegrinder/bot/dispatch/return_manager/__init__.py +21 -0
- telegrinder/bot/dispatch/return_manager/abc.py +107 -0
- telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
- telegrinder/bot/dispatch/return_manager/message.py +34 -0
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/utils.py +20 -0
- telegrinder/bot/dispatch/router/__init__.py +4 -0
- telegrinder/bot/dispatch/router/abc.py +15 -0
- telegrinder/bot/dispatch/router/base.py +154 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -0
- telegrinder/bot/dispatch/view/abc.py +15 -0
- telegrinder/bot/dispatch/view/base.py +226 -0
- telegrinder/bot/dispatch/view/box.py +207 -0
- telegrinder/bot/dispatch/view/media_group.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/actions.py +16 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +13 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +53 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +61 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +49 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +264 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +77 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +105 -0
- telegrinder/bot/polling/__init__.py +4 -0
- telegrinder/bot/polling/abc.py +25 -0
- telegrinder/bot/polling/error_handler.py +93 -0
- telegrinder/bot/polling/polling.py +167 -0
- telegrinder/bot/polling/utils.py +12 -0
- telegrinder/bot/rules/__init__.py +166 -0
- telegrinder/bot/rules/abc.py +150 -0
- telegrinder/bot/rules/button.py +20 -0
- telegrinder/bot/rules/callback_data.py +109 -0
- telegrinder/bot/rules/chat_join.py +28 -0
- telegrinder/bot/rules/chat_member_updated.py +145 -0
- telegrinder/bot/rules/command.py +137 -0
- telegrinder/bot/rules/enum_text.py +29 -0
- telegrinder/bot/rules/func.py +21 -0
- telegrinder/bot/rules/fuzzy.py +21 -0
- telegrinder/bot/rules/inline.py +45 -0
- telegrinder/bot/rules/integer.py +19 -0
- telegrinder/bot/rules/is_from.py +213 -0
- telegrinder/bot/rules/logic.py +22 -0
- telegrinder/bot/rules/magic.py +60 -0
- telegrinder/bot/rules/markup.py +51 -0
- telegrinder/bot/rules/media.py +13 -0
- telegrinder/bot/rules/mention.py +15 -0
- telegrinder/bot/rules/message_entities.py +37 -0
- telegrinder/bot/rules/node.py +43 -0
- telegrinder/bot/rules/payload.py +89 -0
- telegrinder/bot/rules/payment_invoice.py +14 -0
- telegrinder/bot/rules/regex.py +34 -0
- telegrinder/bot/rules/rule_enum.py +71 -0
- telegrinder/bot/rules/start.py +73 -0
- telegrinder/bot/rules/state.py +35 -0
- telegrinder/bot/rules/text.py +27 -0
- telegrinder/bot/rules/update.py +14 -0
- telegrinder/bot/scenario/__init__.py +5 -0
- telegrinder/bot/scenario/abc.py +16 -0
- telegrinder/bot/scenario/checkbox.py +183 -0
- telegrinder/bot/scenario/choice.py +44 -0
- telegrinder/client/__init__.py +11 -0
- telegrinder/client/abc.py +136 -0
- telegrinder/client/form_data.py +34 -0
- telegrinder/client/rnet.py +198 -0
- telegrinder/model.py +133 -0
- telegrinder/model.pyi +57 -0
- telegrinder/modules.py +1081 -0
- telegrinder/msgspec_utils/__init__.py +42 -0
- telegrinder/msgspec_utils/abc.py +16 -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 +61 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +388 -0
- telegrinder/msgspec_utils/encoder.py +204 -0
- telegrinder/msgspec_utils/json.py +15 -0
- telegrinder/msgspec_utils/tools.py +80 -0
- telegrinder/node/__init__.py +80 -0
- telegrinder/node/compose.py +193 -0
- telegrinder/node/nodes/__init__.py +96 -0
- telegrinder/node/nodes/attachment.py +169 -0
- telegrinder/node/nodes/callback_query.py +25 -0
- telegrinder/node/nodes/channel.py +97 -0
- telegrinder/node/nodes/command.py +33 -0
- telegrinder/node/nodes/error.py +43 -0
- telegrinder/node/nodes/event.py +70 -0
- telegrinder/node/nodes/file.py +39 -0
- telegrinder/node/nodes/global_node.py +66 -0
- telegrinder/node/nodes/i18n.py +110 -0
- telegrinder/node/nodes/me.py +26 -0
- telegrinder/node/nodes/message_entities.py +15 -0
- telegrinder/node/nodes/payload.py +84 -0
- telegrinder/node/nodes/reply_message.py +14 -0
- telegrinder/node/nodes/source.py +172 -0
- telegrinder/node/nodes/state_mutator.py +71 -0
- telegrinder/node/nodes/text.py +62 -0
- telegrinder/node/scope.py +88 -0
- telegrinder/node/utils.py +38 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -0
- telegrinder/tools/__init__.py +183 -0
- telegrinder/tools/aio.py +147 -0
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +85 -0
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/deep_links/links.py +468 -0
- telegrinder/tools/formatting/deep_links/parsing.py +88 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html.py +241 -0
- telegrinder/tools/fullname.py +82 -0
- telegrinder/tools/global_context/__init__.py +13 -0
- telegrinder/tools/global_context/abc.py +63 -0
- telegrinder/tools/global_context/builtin_context.py +45 -0
- telegrinder/tools/global_context/global_context.py +614 -0
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard/__init__.py +6 -0
- telegrinder/tools/keyboard/abc.py +84 -0
- telegrinder/tools/keyboard/base.py +108 -0
- telegrinder/tools/keyboard/button.py +181 -0
- telegrinder/tools/keyboard/data.py +31 -0
- telegrinder/tools/keyboard/keyboard.py +160 -0
- telegrinder/tools/keyboard/utils.py +95 -0
- telegrinder/tools/lifespan.py +188 -0
- telegrinder/tools/limited_dict.py +35 -0
- telegrinder/tools/loop_wrapper.py +271 -0
- telegrinder/tools/magic/__init__.py +29 -0
- telegrinder/tools/magic/annotations.py +172 -0
- telegrinder/tools/magic/descriptors.py +57 -0
- telegrinder/tools/magic/function.py +254 -0
- telegrinder/tools/magic/inspect.py +16 -0
- telegrinder/tools/magic/shortcut.py +107 -0
- telegrinder/tools/member_descriptor_proxy.py +95 -0
- telegrinder/tools/parse_mode.py +12 -0
- telegrinder/tools/serialization/__init__.py +5 -0
- telegrinder/tools/serialization/abc.py +34 -0
- telegrinder/tools/serialization/json_ser.py +60 -0
- telegrinder/tools/serialization/msgpack_ser.py +197 -0
- telegrinder/tools/serialization/utils.py +18 -0
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_mutator/__init__.py +4 -0
- telegrinder/tools/state_mutator/mutation.py +85 -0
- telegrinder/tools/state_storage/__init__.py +4 -0
- telegrinder/tools/state_storage/abc.py +38 -0
- telegrinder/tools/state_storage/memory.py +27 -0
- telegrinder/tools/strings.py +22 -0
- telegrinder/types/__init__.py +323 -0
- telegrinder/types/enums.py +754 -0
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +6143 -0
- telegrinder/types/methods_utils.py +66 -0
- telegrinder/types/objects.py +8184 -0
- telegrinder/types/webapp.py +129 -0
- telegrinder/verification_utils.py +35 -0
- telegrinder-1.0.0rc1.dist-info/METADATA +166 -0
- telegrinder-1.0.0rc1.dist-info/RECORD +215 -0
- telegrinder-1.0.0rc1.dist-info/WHEEL +4 -0
- telegrinder-1.0.0rc1.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
import typing
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
import kungfu.library
|
|
6
|
+
import msgspec
|
|
7
|
+
|
|
8
|
+
from telegrinder.msgspec_utils.abc import SupportsCast
|
|
9
|
+
from telegrinder.msgspec_utils.custom_types.datetime import datetime, timedelta
|
|
10
|
+
from telegrinder.msgspec_utils.custom_types.enum_meta import BaseEnumMeta
|
|
11
|
+
from telegrinder.msgspec_utils.tools import bundle, fullname, get_origin
|
|
12
|
+
|
|
13
|
+
type Context = dict[str, typing.Any]
|
|
14
|
+
type Order = typing.Literal["deterministic", "sorted"]
|
|
15
|
+
type EncHook[T] = typing.Callable[typing.Concatenate[T, ...], typing.Any]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def to_builtins(
|
|
19
|
+
obj: typing.Any,
|
|
20
|
+
*,
|
|
21
|
+
str_keys: bool = False,
|
|
22
|
+
builtin_types: typing.Iterable[type[typing.Any]] | None = None,
|
|
23
|
+
order: Order | None = None,
|
|
24
|
+
context: Context | None = None,
|
|
25
|
+
) -> kungfu.library.Result[typing.Any, msgspec.ValidationError]:
|
|
26
|
+
try:
|
|
27
|
+
return kungfu.library.Ok(
|
|
28
|
+
encoder.to_builtins(
|
|
29
|
+
obj,
|
|
30
|
+
str_keys=str_keys,
|
|
31
|
+
builtin_types=builtin_types,
|
|
32
|
+
order=order,
|
|
33
|
+
context=context,
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
except msgspec.ValidationError as error:
|
|
37
|
+
return kungfu.library.Error(error)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Encoder:
|
|
41
|
+
"""Class `Encoder` for `msgspec` module with encode hooks for objects.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
from datetime import datetime
|
|
45
|
+
|
|
46
|
+
class MyDatetime(datetime):
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
encoder = Encoder()
|
|
50
|
+
encoder.enc_hooks[MyDatetime] = lambda d: int(d.timestamp())
|
|
51
|
+
|
|
52
|
+
encoder.enc_hook(MyDatetime.now()) #> 1713354732
|
|
53
|
+
encoder.encode({"digit": Digit.ONE}) #> '{"digit":1}'
|
|
54
|
+
```
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
cast_types: dict[type[typing.Any], type[SupportsCast]]
|
|
58
|
+
enc_hooks: dict[typing.Any, EncHook[typing.Any]]
|
|
59
|
+
abstract_enc_hooks: dict[typing.Any, EncHook[typing.Any]]
|
|
60
|
+
|
|
61
|
+
def __init__(self) -> None:
|
|
62
|
+
self.cast_types = { # type: ignore
|
|
63
|
+
dt.datetime: datetime,
|
|
64
|
+
dt.timedelta: timedelta,
|
|
65
|
+
}
|
|
66
|
+
self.enc_hooks = {
|
|
67
|
+
kungfu.library.Some: lambda some: some.value,
|
|
68
|
+
kungfu.library.Nothing: lambda _: None,
|
|
69
|
+
kungfu.library.Sum: lambda s: s.v,
|
|
70
|
+
datetime: lambda date: int(date.timestamp()),
|
|
71
|
+
timedelta: lambda time: time.total_seconds(),
|
|
72
|
+
}
|
|
73
|
+
self.abstract_enc_hooks = {
|
|
74
|
+
BaseEnumMeta: lambda enum_member: enum_member.value,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
def __repr__(self) -> str:
|
|
78
|
+
return "<{}: cast_types={!r}, enc_hooks={!r}, abstract_enc_hooks={!r}>".format(
|
|
79
|
+
type(self).__name__,
|
|
80
|
+
self.cast_types,
|
|
81
|
+
self.enc_hooks,
|
|
82
|
+
self.abstract_enc_hooks,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@contextmanager
|
|
86
|
+
def __call__(
|
|
87
|
+
self,
|
|
88
|
+
*,
|
|
89
|
+
decimal_format: typing.Literal["string", "number"] = "string",
|
|
90
|
+
uuid_format: typing.Literal["canonical", "hex"] = "canonical",
|
|
91
|
+
order: Order | None = None,
|
|
92
|
+
context: Context | None = None,
|
|
93
|
+
) -> typing.Generator[msgspec.json.Encoder, typing.Any, None]:
|
|
94
|
+
"""Context manager returns the `msgspec.json.Encoder` object with passed the `enc_hook`."""
|
|
95
|
+
yield msgspec.json.Encoder(
|
|
96
|
+
enc_hook=self.enc_hook(context),
|
|
97
|
+
decimal_format=decimal_format,
|
|
98
|
+
uuid_format=uuid_format,
|
|
99
|
+
order=order,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def add_enc_hook[T](self, t: type[T], /) -> typing.Callable[[EncHook[T]], EncHook[T]]:
|
|
103
|
+
def decorator(func: EncHook[T], /) -> EncHook[T]:
|
|
104
|
+
encode_hook = self.enc_hooks.setdefault(get_origin(t), func)
|
|
105
|
+
return func if encode_hook is not func else encode_hook
|
|
106
|
+
|
|
107
|
+
return decorator
|
|
108
|
+
|
|
109
|
+
def add_abstract_enc_hook[T](self, abstract_type: type[T], /) -> typing.Callable[[EncHook[T]], EncHook[T]]:
|
|
110
|
+
def decorator(func: EncHook[T], /) -> EncHook[T]:
|
|
111
|
+
return self.abstract_enc_hooks.setdefault(get_origin(abstract_type), func)
|
|
112
|
+
|
|
113
|
+
return decorator
|
|
114
|
+
|
|
115
|
+
def get_abstract_enc_hook(self, subtype: type[typing.Any], /) -> EncHook[typing.Any] | None:
|
|
116
|
+
for abstract, enc_hook in self.abstract_enc_hooks.items():
|
|
117
|
+
if issubclass(subtype, abstract) or issubclass(type(subtype), abstract):
|
|
118
|
+
return enc_hook
|
|
119
|
+
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
def enc_hook(self, context: Context | None = None, /) -> EncHook[typing.Any]:
|
|
123
|
+
def inner(obj: typing.Any, /) -> typing.Any:
|
|
124
|
+
origin_type = get_origin(obj)
|
|
125
|
+
|
|
126
|
+
if (enc_hook_func := self.enc_hooks.get(origin_type)) is None and (
|
|
127
|
+
enc_hook_func := self.get_abstract_enc_hook(origin_type)
|
|
128
|
+
) is None:
|
|
129
|
+
raise NotImplementedError(
|
|
130
|
+
f"Not implemented encode hook for object of type `{fullname(origin_type)}`. "
|
|
131
|
+
"You can implement encode hook for this object.",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return bundle(enc_hook_func, context or {}, start_idx=1)(obj)
|
|
135
|
+
|
|
136
|
+
return inner
|
|
137
|
+
|
|
138
|
+
@typing.overload
|
|
139
|
+
def encode(
|
|
140
|
+
self,
|
|
141
|
+
obj: typing.Any,
|
|
142
|
+
*,
|
|
143
|
+
order: Order | None = None,
|
|
144
|
+
context: Context | None = None,
|
|
145
|
+
) -> str: ...
|
|
146
|
+
|
|
147
|
+
@typing.overload
|
|
148
|
+
def encode(
|
|
149
|
+
self,
|
|
150
|
+
obj: typing.Any,
|
|
151
|
+
*,
|
|
152
|
+
as_str: typing.Literal[True],
|
|
153
|
+
order: Order | None = None,
|
|
154
|
+
context: Context | None = None,
|
|
155
|
+
) -> str: ...
|
|
156
|
+
|
|
157
|
+
@typing.overload
|
|
158
|
+
def encode(
|
|
159
|
+
self,
|
|
160
|
+
obj: typing.Any,
|
|
161
|
+
*,
|
|
162
|
+
as_str: typing.Literal[False],
|
|
163
|
+
order: Order | None = None,
|
|
164
|
+
context: Context | None = None,
|
|
165
|
+
) -> bytes: ...
|
|
166
|
+
|
|
167
|
+
def encode(
|
|
168
|
+
self,
|
|
169
|
+
obj: typing.Any,
|
|
170
|
+
*,
|
|
171
|
+
as_str: bool = True,
|
|
172
|
+
order: Order | None = None,
|
|
173
|
+
context: Context | None = None,
|
|
174
|
+
) -> str | bytes:
|
|
175
|
+
buf = msgspec.json.encode(obj, enc_hook=self.enc_hook(context), order=order)
|
|
176
|
+
return buf.decode() if as_str else buf
|
|
177
|
+
|
|
178
|
+
def to_builtins(
|
|
179
|
+
self,
|
|
180
|
+
obj: typing.Any,
|
|
181
|
+
*,
|
|
182
|
+
str_keys: bool = False,
|
|
183
|
+
builtin_types: typing.Iterable[type[typing.Any]] | None = None,
|
|
184
|
+
order: Order | None = None,
|
|
185
|
+
context: Context | None = None,
|
|
186
|
+
) -> typing.Any:
|
|
187
|
+
return msgspec.to_builtins(
|
|
188
|
+
obj,
|
|
189
|
+
str_keys=str_keys,
|
|
190
|
+
builtin_types=builtin_types,
|
|
191
|
+
enc_hook=self.enc_hook(context),
|
|
192
|
+
order=order,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def cast(self, obj: typing.Any, /) -> typing.Any:
|
|
196
|
+
if (caster := self.cast_types.get(get_origin(obj))) is not None:
|
|
197
|
+
return caster.cast(obj)
|
|
198
|
+
return obj
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
encoder: typing.Final = Encoder()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
__all__ = ("Encoder", "encoder", "to_builtins")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from telegrinder.msgspec_utils.decoder import decoder
|
|
4
|
+
from telegrinder.msgspec_utils.encoder import encoder
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def dumps(o: typing.Any) -> str:
|
|
8
|
+
return encoder.encode(o)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def loads(s: str | bytes) -> typing.Any:
|
|
12
|
+
return decoder.decode(s)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = ("dumps", "loads")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
import kungfu.library
|
|
4
|
+
import msgspec
|
|
5
|
+
from kungfu.library.monad.option import NOTHING, Nothing
|
|
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, kungfu.library.Sum))
|
|
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_none(obj: typing.Any, /) -> typing.TypeIs[Nothing | None]:
|
|
39
|
+
return obj is None or obj is NOTHING
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_common_type[T](t: T, /) -> typing.TypeGuard[type[T]]:
|
|
43
|
+
if not isinstance(t, type):
|
|
44
|
+
return False
|
|
45
|
+
return t in _COMMON_TYPES or issubclass(t, msgspec.Struct) or hasattr(t, "__dataclass_fields__")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def struct_asdict(
|
|
49
|
+
struct: msgspec.Struct,
|
|
50
|
+
/,
|
|
51
|
+
*,
|
|
52
|
+
exclude_unset: bool = True,
|
|
53
|
+
unset_as_nothing: bool = False,
|
|
54
|
+
) -> dict[str, typing.Any]:
|
|
55
|
+
return {
|
|
56
|
+
k: v if not unset_as_nothing else NOTHING if v is msgspec.UNSET else v
|
|
57
|
+
for k, v in msgspec.structs.asdict(struct).items()
|
|
58
|
+
if not (exclude_unset and (is_none(v) or v is msgspec.UNSET))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def type_check(obj: typing.Any, t: typing.Any) -> bool:
|
|
63
|
+
return (
|
|
64
|
+
isinstance(obj, t)
|
|
65
|
+
if isinstance(t, type) and issubclass(t, msgspec.Struct)
|
|
66
|
+
else type(obj) in t
|
|
67
|
+
if isinstance(t, tuple)
|
|
68
|
+
else type(obj) is t
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
__all__ = (
|
|
73
|
+
"get_class_annotations",
|
|
74
|
+
"get_origin",
|
|
75
|
+
"get_type_hints",
|
|
76
|
+
"is_common_type",
|
|
77
|
+
"is_none",
|
|
78
|
+
"struct_asdict",
|
|
79
|
+
"type_check",
|
|
80
|
+
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""`Node` module which implements [nodnod](https://github.com/timoniq/nodnod)
|
|
2
|
+
integration for convenient dependency injection via `nodes`.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from telegrinder.node import scalar_node, global_node, per_call
|
|
7
|
+
|
|
8
|
+
@global_node
|
|
9
|
+
@scalar_node
|
|
10
|
+
class Hello:
|
|
11
|
+
@classmethod
|
|
12
|
+
def __compose__(cls) -> str:
|
|
13
|
+
return "Hello"
|
|
14
|
+
|
|
15
|
+
@global_node
|
|
16
|
+
@scalar_node
|
|
17
|
+
class World:
|
|
18
|
+
@classmethod
|
|
19
|
+
def __compose__(cls) -> str:
|
|
20
|
+
return "World"
|
|
21
|
+
|
|
22
|
+
@per_call
|
|
23
|
+
@scalar_node
|
|
24
|
+
class Greeter:
|
|
25
|
+
@classmethod
|
|
26
|
+
def __compose__(cls, hello: Hello, world: World) -> str:
|
|
27
|
+
return f"{hello}, {world}!"
|
|
28
|
+
|
|
29
|
+
@bot.on.message()
|
|
30
|
+
async def hello_world(greeter: Greeter) -> str:
|
|
31
|
+
return greeter
|
|
32
|
+
```
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# pyright: reportUnusedImport=false, reportUnsupportedDunderAll=false
|
|
36
|
+
|
|
37
|
+
from nodnod import (
|
|
38
|
+
DataNode,
|
|
39
|
+
Injection,
|
|
40
|
+
Node,
|
|
41
|
+
NodeConstructor,
|
|
42
|
+
ResultNode,
|
|
43
|
+
Scalar,
|
|
44
|
+
case,
|
|
45
|
+
generic_node,
|
|
46
|
+
polymorphic,
|
|
47
|
+
scalar_node,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
from telegrinder.node.compose import Composable, compose, create_composable, run_agent
|
|
51
|
+
from telegrinder.node.nodes import *
|
|
52
|
+
from telegrinder.node.nodes import __all__ as nodes_all
|
|
53
|
+
from telegrinder.node.scope import GLOBAL, PER_CALL, PER_EVENT, NodeScope, global_node, per_call, per_event
|
|
54
|
+
from telegrinder.node.utils import as_node, is_node
|
|
55
|
+
|
|
56
|
+
__all__ = nodes_all + (
|
|
57
|
+
"GLOBAL",
|
|
58
|
+
"PER_CALL",
|
|
59
|
+
"PER_EVENT",
|
|
60
|
+
"Composable",
|
|
61
|
+
"DataNode",
|
|
62
|
+
"NodeConstructor",
|
|
63
|
+
"NodeScope",
|
|
64
|
+
"Node",
|
|
65
|
+
"ResultNode",
|
|
66
|
+
"Injection",
|
|
67
|
+
"Scalar",
|
|
68
|
+
"as_node",
|
|
69
|
+
"compose",
|
|
70
|
+
"create_composable",
|
|
71
|
+
"run_agent",
|
|
72
|
+
"global_node",
|
|
73
|
+
"is_node",
|
|
74
|
+
"case",
|
|
75
|
+
"generic_node",
|
|
76
|
+
"polymorphic",
|
|
77
|
+
"scalar_node",
|
|
78
|
+
"per_call",
|
|
79
|
+
"per_event",
|
|
80
|
+
)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Compose nodes with specific agent and context.
|
|
2
|
+
|
|
3
|
+
This module provides the `compose` function for executing nodes with dependency injection.
|
|
4
|
+
|
|
5
|
+
Key features:
|
|
6
|
+
- Execute functions as nodes with automatic dependency resolution
|
|
7
|
+
- Work with Node classes and ready-made Composable objects
|
|
8
|
+
- Integration with `Context` for access to API, events, data, and other nodes
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
# Executing a function node
|
|
12
|
+
async def process_data(user_id: int, api: API) -> str:
|
|
13
|
+
user = await api.get_chat(user_id)
|
|
14
|
+
return f"Hello, {user.first_name}!"
|
|
15
|
+
|
|
16
|
+
async def handler(context: Context):
|
|
17
|
+
async with compose(process_data, context) as result:
|
|
18
|
+
match result:
|
|
19
|
+
case Ok(value):
|
|
20
|
+
print(value) # "Hello, John!"
|
|
21
|
+
case Error(error):
|
|
22
|
+
print(f"Error: {error}")
|
|
23
|
+
|
|
24
|
+
# Executing a node with additional dependencies
|
|
25
|
+
from telegrinder.node import per_call, scalar_node
|
|
26
|
+
|
|
27
|
+
@per_call
|
|
28
|
+
@scalar_node
|
|
29
|
+
class Database:
|
|
30
|
+
@classmethod
|
|
31
|
+
def __compose__(cls) -> "Database":
|
|
32
|
+
return cls()
|
|
33
|
+
|
|
34
|
+
async def get_user_data(db: Database, user_id: int) -> dict[str, Any]:
|
|
35
|
+
# Use db to fetch data
|
|
36
|
+
return {"id": user_id, "name": "User"}
|
|
37
|
+
|
|
38
|
+
async def handler(context: Context):
|
|
39
|
+
async with compose(get_user_data, context) as result:
|
|
40
|
+
user_data = result.unwrap()
|
|
41
|
+
|
|
42
|
+
# Passing additional root dependencies
|
|
43
|
+
async def handler(context: Context):
|
|
44
|
+
custom_data = {"custom_key": "custom_value"}
|
|
45
|
+
|
|
46
|
+
async with compose(
|
|
47
|
+
my_node,
|
|
48
|
+
context,
|
|
49
|
+
roots={CustomType: custom_data}
|
|
50
|
+
) as result:
|
|
51
|
+
# roots allows injecting additional dependencies
|
|
52
|
+
pass
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The `compose` function returns an async context manager that:
|
|
56
|
+
|
|
57
|
+
- Creates a local scope for node composing
|
|
58
|
+
- Automatically resolves all node dependencies
|
|
59
|
+
- Returns `Result[T, NodeError]` with the composed result
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
import dataclasses
|
|
63
|
+
import typing
|
|
64
|
+
from contextlib import asynccontextmanager
|
|
65
|
+
from functools import lru_cache
|
|
66
|
+
|
|
67
|
+
from kungfu.library.monad.result import Error, Ok, Result
|
|
68
|
+
from nodnod.agent.base import Agent
|
|
69
|
+
from nodnod.agent.event_loop.agent import EventLoopAgent
|
|
70
|
+
from nodnod.error import NodeError
|
|
71
|
+
from nodnod.interface import create_agent_from_node, create_node_from_function, inject_internals
|
|
72
|
+
from nodnod.interface.is_node import is_node
|
|
73
|
+
from nodnod.interface.node_from_function import Externals
|
|
74
|
+
from nodnod.node import Node
|
|
75
|
+
from nodnod.scope import Scope
|
|
76
|
+
from nodnod.utils.is_type import is_type
|
|
77
|
+
|
|
78
|
+
from telegrinder.node.scope import TELEGRINDER_CONTEXT, MappedScopes, NodeScope
|
|
79
|
+
from telegrinder.tools.aio import maybe_awaitable
|
|
80
|
+
from telegrinder.tools.magic.function import Function
|
|
81
|
+
|
|
82
|
+
if typing.TYPE_CHECKING:
|
|
83
|
+
from telegrinder.bot.dispatch.context import Context
|
|
84
|
+
|
|
85
|
+
type _Composable[T: Agent] = Function[..., typing.Any] | type[Node[typing.Any, typing.Any]] | Composable[T]
|
|
86
|
+
|
|
87
|
+
NODE_GLOBAL_SCOPE: typing.Final = TELEGRINDER_CONTEXT.node_global_scope
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@lru_cache(maxsize=1024 * 4)
|
|
91
|
+
def create_composable[T: Agent](
|
|
92
|
+
node_or_function: type[Node[typing.Any, typing.Any]] | Function[..., typing.Any],
|
|
93
|
+
/,
|
|
94
|
+
*,
|
|
95
|
+
agent_cls: type[T] = EventLoopAgent,
|
|
96
|
+
) -> Composable[T]:
|
|
97
|
+
if not isinstance(node_or_function, type):
|
|
98
|
+
node_or_function = create_node_from_function(node_or_function)
|
|
99
|
+
return Composable(node_or_function, create_agent_from_node(node_or_function, agent_cls=agent_cls))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@asynccontextmanager
|
|
103
|
+
async def run_agent(
|
|
104
|
+
agent: Agent,
|
|
105
|
+
context: Context,
|
|
106
|
+
*,
|
|
107
|
+
roots: dict[type[typing.Any], typing.Any] | None = None,
|
|
108
|
+
per_event_scope: Scope | None = None,
|
|
109
|
+
) -> typing.AsyncGenerator[Result[Scope, NodeError]]:
|
|
110
|
+
event_scope = per_event_scope if per_event_scope is not None else context.per_event_scope
|
|
111
|
+
mapped_scopes = MappedScopes(global_scope=NODE_GLOBAL_SCOPE, per_event_scope=event_scope)
|
|
112
|
+
internals = {type(context): context, Externals: context}
|
|
113
|
+
|
|
114
|
+
async with event_scope.create_child(detail=NodeScope.PER_CALL) as local_scope:
|
|
115
|
+
try:
|
|
116
|
+
inject_internals(local_scope, internals | (roots or {}))
|
|
117
|
+
await maybe_awaitable(agent.run(local_scope, mapped_scopes))
|
|
118
|
+
yield Ok(local_scope.merge())
|
|
119
|
+
except NodeError as error:
|
|
120
|
+
yield Error(error)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@typing.overload
|
|
124
|
+
def compose[R](
|
|
125
|
+
function: Function[..., R],
|
|
126
|
+
/,
|
|
127
|
+
context: Context,
|
|
128
|
+
*,
|
|
129
|
+
per_event_scope: Scope | None = None,
|
|
130
|
+
agent_cls: type[Agent] | None = None,
|
|
131
|
+
roots: dict[type[typing.Any], typing.Any] | None = None,
|
|
132
|
+
) -> typing.AsyncContextManager[Result[R, NodeError], None]: ...
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@typing.overload
|
|
136
|
+
def compose[T: Agent](
|
|
137
|
+
composable: Composable[T],
|
|
138
|
+
/,
|
|
139
|
+
context: Context,
|
|
140
|
+
*,
|
|
141
|
+
per_event_scope: Scope | None = None,
|
|
142
|
+
roots: dict[type[typing.Any], typing.Any] | None = None,
|
|
143
|
+
) -> typing.AsyncContextManager[Result[typing.Any, NodeError], None]: ...
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@typing.overload
|
|
147
|
+
def compose[T](
|
|
148
|
+
node: type[Node[T, typing.Any]],
|
|
149
|
+
/,
|
|
150
|
+
context: Context,
|
|
151
|
+
*,
|
|
152
|
+
agent: Agent,
|
|
153
|
+
per_event_scope: Scope | None = None,
|
|
154
|
+
roots: dict[type[typing.Any], typing.Any] | None = None,
|
|
155
|
+
) -> typing.AsyncContextManager[Result[T, NodeError], None]: ...
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@asynccontextmanager
|
|
159
|
+
async def compose[T = typing.Any](
|
|
160
|
+
node: _Composable[Agent],
|
|
161
|
+
context: Context,
|
|
162
|
+
*,
|
|
163
|
+
per_event_scope: Scope | None = None,
|
|
164
|
+
agent: Agent | None = None,
|
|
165
|
+
agent_cls: type[Agent] | None = None,
|
|
166
|
+
roots: dict[type[typing.Any], typing.Any] | None = None,
|
|
167
|
+
) -> typing.AsyncGenerator[Result[T, NodeError], None]:
|
|
168
|
+
composable = None
|
|
169
|
+
|
|
170
|
+
if isinstance(node, Composable):
|
|
171
|
+
composable = node
|
|
172
|
+
elif not is_node(node):
|
|
173
|
+
composable = create_composable(node, agent_cls=agent_cls or EventLoopAgent)
|
|
174
|
+
|
|
175
|
+
if composable is not None:
|
|
176
|
+
node, agent = composable.node, typing.cast("Agent", composable.agent)
|
|
177
|
+
elif not is_type(node, Node):
|
|
178
|
+
raise TypeError("Compose requires function, node, or composable.")
|
|
179
|
+
|
|
180
|
+
if agent is None:
|
|
181
|
+
raise ValueError("Agent is required.")
|
|
182
|
+
|
|
183
|
+
async with run_agent(agent, context, roots=roots, per_event_scope=per_event_scope) as result:
|
|
184
|
+
yield result.map(lambda scope: scope[node].value)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
188
|
+
class Composable[T: Agent = Agent]:
|
|
189
|
+
node: type[Node[typing.Any, typing.Any]]
|
|
190
|
+
agent: T
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
__all__ = ("Composable", "compose", "create_composable", "run_agent")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Built-in nodes."""
|
|
2
|
+
|
|
3
|
+
from telegrinder.node.nodes.attachment import (
|
|
4
|
+
Animation,
|
|
5
|
+
Attachment,
|
|
6
|
+
Audio,
|
|
7
|
+
Document,
|
|
8
|
+
MediaGroup,
|
|
9
|
+
Photo,
|
|
10
|
+
Poll,
|
|
11
|
+
SuccessfulPayment,
|
|
12
|
+
Video,
|
|
13
|
+
VideoNote,
|
|
14
|
+
Voice,
|
|
15
|
+
)
|
|
16
|
+
from telegrinder.node.nodes.callback_query import CallbackQueryData, CallbackQueryDataJson
|
|
17
|
+
from telegrinder.node.nodes.channel import (
|
|
18
|
+
Channel,
|
|
19
|
+
ChannelId,
|
|
20
|
+
ChannelPost,
|
|
21
|
+
ChannelPostId,
|
|
22
|
+
ChatMessageChannelPost,
|
|
23
|
+
ChatMessageChannelPostAuthor,
|
|
24
|
+
ChatMessageChannelPostChannel,
|
|
25
|
+
ChatMessageChannelPostChannelId,
|
|
26
|
+
ChatMessageChannelPostId,
|
|
27
|
+
)
|
|
28
|
+
from telegrinder.node.nodes.command import CommandInfo
|
|
29
|
+
from telegrinder.node.nodes.error import Error
|
|
30
|
+
from telegrinder.node.nodes.event import EventNode
|
|
31
|
+
from telegrinder.node.nodes.file import File, FileId
|
|
32
|
+
from telegrinder.node.nodes.global_node import GlobalNode
|
|
33
|
+
from telegrinder.node.nodes.i18n import ABCTranslator, BaseTranslator, I18NConfig, KeySeparator
|
|
34
|
+
from telegrinder.node.nodes.me import BotUsername, Me
|
|
35
|
+
from telegrinder.node.nodes.message_entities import MessageEntities
|
|
36
|
+
from telegrinder.node.nodes.payload import Payload, PayloadData, PayloadSerializer
|
|
37
|
+
from telegrinder.node.nodes.reply_message import ReplyMessage
|
|
38
|
+
from telegrinder.node.nodes.source import ChatId, ChatSource, Locale, Source, UserId, UserSource
|
|
39
|
+
from telegrinder.node.nodes.state_mutator import State, StateMutator
|
|
40
|
+
from telegrinder.node.nodes.text import Caption, HTMLCaption, HTMLText, Text, TextInteger, TextLiteral
|
|
41
|
+
|
|
42
|
+
__all__ = (
|
|
43
|
+
"ABCTranslator",
|
|
44
|
+
"Animation",
|
|
45
|
+
"Attachment",
|
|
46
|
+
"Audio",
|
|
47
|
+
"BaseTranslator",
|
|
48
|
+
"BotUsername",
|
|
49
|
+
"CallbackQueryData",
|
|
50
|
+
"CallbackQueryDataJson",
|
|
51
|
+
"Caption",
|
|
52
|
+
"Channel",
|
|
53
|
+
"ChannelId",
|
|
54
|
+
"ChannelPost",
|
|
55
|
+
"ChannelPostId",
|
|
56
|
+
"ChatId",
|
|
57
|
+
"ChatMessageChannelPost",
|
|
58
|
+
"ChatMessageChannelPostAuthor",
|
|
59
|
+
"ChatMessageChannelPostChannel",
|
|
60
|
+
"ChatMessageChannelPostChannelId",
|
|
61
|
+
"ChatMessageChannelPostId",
|
|
62
|
+
"ChatSource",
|
|
63
|
+
"CommandInfo",
|
|
64
|
+
"Document",
|
|
65
|
+
"Error",
|
|
66
|
+
"EventNode",
|
|
67
|
+
"File",
|
|
68
|
+
"FileId",
|
|
69
|
+
"GlobalNode",
|
|
70
|
+
"HTMLCaption",
|
|
71
|
+
"HTMLText",
|
|
72
|
+
"I18NConfig",
|
|
73
|
+
"KeySeparator",
|
|
74
|
+
"Locale",
|
|
75
|
+
"Me",
|
|
76
|
+
"MediaGroup",
|
|
77
|
+
"MessageEntities",
|
|
78
|
+
"Payload",
|
|
79
|
+
"PayloadData",
|
|
80
|
+
"PayloadSerializer",
|
|
81
|
+
"Photo",
|
|
82
|
+
"Poll",
|
|
83
|
+
"ReplyMessage",
|
|
84
|
+
"Source",
|
|
85
|
+
"State",
|
|
86
|
+
"StateMutator",
|
|
87
|
+
"SuccessfulPayment",
|
|
88
|
+
"Text",
|
|
89
|
+
"TextInteger",
|
|
90
|
+
"TextLiteral",
|
|
91
|
+
"UserId",
|
|
92
|
+
"UserSource",
|
|
93
|
+
"Video",
|
|
94
|
+
"VideoNote",
|
|
95
|
+
"Voice",
|
|
96
|
+
)
|