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
|
@@ -6,9 +6,9 @@ import typing_extensions as typing
|
|
|
6
6
|
from fntypes.co import Error, Nothing, Ok, Option, Result, Some
|
|
7
7
|
|
|
8
8
|
from telegrinder.modules import logger
|
|
9
|
-
from telegrinder.msgspec_utils import
|
|
10
|
-
|
|
11
|
-
from .abc import ABCGlobalContext, CtxVar, CtxVariable, GlobalCtxVar
|
|
9
|
+
from telegrinder.msgspec_utils import convert
|
|
10
|
+
from telegrinder.tools.fullname import fullname
|
|
11
|
+
from telegrinder.tools.global_context.abc import NODEFAULT, ABCGlobalContext, CtxVar, CtxVariable, GlobalCtxVar
|
|
12
12
|
|
|
13
13
|
T = typing.TypeVar("T")
|
|
14
14
|
F = typing.TypeVar("F", bound=typing.Callable)
|
|
@@ -23,7 +23,7 @@ else:
|
|
|
23
23
|
def type_check(value: object, value_type: type[T]) -> typing.TypeGuard[T]:
|
|
24
24
|
if value_type in (typing.Any, object):
|
|
25
25
|
return True
|
|
26
|
-
match
|
|
26
|
+
match convert(value, value_type):
|
|
27
27
|
case Ok(v):
|
|
28
28
|
return type(value) is type(v)
|
|
29
29
|
case Error(_):
|
|
@@ -35,7 +35,9 @@ def is_dunder(name: str) -> bool:
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def get_orig_class(obj: T) -> type[T]:
|
|
38
|
-
|
|
38
|
+
if "__orig_class__" not in obj.__dict__:
|
|
39
|
+
return type(obj)
|
|
40
|
+
return obj.__dict__["__orig_class__"]
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
def root_protection(func: F) -> F:
|
|
@@ -65,19 +67,82 @@ def root_protection(func: F) -> F:
|
|
|
65
67
|
return wrapper # type: ignore
|
|
66
68
|
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
@typing.overload
|
|
71
|
+
def ctx_var() -> typing.Any: ...
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@typing.overload
|
|
75
|
+
def ctx_var(
|
|
76
|
+
*,
|
|
77
|
+
init: bool = ...,
|
|
78
|
+
const: bool = ...,
|
|
79
|
+
) -> typing.Any: ...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@typing.overload
|
|
83
|
+
def ctx_var(
|
|
84
|
+
*,
|
|
85
|
+
default: typing.Any,
|
|
86
|
+
init: bool = ...,
|
|
87
|
+
const: bool = ...,
|
|
88
|
+
) -> typing.Any: ...
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@typing.overload
|
|
92
|
+
def ctx_var(
|
|
93
|
+
*,
|
|
94
|
+
default_factory: typing.Callable[[], typing.Any],
|
|
95
|
+
init: bool = ...,
|
|
96
|
+
const: bool = ...,
|
|
97
|
+
) -> typing.Any: ...
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def ctx_var(
|
|
101
|
+
*,
|
|
102
|
+
default: typing.Any = NODEFAULT,
|
|
103
|
+
default_factory: typing.Any = NODEFAULT,
|
|
104
|
+
const: bool = False,
|
|
105
|
+
**_: typing.Any,
|
|
106
|
+
) -> typing.Any:
|
|
69
107
|
"""Example:
|
|
70
108
|
```
|
|
71
109
|
class MyCtx(GlobalContext):
|
|
110
|
+
__ctx_name__ = "my_ctx"
|
|
111
|
+
|
|
72
112
|
name: str
|
|
73
|
-
URL: str = ctx_var("https://google.com",
|
|
113
|
+
URL: typing.Final[str] = ctx_var(default="https://google.com", init=False, const=True)
|
|
74
114
|
|
|
75
|
-
ctx = MyCtx(name=
|
|
115
|
+
ctx = MyCtx(name="John")
|
|
76
116
|
ctx.URL #: 'https://google.com'
|
|
77
117
|
ctx.URL = '...' #: type checking error & exception 'TypeError'
|
|
78
118
|
```
|
|
79
119
|
"""
|
|
80
|
-
return
|
|
120
|
+
return CtxVar(value=default, factory=default_factory, const=const)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def runtime_init[T: ABCGlobalContext](cls: type[T], /) -> type[T]:
|
|
124
|
+
r'''Initialization the global context at runtime.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
@runtime_init
|
|
128
|
+
class Box(ABCGlobalContext):
|
|
129
|
+
__ctx_name__ = "box"
|
|
130
|
+
|
|
131
|
+
cookies: list[Cookie] = ctx_var(default_factory=lambda: [ChocolateCookie()], init=False)
|
|
132
|
+
"""
|
|
133
|
+
init=False means that when calling the class constructor it will not be necessary
|
|
134
|
+
to pass this field to the class constructor, because it will already be initialized.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
box = Box() # So, this global context has already been initialized, so calling the class
|
|
138
|
+
# immediately returns an initialized instance of this class from the memory storage.
|
|
139
|
+
|
|
140
|
+
box.cookies.append(OatmealCookie())
|
|
141
|
+
print(box.cookies) # [<ChocolateCookie>, <OatmealCookie>]
|
|
142
|
+
```
|
|
143
|
+
'''
|
|
144
|
+
cls() # Init an instance of the global context.
|
|
145
|
+
return cls
|
|
81
146
|
|
|
82
147
|
|
|
83
148
|
@dataclasses.dataclass(frozen=True, eq=False, slots=True)
|
|
@@ -98,7 +163,7 @@ class Storage:
|
|
|
98
163
|
)
|
|
99
164
|
|
|
100
165
|
def __repr__(self) -> str:
|
|
101
|
-
return "<
|
|
166
|
+
return "<Storage: %s>" % ", ".join(repr(x) for x in self._storage)
|
|
102
167
|
|
|
103
168
|
@property
|
|
104
169
|
def storage(self) -> dict[str, "GlobalContext"]:
|
|
@@ -118,28 +183,39 @@ class Storage:
|
|
|
118
183
|
@typing.dataclass_transform(
|
|
119
184
|
kw_only_default=True,
|
|
120
185
|
order_default=True,
|
|
186
|
+
frozen_default=False,
|
|
121
187
|
field_specifiers=(ctx_var,),
|
|
122
188
|
)
|
|
123
189
|
class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, GlobalCtxVar[CtxValueT]]):
|
|
124
|
-
"""
|
|
190
|
+
"""This is class to store the context globally.
|
|
125
191
|
|
|
192
|
+
`GlobalContext` is a dictionary with additional methods for working with context.
|
|
193
|
+
|
|
194
|
+
Example:
|
|
126
195
|
```
|
|
127
196
|
ctx = GlobalContext()
|
|
128
197
|
ctx["client"] = Client()
|
|
129
|
-
ctx.
|
|
198
|
+
ctx.host = CtxVar("128.0.0.7:8888", const=True)
|
|
130
199
|
|
|
131
200
|
def request():
|
|
132
201
|
data = {"user": "root_user", "password": "secret_password"}
|
|
133
|
-
ctx.client.request(ctx.
|
|
202
|
+
ctx.client.request(ctx.host + "/login", data)
|
|
203
|
+
```
|
|
204
|
+
|
|
134
205
|
"""
|
|
135
206
|
|
|
136
207
|
__ctx_name__: str | None
|
|
208
|
+
"""Global context name."""
|
|
209
|
+
|
|
137
210
|
__storage__: typing.ClassVar[Storage] = Storage()
|
|
211
|
+
"""Storage memory; this is the storage where all initialized global contexts are stored."""
|
|
212
|
+
|
|
138
213
|
__root_attributes__: typing.ClassVar[tuple[RootAttr, ...]] = (
|
|
139
|
-
RootAttr("__ctx_name__"),
|
|
140
|
-
RootAttr("__root_attributes__"),
|
|
141
|
-
RootAttr("__storage__"),
|
|
214
|
+
RootAttr(name="__ctx_name__"),
|
|
215
|
+
RootAttr(name="__root_attributes__"),
|
|
216
|
+
RootAttr(name="__storage__"),
|
|
142
217
|
)
|
|
218
|
+
"""The sequence of root attributes of this class including this attribute."""
|
|
143
219
|
|
|
144
220
|
def __new__(
|
|
145
221
|
cls,
|
|
@@ -148,13 +224,15 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
148
224
|
**variables: typing.Any | CtxVar[CtxValueT],
|
|
149
225
|
) -> typing.Self:
|
|
150
226
|
"""Create or get from storage a new `GlobalContext` object."""
|
|
151
|
-
if not
|
|
227
|
+
if cls is not GlobalContext:
|
|
152
228
|
defaults = {}
|
|
153
229
|
for name in cls.__annotations__:
|
|
154
230
|
if name in cls.__dict__ and name not in cls.__root_attributes__:
|
|
155
231
|
defaults[name] = getattr(cls, name)
|
|
156
232
|
delattr(cls, name)
|
|
157
|
-
|
|
233
|
+
|
|
234
|
+
default_ = defaults[name]
|
|
235
|
+
if isinstance(default_, CtxVar) and default_.const:
|
|
158
236
|
variables.pop(name, None)
|
|
159
237
|
|
|
160
238
|
variables = defaults | variables
|
|
@@ -184,36 +262,55 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
184
262
|
self.set_context_variables(variables)
|
|
185
263
|
|
|
186
264
|
def __repr__(self) -> str:
|
|
187
|
-
return "<{}
|
|
188
|
-
f"{self
|
|
189
|
-
", ".join(
|
|
265
|
+
return "<{} contains variables: {{ {} }}>".format(
|
|
266
|
+
f"{fullname(self)}@{self.ctx_name}",
|
|
267
|
+
", ".join(var_name for var_name in self),
|
|
190
268
|
)
|
|
191
269
|
|
|
192
|
-
def __eq__(self, __value:
|
|
270
|
+
def __eq__(self, __value: object) -> bool:
|
|
193
271
|
"""Returns True if the names of context stores
|
|
194
272
|
that use self and __value instances are equivalent.
|
|
195
273
|
"""
|
|
196
|
-
|
|
274
|
+
if not isinstance(__value, type(self)):
|
|
275
|
+
return NotImplemented
|
|
276
|
+
return self.__ctx_name__ == __value.__ctx_name__ and self == __value
|
|
197
277
|
|
|
198
|
-
def __setitem__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]):
|
|
278
|
+
def __setitem__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]) -> None:
|
|
199
279
|
if is_dunder(__name):
|
|
200
|
-
raise NameError("Cannot set a context variable with dunder name.")
|
|
280
|
+
raise NameError("Cannot set a context variable with a dunder name.")
|
|
281
|
+
|
|
201
282
|
var = self.get(__name)
|
|
202
|
-
if var and var.
|
|
283
|
+
if var and (var.value.const and var.value.value is not NODEFAULT):
|
|
203
284
|
raise TypeError(f"Unable to set variable {__name!r}, because it's a constant.")
|
|
204
|
-
|
|
285
|
+
|
|
286
|
+
dict.__setitem__(
|
|
287
|
+
self,
|
|
288
|
+
__name,
|
|
289
|
+
GlobalCtxVar.from_var(
|
|
290
|
+
name=__name,
|
|
291
|
+
ctx_value=__value,
|
|
292
|
+
const=var.map(lambda var: var.const).unwrap_or(False),
|
|
293
|
+
),
|
|
294
|
+
)
|
|
205
295
|
|
|
206
296
|
def __getitem__(self, __name: str) -> CtxValueT:
|
|
207
|
-
|
|
297
|
+
value = self.get(__name).unwrap().value
|
|
298
|
+
if value is NODEFAULT:
|
|
299
|
+
raise NameError(f"Variable {__name!r} is not defined in {self.ctx_name!r}.")
|
|
300
|
+
return value
|
|
208
301
|
|
|
209
|
-
def __delitem__(self, __name: str):
|
|
302
|
+
def __delitem__(self, __name: str) -> None:
|
|
210
303
|
var = self.get(__name).unwrap()
|
|
211
304
|
if var.const:
|
|
212
305
|
raise TypeError(f"Unable to delete variable {__name!r}, because it's a constant.")
|
|
306
|
+
|
|
307
|
+
if var.value is NODEFAULT:
|
|
308
|
+
raise NameError(f"Variable {__name!r} is not defined in {self.ctx_name!r}.")
|
|
309
|
+
|
|
213
310
|
dict.__delitem__(self, __name)
|
|
214
311
|
|
|
215
312
|
@root_protection
|
|
216
|
-
def __setattr__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]):
|
|
313
|
+
def __setattr__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]) -> None:
|
|
217
314
|
"""Setting a context variable."""
|
|
218
315
|
if is_dunder(__name):
|
|
219
316
|
return object.__setattr__(self, __name, __value)
|
|
@@ -235,14 +332,12 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
235
332
|
|
|
236
333
|
@property
|
|
237
334
|
def ctx_name(self) -> str:
|
|
238
|
-
"""
|
|
239
|
-
return self.__ctx_name__ or "<Unnamed
|
|
335
|
+
"""Global context name."""
|
|
336
|
+
return self.__ctx_name__ or "<Unnamed global context at %#x>" % id(self)
|
|
240
337
|
|
|
241
338
|
@classmethod
|
|
242
339
|
def is_root_attribute(cls, name: str) -> bool:
|
|
243
|
-
"""Returns True if
|
|
244
|
-
otherwise False.
|
|
245
|
-
"""
|
|
340
|
+
"""Returns True if name is a root attribute."""
|
|
246
341
|
return name in cls.__root_attributes__
|
|
247
342
|
|
|
248
343
|
def set_context_variables(self, variables: typing.Mapping[str, CtxValueT | CtxVariable[CtxValueT]]) -> None:
|
|
@@ -314,11 +409,14 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
314
409
|
"""Get context variable by name."""
|
|
315
410
|
var_value_type = typing.Any if var_value_type is object else var_value_type
|
|
316
411
|
generic_types = typing.get_args(get_orig_class(self))
|
|
412
|
+
|
|
317
413
|
if generic_types and var_value_type is object:
|
|
318
414
|
var_value_type = generic_types[0]
|
|
415
|
+
|
|
319
416
|
var = dict.get(self, var_name)
|
|
320
417
|
if var is None:
|
|
321
418
|
return Nothing()
|
|
419
|
+
|
|
322
420
|
assert type_check(var.value, var_value_type), (
|
|
323
421
|
"Context variable value type of {!r} does not correspond to the expected type {!r}.".format(
|
|
324
422
|
type(var.value).__name__,
|
|
@@ -350,6 +448,7 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
350
448
|
var = self.get(old_var_name).unwrap()
|
|
351
449
|
if var.const:
|
|
352
450
|
return Error(f"Unable to rename variable {old_var_name!r}, because it's a constant.")
|
|
451
|
+
|
|
353
452
|
del self[old_var_name]
|
|
354
453
|
self[new_var_name] = var.value
|
|
355
454
|
return Ok(_())
|
|
@@ -360,6 +459,7 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
360
459
|
"""
|
|
361
460
|
if not self:
|
|
362
461
|
return
|
|
462
|
+
|
|
363
463
|
if include_consts:
|
|
364
464
|
logger.warning(
|
|
365
465
|
"Constants from the global context {!r} have been cleaned up!",
|
|
@@ -375,9 +475,11 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
375
475
|
"""Delete context by `ctx_name`."""
|
|
376
476
|
if not self.__ctx_name__:
|
|
377
477
|
return Error("Cannot delete unnamed context.")
|
|
478
|
+
|
|
378
479
|
ctx = self.__storage__.get(self.ctx_name).unwrap()
|
|
379
480
|
dict.clear(ctx)
|
|
380
481
|
self.__storage__.delete(self.ctx_name)
|
|
482
|
+
|
|
381
483
|
logger.warning(f"Global context {self.ctx_name!r} has been deleted!")
|
|
382
484
|
return Ok(_())
|
|
383
485
|
|
|
@@ -391,8 +493,5 @@ __all__ = (
|
|
|
391
493
|
"RootAttr",
|
|
392
494
|
"Storage",
|
|
393
495
|
"ctx_var",
|
|
394
|
-
"
|
|
395
|
-
"is_dunder",
|
|
396
|
-
"root_protection",
|
|
397
|
-
"type_check",
|
|
496
|
+
"runtime_init",
|
|
398
497
|
)
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from telegrinder.tools.keyboard.base import ABCKeyboard, ABCStaticKeyboard, BaseKeyboard, BaseStaticKeyboard
|
|
2
|
+
from telegrinder.tools.keyboard.buttons.base import (
|
|
3
|
+
BaseButton,
|
|
4
|
+
BaseStaticButton,
|
|
5
|
+
)
|
|
6
|
+
from telegrinder.tools.keyboard.buttons.buttons import (
|
|
7
|
+
Button,
|
|
8
|
+
InlineButton,
|
|
9
|
+
)
|
|
10
|
+
from telegrinder.tools.keyboard.buttons.static_buttons import (
|
|
11
|
+
StaticButton,
|
|
12
|
+
StaticInlineButton,
|
|
13
|
+
)
|
|
14
|
+
from telegrinder.tools.keyboard.buttons.tools import RowButtons
|
|
15
|
+
from telegrinder.tools.keyboard.data import KeyboardModel
|
|
16
|
+
from telegrinder.tools.keyboard.keyboard import InlineKeyboard, Keyboard
|
|
17
|
+
from telegrinder.tools.keyboard.static_keyboard import (
|
|
18
|
+
StaticInlineKeyboard,
|
|
19
|
+
StaticKeyboard,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = (
|
|
23
|
+
"ABCKeyboard",
|
|
24
|
+
"ABCStaticKeyboard",
|
|
25
|
+
"BaseButton",
|
|
26
|
+
"BaseKeyboard",
|
|
27
|
+
"BaseStaticButton",
|
|
28
|
+
"BaseStaticKeyboard",
|
|
29
|
+
"Button",
|
|
30
|
+
"InlineButton",
|
|
31
|
+
"InlineKeyboard",
|
|
32
|
+
"Keyboard",
|
|
33
|
+
"KeyboardModel",
|
|
34
|
+
"RowButtons",
|
|
35
|
+
"StaticButton",
|
|
36
|
+
"StaticInlineButton",
|
|
37
|
+
"StaticInlineKeyboard",
|
|
38
|
+
"StaticKeyboard",
|
|
39
|
+
)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from telegrinder.tools.fullname import fullname
|
|
7
|
+
from telegrinder.tools.keyboard.buttons.base import BaseButton, BaseStaticButton
|
|
8
|
+
from telegrinder.tools.keyboard.buttons.tools import RowButtons
|
|
9
|
+
|
|
10
|
+
if typing.TYPE_CHECKING:
|
|
11
|
+
from telegrinder.types.objects import InlineKeyboardMarkup, ReplyKeyboardMarkup
|
|
12
|
+
|
|
13
|
+
type DictStrAny = dict[str, typing.Any]
|
|
14
|
+
type AnyMarkup = ReplyKeyboardMarkup | InlineKeyboardMarkup
|
|
15
|
+
type RawKeyboard = list[list[DictStrAny]]
|
|
16
|
+
type Button = DictStrAny | list[DictStrAny] | BaseButton | RowButtons[BaseButton]
|
|
17
|
+
|
|
18
|
+
KEYBOARD_BUTTON_CLASS_KEY: typing.Final[str] = "_button_class"
|
|
19
|
+
NO_VALUE: typing.Final[object] = object()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _has_under(name: str, /) -> bool:
|
|
23
|
+
return name.startswith("_")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_buttons(cls_: type[ABCStaticKeyboard], /) -> dict[str, BaseStaticButton]:
|
|
27
|
+
button_class = cls_.get_button_class()
|
|
28
|
+
return {k: v for k, v in dict(vars(cls_)).items() if isinstance(v, button_class)}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def copy_keyboard(keyboard: RawKeyboard, /) -> RawKeyboard:
|
|
32
|
+
return [row.copy() for row in keyboard if row]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ABCKeyboard(abc.ABC):
|
|
36
|
+
keyboard: RawKeyboard
|
|
37
|
+
|
|
38
|
+
@abc.abstractmethod
|
|
39
|
+
def dict(self) -> DictStrAny:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
@abc.abstractmethod
|
|
43
|
+
def get_markup(self) -> AnyMarkup:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abc.abstractmethod
|
|
47
|
+
def copy(self, **with_changes: typing.Any) -> typing.Self:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def add(self, button: Button, /) -> typing.Self:
|
|
51
|
+
if not self.keyboard:
|
|
52
|
+
self.row()
|
|
53
|
+
|
|
54
|
+
if isinstance(button, RowButtons):
|
|
55
|
+
self.keyboard[-1].extend(button.get_data())
|
|
56
|
+
if button.auto_row:
|
|
57
|
+
self.row()
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
if isinstance(button, list):
|
|
61
|
+
self.keyboard[-1].extend(button)
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
self.keyboard[-1].append(button if isinstance(button, dict) else button.get_data())
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def row(self) -> typing.Self:
|
|
68
|
+
if len(self.keyboard) and not len(self.keyboard[-1]):
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
self.keyboard.append([])
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
def format_text(self, **format_data: typing.Any) -> typing.Self:
|
|
75
|
+
copy_keyboard = self.copy()
|
|
76
|
+
|
|
77
|
+
for row in self.keyboard:
|
|
78
|
+
for button in row:
|
|
79
|
+
button["text"] = button["text"].format(**format_data)
|
|
80
|
+
|
|
81
|
+
return copy_keyboard
|
|
82
|
+
|
|
83
|
+
def merge(self, other: typing.Self, /) -> typing.Self:
|
|
84
|
+
self.keyboard.extend(copy_keyboard(other.keyboard))
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
def merge_to_last_row(self, other: typing.Self, /) -> typing.Self:
|
|
88
|
+
kb_len = len(other.keyboard)
|
|
89
|
+
|
|
90
|
+
for index, row in enumerate(copy_keyboard(other.keyboard), start=1):
|
|
91
|
+
for button in row:
|
|
92
|
+
self.keyboard[-1].append(button)
|
|
93
|
+
|
|
94
|
+
if index < kb_len:
|
|
95
|
+
self.keyboard.append([])
|
|
96
|
+
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class StaticKeyboardMeta(type):
|
|
101
|
+
if not typing.TYPE_CHECKING:
|
|
102
|
+
|
|
103
|
+
def __getattr__(cls, name, /):
|
|
104
|
+
if not _has_under(name):
|
|
105
|
+
buttons = _get_buttons(cls)
|
|
106
|
+
if name in buttons:
|
|
107
|
+
return buttons[name]
|
|
108
|
+
return super().__getattribute__(name)
|
|
109
|
+
|
|
110
|
+
def __setattr__(cls, name: str, value: typing.Any, /) -> None:
|
|
111
|
+
if not _has_under(name) and name in _get_buttons(cls): # type: ignore
|
|
112
|
+
raise AttributeError(f"Cannot reassing attribute {name!r}.")
|
|
113
|
+
return super().__setattr__(name, value)
|
|
114
|
+
|
|
115
|
+
def __delattr__(cls, name: str, /) -> None:
|
|
116
|
+
if not _has_under(name) and name in _get_buttons(cls): # type: ignore
|
|
117
|
+
raise AttributeError(f"Cannot delete attribute {name!r}.")
|
|
118
|
+
return super().__delattr__(name)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ABCStaticKeyboardMeta(abc.ABCMeta, StaticKeyboardMeta):
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ABCStaticKeyboard(metaclass=ABCStaticKeyboardMeta):
|
|
126
|
+
__keyboard__: typing.Any
|
|
127
|
+
|
|
128
|
+
@abc.abstractmethod
|
|
129
|
+
def dict(self) -> DictStrAny:
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
@abc.abstractmethod
|
|
133
|
+
def get_markup(self) -> AnyMarkup:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def get_button_class(cls) -> type[BaseStaticButton]:
|
|
138
|
+
sentinel = object()
|
|
139
|
+
button_class = getattr(cls, KEYBOARD_BUTTON_CLASS_KEY, sentinel)
|
|
140
|
+
|
|
141
|
+
if button_class not in (sentinel, NO_VALUE):
|
|
142
|
+
return typing.cast("type[BaseStaticButton]", button_class)
|
|
143
|
+
|
|
144
|
+
if button_class is not NO_VALUE:
|
|
145
|
+
for obj in cls.__dict__.values():
|
|
146
|
+
if isinstance(obj, BaseStaticButton):
|
|
147
|
+
button_class = type(obj)
|
|
148
|
+
setattr(cls, KEYBOARD_BUTTON_CLASS_KEY, button_class)
|
|
149
|
+
return button_class
|
|
150
|
+
|
|
151
|
+
setattr(cls, KEYBOARD_BUTTON_CLASS_KEY, NO_VALUE)
|
|
152
|
+
raise ValueError(f"Static keyboard {fullname(cls)!r} has no static buttons.")
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def get_buttons(cls) -> dict[str, BaseStaticButton]:
|
|
156
|
+
return _get_buttons(cls)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
__all__ = ("ABCKeyboard", "ABCStaticKeyboard")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from telegrinder.tools.keyboard.abc import ABCKeyboard, ABCStaticKeyboard, AnyMarkup, DictStrAny
|
|
7
|
+
from telegrinder.tools.keyboard.buttons.base import BaseButton
|
|
8
|
+
|
|
9
|
+
if typing.TYPE_CHECKING:
|
|
10
|
+
from telegrinder.tools.keyboard.buttons.base import BaseStaticButton
|
|
11
|
+
|
|
12
|
+
KEYBOARD_BUTTONS_KEY: typing.Final[str] = "_buttons"
|
|
13
|
+
NO_VALUE: typing.Final[object] = object()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _is_implemented[T: ABCKeyboard](
|
|
17
|
+
value: object,
|
|
18
|
+
kb_class: type[T],
|
|
19
|
+
/,
|
|
20
|
+
) -> typing.TypeGuard[T | BaseButton]:
|
|
21
|
+
return isinstance(value, BaseButton | kb_class)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaseKeyboard(ABCKeyboard, abc.ABC):
|
|
25
|
+
@abc.abstractmethod
|
|
26
|
+
def dict(self) -> DictStrAny:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@abc.abstractmethod
|
|
30
|
+
def get_markup(self) -> AnyMarkup:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abc.abstractmethod
|
|
34
|
+
def copy(self, **with_changes: typing.Any) -> typing.Self:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def __and__(self, other: object, /) -> typing.Self:
|
|
38
|
+
if not _is_implemented(other, type(self)):
|
|
39
|
+
return NotImplemented
|
|
40
|
+
return self.add(other) if isinstance(other, BaseButton) else self.merge(other)
|
|
41
|
+
|
|
42
|
+
def __or__(self, other: object, /) -> typing.Self:
|
|
43
|
+
if not _is_implemented(other, type(self)):
|
|
44
|
+
return NotImplemented
|
|
45
|
+
|
|
46
|
+
kb = self.row()
|
|
47
|
+
return kb.add(other) if isinstance(other, BaseButton) else kb.merge_to_last_row(other)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class BaseStaticKeyboard(ABCStaticKeyboard, abc.ABC):
|
|
51
|
+
__max_in_row__: int
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def get_buttons(cls) -> dict[str, BaseStaticButton]:
|
|
55
|
+
if (buttons := cls._get_secret_value(KEYBOARD_BUTTONS_KEY)) is not NO_VALUE:
|
|
56
|
+
return buttons
|
|
57
|
+
return cls._set_secret_value(KEYBOARD_BUTTONS_KEY, super().get_buttons())
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def _set_secret_value[V](cls, key: str, value: V, /) -> V:
|
|
61
|
+
setattr(cls, key, value)
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def _get_secret_value(cls, key: str, /) -> typing.Any:
|
|
66
|
+
return getattr(cls, key, NO_VALUE)
|
|
67
|
+
|
|
68
|
+
@abc.abstractmethod
|
|
69
|
+
def dict(self) -> DictStrAny:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
@abc.abstractmethod
|
|
73
|
+
def get_markup(self) -> AnyMarkup:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
__all__ = ("BaseKeyboard", "BaseStaticKeyboard")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from telegrinder.tools.keyboard.buttons.base import BaseButton, BaseStaticButton
|
|
2
|
+
from telegrinder.tools.keyboard.buttons.buttons import Button, InlineButton
|
|
3
|
+
from telegrinder.tools.keyboard.buttons.static_buttons import StaticButton, StaticInlineButton
|
|
4
|
+
from telegrinder.tools.keyboard.buttons.tools import RowButtons
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
"BaseButton",
|
|
8
|
+
"BaseStaticButton",
|
|
9
|
+
"Button",
|
|
10
|
+
"InlineButton",
|
|
11
|
+
"RowButtons",
|
|
12
|
+
"StaticButton",
|
|
13
|
+
"StaticInlineButton",
|
|
14
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from telegrinder.tools.fullname import fullname
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseButton:
|
|
8
|
+
def get_data(self) -> dict[str, typing.Any]:
|
|
9
|
+
assert dataclasses.is_dataclass(self), f"{fullname(self)} is not a dataclass."
|
|
10
|
+
return {k: v for k, v in dataclasses.asdict(self).items() if v is not None}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclasses.dataclass(kw_only=True)
|
|
14
|
+
class BaseStaticButton(BaseButton):
|
|
15
|
+
row: bool = dataclasses.field(default=False, repr=False)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ("BaseButton", "BaseStaticButton")
|