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
telegrinder/node/i18n.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import dataclasses
|
|
3
|
+
import gettext
|
|
4
|
+
import os
|
|
5
|
+
import pathlib
|
|
6
|
+
import typing
|
|
7
|
+
from functools import cached_property
|
|
8
|
+
|
|
9
|
+
from telegrinder.node.base import DataNode, GlobalNode
|
|
10
|
+
from telegrinder.node.source import Locale
|
|
11
|
+
|
|
12
|
+
type Separator = KeySeparator
|
|
13
|
+
|
|
14
|
+
DEFAULT_SEPARATOR: typing.Final[str] = "-"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclasses.dataclass(frozen=True)
|
|
18
|
+
class KeySeparator(GlobalNode[Separator], DataNode):
|
|
19
|
+
value: str
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def set(cls, value: str, /) -> None:
|
|
23
|
+
super().set(cls(value))
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def compose(cls) -> Separator:
|
|
27
|
+
return cls.get(default=cls(DEFAULT_SEPARATOR))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclasses.dataclass(kw_only=True)
|
|
31
|
+
class ABCTranslator(DataNode, abc.ABC):
|
|
32
|
+
locale: str
|
|
33
|
+
separator: str
|
|
34
|
+
_keys: list[str] = dataclasses.field(default_factory=list[str], init=False)
|
|
35
|
+
|
|
36
|
+
@typing.overload
|
|
37
|
+
def __call__(self, **context: typing.Any) -> str: ...
|
|
38
|
+
|
|
39
|
+
@typing.overload
|
|
40
|
+
def __call__(self, message_id: str, /, **context: typing.Any) -> str: ...
|
|
41
|
+
|
|
42
|
+
def __call__(self, message_id: str | None = None, **context: typing.Any) -> str:
|
|
43
|
+
result = self.translate(message_id or self.message_id, **context)
|
|
44
|
+
if not message_id:
|
|
45
|
+
self._keys.clear()
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
def __getattr__(self, __key: str) -> typing.Self:
|
|
49
|
+
self._keys.append(__key)
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def message_id(self) -> str:
|
|
54
|
+
return self.separator.join(self._keys)
|
|
55
|
+
|
|
56
|
+
@abc.abstractmethod
|
|
57
|
+
def translate(self, message_id: str, **context: typing.Any) -> str:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def compose(cls, locale: Locale, separator: KeySeparator) -> typing.Self:
|
|
62
|
+
return cls(locale=locale, separator=separator.value)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclasses.dataclass
|
|
66
|
+
class I18NConfig:
|
|
67
|
+
domain: str
|
|
68
|
+
folder: str | pathlib.Path
|
|
69
|
+
default_locale: str = dataclasses.field(default="en")
|
|
70
|
+
|
|
71
|
+
@cached_property
|
|
72
|
+
def translators(self) -> dict[str, gettext.GNUTranslations]:
|
|
73
|
+
result = {}
|
|
74
|
+
|
|
75
|
+
for name in os.listdir(self.folder):
|
|
76
|
+
if not os.path.isdir(os.path.join(self.folder, name)):
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
mo_path = os.path.join(self.folder, name, "LC_MESSAGES", f"{self.domain}.mo")
|
|
80
|
+
|
|
81
|
+
if os.path.exists(mo_path):
|
|
82
|
+
with open(mo_path, "rb") as f:
|
|
83
|
+
result[name] = gettext.GNUTranslations(f)
|
|
84
|
+
elif os.path.exists(mo_path[:-2] + "po"):
|
|
85
|
+
raise FileNotFoundError(".po files should be compiled first")
|
|
86
|
+
|
|
87
|
+
return result
|
|
88
|
+
|
|
89
|
+
def get_translator(self, locale: str, /) -> gettext.GNUTranslations:
|
|
90
|
+
locale = locale if locale in self.translators else self.default_locale
|
|
91
|
+
return self.translators[locale]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class BaseTranslator(ABCTranslator):
|
|
95
|
+
config: typing.ClassVar[I18NConfig]
|
|
96
|
+
|
|
97
|
+
def __class_getitem__(cls, config: I18NConfig, /) -> typing.Any:
|
|
98
|
+
return type(cls.__name__, (cls,), dict(config=config))
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def configure(cls, config: I18NConfig, /) -> None:
|
|
102
|
+
cls.config = config
|
|
103
|
+
|
|
104
|
+
def translate(self, message_id: str, **context: typing.Any) -> str:
|
|
105
|
+
return self.config.get_translator(self.locale).gettext(message_id).format(**context)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
__all__ = ("ABCTranslator", "BaseTranslator", "I18NConfig", "KeySeparator")
|
telegrinder/node/me.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from telegrinder.api.api import API
|
|
2
2
|
from telegrinder.node.base import ComposeError, scalar_node
|
|
3
|
-
from telegrinder.node.scope import
|
|
3
|
+
from telegrinder.node.scope import global_node
|
|
4
4
|
from telegrinder.types.objects import User
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
@scalar_node
|
|
7
|
+
@scalar_node
|
|
8
|
+
@global_node
|
|
8
9
|
class Me:
|
|
9
10
|
@classmethod
|
|
10
11
|
async def compose(cls, api: API) -> User:
|
telegrinder/node/payload.py
CHANGED
|
@@ -8,7 +8,7 @@ from telegrinder.bot.cute_types.message import MessageCute
|
|
|
8
8
|
from telegrinder.bot.cute_types.pre_checkout_query import PreCheckoutQueryCute
|
|
9
9
|
from telegrinder.node.base import ComposeError, DataNode, FactoryNode, GlobalNode, scalar_node
|
|
10
10
|
from telegrinder.node.polymorphic import Polymorphic, impl
|
|
11
|
-
from telegrinder.tools.
|
|
11
|
+
from telegrinder.tools.callback_data_serialization import ABCDataSerializer, JSONSerializer
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@scalar_node[str]
|
telegrinder/node/polymorphic.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import inspect
|
|
2
1
|
import typing
|
|
3
2
|
|
|
4
3
|
from fntypes.result import Error, Ok
|
|
@@ -9,16 +8,48 @@ from telegrinder.bot.dispatch.context import Context
|
|
|
9
8
|
from telegrinder.modules import logger
|
|
10
9
|
from telegrinder.node.base import ComposeError, Node, get_nodes
|
|
11
10
|
from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, NodeSession, compose_nodes
|
|
12
|
-
from telegrinder.node.scope import NodeScope
|
|
13
|
-
from telegrinder.tools.
|
|
11
|
+
from telegrinder.node.scope import NodeScope, get_scope
|
|
12
|
+
from telegrinder.tools.aio import maybe_awaitable
|
|
13
|
+
from telegrinder.tools.fullname import fullname
|
|
14
|
+
from telegrinder.tools.magic.function import bundle
|
|
14
15
|
from telegrinder.types.objects import Update
|
|
15
16
|
|
|
17
|
+
type Impl = type[classmethod]
|
|
18
|
+
|
|
19
|
+
MORPH_IMPLEMENTATIONS_KEY: typing.Final[str] = "__morph_implementations__"
|
|
20
|
+
IMPL_MARK_KEY: typing.Final[str] = "_is_impl"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@typing.cast("typing.Callable[..., Impl]", lambda f: f)
|
|
24
|
+
def impl(method: typing.Callable[..., typing.Any]):
|
|
25
|
+
setattr(method, IMPL_MARK_KEY, True)
|
|
26
|
+
return classmethod(method)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_polymorphic_implementations(
|
|
30
|
+
cls: type["Polymorphic"],
|
|
31
|
+
/,
|
|
32
|
+
) -> list[typing.Callable[typing.Concatenate[type["Polymorphic"], ...], typing.Any]]:
|
|
33
|
+
moprh_impls = getattr(cls, MORPH_IMPLEMENTATIONS_KEY, None)
|
|
34
|
+
if moprh_impls is not None:
|
|
35
|
+
return moprh_impls
|
|
36
|
+
|
|
37
|
+
impls = []
|
|
38
|
+
for cls_ in cls.mro():
|
|
39
|
+
impls += [
|
|
40
|
+
obj.__func__
|
|
41
|
+
for obj in vars(cls_).values()
|
|
42
|
+
if isinstance(obj, classmethod) and getattr(obj.__func__, IMPL_MARK_KEY, False)
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
setattr(cls, MORPH_IMPLEMENTATIONS_KEY, impls)
|
|
46
|
+
return impls
|
|
47
|
+
|
|
16
48
|
|
|
17
49
|
class Polymorphic(Node):
|
|
18
50
|
@classmethod
|
|
19
51
|
async def compose(cls, raw_update: Update, update: UpdateCute, context: Context) -> typing.Any:
|
|
20
|
-
|
|
21
|
-
scope = getattr(cls, "scope", None)
|
|
52
|
+
scope = get_scope(cls)
|
|
22
53
|
node_ctx = context.get_or_set(CONTEXT_STORE_NODES_KEY, {})
|
|
23
54
|
data = {
|
|
24
55
|
API: update.ctx_api,
|
|
@@ -27,39 +58,43 @@ class Polymorphic(Node):
|
|
|
27
58
|
}
|
|
28
59
|
|
|
29
60
|
for i, impl_ in enumerate(get_polymorphic_implementations(cls)):
|
|
30
|
-
|
|
31
|
-
|
|
61
|
+
# To determine whether this is a right morph, all subnodes must be resolved
|
|
62
|
+
if scope is NodeScope.PER_EVENT and (cls, i) in node_ctx:
|
|
63
|
+
return node_ctx[(cls, i)].value
|
|
32
64
|
|
|
33
65
|
match await compose_nodes(get_nodes(impl_), context, data=data):
|
|
34
66
|
case Ok(col):
|
|
35
67
|
node_collection = col
|
|
36
68
|
case Error(err):
|
|
37
|
-
logger.debug(
|
|
69
|
+
logger.debug(
|
|
70
|
+
"Impl `{}` composition failed with error: {!r}",
|
|
71
|
+
fullname(impl_),
|
|
72
|
+
err,
|
|
73
|
+
)
|
|
74
|
+
continue
|
|
38
75
|
|
|
39
|
-
|
|
40
|
-
logger.debug("Impl {!r} composition failed!", impl_.__name__)
|
|
41
|
-
continue
|
|
76
|
+
result = None
|
|
42
77
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
78
|
+
try:
|
|
79
|
+
result = await maybe_awaitable(
|
|
80
|
+
bundle(impl_, data, typebundle=True)(
|
|
81
|
+
cls,
|
|
82
|
+
**node_collection.values,
|
|
83
|
+
),
|
|
48
84
|
)
|
|
49
|
-
res: NodeSession = node_ctx[(cls, i)]
|
|
50
|
-
await node_collection.close_all()
|
|
51
|
-
return res.value
|
|
52
|
-
|
|
53
|
-
result = impl_(cls, **node_collection.values | magic_bundle(impl_, data, typebundle=True))
|
|
54
|
-
if inspect.isawaitable(result):
|
|
55
|
-
result = await result
|
|
56
85
|
|
|
57
|
-
|
|
58
|
-
|
|
86
|
+
if scope is NodeScope.PER_EVENT:
|
|
87
|
+
node_ctx[(cls, i)] = NodeSession(cls, result)
|
|
59
88
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
return result
|
|
90
|
+
except ComposeError as compose_error:
|
|
91
|
+
logger.debug(
|
|
92
|
+
"Failed to compose morph impl `{}`, error: {!r}",
|
|
93
|
+
fullname(impl_),
|
|
94
|
+
compose_error.message,
|
|
95
|
+
)
|
|
96
|
+
finally:
|
|
97
|
+
await node_collection.close_all(with_value=result)
|
|
63
98
|
|
|
64
99
|
raise ComposeError("No implementation found.")
|
|
65
100
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
2
|
+
from telegrinder.node.base import ComposeError, scalar_node
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@scalar_node
|
|
6
|
+
class ReplyMessage:
|
|
7
|
+
@classmethod
|
|
8
|
+
def compose(cls, message: MessageCute) -> MessageCute:
|
|
9
|
+
return message.reply_to_message.expect(ComposeError("Message doesn't have reply"))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = ("ReplyMessage",)
|
telegrinder/node/rule.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
-
import importlib
|
|
3
2
|
import typing
|
|
4
3
|
|
|
5
4
|
from telegrinder.bot.cute_types.update import UpdateCute
|
|
@@ -9,6 +8,12 @@ from telegrinder.node.base import ComposeError, Node
|
|
|
9
8
|
if typing.TYPE_CHECKING:
|
|
10
9
|
from telegrinder.bot.dispatch.process import check_rule
|
|
11
10
|
from telegrinder.bot.rules.abc import ABCRule
|
|
11
|
+
else:
|
|
12
|
+
|
|
13
|
+
def check_rule(*args, **kwargs):
|
|
14
|
+
from telegrinder.bot.dispatch.process import check_rule
|
|
15
|
+
|
|
16
|
+
return check_rule(*args, **kwargs)
|
|
12
17
|
|
|
13
18
|
|
|
14
19
|
class RuleChain(dict[str, typing.Any], Node):
|
|
@@ -36,18 +41,6 @@ class RuleChain(dict[str, typing.Any], Node):
|
|
|
36
41
|
|
|
37
42
|
@classmethod
|
|
38
43
|
async def compose(cls, update: UpdateCute) -> typing.Any:
|
|
39
|
-
# Hack to avoid circular import
|
|
40
|
-
globalns = globals()
|
|
41
|
-
if "check_rule" not in globalns:
|
|
42
|
-
globalns.update(
|
|
43
|
-
{
|
|
44
|
-
"check_rule": getattr(
|
|
45
|
-
importlib.import_module("telegrinder.bot.dispatch.process"),
|
|
46
|
-
"check_rule",
|
|
47
|
-
),
|
|
48
|
-
},
|
|
49
|
-
)
|
|
50
|
-
|
|
51
44
|
ctx = Context()
|
|
52
45
|
for rule in cls.rules:
|
|
53
46
|
if not await check_rule(update.api, rule, update, ctx):
|
telegrinder/node/scope.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import enum
|
|
2
4
|
import typing
|
|
3
5
|
|
|
4
6
|
if typing.TYPE_CHECKING:
|
|
5
|
-
from .base import
|
|
7
|
+
from telegrinder.node.base import Composable
|
|
8
|
+
|
|
9
|
+
NODE_SCOPE_KEY: typing.Final[str] = "scope"
|
|
6
10
|
|
|
7
11
|
|
|
8
12
|
class NodeScope(enum.Enum):
|
|
@@ -16,26 +20,31 @@ PER_CALL = NodeScope.PER_CALL
|
|
|
16
20
|
GLOBAL = NodeScope.GLOBAL
|
|
17
21
|
|
|
18
22
|
|
|
19
|
-
def per_call[T:
|
|
23
|
+
def per_call[T: Composable[typing.Any]](node: type[T]) -> type[T]:
|
|
20
24
|
setattr(node, "scope", PER_CALL)
|
|
21
25
|
return node
|
|
22
26
|
|
|
23
27
|
|
|
24
|
-
def per_event[T:
|
|
28
|
+
def per_event[T: Composable[typing.Any]](node: type[T]) -> type[T]:
|
|
25
29
|
setattr(node, "scope", PER_EVENT)
|
|
26
30
|
return node
|
|
27
31
|
|
|
28
32
|
|
|
29
|
-
def global_node[T:
|
|
33
|
+
def global_node[T: Composable[typing.Any]](node: type[T]) -> type[T]:
|
|
30
34
|
setattr(node, "scope", GLOBAL)
|
|
31
35
|
return node
|
|
32
36
|
|
|
33
37
|
|
|
38
|
+
def get_scope(node: Composable[typing.Any], /) -> NodeScope:
|
|
39
|
+
return getattr(node, NODE_SCOPE_KEY, PER_EVENT)
|
|
40
|
+
|
|
41
|
+
|
|
34
42
|
__all__ = (
|
|
35
43
|
"GLOBAL",
|
|
36
|
-
"NodeScope",
|
|
37
44
|
"PER_CALL",
|
|
38
45
|
"PER_EVENT",
|
|
46
|
+
"NodeScope",
|
|
47
|
+
"get_scope",
|
|
39
48
|
"global_node",
|
|
40
49
|
"per_call",
|
|
41
50
|
"per_event",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from telegrinder.modules import logger
|
|
7
|
+
from telegrinder.node.exceptions import ComposeError
|
|
8
|
+
from telegrinder.node.scope import NodeScope, get_scope
|
|
9
|
+
from telegrinder.tools.aio import Generator, stop_generator
|
|
10
|
+
from telegrinder.tools.fullname import fullname
|
|
11
|
+
|
|
12
|
+
if typing.TYPE_CHECKING:
|
|
13
|
+
from telegrinder.node.base import IsNode
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclasses.dataclass(slots=True, repr=False)
|
|
17
|
+
class NodeSession:
|
|
18
|
+
node: IsNode
|
|
19
|
+
value: typing.Any
|
|
20
|
+
generator: Generator[typing.Any, typing.Any, typing.Any] | None = None
|
|
21
|
+
|
|
22
|
+
def __repr__(self) -> str:
|
|
23
|
+
return f"<{type(self).__name__}: {self.value!r}" + (" (ACTIVE)>" if self.generator else ">")
|
|
24
|
+
|
|
25
|
+
async def close(
|
|
26
|
+
self,
|
|
27
|
+
with_value: typing.Any | None = None,
|
|
28
|
+
scopes: tuple[NodeScope, ...] = (NodeScope.PER_CALL,),
|
|
29
|
+
) -> typing.Any | None:
|
|
30
|
+
result = None
|
|
31
|
+
|
|
32
|
+
if self.generator is not None and get_scope(self.node) in scopes:
|
|
33
|
+
try:
|
|
34
|
+
await stop_generator(self.generator, with_value)
|
|
35
|
+
except ComposeError as compose_error:
|
|
36
|
+
logger.debug(
|
|
37
|
+
"Caught compose error when closing session for node `{}`: {}",
|
|
38
|
+
fullname(self.node),
|
|
39
|
+
compose_error.message,
|
|
40
|
+
)
|
|
41
|
+
except BaseException as exception:
|
|
42
|
+
logger.debug(
|
|
43
|
+
"Uncaught {!r} was occurred when closing session for node `{}`",
|
|
44
|
+
exception,
|
|
45
|
+
fullname(self.node),
|
|
46
|
+
)
|
|
47
|
+
finally:
|
|
48
|
+
self.generator = None
|
|
49
|
+
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = ("NodeSession",)
|
telegrinder/node/source.py
CHANGED
|
@@ -4,7 +4,13 @@ import typing
|
|
|
4
4
|
from fntypes.option import Nothing, Option, Some
|
|
5
5
|
|
|
6
6
|
from telegrinder.api.api import API
|
|
7
|
-
from telegrinder.bot.cute_types import
|
|
7
|
+
from telegrinder.bot.cute_types import (
|
|
8
|
+
CallbackQueryCute,
|
|
9
|
+
ChatJoinRequestCute,
|
|
10
|
+
ChatMemberUpdatedCute,
|
|
11
|
+
MessageCute,
|
|
12
|
+
PreCheckoutQueryCute,
|
|
13
|
+
)
|
|
8
14
|
from telegrinder.node.base import ComposeError, DataNode, scalar_node
|
|
9
15
|
from telegrinder.node.polymorphic import Polymorphic, impl
|
|
10
16
|
from telegrinder.types.objects import Chat, Message, User
|
|
@@ -20,7 +26,7 @@ class Source(Polymorphic, DataNode):
|
|
|
20
26
|
@impl
|
|
21
27
|
def compose_message(cls, message: MessageCute) -> typing.Self:
|
|
22
28
|
return cls(
|
|
23
|
-
api=message.
|
|
29
|
+
api=message.api,
|
|
24
30
|
from_user=message.from_user,
|
|
25
31
|
chat=Some(message.chat),
|
|
26
32
|
thread_id=message.message_thread_id,
|
|
@@ -29,28 +35,33 @@ class Source(Polymorphic, DataNode):
|
|
|
29
35
|
@impl
|
|
30
36
|
def compose_callback_query(cls, callback_query: CallbackQueryCute) -> typing.Self:
|
|
31
37
|
return cls(
|
|
32
|
-
api=callback_query.
|
|
38
|
+
api=callback_query.api,
|
|
33
39
|
from_user=callback_query.from_user,
|
|
34
40
|
chat=callback_query.chat,
|
|
35
41
|
thread_id=callback_query.message_thread_id,
|
|
36
42
|
)
|
|
37
43
|
|
|
44
|
+
@impl
|
|
45
|
+
def compose_chat_member_updated(cls, chat_member_updated: ChatMemberUpdatedCute) -> typing.Self:
|
|
46
|
+
return cls(
|
|
47
|
+
api=chat_member_updated.api,
|
|
48
|
+
from_user=chat_member_updated.from_user,
|
|
49
|
+
chat=Some(chat_member_updated.chat),
|
|
50
|
+
)
|
|
51
|
+
|
|
38
52
|
@impl
|
|
39
53
|
def compose_chat_join_request(cls, chat_join_request: ChatJoinRequestCute) -> typing.Self:
|
|
40
54
|
return cls(
|
|
41
|
-
api=chat_join_request.
|
|
55
|
+
api=chat_join_request.api,
|
|
42
56
|
from_user=chat_join_request.from_user,
|
|
43
57
|
chat=Some(chat_join_request.chat),
|
|
44
|
-
thread_id=Nothing(),
|
|
45
58
|
)
|
|
46
59
|
|
|
47
60
|
@impl
|
|
48
61
|
def compose_pre_checkout_query(cls, pre_checkout_query: PreCheckoutQueryCute) -> typing.Self:
|
|
49
62
|
return cls(
|
|
50
|
-
api=pre_checkout_query.
|
|
63
|
+
api=pre_checkout_query.api,
|
|
51
64
|
from_user=pre_checkout_query.from_user,
|
|
52
|
-
chat=Nothing(),
|
|
53
|
-
thread_id=Nothing(),
|
|
54
65
|
)
|
|
55
66
|
|
|
56
67
|
async def send(self, text: str, **kwargs: typing.Any) -> Message:
|
|
@@ -77,6 +88,13 @@ class UserSource:
|
|
|
77
88
|
return source.from_user
|
|
78
89
|
|
|
79
90
|
|
|
91
|
+
@scalar_node
|
|
92
|
+
class ChatId:
|
|
93
|
+
@classmethod
|
|
94
|
+
def compose(cls, chat: ChatSource) -> int:
|
|
95
|
+
return chat.id
|
|
96
|
+
|
|
97
|
+
|
|
80
98
|
@scalar_node
|
|
81
99
|
class UserId:
|
|
82
100
|
@classmethod
|
|
@@ -84,4 +102,18 @@ class UserId:
|
|
|
84
102
|
return user.id
|
|
85
103
|
|
|
86
104
|
|
|
87
|
-
|
|
105
|
+
@scalar_node
|
|
106
|
+
class Locale:
|
|
107
|
+
@classmethod
|
|
108
|
+
def compose(cls, user: UserSource) -> str:
|
|
109
|
+
return user.language_code.expect(ComposeError("User has no language code."))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
__all__ = (
|
|
113
|
+
"ChatId",
|
|
114
|
+
"ChatSource",
|
|
115
|
+
"Locale",
|
|
116
|
+
"Source",
|
|
117
|
+
"UserId",
|
|
118
|
+
"UserSource",
|
|
119
|
+
)
|
telegrinder/node/text.py
CHANGED
|
@@ -2,7 +2,6 @@ import typing
|
|
|
2
2
|
|
|
3
3
|
from telegrinder.bot.cute_types.message import MessageCute
|
|
4
4
|
from telegrinder.node.base import ComposeError, FactoryNode, scalar_node
|
|
5
|
-
from telegrinder.node.either import Either
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
@scalar_node
|
|
@@ -26,7 +25,7 @@ class Text:
|
|
|
26
25
|
@scalar_node
|
|
27
26
|
class TextInteger:
|
|
28
27
|
@classmethod
|
|
29
|
-
def compose(cls, text:
|
|
28
|
+
def compose(cls, text: Text | Caption) -> int:
|
|
30
29
|
if not text.isdigit():
|
|
31
30
|
raise ComposeError("Text is not digit.")
|
|
32
31
|
return int(text)
|
|
File without changes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import inspect
|
|
2
1
|
import typing
|
|
3
2
|
|
|
4
3
|
from telegrinder.node.base import ComposeError, IsNode, Node
|
|
5
4
|
from telegrinder.node.container import ContainerNode
|
|
5
|
+
from telegrinder.tools.aio import maybe_awaitable
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def cast_false_to_none[Value](value: Value) -> Value | None:
|
|
@@ -22,10 +22,8 @@ def generate_node(
|
|
|
22
22
|
func: typing.Callable[..., typing.Any],
|
|
23
23
|
casts: tuple[typing.Callable[[typing.Any], typing.Any], ...] = (cast_false_to_none, error_on_none),
|
|
24
24
|
) -> type[Node]:
|
|
25
|
-
async def compose(
|
|
26
|
-
result = func(*args)
|
|
27
|
-
if inspect.isawaitable(result):
|
|
28
|
-
result = await result
|
|
25
|
+
async def compose(_, *args: typing.Any) -> typing.Any:
|
|
26
|
+
result = await maybe_awaitable(func(*args))
|
|
29
27
|
for cast in casts:
|
|
30
28
|
result = cast(result)
|
|
31
29
|
return result
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from telegrinder.node.base import ComposeError, NodeClass, scalar_node
|
|
4
|
+
from telegrinder.node.scope import per_call
|
|
5
|
+
from telegrinder.tools.magic.annotations import TypeParameter, get_generic_parameters
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@scalar_node
|
|
9
|
+
@per_call
|
|
10
|
+
class TypeArgs:
|
|
11
|
+
@classmethod
|
|
12
|
+
def compose(cls, node_cls: NodeClass) -> dict[TypeParameter, typing.Any]:
|
|
13
|
+
return get_generic_parameters(node_cls).expect(ComposeError("No generic alias."))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ("TypeArgs",)
|
telegrinder/py.typed
CHANGED
|
File without changes
|
telegrinder/rules.py
CHANGED
|
File without changes
|