telegrinder 0.3.0__py3-none-any.whl → 0.3.0.post2__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 +1 -1
- telegrinder/bot/cute_types/callback_query.py +2 -14
- telegrinder/bot/cute_types/chat_join_request.py +1 -1
- telegrinder/bot/cute_types/chat_member_updated.py +1 -1
- telegrinder/bot/cute_types/inline_query.py +1 -6
- telegrinder/bot/cute_types/message.py +1 -21
- telegrinder/bot/cute_types/update.py +1 -1
- telegrinder/bot/dispatch/abc.py +45 -3
- telegrinder/bot/dispatch/dispatch.py +8 -8
- telegrinder/bot/dispatch/handler/func.py +8 -10
- telegrinder/bot/dispatch/process.py +1 -1
- telegrinder/bot/dispatch/view/base.py +31 -20
- telegrinder/bot/dispatch/view/raw.py +20 -16
- telegrinder/bot/dispatch/waiter_machine/actions.py +3 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +15 -18
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +21 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +15 -16
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +6 -6
- telegrinder/bot/dispatch/waiter_machine/machine.py +26 -32
- telegrinder/bot/dispatch/waiter_machine/short_state.py +10 -4
- telegrinder/bot/rules/abc.py +37 -7
- telegrinder/bot/rules/adapter/raw_update.py +1 -3
- telegrinder/bot/rules/inline.py +1 -2
- telegrinder/bot/rules/markup.py +5 -2
- telegrinder/bot/rules/start.py +1 -1
- telegrinder/bot/rules/text.py +5 -3
- telegrinder/bot/scenario/checkbox.py +1 -1
- telegrinder/msgspec_utils.py +11 -3
- telegrinder/node/attachment.py +6 -6
- telegrinder/node/base.py +17 -11
- telegrinder/node/callback_query.py +1 -1
- telegrinder/node/command.py +1 -1
- telegrinder/node/composer.py +5 -2
- telegrinder/node/container.py +1 -1
- telegrinder/node/event.py +1 -1
- telegrinder/node/message.py +1 -1
- telegrinder/node/polymorphic.py +6 -3
- telegrinder/node/rule.py +1 -1
- telegrinder/node/source.py +5 -7
- telegrinder/node/text.py +2 -2
- telegrinder/node/tools/generator.py +1 -2
- telegrinder/node/update.py +3 -3
- telegrinder/rules.py +2 -0
- telegrinder/tools/buttons.py +4 -4
- telegrinder/tools/error_handler/abc.py +7 -7
- telegrinder/tools/error_handler/error_handler.py +58 -47
- telegrinder/tools/formatting/html.py +0 -2
- telegrinder/tools/functional.py +3 -0
- telegrinder/tools/global_context/telegrinder_ctx.py +2 -0
- telegrinder/tools/i18n/__init__.py +1 -1
- telegrinder/tools/i18n/{base.py → abc.py} +0 -0
- telegrinder/tools/i18n/middleware/__init__.py +1 -1
- telegrinder/tools/i18n/middleware/{base.py → abc.py} +3 -2
- telegrinder/tools/i18n/simple.py +11 -12
- telegrinder/tools/keyboard.py +9 -9
- {telegrinder-0.3.0.dist-info → telegrinder-0.3.0.post2.dist-info}/METADATA +1 -1
- {telegrinder-0.3.0.dist-info → telegrinder-0.3.0.post2.dist-info}/RECORD +59 -59
- {telegrinder-0.3.0.dist-info → telegrinder-0.3.0.post2.dist-info}/LICENSE +0 -0
- {telegrinder-0.3.0.dist-info → telegrinder-0.3.0.post2.dist-info}/WHEEL +0 -0
telegrinder/node/source.py
CHANGED
|
@@ -21,7 +21,7 @@ class Source(Polymorphic, DataNode):
|
|
|
21
21
|
thread_id: Option[int] = dataclasses.field(default_factory=lambda: Nothing())
|
|
22
22
|
|
|
23
23
|
@impl
|
|
24
|
-
|
|
24
|
+
def compose_message(cls, message: MessageNode) -> typing.Self:
|
|
25
25
|
return cls(
|
|
26
26
|
api=message.ctx_api,
|
|
27
27
|
chat=message.chat,
|
|
@@ -30,7 +30,7 @@ class Source(Polymorphic, DataNode):
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
@impl
|
|
33
|
-
|
|
33
|
+
def compose_callback_query(cls, callback_query: CallbackQueryNode) -> typing.Self:
|
|
34
34
|
return cls(
|
|
35
35
|
api=callback_query.ctx_api,
|
|
36
36
|
chat=callback_query.chat.expect(ComposeError("CallbackQueryNode has no chat")),
|
|
@@ -39,9 +39,7 @@ class Source(Polymorphic, DataNode):
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
@impl
|
|
42
|
-
|
|
43
|
-
cls, chat_join_request: EventNode[ChatJoinRequestCute]
|
|
44
|
-
) -> typing.Self:
|
|
42
|
+
def compose_chat_join_request(cls, chat_join_request: EventNode[ChatJoinRequestCute]) -> typing.Self:
|
|
45
43
|
return cls(
|
|
46
44
|
api=chat_join_request.ctx_api,
|
|
47
45
|
chat=chat_join_request.chat,
|
|
@@ -60,13 +58,13 @@ class Source(Polymorphic, DataNode):
|
|
|
60
58
|
|
|
61
59
|
class ChatSource(ScalarNode, Chat):
|
|
62
60
|
@classmethod
|
|
63
|
-
|
|
61
|
+
def compose(cls, source: Source) -> Chat:
|
|
64
62
|
return source.chat
|
|
65
63
|
|
|
66
64
|
|
|
67
65
|
class UserSource(ScalarNode, User):
|
|
68
66
|
@classmethod
|
|
69
|
-
|
|
67
|
+
def compose(cls, source: Source) -> User:
|
|
70
68
|
return source.from_user
|
|
71
69
|
|
|
72
70
|
|
telegrinder/node/text.py
CHANGED
|
@@ -4,7 +4,7 @@ from telegrinder.node.message import MessageNode
|
|
|
4
4
|
|
|
5
5
|
class Text(ScalarNode, str):
|
|
6
6
|
@classmethod
|
|
7
|
-
|
|
7
|
+
def compose(cls, message: MessageNode) -> str:
|
|
8
8
|
if not message.text:
|
|
9
9
|
raise ComposeError("Message has no text.")
|
|
10
10
|
return message.text.unwrap()
|
|
@@ -12,7 +12,7 @@ class Text(ScalarNode, str):
|
|
|
12
12
|
|
|
13
13
|
class TextInteger(ScalarNode, int):
|
|
14
14
|
@classmethod
|
|
15
|
-
|
|
15
|
+
def compose(cls, text: Text) -> int:
|
|
16
16
|
if not text.isdigit():
|
|
17
17
|
raise ComposeError("Text is not digit.")
|
|
18
18
|
return int(text)
|
|
@@ -25,8 +25,7 @@ def generate_node(
|
|
|
25
25
|
casts: tuple[typing.Callable[[typing.Any], typing.Any], ...] = (cast_false_to_none, error_on_none),
|
|
26
26
|
) -> type[Node]:
|
|
27
27
|
async def compose(cls, **kw) -> typing.Any:
|
|
28
|
-
|
|
29
|
-
result = func(*args) # type: ignore
|
|
28
|
+
result = func(*ContainerNode.compose(**kw)) # type: ignore
|
|
30
29
|
if inspect.isawaitable(result):
|
|
31
30
|
result = await result
|
|
32
31
|
for cast in casts:
|
telegrinder/node/update.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from telegrinder.api import API
|
|
1
|
+
from telegrinder.api.api import API
|
|
2
2
|
from telegrinder.bot.cute_types import UpdateCute
|
|
3
3
|
from telegrinder.node.base import ScalarNode
|
|
4
|
-
from telegrinder.types import Update
|
|
4
|
+
from telegrinder.types.objects import Update
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class UpdateNode(ScalarNode, UpdateCute):
|
|
8
8
|
@classmethod
|
|
9
|
-
|
|
9
|
+
def compose(cls, update: Update, api: API) -> UpdateCute:
|
|
10
10
|
if isinstance(update, UpdateCute):
|
|
11
11
|
return update
|
|
12
12
|
return UpdateCute.from_update(update, api)
|
telegrinder/rules.py
CHANGED
telegrinder/tools/buttons.py
CHANGED
|
@@ -14,7 +14,7 @@ from telegrinder.types.objects import (
|
|
|
14
14
|
WebAppInfo,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
KeyboardButton = typing.TypeVar("KeyboardButton", bound="BaseButton")
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
@dataclasses.dataclass
|
|
@@ -27,11 +27,11 @@ class BaseButton:
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class RowButtons(typing.Generic[
|
|
31
|
-
buttons: list[
|
|
30
|
+
class RowButtons(typing.Generic[KeyboardButton]):
|
|
31
|
+
buttons: list[KeyboardButton]
|
|
32
32
|
auto_row: bool
|
|
33
33
|
|
|
34
|
-
def __init__(self, *buttons:
|
|
34
|
+
def __init__(self, *buttons: KeyboardButton, auto_row: bool = True) -> None:
|
|
35
35
|
self.buttons = list(buttons)
|
|
36
36
|
self.auto_row = auto_row
|
|
37
37
|
|
|
@@ -6,28 +6,28 @@ from fntypes.result import Result
|
|
|
6
6
|
from telegrinder.api import API
|
|
7
7
|
from telegrinder.bot.dispatch.context import Context
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
Handler = typing.Callable[
|
|
9
|
+
Event = typing.TypeVar("Event")
|
|
10
|
+
Handler = typing.Callable[..., typing.Awaitable[typing.Any]]
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class ABCErrorHandler(ABC, typing.Generic[
|
|
13
|
+
class ABCErrorHandler(ABC, typing.Generic[Event]):
|
|
14
14
|
@abstractmethod
|
|
15
15
|
def __call__(
|
|
16
16
|
self,
|
|
17
17
|
*args: typing.Any,
|
|
18
18
|
**kwargs: typing.Any,
|
|
19
19
|
) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Callable[..., typing.Any]]:
|
|
20
|
-
"""Decorator for registering callback as
|
|
20
|
+
"""Decorator for registering callback as a catcher for the error handler."""
|
|
21
21
|
|
|
22
22
|
@abstractmethod
|
|
23
23
|
async def run(
|
|
24
24
|
self,
|
|
25
|
-
handler: Handler
|
|
26
|
-
event:
|
|
25
|
+
handler: Handler,
|
|
26
|
+
event: Event,
|
|
27
27
|
api: API,
|
|
28
28
|
ctx: Context,
|
|
29
29
|
) -> Result[typing.Any, typing.Any]:
|
|
30
|
-
"""Run error handler."""
|
|
30
|
+
"""Run the error handler."""
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
__all__ = ("ABCErrorHandler",)
|
|
@@ -3,21 +3,32 @@ import typing
|
|
|
3
3
|
|
|
4
4
|
from fntypes.result import Error, Ok, Result
|
|
5
5
|
|
|
6
|
-
from telegrinder.api import API
|
|
6
|
+
from telegrinder.api.api import API
|
|
7
7
|
from telegrinder.bot.dispatch.context import Context
|
|
8
8
|
from telegrinder.modules import logger
|
|
9
|
+
from telegrinder.node.base import is_node
|
|
10
|
+
from telegrinder.tools.error_handler.abc import ABCErrorHandler, Event, Handler
|
|
11
|
+
from telegrinder.tools.error_handler.error import CatcherError
|
|
9
12
|
from telegrinder.tools.magic import magic_bundle
|
|
10
13
|
|
|
11
|
-
from .abc import ABCErrorHandler, EventT, Handler
|
|
12
|
-
from .error import CatcherError
|
|
13
|
-
|
|
14
14
|
F = typing.TypeVar("F", bound="FuncCatcher")
|
|
15
15
|
ExceptionT = typing.TypeVar("ExceptionT", bound=BaseException, contravariant=True)
|
|
16
16
|
FuncCatcher = typing.Callable[typing.Concatenate[ExceptionT, ...], typing.Awaitable[typing.Any]]
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
async def run_handler(
|
|
20
|
+
handler: Handler,
|
|
21
|
+
event: typing.Any,
|
|
22
|
+
ctx: dict[str, typing.Any],
|
|
23
|
+
) -> typing.Any:
|
|
24
|
+
annotations = tuple(handler.__annotations__.values())
|
|
25
|
+
start_idx = 0 if is_node(None if not annotations else annotations[0]) else 1
|
|
26
|
+
context = magic_bundle(handler, ctx, start_idx=start_idx)
|
|
27
|
+
return await (handler(event, **context) if start_idx == 1 else handler(**context))
|
|
28
|
+
|
|
29
|
+
|
|
19
30
|
@dataclasses.dataclass(frozen=True, repr=False, slots=True)
|
|
20
|
-
class Catcher(typing.Generic[
|
|
31
|
+
class Catcher(typing.Generic[Event]):
|
|
21
32
|
func: FuncCatcher[BaseException]
|
|
22
33
|
exceptions: list[type[BaseException] | BaseException] = dataclasses.field(
|
|
23
34
|
default_factory=lambda: [],
|
|
@@ -37,20 +48,29 @@ class Catcher(typing.Generic[EventT]):
|
|
|
37
48
|
|
|
38
49
|
async def __call__(
|
|
39
50
|
self,
|
|
40
|
-
handler: Handler
|
|
41
|
-
event:
|
|
51
|
+
handler: Handler,
|
|
52
|
+
event: Event,
|
|
42
53
|
api: API,
|
|
43
54
|
ctx: Context,
|
|
44
55
|
) -> Result[typing.Any, BaseException]:
|
|
45
56
|
try:
|
|
46
|
-
return Ok(await handler
|
|
57
|
+
return Ok(await run_handler(handler, event, ctx))
|
|
47
58
|
except BaseException as exc:
|
|
48
|
-
return await self.
|
|
59
|
+
return await self.run(api, event, ctx, exc, handler.__name__)
|
|
60
|
+
|
|
61
|
+
def match_exception(self, exception: BaseException) -> bool:
|
|
62
|
+
for exc in self.exceptions:
|
|
63
|
+
if isinstance(exc, type) and type(exception) is exc:
|
|
64
|
+
return True
|
|
65
|
+
if isinstance(exc, object) and type(exception) is type(exc):
|
|
66
|
+
return True if not exc.args else exc.args == exception.args
|
|
67
|
+
|
|
68
|
+
return False
|
|
49
69
|
|
|
50
|
-
async def
|
|
70
|
+
async def run(
|
|
51
71
|
self,
|
|
52
72
|
api: API,
|
|
53
|
-
event:
|
|
73
|
+
event: Event,
|
|
54
74
|
ctx: Context,
|
|
55
75
|
exception: BaseException,
|
|
56
76
|
handler_name: str,
|
|
@@ -61,26 +81,14 @@ class Catcher(typing.Generic[EventT]):
|
|
|
61
81
|
exception, handler_name, self.func.__name__
|
|
62
82
|
)
|
|
63
83
|
)
|
|
64
|
-
return Ok(
|
|
65
|
-
|
|
66
|
-
exception,
|
|
67
|
-
**magic_bundle(self.func, {"event": event, "api": api} | ctx), # type: ignore
|
|
68
|
-
)
|
|
69
|
-
)
|
|
84
|
+
return Ok(await run_handler(self.func, event, {"event": event, "api": api} | ctx))
|
|
85
|
+
|
|
70
86
|
logger.debug("Failed to match exception {!r}.", exception.__class__.__name__)
|
|
71
87
|
return Error(exception)
|
|
72
88
|
|
|
73
|
-
def match_exception(self, exception: BaseException) -> bool:
|
|
74
|
-
for exc in self.exceptions:
|
|
75
|
-
if isinstance(exc, type) and type(exception) is exc:
|
|
76
|
-
return True
|
|
77
|
-
if isinstance(exc, object) and type(exception) is type(exc):
|
|
78
|
-
return True if not exc.args else exc.args == exception.args
|
|
79
|
-
return False
|
|
80
|
-
|
|
81
89
|
|
|
82
|
-
class ErrorHandler(ABCErrorHandler[
|
|
83
|
-
def __init__(self, catcher: Catcher[
|
|
90
|
+
class ErrorHandler(ABCErrorHandler[Event]):
|
|
91
|
+
def __init__(self, catcher: Catcher[Event] | None = None, /) -> None:
|
|
84
92
|
self.catcher = catcher
|
|
85
93
|
|
|
86
94
|
def __repr__(self) -> str:
|
|
@@ -103,8 +111,8 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
103
111
|
):
|
|
104
112
|
"""Register the catcher.
|
|
105
113
|
|
|
106
|
-
:param logging: Logging the result of the catcher at the level
|
|
107
|
-
:param raise_exception: Raise an exception if the catcher
|
|
114
|
+
:param logging: Logging the result of the catcher at the level `DEBUG`.
|
|
115
|
+
:param raise_exception: Raise an exception if the catcher has not started.
|
|
108
116
|
:param ignore_errors: Ignore errors that may occur.
|
|
109
117
|
"""
|
|
110
118
|
|
|
@@ -121,10 +129,22 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
121
129
|
|
|
122
130
|
return decorator
|
|
123
131
|
|
|
132
|
+
def _process_catcher_error(self, error: CatcherError) -> Result[None, BaseException]:
|
|
133
|
+
assert self.catcher is not None
|
|
134
|
+
|
|
135
|
+
if self.catcher.raise_exception:
|
|
136
|
+
raise error.exc from None
|
|
137
|
+
if self.catcher.logging:
|
|
138
|
+
logger.error(error.message)
|
|
139
|
+
if not self.catcher.ignore_errors:
|
|
140
|
+
return Error(error.exc)
|
|
141
|
+
|
|
142
|
+
return Ok(None)
|
|
143
|
+
|
|
124
144
|
async def process(
|
|
125
145
|
self,
|
|
126
|
-
handler: Handler
|
|
127
|
-
event:
|
|
146
|
+
handler: Handler,
|
|
147
|
+
event: Event,
|
|
128
148
|
api: API,
|
|
129
149
|
ctx: Context,
|
|
130
150
|
) -> Result[typing.Any, BaseException]:
|
|
@@ -143,27 +163,15 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
143
163
|
)
|
|
144
164
|
)
|
|
145
165
|
|
|
146
|
-
def process_catcher_error(self, error: CatcherError) -> Result[None, BaseException]:
|
|
147
|
-
assert self.catcher is not None
|
|
148
|
-
|
|
149
|
-
if self.catcher.raise_exception:
|
|
150
|
-
raise error.exc from None
|
|
151
|
-
if self.catcher.logging:
|
|
152
|
-
logger.error(error.message)
|
|
153
|
-
if not self.catcher.ignore_errors:
|
|
154
|
-
return Error(error.exc)
|
|
155
|
-
|
|
156
|
-
return Ok(None)
|
|
157
|
-
|
|
158
166
|
async def run(
|
|
159
167
|
self,
|
|
160
|
-
handler: Handler
|
|
161
|
-
event:
|
|
168
|
+
handler: Handler,
|
|
169
|
+
event: Event,
|
|
162
170
|
api: API,
|
|
163
171
|
ctx: Context,
|
|
164
172
|
) -> Result[typing.Any, BaseException]:
|
|
165
173
|
if not self.catcher:
|
|
166
|
-
return Ok(await handler
|
|
174
|
+
return Ok(await run_handler(handler, event, ctx))
|
|
167
175
|
|
|
168
176
|
match await self.process(handler, event, api, ctx):
|
|
169
177
|
case Ok(value) as ok:
|
|
@@ -176,7 +184,10 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
176
184
|
return ok
|
|
177
185
|
case Error(exc) as err:
|
|
178
186
|
if isinstance(exc, CatcherError):
|
|
179
|
-
return self.
|
|
187
|
+
return self._process_catcher_error(exc)
|
|
180
188
|
if self.catcher.ignore_errors:
|
|
181
189
|
return Ok(None)
|
|
182
190
|
return err
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
__all__ = ("Catcher", "ErrorHandler")
|
telegrinder/tools/functional.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
import typing
|
|
2
3
|
|
|
3
4
|
import vbml
|
|
@@ -19,6 +20,7 @@ class TelegrinderContext(GlobalContext):
|
|
|
19
20
|
|
|
20
21
|
__ctx_name__ = "telegrinder"
|
|
21
22
|
|
|
23
|
+
vbml_pattern_flags: re.RegexFlag | None = None
|
|
22
24
|
vbml_patcher: typing.ClassVar[vbml.Patcher] = ctx_var(vbml.Patcher(), const=True)
|
|
23
25
|
|
|
24
26
|
|
|
File without changes
|
|
@@ -2,6 +2,7 @@ import typing
|
|
|
2
2
|
from abc import abstractmethod
|
|
3
3
|
|
|
4
4
|
from telegrinder.bot.cute_types.base import BaseCute
|
|
5
|
+
from telegrinder.bot.dispatch.context import Context
|
|
5
6
|
from telegrinder.bot.dispatch.middleware import ABCMiddleware
|
|
6
7
|
from telegrinder.tools.i18n import ABCI18n, I18nEnum
|
|
7
8
|
|
|
@@ -9,14 +10,14 @@ T = typing.TypeVar("T", bound=BaseCute)
|
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class ABCTranslatorMiddleware(ABCMiddleware[T]):
|
|
12
|
-
def __init__(self, i18n: ABCI18n):
|
|
13
|
+
def __init__(self, i18n: ABCI18n) -> None:
|
|
13
14
|
self.i18n = i18n
|
|
14
15
|
|
|
15
16
|
@abstractmethod
|
|
16
17
|
async def get_locale(self, event: T) -> str:
|
|
17
18
|
pass
|
|
18
19
|
|
|
19
|
-
async def pre(self, event: T, ctx:
|
|
20
|
+
async def pre(self, event: T, ctx: Context) -> bool:
|
|
20
21
|
ctx[I18nEnum.I18N] = self.i18n.get_translator_by_locale(await self.get_locale(event))
|
|
21
22
|
return True
|
|
22
23
|
|
telegrinder/tools/i18n/simple.py
CHANGED
|
@@ -3,12 +3,20 @@
|
|
|
3
3
|
import gettext
|
|
4
4
|
import os
|
|
5
5
|
|
|
6
|
-
from telegrinder.tools.i18n import ABCI18n
|
|
7
|
-
|
|
6
|
+
from telegrinder.tools.i18n.abc import ABCI18n, ABCTranslator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SimpleTranslator(ABCTranslator):
|
|
10
|
+
def __init__(self, locale: str, g: gettext.GNUTranslations) -> None:
|
|
11
|
+
self.g = g
|
|
12
|
+
super().__init__(locale)
|
|
13
|
+
|
|
14
|
+
def get(self, __key: str, *args: object, **kwargs: object) -> str:
|
|
15
|
+
return self.g.gettext(__key).format(*args, **kwargs)
|
|
8
16
|
|
|
9
17
|
|
|
10
18
|
class SimpleI18n(ABCI18n):
|
|
11
|
-
def __init__(self, folder: str, domain: str, default_locale: str):
|
|
19
|
+
def __init__(self, folder: str, domain: str, default_locale: str) -> None:
|
|
12
20
|
self.folder = folder
|
|
13
21
|
self.domain = domain
|
|
14
22
|
self.default_locale = default_locale
|
|
@@ -32,13 +40,4 @@ class SimpleI18n(ABCI18n):
|
|
|
32
40
|
return SimpleTranslator(locale, self.translators.get(locale, self.translators[self.default_locale]))
|
|
33
41
|
|
|
34
42
|
|
|
35
|
-
class SimpleTranslator(ABCTranslator):
|
|
36
|
-
def __init__(self, locale: str, g: gettext.GNUTranslations):
|
|
37
|
-
self.g = g
|
|
38
|
-
super().__init__(locale)
|
|
39
|
-
|
|
40
|
-
def get(self, __key: str, *args, **kwargs) -> str:
|
|
41
|
-
return self.g.gettext(__key).format(*args, **kwargs)
|
|
42
|
-
|
|
43
|
-
|
|
44
43
|
__all__ = ("SimpleI18n", "SimpleTranslator")
|
telegrinder/tools/keyboard.py
CHANGED
|
@@ -11,12 +11,16 @@ from telegrinder.types.objects import (
|
|
|
11
11
|
ReplyKeyboardRemove,
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
from .buttons import Button,
|
|
14
|
+
from .buttons import Button, InlineButton, KeyboardButton, RowButtons
|
|
15
15
|
|
|
16
16
|
DictStrAny: typing.TypeAlias = dict[str, typing.Any]
|
|
17
17
|
AnyMarkup: typing.TypeAlias = InlineKeyboardMarkup | ReplyKeyboardMarkup
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def copy_keyboard(keyboard: list[list[DictStrAny]]) -> list[list[DictStrAny]]:
|
|
21
|
+
return [row.copy() for row in keyboard]
|
|
22
|
+
|
|
23
|
+
|
|
20
24
|
@dataclasses.dataclass(kw_only=True, slots=True)
|
|
21
25
|
class KeyboardModel:
|
|
22
26
|
resize_keyboard: bool
|
|
@@ -26,13 +30,8 @@ class KeyboardModel:
|
|
|
26
30
|
keyboard: list[list[DictStrAny]]
|
|
27
31
|
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return [row.copy() for row in keyboard]
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class ABCMarkup(ABC, typing.Generic[ButtonT]):
|
|
35
|
-
BUTTON: type[ButtonT]
|
|
33
|
+
class ABCMarkup(ABC, typing.Generic[KeyboardButton]):
|
|
34
|
+
BUTTON: type[KeyboardButton]
|
|
36
35
|
keyboard: list[list[DictStrAny]]
|
|
37
36
|
|
|
38
37
|
@abstractmethod
|
|
@@ -47,7 +46,7 @@ class ABCMarkup(ABC, typing.Generic[ButtonT]):
|
|
|
47
46
|
def get_empty_markup(cls) -> AnyMarkup:
|
|
48
47
|
return cls().get_markup()
|
|
49
48
|
|
|
50
|
-
def add(self, row_or_button: RowButtons[
|
|
49
|
+
def add(self, row_or_button: RowButtons[KeyboardButton] | KeyboardButton) -> typing.Self:
|
|
51
50
|
if not len(self.keyboard):
|
|
52
51
|
self.row()
|
|
53
52
|
|
|
@@ -129,4 +128,5 @@ __all__ = (
|
|
|
129
128
|
"InlineKeyboard",
|
|
130
129
|
"Keyboard",
|
|
131
130
|
"KeyboardModel",
|
|
131
|
+
"copy_keyboard",
|
|
132
132
|
)
|