telegrinder 0.4.2__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +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 +2621 -2590
- 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 +64 -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 +7846 -7058
- telegrinder/verification_utils.py +3 -1
- telegrinder-0.5.0.dist-info/METADATA +162 -0
- telegrinder-0.5.0.dist-info/RECORD +200 -0
- {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.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.0.dist-info}/WHEEL +0 -0
telegrinder/bot/bot.py
CHANGED
|
@@ -1,36 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import typing_extensions as typing
|
|
2
4
|
|
|
3
|
-
from telegrinder.api.api import API
|
|
5
|
+
from telegrinder.api.api import API
|
|
4
6
|
from telegrinder.bot.dispatch import dispatch as dp
|
|
5
7
|
from telegrinder.bot.dispatch.abc import ABCDispatch
|
|
6
8
|
from telegrinder.bot.polling import polling as pg
|
|
7
9
|
from telegrinder.bot.polling.abc import ABCPolling
|
|
8
10
|
from telegrinder.modules import logger
|
|
9
|
-
from telegrinder.tools.
|
|
10
|
-
from telegrinder.tools.loop_wrapper import
|
|
11
|
+
from telegrinder.tools.global_context.builtin_context import TelegrinderContext
|
|
12
|
+
from telegrinder.tools.loop_wrapper import LoopWrapper
|
|
13
|
+
|
|
14
|
+
if typing.TYPE_CHECKING:
|
|
15
|
+
from telegrinder.node.composer import Composer
|
|
11
16
|
|
|
12
|
-
Dispatch = typing.TypeVar("Dispatch", bound=ABCDispatch, default=dp.Dispatch
|
|
13
|
-
Polling = typing.TypeVar("Polling", bound=ABCPolling, default=pg.Polling
|
|
14
|
-
LoopWrapper = typing.TypeVar("LoopWrapper", bound=ABCLoopWrapper, default=lw.LoopWrapper)
|
|
17
|
+
Dispatch = typing.TypeVar("Dispatch", bound=ABCDispatch, default=dp.Dispatch)
|
|
18
|
+
Polling = typing.TypeVar("Polling", bound=ABCPolling, default=pg.Polling)
|
|
15
19
|
|
|
20
|
+
CONTEXT: typing.Final[TelegrinderContext] = TelegrinderContext()
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
|
|
23
|
+
class Telegrinder(typing.Generic[Dispatch, Polling]):
|
|
18
24
|
def __init__(
|
|
19
25
|
self,
|
|
20
|
-
api: API
|
|
26
|
+
api: API,
|
|
21
27
|
*,
|
|
22
28
|
dispatch: Dispatch | None = None,
|
|
23
29
|
polling: Polling | None = None,
|
|
24
30
|
loop_wrapper: LoopWrapper | None = None,
|
|
25
31
|
) -> None:
|
|
26
32
|
self.api = api
|
|
27
|
-
self.dispatch = typing.cast(Dispatch, dispatch or dp.Dispatch())
|
|
28
|
-
self.polling = typing.cast(Polling, polling or pg.Polling(api))
|
|
29
|
-
self.loop_wrapper =
|
|
33
|
+
self.dispatch = typing.cast("Dispatch", dispatch or dp.Dispatch())
|
|
34
|
+
self.polling = typing.cast("Polling", polling or pg.Polling(api))
|
|
35
|
+
self.loop_wrapper = loop_wrapper or CONTEXT.loop_wrapper
|
|
30
36
|
|
|
31
37
|
def __repr__(self) -> str:
|
|
32
38
|
return "<{}: api={!r}, dispatch={!r}, polling={!r}, loop_wrapper={!r}>".format(
|
|
33
|
-
self.
|
|
39
|
+
type(self).__name__,
|
|
34
40
|
self.api,
|
|
35
41
|
self.dispatch,
|
|
36
42
|
self.polling,
|
|
@@ -41,6 +47,10 @@ class Telegrinder(typing.Generic[HTTPClient, Dispatch, Polling, LoopWrapper]):
|
|
|
41
47
|
def on(self) -> Dispatch:
|
|
42
48
|
return self.dispatch
|
|
43
49
|
|
|
50
|
+
@property
|
|
51
|
+
def composer(self) -> Composer:
|
|
52
|
+
return CONTEXT.composer.unwrap()
|
|
53
|
+
|
|
44
54
|
async def reset_webhook(self) -> None:
|
|
45
55
|
if not (await self.api.get_webhook_info()).unwrap().url:
|
|
46
56
|
return
|
|
@@ -52,32 +62,28 @@ class Telegrinder(typing.Generic[HTTPClient, Dispatch, Polling, LoopWrapper]):
|
|
|
52
62
|
offset: int = 0,
|
|
53
63
|
skip_updates: bool = False,
|
|
54
64
|
) -> typing.NoReturn:
|
|
55
|
-
async def polling() -> typing.NoReturn:
|
|
65
|
+
async def polling() -> typing.NoReturn: # type: ignore
|
|
56
66
|
if skip_updates:
|
|
57
67
|
logger.debug("Dropping pending updates")
|
|
58
68
|
await self.reset_webhook()
|
|
59
69
|
await self.api.delete_webhook(drop_pending_updates=True)
|
|
60
|
-
self.polling.offset = offset
|
|
61
70
|
|
|
62
71
|
async for updates in self.polling.listen():
|
|
63
72
|
for update in updates:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
self.loop_wrapper.add_task(self.dispatch.feed(update, self.api))
|
|
70
|
-
|
|
71
|
-
if self.loop_wrapper.is_running:
|
|
73
|
+
await self.loop_wrapper.create_task(self.dispatch.feed(update, self.api))
|
|
74
|
+
|
|
75
|
+
self.polling.offset = offset
|
|
76
|
+
|
|
77
|
+
if self.loop_wrapper.running:
|
|
72
78
|
await polling()
|
|
73
79
|
else:
|
|
74
80
|
self.loop_wrapper.add_task(polling())
|
|
75
|
-
self.loop_wrapper.
|
|
81
|
+
self.loop_wrapper.run()
|
|
76
82
|
|
|
77
83
|
def run_forever(self, *, offset: int = 0, skip_updates: bool = False) -> typing.NoReturn:
|
|
78
|
-
logger.
|
|
84
|
+
logger.info("Running blocking polling (id={})", self.api.id)
|
|
79
85
|
self.loop_wrapper.add_task(self.run_polling(offset=offset, skip_updates=skip_updates))
|
|
80
|
-
self.loop_wrapper.
|
|
86
|
+
self.loop_wrapper.run()
|
|
81
87
|
|
|
82
88
|
|
|
83
89
|
__all__ = ("Telegrinder",)
|
|
File without changes
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
from telegrinder.api.api import API
|
|
6
|
-
from telegrinder.model import Model, is_none
|
|
3
|
+
from telegrinder.model import Model
|
|
4
|
+
from telegrinder.tools.magic.shortcut import shortcut
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
def compose_method_params[Cute: BaseCute](
|
|
@@ -27,46 +25,53 @@ def compose_method_params[Cute: BaseCute](
|
|
|
27
25
|
|
|
28
26
|
|
|
29
27
|
if typing.TYPE_CHECKING:
|
|
28
|
+
from fntypes.option import Option
|
|
29
|
+
|
|
30
|
+
from telegrinder.api.api import API
|
|
30
31
|
from telegrinder.node.base import Node
|
|
32
|
+
from telegrinder.types.objects import Update
|
|
31
33
|
|
|
32
|
-
class BaseCute[
|
|
34
|
+
class BaseCute[T: Model](Model):
|
|
33
35
|
api: API
|
|
34
36
|
|
|
35
37
|
@classmethod
|
|
36
38
|
def as_node(cls) -> type[Node]: ...
|
|
37
39
|
|
|
38
40
|
@classmethod
|
|
39
|
-
def from_update(cls, update:
|
|
41
|
+
def from_update(cls, update: T, bound_api: API) -> typing.Self: ...
|
|
40
42
|
|
|
41
43
|
@property
|
|
42
44
|
def ctx_api(self) -> API: ...
|
|
43
45
|
|
|
46
|
+
@property
|
|
47
|
+
def raw_update(self) -> Update: ...
|
|
48
|
+
|
|
49
|
+
def bind_raw_update(self, raw_update: Update, /) -> typing.Self: ...
|
|
50
|
+
|
|
51
|
+
def get_raw_update(self) -> Option[Update]: ...
|
|
52
|
+
|
|
44
53
|
def to_dict(
|
|
45
54
|
self,
|
|
46
55
|
*,
|
|
47
56
|
exclude_fields: set[str] | None = None,
|
|
48
|
-
) -> dict[str, typing.Any]:
|
|
49
|
-
""":param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
|
|
50
|
-
:return: A dictionary representation of this cute model.
|
|
51
|
-
"""
|
|
52
|
-
...
|
|
57
|
+
) -> dict[str, typing.Any]: ...
|
|
53
58
|
|
|
54
59
|
def to_full_dict(
|
|
55
60
|
self,
|
|
56
61
|
*,
|
|
57
62
|
exclude_fields: set[str] | None = None,
|
|
58
|
-
) -> dict[str, typing.Any]:
|
|
59
|
-
""":param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
|
|
60
|
-
:return: A dictionary representation of this model including all models, structs, custom types.
|
|
61
|
-
"""
|
|
62
|
-
...
|
|
63
|
+
) -> dict[str, typing.Any]: ...
|
|
63
64
|
|
|
64
65
|
else:
|
|
65
|
-
import
|
|
66
|
-
from fntypes.
|
|
66
|
+
from fntypes.co import Some, Variative
|
|
67
|
+
from fntypes.misc import from_optional
|
|
67
68
|
|
|
68
|
-
from telegrinder.msgspec_utils import Option,
|
|
69
|
+
from telegrinder.msgspec_utils import Option, encoder, struct_asdict
|
|
69
70
|
from telegrinder.msgspec_utils import get_class_annotations as _get_class_annotations
|
|
71
|
+
from telegrinder.types.objects import Update
|
|
72
|
+
|
|
73
|
+
BOUND_API_KEY = "bound_api"
|
|
74
|
+
RAW_UPDATE_BIND_KEY = "raw_update_bind"
|
|
70
75
|
|
|
71
76
|
def _get_cute_from_generic(generic_args, /):
|
|
72
77
|
for arg in generic_args:
|
|
@@ -94,79 +99,113 @@ else:
|
|
|
94
99
|
|
|
95
100
|
return cute_annotations
|
|
96
101
|
|
|
97
|
-
def
|
|
102
|
+
def _maybe_wrapped(value, /):
|
|
103
|
+
wrapped_value = None
|
|
104
|
+
|
|
98
105
|
while isinstance(value, Variative | Some):
|
|
106
|
+
wrapped_value = value
|
|
107
|
+
|
|
99
108
|
if isinstance(value, Variative):
|
|
100
109
|
value = value.v
|
|
110
|
+
|
|
101
111
|
if isinstance(value, Some):
|
|
102
112
|
value = value.value
|
|
103
|
-
return value
|
|
104
|
-
|
|
105
|
-
class BaseCute[Update]:
|
|
106
|
-
api: API
|
|
107
113
|
|
|
114
|
+
return wrapped_value if wrapped_value is not None else value
|
|
115
|
+
|
|
116
|
+
def _wrap_value(value, type_):
|
|
117
|
+
args = [type_]
|
|
118
|
+
types = []
|
|
119
|
+
|
|
120
|
+
while args:
|
|
121
|
+
arg = args.pop(0)
|
|
122
|
+
origin_arg = typing.get_origin(arg) or arg
|
|
123
|
+
if issubclass(origin_arg, Variative | Option):
|
|
124
|
+
args.extend(typing.get_args(arg))
|
|
125
|
+
types.append(Some if issubclass(origin_arg, Option) else arg)
|
|
126
|
+
|
|
127
|
+
result = value
|
|
128
|
+
for t in types[::-1]:
|
|
129
|
+
result = t(result)
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
def _to_cute(cls, field, value, bound_api):
|
|
133
|
+
maybe_wrapped_value = _maybe_wrapped(value)
|
|
134
|
+
is_wrapped_value = isinstance(maybe_wrapped_value, Variative | Some)
|
|
135
|
+
cute = cls.__cute_annotations__[field].from_update(
|
|
136
|
+
maybe_wrapped_value._value if is_wrapped_value else maybe_wrapped_value,
|
|
137
|
+
bound_api=bound_api,
|
|
138
|
+
)
|
|
139
|
+
return _wrap_value(cute, cls.__annotations__[field]) if is_wrapped_value else cute
|
|
140
|
+
|
|
141
|
+
class BaseCute[T]:
|
|
108
142
|
def __init_subclass__(cls, *args, **kwargs):
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if not cls.__bases__ or not issubclass(cls.__bases__[0], BaseCute):
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
cls.__is_solved_annotations__ = False
|
|
143
|
+
cls.__is_resolved_annotations__ = False
|
|
115
144
|
cls.__cute_annotations__ = None
|
|
116
145
|
cls.__event_node__ = None
|
|
117
146
|
|
|
118
147
|
@classmethod
|
|
119
148
|
def as_node(cls):
|
|
120
149
|
if cls.__event_node__ is None:
|
|
121
|
-
from telegrinder.node.event import
|
|
150
|
+
from telegrinder.node.event import EventNode
|
|
151
|
+
|
|
152
|
+
cls.__event_node__ = EventNode[cls]
|
|
122
153
|
|
|
123
|
-
cls.__event_node__ = _EventNode[cls]
|
|
124
154
|
return cls.__event_node__
|
|
125
155
|
|
|
126
156
|
@classmethod
|
|
127
157
|
def from_update(cls, update, bound_api):
|
|
128
|
-
if not cls.
|
|
129
|
-
cls.
|
|
158
|
+
if not cls.__is_resolved_annotations__:
|
|
159
|
+
cls.__is_resolved_annotations__ = True
|
|
130
160
|
cls.__annotations__ = _get_class_annotations(cls)
|
|
131
161
|
|
|
132
162
|
if cls.__cute_annotations__ is None:
|
|
133
163
|
cls.__cute_annotations__ = _get_cute_annotations(cls.__annotations__)
|
|
134
164
|
|
|
135
|
-
|
|
165
|
+
cute = cls(
|
|
136
166
|
**{
|
|
137
|
-
field:
|
|
138
|
-
cls.__cute_annotations__[field].from_update(_validate_value(value), bound_api=bound_api),
|
|
139
|
-
type=cls.__annotations__[field],
|
|
140
|
-
)
|
|
141
|
-
if field in cls.__cute_annotations__ and not isinstance(value, Nothing | msgspec.UnsetType)
|
|
142
|
-
else value
|
|
167
|
+
field: _to_cute(cls, field, value, bound_api) if field in cls.__cute_annotations__ else value
|
|
143
168
|
for field, value in update.to_dict().items()
|
|
144
169
|
},
|
|
145
|
-
|
|
170
|
+
)._set_bound_api(api=bound_api)
|
|
171
|
+
return cute.bind_raw_update(update) if isinstance(update, Update) else cute
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def api(self):
|
|
175
|
+
return self.__dict__[BOUND_API_KEY]
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def raw_update(self):
|
|
179
|
+
return self.get_raw_update().expect(
|
|
180
|
+
ValueError(f"Cute model `{type(self).__name__}` has no bind `Update` object."),
|
|
146
181
|
)
|
|
147
182
|
|
|
148
183
|
@property
|
|
149
184
|
def ctx_api(self):
|
|
150
185
|
return self.api
|
|
151
186
|
|
|
187
|
+
def get_raw_update(self):
|
|
188
|
+
return from_optional(self.__dict__.get(RAW_UPDATE_BIND_KEY))
|
|
189
|
+
|
|
190
|
+
def bind_raw_update(self, raw_update, /):
|
|
191
|
+
cuties = [self]
|
|
192
|
+
|
|
193
|
+
if isinstance(self, Update) and isinstance(cute := self.incoming_update, BaseCute):
|
|
194
|
+
cuties.append(cute)
|
|
195
|
+
|
|
196
|
+
for cute in cuties:
|
|
197
|
+
cute.__dict__[RAW_UPDATE_BIND_KEY] = raw_update
|
|
198
|
+
|
|
199
|
+
return self
|
|
200
|
+
|
|
201
|
+
def _set_bound_api(self, api):
|
|
202
|
+
self.__dict__[BOUND_API_KEY] = api
|
|
203
|
+
return self
|
|
204
|
+
|
|
152
205
|
def _to_dict(self, dct_name, exclude_fields, full):
|
|
153
206
|
if dct_name not in self.__dict__:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if not full
|
|
157
|
-
else encoder.to_builtins(
|
|
158
|
-
{
|
|
159
|
-
k: field.to_dict(exclude_fields=exclude_fields)
|
|
160
|
-
if isinstance(field, BaseCute)
|
|
161
|
-
else field
|
|
162
|
-
for k in self.__struct_fields__
|
|
163
|
-
if k not in exclude_fields
|
|
164
|
-
and not isinstance(field := _validate_value(getattr(self, k)), msgspec.UnsetType)
|
|
165
|
-
and not is_none(field)
|
|
166
|
-
},
|
|
167
|
-
order="deterministic",
|
|
168
|
-
)
|
|
169
|
-
)
|
|
207
|
+
dct = struct_asdict(self)
|
|
208
|
+
self.__dict__[dct_name] = dct if not full else encoder.to_builtins(dct, order="deterministic")
|
|
170
209
|
|
|
171
210
|
if not exclude_fields:
|
|
172
211
|
return self.__dict__[dct_name]
|
|
@@ -174,11 +213,14 @@ else:
|
|
|
174
213
|
return {key: value for key, value in self.__dict__[dct_name].items() if key not in exclude_fields}
|
|
175
214
|
|
|
176
215
|
def to_dict(self, *, exclude_fields=None, full=False):
|
|
177
|
-
|
|
178
|
-
|
|
216
|
+
return self._to_dict(
|
|
217
|
+
"model_as_dict" if not full else "model_as_full_dict",
|
|
218
|
+
exclude_fields=exclude_fields or set(),
|
|
219
|
+
full=full,
|
|
220
|
+
)
|
|
179
221
|
|
|
180
222
|
def to_full_dict(self, *, exclude_fields=None):
|
|
181
223
|
return self.to_dict(exclude_fields=exclude_fields, full=True)
|
|
182
224
|
|
|
183
225
|
|
|
184
|
-
__all__ = ("BaseCute", "compose_method_params")
|
|
226
|
+
__all__ = ("BaseCute", "compose_method_params", "shortcut")
|