telegrinder 0.4.2__py3-none-any.whl → 0.5.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 (233) hide show
  1. telegrinder/__init__.py +37 -55
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +6 -4
  4. telegrinder/api/api.py +100 -26
  5. telegrinder/api/error.py +42 -8
  6. telegrinder/api/response.py +4 -1
  7. telegrinder/api/token.py +2 -2
  8. telegrinder/bot/__init__.py +9 -25
  9. telegrinder/bot/bot.py +31 -25
  10. telegrinder/bot/cute_types/__init__.py +0 -0
  11. telegrinder/bot/cute_types/base.py +103 -61
  12. telegrinder/bot/cute_types/callback_query.py +447 -400
  13. telegrinder/bot/cute_types/chat_join_request.py +59 -62
  14. telegrinder/bot/cute_types/chat_member_updated.py +154 -157
  15. telegrinder/bot/cute_types/inline_query.py +41 -44
  16. telegrinder/bot/cute_types/message.py +2621 -2590
  17. telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
  18. telegrinder/bot/cute_types/update.py +1 -8
  19. telegrinder/bot/cute_types/utils.py +1 -1
  20. telegrinder/bot/dispatch/__init__.py +10 -15
  21. telegrinder/bot/dispatch/abc.py +12 -11
  22. telegrinder/bot/dispatch/action.py +104 -0
  23. telegrinder/bot/dispatch/context.py +32 -26
  24. telegrinder/bot/dispatch/dispatch.py +61 -134
  25. telegrinder/bot/dispatch/handler/__init__.py +2 -0
  26. telegrinder/bot/dispatch/handler/abc.py +10 -8
  27. telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
  28. telegrinder/bot/dispatch/handler/base.py +10 -33
  29. telegrinder/bot/dispatch/handler/document_reply.py +2 -3
  30. telegrinder/bot/dispatch/handler/func.py +55 -87
  31. telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
  32. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  33. telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
  34. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
  35. telegrinder/bot/dispatch/handler/video_reply.py +2 -3
  36. telegrinder/bot/dispatch/middleware/__init__.py +0 -0
  37. telegrinder/bot/dispatch/middleware/abc.py +79 -55
  38. telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
  39. telegrinder/bot/dispatch/process.py +84 -105
  40. telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
  41. telegrinder/bot/dispatch/return_manager/abc.py +102 -65
  42. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
  43. telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
  44. telegrinder/bot/dispatch/return_manager/message.py +8 -10
  45. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
  46. telegrinder/bot/dispatch/view/__init__.py +4 -4
  47. telegrinder/bot/dispatch/view/abc.py +6 -16
  48. telegrinder/bot/dispatch/view/base.py +54 -178
  49. telegrinder/bot/dispatch/view/box.py +19 -18
  50. telegrinder/bot/dispatch/view/callback_query.py +4 -8
  51. telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
  52. telegrinder/bot/dispatch/view/chat_member.py +5 -25
  53. telegrinder/bot/dispatch/view/error.py +9 -0
  54. telegrinder/bot/dispatch/view/inline_query.py +4 -8
  55. telegrinder/bot/dispatch/view/message.py +5 -25
  56. telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
  57. telegrinder/bot/dispatch/view/raw.py +3 -109
  58. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
  59. telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
  60. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
  61. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
  62. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
  63. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  64. telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
  65. telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
  66. telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
  67. telegrinder/bot/polling/__init__.py +0 -0
  68. telegrinder/bot/polling/abc.py +0 -0
  69. telegrinder/bot/polling/polling.py +209 -88
  70. telegrinder/bot/rules/__init__.py +3 -16
  71. telegrinder/bot/rules/abc.py +42 -122
  72. telegrinder/bot/rules/callback_data.py +29 -49
  73. telegrinder/bot/rules/chat_join.py +5 -23
  74. telegrinder/bot/rules/command.py +8 -4
  75. telegrinder/bot/rules/enum_text.py +3 -4
  76. telegrinder/bot/rules/func.py +7 -14
  77. telegrinder/bot/rules/fuzzy.py +3 -4
  78. telegrinder/bot/rules/inline.py +8 -20
  79. telegrinder/bot/rules/integer.py +2 -3
  80. telegrinder/bot/rules/is_from.py +12 -11
  81. telegrinder/bot/rules/logic.py +11 -5
  82. telegrinder/bot/rules/markup.py +22 -14
  83. telegrinder/bot/rules/mention.py +8 -7
  84. telegrinder/bot/rules/message_entities.py +8 -4
  85. telegrinder/bot/rules/node.py +23 -12
  86. telegrinder/bot/rules/payload.py +5 -4
  87. telegrinder/bot/rules/payment_invoice.py +6 -21
  88. telegrinder/bot/rules/regex.py +2 -4
  89. telegrinder/bot/rules/rule_enum.py +8 -7
  90. telegrinder/bot/rules/start.py +5 -6
  91. telegrinder/bot/rules/state.py +1 -1
  92. telegrinder/bot/rules/text.py +4 -15
  93. telegrinder/bot/rules/update.py +3 -4
  94. telegrinder/bot/scenario/__init__.py +0 -0
  95. telegrinder/bot/scenario/abc.py +6 -5
  96. telegrinder/bot/scenario/checkbox.py +1 -1
  97. telegrinder/bot/scenario/choice.py +30 -39
  98. telegrinder/client/__init__.py +3 -5
  99. telegrinder/client/abc.py +11 -6
  100. telegrinder/client/aiohttp.py +141 -27
  101. telegrinder/client/form_data.py +1 -1
  102. telegrinder/model.py +61 -89
  103. telegrinder/modules.py +325 -102
  104. telegrinder/msgspec_utils/__init__.py +40 -0
  105. telegrinder/msgspec_utils/abc.py +18 -0
  106. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  107. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  108. telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
  109. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  110. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  111. telegrinder/msgspec_utils/decoder.py +389 -0
  112. telegrinder/msgspec_utils/encoder.py +206 -0
  113. telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
  114. telegrinder/msgspec_utils/tools.py +75 -0
  115. telegrinder/node/__init__.py +24 -7
  116. telegrinder/node/attachment.py +1 -0
  117. telegrinder/node/base.py +154 -72
  118. telegrinder/node/callback_query.py +5 -5
  119. telegrinder/node/collection.py +39 -0
  120. telegrinder/node/command.py +1 -2
  121. telegrinder/node/composer.py +121 -72
  122. telegrinder/node/container.py +11 -8
  123. telegrinder/node/context.py +48 -0
  124. telegrinder/node/either.py +27 -40
  125. telegrinder/node/error.py +41 -0
  126. telegrinder/node/event.py +37 -11
  127. telegrinder/node/exceptions.py +7 -0
  128. telegrinder/node/file.py +0 -0
  129. telegrinder/node/i18n.py +108 -0
  130. telegrinder/node/me.py +3 -2
  131. telegrinder/node/payload.py +1 -1
  132. telegrinder/node/polymorphic.py +63 -28
  133. telegrinder/node/reply_message.py +12 -0
  134. telegrinder/node/rule.py +6 -13
  135. telegrinder/node/scope.py +14 -5
  136. telegrinder/node/session.py +53 -0
  137. telegrinder/node/source.py +41 -9
  138. telegrinder/node/text.py +1 -2
  139. telegrinder/node/tools/__init__.py +0 -0
  140. telegrinder/node/tools/generator.py +3 -5
  141. telegrinder/node/utility.py +16 -0
  142. telegrinder/py.typed +0 -0
  143. telegrinder/rules.py +0 -0
  144. telegrinder/tools/__init__.py +48 -88
  145. telegrinder/tools/aio.py +103 -0
  146. telegrinder/tools/callback_data_serialization/__init__.py +5 -0
  147. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
  148. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
  149. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
  150. telegrinder/tools/final.py +21 -0
  151. telegrinder/tools/formatting/__init__.py +2 -18
  152. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  153. telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
  154. telegrinder/tools/formatting/deep_links/parsing.py +90 -0
  155. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  156. telegrinder/tools/formatting/html_formatter.py +18 -45
  157. telegrinder/tools/fullname.py +83 -0
  158. telegrinder/tools/global_context/__init__.py +4 -3
  159. telegrinder/tools/global_context/abc.py +17 -14
  160. telegrinder/tools/global_context/builtin_context.py +39 -0
  161. telegrinder/tools/global_context/global_context.py +138 -39
  162. telegrinder/tools/input_file_directory.py +0 -0
  163. telegrinder/tools/keyboard/__init__.py +39 -0
  164. telegrinder/tools/keyboard/abc.py +159 -0
  165. telegrinder/tools/keyboard/base.py +77 -0
  166. telegrinder/tools/keyboard/buttons/__init__.py +14 -0
  167. telegrinder/tools/keyboard/buttons/base.py +18 -0
  168. telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
  169. telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
  170. telegrinder/tools/keyboard/buttons/tools.py +18 -0
  171. telegrinder/tools/keyboard/data.py +20 -0
  172. telegrinder/tools/keyboard/keyboard.py +131 -0
  173. telegrinder/tools/keyboard/static_keyboard.py +83 -0
  174. telegrinder/tools/lifespan.py +87 -51
  175. telegrinder/tools/limited_dict.py +4 -1
  176. telegrinder/tools/loop_wrapper.py +332 -0
  177. telegrinder/tools/magic/__init__.py +32 -0
  178. telegrinder/tools/magic/annotations.py +165 -0
  179. telegrinder/tools/magic/dictionary.py +20 -0
  180. telegrinder/tools/magic/function.py +246 -0
  181. telegrinder/tools/magic/shortcut.py +111 -0
  182. telegrinder/tools/parse_mode.py +9 -3
  183. telegrinder/tools/singleton/__init__.py +4 -0
  184. telegrinder/tools/singleton/abc.py +14 -0
  185. telegrinder/tools/singleton/singleton.py +18 -0
  186. telegrinder/tools/state_storage/__init__.py +0 -0
  187. telegrinder/tools/state_storage/abc.py +6 -1
  188. telegrinder/tools/state_storage/memory.py +1 -1
  189. telegrinder/tools/strings.py +0 -0
  190. telegrinder/types/__init__.py +307 -268
  191. telegrinder/types/enums.py +64 -37
  192. telegrinder/types/input_file.py +3 -3
  193. telegrinder/types/methods.py +5699 -5055
  194. telegrinder/types/methods_utils.py +62 -0
  195. telegrinder/types/objects.py +7846 -7058
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.0.dist-info/METADATA +162 -0
  198. telegrinder-0.5.0.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.dist-info}/licenses/LICENSE +2 -2
  200. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
  201. telegrinder/bot/rules/id.py +0 -24
  202. telegrinder/bot/rules/message.py +0 -15
  203. telegrinder/client/sonic.py +0 -212
  204. telegrinder/msgspec_utils.py +0 -478
  205. telegrinder/tools/adapter/__init__.py +0 -19
  206. telegrinder/tools/adapter/abc.py +0 -49
  207. telegrinder/tools/adapter/dataclass.py +0 -56
  208. telegrinder/tools/adapter/errors.py +0 -5
  209. telegrinder/tools/adapter/event.py +0 -61
  210. telegrinder/tools/adapter/node.py +0 -46
  211. telegrinder/tools/adapter/raw_event.py +0 -27
  212. telegrinder/tools/adapter/raw_update.py +0 -30
  213. telegrinder/tools/callback_data_serilization/__init__.py +0 -5
  214. telegrinder/tools/error_handler/__init__.py +0 -10
  215. telegrinder/tools/error_handler/abc.py +0 -30
  216. telegrinder/tools/error_handler/error.py +0 -9
  217. telegrinder/tools/error_handler/error_handler.py +0 -179
  218. telegrinder/tools/formatting/spec_html_formats.py +0 -75
  219. telegrinder/tools/functional.py +0 -8
  220. telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
  221. telegrinder/tools/i18n/__init__.py +0 -12
  222. telegrinder/tools/i18n/abc.py +0 -32
  223. telegrinder/tools/i18n/middleware/__init__.py +0 -3
  224. telegrinder/tools/i18n/middleware/abc.py +0 -22
  225. telegrinder/tools/i18n/simple.py +0 -43
  226. telegrinder/tools/keyboard.py +0 -132
  227. telegrinder/tools/loop_wrapper/__init__.py +0 -4
  228. telegrinder/tools/loop_wrapper/abc.py +0 -20
  229. telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
  230. telegrinder/tools/magic.py +0 -344
  231. telegrinder-0.4.2.dist-info/METADATA +0 -151
  232. telegrinder-0.4.2.dist-info/RECORD +0 -182
  233. {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,90 @@
1
+ import types
2
+ import typing
3
+ from collections import OrderedDict
4
+ from datetime import timedelta
5
+ from urllib.parse import urlencode
6
+
7
+ from telegrinder.tools.magic.function import get_func_annotations
8
+
9
+ type DeepLinkFunction[**P] = typing.Callable[P, str]
10
+ type NoValue = types.EllipsisType
11
+
12
+ Parameter = typing.Annotated
13
+
14
+ NO_VALUE: typing.Final[NoValue] = typing.cast("NoValue", ...)
15
+
16
+
17
+ def get_query_params(
18
+ func: DeepLinkFunction[...],
19
+ kwargs: dict[str, typing.Any],
20
+ order_params: set[str] | None = None,
21
+ ) -> dict[str, typing.Any]:
22
+ annotations = get_func_annotations(func)
23
+ params = OrderedDict()
24
+ param_names = (
25
+ [*order_params, *(p for p in annotations if p not in order_params)] if order_params else annotations
26
+ )
27
+
28
+ for param_name in param_names:
29
+ annotation = annotations[param_name]
30
+ if param_name in kwargs:
31
+ value = kwargs[param_name]
32
+ if typing.get_origin(annotation) is Parameter:
33
+ param_name, validator = get_parameter_metadata(annotation)
34
+ value = validator(value) if validator is not None else value
35
+
36
+ params[param_name] = value
37
+
38
+ return params
39
+
40
+
41
+ def parse_query_params(
42
+ params: dict[str, typing.Any],
43
+ no_value_params: set[str] | None = None,
44
+ /,
45
+ ) -> tuple[set[str], dict[str, typing.Any]]:
46
+ no_value_params = no_value_params or set()
47
+ params_: dict[str, typing.Any] = {}
48
+
49
+ for key, value in params.items():
50
+ if value in (False, None):
51
+ continue
52
+
53
+ if value in (True, NO_VALUE):
54
+ no_value_params.add(key)
55
+ continue
56
+ if isinstance(value, timedelta):
57
+ value = int(value.total_seconds())
58
+
59
+ params_[key] = value
60
+
61
+ return (no_value_params, params_)
62
+
63
+
64
+ def get_parameter_metadata(
65
+ parameter: typing.Any,
66
+ ) -> tuple[str, typing.Callable[[typing.Any], typing.Any] | None]:
67
+ meta: tuple[typing.Any, ...] = getattr(parameter, "__metadata__")
68
+ return meta if len(meta) == 2 else (meta[0], None)
69
+
70
+
71
+ def parse_deep_link(
72
+ *,
73
+ link: str,
74
+ params: dict[str, typing.Any],
75
+ no_value_params: set[str] | None = None,
76
+ ) -> str:
77
+ no_value_params, params = parse_query_params(params, no_value_params)
78
+ query = urlencode(params, encoding="UTF-8") + ("&" if no_value_params else "") + "&".join(no_value_params)
79
+ return f"{link}?{query}"
80
+
81
+
82
+ __all__ = (
83
+ "NO_VALUE",
84
+ "NoValue",
85
+ "Parameter",
86
+ "get_parameter_metadata",
87
+ "get_query_params",
88
+ "parse_deep_link",
89
+ "parse_query_params",
90
+ )
@@ -0,0 +1,8 @@
1
+ import typing
2
+
3
+
4
+ def separate_by_plus_char(iterable: typing.Iterable[str] | None = None, /) -> str | None:
5
+ return None if not iterable else "+".join(iterable)
6
+
7
+
8
+ __all__ = ("separate_by_plus_char",)
@@ -1,17 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
- import dataclasses
4
3
  import html
5
4
  import string
6
5
  import typing
7
6
  from contextlib import suppress
8
7
 
8
+ from telegrinder.tools.formatting.deep_links import tg_mention_link
9
9
  from telegrinder.tools.parse_mode import ParseMode
10
10
  from telegrinder.types.enums import ProgrammingLanguage
11
11
 
12
- from .deep_links import tg_mention_link
13
- from .spec_html_formats import SpecialFormat, is_spec_format
14
-
15
12
  type HTMLFormat = str | TagFormat
16
13
 
17
14
  HTML_UNION_SPECIFIERS_SEPARATOR: typing.Final[str] = "+"
@@ -54,18 +51,13 @@ class StringFormatter(string.Formatter):
54
51
 
55
52
  return fmt
56
53
 
57
- def get_spec_formatter(self, value: SpecialFormat) -> typing.Callable[..., TagFormat]:
58
- return globals()[value.__formatter_name__]
59
-
60
54
  def make_tag_format(self, value: typing.Any, fmts: list[HTMLFormat]) -> TagFormat:
61
- if is_spec_format(value):
62
- value = value.string
63
-
64
55
  current_format = globals()[fmts.pop(0)](
65
56
  str(value)
66
57
  if isinstance(value, TagFormat)
67
58
  else escape(FormatString(value) if not isinstance(value, str) else value)
68
59
  )
60
+
69
61
  for fmt in fmts:
70
62
  current_format = globals()[fmt](current_format)
71
63
 
@@ -81,20 +73,7 @@ class StringFormatter(string.Formatter):
81
73
 
82
74
  def format_field(self, value: typing.Any, fmt: str) -> HTMLFormatter:
83
75
  with suppress(ValueError):
84
- return HTMLFormatter(
85
- format(
86
- (
87
- value.formatting()
88
- if isinstance(value, TagFormat)
89
- else (
90
- self.get_spec_formatter(value)(**dataclasses.asdict(value)).formatting()
91
- if is_spec_format(value)
92
- else value
93
- )
94
- ),
95
- fmt,
96
- )
97
- )
76
+ return HTMLFormatter(format(value.formatting() if isinstance(value, TagFormat) else value, fmt))
98
77
 
99
78
  fmts = list(
100
79
  map(
@@ -102,13 +81,7 @@ class StringFormatter(string.Formatter):
102
81
  fmt.split(HTML_UNION_SPECIFIERS_SEPARATOR),
103
82
  ),
104
83
  )
105
- tag_format = self.make_tag_format(value, fmts)
106
-
107
- if is_spec_format(value):
108
- value.string = tag_format
109
- tag_format = self.get_spec_formatter(value)(**dataclasses.asdict(value))
110
-
111
- return tag_format.formatting()
84
+ return self.make_tag_format(value, fmts).formatting()
112
85
 
113
86
  def format(self, __string: str, *args: object, **kwargs: object) -> HTMLFormatter:
114
87
  return HTMLFormatter(super().format(__string, *args, **kwargs))
@@ -192,15 +165,15 @@ class TagFormat(FormatString):
192
165
 
193
166
  class HTMLFormatter(FormatString):
194
167
  """>>> HTMLFormatter(bold("Hello, World"))
195
- >>> '<b>Hello, World</b>'
168
+ '<b>Hello, World</b>'
196
169
  >>> HTMLFormatter("Hi, {name:italic}").format(name="Max")
197
- >>> 'Hi, <i>Max</i>'
170
+ 'Hi, <i>Max</i>'
198
171
  """
199
172
 
200
- PARSE_MODE = ParseMode.HTML
173
+ PARSE_MODE: typing.Final[str] = ParseMode.HTML
201
174
 
202
175
 
203
- def escape(string: str) -> EscapedString:
176
+ def escape(string: str, /) -> EscapedString:
204
177
  if isinstance(string, EscapedString | HTMLFormatter):
205
178
  return EscapedString(string)
206
179
  return EscapedString(html.escape(string, quote=False))
@@ -226,45 +199,45 @@ def italic(string: str) -> TagFormat:
226
199
  return TagFormat(string, tag="i")
227
200
 
228
201
 
229
- def link(href: str, string: str | None = None) -> TagFormat:
202
+ def link(href: str, /, *, text: str | None = None) -> TagFormat:
230
203
  return TagFormat(
231
- string or href,
204
+ text or href,
232
205
  tag="a",
233
206
  href=QUOT_MARK + href + QUOT_MARK,
234
207
  )
235
208
 
236
209
 
237
- def pre_code(string: str, lang: str | ProgrammingLanguage | None = None) -> TagFormat:
210
+ def pre_code(string: str, /, *, lang: str | ProgrammingLanguage | None = None) -> TagFormat:
238
211
  if lang is None:
239
212
  return TagFormat(string, tag="pre")
213
+
240
214
  lang = lang.value if isinstance(lang, ProgrammingLanguage) else lang
241
215
  return pre_code(TagFormat(string, tag="code", **{"class": f"language-{lang}"}))
242
216
 
243
217
 
244
- def spoiler(string: str) -> TagFormat:
218
+ def spoiler(string: str, /) -> TagFormat:
245
219
  return TagFormat(string, tag="tg-spoiler")
246
220
 
247
221
 
248
- def strike(string: str) -> TagFormat:
222
+ def strike(string: str, /) -> TagFormat:
249
223
  return TagFormat(string, tag="s")
250
224
 
251
225
 
252
- def mention(string: str, user_id: int) -> TagFormat:
253
- return link(tg_mention_link(user_id=user_id), string)
226
+ def mention(string: str, /, *, user_id: int) -> TagFormat:
227
+ return link(tg_mention_link(user_id=user_id), text=string)
254
228
 
255
229
 
256
- def tg_emoji(string: str, emoji_id: int) -> TagFormat:
230
+ def tg_emoji(string: str, /, *, emoji_id: int) -> TagFormat:
257
231
  return TagFormat(string, tag="tg-emoji", emoji_id=emoji_id)
258
232
 
259
233
 
260
- def underline(string: str) -> TagFormat:
234
+ def underline(string: str, /) -> TagFormat:
261
235
  return TagFormat(string, tag="u")
262
236
 
263
237
 
264
238
  __all__ = (
265
239
  "FormatString",
266
240
  "HTMLFormatter",
267
- "SpecialFormat",
268
241
  "block_quote",
269
242
  "bold",
270
243
  "code_inline",
@@ -0,0 +1,83 @@
1
+ import builtins
2
+ import inspect
3
+ import os.path
4
+ import sys
5
+ import types
6
+
7
+ import typing_extensions as typing
8
+
9
+ type RoutineMethodType = (
10
+ types.MethodType | types.MethodDescriptorType | types.MethodWrapperType | types.BuiltinMethodType
11
+ )
12
+ type RoutineDescriptorType = types.MethodDescriptorType | types.GetSetDescriptorType
13
+
14
+ _BUILTINS: typing.Final[frozenset[typing.Any]] = frozenset(
15
+ x for name in dir(builtins) if getattr((x := getattr(builtins, name)), "__module__", None) == "builtins"
16
+ )
17
+ _CACHE_KEY: typing.Final[str] = "__fullname_cache__"
18
+
19
+
20
+ def _is_builtin(obj: typing.Any, /) -> bool:
21
+ return inspect.isbuiltin(obj) or obj in _BUILTINS
22
+
23
+
24
+ def _is_routine_method(obj: typing.Any, /) -> typing.TypeIs[RoutineMethodType]:
25
+ return inspect.isbuiltin(obj) or inspect.ismethod(obj) or inspect.ismethodwrapper(obj)
26
+
27
+
28
+ def _is_routine_descriptor(obj: typing.Any, /) -> typing.TypeIs[RoutineDescriptorType]:
29
+ return inspect.ismethoddescriptor(obj) or inspect.isgetsetdescriptor(obj)
30
+
31
+
32
+ def _module_name(module: types.ModuleType, /) -> str:
33
+ if (mod_name := module.__name__) != "__main__":
34
+ return mod_name
35
+
36
+ mod_package = module.__package__ or ""
37
+ mod_file = module.__file__
38
+ if mod_file is None:
39
+ return mod_package
40
+
41
+ mod_fname = os.path.basename(mod_file).removesuffix(".py")
42
+ return mod_package if mod_package in ("__init__", "__main__") else mod_fname
43
+
44
+
45
+ def fullname(obj: object, /) -> str:
46
+ """The full name (`__module__.__name__`) of the object."""
47
+
48
+ if isinstance(obj, staticmethod | classmethod):
49
+ obj = obj.__func__
50
+
51
+ elif (
52
+ not inspect.isroutine(obj)
53
+ and not inspect.isgetsetdescriptor(obj)
54
+ and not isinstance(obj, type)
55
+ and not inspect.ismodule(obj)
56
+ ):
57
+ obj = type(obj)
58
+
59
+ if (name := fullname.__dict__.setdefault(_CACHE_KEY, dict()).get(obj)) is not None:
60
+ return name
61
+
62
+ if inspect.ismodule(obj):
63
+ fullname.__dict__[_CACHE_KEY][obj] = name = _module_name(obj)
64
+ return name
65
+
66
+ qualname = obj.__qualname__
67
+ orig_obj = obj
68
+
69
+ if _is_routine_method(obj) or _is_routine_descriptor(obj):
70
+ obj_cls = obj.__objclass__ if _is_routine_descriptor(obj) else obj.__self__
71
+ obj = type(obj_cls) if not isinstance(obj_cls, type) else obj_cls
72
+
73
+ module: str = getattr(orig_obj, "__module__", obj.__module__)
74
+ if _is_builtin(obj) and module != builtins.__name__:
75
+ module = builtins.__name__
76
+ else:
77
+ module = _module_name(sys.modules[module])
78
+
79
+ fullname.__dict__[_CACHE_KEY][orig_obj] = name = ".".join((module, qualname))
80
+ return name
81
+
82
+
83
+ __all__ = ("fullname",)
@@ -1,6 +1,6 @@
1
- from .abc import ABCGlobalContext, CtxVar, GlobalCtxVar
2
- from .global_context import GlobalContext, ctx_var
3
- from .telegrinder_ctx import TelegrinderContext
1
+ from telegrinder.tools.global_context.abc import ABCGlobalContext, CtxVar, GlobalCtxVar
2
+ from telegrinder.tools.global_context.builtin_context import TelegrinderContext
3
+ from telegrinder.tools.global_context.global_context import GlobalContext, ctx_var, runtime_init
4
4
 
5
5
  __all__ = (
6
6
  "ABCGlobalContext",
@@ -9,4 +9,5 @@ __all__ = (
9
9
  "GlobalCtxVar",
10
10
  "TelegrinderContext",
11
11
  "ctx_var",
12
+ "runtime_init",
12
13
  )
@@ -7,38 +7,41 @@ import typing_extensions as typing
7
7
 
8
8
  T = typing.TypeVar("T", default=typing.Any)
9
9
 
10
+ NODEFAULT: typing.Final[object] = object()
10
11
 
11
- @dataclasses.dataclass(repr=False, frozen=True)
12
+
13
+ @dataclasses.dataclass(frozen=True)
12
14
  class CtxVar(typing.Generic[T]):
13
15
  value: T
16
+ factory: typing.Any = dataclasses.field(default=NODEFAULT, kw_only=True)
14
17
  const: bool = dataclasses.field(default=False, kw_only=True)
15
18
 
16
- def __repr__(self) -> str:
17
- return "<{}(value={!r})>".format(
18
- ("Const" if self.const else "") + CtxVar.__name__,
19
- self.value,
20
- )
21
-
22
19
 
23
20
  @dataclasses.dataclass(repr=False, frozen=True)
24
- class GlobalCtxVar(typing.Generic[T]):
21
+ class GlobalCtxVar(CtxVar[T], typing.Generic[T]):
25
22
  name: str
26
23
  value: T
24
+ factory: typing.Any = dataclasses.field(default=NODEFAULT, kw_only=True)
27
25
  const: bool = dataclasses.field(default=False, kw_only=True)
28
26
 
29
27
  def __repr__(self) -> str:
30
28
  return "<{}({}={})>".format(
31
29
  self.__class__.__name__,
32
30
  self.name,
33
- repr(CtxVar(self.value, const=self.const)),
31
+ repr(CtxVar(self.value, const=self.const, factory=self.factory)),
34
32
  )
35
33
 
36
34
  @classmethod
37
- def collect(cls, name: str, ctx_value: T | CtxVariable[T]) -> typing.Self:
38
- ctx_value = CtxVar(ctx_value) if not isinstance(ctx_value, CtxVar | GlobalCtxVar) else ctx_value
39
- params = ctx_value.__dict__
40
- params["name"] = name
41
- return cls(**params)
35
+ def from_var(
36
+ cls,
37
+ name: str,
38
+ ctx_value: T | CtxVariable[T],
39
+ const: bool = False,
40
+ ) -> typing.Self:
41
+ var = CtxVar(ctx_value, const=const) if not isinstance(ctx_value, CtxVar | GlobalCtxVar) else ctx_value
42
+ if var.value is NODEFAULT and var.factory is not NODEFAULT:
43
+ var = dataclasses.replace(var, value=var.factory())
44
+ return cls(**dict(var.__dict__) | dict(name=name))
42
45
 
43
46
 
44
47
  class ABCGlobalContext(ABC, typing.Generic[T]):
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import typing
5
+
6
+ from fntypes.option import Nothing, Option
7
+ from vbml.patcher.abc import ABCPatcher
8
+ from vbml.patcher.patcher import Patcher
9
+
10
+ from telegrinder.tools.global_context.global_context import GlobalContext, ctx_var, runtime_init
11
+ from telegrinder.tools.loop_wrapper import LoopWrapper
12
+
13
+ if typing.TYPE_CHECKING:
14
+ from telegrinder.node.composer import Composer
15
+
16
+
17
+ @runtime_init
18
+ class TelegrinderContext(GlobalContext):
19
+ """The type-hinted telegrinder context called `telegrinder`.
20
+
21
+ Example:
22
+ ```
23
+ from telegrinder.tools.global_context import GlobalContext, TelegrinderContext
24
+
25
+ telegrinder_ctx = TelegrinderContext(...) # with type-hints
26
+ assert telegrinder_ctx == GlobalContext("telegrinder") # ok
27
+ ```
28
+
29
+ """
30
+
31
+ __ctx_name__ = "telegrinder"
32
+
33
+ composer: Option[Composer] = ctx_var(default=Nothing(), init=False)
34
+ vbml_pattern_flags: re.RegexFlag | None = ctx_var(default=None, init=False)
35
+ vbml_patcher: ABCPatcher = ctx_var(default_factory=Patcher, init=False)
36
+ loop_wrapper: LoopWrapper = ctx_var(default_factory=LoopWrapper, init=False)
37
+
38
+
39
+ __all__ = ("TelegrinderContext",)