telegrinder 0.3.0.post1__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.

Files changed (59) hide show
  1. telegrinder/__init__.py +1 -1
  2. telegrinder/bot/cute_types/callback_query.py +2 -14
  3. telegrinder/bot/cute_types/chat_join_request.py +1 -1
  4. telegrinder/bot/cute_types/chat_member_updated.py +1 -1
  5. telegrinder/bot/cute_types/inline_query.py +1 -6
  6. telegrinder/bot/cute_types/message.py +1 -21
  7. telegrinder/bot/cute_types/update.py +1 -1
  8. telegrinder/bot/dispatch/abc.py +45 -3
  9. telegrinder/bot/dispatch/dispatch.py +8 -8
  10. telegrinder/bot/dispatch/handler/func.py +8 -10
  11. telegrinder/bot/dispatch/process.py +1 -1
  12. telegrinder/bot/dispatch/view/base.py +31 -20
  13. telegrinder/bot/dispatch/view/raw.py +20 -16
  14. telegrinder/bot/dispatch/waiter_machine/actions.py +3 -0
  15. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +15 -18
  16. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +21 -13
  17. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +15 -16
  18. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +6 -6
  19. telegrinder/bot/dispatch/waiter_machine/machine.py +24 -30
  20. telegrinder/bot/dispatch/waiter_machine/short_state.py +10 -4
  21. telegrinder/bot/rules/abc.py +37 -7
  22. telegrinder/bot/rules/adapter/raw_update.py +1 -3
  23. telegrinder/bot/rules/inline.py +1 -2
  24. telegrinder/bot/rules/markup.py +5 -2
  25. telegrinder/bot/rules/start.py +1 -1
  26. telegrinder/bot/rules/text.py +5 -3
  27. telegrinder/bot/scenario/checkbox.py +1 -1
  28. telegrinder/msgspec_utils.py +11 -3
  29. telegrinder/node/attachment.py +6 -6
  30. telegrinder/node/base.py +17 -11
  31. telegrinder/node/callback_query.py +1 -1
  32. telegrinder/node/command.py +1 -1
  33. telegrinder/node/composer.py +5 -2
  34. telegrinder/node/container.py +1 -1
  35. telegrinder/node/event.py +1 -1
  36. telegrinder/node/message.py +1 -1
  37. telegrinder/node/polymorphic.py +6 -3
  38. telegrinder/node/rule.py +1 -1
  39. telegrinder/node/source.py +5 -7
  40. telegrinder/node/text.py +2 -2
  41. telegrinder/node/tools/generator.py +1 -2
  42. telegrinder/node/update.py +3 -3
  43. telegrinder/rules.py +2 -0
  44. telegrinder/tools/buttons.py +4 -4
  45. telegrinder/tools/error_handler/abc.py +7 -7
  46. telegrinder/tools/error_handler/error_handler.py +58 -47
  47. telegrinder/tools/formatting/html.py +0 -2
  48. telegrinder/tools/functional.py +3 -0
  49. telegrinder/tools/global_context/telegrinder_ctx.py +2 -0
  50. telegrinder/tools/i18n/__init__.py +1 -1
  51. telegrinder/tools/i18n/{base.py → abc.py} +0 -0
  52. telegrinder/tools/i18n/middleware/__init__.py +1 -1
  53. telegrinder/tools/i18n/middleware/{base.py → abc.py} +3 -2
  54. telegrinder/tools/i18n/simple.py +11 -12
  55. telegrinder/tools/keyboard.py +9 -9
  56. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.0.post2.dist-info}/METADATA +1 -1
  57. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.0.post2.dist-info}/RECORD +59 -59
  58. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.0.post2.dist-info}/LICENSE +0 -0
  59. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.0.post2.dist-info}/WHEEL +0 -0
