Passed
Push — master ( fe11c1...1299d2 )
by torrua
01:29 queued 10s
created

keyboa.button.Button.button()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 1
dl 0
loc 3
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 generate(self) -> InlineKeyboardButton:
50
        """
51
        This function creates an InlineKeyboardButton object from various data types,
52
        such as str, int, tuple, dict.
53
        :return: InlineKeyboardButton
54
55
        Covered by tests.
56
        """
57
58 1
        if isinstance(self.button_data, InlineKeyboardButton):
59 1
            return self.button_data
60
61 1
        if isinstance(self.button_data, dict) and self.button_data.get("text"):
62 1
            return InlineKeyboardButton(**self.button_data)
63
64 1
        self.is_auto_copy_text_to_callback()
65
66 1
        button_tuple = self._verified_button_tuple
67 1
        text = self.get_text(button_tuple)
68 1
        raw_callback = self.get_callback(button_tuple)
69 1
        callback_data = self.get_callback_data(
70
            raw_callback, self.front_marker, self.back_marker
71
        )
72
73 1
        prepared_button = {"text": text, "callback_data": callback_data}
74
75 1
        return InlineKeyboardButton(**prepared_button)
76
77 1
    @property
78
    def button(self):
79 1
        return self.generate()
80
81 1
    def is_auto_copy_text_to_callback(self):
82
        """
83
        Enable copy_text_to_callback parameter if button_data is str or int
84
        :return:
85
        """
86 1
        if self.copy_text_to_callback is None and isinstance(
87
            self.button_data, (str, int)
88
        ):
89 1
            self.copy_text_to_callback = True
90
91 1
    @classmethod
92 1
    def get_callback(cls, button_data: tuple) -> str:
93
        """
94
        :param button_data:
95
        :return:
96
        """
97 1
        callback = button_data[1]
98 1
        cls.is_callback_proper_type(callback)
99 1
        return callback
100
101 1
    @classmethod
102 1
    def get_callback_data(
103
        cls,
104
        raw_callback: CallbackDataMarker,
105
        front_marker: CallbackDataMarker = str(),
106
        back_marker: CallbackDataMarker = str(),
107
    ) -> str:
108
        """
109
        :param raw_callback:
110
        :param front_marker:
111
        :param back_marker:
112
        :return:
113
        """
114
115 1
        front_marker = cls.get_checked_marker(front_marker)
116 1
        back_marker = cls.get_checked_marker(back_marker)
117
118 1
        callback_data = "%s%s%s" % (front_marker, raw_callback, back_marker)
119
120 1
        if not callback_data:
121 1
            raise ValueError("The callback data cannot be empty.")
122
123 1
        cls.is_callback_data_in_limits(callback_data)
124
125 1
        return callback_data
126
127 1
    @staticmethod
128 1
    def get_checked_marker(marker: CallbackDataMarker) -> CallbackDataMarker:
129
        """
130
        :param marker:
131
        :return:
132
        """
133 1
        if marker is None:
134 1
            marker = str()
135
136 1
        if not isinstance(marker, callback_data_types):
137 1
            type_error_message = "Marker could not have %s type. Only %s allowed." % (
138
                type(marker),
139
                CallbackDataMarker,
140
            )
141 1
            raise TypeError(type_error_message)
142
143 1
        return marker
144
145 1
    @staticmethod
146 1
    def get_text(button_data: tuple) -> str:
147
        """
148
        :param button_data:
149
        :return:
150
        """
151 1
        raw_text = button_data[0]
152 1
        if not isinstance(raw_text, button_text_types):
153 1
            type_error_message = "Button text cannot be %s. Only %s allowed." % (
154
                type(raw_text),
155
                ButtonText,
156
            )
157 1
            raise TypeError(type_error_message)
158 1
        text = str(raw_text)
159 1
        if not text:
160 1
            raise ValueError("Button text cannot be empty.")
161 1
        return text
162
163 1
    @property
164 1
    def _verified_button_tuple(self) -> tuple:
165
        """
166
        :return:
167
        """
168 1
        self.is_button_data_proper_type(self.button_data)
169
170 1
        btn_tuple = self._raw_tuple_from_button_data
171
172 1
        if len(btn_tuple) == 1 or btn_tuple[1] is None:
173 1
            btn_tuple = (
174
                btn_tuple[0],
175
                btn_tuple[0] if self.copy_text_to_callback else str(),
176
            )
177 1
        return btn_tuple
178
179 1
    @property
180 1
    def _raw_tuple_from_button_data(self) -> tuple:
181
        """
182
        :return:
183
        """
184 1
        if isinstance(self.button_data, (str, int)):
185 1
            btn_tuple = (
186
                self.button_data,
187
                self.button_data if self.copy_text_to_callback else str(),
188
            )
189
190 1
        elif isinstance(self.button_data, dict):
191 1
            if len(self.button_data.keys()) != 1:
192 1
                value_type_error = (
193
                    "Cannot convert dictionary to InlineButtonData object. "
194
                    "You passed more than one item, but did not add 'text' key."
195
                )
196 1
                raise ValueError(value_type_error)
197
198 1
            btn_tuple = next(iter(self.button_data.items()))
199
        else:
200 1
            btn_tuple = self.button_data
201
        return btn_tuple
202