telegrinder 0.4.2__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +37 -55
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +6 -4
- telegrinder/api/api.py +100 -26
- telegrinder/api/error.py +42 -8
- telegrinder/api/response.py +4 -1
- telegrinder/api/token.py +2 -2
- telegrinder/bot/__init__.py +9 -25
- telegrinder/bot/bot.py +31 -25
- telegrinder/bot/cute_types/__init__.py +0 -0
- telegrinder/bot/cute_types/base.py +103 -61
- telegrinder/bot/cute_types/callback_query.py +447 -400
- telegrinder/bot/cute_types/chat_join_request.py +59 -62
- telegrinder/bot/cute_types/chat_member_updated.py +154 -157
- telegrinder/bot/cute_types/inline_query.py +41 -44
- telegrinder/bot/cute_types/message.py +98 -67
- telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
- telegrinder/bot/cute_types/update.py +1 -8
- telegrinder/bot/cute_types/utils.py +1 -1
- telegrinder/bot/dispatch/__init__.py +10 -15
- telegrinder/bot/dispatch/abc.py +12 -11
- telegrinder/bot/dispatch/action.py +104 -0
- telegrinder/bot/dispatch/context.py +32 -26
- telegrinder/bot/dispatch/dispatch.py +61 -134
- telegrinder/bot/dispatch/handler/__init__.py +2 -0
- telegrinder/bot/dispatch/handler/abc.py +10 -8
- telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
- telegrinder/bot/dispatch/handler/base.py +10 -33
- telegrinder/bot/dispatch/handler/document_reply.py +2 -3
- telegrinder/bot/dispatch/handler/func.py +55 -87
- telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
- telegrinder/bot/dispatch/handler/message_reply.py +2 -3
- telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
- telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
- telegrinder/bot/dispatch/handler/video_reply.py +2 -3
- telegrinder/bot/dispatch/middleware/__init__.py +0 -0
- telegrinder/bot/dispatch/middleware/abc.py +79 -55
- telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
- telegrinder/bot/dispatch/process.py +84 -105
- telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
- telegrinder/bot/dispatch/return_manager/abc.py +102 -65
- telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
- telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
- telegrinder/bot/dispatch/return_manager/message.py +8 -10
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
- telegrinder/bot/dispatch/view/__init__.py +4 -4
- telegrinder/bot/dispatch/view/abc.py +6 -16
- telegrinder/bot/dispatch/view/base.py +54 -178
- telegrinder/bot/dispatch/view/box.py +19 -18
- telegrinder/bot/dispatch/view/callback_query.py +4 -8
- telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
- telegrinder/bot/dispatch/view/chat_member.py +5 -25
- telegrinder/bot/dispatch/view/error.py +9 -0
- telegrinder/bot/dispatch/view/inline_query.py +4 -8
- telegrinder/bot/dispatch/view/message.py +5 -25
- telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
- telegrinder/bot/dispatch/view/raw.py +3 -109
- telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
- telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
- telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
- telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
- telegrinder/bot/polling/__init__.py +0 -0
- telegrinder/bot/polling/abc.py +0 -0
- telegrinder/bot/polling/polling.py +209 -88
- telegrinder/bot/rules/__init__.py +3 -16
- telegrinder/bot/rules/abc.py +42 -122
- telegrinder/bot/rules/callback_data.py +29 -49
- telegrinder/bot/rules/chat_join.py +5 -23
- telegrinder/bot/rules/command.py +8 -4
- telegrinder/bot/rules/enum_text.py +3 -4
- telegrinder/bot/rules/func.py +7 -14
- telegrinder/bot/rules/fuzzy.py +3 -4
- telegrinder/bot/rules/inline.py +8 -20
- telegrinder/bot/rules/integer.py +2 -3
- telegrinder/bot/rules/is_from.py +12 -11
- telegrinder/bot/rules/logic.py +11 -5
- telegrinder/bot/rules/markup.py +22 -14
- telegrinder/bot/rules/mention.py +8 -7
- telegrinder/bot/rules/message_entities.py +8 -4
- telegrinder/bot/rules/node.py +23 -12
- telegrinder/bot/rules/payload.py +5 -4
- telegrinder/bot/rules/payment_invoice.py +6 -21
- telegrinder/bot/rules/regex.py +2 -4
- telegrinder/bot/rules/rule_enum.py +8 -7
- telegrinder/bot/rules/start.py +5 -6
- telegrinder/bot/rules/state.py +1 -1
- telegrinder/bot/rules/text.py +4 -15
- telegrinder/bot/rules/update.py +3 -4
- telegrinder/bot/scenario/__init__.py +0 -0
- telegrinder/bot/scenario/abc.py +6 -5
- telegrinder/bot/scenario/checkbox.py +1 -1
- telegrinder/bot/scenario/choice.py +30 -39
- telegrinder/client/__init__.py +3 -5
- telegrinder/client/abc.py +11 -6
- telegrinder/client/aiohttp.py +141 -27
- telegrinder/client/form_data.py +1 -1
- telegrinder/model.py +61 -89
- telegrinder/modules.py +325 -102
- telegrinder/msgspec_utils/__init__.py +40 -0
- telegrinder/msgspec_utils/abc.py +18 -0
- telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
- telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
- telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +389 -0
- telegrinder/msgspec_utils/encoder.py +206 -0
- telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
- telegrinder/msgspec_utils/tools.py +75 -0
- telegrinder/node/__init__.py +24 -7
- telegrinder/node/attachment.py +1 -0
- telegrinder/node/base.py +154 -72
- telegrinder/node/callback_query.py +5 -5
- telegrinder/node/collection.py +39 -0
- telegrinder/node/command.py +1 -2
- telegrinder/node/composer.py +121 -72
- telegrinder/node/container.py +11 -8
- telegrinder/node/context.py +48 -0
- telegrinder/node/either.py +27 -40
- telegrinder/node/error.py +41 -0
- telegrinder/node/event.py +37 -11
- telegrinder/node/exceptions.py +7 -0
- telegrinder/node/file.py +0 -0
- telegrinder/node/i18n.py +108 -0
- telegrinder/node/me.py +3 -2
- telegrinder/node/payload.py +1 -1
- telegrinder/node/polymorphic.py +63 -28
- telegrinder/node/reply_message.py +12 -0
- telegrinder/node/rule.py +6 -13
- telegrinder/node/scope.py +14 -5
- telegrinder/node/session.py +53 -0
- telegrinder/node/source.py +41 -9
- telegrinder/node/text.py +1 -2
- telegrinder/node/tools/__init__.py +0 -0
- telegrinder/node/tools/generator.py +3 -5
- telegrinder/node/utility.py +16 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +0 -0
- telegrinder/tools/__init__.py +48 -88
- telegrinder/tools/aio.py +103 -0
- telegrinder/tools/callback_data_serialization/__init__.py +5 -0
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +2 -18
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
- telegrinder/tools/formatting/deep_links/parsing.py +90 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html_formatter.py +18 -45
- telegrinder/tools/fullname.py +83 -0
- telegrinder/tools/global_context/__init__.py +4 -3
- telegrinder/tools/global_context/abc.py +17 -14
- telegrinder/tools/global_context/builtin_context.py +39 -0
- telegrinder/tools/global_context/global_context.py +138 -39
- telegrinder/tools/input_file_directory.py +0 -0
- telegrinder/tools/keyboard/__init__.py +39 -0
- telegrinder/tools/keyboard/abc.py +159 -0
- telegrinder/tools/keyboard/base.py +77 -0
- telegrinder/tools/keyboard/buttons/__init__.py +14 -0
- telegrinder/tools/keyboard/buttons/base.py +18 -0
- telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
- telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
- telegrinder/tools/keyboard/buttons/tools.py +18 -0
- telegrinder/tools/keyboard/data.py +20 -0
- telegrinder/tools/keyboard/keyboard.py +131 -0
- telegrinder/tools/keyboard/static_keyboard.py +83 -0
- telegrinder/tools/lifespan.py +87 -51
- telegrinder/tools/limited_dict.py +4 -1
- telegrinder/tools/loop_wrapper.py +332 -0
- telegrinder/tools/magic/__init__.py +32 -0
- telegrinder/tools/magic/annotations.py +165 -0
- telegrinder/tools/magic/dictionary.py +20 -0
- telegrinder/tools/magic/function.py +246 -0
- telegrinder/tools/magic/shortcut.py +111 -0
- telegrinder/tools/parse_mode.py +9 -3
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_storage/__init__.py +0 -0
- telegrinder/tools/state_storage/abc.py +6 -1
- telegrinder/tools/state_storage/memory.py +1 -1
- telegrinder/tools/strings.py +0 -0
- telegrinder/types/__init__.py +307 -268
- telegrinder/types/enums.py +68 -37
- telegrinder/types/input_file.py +3 -3
- telegrinder/types/methods.py +5699 -5055
- telegrinder/types/methods_utils.py +62 -0
- telegrinder/types/objects.py +1782 -994
- telegrinder/verification_utils.py +3 -1
- telegrinder-0.5.1.dist-info/METADATA +162 -0
- telegrinder-0.5.1.dist-info/RECORD +200 -0
- {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/licenses/LICENSE +2 -2
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
- telegrinder/bot/rules/id.py +0 -24
- telegrinder/bot/rules/message.py +0 -15
- telegrinder/client/sonic.py +0 -212
- telegrinder/msgspec_utils.py +0 -478
- telegrinder/tools/adapter/__init__.py +0 -19
- telegrinder/tools/adapter/abc.py +0 -49
- telegrinder/tools/adapter/dataclass.py +0 -56
- telegrinder/tools/adapter/errors.py +0 -5
- telegrinder/tools/adapter/event.py +0 -61
- telegrinder/tools/adapter/node.py +0 -46
- telegrinder/tools/adapter/raw_event.py +0 -27
- telegrinder/tools/adapter/raw_update.py +0 -30
- telegrinder/tools/callback_data_serilization/__init__.py +0 -5
- telegrinder/tools/error_handler/__init__.py +0 -10
- telegrinder/tools/error_handler/abc.py +0 -30
- telegrinder/tools/error_handler/error.py +0 -9
- telegrinder/tools/error_handler/error_handler.py +0 -179
- telegrinder/tools/formatting/spec_html_formats.py +0 -75
- telegrinder/tools/functional.py +0 -8
- telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
- telegrinder/tools/i18n/__init__.py +0 -12
- telegrinder/tools/i18n/abc.py +0 -32
- telegrinder/tools/i18n/middleware/__init__.py +0 -3
- telegrinder/tools/i18n/middleware/abc.py +0 -22
- telegrinder/tools/i18n/simple.py +0 -43
- telegrinder/tools/keyboard.py +0 -132
- telegrinder/tools/loop_wrapper/__init__.py +0 -4
- telegrinder/tools/loop_wrapper/abc.py +0 -20
- telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
- telegrinder/tools/magic.py +0 -344
- telegrinder-0.4.2.dist-info/METADATA +0 -151
- telegrinder-0.4.2.dist-info/RECORD +0 -182
- {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/WHEEL +0 -0
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import dataclasses
|
|
4
|
+
import types
|
|
2
5
|
import typing
|
|
3
6
|
|
|
4
7
|
import msgspec
|
|
5
8
|
|
|
6
9
|
from telegrinder.msgspec_utils import encoder
|
|
10
|
+
from telegrinder.tools.callback_data_serialization import ABCDataSerializer, JSONSerializer
|
|
11
|
+
from telegrinder.tools.keyboard.buttons.base import BaseButton
|
|
7
12
|
from telegrinder.types.objects import (
|
|
8
13
|
CallbackGame,
|
|
9
14
|
CopyTextButton,
|
|
@@ -15,34 +20,70 @@ from telegrinder.types.objects import (
|
|
|
15
20
|
WebAppInfo,
|
|
16
21
|
)
|
|
17
22
|
|
|
18
|
-
from .callback_data_serilization import ABCDataSerializer, JSONSerializer
|
|
19
|
-
|
|
20
23
|
if typing.TYPE_CHECKING:
|
|
21
24
|
from _typeshed import DataclassInstance
|
|
22
25
|
|
|
26
|
+
from telegrinder.tools.keyboard.abc import ABCKeyboard
|
|
27
|
+
from telegrinder.tools.keyboard.keyboard import InlineKeyboard, Keyboard
|
|
28
|
+
|
|
23
29
|
type CallbackData = str | bytes | dict[str, typing.Any] | DataclassInstance | msgspec.Struct
|
|
24
30
|
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
def _get_keyboard_class(name: str, /) -> type[ABCKeyboard]:
|
|
33
|
+
from telegrinder.tools.keyboard.keyboard import InlineKeyboard, Keyboard # noqa
|
|
34
|
+
|
|
35
|
+
return locals()[name]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _magic_keyboard(
|
|
39
|
+
name_keyboard: str,
|
|
40
|
+
button: BaseButton,
|
|
41
|
+
other: typing.Any,
|
|
42
|
+
/,
|
|
43
|
+
*,
|
|
44
|
+
row: bool = False,
|
|
45
|
+
) -> ABCKeyboard | types.NotImplementedType:
|
|
46
|
+
keyboard_type: type[typing.Any] = _get_keyboard_class(name_keyboard)
|
|
47
|
+
if not isinstance(other, keyboard_type | type(button)):
|
|
48
|
+
return NotImplemented
|
|
49
|
+
|
|
50
|
+
keyboard: ABCKeyboard = keyboard_type().add(button)
|
|
51
|
+
if row:
|
|
52
|
+
keyboard = keyboard.row()
|
|
30
53
|
|
|
54
|
+
return keyboard.merge_to_last_row(other) if isinstance(other, keyboard_type) else keyboard.add(other)
|
|
31
55
|
|
|
32
|
-
class RowButtons[KeyboardButton: BaseButton]:
|
|
33
|
-
buttons: list[KeyboardButton]
|
|
34
|
-
auto_row: bool
|
|
35
56
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
57
|
+
class ConvertButtonMixin[T: ABCKeyboard]:
|
|
58
|
+
if typing.TYPE_CHECKING:
|
|
59
|
+
as_keyboard: type[T]
|
|
60
|
+
else:
|
|
61
|
+
KEYBOARD_CLASS_KEY = "__keyboard_class__"
|
|
39
62
|
|
|
40
|
-
|
|
41
|
-
|
|
63
|
+
@classmethod
|
|
64
|
+
def get_keyboard_class(cls):
|
|
65
|
+
if (kb_cls := getattr(cls, cls.KEYBOARD_CLASS_KEY, None)) is not None:
|
|
66
|
+
return kb_cls
|
|
67
|
+
|
|
68
|
+
arg = None
|
|
69
|
+
for base in cls.__orig_bases__:
|
|
70
|
+
origin = typing.get_origin(base) or base
|
|
71
|
+
if issubclass(origin, ConvertButtonMixin):
|
|
72
|
+
arg = typing.get_origin(arg := typing.get_args(base)[0]) or arg
|
|
73
|
+
|
|
74
|
+
assert arg is not None
|
|
75
|
+
kb_cls_name = arg.__forward_arg__ if isinstance(arg, typing.ForwardRef) else arg.__name__
|
|
76
|
+
kb_cls = _get_keyboard_class(kb_cls_name)
|
|
77
|
+
setattr(cls, cls.KEYBOARD_CLASS_KEY, kb_cls)
|
|
78
|
+
return kb_cls
|
|
79
|
+
|
|
80
|
+
def as_keyboard(self, *args, **kwargs):
|
|
81
|
+
keyboard = self.get_keyboard_class()(*args, **kwargs)
|
|
82
|
+
return keyboard.add(self)
|
|
42
83
|
|
|
43
84
|
|
|
44
85
|
@dataclasses.dataclass
|
|
45
|
-
class Button(BaseButton):
|
|
86
|
+
class Button(BaseButton, ConvertButtonMixin["Keyboard"]):
|
|
46
87
|
text: str
|
|
47
88
|
request_contact: bool = dataclasses.field(default=False, kw_only=True)
|
|
48
89
|
request_location: bool = dataclasses.field(default=False, kw_only=True)
|
|
@@ -57,9 +98,15 @@ class Button(BaseButton):
|
|
|
57
98
|
)
|
|
58
99
|
web_app: WebAppInfo | None = dataclasses.field(default=None, kw_only=True)
|
|
59
100
|
|
|
101
|
+
def __and__(self, other: object, /) -> Keyboard:
|
|
102
|
+
return _magic_keyboard("Keyboard", self, other)
|
|
103
|
+
|
|
104
|
+
def __or__(self, other: object, /) -> Keyboard:
|
|
105
|
+
return _magic_keyboard("Keyboard", self, other, row=True)
|
|
106
|
+
|
|
60
107
|
|
|
61
108
|
@dataclasses.dataclass
|
|
62
|
-
class InlineButton(BaseButton):
|
|
109
|
+
class InlineButton(BaseButton, ConvertButtonMixin["InlineKeyboard"]):
|
|
63
110
|
text: str
|
|
64
111
|
url: str | None = dataclasses.field(default=None, kw_only=True)
|
|
65
112
|
login_url: LoginUrl | None = dataclasses.field(default=None, kw_only=True)
|
|
@@ -86,7 +133,7 @@ class InlineButton(BaseButton):
|
|
|
86
133
|
or dataclasses.is_dataclass(self.callback_data)
|
|
87
134
|
):
|
|
88
135
|
callback_data_serializer = callback_data_serializer or JSONSerializer(
|
|
89
|
-
self.callback_data
|
|
136
|
+
type(self.callback_data),
|
|
90
137
|
)
|
|
91
138
|
|
|
92
139
|
if callback_data_serializer is not None:
|
|
@@ -100,10 +147,11 @@ class InlineButton(BaseButton):
|
|
|
100
147
|
if isinstance(self.web_app, str):
|
|
101
148
|
self.web_app = WebAppInfo(url=self.web_app)
|
|
102
149
|
|
|
150
|
+
def __and__(self, other: object, /) -> InlineKeyboard:
|
|
151
|
+
return _magic_keyboard("InlineKeyboard", self, other)
|
|
103
152
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
)
|
|
153
|
+
def __or__(self, other: object, /) -> InlineKeyboard:
|
|
154
|
+
return _magic_keyboard("InlineKeyboard", self, other, row=True)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
__all__ = ("Button", "InlineButton")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import types
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from telegrinder.bot.rules.abc import ABCRule
|
|
6
|
+
from telegrinder.bot.rules.payload import PayloadJsonEqRule, PayloadMarkupRule, PayloadModelRule
|
|
7
|
+
from telegrinder.bot.rules.text import Text
|
|
8
|
+
from telegrinder.tools.callback_data_serialization import ABCDataSerializer
|
|
9
|
+
from telegrinder.tools.keyboard.buttons.base import BaseStaticButton
|
|
10
|
+
from telegrinder.tools.keyboard.buttons.buttons import Button, InlineButton
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclasses.dataclass
|
|
14
|
+
class StaticButtonMixin(BaseStaticButton):
|
|
15
|
+
__and__: typing.ClassVar[None] = None
|
|
16
|
+
__or__: typing.ClassVar[None] = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclasses.dataclass
|
|
20
|
+
class StaticButton(StaticButtonMixin, Button, Text):
|
|
21
|
+
if not typing.TYPE_CHECKING:
|
|
22
|
+
|
|
23
|
+
def __init__(self, text, **kwargs):
|
|
24
|
+
self.row = kwargs.pop("row", False)
|
|
25
|
+
Button.__init__(self, text=text, **kwargs)
|
|
26
|
+
Text.__init__(self, text)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclasses.dataclass
|
|
30
|
+
class StaticInlineButton(StaticButtonMixin, InlineButton, ABCRule):
|
|
31
|
+
if not typing.TYPE_CHECKING:
|
|
32
|
+
|
|
33
|
+
def __init__(self, *args, **kwargs):
|
|
34
|
+
self.row = kwargs.pop("row", False)
|
|
35
|
+
InlineButton.__init__(self, *args, **kwargs)
|
|
36
|
+
|
|
37
|
+
check = lambda self, *args, **kwargs: False
|
|
38
|
+
else:
|
|
39
|
+
|
|
40
|
+
def check(self, *args: typing.Any, **kwargs: typing.Any) -> bool: ...
|
|
41
|
+
|
|
42
|
+
def __post_init__(self, callback_data_serializer: ABCDataSerializer[typing.Any] | None) -> None:
|
|
43
|
+
if isinstance(self.callback_data, str):
|
|
44
|
+
self.check = PayloadMarkupRule(self.callback_data).check
|
|
45
|
+
elif isinstance(self.callback_data, dict):
|
|
46
|
+
self.check = PayloadJsonEqRule(self.callback_data).check
|
|
47
|
+
elif not isinstance(self.callback_data, (bytes, types.NoneType)):
|
|
48
|
+
self.check = PayloadModelRule(
|
|
49
|
+
type(self.callback_data),
|
|
50
|
+
serializer=type(callback_data_serializer) if callback_data_serializer else None,
|
|
51
|
+
).check
|
|
52
|
+
|
|
53
|
+
super().__post_init__(callback_data_serializer)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = ("StaticButton", "StaticInlineButton")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
if typing.TYPE_CHECKING:
|
|
4
|
+
from telegrinder.tools.keyboard.buttons.base import BaseButton
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RowButtons[KeyboardButton: BaseButton]:
|
|
8
|
+
buttons: typing.Iterable[KeyboardButton]
|
|
9
|
+
|
|
10
|
+
def __init__(self, *buttons: KeyboardButton, auto_row: bool = True) -> None:
|
|
11
|
+
self.buttons = buttons
|
|
12
|
+
self.auto_row = auto_row
|
|
13
|
+
|
|
14
|
+
def get_data(self) -> list[dict[str, typing.Any]]:
|
|
15
|
+
return [b.get_data() for b in self.buttons]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ("RowButtons",)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
if typing.TYPE_CHECKING:
|
|
7
|
+
from telegrinder.tools.keyboard.abc import DictStrAny
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
11
|
+
class KeyboardModel:
|
|
12
|
+
keyboard: typing.Iterable[typing.Iterable[DictStrAny]] = dataclasses.field(default_factory=lambda: [[]])
|
|
13
|
+
resize_keyboard: bool = dataclasses.field(default=True)
|
|
14
|
+
one_time_keyboard: bool = dataclasses.field(default=False)
|
|
15
|
+
selective: bool = dataclasses.field(default=False)
|
|
16
|
+
is_persistent: bool = dataclasses.field(default=False)
|
|
17
|
+
input_field_placeholder: str | None = dataclasses.field(default=None)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ("KeyboardModel",)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import typing
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
|
|
7
|
+
from telegrinder.tools.keyboard.base import BaseKeyboard, DictStrAny
|
|
8
|
+
from telegrinder.tools.keyboard.data import KeyboardModel
|
|
9
|
+
from telegrinder.types.objects import (
|
|
10
|
+
InlineKeyboardMarkup,
|
|
11
|
+
ReplyKeyboardMarkup,
|
|
12
|
+
ReplyKeyboardRemove,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if typing.TYPE_CHECKING:
|
|
16
|
+
from telegrinder.tools.keyboard.abc import RawKeyboard
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclasses.dataclass(kw_only=True, frozen=True)
|
|
20
|
+
class KeyboardModelMixin(KeyboardModel if typing.TYPE_CHECKING else object):
|
|
21
|
+
keyboard_model: KeyboardModel = dataclasses.field(init=False)
|
|
22
|
+
|
|
23
|
+
if not typing.TYPE_CHECKING:
|
|
24
|
+
keyboard_model = dataclasses.field(
|
|
25
|
+
default_factory=lambda: KeyboardModel(keyboard=[[]]),
|
|
26
|
+
repr=False,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def keyboard(self) -> RawKeyboard:
|
|
31
|
+
return self.keyboard_model.keyboard # type: ignore
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def is_persistent(self) -> bool:
|
|
35
|
+
return self.keyboard_model.is_persistent
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def one_time_keyboard(self) -> bool:
|
|
39
|
+
return self.keyboard_model.one_time_keyboard
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def resize_keyboard(self) -> bool:
|
|
43
|
+
return self.keyboard_model.resize_keyboard
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def is_selective(self) -> bool:
|
|
47
|
+
return self.keyboard_model.selective
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def input_field_placeholder(self) -> str | None:
|
|
51
|
+
return self.keyboard_model.input_field_placeholder
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
55
|
+
class Keyboard(KeyboardModelMixin, BaseKeyboard):
|
|
56
|
+
if not typing.TYPE_CHECKING:
|
|
57
|
+
|
|
58
|
+
def __init__(self, **kwargs):
|
|
59
|
+
super().__init__(keyboard_model=kwargs.pop("keyboard_model", None) or KeyboardModel(**kwargs))
|
|
60
|
+
|
|
61
|
+
def dict(self) -> DictStrAny:
|
|
62
|
+
return dataclasses.asdict(self.keyboard_model) | {"keyboard": [row for row in self.keyboard if row]}
|
|
63
|
+
|
|
64
|
+
def get_markup(self) -> ReplyKeyboardMarkup:
|
|
65
|
+
return ReplyKeyboardMarkup.from_dict(self.dict())
|
|
66
|
+
|
|
67
|
+
def copy(self, **with_changes: typing.Any) -> typing.Self:
|
|
68
|
+
return dataclasses.replace(
|
|
69
|
+
self,
|
|
70
|
+
keyboard_model=dataclasses.replace(
|
|
71
|
+
self.keyboard_model,
|
|
72
|
+
keyboard=deepcopy(self.keyboard_model.keyboard),
|
|
73
|
+
**with_changes,
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def get_keyboard_remove(self) -> ReplyKeyboardRemove:
|
|
78
|
+
return ReplyKeyboardRemove.from_data(
|
|
79
|
+
remove_keyboard=True,
|
|
80
|
+
selective=self.keyboard_model.selective,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def resize(self) -> typing.Self:
|
|
84
|
+
return self.copy(resize_keyboard=True)
|
|
85
|
+
|
|
86
|
+
def one_time(self) -> typing.Self:
|
|
87
|
+
return self.copy(one_time_keyboard=True)
|
|
88
|
+
|
|
89
|
+
def selective(self) -> typing.Self:
|
|
90
|
+
return self.copy(selective=True)
|
|
91
|
+
|
|
92
|
+
def persistent(self) -> typing.Self:
|
|
93
|
+
return self.copy(is_persistent=True)
|
|
94
|
+
|
|
95
|
+
def no_resize(self) -> typing.Self:
|
|
96
|
+
return self.copy(resize_keyboard=False)
|
|
97
|
+
|
|
98
|
+
def no_one_time(self) -> typing.Self:
|
|
99
|
+
return self.copy(one_time_keyboard=False)
|
|
100
|
+
|
|
101
|
+
def no_selective(self) -> typing.Self:
|
|
102
|
+
return self.copy(selective=False)
|
|
103
|
+
|
|
104
|
+
def no_persistent(self) -> typing.Self:
|
|
105
|
+
return self.copy(is_persistent=False)
|
|
106
|
+
|
|
107
|
+
def placeholder(self, value: str | None, /) -> typing.Self:
|
|
108
|
+
return self.copy(input_field_placeholder=value)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclasses.dataclass(frozen=True)
|
|
112
|
+
class InlineKeyboard(BaseKeyboard):
|
|
113
|
+
keyboard: RawKeyboard = dataclasses.field(init=False)
|
|
114
|
+
|
|
115
|
+
if not typing.TYPE_CHECKING:
|
|
116
|
+
keyboard = dataclasses.field(
|
|
117
|
+
default_factory=lambda: [[]],
|
|
118
|
+
repr=False,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def dict(self) -> DictStrAny:
|
|
122
|
+
return dict(inline_keyboard=[row for row in self.keyboard if row])
|
|
123
|
+
|
|
124
|
+
def get_markup(self) -> InlineKeyboardMarkup:
|
|
125
|
+
return InlineKeyboardMarkup.from_dict(self.dict())
|
|
126
|
+
|
|
127
|
+
def copy(self, **_: typing.Any) -> typing.Self:
|
|
128
|
+
return dataclasses.replace(self, keyboard=deepcopy(self.keyboard))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
__all__ = ("InlineKeyboard", "Keyboard")
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from telegrinder.tools.keyboard.abc import DictStrAny
|
|
7
|
+
from telegrinder.tools.keyboard.base import BaseStaticKeyboard
|
|
8
|
+
from telegrinder.tools.keyboard.data import KeyboardModel
|
|
9
|
+
from telegrinder.types.objects import InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
|
10
|
+
|
|
11
|
+
type RawKeyboard = typing.Iterable[typing.Iterable[DictStrAny]]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_keyboard(cls_: type[BaseStaticKeyboard], /) -> RawKeyboard:
|
|
15
|
+
buttons = cls_.get_buttons()
|
|
16
|
+
max_in_row = cls_.__max_in_row__
|
|
17
|
+
keyboard = [[]]
|
|
18
|
+
|
|
19
|
+
for button in buttons.values():
|
|
20
|
+
keyboard[-1].append(button.get_data())
|
|
21
|
+
|
|
22
|
+
if (button.row is True or len(keyboard[-1]) >= max_in_row) and keyboard[-1]:
|
|
23
|
+
keyboard.append([])
|
|
24
|
+
|
|
25
|
+
return tuple(tuple(x) for x in keyboard if x)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StaticKeyboard(BaseStaticKeyboard):
|
|
29
|
+
__keyboard__: KeyboardModel
|
|
30
|
+
|
|
31
|
+
def __init_subclass__(
|
|
32
|
+
cls,
|
|
33
|
+
*,
|
|
34
|
+
resize_keyboard: bool = True,
|
|
35
|
+
one_time_keyboard: bool = False,
|
|
36
|
+
selective: bool = False,
|
|
37
|
+
is_persistent: bool = False,
|
|
38
|
+
input_field_placeholder: str | None = None,
|
|
39
|
+
max_in_row: int = 3,
|
|
40
|
+
) -> None:
|
|
41
|
+
cls.__max_in_row__ = max_in_row
|
|
42
|
+
cls.__keyboard__ = KeyboardModel(
|
|
43
|
+
keyboard=create_keyboard(cls),
|
|
44
|
+
resize_keyboard=resize_keyboard,
|
|
45
|
+
one_time_keyboard=one_time_keyboard,
|
|
46
|
+
selective=selective,
|
|
47
|
+
is_persistent=is_persistent,
|
|
48
|
+
input_field_placeholder=input_field_placeholder,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def dict(cls) -> DictStrAny:
|
|
53
|
+
return dataclasses.asdict(cls.__keyboard__)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def get_markup(cls) -> ReplyKeyboardMarkup:
|
|
57
|
+
return ReplyKeyboardMarkup.from_dict(cls.dict())
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def get_keyboard_remove(cls) -> ReplyKeyboardRemove:
|
|
61
|
+
return ReplyKeyboardRemove.from_data(
|
|
62
|
+
remove_keyboard=True,
|
|
63
|
+
selective=cls.__keyboard__.selective,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class StaticInlineKeyboard(BaseStaticKeyboard):
|
|
68
|
+
__keyboard__: RawKeyboard
|
|
69
|
+
|
|
70
|
+
def __init_subclass__(cls, *, max_in_row: int = 3) -> None:
|
|
71
|
+
cls.__max_in_row__ = max_in_row
|
|
72
|
+
cls.__keyboard__ = create_keyboard(cls)
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def dict(cls) -> DictStrAny:
|
|
76
|
+
return dict(inline_keyboard=cls.__keyboard__)
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def get_markup(cls) -> InlineKeyboardMarkup:
|
|
80
|
+
return InlineKeyboardMarkup.from_dict(cls.dict())
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
__all__ = ("StaticInlineKeyboard", "StaticKeyboard")
|
telegrinder/tools/lifespan.py
CHANGED
|
@@ -3,20 +3,15 @@ import dataclasses
|
|
|
3
3
|
import datetime
|
|
4
4
|
import typing
|
|
5
5
|
|
|
6
|
+
from telegrinder.modules import logger
|
|
7
|
+
from telegrinder.tools.aio import run_task
|
|
8
|
+
from telegrinder.tools.fullname import fullname
|
|
9
|
+
|
|
6
10
|
type CoroutineTask[T] = typing.Coroutine[typing.Any, typing.Any, T]
|
|
7
11
|
type CoroutineFunc[**P, T] = typing.Callable[P, CoroutineTask[T]]
|
|
8
12
|
type Task[**P, T] = CoroutineFunc[P, T] | CoroutineTask[T] | DelayedTask[typing.Callable[P, CoroutineTask[T]]]
|
|
9
13
|
|
|
10
14
|
|
|
11
|
-
def run_tasks(
|
|
12
|
-
tasks: list[CoroutineTask[typing.Any]],
|
|
13
|
-
/,
|
|
14
|
-
) -> None:
|
|
15
|
-
loop = asyncio.get_event_loop()
|
|
16
|
-
while tasks:
|
|
17
|
-
loop.run_until_complete(tasks.pop(0))
|
|
18
|
-
|
|
19
|
-
|
|
20
15
|
def to_coroutine_task[**P, T](task: Task[P, T], /) -> CoroutineTask[T]:
|
|
21
16
|
if asyncio.iscoroutinefunction(task) or isinstance(task, DelayedTask):
|
|
22
17
|
task = task()
|
|
@@ -25,12 +20,17 @@ def to_coroutine_task[**P, T](task: Task[P, T], /) -> CoroutineTask[T]:
|
|
|
25
20
|
return task
|
|
26
21
|
|
|
27
22
|
|
|
28
|
-
@dataclasses.dataclass
|
|
29
|
-
class DelayedTask[
|
|
30
|
-
|
|
23
|
+
@dataclasses.dataclass
|
|
24
|
+
class DelayedTask[Function: CoroutineFunc[..., typing.Any]]:
|
|
25
|
+
_cancelled: bool = dataclasses.field(default=False, init=False, repr=False)
|
|
26
|
+
_task: asyncio.Task[typing.Any] | None = dataclasses.field(default=None, init=False, repr=False)
|
|
27
|
+
|
|
28
|
+
function: Function
|
|
31
29
|
seconds: float | datetime.timedelta
|
|
32
30
|
repeat: bool = dataclasses.field(default=False, kw_only=True)
|
|
33
|
-
|
|
31
|
+
|
|
32
|
+
def __post_init__(self) -> None:
|
|
33
|
+
self.function.cancel = self.cancel
|
|
34
34
|
|
|
35
35
|
@property
|
|
36
36
|
def is_cancelled(self) -> bool:
|
|
@@ -38,68 +38,104 @@ class DelayedTask[Handler: CoroutineFunc[..., typing.Any]]:
|
|
|
38
38
|
|
|
39
39
|
@property
|
|
40
40
|
def delay(self) -> float:
|
|
41
|
-
return self.seconds if isinstance(self.seconds, int | float) else self.seconds.total_seconds()
|
|
41
|
+
return float(self.seconds) if isinstance(self.seconds, int | float) else self.seconds.total_seconds()
|
|
42
42
|
|
|
43
43
|
async def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
|
|
44
|
-
|
|
44
|
+
self._task = self._task or asyncio.current_task()
|
|
45
|
+
stopped = False
|
|
46
|
+
|
|
47
|
+
while not self._cancelled and not stopped:
|
|
45
48
|
await asyncio.sleep(self.delay)
|
|
46
|
-
if self.is_cancelled:
|
|
47
|
-
break
|
|
48
49
|
try:
|
|
49
|
-
await self.
|
|
50
|
+
await self.function(*args, **kwargs)
|
|
51
|
+
except BaseException:
|
|
52
|
+
logger.exception(
|
|
53
|
+
"Delayed task for function `{}` caught an exception, traceback message below:",
|
|
54
|
+
fullname(self.function),
|
|
55
|
+
)
|
|
50
56
|
finally:
|
|
51
|
-
|
|
52
|
-
break
|
|
57
|
+
stopped = not self.repeat
|
|
53
58
|
|
|
54
|
-
def cancel(self) ->
|
|
59
|
+
def cancel(self) -> bool:
|
|
55
60
|
if not self._cancelled:
|
|
56
|
-
self._cancelled = True
|
|
61
|
+
self._cancelled = True if self._task is None else self._task.cancel()
|
|
62
|
+
self._task = None
|
|
63
|
+
|
|
64
|
+
return self._cancelled
|
|
57
65
|
|
|
58
66
|
|
|
59
|
-
@dataclasses.dataclass(kw_only=True, slots=True,
|
|
67
|
+
@dataclasses.dataclass(kw_only=True, slots=True, repr=False)
|
|
60
68
|
class Lifespan:
|
|
69
|
+
_started: bool = dataclasses.field(default=False, init=False)
|
|
61
70
|
startup_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
62
71
|
shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
63
72
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
return task
|
|
73
|
+
def __repr__(self) -> str:
|
|
74
|
+
return "<{}: started={}>".format(fullname(self), self._started)
|
|
67
75
|
|
|
68
|
-
def
|
|
69
|
-
self.
|
|
70
|
-
|
|
76
|
+
def __add__(self, other: object, /) -> typing.Self:
|
|
77
|
+
if not isinstance(other, self.__class__):
|
|
78
|
+
return NotImplemented
|
|
71
79
|
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
return self.__class__(
|
|
81
|
+
startup_tasks=self.startup_tasks + other.startup_tasks,
|
|
82
|
+
shutdown_tasks=self.shutdown_tasks + other.shutdown_tasks,
|
|
83
|
+
)
|
|
74
84
|
|
|
75
|
-
def
|
|
76
|
-
|
|
85
|
+
def __iadd__(self, other: object, /) -> typing.Self:
|
|
86
|
+
if not isinstance(other, self.__class__):
|
|
87
|
+
return NotImplemented
|
|
88
|
+
|
|
89
|
+
self.startup_tasks.extend(other.startup_tasks)
|
|
90
|
+
self.shutdown_tasks.extend(other.shutdown_tasks)
|
|
91
|
+
return self
|
|
77
92
|
|
|
78
93
|
def __enter__(self) -> None:
|
|
79
94
|
self.start()
|
|
80
95
|
|
|
81
|
-
def __exit__(self) -> None:
|
|
96
|
+
def __exit__(self, *args: typing.Any) -> None:
|
|
82
97
|
self.shutdown()
|
|
83
98
|
|
|
84
99
|
async def __aenter__(self) -> None:
|
|
85
|
-
|
|
86
|
-
await task
|
|
100
|
+
await self._start()
|
|
87
101
|
|
|
88
|
-
async def __aexit__(self, *args) -> None:
|
|
89
|
-
|
|
90
|
-
await task
|
|
102
|
+
async def __aexit__(self, *args: typing.Any) -> None:
|
|
103
|
+
await self._shutdown()
|
|
91
104
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
@property
|
|
106
|
+
def started(self) -> bool:
|
|
107
|
+
return self._started
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
async def _run_tasks(tasks: list[CoroutineTask[typing.Any]], /) -> None:
|
|
111
|
+
while tasks:
|
|
112
|
+
await tasks.pop(0)
|
|
113
|
+
|
|
114
|
+
async def _start(self) -> None:
|
|
115
|
+
if not self._started:
|
|
116
|
+
logger.debug("Running lifespan startup tasks")
|
|
117
|
+
self._started = True
|
|
118
|
+
await self._run_tasks(self.startup_tasks)
|
|
119
|
+
|
|
120
|
+
async def _shutdown(self) -> None:
|
|
121
|
+
if self._started:
|
|
122
|
+
logger.debug("Running lifespan shutdown tasks")
|
|
123
|
+
self._started = False
|
|
124
|
+
await self._run_tasks(self.shutdown_tasks)
|
|
125
|
+
|
|
126
|
+
def start(self) -> None:
|
|
127
|
+
run_task(self._start())
|
|
128
|
+
|
|
129
|
+
def shutdown(self) -> None:
|
|
130
|
+
run_task(self._shutdown())
|
|
131
|
+
|
|
132
|
+
def on_startup[**P, T](self, task: Task[P, T], /) -> Task[P, T]:
|
|
133
|
+
self.startup_tasks.append(to_coroutine_task(task))
|
|
134
|
+
return task
|
|
135
|
+
|
|
136
|
+
def on_shutdown[**P, T](self, task: Task[P, T], /) -> Task[P, T]:
|
|
137
|
+
self.shutdown_tasks.append(to_coroutine_task(task))
|
|
138
|
+
return task
|
|
97
139
|
|
|
98
140
|
|
|
99
|
-
__all__ = (
|
|
100
|
-
"CoroutineTask",
|
|
101
|
-
"DelayedTask",
|
|
102
|
-
"Lifespan",
|
|
103
|
-
"run_tasks",
|
|
104
|
-
"to_coroutine_task",
|
|
105
|
-
)
|
|
141
|
+
__all__ = ("CoroutineTask", "DelayedTask", "Lifespan", "run_task", "to_coroutine_task")
|
|
@@ -13,17 +13,20 @@ class LimitedDict[Key, Value](UserDict[Key, Value]):
|
|
|
13
13
|
was reached, otherwise None.
|
|
14
14
|
"""
|
|
15
15
|
deleted_item = None
|
|
16
|
+
|
|
16
17
|
if len(self.queue) >= self.maxlimit:
|
|
17
18
|
deleted_item = self.pop(self.queue.popleft(), None)
|
|
19
|
+
|
|
18
20
|
if key not in self.queue:
|
|
19
21
|
self.queue.append(key)
|
|
22
|
+
|
|
20
23
|
super().__setitem__(key, value)
|
|
21
24
|
return deleted_item
|
|
22
25
|
|
|
23
26
|
def __setitem__(self, key: Key, value: Value, /) -> None:
|
|
24
27
|
self.set(key, value)
|
|
25
28
|
|
|
26
|
-
def __delitem__(self, key: Key) -> None:
|
|
29
|
+
def __delitem__(self, key: Key, /) -> None:
|
|
27
30
|
if key in self.queue:
|
|
28
31
|
self.queue.remove(key)
|
|
29
32
|
return super().__delitem__(key)
|