telegrinder 0.3.4__py3-none-any.whl → 0.4.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 +148 -149
- telegrinder/api/__init__.py +9 -8
- telegrinder/api/api.py +101 -93
- telegrinder/api/error.py +20 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +72 -66
- telegrinder/bot/bot.py +83 -76
- telegrinder/bot/cute_types/__init__.py +19 -17
- telegrinder/bot/cute_types/base.py +184 -258
- telegrinder/bot/cute_types/callback_query.py +400 -385
- telegrinder/bot/cute_types/chat_join_request.py +62 -61
- telegrinder/bot/cute_types/chat_member_updated.py +157 -160
- telegrinder/bot/cute_types/inline_query.py +44 -43
- telegrinder/bot/cute_types/message.py +2590 -2637
- telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
- telegrinder/bot/cute_types/update.py +112 -104
- telegrinder/bot/cute_types/utils.py +62 -95
- telegrinder/bot/dispatch/__init__.py +59 -55
- telegrinder/bot/dispatch/abc.py +76 -77
- telegrinder/bot/dispatch/context.py +96 -98
- telegrinder/bot/dispatch/dispatch.py +254 -202
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +23 -24
- telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
- telegrinder/bot/dispatch/handler/base.py +57 -57
- telegrinder/bot/dispatch/handler/document_reply.py +44 -44
- telegrinder/bot/dispatch/handler/func.py +129 -135
- telegrinder/bot/dispatch/handler/media_group_reply.py +44 -43
- telegrinder/bot/dispatch/handler/message_reply.py +36 -36
- telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
- telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
- telegrinder/bot/dispatch/handler/video_reply.py +44 -44
- telegrinder/bot/dispatch/middleware/__init__.py +3 -3
- telegrinder/bot/dispatch/middleware/abc.py +97 -22
- telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
- telegrinder/bot/dispatch/process.py +151 -157
- telegrinder/bot/dispatch/return_manager/__init__.py +15 -13
- telegrinder/bot/dispatch/return_manager/abc.py +104 -108
- telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
- telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
- telegrinder/bot/dispatch/return_manager/message.py +36 -36
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -13
- telegrinder/bot/dispatch/view/abc.py +45 -41
- telegrinder/bot/dispatch/view/base.py +231 -200
- telegrinder/bot/dispatch/view/box.py +140 -129
- telegrinder/bot/dispatch/view/callback_query.py +16 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +11 -16
- telegrinder/bot/dispatch/view/chat_member.py +37 -39
- telegrinder/bot/dispatch/view/inline_query.py +16 -17
- telegrinder/bot/dispatch/view/message.py +43 -44
- telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
- telegrinder/bot/dispatch/view/raw.py +116 -114
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +14 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +59 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +20 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +251 -172
- telegrinder/bot/dispatch/waiter_machine/middleware.py +94 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +57 -68
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +139 -131
- telegrinder/bot/rules/__init__.py +85 -62
- telegrinder/bot/rules/abc.py +213 -206
- telegrinder/bot/rules/callback_data.py +122 -163
- telegrinder/bot/rules/chat_join.py +45 -43
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +33 -36
- telegrinder/bot/rules/func.py +28 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/id.py +24 -0
- telegrinder/bot/rules/inline.py +58 -56
- telegrinder/bot/rules/integer.py +21 -20
- telegrinder/bot/rules/is_from.py +127 -127
- telegrinder/bot/rules/logic.py +18 -0
- telegrinder/bot/rules/markup.py +42 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +15 -17
- telegrinder/bot/rules/message_entities.py +33 -35
- telegrinder/bot/rules/node.py +33 -27
- telegrinder/bot/rules/payload.py +81 -0
- telegrinder/bot/rules/payment_invoice.py +29 -0
- telegrinder/bot/rules/regex.py +36 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +35 -37
- telegrinder/bot/rules/text.py +38 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +17 -19
- telegrinder/bot/scenario/checkbox.py +174 -176
- telegrinder/bot/scenario/choice.py +48 -51
- telegrinder/client/__init__.py +12 -4
- telegrinder/client/abc.py +100 -75
- telegrinder/client/aiohttp.py +134 -130
- telegrinder/client/form_data.py +31 -0
- telegrinder/client/sonic.py +212 -0
- telegrinder/model.py +208 -315
- telegrinder/modules.py +239 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +478 -410
- telegrinder/node/__init__.py +86 -25
- telegrinder/node/attachment.py +163 -87
- telegrinder/node/base.py +288 -160
- telegrinder/node/callback_query.py +54 -53
- telegrinder/node/command.py +34 -33
- telegrinder/node/composer.py +163 -198
- telegrinder/node/container.py +33 -27
- telegrinder/node/either.py +82 -0
- telegrinder/node/event.py +54 -65
- telegrinder/node/file.py +51 -0
- telegrinder/node/me.py +15 -16
- telegrinder/node/payload.py +78 -0
- telegrinder/node/polymorphic.py +67 -48
- telegrinder/node/rule.py +72 -76
- telegrinder/node/scope.py +36 -38
- telegrinder/node/source.py +87 -71
- telegrinder/node/text.py +53 -41
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +36 -40
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -62
- telegrinder/tools/__init__.py +152 -93
- telegrinder/tools/adapter/__init__.py +19 -0
- telegrinder/tools/adapter/abc.py +49 -0
- telegrinder/tools/adapter/dataclass.py +56 -0
- telegrinder/{bot/rules → tools}/adapter/errors.py +5 -5
- telegrinder/{bot/rules → tools}/adapter/event.py +63 -65
- telegrinder/{bot/rules → tools}/adapter/node.py +46 -48
- telegrinder/{bot/rules → tools}/adapter/raw_event.py +27 -27
- telegrinder/{bot/rules → tools}/adapter/raw_update.py +30 -30
- telegrinder/tools/buttons.py +106 -80
- telegrinder/tools/callback_data_serilization/__init__.py +5 -0
- telegrinder/tools/callback_data_serilization/abc.py +51 -0
- telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
- telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
- telegrinder/tools/error_handler/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +30 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +179 -193
- telegrinder/tools/formatting/__init__.py +83 -63
- telegrinder/tools/formatting/deep_links.py +541 -0
- telegrinder/tools/formatting/{html.py → html_formatter.py} +266 -294
- telegrinder/tools/formatting/spec_html_formats.py +71 -117
- telegrinder/tools/functional.py +8 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +387 -412
- telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
- telegrinder/tools/i18n/__init__.py +7 -7
- telegrinder/tools/i18n/abc.py +30 -30
- telegrinder/tools/i18n/middleware/__init__.py +3 -3
- telegrinder/tools/i18n/middleware/abc.py +22 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard.py +128 -128
- telegrinder/tools/lifespan.py +105 -0
- telegrinder/tools/limited_dict.py +32 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +20 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +169 -224
- telegrinder/tools/magic.py +307 -157
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +31 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/tools/strings.py +13 -0
- telegrinder/types/__init__.py +268 -260
- telegrinder/types/enums.py +711 -701
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +5055 -4633
- telegrinder/types/objects.py +7058 -6950
- telegrinder/verification_utils.py +30 -32
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +22 -22
- telegrinder-0.4.0.dist-info/METADATA +144 -0
- telegrinder-0.4.0.dist-info/RECORD +182 -0
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
- telegrinder/bot/rules/adapter/__init__.py +0 -17
- telegrinder/bot/rules/adapter/abc.py +0 -31
- telegrinder/node/message.py +0 -14
- telegrinder/node/update.py +0 -15
- telegrinder/tools/formatting/links.py +0 -38
- telegrinder/tools/kb_set/__init__.py +0 -4
- telegrinder/tools/kb_set/base.py +0 -15
- telegrinder/tools/kb_set/yaml.py +0 -63
- telegrinder-0.3.4.dist-info/METADATA +0 -110
- telegrinder-0.3.4.dist-info/RECORD +0 -165
telegrinder/node/file.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
import telegrinder.types as tg_types
|
|
4
|
+
from telegrinder.api.api import API
|
|
5
|
+
from telegrinder.node.attachment import Animation, Audio, Document, Photo, Video, VideoNote, Voice
|
|
6
|
+
from telegrinder.node.base import FactoryNode
|
|
7
|
+
|
|
8
|
+
type Attachment = Animation | Audio | Document | Photo | Video | VideoNote | Voice
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _FileId(FactoryNode):
|
|
12
|
+
attachment_node: type[Attachment]
|
|
13
|
+
|
|
14
|
+
def __class_getitem__(cls, attachment_node: type[Attachment], /):
|
|
15
|
+
return cls(attachment_node=attachment_node)
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def get_subnodes(cls):
|
|
19
|
+
return {"attach": cls.attachment_node}
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def compose(cls, attach: Attachment) -> str:
|
|
23
|
+
if isinstance(attach, Photo):
|
|
24
|
+
return attach.sizes[-1].file_id
|
|
25
|
+
return attach.file_id
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _File(FactoryNode):
|
|
29
|
+
attachment_node: type[Attachment]
|
|
30
|
+
|
|
31
|
+
def __class_getitem__(cls, attachment_node: type[Attachment], /):
|
|
32
|
+
return cls(attachment_node=attachment_node)
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def get_subnodes(cls):
|
|
36
|
+
return {"file_id": _FileId[cls.attachment_node]}
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
async def compose(cls, file_id: str, api: API) -> tg_types.File:
|
|
40
|
+
return (await api.get_file(file_id=file_id)).expect("File can't be downloaded.")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if typing.TYPE_CHECKING:
|
|
44
|
+
type FileId[T: Attachment] = str
|
|
45
|
+
type File[T: Attachment] = tg_types.File
|
|
46
|
+
else:
|
|
47
|
+
FileId = _FileId
|
|
48
|
+
File = _File
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = ("File", "FileId")
|
telegrinder/node/me.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
from telegrinder.api.api import API
|
|
2
|
-
from telegrinder.node.base import ComposeError,
|
|
3
|
-
from telegrinder.node.scope import GLOBAL
|
|
4
|
-
from telegrinder.types.objects import User
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
me
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
__all__ = ("Me",)
|
|
1
|
+
from telegrinder.api.api import API
|
|
2
|
+
from telegrinder.node.base import ComposeError, scalar_node
|
|
3
|
+
from telegrinder.node.scope import GLOBAL
|
|
4
|
+
from telegrinder.types.objects import User
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@scalar_node(scope=GLOBAL)
|
|
8
|
+
class Me:
|
|
9
|
+
@classmethod
|
|
10
|
+
async def compose(cls, api: API) -> User:
|
|
11
|
+
me = await api.get_me()
|
|
12
|
+
return me.expect(ComposeError("Can't complete api.get_me() request."))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = ("Me",)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from fntypes.result import Error, Ok
|
|
5
|
+
|
|
6
|
+
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
7
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
8
|
+
from telegrinder.bot.cute_types.pre_checkout_query import PreCheckoutQueryCute
|
|
9
|
+
from telegrinder.node.base import ComposeError, DataNode, FactoryNode, GlobalNode, scalar_node
|
|
10
|
+
from telegrinder.node.polymorphic import Polymorphic, impl
|
|
11
|
+
from telegrinder.tools.callback_data_serilization import ABCDataSerializer, JSONSerializer
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@scalar_node[str]
|
|
15
|
+
class Payload(Polymorphic):
|
|
16
|
+
@impl
|
|
17
|
+
def compose_pre_checkout_query(cls, event: PreCheckoutQueryCute) -> str:
|
|
18
|
+
return event.invoice_payload
|
|
19
|
+
|
|
20
|
+
@impl
|
|
21
|
+
def compose_callback_query(cls, event: CallbackQueryCute) -> str:
|
|
22
|
+
return event.data.expect("CallbackQuery has no data.")
|
|
23
|
+
|
|
24
|
+
@impl
|
|
25
|
+
def compose_message(cls, event: MessageCute) -> str:
|
|
26
|
+
return event.successful_payment.map(
|
|
27
|
+
lambda payment: payment.invoice_payload,
|
|
28
|
+
).expect("Message has no successful payment.")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
32
|
+
class PayloadSerializer[T: type[ABCDataSerializer[typing.Any]]](DataNode, GlobalNode[T]):
|
|
33
|
+
serializer: type[ABCDataSerializer[typing.Any]]
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def compose(cls) -> typing.Self:
|
|
37
|
+
return cls(serializer=cls.get(default=JSONSerializer))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _PayloadData(FactoryNode):
|
|
41
|
+
data_type: type[typing.Any]
|
|
42
|
+
serializer: type[ABCDataSerializer[typing.Any]] | None = None
|
|
43
|
+
|
|
44
|
+
def __class_getitem__(
|
|
45
|
+
cls,
|
|
46
|
+
data_type: type[typing.Any] | tuple[type[typing.Any], type[ABCDataSerializer[typing.Any]]],
|
|
47
|
+
/,
|
|
48
|
+
):
|
|
49
|
+
data_type, serializer = (data_type, None) if not isinstance(data_type, tuple) else data_type
|
|
50
|
+
return cls(data_type=data_type, serializer=serializer)
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def compose(cls, payload: Payload, payload_serializer: PayloadSerializer) -> typing.Any:
|
|
54
|
+
serializer = cls.serializer or payload_serializer.serializer
|
|
55
|
+
match serializer(cls.data_type).deserialize(payload):
|
|
56
|
+
case Ok(value):
|
|
57
|
+
return value
|
|
58
|
+
case Error(err):
|
|
59
|
+
raise ComposeError(err)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if typing.TYPE_CHECKING:
|
|
63
|
+
import typing_extensions
|
|
64
|
+
|
|
65
|
+
DataType = typing.TypeVar("DataType")
|
|
66
|
+
Serializer = typing_extensions.TypeVar(
|
|
67
|
+
"Serializer",
|
|
68
|
+
bound=ABCDataSerializer,
|
|
69
|
+
default=JSONSerializer[typing.Any],
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
type PayloadDataType[DataType, Serializer] = typing.Annotated[DataType, Serializer]
|
|
73
|
+
PayloadData: typing.TypeAlias = PayloadDataType[DataType, Serializer]
|
|
74
|
+
else:
|
|
75
|
+
PayloadData = _PayloadData
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
__all__ = ("Payload", "PayloadData", "PayloadSerializer")
|
telegrinder/node/polymorphic.py
CHANGED
|
@@ -1,48 +1,67 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
from telegrinder.
|
|
7
|
-
from telegrinder.
|
|
8
|
-
from telegrinder.
|
|
9
|
-
from telegrinder.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
1
|
+
import inspect
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from fntypes.result import Error, Ok
|
|
5
|
+
|
|
6
|
+
from telegrinder.api.api import API
|
|
7
|
+
from telegrinder.bot.cute_types.update import UpdateCute
|
|
8
|
+
from telegrinder.bot.dispatch.context import Context
|
|
9
|
+
from telegrinder.modules import logger
|
|
10
|
+
from telegrinder.node.base import ComposeError, Node, get_nodes
|
|
11
|
+
from telegrinder.node.composer import CONTEXT_STORE_NODES_KEY, NodeSession, compose_nodes
|
|
12
|
+
from telegrinder.node.scope import NodeScope
|
|
13
|
+
from telegrinder.tools.magic import get_impls, impl, magic_bundle
|
|
14
|
+
from telegrinder.types.objects import Update
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Polymorphic(Node):
|
|
18
|
+
@classmethod
|
|
19
|
+
async def compose(cls, raw_update: Update, update: UpdateCute, context: Context) -> typing.Any:
|
|
20
|
+
logger.debug(f"Composing polymorphic node {cls.__name__!r}...")
|
|
21
|
+
scope = getattr(cls, "scope", None)
|
|
22
|
+
node_ctx = context.get_or_set(CONTEXT_STORE_NODES_KEY, {})
|
|
23
|
+
data = {
|
|
24
|
+
API: update.ctx_api,
|
|
25
|
+
Context: context,
|
|
26
|
+
Update: raw_update,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for i, impl_ in enumerate(get_impls(cls)):
|
|
30
|
+
logger.debug("Checking impl {!r}...", impl_.__name__)
|
|
31
|
+
node_collection = None
|
|
32
|
+
|
|
33
|
+
match await compose_nodes(get_nodes(impl_), context, data=data):
|
|
34
|
+
case Ok(col):
|
|
35
|
+
node_collection = col
|
|
36
|
+
case Error(err):
|
|
37
|
+
logger.debug(f"Composition failed with error: {err!r}")
|
|
38
|
+
|
|
39
|
+
if node_collection is None:
|
|
40
|
+
logger.debug("Impl {!r} composition failed!", impl_.__name__)
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
# To determine whether this is a right morph, all subnodes must be resolved
|
|
44
|
+
if scope is NodeScope.PER_EVENT and (cls, i) in node_ctx:
|
|
45
|
+
logger.debug(
|
|
46
|
+
"Morph is already cached as per_event node, using its value. Impl {!r} succeeded!",
|
|
47
|
+
impl_.__name__,
|
|
48
|
+
)
|
|
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
|
+
|
|
57
|
+
if scope is NodeScope.PER_EVENT:
|
|
58
|
+
node_ctx[(cls, i)] = NodeSession(cls, result, {})
|
|
59
|
+
|
|
60
|
+
await node_collection.close_all(with_value=result)
|
|
61
|
+
logger.debug("Impl {!r} succeeded with value: {!r}", impl_.__name__, result)
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
raise ComposeError("No implementation found.")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__all__ = ("Polymorphic", "impl")
|
telegrinder/node/rule.py
CHANGED
|
@@ -1,76 +1,72 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import importlib
|
|
3
|
-
import typing
|
|
4
|
-
|
|
5
|
-
from telegrinder.bot.
|
|
6
|
-
from telegrinder.
|
|
7
|
-
from telegrinder.node.
|
|
8
|
-
|
|
9
|
-
if typing.TYPE_CHECKING:
|
|
10
|
-
from telegrinder.bot.dispatch.process import check_rule
|
|
11
|
-
from telegrinder.bot.rules.abc import ABCRule
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class RuleChain(dict[str, typing.Any], Node):
|
|
15
|
-
dataclass: type[typing.Any] = dict
|
|
16
|
-
rules: tuple["ABCRule", ...] = ()
|
|
17
|
-
|
|
18
|
-
def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
19
|
-
super().__init_subclass__(*args, **kwargs)
|
|
20
|
-
|
|
21
|
-
if cls.__name__ == "_RuleNode":
|
|
22
|
-
return
|
|
23
|
-
cls.dataclass = cls.generate_node_dataclass(cls)
|
|
24
|
-
|
|
25
|
-
def __new__(cls, *rules: "ABCRule") -> type[Node]:
|
|
26
|
-
return type("_RuleNode", (cls,), {"dataclass": dict, "rules": rules}) # type: ignore
|
|
27
|
-
|
|
28
|
-
def __class_getitem__(cls, items: "ABCRule | tuple[ABCRule, ...]", /) -> typing.Self:
|
|
29
|
-
if not isinstance(items, tuple):
|
|
30
|
-
items = (items,)
|
|
31
|
-
return cls(*items)
|
|
32
|
-
|
|
33
|
-
@staticmethod
|
|
34
|
-
def generate_node_dataclass(cls_: type["RuleChain"]): # noqa: ANN205
|
|
35
|
-
return dataclasses.dataclass(type(cls_.__name__, (object,), dict(cls_.__dict__)))
|
|
36
|
-
|
|
37
|
-
@classmethod
|
|
38
|
-
async def compose(cls, update:
|
|
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
|
-
ctx = Context()
|
|
52
|
-
for rule in cls.rules:
|
|
53
|
-
if not await check_rule(update.api, rule, update, ctx):
|
|
54
|
-
raise ComposeError(f"Rule {rule!r} failed!")
|
|
55
|
-
|
|
56
|
-
try:
|
|
57
|
-
if dataclasses.is_dataclass(cls.dataclass):
|
|
58
|
-
return cls.dataclass(**{k: ctx[k] for k in cls.__annotations__})
|
|
59
|
-
return cls.dataclass(**ctx)
|
|
60
|
-
except Exception as exc:
|
|
61
|
-
raise ComposeError(f"Dataclass validation error: {exc}")
|
|
62
|
-
|
|
63
|
-
@classmethod
|
|
64
|
-
def as_node(cls) -> type[typing.Self]:
|
|
65
|
-
return cls
|
|
66
|
-
|
|
67
|
-
@classmethod
|
|
68
|
-
def
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return False
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
__all__ = ("RuleChain",)
|
|
1
|
+
import dataclasses
|
|
2
|
+
import importlib
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from telegrinder.bot.cute_types.update import UpdateCute
|
|
6
|
+
from telegrinder.bot.dispatch.context import Context
|
|
7
|
+
from telegrinder.node.base import ComposeError, Node
|
|
8
|
+
|
|
9
|
+
if typing.TYPE_CHECKING:
|
|
10
|
+
from telegrinder.bot.dispatch.process import check_rule
|
|
11
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RuleChain(dict[str, typing.Any], Node):
|
|
15
|
+
dataclass: type[typing.Any] = dict
|
|
16
|
+
rules: tuple["ABCRule", ...] = ()
|
|
17
|
+
|
|
18
|
+
def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
19
|
+
super().__init_subclass__(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
if cls.__name__ == "_RuleNode":
|
|
22
|
+
return
|
|
23
|
+
cls.dataclass = cls.generate_node_dataclass(cls)
|
|
24
|
+
|
|
25
|
+
def __new__(cls, *rules: "ABCRule") -> type[Node]:
|
|
26
|
+
return type("_RuleNode", (cls,), {"dataclass": dict, "rules": rules}) # type: ignore
|
|
27
|
+
|
|
28
|
+
def __class_getitem__(cls, items: "ABCRule | tuple[ABCRule, ...]", /) -> typing.Self:
|
|
29
|
+
if not isinstance(items, tuple):
|
|
30
|
+
items = (items,)
|
|
31
|
+
return cls(*items)
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def generate_node_dataclass(cls_: type["RuleChain"]): # noqa: ANN205
|
|
35
|
+
return dataclasses.dataclass(type(cls_.__name__, (object,), dict(cls_.__dict__)))
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
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
|
+
ctx = Context()
|
|
52
|
+
for rule in cls.rules:
|
|
53
|
+
if not await check_rule(update.api, rule, update, ctx):
|
|
54
|
+
raise ComposeError(f"Rule {rule!r} failed!")
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
if dataclasses.is_dataclass(cls.dataclass):
|
|
58
|
+
return cls.dataclass(**{k: ctx[k] for k in cls.__annotations__})
|
|
59
|
+
return cls.dataclass(**ctx)
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
raise ComposeError(f"Dataclass validation error: {exc}")
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def as_node(cls) -> type[typing.Self]:
|
|
65
|
+
return cls
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def is_generator(cls) -> typing.Literal[False]:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
__all__ = ("RuleChain",)
|
telegrinder/node/scope.py
CHANGED
|
@@ -1,44 +1,42 @@
|
|
|
1
|
-
import enum
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
if typing.TYPE_CHECKING:
|
|
5
|
-
from .base import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
__all__ = (
|
|
1
|
+
import enum
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
if typing.TYPE_CHECKING:
|
|
5
|
+
from .base import IsNode
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NodeScope(enum.Enum):
|
|
9
|
+
GLOBAL = enum.auto()
|
|
10
|
+
PER_EVENT = enum.auto()
|
|
11
|
+
PER_CALL = enum.auto()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
PER_EVENT = NodeScope.PER_EVENT
|
|
15
|
+
PER_CALL = NodeScope.PER_CALL
|
|
16
|
+
GLOBAL = NodeScope.GLOBAL
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def per_call[T: IsNode](node: type[T]) -> type[T]:
|
|
20
|
+
setattr(node, "scope", PER_CALL)
|
|
21
|
+
return node
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def per_event[T: IsNode](node: type[T]) -> type[T]:
|
|
25
|
+
setattr(node, "scope", PER_EVENT)
|
|
26
|
+
return node
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def global_node[T: IsNode](node: type[T]) -> type[T]:
|
|
30
|
+
setattr(node, "scope", GLOBAL)
|
|
31
|
+
return node
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__all__ = (
|
|
37
35
|
"GLOBAL",
|
|
38
36
|
"NodeScope",
|
|
39
37
|
"PER_CALL",
|
|
40
38
|
"PER_EVENT",
|
|
41
39
|
"global_node",
|
|
42
40
|
"per_call",
|
|
43
|
-
"per_event",
|
|
44
|
-
)
|
|
41
|
+
"per_event",
|
|
42
|
+
)
|
telegrinder/node/source.py
CHANGED
|
@@ -1,71 +1,87 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
from fntypes.option import Nothing, Option
|
|
5
|
-
|
|
6
|
-
from telegrinder.api.api import API
|
|
7
|
-
from telegrinder.bot.cute_types import ChatJoinRequestCute
|
|
8
|
-
from telegrinder.node.base import ComposeError, DataNode,
|
|
9
|
-
from telegrinder.node.
|
|
10
|
-
from telegrinder.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from fntypes.option import Nothing, Option, Some
|
|
5
|
+
|
|
6
|
+
from telegrinder.api.api import API
|
|
7
|
+
from telegrinder.bot.cute_types import CallbackQueryCute, ChatJoinRequestCute, MessageCute, PreCheckoutQueryCute
|
|
8
|
+
from telegrinder.node.base import ComposeError, DataNode, scalar_node
|
|
9
|
+
from telegrinder.node.polymorphic import Polymorphic, impl
|
|
10
|
+
from telegrinder.types.objects import Chat, Message, User
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
14
|
+
class Source(Polymorphic, DataNode):
|
|
15
|
+
api: API
|
|
16
|
+
from_user: User
|
|
17
|
+
chat: Option[Chat] = dataclasses.field(default_factory=Nothing)
|
|
18
|
+
thread_id: Option[int] = dataclasses.field(default_factory=Nothing)
|
|
19
|
+
|
|
20
|
+
@impl
|
|
21
|
+
def compose_message(cls, message: MessageCute) -> typing.Self:
|
|
22
|
+
return cls(
|
|
23
|
+
api=message.ctx_api,
|
|
24
|
+
from_user=message.from_user,
|
|
25
|
+
chat=Some(message.chat),
|
|
26
|
+
thread_id=message.message_thread_id,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
@impl
|
|
30
|
+
def compose_callback_query(cls, callback_query: CallbackQueryCute) -> typing.Self:
|
|
31
|
+
return cls(
|
|
32
|
+
api=callback_query.ctx_api,
|
|
33
|
+
from_user=callback_query.from_user,
|
|
34
|
+
chat=callback_query.chat,
|
|
35
|
+
thread_id=callback_query.message_thread_id,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@impl
|
|
39
|
+
def compose_chat_join_request(cls, chat_join_request: ChatJoinRequestCute) -> typing.Self:
|
|
40
|
+
return cls(
|
|
41
|
+
api=chat_join_request.ctx_api,
|
|
42
|
+
from_user=chat_join_request.from_user,
|
|
43
|
+
chat=Some(chat_join_request.chat),
|
|
44
|
+
thread_id=Nothing(),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
@impl
|
|
48
|
+
def compose_pre_checkout_query(cls, pre_checkout_query: PreCheckoutQueryCute) -> typing.Self:
|
|
49
|
+
return cls(
|
|
50
|
+
api=pre_checkout_query.ctx_api,
|
|
51
|
+
from_user=pre_checkout_query.from_user,
|
|
52
|
+
chat=Nothing(),
|
|
53
|
+
thread_id=Nothing(),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
async def send(self, text: str, **kwargs: typing.Any) -> Message:
|
|
57
|
+
result = await self.api.send_message(
|
|
58
|
+
chat_id=self.chat.map_or(self.from_user.id, lambda chat: chat.id).unwrap(),
|
|
59
|
+
message_thread_id=self.thread_id.unwrap_or_none(),
|
|
60
|
+
text=text,
|
|
61
|
+
**kwargs,
|
|
62
|
+
)
|
|
63
|
+
return result.unwrap()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@scalar_node
|
|
67
|
+
class ChatSource:
|
|
68
|
+
@classmethod
|
|
69
|
+
def compose(cls, source: Source) -> Chat:
|
|
70
|
+
return source.chat.expect(ComposeError("Source has no chat."))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@scalar_node
|
|
74
|
+
class UserSource:
|
|
75
|
+
@classmethod
|
|
76
|
+
def compose(cls, source: Source) -> User:
|
|
77
|
+
return source.from_user
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@scalar_node
|
|
81
|
+
class UserId:
|
|
82
|
+
@classmethod
|
|
83
|
+
def compose(cls, user: UserSource) -> int:
|
|
84
|
+
return user.id
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
__all__ = ("ChatSource", "Source", "UserId", "UserSource")
|