From bcda7bea4cb924234e2671ea0e07e3855987d95e Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Mon, 31 Jul 2023 01:19:43 +0300 Subject: [PATCH] Enhance keyboard utility, improved documentation for this utility. Updated the 'aiogram/utils/keyboard.py' file with new methods for integrating buttons and keyboard creation more seamlessly. Added functionality to create buttons from existing markup and attach another builder. This improvement aims to make the keyboard building process more user-friendly and flexible. --- aiogram/utils/keyboard.py | 39 +++++++++++++++++++ docs/utils/keyboard.rst | 39 +++++++++++++++---- .../test_types/test_chat_join_request.py | 6 +-- 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/aiogram/utils/keyboard.py b/aiogram/utils/keyboard.py index 84258e6d..9fcab689 100644 --- a/aiogram/utils/keyboard.py +++ b/aiogram/utils/keyboard.py @@ -236,6 +236,12 @@ class KeyboardBuilder(Generic[ButtonType]): return self def button(self, **kwargs: Any) -> "KeyboardBuilder[ButtonType]": + """ + Add button to markup + + :param kwargs: + :return: + """ if isinstance(callback_data := kwargs.get("callback_data", None), CallbackData): kwargs["callback_data"] = callback_data.pack() button = self._button_type(**kwargs) @@ -246,6 +252,17 @@ class KeyboardBuilder(Generic[ButtonType]): return ReplyKeyboardMarkup(keyboard=self.export(), **kwargs) return InlineKeyboardMarkup(inline_keyboard=self.export()) + def attach(self, builder: "KeyboardBuilder[ButtonType]") -> "KeyboardBuilder[ButtonType]": + if not isinstance(builder, KeyboardBuilder): + raise ValueError(f"Only KeyboardBuilder can be attached, not {type(builder).__name__}") + if builder._button_type is not self._button_type: + raise ValueError( + f"Only builders with same button type can be attached, " + f"not {self._button_type.__name__} and {builder._button_type.__name__}" + ) + self._markup.extend(builder.export()) + return self + def repeat_last(items: Iterable[T]) -> Generator[T, None, None]: items_iter = iter(items) @@ -303,6 +320,18 @@ class InlineKeyboardBuilder(KeyboardBuilder[InlineKeyboardButton]): """ return InlineKeyboardBuilder(markup=self.export()) + @classmethod + def from_markup( + cls: Type["InlineKeyboardBuilder"], markup: InlineKeyboardMarkup + ) -> "InlineKeyboardBuilder": + """ + Create builder from existing markup + + :param markup: + :return: + """ + return cls(markup=markup.inline_keyboard) + class ReplyKeyboardBuilder(KeyboardBuilder[KeyboardButton]): """ @@ -336,3 +365,13 @@ class ReplyKeyboardBuilder(KeyboardBuilder[KeyboardButton]): :return: """ return ReplyKeyboardBuilder(markup=self.export()) + + @classmethod + def from_markup(cls, markup: ReplyKeyboardMarkup) -> "ReplyKeyboardBuilder": + """ + Create builder from existing markup + + :param markup: + :return: + """ + return cls(markup=markup.keyboard) diff --git a/docs/utils/keyboard.rst b/docs/utils/keyboard.rst index 8988244e..35559fc0 100644 --- a/docs/utils/keyboard.rst +++ b/docs/utils/keyboard.rst @@ -14,6 +14,8 @@ Keyboard builder helps to dynamically generate markup. Usage example ============= +For example you want to generate inline keyboard with 10 buttons + .. code-block:: python builder = InlineKeyboardBuilder() @@ -21,22 +23,45 @@ Usage example for index in range(1, 11): builder.button(text=f"Set {index}", callback_data=f"set:{index}") + +then adjust this buttons to some grid, for example first line will have 3 buttons, the next lines will have 2 buttons + +.. code-block:: + builder.adjust(3, 2) +also you can attach another builder to this one + +.. code-block:: python + + another_builder = InlineKeyboardBuilder(...)... # Another builder with some buttons + builder.attach(another_builder) + +or you can attach some already generated markup + +.. code-block:: python + + markup = InlineKeyboardMarkup(inline_keyboard=[...]) # Some markup + builder.attach(InlineKeyboardBuilder.from_markup(markup)) + +and finally you can export this markup to use it in your message + +.. code-block:: python + await message.answer("Some text here", reply_markup=builder.as_markup()) +Reply keyboard builder has the same interface + +.. warning:: + + Note that you can't attach reply keyboard builder to inline keyboard builder and vice versa -Base builder -============ -.. autoclass:: aiogram.utils.keyboard.ReplyKeyboardBuilder - :members: __init__, buttons, copy, export, add, row, adjust, button, as_markup - :undoc-members: True Inline Keyboard =============== .. autoclass:: aiogram.utils.keyboard.InlineKeyboardBuilder - :noindex: + :members: __init__, buttons, copy, export, add, row, adjust, from_markup, attach .. method:: button(text: str, url: Optional[str] = None, login_url: Optional[LoginUrl] = None, callback_data: Optional[Union[str, CallbackData]] = None, switch_inline_query: Optional[str] = None, switch_inline_query_current_chat: Optional[str] = None, callback_game: Optional[CallbackGame] = None, pay: Optional[bool] = None, **kwargs: Any) -> aiogram.utils.keyboard.InlineKeyboardBuilder :noindex: @@ -52,7 +77,7 @@ Reply Keyboard ============== .. autoclass:: aiogram.utils.keyboard.ReplyKeyboardBuilder - :noindex: + :members: __init__, buttons, copy, export, add, row, adjust, from_markup, attach .. method:: button(text: str, request_contact: Optional[bool] = None, request_location: Optional[bool] = None, request_poll: Optional[KeyboardButtonPollType] = None, **kwargs: Any) -> aiogram.utils.keyboard.ReplyKeyboardBuilder :noindex: diff --git a/tests/test_api/test_types/test_chat_join_request.py b/tests/test_api/test_types/test_chat_join_request.py index a36dd935..ec442cb1 100644 --- a/tests/test_api/test_types/test_chat_join_request.py +++ b/tests/test_api/test_types/test_chat_join_request.py @@ -24,11 +24,7 @@ from aiogram.methods import ( SendVideoNote, SendVoice, ) -from aiogram.types import ( - Chat, - ChatJoinRequest, - User, -) +from aiogram.types import Chat, ChatJoinRequest, User class TestChatJoinRequest: