Passed
Push — master ( fd05b8...265ada )
by torrua
02:26
created

keyboa.button   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 102
dl 0
loc 210
ccs 75
cts 75
cp 1
rs 10
c 0
b 0
f 0
wmc 25

7 Methods

Rating   Name   Duplication   Size   Complexity  
A Button.generate() 0 28 4
A Button.get_raw_tuple_from_button_data() 0 22 5
A Button.get_verified_button_tuple() 0 25 5
A Button.get_checked_marker() 0 18 3
A Button.get_callback() 0 14 2
A Button.get_text() 0 17 3
A Button.get_callback_data() 0 30 3
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
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: bool = False
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
        button_tuple = self.get_verified_button_tuple(
66
            self.button_data, self.copy_text_to_callback
67
        )
68
69 1
        text = self.get_text(button_tuple)
70 1
        raw_callback = self.get_callback(button_tuple)
71 1
        callback_data = self.get_callback_data(
72
            raw_callback, self.front_marker, self.back_marker
73
        )
74
75 1
        prepared_button = {"text": text, "callback_data": callback_data}
76
77 1
        return InlineKeyboardButton(**prepared_button)
78
79 1
    @staticmethod
80 1
    def get_callback(button_data: tuple) -> str:
81
        """
82
        :param button_data:
83
        :return:
84
        """
85 1
        callback = button_data[1]
86 1
        if not isinstance(callback, callback_data_types):
87 1
            type_error_message = "Callback cannot be %s. Only %s allowed." % (
88
                type(callback),
89
                callback_data_types,
90
            )
91 1
            raise TypeError(type_error_message)
92 1
        return callback
93
94 1
    @classmethod
95 1
    def get_callback_data(
96
        cls,
97
        raw_callback: CallbackDataMarker,
98
        front_marker: CallbackDataMarker = str(),
99
        back_marker: CallbackDataMarker = str(),
100
    ) -> str:
101
        """
102
        :param raw_callback:
103
        :param front_marker:
104
        :param back_marker:
105
        :return:
106
        """
107
108 1
        front_marker = cls.get_checked_marker(front_marker)
109 1
        back_marker = cls.get_checked_marker(back_marker)
110
111 1
        callback_data = "%s%s%s" % (front_marker, raw_callback, back_marker)
112
113 1
        if not callback_data:
114 1
            raise ValueError("The callback data cannot be empty.")
115
116 1
        if len(callback_data.encode()) > MAXIMUM_CBD_LENGTH:
117 1
            size_error_message = (
118
                "The callback data cannot be more than "
119
                "64 bytes for one button. Your size is %s" % len(callback_data.encode())
120
            )
121 1
            raise ValueError(size_error_message)
122
123 1
        return callback_data
124
125 1
    @staticmethod
126 1
    def get_checked_marker(marker: CallbackDataMarker) -> CallbackDataMarker:
127
        """
128
        :param marker:
129
        :return:
130
        """
131
        # TODO Refactor Separate functions
132 1
        if marker is None:
133 1
            marker = str()
134
135 1
        if not isinstance(marker, callback_data_types):
136 1
            type_error_message = "Marker could not have %s type. Only %s allowed." % (
137
                type(marker),
138
                CallbackDataMarker,
139
            )
140 1
            raise TypeError(type_error_message)
141
142 1
        return marker
143
144 1
    @staticmethod
145 1
    def get_text(button_data: tuple) -> str:
146
        """
147
        :param button_data:
148
        :return:
149
        """
150 1
        raw_text = button_data[0]
151 1
        if not isinstance(raw_text, button_text_types):
152 1
            type_error_message = "Button text cannot be %s. Only %s allowed." % (
153
                type(raw_text),
154
                ButtonText,
155
            )
156 1
            raise TypeError(type_error_message)
157 1
        text = str(raw_text)
158 1
        if not text:
159 1
            raise ValueError("Button text cannot be empty.")
160 1
        return text
161
162 1
    @classmethod
163 1
    def get_verified_button_tuple(
164
        cls, button_data: InlineButtonData, copy_text_to_callback: bool
165
    ) -> tuple:
166
        """
167
        :param button_data:
168
        :param copy_text_to_callback:
169
        :return:
170
        """
171 1
        if not isinstance(button_data, (tuple, dict, str, int)):
172 1
            type_error_message = (
173
                "Cannot create %s from %s. Please use %s instead.\n"
174
                "Probably you specified 'auto_alignment' or 'items_in_line' "
175
                "parameter for StructuredSequence."
176
                % (InlineKeyboardButton, type(button_data), InlineButtonData)
177
            )
178 1
            raise TypeError(type_error_message)
179
180 1
        btn_tuple = cls.get_raw_tuple_from_button_data(
181
            button_data, copy_text_to_callback
182
        )
183
184 1
        if len(btn_tuple) == 1 or btn_tuple[1] is None:
185 1
            btn_tuple = btn_tuple[0], btn_tuple[0] if copy_text_to_callback else str()
186 1
        return btn_tuple
187
188 1
    @staticmethod
189
    def get_raw_tuple_from_button_data(button_data, copy_text_to_callback):
190
        """
191
        :param button_data:
192
        :param copy_text_to_callback:
193
        :return:
194
        """
195 1
        if isinstance(button_data, (str, int)):
196 1
            btn_tuple = button_data, button_data if copy_text_to_callback else str()
197
198 1
        elif isinstance(button_data, dict):
199 1
            if len(button_data.keys()) != 1:
200 1
                value_type_error = (
201
                    "Cannot convert dictionary to InlineButtonData object. "
202
                    "You passed more than one item, but did not add 'text' key."
203
                )
204 1
                raise ValueError(value_type_error)
205
206 1
            btn_tuple = next(iter(button_data.items()))
207
        else:
208 1
            btn_tuple = button_data
209
        return btn_tuple
210