telegrinder 0.4.2__py3-none-any.whl → 0.5.1__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 +98 -67
  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 +68 -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 +1782 -994
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.1.dist-info/METADATA +162 -0
  198. telegrinder-0.5.1.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.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.1.dist-info}/WHEEL +0 -0
@@ -1,28 +1,19 @@
1
- from .adapter import (
2
- ABCAdapter,
3
- DataclassAdapter,
4
- EventAdapter,
5
- NodeAdapter,
6
- RawEventAdapter,
7
- RawUpdateAdapter,
1
+ from telegrinder.tools.aio import (
2
+ cancel_future,
3
+ maybe_awaitable,
4
+ next_generator,
5
+ run_task,
6
+ send_generator_value,
7
+ stop_generator,
8
8
  )
9
- from .buttons import BaseButton
10
- from .callback_data_serilization import (
9
+ from telegrinder.tools.callback_data_serialization import (
11
10
  ABCDataSerializer,
12
11
  JSONSerializer,
13
12
  MsgPackSerializer,
14
13
  )
15
- from .error_handler import ABCErrorHandler, Catcher, CatcherError, ErrorHandler
16
- from .formatting import (
17
- Base,
18
- BlockQuote,
14
+ from telegrinder.tools.formatting import (
19
15
  FormatString,
20
16
  HTMLFormatter,
21
- Link,
22
- Mention,
23
- PreCode,
24
- SpecialFormat,
25
- TgEmoji,
26
17
  block_quote,
27
18
  bold,
28
19
  code_inline,
@@ -61,119 +52,88 @@ from .formatting import (
61
52
  tg_story_link,
62
53
  underline,
63
54
  )
64
- from .functional import from_optional
65
- from .global_context import (
55
+ from telegrinder.tools.fullname import fullname
56
+ from telegrinder.tools.global_context import (
66
57
  ABCGlobalContext,
67
58
  CtxVar,
68
59
  GlobalContext,
69
60
  GlobalCtxVar,
70
61
  TelegrinderContext,
71
62
  ctx_var,
63
+ runtime_init,
72
64
  )
73
- from .i18n import (
74
- ABCI18n,
75
- ABCTranslator,
76
- ABCTranslatorMiddleware,
77
- I18nEnum,
78
- SimpleI18n,
79
- SimpleTranslator,
80
- )
81
- from .input_file_directory import InputFileDirectory
82
- from .keyboard import (
83
- AnyMarkup,
84
- Button,
85
- InlineButton,
86
- InlineKeyboard,
87
- Keyboard,
88
- RowButtons,
89
- )
90
- from .limited_dict import LimitedDict
91
- from .loop_wrapper import ABCLoopWrapper, DelayedTask, Lifespan, LoopWrapper
92
- from .magic import (
93
- cancel_future,
94
- get_annotations,
95
- get_cached_translation,
65
+ from telegrinder.tools.input_file_directory import InputFileDirectory
66
+ from telegrinder.tools.lifespan import Lifespan
67
+ from telegrinder.tools.limited_dict import LimitedDict
68
+ from telegrinder.tools.loop_wrapper import DelayedTask, LoopWrapper
69
+ from telegrinder.tools.magic import (
70
+ Annotations,
71
+ Bundle,
72
+ bundle,
96
73
  get_default_args,
74
+ get_func_annotations,
97
75
  get_func_parameters,
98
- get_polymorphic_implementations,
99
- impl,
100
- magic_bundle,
76
+ get_generic_parameters,
77
+ join_dicts,
101
78
  resolve_arg_names,
79
+ resolve_kwonly_arg_names,
80
+ resolve_posonly_arg_names,
81
+ shortcut,
102
82
  )
103
- from .parse_mode import ParseMode
104
- from .state_storage import ABCStateStorage, MemoryStateStorage, StateData
83
+ from telegrinder.tools.parse_mode import ParseMode
84
+ from telegrinder.tools.singleton import ABCSingleton, ABCSingletonMeta, Singleton, SingletonMeta
105
85
 
106
86
  __all__ = (
107
- "ABCAdapter",
108
87
  "ABCDataSerializer",
109
- "ABCErrorHandler",
110
88
  "ABCGlobalContext",
111
- "ABCI18n",
112
- "ABCLoopWrapper",
113
- "ABCStateStorage",
114
- "ABCTranslator",
115
- "ABCTranslatorMiddleware",
116
- "AnyMarkup",
117
- "Base",
118
- "BaseButton",
119
- "BlockQuote",
120
- "Button",
121
- "Catcher",
122
- "CatcherError",
89
+ "ABCSingleton",
90
+ "ABCSingletonMeta",
91
+ "Annotations",
92
+ "Bundle",
123
93
  "CtxVar",
124
- "DataclassAdapter",
125
94
  "DelayedTask",
126
- "ErrorHandler",
127
- "EventAdapter",
128
95
  "FormatString",
129
96
  "GlobalContext",
130
97
  "GlobalCtxVar",
131
98
  "HTMLFormatter",
132
- "I18nEnum",
133
- "InlineButton",
134
- "InlineKeyboard",
135
99
  "InputFileDirectory",
136
100
  "JSONSerializer",
137
- "Keyboard",
138
101
  "Lifespan",
139
102
  "LimitedDict",
140
- "Link",
141
103
  "LoopWrapper",
142
- "MemoryStateStorage",
143
- "Mention",
144
104
  "MsgPackSerializer",
145
- "NodeAdapter",
146
105
  "ParseMode",
147
- "PreCode",
148
- "RawEventAdapter",
149
- "RawUpdateAdapter",
150
- "RowButtons",
151
- "SimpleI18n",
152
- "SimpleTranslator",
153
- "SpecialFormat",
154
- "StateData",
106
+ "Singleton",
107
+ "SingletonMeta",
155
108
  "TelegrinderContext",
156
- "TgEmoji",
157
109
  "block_quote",
158
110
  "bold",
111
+ "bundle",
159
112
  "cancel_future",
160
113
  "code_inline",
161
114
  "ctx_var",
162
115
  "escape",
163
- "from_optional",
164
- "get_annotations",
165
- "get_cached_translation",
116
+ "fullname",
166
117
  "get_default_args",
118
+ "get_func_annotations",
167
119
  "get_func_parameters",
168
- "get_polymorphic_implementations",
169
- "impl",
120
+ "get_generic_parameters",
170
121
  "italic",
122
+ "join_dicts",
171
123
  "link",
172
- "magic_bundle",
124
+ "maybe_awaitable",
173
125
  "mention",
126
+ "next_generator",
174
127
  "pre_code",
175
128
  "resolve_arg_names",
129
+ "resolve_kwonly_arg_names",
130
+ "resolve_posonly_arg_names",
131
+ "run_task",
132
+ "runtime_init",
133
+ "send_generator_value",
134
+ "shortcut",
176
135
  "spoiler",
136
+ "stop_generator",
177
137
  "strike",
178
138
  "tg_bot_attach_open_any_chat",
179
139
  "tg_bot_attach_open_current_chat",
@@ -0,0 +1,103 @@
1
+ import asyncio
2
+ from inspect import isasyncgen, isawaitable
3
+
4
+ import typing_extensions as typing
5
+
6
+ type Generator[Yield, Send, Return] = typing.AsyncGenerator[Yield, Send] | typing.Generator[Yield, Send, Return]
7
+
8
+
9
+ def run_task[T](
10
+ task: typing.Awaitable[T],
11
+ /,
12
+ *,
13
+ loop: asyncio.AbstractEventLoop | None = None,
14
+ ) -> T:
15
+ loop = loop or asyncio.get_event_loop()
16
+ return loop.run_until_complete(future=task)
17
+
18
+
19
+ async def next_generator[T](generator: Generator[T, typing.Any, typing.Any], /) -> T:
20
+ return await send_generator_value(generator, None)
21
+
22
+
23
+ async def send_generator_value[Yield, Send](
24
+ generator: Generator[Yield, Send, typing.Any],
25
+ value: Send | None,
26
+ /,
27
+ ) -> Yield:
28
+ try:
29
+ return (
30
+ await generator.asend(value) if isasyncgen(generator) else generator.send(value) # type: ignore
31
+ )
32
+ except StopIteration as exc:
33
+ raise StopGenerator(exc.value, exc.args) from exc
34
+
35
+
36
+ async def stop_generator[Send, Return](
37
+ generator: Generator[typing.Any, Send, Return],
38
+ with_value: Send | None = None,
39
+ /,
40
+ ) -> Return | None:
41
+ try:
42
+ await send_generator_value(generator, with_value)
43
+ except (StopGenerator, StopAsyncIteration) as exc:
44
+ return exc.value if isinstance(exc, StopGenerator) else None
45
+
46
+
47
+ async def maybe_awaitable[T](obj: T | typing.Awaitable[T], /) -> T:
48
+ if isawaitable(obj):
49
+ return await obj
50
+ return obj
51
+
52
+
53
+ # Source code: https://github.com/facebookincubator/later/blob/main/later/task.py#L75
54
+ async def cancel_future(fut: asyncio.Future[typing.Any], /) -> None:
55
+ if fut.done():
56
+ return
57
+
58
+ fut.cancel()
59
+ exc: asyncio.CancelledError | None = None
60
+
61
+ while not fut.done():
62
+ shielded = asyncio.shield(fut)
63
+ try:
64
+ await asyncio.wait([shielded])
65
+ except asyncio.CancelledError as ex:
66
+ exc = ex
67
+ finally:
68
+ # Insure we handle the exception/value that may exist on the shielded task
69
+ # This will prevent errors logged to the asyncio logger
70
+ if shielded.done() and not shielded.cancelled() and not shielded.exception():
71
+ shielded.result()
72
+
73
+ if fut.cancelled():
74
+ if exc is None:
75
+ return
76
+ raise exc from None
77
+
78
+ ex = fut.exception()
79
+ if ex is not None:
80
+ raise ex from None
81
+
82
+ raise asyncio.InvalidStateError(
83
+ f"Task did not raise CancelledError on cancel: {fut!r} had result {fut.result()!r}",
84
+ )
85
+
86
+
87
+ class StopGenerator(Exception):
88
+ value: typing.Any
89
+
90
+ def __init__(self, value: typing.Any, *args: object) -> None:
91
+ super().__init__(*args)
92
+ self.value = value
93
+
94
+
95
+ __all__ = (
96
+ "StopGenerator",
97
+ "cancel_future",
98
+ "maybe_awaitable",
99
+ "next_generator",
100
+ "run_task",
101
+ "send_generator_value",
102
+ "stop_generator",
103
+ )
@@ -0,0 +1,5 @@
1
+ from telegrinder.tools.callback_data_serialization.abc import ABCDataSerializer
2
+ from telegrinder.tools.callback_data_serialization.json_ser import JSONSerializer
3
+ from telegrinder.tools.callback_data_serialization.msgpack_ser import MsgPackSerializer
4
+
5
+ __all__ = ("ABCDataSerializer", "JSONSerializer", "MsgPackSerializer")
@@ -5,8 +5,7 @@ from fntypes.result import Error, Ok, Result
5
5
 
6
6
  from telegrinder.modules import json
7
7
  from telegrinder.msgspec_utils import decoder
8
-
9
- from .abc import ABCDataSerializer, ModelType
8
+ from telegrinder.tools.callback_data_serialization.abc import ABCDataSerializer, ModelType
10
9
 
11
10
  type Json = dict[str, typing.Any] | ModelType
12
11
 
@@ -39,7 +38,7 @@ class JSONSerializer[JsonT: Json](ABCDataSerializer[JsonT]):
39
38
  return self.key + json.dumps(data)
40
39
 
41
40
  def deserialize(self, serialized_data: str) -> Result[JsonT, str]:
42
- if self.ident_key and not serialized_data.startswith(self.key):
41
+ if not serialized_data.startswith(self.key):
43
42
  return Error("Data is not corresponding to key.")
44
43
 
45
44
  data = serialized_data.removeprefix(self.key)
@@ -10,8 +10,20 @@ import msgspec
10
10
  from fntypes.result import Error, Ok, Result
11
11
 
12
12
  from telegrinder.msgspec_utils import decoder, encoder, get_class_annotations
13
+ from telegrinder.tools.callback_data_serialization.abc import ABCDataSerializer, ModelType
13
14
 
14
- from .abc import ABCDataSerializer, ModelType
15
+ DESERIALIZE_EXCEPTIONS: typing.Final[set[type[BaseException]]] = {
16
+ msgspec.DecodeError,
17
+ msgspec.ValidationError,
18
+ binascii.Error,
19
+ }
20
+
21
+ try:
22
+ import brotli # type: ignore
23
+
24
+ DESERIALIZE_EXCEPTIONS.add(brotli.error) # type: ignore
25
+ except ImportError:
26
+ brotli = None
15
27
 
16
28
 
17
29
  @dataclasses.dataclass(frozen=True, slots=True)
@@ -37,16 +49,18 @@ class ModelParser[Model: ModelType]:
37
49
  return isinstance(inspected_type, msgspec.inspect.DataclassType | msgspec.inspect.StructType)
38
50
 
39
51
  def _is_iter_of_model(
40
- self, inspected_type: msgspec.inspect.Type, /
52
+ self,
53
+ inspected_type: msgspec.inspect.Type,
54
+ /,
41
55
  ) -> typing.TypeGuard[msgspec.inspect.ListType]:
42
56
  return isinstance(
43
57
  inspected_type,
44
58
  msgspec.inspect.ListType | msgspec.inspect.SetType | msgspec.inspect.FrozenSetType,
45
59
  ) and self._is_model_type(inspected_type.item_type)
46
60
 
47
- def _validate_annotation(self, annotation: typing.Any, /) -> tuple[type[ModelType], bool] | None:
61
+ def _inspect_annotation(self, annotation: typing.Any, /) -> tuple[type[ModelType], bool] | None:
48
62
  is_iter_of_model = False
49
- type_args: tuple[msgspec.inspect.Type, ...] | None = None
63
+ type_args: tuple[msgspec.inspect.Type, ...] = tuple()
50
64
  inspected_type = msgspec.inspect.type_info(annotation)
51
65
 
52
66
  if self._is_union(inspected_type):
@@ -57,14 +71,15 @@ class ModelParser[Model: ModelType]:
57
71
  elif self._is_model_type(inspected_type):
58
72
  type_args = (inspected_type,)
59
73
 
60
- if type_args is not None:
61
- for arg in type_args:
62
- if self._is_union(arg):
63
- type_args += arg.types
64
- if self._is_model_type(arg):
65
- return (arg.cls, is_iter_of_model)
66
- if self._is_iter_of_model(arg):
67
- return (arg.item_type.cls, True) # type: ignore
74
+ for arg in type_args:
75
+ if self._is_union(arg):
76
+ type_args += arg.types
77
+
78
+ if self._is_model_type(arg):
79
+ return (arg.cls, is_iter_of_model)
80
+
81
+ if self._is_iter_of_model(arg):
82
+ return (arg.item_type.cls, True) # type: ignore
68
83
 
69
84
  return None
70
85
 
@@ -103,8 +118,8 @@ class ModelParser[Model: ModelType]:
103
118
  for index, (field, annotation) in enumerate(get_class_annotations(current_model).items()):
104
119
  obj, model_type, is_iter_of_model = current_data[index], None, False
105
120
 
106
- if isinstance(obj, list) and (validated := self._validate_annotation(annotation)):
107
- model_type, is_iter_of_model = validated
121
+ if isinstance(obj, list) and (inspected := self._inspect_annotation(annotation)):
122
+ model_type, is_iter_of_model = inspected
108
123
 
109
124
  if model_type is not None:
110
125
  if is_iter_of_model:
@@ -145,19 +160,22 @@ class MsgPackSerializer[Model: ModelType](ABCDataSerializer[Model]):
145
160
 
146
161
  @cached_property
147
162
  def key(self) -> bytes:
148
- if self.ident_key:
149
- return msgspec.msgpack.encode(super().key)
150
- return b""
151
-
152
- def serialize(self, data: Model) -> str:
153
- return base64.urlsafe_b64encode(
154
- self.key + msgspec.msgpack.encode(self._model_parser.parse(data), enc_hook=encoder.enc_hook),
155
- ).decode()
156
-
157
- def deserialize(self, serialized_data: str) -> Result[Model, str]:
158
- with suppress(msgspec.DecodeError, msgspec.ValidationError, binascii.Error):
159
- ser_data = base64.urlsafe_b64decode(serialized_data)
160
- if self.ident_key and not ser_data.startswith(self.key):
163
+ return msgspec.msgpack.encode(super().key) if self.ident_key else b""
164
+
165
+ def serialize(self, data: Model, /) -> str:
166
+ encoded = self.key + msgspec.msgpack.encode(self._model_parser.parse(data), enc_hook=encoder.enc_hook)
167
+ if brotli is not None:
168
+ return base64.b85encode(brotli.compress(encoded, quality=11)).decode() # type: ignore
169
+ return base64.urlsafe_b64encode(encoded).decode()
170
+
171
+ def deserialize(self, serialized_data: str, /) -> Result[Model, str]:
172
+ with suppress(*DESERIALIZE_EXCEPTIONS):
173
+ if brotli is not None:
174
+ ser_data = typing.cast("bytes", brotli.decompress(base64.b85decode(serialized_data)))
175
+ else:
176
+ ser_data = base64.urlsafe_b64decode(serialized_data)
177
+
178
+ if not ser_data.startswith(self.key):
161
179
  return Error("Data is not corresponding to key.")
162
180
 
163
181
  data: list[typing.Any] = msgspec.msgpack.decode(
@@ -0,0 +1,21 @@
1
+ import typing
2
+
3
+ from telegrinder.tools.fullname import fullname
4
+
5
+
6
+ class Final:
7
+ if not typing.TYPE_CHECKING:
8
+
9
+ def __new__(cls, *args, **kwargs):
10
+ if cls is Final:
11
+ raise TypeError("Class Final cannot be instantiate.")
12
+ return super().__new__(cls, *args, **kwargs)
13
+
14
+ @typing.final
15
+ def __init_subclass__(cls, **kwargs: typing.Any) -> None:
16
+ for base in cls.__bases__:
17
+ if base is not Final and issubclass(base, Final):
18
+ raise TypeError(f"Final class `{fullname(base)}` cannot be subclassed.")
19
+
20
+
21
+ __all__ = ("Final",)
@@ -1,4 +1,4 @@
1
- from .deep_links import (
1
+ from telegrinder.tools.formatting.deep_links import (
2
2
  tg_bot_attach_open_any_chat,
3
3
  tg_bot_attach_open_current_chat,
4
4
  tg_bot_attach_open_specific_chat,
@@ -25,7 +25,7 @@ from .deep_links import (
25
25
  tg_share_link,
26
26
  tg_story_link,
27
27
  )
28
- from .html_formatter import (
28
+ from telegrinder.tools.formatting.html_formatter import (
29
29
  FormatString,
30
30
  HTMLFormatter,
31
31
  block_quote,
@@ -41,26 +41,10 @@ from .html_formatter import (
41
41
  tg_emoji,
42
42
  underline,
43
43
  )
44
- from .spec_html_formats import (
45
- Base,
46
- BlockQuote,
47
- Link,
48
- Mention,
49
- PreCode,
50
- SpecialFormat,
51
- TgEmoji,
52
- )
53
44
 
54
45
  __all__ = (
55
- "Base",
56
- "BlockQuote",
57
46
  "FormatString",
58
47
  "HTMLFormatter",
59
- "Link",
60
- "Mention",
61
- "PreCode",
62
- "SpecialFormat",
63
- "TgEmoji",
64
48
  "block_quote",
65
49
  "bold",
66
50
  "code_inline",
@@ -0,0 +1,39 @@
1
+ from telegrinder.tools.formatting.deep_links.links import *
2
+ from telegrinder.tools.formatting.deep_links.parsing import *
3
+ from telegrinder.tools.formatting.deep_links.validators import *
4
+
5
+ __all__ = (
6
+ "NO_VALUE",
7
+ "NoValue",
8
+ "Parameter",
9
+ "get_parameter_metadata",
10
+ "get_query_params",
11
+ "parse_deep_link",
12
+ "parse_query_params",
13
+ "separate_by_plus_char",
14
+ "tg_bot_attach_open_any_chat",
15
+ "tg_bot_attach_open_current_chat",
16
+ "tg_bot_attach_open_specific_chat",
17
+ "tg_bot_start_link",
18
+ "tg_bot_startchannel_link",
19
+ "tg_bot_startgroup_link",
20
+ "tg_chat_folder_link",
21
+ "tg_chat_invite_link",
22
+ "tg_direct_mini_app_link",
23
+ "tg_emoji_link",
24
+ "tg_emoji_stickerset_link",
25
+ "tg_invoice_link",
26
+ "tg_language_pack_link",
27
+ "tg_main_mini_app_link",
28
+ "tg_mention_link",
29
+ "tg_open_message_link",
30
+ "tg_premium_multigift_link",
31
+ "tg_premium_offer_link",
32
+ "tg_private_channel_boost_link",
33
+ "tg_private_message_link",
34
+ "tg_public_channel_boost_link",
35
+ "tg_public_message_link",
36
+ "tg_public_username_link",
37
+ "tg_share_link",
38
+ "tg_story_link",
39
+ )
@@ -1,14 +1,17 @@
1
- import types
2
1
  import typing
3
- from collections import OrderedDict
4
2
  from datetime import timedelta
5
3
  from functools import wraps
6
- from urllib.parse import urlencode
7
4
 
8
- from telegrinder.tools.magic import get_annotations
5
+ from telegrinder.tools.formatting.deep_links.parsing import (
6
+ NO_VALUE,
7
+ DeepLinkFunction,
8
+ NoValue,
9
+ Parameter,
10
+ get_query_params,
11
+ parse_deep_link,
12
+ )
13
+ from telegrinder.tools.formatting.deep_links.validators import separate_by_plus_char
9
14
 
10
- type DeepLinkFunction[**P] = typing.Callable[P, str]
11
- type NoValue = types.EllipsisType
12
15
  type Permission = typing.Literal[
13
16
  "change_info",
14
17
  "post_messages",
@@ -32,9 +35,6 @@ type Peer = typing.Literal[
32
35
  "groups",
33
36
  "channels",
34
37
  ]
35
- Parameter = typing.Annotated
36
-
37
- NO_VALUE: typing.Final[NoValue] = typing.cast(NoValue, ...)
38
38
 
39
39
 
40
40
  def deep_link[**P](
@@ -61,79 +61,6 @@ def deep_link[**P](
61
61
  return inner
62
62
 
63
63
 
64
- def get_query_params(
65
- func: DeepLinkFunction[...],
66
- kwargs: dict[str, typing.Any],
67
- order_params: set[str] | None = None,
68
- ) -> dict[str, typing.Any]:
69
- annotations = get_annotations(func)
70
- params = OrderedDict()
71
- param_names = (
72
- [*order_params, *(p for p in annotations if p not in order_params)] if order_params else annotations
73
- )
74
-
75
- for param_name in param_names:
76
- annotation = annotations[param_name]
77
- if param_name in kwargs:
78
- value = kwargs[param_name]
79
- if typing.get_origin(annotation) is Parameter:
80
- param_name, validator = get_parameter_metadata(annotation)
81
- value = validator(value) if validator is not None else value
82
-
83
- params[param_name] = value
84
-
85
- return params
86
-
87
-
88
- def parse_query_params(
89
- params: dict[str, typing.Any],
90
- no_value_params: set[str] | None = None,
91
- /,
92
- ) -> tuple[set[str], dict[str, typing.Any]]:
93
- no_value_params = no_value_params or set()
94
- params_: dict[str, typing.Any] = {}
95
-
96
- for key, value in params.items():
97
- if value in (False, None):
98
- continue
99
-
100
- if value in (True, NO_VALUE):
101
- no_value_params.add(key)
102
- continue
103
- if isinstance(value, timedelta):
104
- value = int(value.total_seconds())
105
-
106
- params_[key] = value
107
-
108
- return (no_value_params, params_)
109
-
110
-
111
- def get_parameter_metadata(
112
- parameter: typing.Any,
113
- ) -> tuple[str, typing.Callable[[typing.Any], typing.Any] | None]:
114
- meta: tuple[typing.Any, ...] = getattr(parameter, "__metadata__")
115
- return meta if len(meta) == 2 else (meta[0], None)
116
-
117
-
118
- def parse_deep_link(
119
- *,
120
- link: str,
121
- params: dict[str, typing.Any],
122
- no_value_params: set[str] | None = None,
123
- ) -> str:
124
- no_value_params, params = parse_query_params(params, no_value_params)
125
- query = urlencode(params, encoding="UTF-8") + ("&" if no_value_params else "") + "&".join(no_value_params)
126
- return f"{link}?{query}"
127
-
128
-
129
- def validate_permissions(perms: list[Permission] | None, /) -> str | None:
130
- return None if not perms else "+".join(perms)
131
-
132
-
133
- def validate_peer(peer: list[Peer] | None, /) -> str | None:
134
- return None if not peer else "+".join(peer)
135
-
136
-
137
64
  @deep_link("tg://resolve")
138
65
  def tg_public_username_link(
139
66
  *,
@@ -364,7 +291,7 @@ def tg_bot_startgroup_link(
364
291
  *,
365
292
  bot_username: Parameter[str, "domain"],
366
293
  parameter: Parameter[str | NoValue, "startgroup"] = NO_VALUE,
367
- permissions: Parameter[list[Permission] | None, "admin", validate_permissions] = None,
294
+ permissions: Parameter[list[Permission] | None, "admin", separate_by_plus_char] = None,
368
295
  ) -> str:
369
296
  """Used to add bots to groups.
370
297
  First of all, check that the `<bot_username>` indeed links to a bot.
@@ -394,7 +321,7 @@ def tg_bot_startgroup_link(
394
321
  def tg_bot_startchannel_link(
395
322
  *,
396
323
  bot_username: Parameter[str, "domain"],
397
- permissions: Parameter[list[Permission], "admin", validate_permissions],
324
+ permissions: Parameter[list[Permission], "admin", separate_by_plus_char],
398
325
  ) -> str:
399
326
  """Used to add bots to channels.
400
327
  First of all, check that the `<bot_username>` indeed links to a bot.
@@ -499,7 +426,7 @@ def tg_bot_attach_open_any_chat(
499
426
  *,
500
427
  bot_username: Parameter[str, "domain"],
501
428
  parameter: Parameter[str | NoValue, "startattach"] = NO_VALUE,
502
- peer: Parameter[list[Peer] | None, "choose", validate_peer] = None,
429
+ peer: Parameter[list[Peer] | None, "choose", separate_by_plus_char] = None,
503
430
  ) -> str:
504
431
  """After installing the `attachment/side` menu entry globally, opens a dialog selection form that will open the attachment menu
505
432
  mini app using `messages.requestWebView` in a specific chat (pass it to the peer parameter of `messages.requestWebView`).