telegrinder 0.1.dev170__py3-none-any.whl → 0.2.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 +2 -2
- telegrinder/api/__init__.py +1 -2
- telegrinder/api/api.py +15 -6
- telegrinder/api/error.py +2 -1
- telegrinder/api/token.py +36 -0
- telegrinder/bot/__init__.py +12 -6
- telegrinder/bot/bot.py +18 -6
- telegrinder/bot/cute_types/__init__.py +7 -7
- telegrinder/bot/cute_types/base.py +122 -20
- telegrinder/bot/cute_types/callback_query.py +10 -6
- telegrinder/bot/cute_types/chat_join_request.py +4 -5
- telegrinder/bot/cute_types/chat_member_updated.py +4 -6
- telegrinder/bot/cute_types/inline_query.py +3 -4
- telegrinder/bot/cute_types/message.py +32 -21
- telegrinder/bot/cute_types/update.py +51 -4
- telegrinder/bot/cute_types/utils.py +3 -466
- telegrinder/bot/dispatch/__init__.py +10 -11
- telegrinder/bot/dispatch/abc.py +8 -5
- telegrinder/bot/dispatch/context.py +17 -8
- telegrinder/bot/dispatch/dispatch.py +71 -48
- telegrinder/bot/dispatch/handler/__init__.py +3 -3
- telegrinder/bot/dispatch/handler/abc.py +4 -4
- telegrinder/bot/dispatch/handler/func.py +46 -22
- telegrinder/bot/dispatch/handler/message_reply.py +6 -7
- telegrinder/bot/dispatch/middleware/__init__.py +1 -1
- telegrinder/bot/dispatch/middleware/abc.py +2 -2
- telegrinder/bot/dispatch/process.py +38 -19
- telegrinder/bot/dispatch/return_manager/__init__.py +4 -4
- telegrinder/bot/dispatch/return_manager/abc.py +3 -3
- telegrinder/bot/dispatch/return_manager/callback_query.py +1 -2
- telegrinder/bot/dispatch/return_manager/inline_query.py +1 -2
- telegrinder/bot/dispatch/return_manager/message.py +1 -2
- telegrinder/bot/dispatch/view/__init__.py +8 -8
- telegrinder/bot/dispatch/view/abc.py +18 -16
- telegrinder/bot/dispatch/view/box.py +75 -64
- telegrinder/bot/dispatch/view/callback_query.py +1 -2
- telegrinder/bot/dispatch/view/chat_join_request.py +1 -2
- telegrinder/bot/dispatch/view/chat_member.py +16 -2
- telegrinder/bot/dispatch/view/inline_query.py +1 -2
- telegrinder/bot/dispatch/view/message.py +12 -5
- telegrinder/bot/dispatch/view/raw.py +9 -8
- telegrinder/bot/dispatch/waiter_machine/__init__.py +3 -3
- telegrinder/bot/dispatch/waiter_machine/machine.py +12 -8
- telegrinder/bot/dispatch/waiter_machine/middleware.py +1 -1
- telegrinder/bot/dispatch/waiter_machine/short_state.py +4 -3
- telegrinder/bot/polling/abc.py +1 -1
- telegrinder/bot/polling/polling.py +6 -6
- telegrinder/bot/rules/__init__.py +20 -20
- telegrinder/bot/rules/abc.py +57 -43
- telegrinder/bot/rules/adapter/__init__.py +5 -5
- telegrinder/bot/rules/adapter/abc.py +6 -3
- telegrinder/bot/rules/adapter/errors.py +2 -1
- telegrinder/bot/rules/adapter/event.py +28 -13
- telegrinder/bot/rules/adapter/node.py +28 -22
- telegrinder/bot/rules/adapter/raw_update.py +13 -5
- telegrinder/bot/rules/callback_data.py +4 -4
- telegrinder/bot/rules/chat_join.py +4 -4
- telegrinder/bot/rules/command.py +5 -7
- telegrinder/bot/rules/func.py +2 -2
- telegrinder/bot/rules/fuzzy.py +1 -1
- telegrinder/bot/rules/inline.py +3 -3
- telegrinder/bot/rules/integer.py +1 -2
- telegrinder/bot/rules/markup.py +5 -3
- telegrinder/bot/rules/message_entities.py +2 -2
- telegrinder/bot/rules/node.py +2 -2
- telegrinder/bot/rules/regex.py +1 -1
- telegrinder/bot/rules/rule_enum.py +1 -1
- telegrinder/bot/rules/text.py +1 -2
- telegrinder/bot/rules/update.py +1 -2
- telegrinder/bot/scenario/abc.py +2 -2
- telegrinder/bot/scenario/checkbox.py +3 -4
- telegrinder/bot/scenario/choice.py +1 -2
- telegrinder/model.py +89 -45
- telegrinder/modules.py +3 -3
- telegrinder/msgspec_utils.py +85 -57
- telegrinder/node/__init__.py +17 -10
- telegrinder/node/attachment.py +19 -16
- telegrinder/node/base.py +46 -22
- telegrinder/node/callback_query.py +5 -9
- telegrinder/node/command.py +6 -2
- telegrinder/node/composer.py +102 -77
- telegrinder/node/container.py +3 -3
- telegrinder/node/event.py +68 -0
- telegrinder/node/me.py +3 -0
- telegrinder/node/message.py +6 -10
- telegrinder/node/polymorphic.py +15 -10
- telegrinder/node/rule.py +20 -6
- telegrinder/node/scope.py +9 -1
- telegrinder/node/source.py +21 -11
- telegrinder/node/text.py +4 -4
- telegrinder/node/update.py +7 -4
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +59 -0
- telegrinder/tools/__init__.py +2 -2
- telegrinder/tools/buttons.py +5 -10
- telegrinder/tools/error_handler/abc.py +2 -2
- telegrinder/tools/error_handler/error.py +2 -0
- telegrinder/tools/error_handler/error_handler.py +6 -6
- telegrinder/tools/formatting/spec_html_formats.py +10 -10
- telegrinder/tools/global_context/__init__.py +2 -2
- telegrinder/tools/global_context/global_context.py +3 -3
- telegrinder/tools/global_context/telegrinder_ctx.py +4 -4
- telegrinder/tools/keyboard.py +3 -3
- telegrinder/tools/loop_wrapper/loop_wrapper.py +47 -13
- telegrinder/tools/magic.py +96 -18
- telegrinder/types/__init__.py +1 -0
- telegrinder/types/enums.py +2 -0
- telegrinder/types/methods.py +91 -15
- telegrinder/types/objects.py +49 -24
- telegrinder/verification_utils.py +1 -3
- {telegrinder-0.1.dev170.dist-info → telegrinder-0.2.0.dist-info}/METADATA +2 -2
- telegrinder-0.2.0.dist-info/RECORD +145 -0
- telegrinder/api/abc.py +0 -73
- telegrinder-0.1.dev170.dist-info/RECORD +0 -143
- {telegrinder-0.1.dev170.dist-info → telegrinder-0.2.0.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev170.dist-info → telegrinder-0.2.0.dist-info}/WHEEL +0 -0
telegrinder/__init__.py
CHANGED
|
@@ -34,7 +34,7 @@ bot.run_forever()
|
|
|
34
34
|
|
|
35
35
|
import typing
|
|
36
36
|
|
|
37
|
-
from .api import
|
|
37
|
+
from .api import API, APIError, APIResponse, Token
|
|
38
38
|
from .bot import (
|
|
39
39
|
ABCDispatch,
|
|
40
40
|
ABCHandler,
|
|
@@ -122,7 +122,7 @@ Bot: typing.TypeAlias = Telegrinder
|
|
|
122
122
|
|
|
123
123
|
|
|
124
124
|
__all__ = (
|
|
125
|
-
"
|
|
125
|
+
"API",
|
|
126
126
|
"ABCClient",
|
|
127
127
|
"ABCDispatch",
|
|
128
128
|
"ABCErrorHandler",
|
telegrinder/api/__init__.py
CHANGED
telegrinder/api/api.py
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
import typing
|
|
2
|
+
from functools import cached_property
|
|
2
3
|
|
|
3
4
|
import msgspec
|
|
4
5
|
from fntypes.result import Error, Ok, Result
|
|
5
6
|
|
|
7
|
+
from telegrinder.api.error import APIError
|
|
6
8
|
from telegrinder.api.response import APIResponse
|
|
9
|
+
from telegrinder.api.token import Token
|
|
7
10
|
from telegrinder.client import ABCClient, AiohttpClient
|
|
8
11
|
from telegrinder.model import DataConverter, decoder
|
|
9
12
|
from telegrinder.types.methods import APIMethods
|
|
10
13
|
|
|
11
|
-
from .abc import ABCAPI, APIError, Token
|
|
12
|
-
|
|
13
14
|
|
|
14
15
|
def compose_data(
|
|
15
16
|
client: ABCClient,
|
|
16
17
|
data: dict[str, typing.Any],
|
|
17
18
|
files: dict[str, tuple[str, bytes]],
|
|
18
19
|
) -> typing.Any:
|
|
19
|
-
converter = DataConverter(
|
|
20
|
+
converter = DataConverter(_files=files.copy())
|
|
20
21
|
return client.get_form(
|
|
21
22
|
data={k: converter(v) for k, v in data.items()},
|
|
22
23
|
files=converter.files,
|
|
23
24
|
)
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
class API(
|
|
27
|
-
"""Bot API with available API methods."""
|
|
27
|
+
class API(APIMethods):
|
|
28
|
+
"""Bot API with available API methods and http client."""
|
|
28
29
|
|
|
29
30
|
API_URL = "https://api.telegram.org/"
|
|
31
|
+
API_FILE_URL = "https://api.telegram.org/file/"
|
|
30
32
|
|
|
31
33
|
def __init__(self, token: Token, *, http: ABCClient | None = None) -> None:
|
|
32
34
|
self.token = token
|
|
@@ -40,7 +42,7 @@ class API(ABCAPI, APIMethods):
|
|
|
40
42
|
self.http,
|
|
41
43
|
)
|
|
42
44
|
|
|
43
|
-
@
|
|
45
|
+
@cached_property
|
|
44
46
|
def id(self) -> int:
|
|
45
47
|
return self.token.bot_id
|
|
46
48
|
|
|
@@ -48,6 +50,13 @@ class API(ABCAPI, APIMethods):
|
|
|
48
50
|
def request_url(self) -> str:
|
|
49
51
|
return self.API_URL + f"bot{self.token}/"
|
|
50
52
|
|
|
53
|
+
@property
|
|
54
|
+
def request_file_url(self) -> str:
|
|
55
|
+
return self.API_FILE_URL + f"bot{self.token}/"
|
|
56
|
+
|
|
57
|
+
async def download_file(self, file_path: str) -> bytes:
|
|
58
|
+
return await self.http.request_content(f"{self.request_file_url}/{file_path}")
|
|
59
|
+
|
|
51
60
|
async def request(
|
|
52
61
|
self,
|
|
53
62
|
method: str,
|
telegrinder/api/error.py
CHANGED
telegrinder/api/token.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import typing
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
|
|
5
|
+
from envparse import env
|
|
6
|
+
|
|
7
|
+
from .error import InvalidTokenError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Token(str):
|
|
11
|
+
def __new__(cls, token: str) -> typing.Self:
|
|
12
|
+
if token.count(":") != 1 or not token.split(":")[0].isdigit():
|
|
13
|
+
raise InvalidTokenError("Invalid token, it should look like this '123:ABC'.")
|
|
14
|
+
return super().__new__(cls, token)
|
|
15
|
+
|
|
16
|
+
def __repr__(self) -> str:
|
|
17
|
+
return f"<Token: {self.bot_id}:{''.join(self.split(':')[-1])[:6]}...>"
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def from_env(
|
|
21
|
+
cls,
|
|
22
|
+
var_name: str = "BOT_TOKEN",
|
|
23
|
+
*,
|
|
24
|
+
is_read: bool = False,
|
|
25
|
+
path_to_envfile: str | pathlib.Path | None = None,
|
|
26
|
+
) -> typing.Self:
|
|
27
|
+
if not is_read:
|
|
28
|
+
env.read_envfile(path_to_envfile)
|
|
29
|
+
return cls(env.str(var_name))
|
|
30
|
+
|
|
31
|
+
@cached_property
|
|
32
|
+
def bot_id(self) -> int:
|
|
33
|
+
return int(self.split(":")[0])
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = ("Token",)
|
telegrinder/bot/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from .bot import Telegrinder
|
|
2
|
-
from .cute_types import (
|
|
1
|
+
from telegrinder.bot.bot import Telegrinder
|
|
2
|
+
from telegrinder.bot.cute_types import (
|
|
3
3
|
BaseCute,
|
|
4
4
|
CallbackQueryCute,
|
|
5
5
|
ChatJoinRequestCute,
|
|
@@ -8,7 +8,7 @@ from .cute_types import (
|
|
|
8
8
|
MessageCute,
|
|
9
9
|
UpdateCute,
|
|
10
10
|
)
|
|
11
|
-
from .dispatch import (
|
|
11
|
+
from telegrinder.bot.dispatch import (
|
|
12
12
|
ABCDispatch,
|
|
13
13
|
ABCHandler,
|
|
14
14
|
ABCMiddleware,
|
|
@@ -37,9 +37,15 @@ from .dispatch import (
|
|
|
37
37
|
clear_wm_storage_worker,
|
|
38
38
|
register_manager,
|
|
39
39
|
)
|
|
40
|
-
from .polling import ABCPolling, Polling
|
|
41
|
-
from .rules import
|
|
42
|
-
|
|
40
|
+
from telegrinder.bot.polling import ABCPolling, Polling
|
|
41
|
+
from telegrinder.bot.rules import (
|
|
42
|
+
ABCRule,
|
|
43
|
+
CallbackQueryRule,
|
|
44
|
+
ChatJoinRequestRule,
|
|
45
|
+
InlineQueryRule,
|
|
46
|
+
MessageRule,
|
|
47
|
+
)
|
|
48
|
+
from telegrinder.bot.scenario import ABCScenario, Checkbox, Choice
|
|
43
49
|
|
|
44
50
|
__all__ = (
|
|
45
51
|
"ABCDispatch",
|
telegrinder/bot/bot.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import typing_extensions as typing
|
|
2
2
|
|
|
3
|
-
from telegrinder.api import API
|
|
4
|
-
from telegrinder.bot.dispatch import ABCDispatch
|
|
5
|
-
from telegrinder.bot.
|
|
3
|
+
from telegrinder.api.api import API
|
|
4
|
+
from telegrinder.bot.dispatch.abc import ABCDispatch
|
|
5
|
+
from telegrinder.bot.dispatch.dispatch import Dispatch
|
|
6
|
+
from telegrinder.bot.polling.abc import ABCPolling
|
|
7
|
+
from telegrinder.bot.polling.polling import Polling
|
|
6
8
|
from telegrinder.modules import logger
|
|
7
|
-
from telegrinder.tools.loop_wrapper import ABCLoopWrapper
|
|
9
|
+
from telegrinder.tools.loop_wrapper import ABCLoopWrapper
|
|
10
|
+
from telegrinder.tools.loop_wrapper.loop_wrapper import LoopWrapper
|
|
8
11
|
|
|
9
12
|
DispatchT = typing.TypeVar("DispatchT", bound=ABCDispatch, default=Dispatch)
|
|
10
13
|
PollingT = typing.TypeVar("PollingT", bound=ABCPolling, default=Polling)
|
|
@@ -43,7 +46,12 @@ class Telegrinder(typing.Generic[DispatchT, PollingT, LoopWrapperT]):
|
|
|
43
46
|
return
|
|
44
47
|
await self.api.delete_webhook()
|
|
45
48
|
|
|
46
|
-
async def run_polling(
|
|
49
|
+
async def run_polling(
|
|
50
|
+
self,
|
|
51
|
+
*,
|
|
52
|
+
offset: int = 0,
|
|
53
|
+
skip_updates: bool = False,
|
|
54
|
+
) -> None:
|
|
47
55
|
if skip_updates:
|
|
48
56
|
logger.debug("Dropping pending updates")
|
|
49
57
|
await self.reset_webhook()
|
|
@@ -52,7 +60,11 @@ class Telegrinder(typing.Generic[DispatchT, PollingT, LoopWrapperT]):
|
|
|
52
60
|
|
|
53
61
|
async for updates in self.polling.listen():
|
|
54
62
|
for update in updates:
|
|
55
|
-
logger.debug(
|
|
63
|
+
logger.debug(
|
|
64
|
+
"Received update (update_id={}, update_type={!r})",
|
|
65
|
+
update.update_id,
|
|
66
|
+
update.update_type.name,
|
|
67
|
+
)
|
|
56
68
|
self.loop_wrapper.add_task(self.dispatch.feed(update, self.api))
|
|
57
69
|
|
|
58
70
|
def run_forever(self, *, offset: int = 0, skip_updates: bool = False) -> None:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
from .base import BaseCute
|
|
2
|
-
from .callback_query import CallbackQueryCute
|
|
3
|
-
from .chat_join_request import ChatJoinRequestCute
|
|
4
|
-
from .chat_member_updated import ChatMemberUpdatedCute
|
|
5
|
-
from .inline_query import InlineQueryCute
|
|
6
|
-
from .message import MessageCute
|
|
7
|
-
from .update import UpdateCute
|
|
1
|
+
from telegrinder.bot.cute_types.base import BaseCute
|
|
2
|
+
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
3
|
+
from telegrinder.bot.cute_types.chat_join_request import ChatJoinRequestCute
|
|
4
|
+
from telegrinder.bot.cute_types.chat_member_updated import ChatMemberUpdatedCute
|
|
5
|
+
from telegrinder.bot.cute_types.inline_query import InlineQueryCute
|
|
6
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
7
|
+
from telegrinder.bot.cute_types.update import UpdateCute
|
|
8
8
|
|
|
9
9
|
__all__ = (
|
|
10
10
|
"BaseCute",
|
|
@@ -3,14 +3,16 @@ import inspect
|
|
|
3
3
|
import typing
|
|
4
4
|
from functools import wraps
|
|
5
5
|
|
|
6
|
+
import typing_extensions
|
|
6
7
|
from fntypes.result import Result
|
|
7
8
|
|
|
8
|
-
from telegrinder.api import
|
|
9
|
+
from telegrinder.api.api import API
|
|
9
10
|
from telegrinder.model import Model, get_params
|
|
10
11
|
|
|
11
12
|
F = typing.TypeVar("F", bound=typing.Callable[..., typing.Any])
|
|
12
13
|
Cute = typing.TypeVar("Cute", bound="BaseCute")
|
|
13
|
-
Update =
|
|
14
|
+
Update = typing_extensions.TypeVar("Update", bound=Model)
|
|
15
|
+
CtxAPI = typing_extensions.TypeVar("CtxAPI", bound=API, default=API)
|
|
14
16
|
|
|
15
17
|
Executor: typing.TypeAlias = typing.Callable[
|
|
16
18
|
[Cute, str, dict[str, typing.Any]],
|
|
@@ -19,31 +21,124 @@ Executor: typing.TypeAlias = typing.Callable[
|
|
|
19
21
|
|
|
20
22
|
if typing.TYPE_CHECKING:
|
|
21
23
|
|
|
22
|
-
class BaseCute(Model, typing.Generic[Update]):
|
|
23
|
-
api:
|
|
24
|
+
class BaseCute(Model, typing.Generic[Update, CtxAPI]):
|
|
25
|
+
api: API
|
|
24
26
|
|
|
25
27
|
@classmethod
|
|
26
|
-
def from_update(cls, update: Update, bound_api:
|
|
28
|
+
def from_update(cls, update: Update, bound_api: API) -> typing.Self: ...
|
|
27
29
|
|
|
28
30
|
@property
|
|
29
|
-
def ctx_api(self) ->
|
|
31
|
+
def ctx_api(self) -> CtxAPI: ...
|
|
32
|
+
|
|
33
|
+
def to_dict(
|
|
34
|
+
self,
|
|
35
|
+
*,
|
|
36
|
+
exclude_fields: set[str] | None = None,
|
|
37
|
+
) -> dict[str, typing.Any]:
|
|
38
|
+
"""
|
|
39
|
+
:param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
|
|
40
|
+
:return: A dictionary representation of this cute model.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
def to_full_dict(
|
|
46
|
+
self,
|
|
47
|
+
*,
|
|
48
|
+
exclude_fields: set[str] | None = None,
|
|
49
|
+
) -> dict[str, typing.Any]:
|
|
50
|
+
"""
|
|
51
|
+
:param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
|
|
52
|
+
:return: A dictionary representation of this model including all models, structs, custom types.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
...
|
|
30
56
|
|
|
31
57
|
else:
|
|
58
|
+
from fntypes.co import Some, Variative
|
|
59
|
+
|
|
60
|
+
from telegrinder.msgspec_utils import Option, decoder
|
|
61
|
+
from telegrinder.msgspec_utils import get_class_annotations as _get_class_annotations
|
|
62
|
+
|
|
63
|
+
def _get_cute_from_args(args):
|
|
64
|
+
for hint in args:
|
|
65
|
+
if not isinstance(hint, type):
|
|
66
|
+
hint = typing.get_origin(hint) or hint
|
|
67
|
+
if not isinstance(hint, type):
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
if hint in (Variative, Some, Option):
|
|
71
|
+
return _get_cute_from_args(typing.get_args(hint))
|
|
72
|
+
if issubclass(hint, BaseCute):
|
|
73
|
+
return hint
|
|
74
|
+
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def _get_cute_annotations(annotations):
|
|
78
|
+
cute_annotations = {}
|
|
79
|
+
|
|
80
|
+
for key, hint in annotations.items():
|
|
81
|
+
if not isinstance(hint, type):
|
|
82
|
+
if (val := _get_cute_from_args(typing.get_args(hint))) is not None:
|
|
83
|
+
cute_annotations[key] = val
|
|
84
|
+
|
|
85
|
+
elif issubclass(hint, BaseCute):
|
|
86
|
+
cute_annotations[key] = hint
|
|
87
|
+
|
|
88
|
+
return cute_annotations
|
|
89
|
+
|
|
90
|
+
def _get_value(value):
|
|
91
|
+
while isinstance(value, Variative | Some):
|
|
92
|
+
if isinstance(value, Variative):
|
|
93
|
+
value = value.v
|
|
94
|
+
if isinstance(value, Some):
|
|
95
|
+
value = value.value
|
|
96
|
+
return value
|
|
97
|
+
|
|
98
|
+
class BaseCute(typing.Generic[Update, CtxAPI]):
|
|
99
|
+
def __init_subclass__(cls, *args, **kwargs):
|
|
100
|
+
super().__init_subclass__(*args, **kwargs)
|
|
101
|
+
|
|
102
|
+
if not cls.__bases__ or not issubclass(cls.__bases__[0], BaseCute):
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
cls.__is_solved_annotations__ = False
|
|
106
|
+
cls.__cute_annotations__ = None
|
|
32
107
|
|
|
33
|
-
class BaseCute(typing.Generic[Update]):
|
|
34
108
|
@classmethod
|
|
35
109
|
def from_update(cls, update, bound_api):
|
|
36
|
-
|
|
110
|
+
if not cls.__is_solved_annotations__:
|
|
111
|
+
cls.__is_solved_annotations__ = True
|
|
112
|
+
cls.__annotations__ = _get_class_annotations(cls)
|
|
113
|
+
|
|
114
|
+
if cls.__cute_annotations__ is None:
|
|
115
|
+
cls.__cute_annotations__ = _get_cute_annotations(cls.__annotations__)
|
|
116
|
+
|
|
117
|
+
return cls(
|
|
118
|
+
**{
|
|
119
|
+
field: decoder.convert(
|
|
120
|
+
cls.__cute_annotations__[field].from_update(_get_value(value), bound_api=bound_api),
|
|
121
|
+
type=cls.__annotations__[field],
|
|
122
|
+
)
|
|
123
|
+
if field in cls.__cute_annotations__ and value
|
|
124
|
+
else value
|
|
125
|
+
for field, value in update.to_dict().items()
|
|
126
|
+
},
|
|
127
|
+
api=bound_api,
|
|
128
|
+
)
|
|
37
129
|
|
|
38
130
|
@property
|
|
39
131
|
def ctx_api(self):
|
|
40
|
-
assert isinstance(self.api, API)
|
|
41
132
|
return self.api
|
|
42
133
|
|
|
43
134
|
def to_dict(self, *, exclude_fields=None):
|
|
44
135
|
exclude_fields = exclude_fields or set()
|
|
45
136
|
return super().to_dict(exclude_fields={"api"} | exclude_fields)
|
|
46
137
|
|
|
138
|
+
def to_full_dict(self, *, exclude_fields=None):
|
|
139
|
+
exclude_fields = exclude_fields or set()
|
|
140
|
+
return super().to_full_dict(exclude_fields={"api"} | exclude_fields)
|
|
141
|
+
|
|
47
142
|
|
|
48
143
|
def compose_method_params(
|
|
49
144
|
params: dict[str, typing.Any],
|
|
@@ -57,12 +152,12 @@ def compose_method_params(
|
|
|
57
152
|
:param params: Method params.
|
|
58
153
|
:param update: Update object.
|
|
59
154
|
:param default_params: Default params. \
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
tuple[1] attribute name to be
|
|
155
|
+
(`str`) - Attribute name to be get from `update` if param is undefined. \
|
|
156
|
+
(`tuple[str, str]`): tuple[0] - Parameter name to be set in `params`, \
|
|
157
|
+
tuple[1] - attribute name to be get from `update`.
|
|
63
158
|
:param validators: Validators mapping (`str, Callable`), key - `Parameter name` \
|
|
64
159
|
for which the validator will be applied, value - `Validator`, if returned `True` \
|
|
65
|
-
parameter will be set, otherwise will not
|
|
160
|
+
parameter will be set, otherwise will not.
|
|
66
161
|
:return: Composed params.
|
|
67
162
|
"""
|
|
68
163
|
|
|
@@ -79,8 +174,6 @@ def compose_method_params(
|
|
|
79
174
|
return params
|
|
80
175
|
|
|
81
176
|
|
|
82
|
-
# NOTE: implement parser on ast for methods decorated this decorator
|
|
83
|
-
# to support updates to the schema Bot API.
|
|
84
177
|
def shortcut(
|
|
85
178
|
method_name: str,
|
|
86
179
|
*,
|
|
@@ -96,7 +189,15 @@ def shortcut(
|
|
|
96
189
|
) -> typing.Any:
|
|
97
190
|
if executor is None:
|
|
98
191
|
return await func(self, *args, **kwargs)
|
|
99
|
-
|
|
192
|
+
|
|
193
|
+
if not hasattr(func, "_signature_params"):
|
|
194
|
+
setattr(
|
|
195
|
+
func,
|
|
196
|
+
"_signature_params",
|
|
197
|
+
{k: p for k, p in inspect.signature(func).parameters.items() if k != "self"},
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
signature_params: dict[str, inspect.Parameter] = getattr(func, "_signature_params")
|
|
100
201
|
params: dict[str, typing.Any] = {}
|
|
101
202
|
index = 0
|
|
102
203
|
|
|
@@ -105,9 +206,11 @@ def shortcut(
|
|
|
105
206
|
params[k] = args[index]
|
|
106
207
|
index += 1
|
|
107
208
|
continue
|
|
209
|
+
|
|
108
210
|
if p.kind in (p.VAR_KEYWORD, p.VAR_POSITIONAL):
|
|
109
211
|
params[k] = kwargs.copy() if p.kind is p.VAR_KEYWORD else args[index:]
|
|
110
212
|
continue
|
|
213
|
+
|
|
111
214
|
params[k] = kwargs.pop(k, p.default) if p.default is not p.empty else kwargs.pop(k)
|
|
112
215
|
|
|
113
216
|
return await executor(self, method_name, get_params(params))
|
|
@@ -122,12 +225,11 @@ def shortcut(
|
|
|
122
225
|
return wrapper
|
|
123
226
|
|
|
124
227
|
|
|
125
|
-
@dataclasses.dataclass
|
|
228
|
+
@dataclasses.dataclass(slots=True, frozen=True)
|
|
126
229
|
class Shortcut:
|
|
127
230
|
method_name: str
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
custom_params: set[str] = dataclasses.field(default_factory=lambda: set())
|
|
231
|
+
executor: Executor | None = dataclasses.field(default=None, kw_only=True)
|
|
232
|
+
custom_params: set[str] = dataclasses.field(default_factory=lambda: set(), kw_only=True)
|
|
131
233
|
|
|
132
234
|
|
|
133
235
|
__all__ = ("BaseCute", "Shortcut", "compose_method_params", "shortcut")
|
|
@@ -4,12 +4,15 @@ from contextlib import suppress
|
|
|
4
4
|
import msgspec
|
|
5
5
|
from fntypes.co import Nothing, Result, Some, Variative, unwrapping
|
|
6
6
|
|
|
7
|
-
from telegrinder.api import
|
|
7
|
+
from telegrinder.api import API, APIError
|
|
8
|
+
from telegrinder.bot.cute_types.base import BaseCute, compose_method_params, shortcut
|
|
9
|
+
from telegrinder.bot.cute_types.message import MediaType, MessageCute, ReplyMarkup, execute_method_edit
|
|
8
10
|
from telegrinder.model import get_params
|
|
9
11
|
from telegrinder.msgspec_utils import Option, decoder
|
|
10
|
-
from telegrinder.types import (
|
|
12
|
+
from telegrinder.types.objects import (
|
|
11
13
|
CallbackQuery,
|
|
12
14
|
Chat,
|
|
15
|
+
InaccessibleMessage,
|
|
13
16
|
InlineKeyboardMarkup,
|
|
14
17
|
InputFile,
|
|
15
18
|
InputMedia,
|
|
@@ -20,12 +23,13 @@ from telegrinder.types import (
|
|
|
20
23
|
User,
|
|
21
24
|
)
|
|
22
25
|
|
|
23
|
-
from .base import BaseCute, compose_method_params, shortcut
|
|
24
|
-
from .message import MediaType, MessageCute, ReplyMarkup, execute_method_edit
|
|
25
26
|
|
|
27
|
+
class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
|
|
28
|
+
api: API
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
message: Option[Variative[MessageCute, InaccessibleMessage]] = Nothing()
|
|
31
|
+
"""Optional. Message sent by the bot with the callback button that originated
|
|
32
|
+
the query."""
|
|
29
33
|
|
|
30
34
|
@property
|
|
31
35
|
def from_user(self) -> User:
|
|
@@ -2,15 +2,14 @@ import typing
|
|
|
2
2
|
|
|
3
3
|
from fntypes.result import Result
|
|
4
4
|
|
|
5
|
-
from telegrinder.api
|
|
5
|
+
from telegrinder.api import API, APIError
|
|
6
|
+
from telegrinder.bot.cute_types.base import BaseCute, shortcut
|
|
7
|
+
from telegrinder.bot.cute_types.chat_member_updated import ChatMemberShortcuts, chat_member_interaction
|
|
6
8
|
from telegrinder.types.objects import ChatJoinRequest, User
|
|
7
9
|
|
|
8
|
-
from .base import BaseCute, shortcut
|
|
9
|
-
from .chat_member_updated import ChatMemberShortcuts, chat_member_interaction
|
|
10
|
-
|
|
11
10
|
|
|
12
11
|
class ChatJoinRequestCute(BaseCute[ChatJoinRequest], ChatJoinRequest, ChatMemberShortcuts, kw_only=True):
|
|
13
|
-
api:
|
|
12
|
+
api: API
|
|
14
13
|
|
|
15
14
|
@property
|
|
16
15
|
def from_user(self) -> User:
|
|
@@ -3,13 +3,11 @@ from datetime import datetime
|
|
|
3
3
|
|
|
4
4
|
from fntypes.result import Result
|
|
5
5
|
|
|
6
|
-
from telegrinder.api import
|
|
6
|
+
from telegrinder.api import API, APIError
|
|
7
|
+
from telegrinder.bot.cute_types.base import BaseCute, compose_method_params, shortcut
|
|
7
8
|
from telegrinder.model import get_params
|
|
8
9
|
from telegrinder.types.objects import ChatMemberUpdated, ChatPermissions, User
|
|
9
10
|
|
|
10
|
-
from .base import BaseCute, compose_method_params, shortcut
|
|
11
|
-
from .utils import compose_chat_permissions
|
|
12
|
-
|
|
13
11
|
|
|
14
12
|
async def chat_member_interaction(
|
|
15
13
|
update: BaseCute[typing.Any],
|
|
@@ -17,7 +15,7 @@ async def chat_member_interaction(
|
|
|
17
15
|
params: dict[str, typing.Any],
|
|
18
16
|
) -> Result[typing.Any, APIError]:
|
|
19
17
|
if isinstance(params.get("permissions"), dict):
|
|
20
|
-
params["permissions"] =
|
|
18
|
+
params["permissions"] = ChatPermissions(**params["permissions"])
|
|
21
19
|
params = compose_method_params(
|
|
22
20
|
get_params(locals()),
|
|
23
21
|
update,
|
|
@@ -235,7 +233,7 @@ class ChatMemberShortcuts:
|
|
|
235
233
|
class ChatMemberUpdatedCute(
|
|
236
234
|
BaseCute[ChatMemberUpdated], ChatMemberUpdated, ChatMemberShortcuts, kw_only=True
|
|
237
235
|
):
|
|
238
|
-
api:
|
|
236
|
+
api: API
|
|
239
237
|
|
|
240
238
|
@property
|
|
241
239
|
def from_user(self) -> User:
|
|
@@ -2,7 +2,8 @@ import typing
|
|
|
2
2
|
|
|
3
3
|
from fntypes.result import Result
|
|
4
4
|
|
|
5
|
-
from telegrinder.api import
|
|
5
|
+
from telegrinder.api import API, APIError
|
|
6
|
+
from telegrinder.bot.cute_types.base import BaseCute, compose_method_params, shortcut
|
|
6
7
|
from telegrinder.model import get_params
|
|
7
8
|
from telegrinder.types import (
|
|
8
9
|
InlineQuery,
|
|
@@ -11,11 +12,9 @@ from telegrinder.types import (
|
|
|
11
12
|
User,
|
|
12
13
|
)
|
|
13
14
|
|
|
14
|
-
from .base import BaseCute, compose_method_params, shortcut
|
|
15
|
-
|
|
16
15
|
|
|
17
16
|
class InlineQueryCute(BaseCute[InlineQuery], InlineQuery, kw_only=True):
|
|
18
|
-
api:
|
|
17
|
+
api: API
|
|
19
18
|
|
|
20
19
|
@property
|
|
21
20
|
def from_user(self) -> User:
|