telegrinder 0.3.4.post1__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +30 -31
- telegrinder/api/__init__.py +2 -1
- telegrinder/api/api.py +28 -20
- telegrinder/api/error.py +8 -4
- telegrinder/api/response.py +2 -2
- telegrinder/api/token.py +2 -2
- telegrinder/bot/__init__.py +6 -0
- telegrinder/bot/bot.py +38 -31
- telegrinder/bot/cute_types/__init__.py +2 -0
- telegrinder/bot/cute_types/base.py +54 -128
- telegrinder/bot/cute_types/callback_query.py +76 -61
- telegrinder/bot/cute_types/chat_join_request.py +4 -3
- telegrinder/bot/cute_types/chat_member_updated.py +28 -31
- telegrinder/bot/cute_types/inline_query.py +5 -4
- telegrinder/bot/cute_types/message.py +555 -602
- telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
- telegrinder/bot/cute_types/update.py +20 -12
- telegrinder/bot/cute_types/utils.py +3 -36
- telegrinder/bot/dispatch/__init__.py +4 -0
- telegrinder/bot/dispatch/abc.py +8 -9
- telegrinder/bot/dispatch/context.py +5 -7
- telegrinder/bot/dispatch/dispatch.py +85 -33
- telegrinder/bot/dispatch/handler/abc.py +5 -6
- telegrinder/bot/dispatch/handler/audio_reply.py +2 -2
- telegrinder/bot/dispatch/handler/base.py +3 -3
- telegrinder/bot/dispatch/handler/document_reply.py +2 -2
- telegrinder/bot/dispatch/handler/func.py +36 -42
- telegrinder/bot/dispatch/handler/media_group_reply.py +5 -4
- telegrinder/bot/dispatch/handler/message_reply.py +2 -2
- telegrinder/bot/dispatch/handler/photo_reply.py +2 -2
- telegrinder/bot/dispatch/handler/sticker_reply.py +2 -2
- telegrinder/bot/dispatch/handler/video_reply.py +2 -2
- telegrinder/bot/dispatch/middleware/abc.py +83 -8
- telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
- telegrinder/bot/dispatch/process.py +44 -50
- telegrinder/bot/dispatch/return_manager/__init__.py +2 -0
- telegrinder/bot/dispatch/return_manager/abc.py +6 -10
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
- telegrinder/bot/dispatch/view/__init__.py +2 -0
- telegrinder/bot/dispatch/view/abc.py +10 -6
- telegrinder/bot/dispatch/view/base.py +81 -50
- telegrinder/bot/dispatch/view/box.py +20 -9
- telegrinder/bot/dispatch/view/callback_query.py +3 -4
- telegrinder/bot/dispatch/view/chat_join_request.py +2 -7
- telegrinder/bot/dispatch/view/chat_member.py +3 -5
- telegrinder/bot/dispatch/view/inline_query.py +3 -4
- telegrinder/bot/dispatch/view/message.py +3 -4
- telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
- telegrinder/bot/dispatch/view/raw.py +42 -40
- telegrinder/bot/dispatch/waiter_machine/actions.py +5 -4
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +9 -7
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +3 -2
- telegrinder/bot/dispatch/waiter_machine/machine.py +113 -34
- telegrinder/bot/dispatch/waiter_machine/middleware.py +15 -10
- telegrinder/bot/dispatch/waiter_machine/short_state.py +7 -18
- telegrinder/bot/polling/polling.py +62 -54
- telegrinder/bot/rules/__init__.py +24 -1
- telegrinder/bot/rules/abc.py +17 -10
- telegrinder/bot/rules/callback_data.py +20 -61
- telegrinder/bot/rules/chat_join.py +6 -4
- telegrinder/bot/rules/command.py +4 -4
- telegrinder/bot/rules/enum_text.py +1 -4
- telegrinder/bot/rules/func.py +5 -3
- telegrinder/bot/rules/fuzzy.py +1 -1
- telegrinder/bot/rules/id.py +24 -0
- telegrinder/bot/rules/inline.py +6 -4
- telegrinder/bot/rules/integer.py +2 -1
- telegrinder/bot/rules/logic.py +18 -0
- telegrinder/bot/rules/markup.py +5 -6
- telegrinder/bot/rules/message.py +2 -4
- telegrinder/bot/rules/message_entities.py +1 -3
- telegrinder/bot/rules/node.py +15 -9
- telegrinder/bot/rules/payload.py +81 -0
- telegrinder/bot/rules/payment_invoice.py +29 -0
- telegrinder/bot/rules/regex.py +5 -6
- telegrinder/bot/rules/state.py +1 -3
- telegrinder/bot/rules/text.py +10 -5
- telegrinder/bot/rules/update.py +0 -0
- telegrinder/bot/scenario/abc.py +2 -4
- telegrinder/bot/scenario/checkbox.py +12 -14
- telegrinder/bot/scenario/choice.py +6 -9
- telegrinder/client/__init__.py +9 -1
- telegrinder/client/abc.py +35 -10
- telegrinder/client/aiohttp.py +28 -24
- telegrinder/client/form_data.py +31 -0
- telegrinder/client/sonic.py +212 -0
- telegrinder/model.py +38 -145
- telegrinder/modules.py +3 -1
- telegrinder/msgspec_utils.py +136 -68
- telegrinder/node/__init__.py +74 -13
- telegrinder/node/attachment.py +92 -16
- telegrinder/node/base.py +196 -68
- telegrinder/node/callback_query.py +17 -16
- telegrinder/node/command.py +3 -2
- telegrinder/node/composer.py +40 -75
- telegrinder/node/container.py +13 -7
- telegrinder/node/either.py +82 -0
- telegrinder/node/event.py +20 -31
- telegrinder/node/file.py +51 -0
- telegrinder/node/me.py +4 -5
- telegrinder/node/payload.py +78 -0
- telegrinder/node/polymorphic.py +27 -8
- telegrinder/node/rule.py +2 -6
- telegrinder/node/scope.py +4 -6
- telegrinder/node/source.py +37 -21
- telegrinder/node/text.py +20 -8
- telegrinder/node/tools/generator.py +7 -11
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +0 -61
- telegrinder/tools/__init__.py +97 -38
- telegrinder/tools/adapter/__init__.py +19 -0
- telegrinder/tools/adapter/abc.py +49 -0
- telegrinder/tools/adapter/dataclass.py +56 -0
- telegrinder/{bot/rules → tools}/adapter/event.py +8 -10
- telegrinder/{bot/rules → tools}/adapter/node.py +8 -10
- telegrinder/{bot/rules → tools}/adapter/raw_event.py +2 -2
- telegrinder/{bot/rules → tools}/adapter/raw_update.py +2 -2
- telegrinder/tools/buttons.py +52 -26
- telegrinder/tools/callback_data_serilization/__init__.py +5 -0
- telegrinder/tools/callback_data_serilization/abc.py +51 -0
- telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
- telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
- telegrinder/tools/error_handler/abc.py +4 -7
- telegrinder/tools/error_handler/error.py +0 -0
- telegrinder/tools/error_handler/error_handler.py +34 -48
- telegrinder/tools/formatting/__init__.py +57 -37
- telegrinder/tools/formatting/deep_links.py +541 -0
- telegrinder/tools/formatting/{html.py → html_formatter.py} +51 -79
- telegrinder/tools/formatting/spec_html_formats.py +14 -60
- telegrinder/tools/functional.py +1 -5
- telegrinder/tools/global_context/global_context.py +26 -51
- telegrinder/tools/global_context/telegrinder_ctx.py +3 -3
- telegrinder/tools/i18n/abc.py +0 -0
- telegrinder/tools/i18n/middleware/abc.py +3 -6
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard.py +9 -9
- telegrinder/tools/lifespan.py +105 -0
- telegrinder/tools/limited_dict.py +5 -10
- telegrinder/tools/loop_wrapper/abc.py +7 -2
- telegrinder/tools/loop_wrapper/loop_wrapper.py +40 -95
- telegrinder/tools/magic.py +184 -34
- telegrinder/tools/state_storage/__init__.py +0 -0
- telegrinder/tools/state_storage/abc.py +5 -9
- telegrinder/tools/state_storage/memory.py +1 -1
- telegrinder/tools/strings.py +13 -0
- telegrinder/types/__init__.py +8 -0
- telegrinder/types/enums.py +31 -21
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +531 -109
- telegrinder/types/objects.py +934 -826
- telegrinder/verification_utils.py +0 -2
- {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +2 -2
- telegrinder-0.4.0.dist-info/METADATA +144 -0
- telegrinder-0.4.0.dist-info/RECORD +182 -0
- {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
- telegrinder/bot/rules/adapter/__init__.py +0 -17
- telegrinder/bot/rules/adapter/abc.py +0 -31
- telegrinder/node/message.py +0 -14
- telegrinder/node/update.py +0 -15
- telegrinder/tools/formatting/links.py +0 -38
- telegrinder/tools/kb_set/__init__.py +0 -4
- telegrinder/tools/kb_set/base.py +0 -15
- telegrinder/tools/kb_set/yaml.py +0 -63
- telegrinder-0.3.4.post1.dist-info/METADATA +0 -110
- telegrinder-0.3.4.post1.dist-info/RECORD +0 -165
- /telegrinder/{bot/rules → tools}/adapter/errors.py +0 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
1
4
|
import html
|
|
2
5
|
import string
|
|
3
6
|
import typing
|
|
@@ -6,34 +9,29 @@ from contextlib import suppress
|
|
|
6
9
|
from telegrinder.tools.parse_mode import ParseMode
|
|
7
10
|
from telegrinder.types.enums import ProgrammingLanguage
|
|
8
11
|
|
|
9
|
-
from .
|
|
10
|
-
get_channel_boost_link,
|
|
11
|
-
get_invite_chat_link,
|
|
12
|
-
get_mention_link,
|
|
13
|
-
get_resolve_domain_link,
|
|
14
|
-
get_start_bot_link,
|
|
15
|
-
get_start_group_link,
|
|
16
|
-
)
|
|
12
|
+
from .deep_links import tg_mention_link
|
|
17
13
|
from .spec_html_formats import SpecialFormat, is_spec_format
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
type HTMLFormat = str | TagFormat
|
|
16
|
+
|
|
17
|
+
HTML_UNION_SPECIFIERS_SEPARATOR: typing.Final[str] = "+"
|
|
18
|
+
TAG_FORMAT: typing.Final[str] = "<{tag}{data}>{content}</{tag}>"
|
|
19
|
+
QUOT_MARK: typing.Final[str] = '"'
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
class StringFormatterProto(typing.Protocol):
|
|
24
|
-
def format_field(self, value: typing.Any, fmt: str) ->
|
|
23
|
+
def format_field(self, value: typing.Any, fmt: str) -> HTMLFormatter: ...
|
|
25
24
|
|
|
26
|
-
def format(self, __string: str, *args: object, **kwargs: object) ->
|
|
25
|
+
def format(self, __string: str, *args: object, **kwargs: object) -> HTMLFormatter: ...
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
class StringFormatter(string.Formatter):
|
|
30
29
|
"""String formatter, using substitutions from args and kwargs.
|
|
31
30
|
The substitutions are identified by braces ('{' and '}') with
|
|
32
|
-
specifiers: `bold`, `italic`, etc
|
|
31
|
+
specifiers: `bold`, `italic`, `underline`, `strike` etc...
|
|
33
32
|
"""
|
|
34
33
|
|
|
35
34
|
__formats__: typing.ClassVar[tuple[str, ...]] = (
|
|
36
|
-
"blockquote",
|
|
37
35
|
"bold",
|
|
38
36
|
"code_inline",
|
|
39
37
|
"italic",
|
|
@@ -42,9 +40,10 @@ class StringFormatter(string.Formatter):
|
|
|
42
40
|
"underline",
|
|
43
41
|
)
|
|
44
42
|
|
|
45
|
-
def
|
|
43
|
+
def validate_html_format(self, value: typing.Any, fmt: str) -> HTMLFormat:
|
|
46
44
|
if not fmt:
|
|
47
45
|
raise ValueError("Formats union should be: format+format.")
|
|
46
|
+
|
|
48
47
|
if fmt not in self.__formats__:
|
|
49
48
|
raise ValueError(
|
|
50
49
|
"Unknown format {!r} for object of type {!r}.".format(
|
|
@@ -52,12 +51,13 @@ class StringFormatter(string.Formatter):
|
|
|
52
51
|
type(value).__name__,
|
|
53
52
|
)
|
|
54
53
|
)
|
|
54
|
+
|
|
55
55
|
return fmt
|
|
56
56
|
|
|
57
|
-
def get_spec_formatter(self, value: SpecialFormat) -> typing.Callable[...,
|
|
57
|
+
def get_spec_formatter(self, value: SpecialFormat) -> typing.Callable[..., TagFormat]:
|
|
58
58
|
return globals()[value.__formatter_name__]
|
|
59
59
|
|
|
60
|
-
def
|
|
60
|
+
def make_tag_format(self, value: typing.Any, fmts: list[HTMLFormat]) -> TagFormat:
|
|
61
61
|
if is_spec_format(value):
|
|
62
62
|
value = value.string
|
|
63
63
|
|
|
@@ -79,7 +79,7 @@ class StringFormatter(string.Formatter):
|
|
|
79
79
|
else current_format
|
|
80
80
|
)
|
|
81
81
|
|
|
82
|
-
def format_field(self, value: typing.Any, fmt: str) ->
|
|
82
|
+
def format_field(self, value: typing.Any, fmt: str) -> HTMLFormatter:
|
|
83
83
|
with suppress(ValueError):
|
|
84
84
|
return HTMLFormatter(
|
|
85
85
|
format(
|
|
@@ -87,7 +87,7 @@ class StringFormatter(string.Formatter):
|
|
|
87
87
|
value.formatting()
|
|
88
88
|
if isinstance(value, TagFormat)
|
|
89
89
|
else (
|
|
90
|
-
self.get_spec_formatter(value)(**value
|
|
90
|
+
self.get_spec_formatter(value)(**dataclasses.asdict(value)).formatting()
|
|
91
91
|
if is_spec_format(value)
|
|
92
92
|
else value
|
|
93
93
|
)
|
|
@@ -95,31 +95,35 @@ class StringFormatter(string.Formatter):
|
|
|
95
95
|
fmt,
|
|
96
96
|
)
|
|
97
97
|
)
|
|
98
|
-
return self.format_raw_value(value, fmt)
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
fmts = list(
|
|
100
|
+
map(
|
|
101
|
+
lambda fmt: self.validate_html_format(value, fmt),
|
|
102
|
+
fmt.split(HTML_UNION_SPECIFIERS_SEPARATOR),
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
tag_format = self.make_tag_format(value, fmts)
|
|
103
106
|
|
|
104
107
|
if is_spec_format(value):
|
|
105
108
|
value.string = tag_format
|
|
106
|
-
tag_format = self.get_spec_formatter(value)(**value
|
|
109
|
+
tag_format = self.get_spec_formatter(value)(**dataclasses.asdict(value))
|
|
107
110
|
|
|
108
111
|
return tag_format.formatting()
|
|
109
112
|
|
|
110
|
-
def format(self, __string: str, *args: object, **kwargs: object) ->
|
|
113
|
+
def format(self, __string: str, *args: object, **kwargs: object) -> HTMLFormatter:
|
|
111
114
|
return HTMLFormatter(super().format(__string, *args, **kwargs))
|
|
112
115
|
|
|
113
116
|
|
|
114
117
|
class FormatString(str):
|
|
115
118
|
STRING_FORMATTER: StringFormatterProto = StringFormatter()
|
|
116
119
|
|
|
117
|
-
def __new__(cls, string: str) -> typing.Self:
|
|
120
|
+
def __new__(cls, string: str, /) -> typing.Self:
|
|
118
121
|
if isinstance(string, TagFormat):
|
|
119
122
|
return super().__new__(cls, string.formatting())
|
|
120
123
|
return super().__new__(cls, string)
|
|
121
124
|
|
|
122
|
-
def __add__(self, value: str) ->
|
|
125
|
+
def __add__(self, value: str) -> HTMLFormatter:
|
|
126
|
+
"""Returns self+value."""
|
|
123
127
|
return HTMLFormatter(
|
|
124
128
|
str.__add__(
|
|
125
129
|
escape(self),
|
|
@@ -127,12 +131,12 @@ class FormatString(str):
|
|
|
127
131
|
)
|
|
128
132
|
)
|
|
129
133
|
|
|
130
|
-
def __radd__(self, value: str) ->
|
|
131
|
-
"""
|
|
134
|
+
def __radd__(self, value: str) -> HTMLFormatter:
|
|
135
|
+
"""Returns value+self."""
|
|
132
136
|
return HTMLFormatter(FormatString.__add__(FormatString(value), self).as_str())
|
|
133
137
|
|
|
134
138
|
def as_str(self) -> str:
|
|
135
|
-
"""
|
|
139
|
+
"""Returns self as a standart string."""
|
|
136
140
|
return self.__str__()
|
|
137
141
|
|
|
138
142
|
def format(self, *args: object, **kwargs: object) -> "HTMLFormatter":
|
|
@@ -152,9 +156,10 @@ class TagFormat(FormatString):
|
|
|
152
156
|
def __new__(
|
|
153
157
|
cls,
|
|
154
158
|
string: str,
|
|
159
|
+
/,
|
|
155
160
|
*,
|
|
156
161
|
tag: str,
|
|
157
|
-
**data: typing.Any,
|
|
162
|
+
**data: typing.Any | None,
|
|
158
163
|
) -> typing.Self:
|
|
159
164
|
if isinstance(string, TagFormat):
|
|
160
165
|
string = string.formatting()
|
|
@@ -166,15 +171,16 @@ class TagFormat(FormatString):
|
|
|
166
171
|
),
|
|
167
172
|
):
|
|
168
173
|
string = escape(string)
|
|
174
|
+
|
|
169
175
|
obj = super().__new__(cls, string)
|
|
170
176
|
obj.tag = tag
|
|
171
177
|
obj.data = data
|
|
172
178
|
return obj
|
|
173
179
|
|
|
174
180
|
def get_tag_data(self) -> str:
|
|
175
|
-
return "".join(f" {k}={v}" for k, v in self.data.items())
|
|
181
|
+
return "".join(f" {k}={v}" if v is not None else f" {k}" for k, v in self.data.items())
|
|
176
182
|
|
|
177
|
-
def formatting(self) ->
|
|
183
|
+
def formatting(self) -> HTMLFormatter:
|
|
178
184
|
return HTMLFormatter(
|
|
179
185
|
TAG_FORMAT.format(
|
|
180
186
|
tag=self.tag,
|
|
@@ -185,11 +191,10 @@ class TagFormat(FormatString):
|
|
|
185
191
|
|
|
186
192
|
|
|
187
193
|
class HTMLFormatter(FormatString):
|
|
188
|
-
"""
|
|
189
|
-
>>>
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
'Hi, <i>Max</i>'
|
|
194
|
+
""">>> HTMLFormatter(bold("Hello, World"))
|
|
195
|
+
>>> '<b>Hello, World</b>'
|
|
196
|
+
>>> HTMLFormatter("Hi, {name:italic}").format(name="Max")
|
|
197
|
+
>>> 'Hi, <i>Max</i>'
|
|
193
198
|
"""
|
|
194
199
|
|
|
195
200
|
PARSE_MODE = ParseMode.HTML
|
|
@@ -201,18 +206,18 @@ def escape(string: str) -> EscapedString:
|
|
|
201
206
|
return EscapedString(html.escape(string, quote=False))
|
|
202
207
|
|
|
203
208
|
|
|
204
|
-
def block_quote(string: str) -> TagFormat:
|
|
205
|
-
return TagFormat(
|
|
209
|
+
def block_quote(string: str, expandable: bool = False) -> TagFormat:
|
|
210
|
+
return TagFormat(
|
|
211
|
+
string,
|
|
212
|
+
tag="blockquote",
|
|
213
|
+
**{} if not expandable else {"expandable": None},
|
|
214
|
+
)
|
|
206
215
|
|
|
207
216
|
|
|
208
217
|
def bold(string: str) -> TagFormat:
|
|
209
218
|
return TagFormat(string, tag="b")
|
|
210
219
|
|
|
211
220
|
|
|
212
|
-
def channel_boost_link(channel_id: str | int, string: str | None = None):
|
|
213
|
-
return link(get_channel_boost_link(channel_id), string)
|
|
214
|
-
|
|
215
|
-
|
|
216
221
|
def code_inline(string: str) -> TagFormat:
|
|
217
222
|
return TagFormat(string, tag="code")
|
|
218
223
|
|
|
@@ -240,38 +245,16 @@ def spoiler(string: str) -> TagFormat:
|
|
|
240
245
|
return TagFormat(string, tag="tg-spoiler")
|
|
241
246
|
|
|
242
247
|
|
|
243
|
-
def start_bot_link(bot_id: str | int, data: str, string: str | None = None) -> TagFormat:
|
|
244
|
-
return link(get_start_bot_link(bot_id, data), string)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def start_group_link(bot_id: str | int, data: str, string: str | None = None) -> TagFormat:
|
|
248
|
-
return link(get_start_group_link(bot_id, data), string)
|
|
249
|
-
|
|
250
|
-
|
|
251
248
|
def strike(string: str) -> TagFormat:
|
|
252
249
|
return TagFormat(string, tag="s")
|
|
253
250
|
|
|
254
251
|
|
|
255
252
|
def mention(string: str, user_id: int) -> TagFormat:
|
|
256
|
-
return link(
|
|
253
|
+
return link(tg_mention_link(user_id=user_id), string)
|
|
257
254
|
|
|
258
255
|
|
|
259
256
|
def tg_emoji(string: str, emoji_id: int) -> TagFormat:
|
|
260
|
-
return TagFormat(string, tag="tg-emoji",
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def invite_chat_link(invite_link: str, string: str | None = None) -> TagFormat:
|
|
264
|
-
return link(
|
|
265
|
-
get_invite_chat_link(invite_link),
|
|
266
|
-
string or f"https://t.me/joinchat/{invite_link}",
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
def resolve_domain(username: str, string: str | None = None) -> TagFormat:
|
|
271
|
-
return link(
|
|
272
|
-
get_resolve_domain_link(username),
|
|
273
|
-
string or f"t.me/{username}",
|
|
274
|
-
)
|
|
257
|
+
return TagFormat(string, tag="tg-emoji", emoji_id=emoji_id)
|
|
275
258
|
|
|
276
259
|
|
|
277
260
|
def underline(string: str) -> TagFormat:
|
|
@@ -284,24 +267,13 @@ __all__ = (
|
|
|
284
267
|
"SpecialFormat",
|
|
285
268
|
"block_quote",
|
|
286
269
|
"bold",
|
|
287
|
-
"channel_boost_link",
|
|
288
270
|
"code_inline",
|
|
289
271
|
"escape",
|
|
290
|
-
"get_channel_boost_link",
|
|
291
|
-
"get_invite_chat_link",
|
|
292
|
-
"get_mention_link",
|
|
293
|
-
"get_resolve_domain_link",
|
|
294
|
-
"get_start_bot_link",
|
|
295
|
-
"get_start_group_link",
|
|
296
|
-
"invite_chat_link",
|
|
297
272
|
"italic",
|
|
298
273
|
"link",
|
|
299
274
|
"mention",
|
|
300
275
|
"pre_code",
|
|
301
|
-
"resolve_domain",
|
|
302
276
|
"spoiler",
|
|
303
|
-
"start_bot_link",
|
|
304
|
-
"start_group_link",
|
|
305
277
|
"strike",
|
|
306
278
|
"tg_emoji",
|
|
307
279
|
"underline",
|
|
@@ -4,28 +4,20 @@ import typing
|
|
|
4
4
|
from telegrinder.types.enums import ProgrammingLanguage
|
|
5
5
|
|
|
6
6
|
SpecialFormat: typing.TypeAlias = typing.Union[
|
|
7
|
-
"
|
|
8
|
-
"InviteChatLink",
|
|
7
|
+
"BlockQuote",
|
|
9
8
|
"Link",
|
|
10
9
|
"Mention",
|
|
11
10
|
"PreCode",
|
|
12
|
-
"ResolveDomain",
|
|
13
|
-
"StartBotLink",
|
|
14
|
-
"StartGroupLink",
|
|
15
11
|
"TgEmoji",
|
|
16
12
|
]
|
|
17
13
|
|
|
18
14
|
|
|
19
15
|
def is_spec_format(obj: typing.Any) -> typing.TypeGuard[SpecialFormat]:
|
|
20
|
-
return (
|
|
21
|
-
dataclasses.is_dataclass(obj)
|
|
22
|
-
and hasattr(obj, "__formatter_name__")
|
|
23
|
-
and isinstance(obj, BaseSpecFormat)
|
|
24
|
-
)
|
|
16
|
+
return dataclasses.is_dataclass(obj) and hasattr(obj, "__formatter_name__") and isinstance(obj, Base)
|
|
25
17
|
|
|
26
18
|
|
|
27
|
-
@dataclasses.dataclass(repr=False
|
|
28
|
-
class
|
|
19
|
+
@dataclasses.dataclass(repr=False)
|
|
20
|
+
class Base:
|
|
29
21
|
__formatter_name__: typing.ClassVar[str] = dataclasses.field(init=False, repr=False)
|
|
30
22
|
|
|
31
23
|
def __repr__(self) -> str:
|
|
@@ -33,23 +25,7 @@ class BaseSpecFormat:
|
|
|
33
25
|
|
|
34
26
|
|
|
35
27
|
@dataclasses.dataclass(repr=False, slots=True)
|
|
36
|
-
class
|
|
37
|
-
__formatter_name__ = "channel_boost_link"
|
|
38
|
-
|
|
39
|
-
channel_id: str | int
|
|
40
|
-
string: str | None = None
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@dataclasses.dataclass(repr=False, slots=True)
|
|
44
|
-
class InviteChatLink(BaseSpecFormat):
|
|
45
|
-
__formatter_name__ = "invite_chat_link"
|
|
46
|
-
|
|
47
|
-
invite_link: str
|
|
48
|
-
string: str | None = None
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@dataclasses.dataclass(repr=False, slots=True)
|
|
52
|
-
class Mention(BaseSpecFormat):
|
|
28
|
+
class Mention(Base):
|
|
53
29
|
__formatter_name__ = "mention"
|
|
54
30
|
|
|
55
31
|
string: str
|
|
@@ -57,7 +33,7 @@ class Mention(BaseSpecFormat):
|
|
|
57
33
|
|
|
58
34
|
|
|
59
35
|
@dataclasses.dataclass(repr=False, slots=True)
|
|
60
|
-
class Link(
|
|
36
|
+
class Link(Base):
|
|
61
37
|
__formatter_name__ = "link"
|
|
62
38
|
|
|
63
39
|
href: str
|
|
@@ -65,7 +41,7 @@ class Link(BaseSpecFormat):
|
|
|
65
41
|
|
|
66
42
|
|
|
67
43
|
@dataclasses.dataclass(repr=False, slots=True)
|
|
68
|
-
class PreCode(
|
|
44
|
+
class PreCode(Base):
|
|
69
45
|
__formatter_name__ = "pre_code"
|
|
70
46
|
|
|
71
47
|
string: str
|
|
@@ -73,7 +49,7 @@ class PreCode(BaseSpecFormat):
|
|
|
73
49
|
|
|
74
50
|
|
|
75
51
|
@dataclasses.dataclass(repr=False, slots=True)
|
|
76
|
-
class TgEmoji(
|
|
52
|
+
class TgEmoji(Base):
|
|
77
53
|
__formatter_name__ = "tg_emoji"
|
|
78
54
|
|
|
79
55
|
string: str
|
|
@@ -81,41 +57,19 @@ class TgEmoji(BaseSpecFormat):
|
|
|
81
57
|
|
|
82
58
|
|
|
83
59
|
@dataclasses.dataclass(repr=False, slots=True)
|
|
84
|
-
class
|
|
85
|
-
__formatter_name__ = "
|
|
60
|
+
class BlockQuote(Base):
|
|
61
|
+
__formatter_name__ = "block_quote"
|
|
86
62
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
string: str | None
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@dataclasses.dataclass(repr=False, slots=True)
|
|
93
|
-
class StartGroupLink(BaseSpecFormat):
|
|
94
|
-
__formatter_name__ = "start_group_link"
|
|
95
|
-
|
|
96
|
-
bot_id: str | int
|
|
97
|
-
data: str
|
|
98
|
-
string: str | None = None
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
@dataclasses.dataclass(repr=False, slots=True)
|
|
102
|
-
class ResolveDomain(BaseSpecFormat):
|
|
103
|
-
__formatter_name__ = "resolve_domain"
|
|
104
|
-
|
|
105
|
-
username: str
|
|
106
|
-
string: str | None = None
|
|
63
|
+
string: str
|
|
64
|
+
expandable: bool = False
|
|
107
65
|
|
|
108
66
|
|
|
109
67
|
__all__ = (
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"InviteChatLink",
|
|
68
|
+
"Base",
|
|
69
|
+
"BlockQuote",
|
|
113
70
|
"Link",
|
|
114
71
|
"Mention",
|
|
115
72
|
"PreCode",
|
|
116
|
-
"ResolveDomain",
|
|
117
73
|
"SpecialFormat",
|
|
118
|
-
"StartBotLink",
|
|
119
|
-
"StartGroupLink",
|
|
120
74
|
"TgEmoji",
|
|
121
75
|
)
|
telegrinder/tools/functional.py
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
|
|
3
1
|
from fntypes import Nothing, Option, Some
|
|
4
2
|
|
|
5
|
-
T = typing.TypeVar("T")
|
|
6
|
-
|
|
7
3
|
|
|
8
|
-
def from_optional(value:
|
|
4
|
+
def from_optional[Value](value: Value | None, /) -> Option[Value]:
|
|
9
5
|
return Some(value) if value is not None else Nothing()
|
|
10
6
|
|
|
11
7
|
|
|
@@ -65,20 +65,19 @@ def root_protection(func: F) -> F:
|
|
|
65
65
|
return wrapper # type: ignore
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
def ctx_var(
|
|
68
|
+
def ctx_var(*, default: T, frozen: bool = False) -> T:
|
|
69
69
|
"""Example:
|
|
70
70
|
```
|
|
71
71
|
class MyCtx(GlobalContext):
|
|
72
|
-
name:
|
|
73
|
-
URL:
|
|
72
|
+
name: str
|
|
73
|
+
URL: str = ctx_var("https://google.com", frozen=True)
|
|
74
74
|
|
|
75
|
-
ctx = MyCtx(name=ctx_var("Alex",
|
|
75
|
+
ctx = MyCtx(name=ctx_var("Alex", frozen=True))
|
|
76
76
|
ctx.URL #: 'https://google.com'
|
|
77
77
|
ctx.URL = '...' #: type checking error & exception 'TypeError'
|
|
78
78
|
```
|
|
79
79
|
"""
|
|
80
|
-
|
|
81
|
-
return typing.cast(T, CtxVar(value, const=const))
|
|
80
|
+
return typing.cast(T, CtxVar(default, const=frozen))
|
|
82
81
|
|
|
83
82
|
|
|
84
83
|
@dataclasses.dataclass(frozen=True, eq=False, slots=True)
|
|
@@ -113,9 +112,7 @@ class Storage:
|
|
|
113
112
|
return Some(ctx) if ctx is not None else Nothing()
|
|
114
113
|
|
|
115
114
|
def delete(self, ctx_name: str) -> None:
|
|
116
|
-
assert (
|
|
117
|
-
self._storage.pop(ctx_name, None) is not None
|
|
118
|
-
), f"Context {ctx_name!r} is not defined in storage."
|
|
115
|
+
assert self._storage.pop(ctx_name, None) is not None, f"Context {ctx_name!r} is not defined in storage."
|
|
119
116
|
|
|
120
117
|
|
|
121
118
|
@typing.dataclass_transform(
|
|
@@ -151,7 +148,6 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
151
148
|
**variables: typing.Any | CtxVar[CtxValueT],
|
|
152
149
|
) -> typing.Self:
|
|
153
150
|
"""Create or get from storage a new `GlobalContext` object."""
|
|
154
|
-
|
|
155
151
|
if not issubclass(GlobalContext, cls):
|
|
156
152
|
defaults = {}
|
|
157
153
|
for name in cls.__annotations__:
|
|
@@ -180,9 +176,7 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
180
176
|
ctx_name: str | None = None,
|
|
181
177
|
/,
|
|
182
178
|
**variables: CtxValueT | CtxVariable[CtxValueT],
|
|
183
|
-
):
|
|
184
|
-
"""Initialization of `GlobalContext` with passed variables."""
|
|
185
|
-
|
|
179
|
+
) -> None:
|
|
186
180
|
if not hasattr(self, "__ctx_name__"):
|
|
187
181
|
self.__ctx_name__ = ctx_name
|
|
188
182
|
|
|
@@ -197,8 +191,8 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
197
191
|
|
|
198
192
|
def __eq__(self, __value: "GlobalContext") -> bool:
|
|
199
193
|
"""Returns True if the names of context stores
|
|
200
|
-
that use self and __value instances are equivalent.
|
|
201
|
-
|
|
194
|
+
that use self and __value instances are equivalent.
|
|
195
|
+
"""
|
|
202
196
|
return isinstance(__value, GlobalContext) and self.__ctx_name__ == __value.__ctx_name__
|
|
203
197
|
|
|
204
198
|
def __setitem__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]):
|
|
@@ -210,7 +204,7 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
210
204
|
dict.__setitem__(self, __name, GlobalCtxVar.collect(__name, __value))
|
|
211
205
|
|
|
212
206
|
def __getitem__(self, __name: str) -> CtxValueT:
|
|
213
|
-
return self.get(__name).unwrap().value
|
|
207
|
+
return self.get(__name).unwrap().value # type: ignore
|
|
214
208
|
|
|
215
209
|
def __delitem__(self, __name: str):
|
|
216
210
|
var = self.get(__name).unwrap()
|
|
@@ -221,7 +215,6 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
221
215
|
@root_protection
|
|
222
216
|
def __setattr__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]):
|
|
223
217
|
"""Setting a context variable."""
|
|
224
|
-
|
|
225
218
|
if is_dunder(__name):
|
|
226
219
|
return object.__setattr__(self, __name, __value)
|
|
227
220
|
self.__setitem__(__name, __value)
|
|
@@ -229,7 +222,6 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
229
222
|
@root_protection
|
|
230
223
|
def __getattr__(self, __name: str) -> CtxValueT:
|
|
231
224
|
"""Getting a context variable."""
|
|
232
|
-
|
|
233
225
|
if is_dunder(__name):
|
|
234
226
|
return object.__getattribute__(self, __name)
|
|
235
227
|
return self.__getitem__(__name)
|
|
@@ -237,7 +229,6 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
237
229
|
@root_protection
|
|
238
230
|
def __delattr__(self, __name: str) -> None:
|
|
239
231
|
"""Removing a context variable."""
|
|
240
|
-
|
|
241
232
|
if is_dunder(__name):
|
|
242
233
|
return object.__delattr__(self, __name)
|
|
243
234
|
self.__delitem__(__name)
|
|
@@ -245,27 +236,22 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
245
236
|
@property
|
|
246
237
|
def ctx_name(self) -> str:
|
|
247
238
|
"""Context name."""
|
|
248
|
-
|
|
249
239
|
return self.__ctx_name__ or "<Unnamed ctx at %#x>" % id(self)
|
|
250
240
|
|
|
251
241
|
@classmethod
|
|
252
242
|
def is_root_attribute(cls, name: str) -> bool:
|
|
253
243
|
"""Returns True if exists root attribute
|
|
254
|
-
otherwise False.
|
|
255
|
-
|
|
244
|
+
otherwise False.
|
|
245
|
+
"""
|
|
256
246
|
return name in cls.__root_attributes__
|
|
257
247
|
|
|
258
|
-
def set_context_variables(
|
|
259
|
-
self, variables: typing.Mapping[str, CtxValueT | CtxVariable[CtxValueT]]
|
|
260
|
-
) -> None:
|
|
248
|
+
def set_context_variables(self, variables: typing.Mapping[str, CtxValueT | CtxVariable[CtxValueT]]) -> None:
|
|
261
249
|
"""Set context variables from mapping."""
|
|
262
|
-
|
|
263
250
|
for name, var in variables.items():
|
|
264
251
|
self[name] = var
|
|
265
252
|
|
|
266
253
|
def get_root_attribute(self, name: str) -> Option[RootAttr]:
|
|
267
254
|
"""Get root attribute by name."""
|
|
268
|
-
|
|
269
255
|
if self.is_root_attribute(name):
|
|
270
256
|
for rattr in self.__root_attributes__:
|
|
271
257
|
if rattr.name == name:
|
|
@@ -274,33 +260,27 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
274
260
|
|
|
275
261
|
def items(self) -> list[tuple[str, GlobalCtxVar[CtxValueT]]]:
|
|
276
262
|
"""Return context variables as set-like items."""
|
|
277
|
-
|
|
278
263
|
return list(dict.items(self))
|
|
279
264
|
|
|
280
265
|
def keys(self) -> list[str]:
|
|
281
266
|
"""Returns context variable names as keys."""
|
|
282
|
-
|
|
283
267
|
return list(dict.keys(self))
|
|
284
268
|
|
|
285
269
|
def values(self) -> list[GlobalCtxVar[CtxValueT]]:
|
|
286
270
|
"""Returns context variables as values."""
|
|
287
|
-
|
|
288
271
|
return list(dict.values(self))
|
|
289
272
|
|
|
290
273
|
def update(self, other: typing.Self) -> None:
|
|
291
274
|
"""Update context."""
|
|
292
|
-
|
|
293
275
|
dict.update(dict(other.items()))
|
|
294
276
|
|
|
295
277
|
def copy(self) -> typing.Self:
|
|
296
278
|
"""Copy context. Returns copied context without ctx_name."""
|
|
297
|
-
|
|
298
279
|
return self.__class__(**self.dict())
|
|
299
280
|
|
|
300
281
|
def dict(self) -> dict[str, GlobalCtxVar[CtxValueT]]:
|
|
301
282
|
"""Returns context as dict."""
|
|
302
|
-
|
|
303
|
-
return {name: deepcopy(var) for name, var in self.items()}
|
|
283
|
+
return {name: deepcopy(var) for name, var in self.items()} # type: ignore
|
|
304
284
|
|
|
305
285
|
@typing.overload
|
|
306
286
|
def pop(self, var_name: str) -> Option[GlobalCtxVar[CtxValueT]]: ...
|
|
@@ -314,7 +294,6 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
314
294
|
|
|
315
295
|
def pop(self, var_name: str, var_value_type=object): # type: ignore
|
|
316
296
|
"""Pop context variable by name."""
|
|
317
|
-
|
|
318
297
|
val = self.get(var_name, var_value_type) # type: ignore
|
|
319
298
|
if val:
|
|
320
299
|
del self[var_name]
|
|
@@ -333,7 +312,6 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
333
312
|
|
|
334
313
|
def get(self, var_name, var_value_type=object): # type: ignore
|
|
335
314
|
"""Get context variable by name."""
|
|
336
|
-
|
|
337
315
|
var_value_type = typing.Any if var_value_type is object else var_value_type
|
|
338
316
|
generic_types = typing.get_args(get_orig_class(self))
|
|
339
317
|
if generic_types and var_value_type is object:
|
|
@@ -341,15 +319,15 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
341
319
|
var = dict.get(self, var_name)
|
|
342
320
|
if var is None:
|
|
343
321
|
return Nothing()
|
|
344
|
-
assert type_check(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
)
|
|
322
|
+
assert type_check(var.value, var_value_type), (
|
|
323
|
+
"Context variable value type of {!r} does not correspond to the expected type {!r}.".format(
|
|
324
|
+
type(var.value).__name__,
|
|
325
|
+
(
|
|
326
|
+
getattr(var_value_type, "__name__")
|
|
327
|
+
if isinstance(var_value_type, type)
|
|
328
|
+
else repr(var_value_type)
|
|
329
|
+
),
|
|
330
|
+
)
|
|
353
331
|
)
|
|
354
332
|
return Some(var)
|
|
355
333
|
|
|
@@ -365,23 +343,21 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
365
343
|
|
|
366
344
|
def get_value(self, var_name, var_value_type=object): # type: ignore
|
|
367
345
|
"""Get context variable value by name."""
|
|
368
|
-
|
|
369
346
|
return self.get(var_name, var_value_type).map(lambda var: var.value)
|
|
370
347
|
|
|
371
348
|
def rename(self, old_var_name: str, new_var_name: str) -> Result[_, str]:
|
|
372
349
|
"""Rename context variable."""
|
|
373
|
-
|
|
374
350
|
var = self.get(old_var_name).unwrap()
|
|
375
351
|
if var.const:
|
|
376
|
-
return Error(f"Unable to rename variable {old_var_name!r},
|
|
352
|
+
return Error(f"Unable to rename variable {old_var_name!r}, because it's a constant.")
|
|
377
353
|
del self[old_var_name]
|
|
378
354
|
self[new_var_name] = var.value
|
|
379
355
|
return Ok(_())
|
|
380
356
|
|
|
381
357
|
def clear(self, *, include_consts: bool = False) -> None:
|
|
382
358
|
"""Clear context. If `include_consts = True`,
|
|
383
|
-
then the context is completely cleared.
|
|
384
|
-
|
|
359
|
+
then the context is completely cleared.
|
|
360
|
+
"""
|
|
385
361
|
if not self:
|
|
386
362
|
return
|
|
387
363
|
if include_consts:
|
|
@@ -397,7 +373,6 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
|
|
|
397
373
|
|
|
398
374
|
def delete_ctx(self) -> Result[_, str]:
|
|
399
375
|
"""Delete context by `ctx_name`."""
|
|
400
|
-
|
|
401
376
|
if not self.__ctx_name__:
|
|
402
377
|
return Error("Cannot delete unnamed context.")
|
|
403
378
|
ctx = self.__storage__.get(self.ctx_name).unwrap()
|