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.

Files changed (169) hide show
  1. telegrinder/__init__.py +30 -31
  2. telegrinder/api/__init__.py +2 -1
  3. telegrinder/api/api.py +28 -20
  4. telegrinder/api/error.py +8 -4
  5. telegrinder/api/response.py +2 -2
  6. telegrinder/api/token.py +2 -2
  7. telegrinder/bot/__init__.py +6 -0
  8. telegrinder/bot/bot.py +38 -31
  9. telegrinder/bot/cute_types/__init__.py +2 -0
  10. telegrinder/bot/cute_types/base.py +54 -128
  11. telegrinder/bot/cute_types/callback_query.py +76 -61
  12. telegrinder/bot/cute_types/chat_join_request.py +4 -3
  13. telegrinder/bot/cute_types/chat_member_updated.py +28 -31
  14. telegrinder/bot/cute_types/inline_query.py +5 -4
  15. telegrinder/bot/cute_types/message.py +555 -602
  16. telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
  17. telegrinder/bot/cute_types/update.py +20 -12
  18. telegrinder/bot/cute_types/utils.py +3 -36
  19. telegrinder/bot/dispatch/__init__.py +4 -0
  20. telegrinder/bot/dispatch/abc.py +8 -9
  21. telegrinder/bot/dispatch/context.py +5 -7
  22. telegrinder/bot/dispatch/dispatch.py +85 -33
  23. telegrinder/bot/dispatch/handler/abc.py +5 -6
  24. telegrinder/bot/dispatch/handler/audio_reply.py +2 -2
  25. telegrinder/bot/dispatch/handler/base.py +3 -3
  26. telegrinder/bot/dispatch/handler/document_reply.py +2 -2
  27. telegrinder/bot/dispatch/handler/func.py +36 -42
  28. telegrinder/bot/dispatch/handler/media_group_reply.py +5 -4
  29. telegrinder/bot/dispatch/handler/message_reply.py +2 -2
  30. telegrinder/bot/dispatch/handler/photo_reply.py +2 -2
  31. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -2
  32. telegrinder/bot/dispatch/handler/video_reply.py +2 -2
  33. telegrinder/bot/dispatch/middleware/abc.py +83 -8
  34. telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
  35. telegrinder/bot/dispatch/process.py +44 -50
  36. telegrinder/bot/dispatch/return_manager/__init__.py +2 -0
  37. telegrinder/bot/dispatch/return_manager/abc.py +6 -10
  38. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
  39. telegrinder/bot/dispatch/view/__init__.py +2 -0
  40. telegrinder/bot/dispatch/view/abc.py +10 -6
  41. telegrinder/bot/dispatch/view/base.py +81 -50
  42. telegrinder/bot/dispatch/view/box.py +20 -9
  43. telegrinder/bot/dispatch/view/callback_query.py +3 -4
  44. telegrinder/bot/dispatch/view/chat_join_request.py +2 -7
  45. telegrinder/bot/dispatch/view/chat_member.py +3 -5
  46. telegrinder/bot/dispatch/view/inline_query.py +3 -4
  47. telegrinder/bot/dispatch/view/message.py +3 -4
  48. telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
  49. telegrinder/bot/dispatch/view/raw.py +42 -40
  50. telegrinder/bot/dispatch/waiter_machine/actions.py +5 -4
  51. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +0 -0
  52. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +0 -0
  53. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +9 -7
  54. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  55. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +3 -2
  56. telegrinder/bot/dispatch/waiter_machine/machine.py +113 -34
  57. telegrinder/bot/dispatch/waiter_machine/middleware.py +15 -10
  58. telegrinder/bot/dispatch/waiter_machine/short_state.py +7 -18
  59. telegrinder/bot/polling/polling.py +62 -54
  60. telegrinder/bot/rules/__init__.py +24 -1
  61. telegrinder/bot/rules/abc.py +17 -10
  62. telegrinder/bot/rules/callback_data.py +20 -61
  63. telegrinder/bot/rules/chat_join.py +6 -4
  64. telegrinder/bot/rules/command.py +4 -4
  65. telegrinder/bot/rules/enum_text.py +1 -4
  66. telegrinder/bot/rules/func.py +5 -3
  67. telegrinder/bot/rules/fuzzy.py +1 -1
  68. telegrinder/bot/rules/id.py +24 -0
  69. telegrinder/bot/rules/inline.py +6 -4
  70. telegrinder/bot/rules/integer.py +2 -1
  71. telegrinder/bot/rules/logic.py +18 -0
  72. telegrinder/bot/rules/markup.py +5 -6
  73. telegrinder/bot/rules/message.py +2 -4
  74. telegrinder/bot/rules/message_entities.py +1 -3
  75. telegrinder/bot/rules/node.py +15 -9
  76. telegrinder/bot/rules/payload.py +81 -0
  77. telegrinder/bot/rules/payment_invoice.py +29 -0
  78. telegrinder/bot/rules/regex.py +5 -6
  79. telegrinder/bot/rules/state.py +1 -3
  80. telegrinder/bot/rules/text.py +10 -5
  81. telegrinder/bot/rules/update.py +0 -0
  82. telegrinder/bot/scenario/abc.py +2 -4
  83. telegrinder/bot/scenario/checkbox.py +12 -14
  84. telegrinder/bot/scenario/choice.py +6 -9
  85. telegrinder/client/__init__.py +9 -1
  86. telegrinder/client/abc.py +35 -10
  87. telegrinder/client/aiohttp.py +28 -24
  88. telegrinder/client/form_data.py +31 -0
  89. telegrinder/client/sonic.py +212 -0
  90. telegrinder/model.py +38 -145
  91. telegrinder/modules.py +3 -1
  92. telegrinder/msgspec_utils.py +136 -68
  93. telegrinder/node/__init__.py +74 -13
  94. telegrinder/node/attachment.py +92 -16
  95. telegrinder/node/base.py +196 -68
  96. telegrinder/node/callback_query.py +17 -16
  97. telegrinder/node/command.py +3 -2
  98. telegrinder/node/composer.py +40 -75
  99. telegrinder/node/container.py +13 -7
  100. telegrinder/node/either.py +82 -0
  101. telegrinder/node/event.py +20 -31
  102. telegrinder/node/file.py +51 -0
  103. telegrinder/node/me.py +4 -5
  104. telegrinder/node/payload.py +78 -0
  105. telegrinder/node/polymorphic.py +27 -8
  106. telegrinder/node/rule.py +2 -6
  107. telegrinder/node/scope.py +4 -6
  108. telegrinder/node/source.py +37 -21
  109. telegrinder/node/text.py +20 -8
  110. telegrinder/node/tools/generator.py +7 -11
  111. telegrinder/py.typed +0 -0
  112. telegrinder/rules.py +0 -61
  113. telegrinder/tools/__init__.py +97 -38
  114. telegrinder/tools/adapter/__init__.py +19 -0
  115. telegrinder/tools/adapter/abc.py +49 -0
  116. telegrinder/tools/adapter/dataclass.py +56 -0
  117. telegrinder/{bot/rules → tools}/adapter/event.py +8 -10
  118. telegrinder/{bot/rules → tools}/adapter/node.py +8 -10
  119. telegrinder/{bot/rules → tools}/adapter/raw_event.py +2 -2
  120. telegrinder/{bot/rules → tools}/adapter/raw_update.py +2 -2
  121. telegrinder/tools/buttons.py +52 -26
  122. telegrinder/tools/callback_data_serilization/__init__.py +5 -0
  123. telegrinder/tools/callback_data_serilization/abc.py +51 -0
  124. telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
  125. telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
  126. telegrinder/tools/error_handler/abc.py +4 -7
  127. telegrinder/tools/error_handler/error.py +0 -0
  128. telegrinder/tools/error_handler/error_handler.py +34 -48
  129. telegrinder/tools/formatting/__init__.py +57 -37
  130. telegrinder/tools/formatting/deep_links.py +541 -0
  131. telegrinder/tools/formatting/{html.py → html_formatter.py} +51 -79
  132. telegrinder/tools/formatting/spec_html_formats.py +14 -60
  133. telegrinder/tools/functional.py +1 -5
  134. telegrinder/tools/global_context/global_context.py +26 -51
  135. telegrinder/tools/global_context/telegrinder_ctx.py +3 -3
  136. telegrinder/tools/i18n/abc.py +0 -0
  137. telegrinder/tools/i18n/middleware/abc.py +3 -6
  138. telegrinder/tools/input_file_directory.py +30 -0
  139. telegrinder/tools/keyboard.py +9 -9
  140. telegrinder/tools/lifespan.py +105 -0
  141. telegrinder/tools/limited_dict.py +5 -10
  142. telegrinder/tools/loop_wrapper/abc.py +7 -2
  143. telegrinder/tools/loop_wrapper/loop_wrapper.py +40 -95
  144. telegrinder/tools/magic.py +184 -34
  145. telegrinder/tools/state_storage/__init__.py +0 -0
  146. telegrinder/tools/state_storage/abc.py +5 -9
  147. telegrinder/tools/state_storage/memory.py +1 -1
  148. telegrinder/tools/strings.py +13 -0
  149. telegrinder/types/__init__.py +8 -0
  150. telegrinder/types/enums.py +31 -21
  151. telegrinder/types/input_file.py +51 -0
  152. telegrinder/types/methods.py +531 -109
  153. telegrinder/types/objects.py +934 -826
  154. telegrinder/verification_utils.py +0 -2
  155. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +2 -2
  156. telegrinder-0.4.0.dist-info/METADATA +144 -0
  157. telegrinder-0.4.0.dist-info/RECORD +182 -0
  158. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
  159. telegrinder/bot/rules/adapter/__init__.py +0 -17
  160. telegrinder/bot/rules/adapter/abc.py +0 -31
  161. telegrinder/node/message.py +0 -14
  162. telegrinder/node/update.py +0 -15
  163. telegrinder/tools/formatting/links.py +0 -38
  164. telegrinder/tools/kb_set/__init__.py +0 -4
  165. telegrinder/tools/kb_set/base.py +0 -15
  166. telegrinder/tools/kb_set/yaml.py +0 -63
  167. telegrinder-0.3.4.post1.dist-info/METADATA +0 -110
  168. telegrinder-0.3.4.post1.dist-info/RECORD +0 -165
  169. /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 .links import (
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
- TAG_FORMAT = "<{tag}{data}>{content}</{tag}>"
20
- QUOT_MARK = '"'
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) -> "HTMLFormatter": ...
23
+ def format_field(self, value: typing.Any, fmt: str) -> HTMLFormatter: ...
25
24
 
