Passed
Push — master ( c202b7...4e2049 )
by torrua
05:26 queued 11s
created

keyboa.button.Button.generate()   B

Complexity

Conditions 8

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 8

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 34
ccs 13
cts 13
cp 1
rs 7.3333
c 0
b 0
f 0
cc 8
nop 1
crap 8
1
# -*- coding:utf-8 -*-
2 1
"""
3
This module contains all the necessary functions for
4
creating buttons for telegram inline keyboards.
5
"""
6 1
from dataclasses import dataclass
7
from typing import Optional
8 1
from telebot.types import InlineKeyboardButton
9
10 1
from keyboa.constants import (
11
    InlineButtonData,
12
    CallbackDataMarker,
13
    callback_data_types,
14
    MAXIMUM_CBD_LENGTH,
15
    button_text_types,
16
    ButtonText,
17
)
18
19
20 1
@dataclass
21
class Button:
22
    """Default Button class
23
    :button_data: InlineButtonData - an object from which the button will be created:
24
    • If string or an integer, it will be used for both text and callback.
25
    • If tuple, the zero element [0] will be the text, and the first [1] will be the callback.
26
    • If dictionary, there are two options:
27
        > if there is no "text" key in dictionary and only one key exists,
28
            the key will be the text, and the value will be the callback.
29
            In this case, no verification of the dictionary's contents is performed!
30
        > if "text" key exists, function passes the whole dictionary to InlineKeyboardButton,
31
            where dictionary's keys represent object's parameters
32
            and dictionary's values represent parameters' values accordingly.
33
        In all other cases ValueError will be called.
34
35
    :front_marker: CallbackDataMarker - a string to be added to the left side of callback.
36
        Optional. The default value is None.
37
38
    :back_marker: CallbackDataMarker - a string to be added to the right side of callback.
39
        Optional. The default value is None.
40
41
    :copy_text_to_callback: If enabled and button_data is a string or integer,
42
        function will copy button text to callback data (and add markers if they exist).
43
        Optional. The default value is False."""
44
45 1
    button_data: InlineButtonData = None
46 1
    front_marker: CallbackDataMarker = str()
47 1
    back_marker: CallbackDataMarker = str()
48 1
    copy_text_to_callback: Optional[bool] = None
49
50 1
    def generate(self) -> InlineKeyboardButton:
51
        """
52
        This function creates an InlineKeyboardButton object from various data types,
53
        such as str, int, tuple, dict.
54
        :return: InlineKeyboardButton
55
56
        Covered by tests.
57
        """
58
59 1
        if isinstance(self.button_data, InlineKeyboardButton):
60 1
            return self.button_data
61
62 1
        if isinstance(self.button_data, dict) and self.button_data.get("text"):
63 1
            return InlineKeyboardButton(**self.button_data)
64
65 1
        if (
66
            self.copy_text_to_callback is None
67
            and isinstance(self.button_data, (str, int))
68
            and not (self.front_marker or self.back_marker)
69 1
        ):
70 1
            self.copy_text_to_callback = True
71 1
72
        button_tuple = self.get_verified_button_tuple(
73
            self.button_data, self.copy_text_to_callback
74
        )
75 1
        text = self.get_text(button_tuple)
76
        raw_callback = self.get_callback(button_tuple)
77 1
        callback_data = self.get_callback_data(
78
            raw_callback, self.front_marker, self.back_marker
79 1
        )
80 1
81
        prepared_button = {"text": text, "callback_data": callback_data}
82
83
        return InlineKeyboardButton(**prepared_button)
84
85 1
    @staticmethod
86 1
    def get_callback(button_data: tuple) -> str:
87 1
        """
88
        :param button_data:
89
        :return:
90
        """
91 1
        callback = button_data[1]
92 1
        if not isinstance(callback, callback_data_types):
93
            type_error_message = "Callback cannot be %s. Only %s allowed." % (
94 1
                type(callback),
95 1
                callback_data_types,
96
            )
97
            raise TypeError(type_error_message)
98
        return callback
99
100
    @classmethod
