Passed
Push — master ( 4ab9d5...a05b40 )
by torrua
01:34 queued 14s
created

keyboa.button.Button._raw_tuple_from_button_data()   A

Complexity

Conditions 5

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 14
nop 1
dl 0
loc 23
ccs 11
cts 11
cp 1
crap 5
rs 9.2333
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
    def is_auto_copy_text_to_callback(self):
78
        """
79
        Enable copy_text_to_callback parameter if button_data is str or int
80
        :return:
81
        """
82 1
        if self.copy_text_to_callback is None and isinstance(
83
            self.button_data, (str, int)
84
        ):
85 1
            self.copy_text_to_callback = True
86
87 1
    @classmethod
88 1
    def get_callback(cls, button_data: tuple) -> str:
89
        """
90
        :param button_data:
91
        :return:
92
        """
93 1
        callback = button_data[1]
94 1
        cls.is_callback_proper_type(callback)
95 1
        return callback
96
97 1
    @classmethod
98 1
    def get_callback_data(
99
        cls,
100
        raw_callback: CallbackDataMarker,
101
        front_marker: CallbackDataMarker = str(),
102
        back_marker: CallbackDataMarker = str(),
103
    ) -> str:
104
        """
105
        :param raw_callback:
106
        :param front_marker:
107
        :param back_marker:
108
        :return:
109
        """
110
111 1
        front_marker = cls.get_checked_marker(front_marker)
112 1
        back_marker = cls.get_checked_marker(back_marker)
113
114 1
        callback_data = "%s%s%s" % (front_marker, raw_callback, back_marker)
115
116 1
        if not callback_data:
117 1
            raise ValueError("The callback data cannot be empty.")
118
119 1
        cls.is_callback_data_in_limits(callback_data)
120
121 1
        return callback_data
122
123 1
    @staticmethod
124 1
    def get_checked_marker(marker: CallbackDataMarker) -> CallbackDataMarker:
125
        """
126
        :param marker:
127
        :return:
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 = "Marker could not have %s type. Only %s allowed." % (
134
                type(marker),
135
                CallbackDataMarker,
136
            )
137 1
            raise TypeError(type_error_message)
138
139 1
        return marker
140
141 1
    @staticmethod
142 1
    def get_text(button_data: tuple) -> str:
143
        """
144
        :param button_data:
145
        :return:
146
        """
147 1
        raw_text = button_data[0]
148 1
        if not isinstance(raw_text, button_text_types):
149 1
            type_error_message = "Button text cannot be %s. Only %s allowed." % (
150
                type(raw_text),
151
                ButtonText,
152
            )
153 1
            raise TypeError(type_error_message)
154 1
        text = str(raw_text)
155 1
        if not text:
156 1
            raise ValueError("Button text cannot be empty.")
157 1
        return text
158
159 1
    @property
160 1
    def _verified_button_tuple(self) -> tuple:
161
        """
162
        :return:
163
        """
164 1
        self.is_button_data_proper_type(self.button_data)
165
166 1
        btn_tuple = self._raw_tuple_from_button_data
167
168 1
        if len(btn_tuple) == 1 or btn_tuple[1] is None:
169 1
            btn_tuple = (
170
                btn_tuple[0],
171
                btn_tuple[0] if self.copy_text_to_callback else str(),
172
            )
173 1
        return btn_tuple
174
175 1
    @property
176 1
    def _raw_tuple_from_button_data(self) -> tuple:
177
        """
178
        :return:
179
        """
180 1
        if isinstance(self.button_data, (str, int)):
181 1
            btn_tuple = (
182
                self.button_data,
183
                self.button_data if self.copy_text_to_callback else str(),
184
            )
185
186 1
        elif isinstance(self.button_data, dict):
187 1
            if len(self.button_data.keys()) != 1:
188 1
                value_type_error = (
189
                    "Cannot convert dictionary to InlineButtonData object. "
190
                    "You passed more than one item, but did not add 'text' key."
191
                )
192 1
                raise ValueError(value_type_error)
193
194 1
            btn_tuple = next(iter(self.button_data.items()))
195
        else:
196 1
            btn_tuple = self.button_data
197
        return btn_tuple
198