26
- def format(self, __string: str, *args: object, **kwargs: object) -> "HTMLFormatter": ...
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 is_html_format(self, value: typing.Any, fmt: str) -> str:
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[..., "TagFormat"]:
57
+ def get_spec_formatter(self, value: SpecialFormat) -> typing.Callable[..., TagFormat]:
58
58
  return globals()[value.__formatter_name__]
59
59
 
60
- def check_formats(self, value: typing.Any, fmts: list[str]) -> "TagFormat":
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) -> "HTMLFormatter":
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.__dict__).formatting()
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
- def format_raw_value(self, value: typing.Any, fmt: str) -> "HTMLFormatter":
101
- fmts = list(map(lambda fmt: self.is_html_format(value, fmt), fmt.split("+")))
102
- tag_format = self.check_formats(value, fmts)
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.__dict__)
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) -> "HTMLFormatter":
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) -> "HTMLFormatter":
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) -> "HTMLFormatter":
131
- """Return value+self."""
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
- """Return self as a standart string."""
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) -> "HTMLFormatter":
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
- >>> HTMLFormatter(bold("Hello, World"))
190
- '<b>Hello, World</b>'
191
- HTMLFormatter("Hi, {name:italic}").format(name="Max")
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(string, tag="blockquote")
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(get_mention_link(user_id), string)
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", **{"emoji-id": emoji_id})
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
- "ChannelBoostLink",
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, slots=True)
28
- class BaseSpecFormat:
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 ChannelBoostLink(BaseSpecFormat):
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(BaseSpecFormat):
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(BaseSpecFormat):
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(BaseSpecFormat):
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 StartBotLink(BaseSpecFormat):
85
- __formatter_name__ = "start_bot_link"
60
+ class BlockQuote(Base):
61
+ __formatter_name__ = "block_quote"
86
62
 
87
- bot_id: str | int
88
- data: str
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
- "BaseSpecFormat",
111
- "ChannelBoostLink",
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
  )
@@ -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: T | None) -> Option[T]:
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(value: T, *, const: bool = False) -> T:
68
+ def ctx_var(*, default: T, frozen: bool = False) -> T:
69
69
  """Example:
70
70
  ```
71
71
  class MyCtx(GlobalContext):
72
- name: typing.Final[str]
73
- URL: typing.Final = ctx_var("https://google.com", const=True)
72
+ name: str
73
+ URL: str = ctx_var("https://google.com", frozen=True)
74
74
 
75
- ctx = MyCtx(name=ctx_var("Alex", const=True))
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
- var.value, var_value_type
346
- ), "Context variable value type of {!r} does not correspond to the expected type {!r}.".format(
347
- type(var.value).__name__,
348
- (
349
- getattr(var_value_type, "__name__")
350
- if isinstance(var_value_type, type)
351
- else repr(var_value_type)
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}, " "because it's a constant.")
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()