101
    def get_callback_data(
102
        cls,
103
        raw_callback: CallbackDataMarker,
104
        front_marker: CallbackDataMarker = str(),
105
        back_marker: CallbackDataMarker = str(),
106
    ) -> str:
107
        """
108 1
        :param raw_callback:
109 1
        :param front_marker:
110
        :param back_marker:
111 1
        :return:
112
        """
113 1
114 1
        front_marker = cls.get_checked_marker(front_marker)
115
        back_marker = cls.get_checked_marker(back_marker)
116 1
117 1
        callback_data = "%s%s%s" % (front_marker, raw_callback, back_marker)
118
119
        if not callback_data:
120
            raise ValueError("The callback data cannot be empty.")
121 1
122
        if len(callback_data.encode()) > MAXIMUM_CBD_LENGTH:
123 1
            size_error_message = (
124
                "The callback data cannot be more than "
125 1
                "64 bytes for one button. Your size is %s" % len(callback_data.encode())
126 1
            )
127
            raise ValueError(size_error_message)
128
129
        return callback_data
130
131
    @staticmethod
132 1
    def get_checked_marker(marker: CallbackDataMarker) -> CallbackDataMarker:
133 1
        """
134
        :param marker:
135 1
        :return:
136 1
        """
137
        if marker is None:
138
            marker = str()
139
140 1
        if not isinstance(marker, callback_data_types):
141
            type_error_message = "Marker could not have %s type. Only %s allowed." % (
142 1
                type(marker),
143
                CallbackDataMarker,
144 1
            )
145 1
            raise TypeError(type_error_message)
146
147
        return marker
148
149
    @staticmethod
150 1
    def get_text(button_data: tuple) -> str:
151 1
        """
152 1
        :param button_data:
153
        :return:
154
        """
155
        raw_text = button_data[0]
156 1
        if not isinstance(raw_text, button_text_types):
157 1
            type_error_message = "Button text cannot be %s. Only %s allowed." % (
158 1
                type(raw_text),
159 1
                ButtonText,
160 1
            )
161
            raise TypeError(type_error_message)
162 1
        text = str(raw_text)
163 1
        if not text:
164
            raise ValueError("Button text cannot be empty.")
165
        return text
166
167
    @classmethod
168
    def get_verified_button_tuple(
169
        cls, button_data: InlineButtonData, copy_text_to_callback: bool
170
    ) -> tuple:
171 1
        """
172 1
        :param button_data:
173
        :param copy_text_to_callback:
174
        :return:
175
        """
176
        if not isinstance(button_data, (tuple, dict, str, int)):
177
            type_error_message = (
178 1
                "Cannot create %s from %s. Please use %s instead.\n"
179
                "Probably you specified 'auto_alignment' or 'items_in_line' "
180 1
                "parameter for StructuredSequence."
181
                % (InlineKeyboardButton, type(button_data), InlineButtonData)
182
            )
183
            raise TypeError(type_error_message)
184 1
185 1
        btn_tuple = cls.get_raw_tuple_from_button_data(
186 1
            button_data, copy_text_to_callback
187
        )
188 1
189
        if len(btn_tuple) == 1 or btn_tuple[1] is None:
190
            btn_tuple = btn_tuple[0], btn_tuple[0] if copy_text_to_callback else str()
191
        return btn_tuple
192
193
    @staticmethod
194
    def get_raw_tuple_from_button_data(button_data, copy_text_to_callback):
195 1
        """
196 1
        :param button_data:
197
        :param copy_text_to_callback:
198 1
        :return:
199 1
        """
200 1
        if isinstance(button_data, (str, int)):
201
            btn_tuple = button_data, button_data if copy_text_to_callback else str()
202
203
        elif isinstance(button_data, dict):
204 1
            if len(button_data.keys()) != 1:
205
                value_type_error = (
206 1
                    "Cannot convert dictionary to InlineButtonData object. "
207
                    "You passed more than one item, but did not add 'text' key."
208 1
                )
209 1
                raise ValueError(value_type_error)
210
211
            btn_tuple = next(iter(button_data.items()))
212
        else:
213
            btn_tuple = button_data
214
        return btn_tuple
215