telegrinder 0.1.dev164__py3-none-any.whl → 0.1.dev166__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 +58 -0
- telegrinder/api/abc.py +1 -1
- telegrinder/api/api.py +8 -6
- telegrinder/api/error.py +2 -3
- telegrinder/bot/__init__.py +14 -0
- telegrinder/bot/bot.py +13 -2
- telegrinder/bot/cute_types/__init__.py +4 -0
- telegrinder/bot/cute_types/base.py +22 -13
- telegrinder/bot/cute_types/chat_join_request.py +63 -0
- telegrinder/bot/cute_types/chat_member_updated.py +244 -0
- telegrinder/bot/cute_types/message.py +33 -6
- telegrinder/bot/cute_types/update.py +5 -4
- telegrinder/bot/cute_types/utils.py +39 -17
- telegrinder/bot/dispatch/__init__.py +9 -1
- telegrinder/bot/dispatch/composition.py +10 -8
- telegrinder/bot/dispatch/context.py +9 -10
- telegrinder/bot/dispatch/dispatch.py +29 -19
- telegrinder/bot/dispatch/handler/abc.py +1 -0
- telegrinder/bot/dispatch/handler/func.py +29 -6
- telegrinder/bot/dispatch/handler/message_reply.py +2 -3
- telegrinder/bot/dispatch/middleware/abc.py +2 -4
- telegrinder/bot/dispatch/process.py +4 -3
- telegrinder/bot/dispatch/return_manager/__init__.py +1 -1
- telegrinder/bot/dispatch/return_manager/abc.py +39 -21
- telegrinder/bot/dispatch/return_manager/callback_query.py +4 -2
- telegrinder/bot/dispatch/return_manager/inline_query.py +4 -2
- telegrinder/bot/dispatch/return_manager/message.py +12 -6
- telegrinder/bot/dispatch/view/__init__.py +8 -2
- telegrinder/bot/dispatch/view/abc.py +26 -20
- telegrinder/bot/dispatch/view/box.py +72 -1
- telegrinder/bot/dispatch/view/callback_query.py +1 -3
- telegrinder/bot/dispatch/view/chat_join_request.py +17 -0
- telegrinder/bot/dispatch/view/chat_member.py +26 -0
- telegrinder/bot/dispatch/view/message.py +23 -1
- telegrinder/bot/dispatch/view/raw.py +116 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -1
- telegrinder/bot/dispatch/waiter_machine/machine.py +73 -19
- telegrinder/bot/dispatch/waiter_machine/middleware.py +3 -3
- telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -3
- telegrinder/bot/polling/polling.py +17 -1
- telegrinder/bot/rules/__init__.py +20 -12
- telegrinder/bot/rules/abc.py +0 -9
- telegrinder/bot/rules/adapter/event.py +31 -22
- telegrinder/bot/rules/callback_data.py +15 -20
- telegrinder/bot/rules/chat_join.py +47 -0
- telegrinder/bot/rules/enum_text.py +7 -2
- telegrinder/bot/rules/inline.py +3 -3
- telegrinder/bot/rules/is_from.py +36 -50
- telegrinder/bot/rules/markup.py +1 -1
- telegrinder/bot/rules/message.py +17 -0
- telegrinder/bot/rules/message_entities.py +1 -1
- telegrinder/bot/rules/start.py +6 -4
- telegrinder/bot/rules/text.py +2 -1
- telegrinder/bot/rules/update.py +16 -0
- telegrinder/bot/scenario/checkbox.py +9 -7
- telegrinder/client/aiohttp.py +4 -4
- telegrinder/model.py +43 -19
- telegrinder/modules.py +16 -32
- telegrinder/msgspec_utils.py +48 -36
- telegrinder/node/__init__.py +12 -12
- telegrinder/node/attachment.py +15 -5
- telegrinder/node/base.py +24 -16
- telegrinder/node/composer.py +8 -6
- telegrinder/node/container.py +1 -1
- telegrinder/node/rule.py +4 -4
- telegrinder/node/source.py +4 -2
- telegrinder/node/tools/generator.py +1 -1
- telegrinder/tools/__init__.py +2 -1
- telegrinder/tools/error_handler/abc.py +4 -3
- telegrinder/tools/error_handler/error_handler.py +22 -16
- telegrinder/tools/formatting/html.py +20 -18
- telegrinder/tools/formatting/links.py +12 -6
- telegrinder/tools/formatting/spec_html_formats.py +5 -6
- telegrinder/tools/global_context/abc.py +5 -1
- telegrinder/tools/global_context/global_context.py +2 -2
- telegrinder/tools/global_context/telegrinder_ctx.py +1 -1
- telegrinder/tools/i18n/base.py +4 -3
- telegrinder/tools/i18n/simple.py +1 -3
- telegrinder/tools/keyboard.py +1 -1
- telegrinder/tools/loop_wrapper/__init__.py +2 -2
- telegrinder/tools/loop_wrapper/abc.py +1 -4
- telegrinder/tools/loop_wrapper/loop_wrapper.py +90 -36
- telegrinder/tools/magic.py +1 -1
- telegrinder/types/__init__.py +206 -0
- telegrinder/types/enums.py +34 -0
- telegrinder/types/methods.py +52 -47
- telegrinder/types/objects.py +531 -88
- telegrinder/verification_utils.py +33 -0
- {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/METADATA +1 -1
- telegrinder-0.1.dev166.dist-info/RECORD +136 -0
- telegrinder-0.1.dev164.dist-info/RECORD +0 -127
- {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/WHEEL +0 -0
telegrinder/bot/rules/is_from.py
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
|
|
3
|
+
from fntypes.co import Nothing, Some
|
|
4
|
+
|
|
3
5
|
from telegrinder.bot.cute_types.base import BaseCute
|
|
6
|
+
from telegrinder.bot.cute_types.update import UpdateCute
|
|
4
7
|
from telegrinder.bot.dispatch.context import Context
|
|
5
8
|
from telegrinder.msgspec_utils import Option
|
|
6
9
|
from telegrinder.types.enums import ChatType, DiceEmoji
|
|
7
10
|
from telegrinder.types.objects import User
|
|
8
11
|
|
|
9
|
-
from .abc import ABCRule, Message
|
|
12
|
+
from .abc import ABCRule, Message
|
|
13
|
+
from .message import MessageRule
|
|
10
14
|
|
|
11
15
|
T = typing.TypeVar("T", bound=BaseCute)
|
|
12
16
|
|
|
13
17
|
|
|
18
|
+
def get_from_user(obj: typing.Any) -> User:
|
|
19
|
+
assert isinstance(obj, FromUserProto)
|
|
20
|
+
return obj.from_.unwrap() if isinstance(obj.from_, Some | Nothing) else obj.from_
|
|
21
|
+
|
|
22
|
+
|
|
14
23
|
@typing.runtime_checkable
|
|
15
24
|
class FromUserProto(typing.Protocol):
|
|
16
25
|
from_: User | Option[User]
|
|
17
26
|
|
|
18
27
|
|
|
19
28
|
class HasFrom(ABCRule[T]):
|
|
20
|
-
async def check(self, event:
|
|
21
|
-
|
|
29
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
30
|
+
event_model = event.incoming_update.unwrap()
|
|
31
|
+
return isinstance(event_model, FromUserProto) and bool(event_model.from_)
|
|
22
32
|
|
|
23
33
|
|
|
24
34
|
class HasDice(MessageRule):
|
|
@@ -49,29 +59,30 @@ class IsSticker(MessageRule):
|
|
|
49
59
|
return bool(message.sticker)
|
|
50
60
|
|
|
51
61
|
|
|
52
|
-
class IsBot(
|
|
53
|
-
async def check(self,
|
|
54
|
-
return
|
|
62
|
+
class IsBot(ABCRule[T], requires=[HasFrom()]):
|
|
63
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
64
|
+
return get_from_user(event.incoming_update.unwrap()).is_bot
|
|
55
65
|
|
|
56
66
|
|
|
57
|
-
class IsUser(
|
|
58
|
-
async def check(self,
|
|
59
|
-
return not
|
|
67
|
+
class IsUser(ABCRule[T], requires=[HasFrom()]):
|
|
68
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
69
|
+
return not get_from_user(event.incoming_update.unwrap()).is_bot
|
|
60
70
|
|
|
61
71
|
|
|
62
|
-
class IsPremium(
|
|
63
|
-
async def check(self,
|
|
64
|
-
return
|
|
72
|
+
class IsPremium(ABCRule[T], requires=[HasFrom()]):
|
|
73
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
74
|
+
return get_from_user(event.incoming_update.unwrap()).is_premium.unwrap_or(False)
|
|
65
75
|
|
|
66
76
|
|
|
67
|
-
class IsLanguageCode(
|
|
77
|
+
class IsLanguageCode(ABCRule[T], requires=[HasFrom()]):
|
|
68
78
|
def __init__(self, lang_codes: str | list[str], /) -> None:
|
|
69
79
|
self.lang_codes = [lang_codes] if isinstance(lang_codes, str) else lang_codes
|
|
70
80
|
|
|
71
|
-
async def check(self,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
82
|
+
return (
|
|
83
|
+
get_from_user(event.incoming_update.unwrap())
|
|
84
|
+
.language_code.unwrap_or_none() in self.lang_codes
|
|
85
|
+
)
|
|
75
86
|
|
|
76
87
|
|
|
77
88
|
class IsForum(MessageRule):
|
|
@@ -79,12 +90,12 @@ class IsForum(MessageRule):
|
|
|
79
90
|
return message.chat.is_forum.unwrap_or(False)
|
|
80
91
|
|
|
81
92
|
|
|
82
|
-
class IsUserId(
|
|
93
|
+
class IsUserId(ABCRule[T], requires=[HasFrom()]):
|
|
83
94
|
def __init__(self, user_ids: int | list[int], /) -> None:
|
|
84
95
|
self.user_ids = [user_ids] if isinstance(user_ids, int) else user_ids
|
|
85
96
|
|
|
86
|
-
async def check(self,
|
|
87
|
-
return
|
|
97
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
98
|
+
return get_from_user(event.incoming_update.unwrap()).id in self.user_ids
|
|
88
99
|
|
|
89
100
|
|
|
90
101
|
class IsChatId(MessageRule):
|
|
@@ -115,44 +126,19 @@ class IsChat(MessageRule):
|
|
|
115
126
|
return message.chat.type in (ChatType.GROUP, ChatType.SUPERGROUP)
|
|
116
127
|
|
|
117
128
|
|
|
118
|
-
class
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
class IsDartDice(MessageRule, requires=[HasDice()]):
|
|
124
|
-
async def check(self, message: Message, ctx: Context) -> bool:
|
|
125
|
-
return message.dice.unwrap().emoji == DiceEmoji.DART
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
class IsBasketballDice(MessageRule, requires=[HasDice()]):
|
|
129
|
-
async def check(self, message: Message, ctx: Context) -> bool:
|
|
130
|
-
return message.dice.unwrap().emoji == DiceEmoji.BASKETBALL
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
class IsFootballDice(MessageRule, requires=[HasDice()]):
|
|
134
|
-
async def check(self, message: Message, ctx: Context) -> bool:
|
|
135
|
-
return message.dice.unwrap().emoji == DiceEmoji.FOOTBALL
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
class IsSlotMachineDice(MessageRule, requires=[HasDice()]):
|
|
139
|
-
async def check(self, message: Message, ctx: Context) -> bool:
|
|
140
|
-
return message.dice.unwrap().emoji == DiceEmoji.SLOT_MACHINE
|
|
141
|
-
|
|
129
|
+
class IsDiceEmoji(MessageRule, requires=[HasDice()]):
|
|
130
|
+
def __init__(self, dice_emoji: DiceEmoji, /) -> None:
|
|
131
|
+
self.dice_emoji = dice_emoji
|
|
142
132
|
|
|
143
|
-
class IsBowlingDice(MessageRule, requires=[HasDice()]):
|
|
144
133
|
async def check(self, message: Message, ctx: Context) -> bool:
|
|
145
|
-
return message.dice.unwrap().emoji ==
|
|
134
|
+
return message.dice.unwrap().emoji == self.dice_emoji
|
|
146
135
|
|
|
147
136
|
|
|
148
137
|
__all__ = (
|
|
149
|
-
"IsBasketballDice",
|
|
150
138
|
"IsBot",
|
|
151
|
-
"IsBowlingDice",
|
|
152
139
|
"IsChat",
|
|
153
140
|
"IsChatId",
|
|
154
|
-
"
|
|
155
|
-
"IsDice",
|
|
141
|
+
"IsDiceEmoji",
|
|
156
142
|
"IsForum",
|
|
157
143
|
"IsForward",
|
|
158
144
|
"IsForwardType",
|
telegrinder/bot/rules/markup.py
CHANGED
|
@@ -24,7 +24,7 @@ def check_string(patterns: list[vbml.Pattern], s: str, ctx: Context) -> bool:
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class Markup(TextMessageRule):
|
|
27
|
-
def __init__(self, patterns: PatternLike | list[PatternLike]):
|
|
27
|
+
def __init__(self, patterns: PatternLike | list[PatternLike], /):
|
|
28
28
|
if not isinstance(patterns, list):
|
|
29
29
|
patterns = [patterns]
|
|
30
30
|
self.patterns = [
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
|
|
3
|
+
from telegrinder.bot.dispatch.context import Context
|
|
4
|
+
from telegrinder.types.objects import Message as MessageEvent
|
|
5
|
+
|
|
6
|
+
from .abc import ABCRule, Message
|
|
7
|
+
from .adapter import EventAdapter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MessageRule(ABCRule[Message], abc.ABC):
|
|
11
|
+
adapter = EventAdapter(MessageEvent, Message)
|
|
12
|
+
|
|
13
|
+
@abc.abstractmethod
|
|
14
|
+
async def check(self, message: Message, ctx: Context) -> bool: ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ("MessageRule",)
|
|
@@ -4,7 +4,7 @@ from telegrinder.bot.dispatch.context import Context
|
|
|
4
4
|
from telegrinder.types.enums import MessageEntityType
|
|
5
5
|
from telegrinder.types.objects import MessageEntity
|
|
6
6
|
|
|
7
|
-
from .
|
|
7
|
+
from .message import Message, MessageRule
|
|
8
8
|
|
|
9
9
|
Entity: typing.TypeAlias = str | MessageEntityType
|
|
10
10
|
|
telegrinder/bot/rules/start.py
CHANGED
|
@@ -3,17 +3,19 @@ import typing
|
|
|
3
3
|
from telegrinder.bot.dispatch.context import Context
|
|
4
4
|
from telegrinder.types.enums import MessageEntityType
|
|
5
5
|
|
|
6
|
-
from .abc import MessageRule
|
|
7
6
|
from .is_from import IsPrivate
|
|
8
7
|
from .markup import Markup, Message
|
|
8
|
+
from .message import MessageRule
|
|
9
9
|
from .message_entities import MessageEntities
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class StartCommand(
|
|
13
|
-
MessageRule,
|
|
14
|
-
|
|
13
|
+
MessageRule,
|
|
14
|
+
requires=[
|
|
15
|
+
IsPrivate()
|
|
16
|
+
& MessageEntities(MessageEntityType.BOT_COMMAND)
|
|
15
17
|
& Markup(["/start <param>", "/start"]),
|
|
16
|
-
]
|
|
18
|
+
],
|
|
17
19
|
):
|
|
18
20
|
def __init__(
|
|
19
21
|
self,
|
telegrinder/bot/rules/text.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from telegrinder.bot.dispatch.context import Context
|
|
2
2
|
from telegrinder.tools.i18n.base import ABCTranslator
|
|
3
3
|
|
|
4
|
-
from .abc import ABC, Message,
|
|
4
|
+
from .abc import ABC, Message, with_caching_translations
|
|
5
|
+
from .message import MessageRule
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class HasText(MessageRule):
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from telegrinder.bot.cute_types.update import UpdateCute
|
|
2
|
+
from telegrinder.bot.dispatch.context import Context
|
|
3
|
+
from telegrinder.types.enums import UpdateType
|
|
4
|
+
|
|
5
|
+
from .abc import ABCRule, T
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IsUpdate(ABCRule[T]):
|
|
9
|
+
def __init__(self, update_type: UpdateType, /) -> None:
|
|
10
|
+
self.update_type = update_type
|
|
11
|
+
|
|
12
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
13
|
+
return event.update_type.unwrap_or_none() == self.update_type
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ("IsUpdate",)
|
|
@@ -45,7 +45,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
45
45
|
self.max_in_row = max_in_row
|
|
46
46
|
self.random_code = secrets.token_hex(8)
|
|
47
47
|
self.waiter_machine = waiter_machine
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
def __repr__(self) -> str:
|
|
50
50
|
return (
|
|
51
51
|
"<{}@{!r}: (choices={!r}, max_in_row={}) with waiter_machine={!r}, ready_text={!r} "
|
|
@@ -69,14 +69,16 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
69
69
|
choice = choices.pop(0)
|
|
70
70
|
kb.add(
|
|
71
71
|
InlineButton(
|
|
72
|
-
text=
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
text=(
|
|
73
|
+
choice.default_text
|
|
74
|
+
if not choice.is_picked
|
|
75
|
+
else choice.picked_text
|
|
76
|
+
),
|
|
75
77
|
callback_data=self.random_code + "/" + choice.code,
|
|
76
78
|
)
|
|
77
79
|
)
|
|
78
80
|
kb.row()
|
|
79
|
-
|
|
81
|
+
|
|
80
82
|
kb.add(InlineButton(self.ready, callback_data=self.random_code + "/ready"))
|
|
81
83
|
return kb.get_markup()
|
|
82
84
|
|
|
@@ -125,14 +127,14 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
125
127
|
reply_markup=self.get_markup(),
|
|
126
128
|
)
|
|
127
129
|
).unwrap()
|
|
128
|
-
|
|
130
|
+
|
|
129
131
|
while True:
|
|
130
132
|
q, _ = await self.waiter_machine.wait(view, (api, message.message_id))
|
|
131
133
|
should_continue = await self.handle(q)
|
|
132
134
|
await q.answer(self.CALLBACK_ANSWER)
|
|
133
135
|
if not should_continue:
|
|
134
136
|
break
|
|
135
|
-
|
|
137
|
+
|
|
136
138
|
return (
|
|
137
139
|
{choice.name: choice.is_picked for choice in self.choices},
|
|
138
140
|
message.message_id,
|
telegrinder/client/aiohttp.py
CHANGED
|
@@ -24,7 +24,7 @@ class AiohttpClient(ABCClient):
|
|
|
24
24
|
self.json_processing_module = json_processing_module or json
|
|
25
25
|
self.session_params = session_params
|
|
26
26
|
self.timeout = timeout or aiohttp.ClientTimeout(total=0)
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
def __repr__(self) -> str:
|
|
29
29
|
return "<{}: session={!r}, timeout={}, closed={}>".format(
|
|
30
30
|
self.__class__.__name__,
|
|
@@ -32,7 +32,7 @@ class AiohttpClient(ABCClient):
|
|
|
32
32
|
self.timeout,
|
|
33
33
|
True if self.session is None else self.session.closed,
|
|
34
34
|
)
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
async def request_raw(
|
|
37
37
|
self,
|
|
38
38
|
url: str,
|
|
@@ -118,10 +118,10 @@ class AiohttpClient(ABCClient):
|
|
|
118
118
|
form = aiohttp.formdata.FormData(quote_fields=False)
|
|
119
119
|
for k, v in data.items():
|
|
120
120
|
form.add_field(k, str(v))
|
|
121
|
-
|
|
121
|
+
|
|
122
122
|
for n, f in files.items():
|
|
123
123
|
form.add_field(n, f[1], filename=f[0])
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
return form
|
|
126
126
|
|
|
127
127
|
def __del__(self) -> None:
|
telegrinder/model.py
CHANGED
|
@@ -14,7 +14,7 @@ T = typing.TypeVar("T")
|
|
|
14
14
|
|
|
15
15
|
if typing.TYPE_CHECKING:
|
|
16
16
|
from telegrinder.api.error import APIError
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
|
|
19
19
|
MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
|
|
20
20
|
"omit_defaults": True,
|
|
@@ -26,16 +26,14 @@ MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
|
|
|
26
26
|
@typing.overload
|
|
27
27
|
def full_result(
|
|
28
28
|
result: Result[msgspec.Raw, "APIError"], full_t: type[T]
|
|
29
|
-
) -> Result[T, "APIError"]:
|
|
30
|
-
...
|
|
29
|
+
) -> Result[T, "APIError"]: ...
|
|
31
30
|
|
|
32
31
|
|
|
33
32
|
@typing.overload
|
|
34
33
|
def full_result(
|
|
35
34
|
result: Result[msgspec.Raw, "APIError"],
|
|
36
35
|
full_t: tuple[type[T], ...],
|
|
37
|
-
) -> Result[T, "APIError"]:
|
|
38
|
-
...
|
|
36
|
+
) -> Result[T, "APIError"]: ...
|
|
39
37
|
|
|
40
38
|
|
|
41
39
|
def full_result(
|
|
@@ -57,11 +55,15 @@ def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
|
|
|
57
55
|
|
|
58
56
|
|
|
59
57
|
class Model(msgspec.Struct, **MODEL_CONFIG):
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_bytes(cls, data: bytes) -> typing.Self:
|
|
60
|
+
return decoder.decode(data, type=cls)
|
|
61
|
+
|
|
60
62
|
def to_dict(
|
|
61
63
|
self,
|
|
62
64
|
*,
|
|
63
65
|
exclude_fields: set[str] | None = None,
|
|
64
|
-
) -> dict[str, typing.Any]:
|
|
66
|
+
) -> dict[str, typing.Any]:
|
|
65
67
|
exclude_fields = exclude_fields or set()
|
|
66
68
|
if "model_as_dict" not in self.__dict__:
|
|
67
69
|
self.__dict__["model_as_dict"] = msgspec.structs.asdict(self)
|
|
@@ -76,18 +78,24 @@ class Model(msgspec.Struct, **MODEL_CONFIG):
|
|
|
76
78
|
class DataConverter:
|
|
77
79
|
files: dict[str, tuple[str, bytes]] = dataclasses.field(default_factory=lambda: {})
|
|
78
80
|
|
|
81
|
+
def __repr__(self) -> str:
|
|
82
|
+
return "<{}: {}>".format(
|
|
83
|
+
self.__class__.__name__,
|
|
84
|
+
", ".join(f"{k}={v!r}" for k, v in self.converters),
|
|
85
|
+
)
|
|
86
|
+
|
|
79
87
|
@property
|
|
80
88
|
def converters(self) -> dict[type[typing.Any], typing.Callable[..., typing.Any]]:
|
|
81
89
|
return {
|
|
82
90
|
get_origin(value.__annotations__["data"]): value
|
|
83
91
|
for key, value in vars(self.__class__).items()
|
|
84
|
-
if key.startswith("convert_") and callable(value)
|
|
92
|
+
if key.startswith("convert_") and callable(value)
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
@staticmethod
|
|
88
96
|
def convert_enum(data: enum.Enum, _: bool = True) -> typing.Any:
|
|
89
97
|
return data.value
|
|
90
|
-
|
|
98
|
+
|
|
91
99
|
@staticmethod
|
|
92
100
|
def convert_datetime(data: datetime, _: bool = True) -> int:
|
|
93
101
|
return int(data.timestamp())
|
|
@@ -99,29 +107,45 @@ class DataConverter:
|
|
|
99
107
|
return converter(data, serialize)
|
|
100
108
|
return converter(self, data, serialize)
|
|
101
109
|
return data
|
|
102
|
-
|
|
110
|
+
|
|
103
111
|
def get_converter(self, t: type[typing.Any]):
|
|
104
112
|
for type, converter in self.converters.items():
|
|
105
113
|
if issubclass(t, type):
|
|
106
114
|
return converter
|
|
107
115
|
return None
|
|
108
|
-
|
|
109
|
-
def convert_model(
|
|
116
|
+
|
|
117
|
+
def convert_model(
|
|
118
|
+
self,
|
|
119
|
+
data: Model,
|
|
120
|
+
serialize: bool = True,
|
|
121
|
+
) -> str | dict[str, typing.Any]:
|
|
110
122
|
converted_dct = self(data.to_dict(), serialize=False)
|
|
111
123
|
return encoder.encode(converted_dct) if serialize is True else converted_dct
|
|
112
|
-
|
|
113
|
-
def convert_dct(
|
|
124
|
+
|
|
125
|
+
def convert_dct(
|
|
126
|
+
self,
|
|
127
|
+
data: dict[str, typing.Any],
|
|
128
|
+
serialize: bool = True,
|
|
129
|
+
) -> dict[str, typing.Any]:
|
|
114
130
|
return {
|
|
115
131
|
k: self(v, serialize=serialize)
|
|
116
132
|
for k, v in data.items()
|
|
117
133
|
if type(v) not in (NoneType, Nothing)
|
|
118
134
|
}
|
|
119
|
-
|
|
120
|
-
def convert_lst(
|
|
135
|
+
|
|
136
|
+
def convert_lst(
|
|
137
|
+
self,
|
|
138
|
+
data: list[typing.Any],
|
|
139
|
+
serialize: bool = True,
|
|
140
|
+
) -> str | list[typing.Any]:
|
|
121
141
|
converted_lst = [self(x, serialize=False) for x in data]
|
|
122
142
|
return encoder.encode(converted_lst) if serialize is True else converted_lst
|
|
123
|
-
|
|
124
|
-
def convert_tpl(
|
|
143
|
+
|
|
144
|
+
def convert_tpl(
|
|
145
|
+
self,
|
|
146
|
+
data: tuple[typing.Any, ...],
|
|
147
|
+
_: bool = True,
|
|
148
|
+
) -> str | tuple[typing.Any, ...]:
|
|
125
149
|
if (
|
|
126
150
|
isinstance(data, tuple)
|
|
127
151
|
and len(data) == 2
|
|
@@ -132,12 +156,12 @@ class DataConverter:
|
|
|
132
156
|
self.files[attach_name] = data
|
|
133
157
|
return "attach://{}".format(attach_name)
|
|
134
158
|
return data
|
|
135
|
-
|
|
159
|
+
|
|
136
160
|
|
|
137
161
|
__all__ = (
|
|
138
162
|
"DataConverter",
|
|
163
|
+
"MODEL_CONFIG",
|
|
139
164
|
"Model",
|
|
140
165
|
"full_result",
|
|
141
166
|
"get_params",
|
|
142
|
-
"MODEL_CONFIG",
|
|
143
167
|
)
|
telegrinder/modules.py
CHANGED
|
@@ -6,32 +6,24 @@ from choicelib import choice_in_order
|
|
|
6
6
|
|
|
7
7
|
@typing.runtime_checkable
|
|
8
8
|
class JSONModule(typing.Protocol):
|
|
9
|
-
def loads(self, s: str | bytes) -> typing.Any:
|
|
10
|
-
...
|
|
9
|
+
def loads(self, s: str | bytes) -> typing.Any: ...
|
|
11
10
|
|
|
12
|
-
def dumps(self, o: typing.Any) -> str:
|
|
13
|
-
...
|
|
11
|
+
def dumps(self, o: typing.Any) -> str: ...
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
@typing.runtime_checkable
|
|
17
15
|
class LoggerModule(typing.Protocol):
|
|
18
|
-
def debug(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
19
|
-
...
|
|
16
|
+
def debug(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
20
17
|
|
|
21
|
-
def info(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
22
|
-
...
|
|
18
|
+
def info(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
23
19
|
|
|
24
|
-
def warning(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
25
|
-
...
|
|
20
|
+
def warning(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
26
21
|
|
|
27
|
-
def error(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
28
|
-
...
|
|
22
|
+
def error(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
29
23
|
|
|
30
|
-
def critical(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
31
|
-
...
|
|
24
|
+
def critical(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
32
25
|
|
|
33
|
-
def exception(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
34
|
-
...
|
|
26
|
+
def exception(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
35
27
|
|
|
36
28
|
def set_level(
|
|
37
29
|
self,
|
|
@@ -43,8 +35,7 @@ class LoggerModule(typing.Protocol):
|
|
|
43
35
|
"CRITICAL",
|
|
44
36
|
"EXCEPTION",
|
|
45
37
|
],
|
|
46
|
-
) -> None:
|
|
47
|
-
...
|
|
38
|
+
) -> None: ...
|
|
48
39
|
|
|
49
40
|
|
|
50
41
|
logger: LoggerModule
|
|
@@ -147,8 +138,7 @@ elif logging_module == "logging":
|
|
|
147
138
|
},
|
|
148
139
|
}
|
|
149
140
|
FORMAT = (
|
|
150
|
-
FORMAT
|
|
151
|
-
.replace("<white>", COLORS["white"])
|
|
141
|
+
FORMAT.replace("<white>", COLORS["white"])
|
|
152
142
|
.replace("</white>", COLORS["reset"])
|
|
153
143
|
.replace("<green>", COLORS["green"])
|
|
154
144
|
.replace("</green>", COLORS["reset"])
|
|
@@ -157,24 +147,20 @@ elif logging_module == "logging":
|
|
|
157
147
|
for level, settings in LEVEL_SETTINGS.items():
|
|
158
148
|
fmt = FORMAT
|
|
159
149
|
for name, color in settings.items():
|
|
160
|
-
fmt = (
|
|
161
|
-
|
|
162
|
-
.replace(f"<{name}>", COLORS[color])
|
|
163
|
-
.replace(f"</{name}>", COLORS["reset"])
|
|
150
|
+
fmt = fmt.replace(f"<{name}>", COLORS[color]).replace(
|
|
151
|
+
f"</{name}>", COLORS["reset"]
|
|
164
152
|
)
|
|
165
153
|
LEVEL_FORMATS[level] = fmt
|
|
166
154
|
|
|
167
|
-
|
|
168
155
|
class TelegrinderLoggingFormatter(logging.Formatter):
|
|
169
|
-
def format(self, record: logging.LogRecord) -> str:
|
|
156
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
170
157
|
if not record.funcName or record.funcName == "<module>":
|
|
171
158
|
record.funcName = "\b"
|
|
172
159
|
frame = next(
|
|
173
160
|
(
|
|
174
161
|
frame
|
|
175
162
|
for frame in inspect.stack()
|
|
176
|
-
if frame.filename == record.pathname
|
|
177
|
-
and frame.lineno == record.lineno
|
|
163
|
+
if frame.filename == record.pathname and frame.lineno == record.lineno
|
|
178
164
|
),
|
|
179
165
|
None,
|
|
180
166
|
)
|
|
@@ -187,7 +173,6 @@ elif logging_module == "logging":
|
|
|
187
173
|
style="{",
|
|
188
174
|
).format(record)
|
|
189
175
|
|
|
190
|
-
|
|
191
176
|
class LogMessage:
|
|
192
177
|
def __init__(self, fmt: typing.Any, args: typing.Any, kwargs: typing.Any) -> None:
|
|
193
178
|
self.fmt = fmt
|
|
@@ -197,7 +182,6 @@ elif logging_module == "logging":
|
|
|
197
182
|
def __str__(self) -> str:
|
|
198
183
|
return self.fmt.format(*self.args, **self.kwargs)
|
|
199
184
|
|
|
200
|
-
|
|
201
185
|
class TelegrinderLoggingStyleAdapter(logging.LoggerAdapter):
|
|
202
186
|
def __init__(
|
|
203
187
|
self,
|
|
@@ -223,7 +207,7 @@ elif logging_module == "logging":
|
|
|
223
207
|
for key in inspect.getfullargspec(self.logger._log).args[1:]
|
|
224
208
|
if key in kwargs
|
|
225
209
|
}
|
|
226
|
-
|
|
210
|
+
|
|
227
211
|
if isinstance(msg, str):
|
|
228
212
|
msg = LogMessage(msg, args, kwargs)
|
|
229
213
|
args = tuple()
|
|
@@ -245,7 +229,7 @@ def _set_logger_level(level):
|
|
|
245
229
|
logging.getLogger("telegrinder").setLevel(logging.getLevelName(level))
|
|
246
230
|
elif logging_module == "loguru":
|
|
247
231
|
import loguru # type: ignore
|
|
248
|
-
|
|
232
|
+
|
|
249
233
|
if handler_id in loguru.logger._core.handlers: # type: ignore
|
|
250
234
|
loguru.logger._core.handlers[handler_id]._levelno = loguru.logger.level(level).no # type: ignore
|
|
251
235
|
|