keyboa.button.Button.__call__()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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