Passed
Push — master ( 879c1c...3da0e6 )
by torrua
01:45
created

keyboa.keyboards   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 100
dl 0
loc 222
ccs 61
cts 61
cp 1
rs 10
c 0
b 0
f 0
wmc 26

5 Functions

Rating   Name   Duplication   Size   Complexity  
B keyboa_maker() 0 49 8
A get_generated_keyboard() 0 33 4
A button_maker() 0 52 4
A get_preformatted_keyboard() 0 23 4
B keyboa_combiner() 0 32 6
1
# -*- coding:utf-8 -*-
2 1
"""
3
This module contains all the necessary functions for
4
creating complex and functional inline keyboards.
5
"""
6
7 1
from collections.abc import Iterable
8 1
from typing import Union, Optional, Tuple
9
10 1
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
11
12
# pylint: disable=R0913
13 1
from keyboa.constants import InlineButtonData, CallbackDataMarker, BlockItems, DEFAULT_ITEMS_IN_LINE
14 1
from keyboa.functions import _keyboa_pre_check, get_callback_data, get_callback, get_text, get_button_tuple, \
15
    calculate_items_in_row
16
17
18
# structureless sequence of InlineButtonData objects
19
20
# structured sequence of InlineButtonData objects
21
22
# unified type that allows you to use any available data types for the keyboard
23
24
25 1
def button_maker(
26
        button_data: InlineButtonData,
27
        front_marker: CallbackDataMarker = str(),
28
        back_marker: CallbackDataMarker = str(),
29
        copy_text_to_callback: bool = False,
30
) -> InlineKeyboardButton:
31
    """
32
    This function creates an InlineKeyboardButton object from various data types,
33
    such as str, int, tuple, dict.
34
35
    :param button_data: InlineButtonData - an object from which the button will be created:
36
    • If string or an integer, it will be used for both text and callback.
37
    • If tuple, the zero element [0] will be the text, and the first [1] will be the callback.
38
    • If dictionary, there are two options:
39
        > if there is no "text" key in dictionary and only one key exists,
40
            the key will be the text, and the value will be the callback.
41
            In this case, no verification of the dictionary's contents is performed!
42
        > if "text" key exists, function passes the whole dictionary to InlineKeyboardButton,
43
            where dictionary's keys represent object's parameters
44
            and dictionary's values represent parameters' values accordingly.
45
        In all other cases ValueError will be called.
46
47
    :param front_marker: CallbackDataMarker - a string to be added to the left side of callback.
48
        Optional. The default value is None.
49
50
    :param back_marker: CallbackDataMarker - a string to be added to the right side of callback.
51
        Optional. The default value is None.
52
53
    :param copy_text_to_callback: If enabled and button_data is a string or integer,
54
        function will copy button text to callback data (and add markers if they exist).
55
        Optional. The default value is False.
56
57
    :return: InlineKeyboardButton
58
59
    Covered by tests.
60
    """
61
62 1
    if isinstance(button_data, InlineKeyboardButton):
63 1
        return button_data
64
65 1
    if isinstance(button_data, dict) and button_data.get("text"):
66 1
        return InlineKeyboardButton(**button_data)
67
68 1
    button_tuple = get_button_tuple(button_data, copy_text_to_callback)
69
70 1
    text = get_text(button_tuple)
71 1
    raw_callback = get_callback(button_tuple)
72 1
    callback_data = get_callback_data(raw_callback, front_marker, back_marker)
73
74 1
    prepared_button = {"text": text, "callback_data": callback_data}
75
76 1
    return InlineKeyboardButton(**prepared_button)
77
78
79 1
def keyboa_maker(
80
        items: BlockItems = None,
81
        front_marker: CallbackDataMarker = None,
82
        back_marker: CallbackDataMarker = None,
83
84
        items_in_row: int = None,
85
        auto_alignment: Union[bool, Iterable] = False,
86
        reverse_alignment_range: bool = False,
87
        slice_start: int = None,
88
        slice_stop: int = None,
89
        slice_step: int = None,
90
91
        copy_text_to_callback: bool = False,
92
        add_to_keyboard: InlineKeyboardMarkup = None,
93
) -> InlineKeyboardMarkup:
94
    """
95
    :param items:
96
    :param front_marker:
97
    :param back_marker:
98
    :param items_in_row:
99
    :param auto_alignment:
100
    :param reverse_alignment_range:
101
    :param slice_start:
102
    :param slice_stop:
103
    :param slice_step:
104
    :param copy_text_to_callback:
105
    :param add_to_keyboard:
106
    :return:
107
    """
