Passed
Push — master ( 3da0e6...48245f )
by torrua
01:25
created

keyboa.functions.get_callback_data()   A

Complexity

Conditions 3

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 14
nop 3
dl 0
loc 26
ccs 10
cts 10
cp 1
crap 3
rs 9.7
c 0
b 0
f 0
1
# -*- coding:utf-8 -*-
2 1
"""
3
Module for small service functions
4
"""
5
6 1
from typing import Optional, Iterable
7
8 1
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
9
10 1
from keyboa.constants import InlineButtonData, button_text_types, \
11
    ButtonText, callback_data_types, CallbackDataMarker, \
12
    BlockItems, MAXIMUM_ITEMS_IN_KEYBOARD, MAXIMUM_ITEMS_IN_LINE, \
13
    MINIMUM_ITEMS_IN_LINE, AUTO_ALIGNMENT_RANGE, MAXIMUM_CBD_LENGTH
14
15
16 1
def _keyboa_pre_check(
17
        items: BlockItems = None,
18
        items_in_row: int = None,
19
        keyboard: InlineKeyboardMarkup = None) -> None:
20
    """
21
    This function checks whether the keyboard parameters are beyond Telegram limits or not.
22
23
    :param items: InlineRowItems - Sequence of elements with optional structure,
24
        where each top-level item will be a row with one or several buttons.
25
26
    :param items_in_row: int - Desired number of buttons in one row. Should be from 1 to 8.
27
        Optional. The default value is None.
28
29
    :param keyboard: InlineKeyboardMarkup object to which we will attach the list items.
30
        We need to count the existing buttons so as not to go beyond the general limits.
31
        Optional. The default value is None.
32
33
    :return: None if everything is okay.
34
35
    Covered by tests.
36
    """
37
38 1
    if items is None:
39 1
        return
40
41 1
    if items and not isinstance(items, list):
42 1
        items = [items, ]
43
44 1
    if keyboard and not isinstance(keyboard, InlineKeyboardMarkup):
45 1
        type_error_message = \
46
            "Keyboard to which the new items will be added " \
47
            "should have InlineKeyboardMarkup type. Now it is a %s" % type(keyboard)
48 1
        raise TypeError(type_error_message)
49
50
    # We need to count existing buttons too if we passed keyboard object to the function
51 1
    items_in_keyboard = get_total_items_number(items, keyboard)
52 1
    check_keyboard_items_limits(items_in_keyboard, items_in_row)
53
54
55 1
def check_keyboard_items_limits(items_in_keyboard: int, items_in_row: int) -> None:
56
    """
57
    :param items_in_keyboard:
58
    :param items_in_row:
59
    :return:
60
    """
61
62 1
    if items_in_keyboard > MAXIMUM_ITEMS_IN_KEYBOARD:
63 1
        value_error_message_keyboard = \
64
            "Telegram Bot API limit exceeded: The keyboard should have " \
65
            "from 1 to %s buttons at all. Your total amount is %s."
66 1
        raise ValueError(value_error_message_keyboard %
67
                         (MAXIMUM_ITEMS_IN_KEYBOARD, items_in_keyboard))
68
69 1
    if items_in_row is not None and \
70
            (MINIMUM_ITEMS_IN_LINE > items_in_row or items_in_row > MAXIMUM_ITEMS_IN_LINE):
71 1
        value_error_message_line = \
72
            "Telegram Bot API limit exceeded: " \
73
            "The keyboard line should have from 1 to %s buttons. You entered %s."
74 1
        raise ValueError(value_error_message_line %
75
                         (MAXIMUM_ITEMS_IN_LINE, items_in_row))
76
77
78 1
def get_total_items_number(items, keyboard) -> int:
79
    """
80
    :param items:
81
    :param keyboard:
82
    :return:
83
    """
84 1
    total_items_number = sum(
85
        len(row) if isinstance(row, (list, tuple, set)) else 1 for row in items)
86
87 1
    if not keyboard:
88 1
        return total_items_number
89
90 1
    keyboard_items = keyboard.__dict__['keyboard']
91 1
    current_keyboard_items_number = sum(len(row) for row in keyboard_items)
92 1
    return total_items_number + current_keyboard_items_number
93
94
95 1
def get_callback_data(
96
        raw_callback: CallbackDataMarker,
97
        front_marker: CallbackDataMarker = str(),
98
        back_marker: CallbackDataMarker = str()) -> str:
99
    """
100
    :param raw_callback:
101
    :param front_marker:
102
    :param back_marker:
103
    :return:
104
    """
105
106 1
    front_marker = get_checked_marker(front_marker)
107 1
    back_marker = get_checked_marker(back_marker)
108
109 1
    callback_data = "%s%s%s" % (front_marker, raw_callback, back_marker)
110
111 1
    if not callback_data:
112 1
        raise ValueError("The callback data cannot be empty.")
113
114 1
    if len(callback_data.encode()) > MAXIMUM_CBD_LENGTH:
115 1
        size_error_message = "The callback data cannot be more than " \
116
                             "64 bytes for one button. Your size is %s" \
117
                             % len(callback_data.encode())
118 1
        raise ValueError(size_error_message)
119
120 1
    return callback_data
121
122
123 1
def get_checked_marker(marker: CallbackDataMarker) -> CallbackDataMarker:
124
    """
125
    :param marker:
126
    :return:
127
    """
