telegrinder 0.1.dev166__py3-none-any.whl → 0.1.dev168__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 +0 -2
- telegrinder/bot/__init__.py +0 -2
- telegrinder/bot/bot.py +1 -3
- telegrinder/bot/cute_types/base.py +3 -12
- telegrinder/bot/cute_types/callback_query.py +2 -4
- telegrinder/bot/cute_types/chat_join_request.py +3 -1
- telegrinder/bot/cute_types/chat_member_updated.py +3 -1
- telegrinder/bot/cute_types/message.py +10 -31
- telegrinder/bot/cute_types/utils.py +1 -3
- telegrinder/bot/dispatch/__init__.py +1 -2
- telegrinder/bot/dispatch/composition.py +1 -3
- telegrinder/bot/dispatch/dispatch.py +5 -6
- telegrinder/bot/dispatch/handler/func.py +4 -11
- telegrinder/bot/dispatch/return_manager/abc.py +9 -13
- telegrinder/bot/dispatch/return_manager/message.py +5 -7
- telegrinder/bot/dispatch/view/abc.py +54 -5
- telegrinder/bot/dispatch/view/box.py +3 -11
- telegrinder/bot/dispatch/view/raw.py +2 -6
- telegrinder/bot/dispatch/waiter_machine/__init__.py +1 -2
- telegrinder/bot/dispatch/waiter_machine/machine.py +43 -88
- telegrinder/bot/dispatch/waiter_machine/middleware.py +12 -5
- telegrinder/bot/dispatch/waiter_machine/short_state.py +15 -5
- telegrinder/bot/polling/polling.py +2 -6
- telegrinder/bot/rules/adapter/event.py +1 -3
- telegrinder/bot/rules/callback_data.py +8 -8
- telegrinder/bot/rules/fuzzy.py +1 -2
- telegrinder/bot/rules/is_from.py +6 -4
- telegrinder/bot/rules/markup.py +1 -2
- telegrinder/bot/rules/mention.py +1 -4
- telegrinder/bot/rules/regex.py +1 -2
- telegrinder/bot/rules/rule_enum.py +1 -3
- telegrinder/bot/rules/start.py +1 -3
- telegrinder/bot/scenario/checkbox.py +6 -10
- telegrinder/bot/scenario/choice.py +4 -3
- telegrinder/client/aiohttp.py +1 -3
- telegrinder/model.py +4 -3
- telegrinder/modules.py +1 -3
- telegrinder/msgspec_utils.py +1 -3
- telegrinder/node/attachment.py +18 -14
- telegrinder/node/base.py +4 -11
- telegrinder/node/composer.py +1 -3
- telegrinder/node/message.py +3 -1
- telegrinder/node/source.py +3 -1
- telegrinder/node/text.py +3 -1
- telegrinder/tools/__init__.py +2 -0
- telegrinder/tools/buttons.py +4 -6
- telegrinder/tools/error_handler/abc.py +1 -3
- telegrinder/tools/error_handler/error.py +3 -6
- telegrinder/tools/error_handler/error_handler.py +17 -13
- telegrinder/tools/formatting/html.py +2 -6
- telegrinder/tools/formatting/links.py +1 -3
- telegrinder/tools/global_context/abc.py +1 -3
- telegrinder/tools/global_context/global_context.py +13 -31
- telegrinder/tools/i18n/middleware/base.py +1 -3
- telegrinder/tools/limited_dict.py +37 -0
- telegrinder/tools/loop_wrapper/loop_wrapper.py +3 -7
- telegrinder/types/__init__.py +30 -0
- telegrinder/types/methods.py +20 -89
- telegrinder/types/objects.py +16 -45
- telegrinder/verification_utils.py +2 -1
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/METADATA +5 -5
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/RECORD +64 -63
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/WHEEL +0 -0
telegrinder/node/attachment.py
CHANGED
|
@@ -14,21 +14,15 @@ from .message import MessageNode
|
|
|
14
14
|
class Attachment(DataNode):
|
|
15
15
|
attachment_type: typing.Literal["audio", "document", "photo", "poll", "video"]
|
|
16
16
|
_: dataclasses.KW_ONLY
|
|
17
|
-
audio: Option[telegrinder.types.Audio] = dataclasses.field(
|
|
18
|
-
default_factory=lambda: Nothing()
|
|
19
|
-
)
|
|
17
|
+
audio: Option[telegrinder.types.Audio] = dataclasses.field(default_factory=lambda: Nothing())
|
|
20
18
|
document: Option[telegrinder.types.Document] = dataclasses.field(
|
|
21
19
|
default_factory=lambda: Nothing()
|
|
22
20
|
)
|
|
23
21
|
photo: Option[list[telegrinder.types.PhotoSize]] = dataclasses.field(
|
|
24
22
|
default_factory=lambda: Nothing()
|
|
25
23
|
)
|
|
26
|
-
poll: Option[telegrinder.types.Poll] = dataclasses.field(
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
video: Option[telegrinder.types.Video] = dataclasses.field(
|
|
30
|
-
default_factory=lambda: Nothing()
|
|
31
|
-
)
|
|
24
|
+
poll: Option[telegrinder.types.Poll] = dataclasses.field(default_factory=lambda: Nothing())
|
|
25
|
+
video: Option[telegrinder.types.Video] = dataclasses.field(default_factory=lambda: Nothing())
|
|
32
26
|
|
|
33
27
|
@classmethod
|
|
34
28
|
async def compose(cls, message: MessageNode) -> "Attachment":
|
|
@@ -44,31 +38,41 @@ class Photo(DataNode):
|
|
|
44
38
|
|
|
45
39
|
@classmethod
|
|
46
40
|
async def compose(cls, attachment: Attachment) -> typing.Self:
|
|
47
|
-
|
|
41
|
+
if not attachment.photo:
|
|
42
|
+
raise ComposeError("Attachment is not an photo")
|
|
43
|
+
return cls(attachment.photo.unwrap())
|
|
48
44
|
|
|
49
45
|
|
|
50
46
|
class Video(ScalarNode, telegrinder.types.Video):
|
|
51
47
|
@classmethod
|
|
52
48
|
async def compose(cls, attachment: Attachment) -> telegrinder.types.Video:
|
|
53
|
-
|
|
49
|
+
if not attachment.video:
|
|
50
|
+
raise ComposeError("Attachment is not an video")
|
|
51
|
+
return attachment.video.unwrap()
|
|
54
52
|
|
|
55
53
|
|
|
56
54
|
class Audio(ScalarNode, telegrinder.types.Audio):
|
|
57
55
|
@classmethod
|
|
58
56
|
async def compose(cls, attachment: Attachment) -> telegrinder.types.Audio:
|
|
59
|
-
|
|
57
|
+
if not attachment.audio:
|
|
58
|
+
raise ComposeError("Attachment is not an audio")
|
|
59
|
+
return attachment.audio.unwrap()
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
class Document(ScalarNode, telegrinder.types.Document):
|
|
63
63
|
@classmethod
|
|
64
64
|
async def compose(cls, attachment: Attachment) -> telegrinder.types.Document:
|
|
65
|
-
|
|
65
|
+
if not attachment.document:
|
|
66
|
+
raise ComposeError("Attachment is not an document")
|
|
67
|
+
return attachment.document.unwrap()
|
|
66
68
|
|
|
67
69
|
|
|
68
70
|
class Poll(ScalarNode, telegrinder.types.Poll):
|
|
69
71
|
@classmethod
|
|
70
72
|
async def compose(cls, attachment: Attachment) -> telegrinder.types.Poll:
|
|
71
|
-
|
|
73
|
+
if not attachment.poll:
|
|
74
|
+
raise ComposeError("Attachment is not an poll")
|
|
75
|
+
return attachment.poll.unwrap()
|
|
72
76
|
|
|
73
77
|
|
|
74
78
|
__all__ = (
|
telegrinder/node/base.py
CHANGED
|
@@ -3,8 +3,7 @@ import inspect
|
|
|
3
3
|
import typing
|
|
4
4
|
|
|
5
5
|
ComposeResult: typing.TypeAlias = (
|
|
6
|
-
typing.Coroutine[typing.Any, typing.Any, typing.Any]
|
|
7
|
-
| typing.AsyncGenerator[typing.Any, None]
|
|
6
|
+
typing.Coroutine[typing.Any, typing.Any, typing.Any] | typing.AsyncGenerator[typing.Any, None]
|
|
8
7
|
)
|
|
9
8
|
|
|
10
9
|
|
|
@@ -16,9 +15,7 @@ class Node(abc.ABC):
|
|
|
16
15
|
|
|
17
16
|
@classmethod
|
|
18
17
|
@abc.abstractmethod
|
|
19
|
-
def compose(
|
|
20
|
-
cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
|
|
21
|
-
) -> ComposeResult:
|
|
18
|
+
def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
|
|
22
19
|
pass
|
|
23
20
|
|
|
24
21
|
@classmethod
|
|
@@ -52,18 +49,14 @@ class DataNode(Node, abc.ABC):
|
|
|
52
49
|
@typing.dataclass_transform()
|
|
53
50
|
@classmethod
|
|
54
51
|
@abc.abstractmethod
|
|
55
|
-
async def compose(
|
|
56
|
-
cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
|
|
57
|
-
) -> ComposeResult:
|
|
52
|
+
async def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
|
|
58
53
|
pass
|
|
59
54
|
|
|
60
55
|
|
|
61
56
|
class ScalarNodeProto(Node, abc.ABC):
|
|
62
57
|
@classmethod
|
|
63
58
|
@abc.abstractmethod
|
|
64
|
-
async def compose(
|
|
65
|
-
cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
|
|
66
|
-
) -> ComposeResult:
|
|
59
|
+
async def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
|
|
67
60
|
pass
|
|
68
61
|
|
|
69
62
|
|
telegrinder/node/composer.py
CHANGED
|
@@ -27,9 +27,7 @@ class NodeSession:
|
|
|
27
27
|
self.generator = None
|
|
28
28
|
|
|
29
29
|
def __repr__(self) -> str:
|
|
30
|
-
return f"<{self.__class__.__name__}: {self.value}" + (
|
|
31
|
-
"ACTIVE>" if self.generator else ">"
|
|
32
|
-
)
|
|
30
|
+
return f"<{self.__class__.__name__}: {self.value}" + ("ACTIVE>" if self.generator else ">")
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
class NodeCollection:
|
telegrinder/node/message.py
CHANGED
|
@@ -9,8 +9,10 @@ from .update import UpdateNode
|
|
|
9
9
|
class MessageNode(ScalarNode, MessageCute):
|
|
10
10
|
@classmethod
|
|
11
11
|
async def compose(cls, update: UpdateNode) -> typing.Self:
|
|
12
|
+
if not update.message:
|
|
13
|
+
raise ComposeError
|
|
12
14
|
return cls(
|
|
13
|
-
**update.message.
|
|
15
|
+
**update.message.unwrap().to_dict(),
|
|
14
16
|
api=update.api,
|
|
15
17
|
)
|
|
16
18
|
|
telegrinder/node/source.py
CHANGED
|
@@ -24,7 +24,9 @@ class Source(DataNode):
|
|
|
24
24
|
|
|
25
25
|
async def send(self, text: str) -> Message:
|
|
26
26
|
result = await self.api.send_message(
|
|
27
|
-
self.chat.id,
|
|
27
|
+
chat_id=self.chat.id,
|
|
28
|
+
message_thread_id=self.thread_id,
|
|
29
|
+
text=text,
|
|
28
30
|
)
|
|
29
31
|
return result.unwrap()
|
|
30
32
|
|
telegrinder/node/text.py
CHANGED
|
@@ -7,7 +7,9 @@ from .message import MessageNode
|
|
|
7
7
|
class Text(ScalarNode, str):
|
|
8
8
|
@classmethod
|
|
9
9
|
async def compose(cls, message: MessageNode) -> typing.Self:
|
|
10
|
-
|
|
10
|
+
if not message.text:
|
|
11
|
+
raise ComposeError("Message has no text")
|
|
12
|
+
return cls(message.text.unwrap())
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
__all__ = ("Text",)
|
telegrinder/tools/__init__.py
CHANGED
|
@@ -66,6 +66,7 @@ from .keyboard import (
|
|
|
66
66
|
Keyboard,
|
|
67
67
|
RowButtons,
|
|
68
68
|
)
|
|
69
|
+
from .limited_dict import LimitedDict
|
|
69
70
|
from .loop_wrapper import ABCLoopWrapper, DelayedTask, Lifespan, LoopWrapper
|
|
70
71
|
from .magic import magic_bundle, resolve_arg_names
|
|
71
72
|
from .parse_mode import ParseMode
|
|
@@ -99,6 +100,7 @@ __all__ = (
|
|
|
99
100
|
"KeyboardSetBase",
|
|
100
101
|
"KeyboardSetYAML",
|
|
101
102
|
"Lifespan",
|
|
103
|
+
"LimitedDict",
|
|
102
104
|
"Link",
|
|
103
105
|
"LoopWrapper",
|
|
104
106
|
"Mention",
|
telegrinder/tools/buttons.py
CHANGED
|
@@ -63,15 +63,13 @@ class InlineButton(BaseButton):
|
|
|
63
63
|
url: str | None = None
|
|
64
64
|
login_url: dict[str, typing.Any] | LoginUrl | None = None
|
|
65
65
|
pay: bool | None = None
|
|
66
|
-
callback_data:
|
|
67
|
-
str | dict[str, typing.Any] | DataclassInstance | msgspec.Struct | None
|
|
68
|
-
) = None
|
|
66
|
+
callback_data: str | dict[str, typing.Any] | DataclassInstance | msgspec.Struct | None = None
|
|
69
67
|
callback_game: dict[str, typing.Any] | CallbackGame | None = None
|
|
70
68
|
switch_inline_query: str | None = None
|
|
71
69
|
switch_inline_query_current_chat: str | None = None
|
|
72
|
-
switch_inline_query_chosen_chat: (
|
|
73
|
-
|
|
74
|
-
)
|
|
70
|
+
switch_inline_query_chosen_chat: dict[str, typing.Any] | SwitchInlineQueryChosenChat | None = (
|
|
71
|
+
None
|
|
72
|
+
)
|
|
75
73
|
web_app: dict[str, typing.Any] | WebAppInfo | None = None
|
|
76
74
|
|
|
77
75
|
|
|
@@ -16,9 +16,7 @@ class ABCErrorHandler(ABC, typing.Generic[EventT]):
|
|
|
16
16
|
self,
|
|
17
17
|
*args: typing.Any,
|
|
18
18
|
**kwargs: typing.Any,
|
|
19
|
-
) -> typing.Callable[
|
|
20
|
-
[typing.Callable[..., typing.Any]], typing.Callable[..., typing.Any]
|
|
21
|
-
]:
|
|
19
|
+
) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Callable[..., typing.Any]]:
|
|
22
20
|
"""Decorator for registering callback as an error handler."""
|
|
23
21
|
|
|
24
22
|
@abstractmethod
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class CatcherError(TypeError):
|
|
5
|
-
def __init__(self, exc: typing.Any, error: str) -> None:
|
|
1
|
+
class CatcherError(BaseException):
|
|
2
|
+
def __init__(self, exc: BaseException, message: str) -> None:
|
|
6
3
|
self.exc = exc
|
|
7
|
-
self.
|
|
4
|
+
self.message = message
|
|
8
5
|
|
|
9
6
|
|
|
10
7
|
__all__ = ("CatcherError",)
|
|
@@ -13,9 +13,7 @@ from .error import CatcherError
|
|
|
13
13
|
|
|
14
14
|
F = typing.TypeVar("F", bound="FuncCatcher")
|
|
15
15
|
ExceptionT = typing.TypeVar("ExceptionT", bound=BaseException, contravariant=True)
|
|
16
|
-
FuncCatcher = typing.Callable[
|
|
17
|
-
typing.Concatenate[ExceptionT, ...], typing.Awaitable[typing.Any]
|
|
18
|
-
]
|
|
16
|
+
FuncCatcher = typing.Callable[typing.Concatenate[ExceptionT, ...], typing.Awaitable[typing.Any]]
|
|
19
17
|
|
|
20
18
|
|
|
21
19
|
@dataclasses.dataclass(frozen=True, repr=False)
|
|
@@ -59,8 +57,7 @@ class Catcher(typing.Generic[EventT]):
|
|
|
59
57
|
) -> Result[typing.Any, BaseException]:
|
|
60
58
|
if self.match_exception(exception):
|
|
61
59
|
logger.debug(
|
|
62
|
-
"Catcher {!r} caught an exception in handler {!r}, "
|
|
63
|
-
"running catcher...".format(
|
|
60
|
+
"Catcher {!r} caught an exception in handler {!r}, " "running catcher...".format(
|
|
64
61
|
self.func.__name__,
|
|
65
62
|
handler_name,
|
|
66
63
|
)
|
|
@@ -109,7 +106,8 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
109
106
|
ignore_errors: bool = False,
|
|
110
107
|
):
|
|
111
108
|
"""Register the catcher.
|
|
112
|
-
|
|
109
|
+
|
|
110
|
+
:param logging: Logging the result of the catcher at the level 'DEBUG'.
|
|
113
111
|
:param raise_exception: Raise an exception if the catcher hasn't started.
|
|
114
112
|
:param ignore_errors: Ignore errors that may occur.
|
|
115
113
|
"""
|
|
@@ -142,21 +140,21 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
142
140
|
return Error(
|
|
143
141
|
CatcherError(
|
|
144
142
|
exc,
|
|
145
|
-
"Exception {
|
|
143
|
+
"Exception {} was occurred during the running catcher {!r}.".format(
|
|
146
144
|
repr(exc), self.catcher.func.__name__
|
|
147
145
|
),
|
|
148
146
|
)
|
|
149
147
|
)
|
|
150
148
|
|
|
151
|
-
def process_catcher_error(self, error: CatcherError) -> Result[None,
|
|
149
|
+
def process_catcher_error(self, error: CatcherError) -> Result[None, BaseException]:
|
|
152
150
|
assert self.catcher is not None
|
|
153
151
|
|
|
154
152
|
if self.catcher.raise_exception:
|
|
155
153
|
raise error.exc from None
|
|
156
|
-
if not self.catcher.ignore_errors:
|
|
157
|
-
return Error(error.error)
|
|
158
154
|
if self.catcher.logging:
|
|
159
|
-
logger.error(error.
|
|
155
|
+
logger.error(error.message)
|
|
156
|
+
if not self.catcher.ignore_errors:
|
|
157
|
+
return Error(error.exc)
|
|
160
158
|
|
|
161
159
|
return Ok(None)
|
|
162
160
|
|
|
@@ -166,12 +164,18 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
166
164
|
event: EventT,
|
|
167
165
|
api: ABCAPI,
|
|
168
166
|
ctx: Context,
|
|
169
|
-
) -> Result[typing.Any,
|
|
167
|
+
) -> Result[typing.Any, BaseException]:
|
|
170
168
|
if not self.catcher:
|
|
171
169
|
return Ok(await handler(event, **magic_bundle(handler, ctx))) # type: ignore
|
|
172
170
|
|
|
173
171
|
match await self.process(handler, event, api, ctx):
|
|
174
|
-
case Ok(
|
|
172
|
+
case Ok(value) as ok:
|
|
173
|
+
if self.catcher.logging:
|
|
174
|
+
logger.debug(
|
|
175
|
+
"Catcher {!r} returned a value: {!r}",
|
|
176
|
+
self.catcher.func.__name__,
|
|
177
|
+
value,
|
|
178
|
+
)
|
|
175
179
|
return ok
|
|
176
180
|
case Error(exc) as err:
|
|
177
181
|
if isinstance(exc, CatcherError):
|
|
@@ -49,9 +49,7 @@ class StringFormatter(string.Formatter):
|
|
|
49
49
|
)
|
|
50
50
|
return fmt
|
|
51
51
|
|
|
52
|
-
def get_spec_formatter(
|
|
53
|
-
self, value: SpecialFormat
|
|
54
|
-
) -> typing.Callable[..., "TagFormat"]:
|
|
52
|
+
def get_spec_formatter(self, value: SpecialFormat) -> typing.Callable[..., "TagFormat"]:
|
|
55
53
|
return globals()[value.__formatter_name__]
|
|
56
54
|
|
|
57
55
|
def check_formats(self, value: typing.Any, fmts: list[str]) -> "TagFormat":
|
|
@@ -241,9 +239,7 @@ def start_bot_link(bot_id: str | int, data: str, string: str | None = None) -> T
|
|
|
241
239
|
return link(get_start_bot_link(bot_id, data), string)
|
|
242
240
|
|
|
243
241
|
|
|
244
|
-
def start_group_link(
|
|
245
|
-
bot_id: str | int, data: str, string: str | None = None
|
|
246
|
-
) -> TagFormat:
|
|
242
|
+
def start_group_link(bot_id: str | int, data: str, string: str | None = None) -> TagFormat:
|
|
247
243
|
return link(get_start_group_link(bot_id, data), string)
|
|
248
244
|
|
|
249
245
|
|
|
@@ -29,9 +29,7 @@ def get_invite_chat_link(invite_link: str) -> str:
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def user_open_message_link(user_id: int, message: str | None = None) -> str:
|
|
32
|
-
return f"tg://openmessage?user_id={user_id}" + (
|
|
33
|
-
"" if not message else f"&msg?text={message}"
|
|
34
|
-
)
|
|
32
|
+
return f"tg://openmessage?user_id={user_id}" + ("" if not message else f"&msg?text={message}")
|
|
35
33
|
|
|
36
34
|
|
|
37
35
|
__all__ = (
|
|
@@ -36,9 +36,7 @@ class GlobalCtxVar(typing.Generic[T]):
|
|
|
36
36
|
@classmethod
|
|
37
37
|
def collect(cls, name: str, ctx_value: T | CtxVariable[T]) -> typing.Self:
|
|
38
38
|
ctx_value = (
|
|
39
|
-
CtxVar(ctx_value)
|
|
40
|
-
if not isinstance(ctx_value, CtxVar | GlobalCtxVar)
|
|
41
|
-
else ctx_value
|
|
39
|
+
CtxVar(ctx_value) if not isinstance(ctx_value, CtxVar | GlobalCtxVar) else ctx_value
|
|
42
40
|
)
|
|
43
41
|
params = ctx_value.__dict__
|
|
44
42
|
params["name"] = name
|
|
@@ -15,10 +15,8 @@ F = typing.TypeVar("F", bound=typing.Callable)
|
|
|
15
15
|
CtxValueT = typing.TypeVar("CtxValueT", default=typing.Any)
|
|
16
16
|
|
|
17
17
|
if typing.TYPE_CHECKING:
|
|
18
|
-
|
|
19
18
|
_: typing.TypeAlias = None
|
|
20
19
|
else:
|
|
21
|
-
|
|
22
20
|
_ = lambda: None
|
|
23
21
|
|
|
24
22
|
|
|
@@ -51,14 +49,10 @@ def root_protection(func: F) -> F:
|
|
|
51
49
|
|
|
52
50
|
@wraps(func)
|
|
53
51
|
def wrapper(self: "GlobalContext", name: str, /, *args) -> typing.Any:
|
|
54
|
-
if self.is_root_attribute(name) and name in (
|
|
55
|
-
self.__dict__ | self.__class__.__dict__
|
|
56
|
-
):
|
|
52
|
+
if self.is_root_attribute(name) and name in (self.__dict__ | self.__class__.__dict__):
|
|
57
53
|
root_attr = self.get_root_attribute(name).unwrap()
|
|
58
54
|
if all((not root_attr.can_be_rewritten, not root_attr.can_be_read)):
|
|
59
|
-
raise AttributeError(
|
|
60
|
-
f"Unable to set, get, delete root attribute {name!r}."
|
|
61
|
-
)
|
|
55
|
+
raise AttributeError(f"Unable to set, get, delete root attribute {name!r}.")
|
|
62
56
|
if func.__name__ == "__setattr__" and not root_attr.can_be_rewritten:
|
|
63
57
|
raise AttributeError(f"Unable to set root attribute {name!r}.")
|
|
64
58
|
if func.__name__ == "__getattr__" and not root_attr.can_be_read:
|
|
@@ -106,9 +100,7 @@ class Storage:
|
|
|
106
100
|
)
|
|
107
101
|
|
|
108
102
|
def __repr__(self) -> str:
|
|
109
|
-
return "<ContextStorage: %s>" % ", ".join(
|
|
110
|
-
"ctx @" + repr(x) for x in self._storage
|
|
111
|
-
)
|
|
103
|
+
return "<ContextStorage: %s>" % ", ".join("ctx @" + repr(x) for x in self._storage)
|
|
112
104
|
|
|
113
105
|
@property
|
|
114
106
|
def storage(self) -> dict[str, "GlobalContext"]:
|
|
@@ -132,7 +124,9 @@ class Storage:
|
|
|
132
124
|
order_default=True,
|
|
133
125
|
field_specifiers=(ctx_var,),
|
|
134
126
|
)
|
|
135
|
-
class GlobalContext(
|
|
127
|
+
class GlobalContext(
|
|
128
|
+
ABCGlobalContext, typing.Generic[CtxValueT], dict[str, GlobalCtxVar[CtxValueT]]
|
|
129
|
+
):
|
|
136
130
|
"""GlobalContext.
|
|
137
131
|
|
|
138
132
|
```
|
|
@@ -208,19 +202,14 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
208
202
|
"""Returns True if the names of context stores
|
|
209
203
|
that use self and __value instances are equivalent."""
|
|
210
204
|
|
|
211
|
-
return (
|
|
212
|
-
isinstance(__value, GlobalContext)
|
|
213
|
-
and self.__ctx_name__ == __value.__ctx_name__
|
|
214
|
-
)
|
|
205
|
+
return isinstance(__value, GlobalContext) and self.__ctx_name__ == __value.__ctx_name__
|
|
215
206
|
|
|
216
207
|
def __setitem__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]):
|
|
217
208
|
if is_dunder(__name):
|
|
218
209
|
raise NameError("Cannot set a context variable with dunder name.")
|
|
219
210
|
var = self.get(__name)
|
|
220
211
|
if var and var.unwrap().const:
|
|
221
|
-
raise TypeError(
|
|
222
|
-
f"Unable to set variable {__name!r}, because it's a constant."
|
|
223
|
-
)
|
|
212
|
+
raise TypeError(f"Unable to set variable {__name!r}, because it's a constant.")
|
|
224
213
|
dict.__setitem__(self, __name, GlobalCtxVar.collect(__name, __value))
|
|
225
214
|
|
|
226
215
|
def __getitem__(self, __name: str) -> CtxValueT:
|
|
@@ -229,9 +218,7 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
229
218
|
def __delitem__(self, __name: str):
|
|
230
219
|
var = self.get(__name).unwrap()
|
|
231
220
|
if var.const:
|
|
232
|
-
raise TypeError(
|
|
233
|
-
f"Unable to delete variable {__name!r}, because it's a constant."
|
|
234
|
-
)
|
|
221
|
+
raise TypeError(f"Unable to delete variable {__name!r}, because it's a constant.")
|
|
235
222
|
dict.__delitem__(self, __name)
|
|
236
223
|
|
|
237
224
|
@root_protection
|
|
@@ -328,9 +315,7 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
328
315
|
var_value_type: type[T],
|
|
329
316
|
) -> Option[GlobalCtxVar[T]]: ...
|
|
330
317
|
|
|
331
|
-
def pop(
|
|
332
|
-
self, var_name: str, var_value_type: type[T] = object
|
|
333
|
-
) -> Option[GlobalCtxVar[T]]:
|
|
318
|
+
def pop(self, var_name: str, var_value_type: type[T] = object) -> Option[GlobalCtxVar[T]]:
|
|
334
319
|
"""Pop context variable by name."""
|
|
335
320
|
|
|
336
321
|
val = self.get(var_name, var_value_type)
|
|
@@ -398,9 +383,7 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
398
383
|
|
|
399
384
|
var = self.get(old_var_name).unwrap()
|
|
400
385
|
if var.const:
|
|
401
|
-
return Error(
|
|
402
|
-
f"Unable to rename variable {old_var_name!r}, " "because it's a constant."
|
|
403
|
-
)
|
|
386
|
+
return Error(f"Unable to rename variable {old_var_name!r}, " "because it's a constant.")
|
|
404
387
|
del self[old_var_name]
|
|
405
388
|
self[new_var_name] = var.value
|
|
406
389
|
return Ok(_())
|
|
@@ -419,9 +402,8 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
419
402
|
return dict.clear(self)
|
|
420
403
|
|
|
421
404
|
for name, var in self.dict().items():
|
|
422
|
-
if var.const:
|
|
423
|
-
|
|
424
|
-
del self[name]
|
|
405
|
+
if not var.const:
|
|
406
|
+
del self[name]
|
|
425
407
|
|
|
426
408
|
def delete_ctx(self) -> Result[_, str]:
|
|
427
409
|
"""Delete context by `ctx_name`."""
|
|
@@ -17,9 +17,7 @@ class ABCTranslatorMiddleware(ABCMiddleware[T]):
|
|
|
17
17
|
pass
|
|
18
18
|
|
|
19
19
|
async def pre(self, event: T, ctx: dict) -> bool:
|
|
20
|
-
ctx[I18nEnum.I18N] = self.i18n.get_translator_by_locale(
|
|
21
|
-
await self.get_locale(event)
|
|
22
|
-
)
|
|
20
|
+
ctx[I18nEnum.I18N] = self.i18n.get_translator_by_locale(await self.get_locale(event))
|
|
23
21
|
return True
|
|
24
22
|
|
|
25
23
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
from collections import UserDict, deque
|
|
3
|
+
|
|
4
|
+
KT = typing.TypeVar("KT")
|
|
5
|
+
VT = typing.TypeVar("VT")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LimitedDict(UserDict[KT, VT]):
|
|
9
|
+
def __init__(self, *, maxlimit: int = 1000) -> None:
|
|
10
|
+
super().__init__()
|
|
11
|
+
self.maxlimit = maxlimit
|
|
12
|
+
self.queue: deque[KT] = deque(maxlen=maxlimit)
|
|
13
|
+
|
|
14
|
+
def set(self, key: KT, value: VT, /) -> VT | None:
|
|
15
|
+
"""Set item in the dictionary.
|
|
16
|
+
Returns a value that was deleted when the limit in the dictionary
|
|
17
|
+
was reached, otherwise None.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
deleted_item = None
|
|
21
|
+
if len(self.queue) >= self.maxlimit:
|
|
22
|
+
deleted_item = self.pop(self.queue.popleft(), None)
|
|
23
|
+
if key not in self.queue:
|
|
24
|
+
self.queue.append(key)
|
|
25
|
+
super().__setitem__(key, value)
|
|
26
|
+
return deleted_item
|
|
27
|
+
|
|
28
|
+
def __setitem__(self, key: KT, value: VT, /) -> None:
|
|
29
|
+
self.set(key, value)
|
|
30
|
+
|
|
31
|
+
def __delitem__(self, key: KT) -> None:
|
|
32
|
+
if key in self.queue:
|
|
33
|
+
self.queue.remove(key)
|
|
34
|
+
return super().__delitem__(key)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = ("LimitedDict",)
|
|
@@ -17,7 +17,7 @@ Task: typing.TypeAlias = typing.Union[CoroutineFunc, CoroutineTask, "DelayedTask
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def run_tasks(
|
|
20
|
-
tasks: list[CoroutineTask[typing.Any]],
|
|
20
|
+
tasks: list[CoroutineTask[typing.Any]],
|
|
21
21
|
loop: asyncio.AbstractEventLoop,
|
|
22
22
|
) -> None:
|
|
23
23
|
while tasks:
|
|
@@ -59,12 +59,8 @@ class DelayedTask(typing.Generic[CoroFunc]):
|
|
|
59
59
|
|
|
60
60
|
@dataclasses.dataclass(kw_only=True)
|
|
61
61
|
class Lifespan:
|
|
62
|
-
startup_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(
|
|
63
|
-
|
|
64
|
-
)
|
|
65
|
-
shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(
|
|
66
|
-
default_factory=lambda: []
|
|
67
|
-
)
|
|
62
|
+
startup_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
63
|
+
shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
68
64
|
|
|
69
65
|
def on_startup(self, task_or_func: Task) -> Task:
|
|
70
66
|
task_or_func = to_coroutine_task(task_or_func)
|