108 1
    keyboard = add_to_keyboard if add_to_keyboard else InlineKeyboardMarkup()
109
110 1
    if items is None:
111 1
        return keyboard
112
113 1
    if items and not isinstance(items, list):
114 1
        items = [items, ]
115
116 1
    items = items[slice_start:slice_stop:slice_step] if items else items
117
118 1
    _keyboa_pre_check(items=items, items_in_row=items_in_row, keyboard=keyboard)
119
120 1
    if items_in_row or auto_alignment:
121 1
        return get_generated_keyboard(
122
            items, front_marker, back_marker, items_in_row, auto_alignment,
123
            reverse_alignment_range, copy_text_to_callback, keyboard)
124
125 1
    return get_preformatted_keyboard(
126
        items, front_marker, back_marker,
127
        copy_text_to_callback, keyboard)
128
129
130 1
def get_preformatted_keyboard(
131
        items, front_marker, back_marker,
132
        copy_text_to_callback, keyboard):
133
    """
134
    :param items:
135
    :param front_marker:
136
    :param back_marker:
137
    :param copy_text_to_callback:
138
    :param keyboard:
139
    :return:
140
    """
141 1
    for index, item in enumerate(items):
142 1
        if not isinstance(item, list):
143 1
            items[index] = [item, ]
144 1
    for row in items:
145 1
        buttons = [button_maker(
146
            button_data=item,
147
            front_marker=front_marker,
148
            back_marker=back_marker,
149
            copy_text_to_callback=copy_text_to_callback
150
        ) for item in row]
151 1
        keyboard.row(*buttons)
152 1
    return keyboard
153
154
155 1
def get_generated_keyboard(
156
        items, front_marker, back_marker, items_in_row,
157
        auto_alignment, reverse_alignment_range,
158
        copy_text_to_callback, keyboard):
159
    """
160
    :param items:
161
    :param front_marker:
162
    :param back_marker:
163
    :param items_in_row:
164
    :param auto_alignment:
165
    :param reverse_alignment_range:
166
    :param copy_text_to_callback:
167
    :param keyboard:
168
    :return:
169
    """
170
171 1
    if auto_alignment:
172 1
        items_in_row = calculate_items_in_row(items, auto_alignment, reverse_alignment_range)
173
174 1
    if not items_in_row:
175 1
        items_in_row = DEFAULT_ITEMS_IN_LINE
176
177 1
    rows_in_keyboard = (len(items) // items_in_row)
178 1
    buttons = [button_maker(
179
        button_data=item,
180
        front_marker=front_marker,
181
        back_marker=back_marker,
182
        copy_text_to_callback=copy_text_to_callback,
183
    ) for item in items]
184 1
    for _row in range(rows_in_keyboard):
185 1
        keyboard.row(*[buttons.pop(0) for _button in range(items_in_row)])
186 1
    keyboard.row(*buttons)
187 1
    return keyboard
188
189
190 1
def keyboa_combiner(
191
        keyboards: Optional[Union[Tuple[InlineKeyboardMarkup, ...], InlineKeyboardMarkup]] = None
192
) -> InlineKeyboardMarkup:
193
    """
194
    This function combines multiple InlineKeyboardMarkup objects into one.
195
196
    :param keyboards: Sequence of InlineKeyboardMarkup objects.
197
        Also could be presented as a standalone InlineKeyboardMarkup.
198
199
    :return: InlineKeyboardMarkup
200
    """
201
202 1
    if keyboards is None:
203 1
        return InlineKeyboardMarkup()
204
205 1
    if isinstance(keyboards, InlineKeyboardMarkup):
206 1
        keyboards = (keyboards, )
207
208 1
    data = []
209 1
    for keyboard in keyboards:
210 1
        if keyboard is None:
211 1
            continue
212
213 1
        if not isinstance(keyboard, InlineKeyboardMarkup):
214 1
            type_error_message = \
215
                "Keyboard element cannot be %s. Only InlineKeyboardMarkup allowed." \
216
                % type(keyboard)
217 1
            raise TypeError(type_error_message)
218
219 1
        data.extend(keyboard.keyboard)
220
221
    return keyboa_maker(data)
222