128
129 1
    if marker is None:
130 1
        marker = str()
131
132 1
    if not isinstance(marker, callback_data_types):
133 1
        type_error_message = \
134
            "Marker could not have %s type. Only %s allowed." \
135
            % (type(marker), CallbackDataMarker)
136 1
        raise TypeError(type_error_message)
137
138 1
    return marker
139
140
141 1
def get_callback(button_data: tuple) -> str:
142
    """
143
    :param button_data:
144
    :return:
145
    """
146 1
    callback = button_data[1]
147 1
    if not isinstance(callback, callback_data_types):
148 1
        type_error_message = "Callback cannot be %s. Only %s allowed." \
149
                             % (type(callback), callback_data_types)
150 1
        raise TypeError(type_error_message)
151 1
    return callback
152
153
154 1
def get_text(button_data: tuple) -> str:
155
    """
156
    :param button_data:
157
    :return:
158
    """
159 1
    raw_text = button_data[0]
160 1
    if not isinstance(raw_text, button_text_types):
161 1
        type_error_message = "Button text cannot be %s. Only %s allowed." \
162
                             % (type(raw_text), ButtonText)
163 1
        raise TypeError(type_error_message)
164 1
    text = str(raw_text)
165 1
    if not text:
166 1
        raise ValueError("Button text cannot be empty.")
167 1
    return text
168
169
170 1
def get_verified_button_tuple(button_data: InlineButtonData, copy_text_to_callback: bool) -> tuple:
171
    """
172
    :param button_data:
173
    :param copy_text_to_callback:
174
    :return:
175
    """
176 1
    if not isinstance(button_data, (tuple, dict, str, int)):
177 1
        type_error_message = \
178
            "Cannot create %s from %s. Please use %s instead.\n" \
179
            "Probably you specified 'auto_alignment' or 'items_in_line' " \
180
            "parameter for StructuredSequence." \
181
            % (InlineKeyboardButton, type(button_data), InlineButtonData)
182 1
        raise TypeError(type_error_message)
183
184 1
    btn_tuple = get_raw_tuple_from_button_data(button_data, copy_text_to_callback)
185
186 1
    if len(btn_tuple) == 1 or btn_tuple[1] is None:
187 1
        btn_tuple = btn_tuple[0], btn_tuple[0] if copy_text_to_callback else str()
188 1
    return btn_tuple
189
190
191 1
def get_raw_tuple_from_button_data(button_data, copy_text_to_callback):
192
    """
193
    :param button_data:
194
    :param copy_text_to_callback:
195
    :return:
196
    """
197 1
    if isinstance(button_data, (str, int)):
198 1
        btn_tuple = button_data, button_data if copy_text_to_callback else str()
199
200 1
    elif isinstance(button_data, dict):
201 1
        if len(button_data.keys()) != 1:
202 1
            value_type_error = \
203
                "Cannot convert dictionary to InlineButtonData object. " \
204
                "You passed more than one item, but did not add 'text' key."
205 1
            raise ValueError(value_type_error)
206
207 1
        btn_tuple = next(iter(button_data.items()))
208
    else:
209 1
        btn_tuple = button_data
210 1
    return btn_tuple
211
212
213 1
def calculate_items_in_row(items, auto_alignment, reverse_alignment_range) -> Optional[int]:
214
    """
215
    :param items:
216
    :param auto_alignment:
217
    :param reverse_alignment_range:
218
    :return:
219
    """
220
221 1
    items_in_row = None
222 1
    alignment_range = get_alignment_range(auto_alignment)
223
224 1
    if reverse_alignment_range:
225 1
        alignment_range = reversed(alignment_range)
226
227 1
    for divider in alignment_range:
228 1
        if not len(items) % divider:
229 1
            items_in_row = divider
230 1
            break
231
232 1
    return items_in_row
233
234
235 1
def get_alignment_range(auto_alignment):
236
    """
237
    :param auto_alignment:
238
    :return:
239
    """
240
241 1
    if isinstance(auto_alignment, bool):
242 1
        return AUTO_ALIGNMENT_RANGE
243
244 1
    check_alignment_settings(auto_alignment)
245 1
    return auto_alignment
246
247
248 1
def check_alignment_settings(auto_alignment):
249
    """
250
    :param auto_alignment:
251
    :return:
252
    """
253 1
    if not (isinstance(auto_alignment, Iterable)
254
            and all(map(lambda s: isinstance(s, int), auto_alignment))):
255 1
        type_error_message = \
256
            "The auto_alignment variable has not a proper type. " \
257
            "Only Iterable of integers or boolean type allowed.\n" \
258
            "You may define it as 'True' to use AUTO_ALIGNMENT_RANGE."
259 1
        raise TypeError(type_error_message)
260 1
    if max(auto_alignment) > MAXIMUM_ITEMS_IN_LINE \
261
            or min(auto_alignment) < MINIMUM_ITEMS_IN_LINE:
262 1
        value_error_message = \
263
            "The auto_alignment's item values should be between " \
264
            "%s and %s. You entered: %s\n" \
265
            "You may define it as 'True' to use AUTO_ALIGNMENT_RANGE." \
266
            % (MINIMUM_ITEMS_IN_LINE, MAXIMUM_ITEMS_IN_LINE, auto_alignment)
267
        raise ValueError(value_error_message)
268