@@ -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
- async def compose_message(cls, message: MessageNode) -> typing.Self:
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
- async def compose_callback_query(cls, callback_query: CallbackQueryNode) -> typing.Self:
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
- async def compose_chat_join_request(
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
- async def compose(cls, source: Source) -> Chat:
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
- async def compose(cls, source: Source) -> User:
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
- async def compose(cls, message: MessageNode) -> str:
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
- async def compose(cls, text: Text) -> int:
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
- args = await ContainerNode.compose(**kw)
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:
@@ -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
- async def compose(cls, update: Update, api: API) -> UpdateCute:
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
@@ -56,5 +56,7 @@ __all__ = (
56
56
  "Regex",
57
57
  "RuleEnum",
58
58
  "StartCommand",
59
+ "State",
60
+ "StateMeta",
59
61
  "Text",
60
62
  )
@@ -14,7 +14,7 @@ from telegrinder.types.objects import (
14
14
  WebAppInfo,
15
15
  )
16
16
 
17
- ButtonT = typing.TypeVar("ButtonT", bound="BaseButton")
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[ButtonT]):
31
- buttons: list[ButtonT]
30
+ class RowButtons(typing.Generic[KeyboardButton]):
31
+ buttons: list[KeyboardButton]
32
32
  auto_row: bool
33
33
 
34
- def __init__(self, *buttons: ButtonT, auto_row: bool = True) -> None:
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
- EventT = typing.TypeVar("EventT")
10
- Handler = typing.Callable[typing.Concatenate[EventT, ...], typing.Awaitable[typing.Any]]
9
+ Event = typing.TypeVar("Event")
10
+ Handler = typing.Callable[..., typing.Awaitable[typing.Any]]
11
11
 
12
12
 
13
- class ABCErrorHandler(ABC, typing.Generic[EventT]):
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 an error handler."""
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[EventT],
26
- event: EventT,
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[EventT]):
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[EventT],
41
- event: EventT,
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(event, **magic_bundle(handler, ctx))) # type: ignore
57
+ return Ok(await run_handler(handler, event, ctx))
47
58
  except BaseException as exc:
48
- return await self.process_exception(api, event, ctx, exc, handler.__name__)
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 process_exception(
70
+ async def run(
51
71
  self,
52
72
  api: API,
53
- event: EventT,
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
- await self.func(
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[EventT]):
83
- def __init__(self, catcher: Catcher[EventT] | None = None, /) -> None:
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 'DEBUG'.
107
- :param raise_exception: Raise an exception if the catcher hasn't started.
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[EventT],
127
- event: EventT,
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[EventT],
161
- event: EventT,
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(event, **magic_bundle(handler, ctx))) # type: ignore
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.process_catcher_error(exc)
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")
@@ -1,5 +1,3 @@
1
- # NOTE: NEED REFACTORING
2
-
3
1
  import html
4
2
  import string
5
3
  import typing
@@ -7,3 +7,6 @@ T = typing.TypeVar("T")
7
7
 
8
8
  def from_optional(value: T | None) -> Option[T]:
9
9
  return Some(value) if value is not None else Nothing()
10
+
11
+
12
+ __all__ = ("from_optional",)
@@ -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
 
@@ -1,4 +1,4 @@
1
- from .base import ABCI18n, ABCTranslator, I18nEnum
1
+ from .abc import ABCI18n, ABCTranslator, I18nEnum
2
2
  from .middleware import ABCTranslatorMiddleware
3
3
  from .simple import SimpleI18n, SimpleTranslator
4
4
 
File without changes
@@ -1,3 +1,3 @@
1
- from .base import ABCTranslatorMiddleware
1
+ from .abc import ABCTranslatorMiddleware
2
2
 
3
3
  __all__ = ("ABCTranslatorMiddleware",)
@@ -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: dict) -> bool:
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
 
@@ -3,12 +3,20 @@
3
3
  import gettext
4
4
  import os
5
5
 
6
- from telegrinder.tools.i18n import ABCI18n
7
- from telegrinder.tools.i18n.base import ABCTranslator
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")
@@ -11,12 +11,16 @@ from telegrinder.types.objects import (
11
11
  ReplyKeyboardRemove,
12
12
  )
13
13
 
14
- from .buttons import Button, ButtonT, InlineButton, RowButtons
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
- def copy_keyboard(keyboard: list[list[DictStrAny]]) -> list[list[DictStrAny]]:
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[ButtonT] | ButtonT) -> typing.Self:
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
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: telegrinder
3
- Version: 0.3.0.post1
3
+ Version: 0.3.0.post2
4
4
  Summary: Modern visionary telegram bot framework.
5
5
  Home-page: https://github.com/timoniq/telegrinder
6
6
  License: MIT