11# Extra functionality for PTB keyboards
22
3- ### This module contains extended keyboard classes with extra functionality for PTB keyboards.
3+ These modules contains extended keyboard classes with extra functionality for PTB keyboards.
44
5- ### Methods is self-descriptive.
5+ ## Module ` base keyboards `
6+
7+ This module provides simple base actions that mostly intended to be used as base class for other extend keyboards.
8+ For example currently ` select keyboards ` made independently
9+ and occasionally repeats the functionality present in this base module.
610
711``` python
812class IExtendedInlineKeyboardMarkup (ABC , ):
913 """ Popular keyboard actions """
1014
1115 def to_list (self , ) -> list[list[InlineKeyboardButton]]:
12-
16+
1317 def find_btn_by_cbk (self , cbk : str , ) -> tuple[InlineKeyboardButton, int , int ] | None :
1418 """ Returns buttons, row index, column index if found, None otherwise """
15-
19+
1620 def get_buttons (self , ) -> list[InlineKeyboardButton]:
1721 """ Just get flat list of buttons of the keyboard """
1822
@@ -35,10 +39,199 @@ class IExtendedInlineKeyboardMarkup(ABC, ):
3539 """
3640```
3741
42+ Popular keyboard actions
43+ ``` python
44+
45+
46+ def to_list (self , ) -> list[list[InlineKeyboardButton]]:
47+
48+
49+ def find_btn_by_cbk (self , cbk : str , ) -> tuple[InlineKeyboardButton, int , int ] | None :
50+ """ Returns buttons, row index, column index if found, None otherwise """
51+
52+
53+ def get_buttons (self , ) -> list[InlineKeyboardButton]:
54+ """ Just get flat list of buttons of the keyboard """
55+
56+
57+ def split (
58+ self ,
59+ buttons_in_row : int ,
60+ buttons : Sequence[InlineKeyboardButton] | None = None ,
61+ update_self : bool = True ,
62+ empty_rows_allowed : bool = True ,
63+ strict : bool = False ,
64+ ) -> list[list[InlineKeyboardButton]]:
65+ """
66+ Split keyboard by N buttons in row.
67+ Last row will contain remainder,
68+ i.e. num of buttons in the last row maybe less than `buttons_in_row` parameter.
69+
70+ Possible enhancement:
71+ keep_empty_rows: bool - keep empty rows in final keyboard if not enough buttons.
72+ # Please create feature issue if you need it.
73+ """
74+ ```
75+
76+ ## Module ` select keyboards `
77+
78+ This module implement checkbox buttons for PTB inline keyboard
79+
80+ ### Class ` SelectKeyboard ` :
81+
82+ Let's first create the keyboard:
83+
84+ ``` python
85+ SelectKeyboard(
86+ inline_keyboard = ((InlineKeyboardButton(... ))),
87+ checkbox_position = 0 ,
88+ checked_symbol = ' +' ,
89+ unchecked_symbol = ' -' ,
90+ )
91+ ```
92+
93+ ` SelectKeyboard ` inherits from ` InlineKeyboardMarkup ` and therefore not differ from it
94+ and may be used as drop in replacement.
95+ The one more explicit meaning of keyboard is container (and therefore kind of manager or arbiter) for his buttons.
96+
97+ #### Use cases
98+
99+ You got the incoming callback with inline reply markup (buttons will be also converted if possible):
100+ ``` python
101+ select_inline_keyboard = SelectKeyboard.from_callback(keyboard = inline_keyboard_markup.inline_keyboard, )
102+ ```
103+
104+ Let's go even forward - and directly update the selected button in one line:
105+ ``` python
106+ select_inline_keyboard = SelectKeyboard.invert_by_callback(keyboard = inline_keyboard_markup.inline_keyboard, )
107+ ```
108+
109+ Or if clicked "select all" option:
110+ ``` python
111+ keyboard = SelectKeyboard.set_all_buttons(keyboard = inline_keyboard, flag = True , )
112+ ```
113+
114+ Check is ` InlineKeyboardMarkup ` can be converted to any known button type of the keyboard
115+ ``` python
116+ button = SelectKeyboard.is_convertable(button = inline_button, )
117+ ```
118+
119+ Check is ` InlineKeyboardMarkup ` can be converted to any known button type of the keyboard
120+ ``` python
121+ button = SelectKeyboard.button_from_inline(button = inline_button, )
122+ ```
123+
124+
125+ ### Class ` SelectButtons: ` :
126+
127+ ``` python
128+ select_button = SelectButton(
129+ is_selected = True , # is_selected: Initial state of the button
130+ checkbox_position = 0 , # checkbox_position: Position of the checkbox in the text
131+ checked_symbol = ' +' , # checked_symbol: Symbol to use when the button is selected
132+ unchecked_symbol = ' -' , # unchecked_symbol: Symbol to use when the button is not selected
133+ text = " Hi! I'm the button which will contain selection symbol after init" , # text: Button text
134+ callback_data = ' ...' ,
135+ ... # Other `InlineKeyboardButton` regular parameters
136+ )
137+ ```
138+
139+ Class ` SelectButton ` represents checkbox button and provides convenience methods to manage button state.
140+ (note: button state is immutable according to PTB objects management policy, so most of the methods returns new state).
141+ It's also inherits from ` InlineKeyboardButton ` and may be used interchangeably with it.
142+ After creation the button will contain callback data in format:
143+ ` 'original callback SELECT_BTN 0 + - 1' `
144+
145+ Which means:
146+ - ` 'SELECT_BTN' ` - key to mark button as select button.
147+ - ` '0' ` - position of the select symbol in the string.
148+ - ` '+' ` - symbol to apply on checking.
149+ - ` '-' ` - symbol to apply on unchecking.
150+ - ` '1' ` - selected state (0 - deselected).
151+
152+
153+ Invert button state (text and callback_data) from ` selected ` to ` deselected ` and vice versa:
154+ ``` python
155+ inverted_button = select_button.invert()
156+ ```
157+ Check the button state is selected:
158+ (Note: that is the property bound to callback)
159+ ``` python
160+ select_button.is_selected # True or False
161+ ```
162+
163+ #### Use cases
164+
165+ Convert ` InlineKeyboardButton ` to ` SelectButton ` (To check is button convertable - use ` is_convertable ` method)
166+ ``` python
167+ select_button = SelectButton.from_inline_button(button = inline_keyboard_button, )
168+ ```
169+
170+ Is button is select button at all? - This will look up for an appropriate callback data
171+ which has ` 'SELECT_BTN' ` pattern
172+ ``` python
173+ select_button = SelectButton.is_convertable(button = inline_keyboard_button, )
174+ ```
175+
176+ ### More buttons:
177+
178+ #### class ` SelectButtonManager ` :
179+ What if ` is_selected ` parameter depend on the other buttons just as for "select all" button
180+ which are selected when all points selected? That's what ` SelectButtonManager ` type mean,
181+ every child of it should implement ` resolve_is_selected ` method.
182+
183+ ` SelectAllButton ` is child of ` SelectButtonManager `
184+ It's similar to ` SelectButton ` and also inherits from ` InlineKeyboardButton ` ,
185+ so the button itself will decide on her state.
186+ ``` python
187+ keyboard = SelectAllButton.resolve_is_selected(keyboard = inline_keyboard, ) # True or False
188+ ```
189+
190+ Let's update button if ` resolve_is_selected ` returned opposite state:
191+ ``` python
192+ updated_select_all_button = select_all_button.update(keyboard = inline_keyboard, ) # just calling `invert` inside
193+ ```
194+
195+ #### class` SimpleButton: ` :
196+ Specifying the same ` checked_symbol ` , ` unchecked_symbol ` ,
197+ etc. for every button may be tedious, so there are 2 workarounds:
198+ 1 . Create common class with overriding ` checked_symbol ` , ` unchecked_symbol ` :
199+ ` class MySelectButtton: checked_symbol = 'Ha-ha!' `
200+ 2 . Use ` SimpleSelectButton ` - this button type will tell to keyboard to use the keyboard parameters of
201+ ` checked_symbol ` , ` unchecked_symbol ` rather than the button:
202+ ``` python
203+ SimpleSelectButton(is_selected = True , cls = SelectButton, ... other button fields)
204+ ```
205+ ` SimpleSelectButton ` has only two purposes: it's a delayed button creation and the indicator.
206+
207+ ### Common architecture notes:
208+ 1 . All types may be divided on 2 parts: creation and parsing or extracting.
209+ ` SimpleButton ` , ` ManagerButton ` is about creation, but eventually (see point 2):
210+ 2 . select button representation expressed via his callback is common or similar for all buttons:
211+ '.* ({SELECT_BTN_S}) (\d+) (\S+) (\S+) ([ 01] )$', so it's sing;e finish point of every button.
212+ 3 . During initialization, we need a structs mostly, most methods intended to handle already initialized objects.
213+ 4 . Three rings of responsibility:
214+ 1 . Select button - target of all modifications.
215+ 2 . Button manager - strategy | logic of manipulation.
216+ 3 . Keyboard - applying and providing context to manipulate by manager on the button.
217+ 5 . Term ` keyboard ` may mean inline keyboard (nested list) or ` InlineKeyboardMarkup ` ,
218+ this depends on context and will be stick to single meaning when will be more clearing of case usage.
219+ 6 . May be ` SimpleButton ` may be dropped and just check is select button fields filled with ` None ` or not
220+ (need to make them optional in this case).
221+
222+
223+ Future improvements thoughts:
224+ 1 . Create ` Checkbox ` class, to allow ` SelectButton ` able to have separate checkboxes for different states.
225+ 2 . Make more states rather than just ` True ` | ` False ` .
226+ 3 . Make checkboxes of different sides (actual for 2 column keyboards), similar as point 1 but easiest as keyboard twik
227+ 4 . Add Some simple set oj emojy checked | unchecked symbols.
228+ 5 . Increase decoupling between keyboard and buttons, improve architecture.
229+ 6 . Add ` eject ` method to divide between
230+
38231## Requirements
39232
40- * ` python-telegram-bot>=20.0 `
233+ * ` python-telegram-bot>=20.0 `
41234
42235## Authors
43236
44- * [ David Shiko] ( https://github.com/david-shiko )
237+ * [ David Shiko] ( https://github.com/david-shiko )
0 commit comments