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
@@ -0,0 +1,332 @@
1
+ import asyncio
2
+ import contextlib
3
+ import datetime
4
+ import enum
5
+ import typing
6
+
7
+ from telegrinder.modules import logger
8
+ from telegrinder.tools.aio import cancel_future, run_task
9
+ from telegrinder.tools.final import Final
10
+ from telegrinder.tools.fullname import fullname
11
+ from telegrinder.tools.lifespan import (
12
+ CoroutineFunc,
13
+ CoroutineTask,
14
+ DelayedTask,
15
+ Lifespan,
16
+ Task,
17
+ to_coroutine_task,
18
+ )
19
+ from telegrinder.tools.singleton.singleton import Singleton
20
+
21
+ type Tasks = set[asyncio.Task[typing.Any]]
22
+ type LoopFactory = typing.Callable[[], asyncio.AbstractEventLoop]
23
+ type DelayedFunctionDecorator[**P, R] = typing.Callable[[typing.Callable[P, R]], DelayedFunction[P, R]]
24
+
25
+
26
+ class DelayedFunction[**P, R](typing.Protocol):
27
+ __name__: str
28
+ __delayed_task__: DelayedTask
29
+
30
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> typing.Coroutine[typing.Any, typing.Any, R]: ...
31
+
32
+ def cancel(self) -> bool:
33
+ """Cancel delayed task."""
34
+ ...
35
+
36
+
37
+ @enum.unique
38
+ class LoopWrapperState(enum.Enum):
39
+ NOT_RUNNING = enum.auto()
40
+ RUNNING = enum.auto()
41
+ RUNNING_MANUALLY = enum.auto()
42
+ SHUTDOWN = enum.auto()
43
+
44
+
45
+ @typing.final
46
+ class LoopWrapper(Singleton, Final):
47
+ _loop: asyncio.AbstractEventLoop
48
+ _lifespan: Lifespan
49
+ _tasks: list[CoroutineTask[typing.Any]]
50
+ _state: LoopWrapperState
51
+ _all_tasks: set[asyncio.Task[typing.Any]]
52
+ _limit: int | None
53
+ _semaphore: asyncio.Semaphore | None
54
+
55
+ __slots__ = (
56
+ "_loop",
57
+ "_lifespan",
58
+ "_tasks",
59
+ "_state",
60
+ "_all_tasks",
61
+ "_limit",
62
+ "_semaphore",
63
+ )
64
+
65
+ def __init__(self) -> None:
66
+ self._loop = asyncio.get_event_loop()
67
+ self._lifespan = Lifespan()
68
+ self._tasks = list()
69
+ self._state = LoopWrapperState.NOT_RUNNING
70
+ self._all_tasks = set()
71
+ self._limit = None
72
+ self._semaphore = None
73
+
74
+ self._create_task(self._run_async_event_loop())
75
+
76
+ def __call__(self) -> asyncio.AbstractEventLoop:
77
+ """A loop factory."""
78
+ return self._loop
79
+
80
+ def __repr__(self) -> str:
81
+ return "<{}: loop={!r} lifespan={!r}>".format(
82
+ fullname(self) + (" (running)" if self.running else ""),
83
+ self._loop,
84
+ self._lifespan,
85
+ )
86
+
87
+ async def _run_async_event_loop(self) -> None:
88
+ if not self.running:
89
+ self._state = LoopWrapperState.RUNNING
90
+ async with self._async_wrap_loop():
91
+ await self._run()
92
+
93
+ async def _run(self) -> None:
94
+ logger.debug("Running loop wrapper")
95
+
96
+ while self._tasks:
97
+ await self.create_task(self._tasks.pop(0))
98
+
99
+ while self.running and (tasks := self._get_all_tasks()):
100
+ await self._process_tasks(tasks)
101
+
102
+ async def _process_tasks(self, tasks: Tasks, /) -> None:
103
+ """Processes the given tasks, checks for exceptions, and raises them if any."""
104
+
105
+ tasks_results, _ = await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
106
+ for task_result in tasks_results:
107
+ try:
108
+ if not task_result.cancelled() and (exception := task_result.exception()) is not None:
109
+ raise exception from None # Raise the exception that was set on the task.
110
+ except BaseException:
111
+ logger.exception("Traceback message below:")
112
+
113
+ async def _cancel_tasks(self) -> None:
114
+ with contextlib.suppress(asyncio.CancelledError, asyncio.InvalidStateError):
115
+ await cancel_future(asyncio.gather(*self._get_all_tasks(), return_exceptions=True))
116
+
117
+ @contextlib.asynccontextmanager
118
+ async def _async_wrap_loop(self) -> typing.AsyncGenerator[typing.Any, None]:
119
+ try:
120
+ await self._lifespan._start()
121
+ yield
122
+ except asyncio.CancelledError:
123
+ logger.info("LoopWrapper task was cancelled, cancelling tasks...")
124
+ await self._cancel_tasks()
125
+ finally:
126
+ await self._shutdown()
127
+
128
+ async def _shutdown(self) -> None:
129
+ await self.lifespan._shutdown()
130
+ logger.debug("Shutting down loop wrapper")
131
+ self._state = LoopWrapperState.SHUTDOWN
132
+
133
+ async def _run_coro_with_semaphore(self, coro: CoroutineTask[typing.Any], /) -> None:
134
+ assert self._semaphore is not None
135
+ try:
136
+ await coro
137
+ finally:
138
+ self._semaphore.release()
139
+
140
+ async def _create_task_with_semaphore(self, coro: CoroutineTask[typing.Any], /) -> None:
141
+ assert self._semaphore is not None
142
+ await self._semaphore.acquire()
143
+ self._create_task(self._run_coro_with_semaphore(coro))
144
+
145
+ def _create_task(self, coro: CoroutineTask[typing.Any], /) -> None:
146
+ task = self._loop.create_task(coro)
147
+ self._all_tasks.add(task)
148
+ task.add_done_callback(self._all_tasks.discard)
149
+
150
+ def _get_all_tasks(self) -> Tasks:
151
+ """Get a set of all tasks from the loop wrapper and event loop (`exclude the current task if any`)."""
152
+
153
+ return (self._all_tasks | asyncio.all_tasks(loop=self._loop)).symmetric_difference(
154
+ set() if (task := asyncio.current_task(self.loop)) is None else {task},
155
+ )
156
+
157
+ def _close_loop(self) -> None:
158
+ if not self._loop.is_closed():
159
+ logger.debug("Closing event loop {!r}", self._loop)
160
+ self._loop.close()
161
+
162
+ @contextlib.contextmanager
163
+ def _wrap_loop(self, *, close_loop: bool = True) -> typing.Generator[typing.Any, None, None]:
164
+ try:
165
+ self.lifespan.start()
166
+ yield
167
+ except KeyboardInterrupt:
168
+ logger.info("Caught KeyboardInterrupt, cancelling tasks...")
169
+ run_task(self._cancel_tasks())
170
+ finally:
171
+ run_task(self._shutdown())
172
+ if close_loop:
173
+ self._close_loop()
174
+
175
+ def _get_delayed_task_decorator(
176
+ self,
177
+ repeat: bool,
178
+ days: int = 0,
179
+ hours: int = 0,
180
+ minutes: int = 0,
181
+ seconds: float | datetime.timedelta = 0.0,
182
+ ) -> typing.Callable[..., typing.Any]:
183
+ if isinstance(seconds, int | float):
184
+ seconds += minutes * 60
185
+ seconds += hours * 60 * 60
186
+ seconds += days * 24 * 60 * 60
187
+
188
+ def decorator[Func: CoroutineFunc[..., typing.Any]](function: Func) -> Func:
189
+ self.add_task(DelayedTask(function, seconds, repeat=repeat))
190
+ return function
191
+
192
+ return decorator
193
+
194
+ @property
195
+ def lifespan(self) -> Lifespan:
196
+ return self._lifespan
197
+
198
+ @property
199
+ def loop(self) -> asyncio.AbstractEventLoop:
200
+ return self._loop
201
+
202
+ @property
203
+ def running(self) -> bool:
204
+ return self._state in {LoopWrapperState.RUNNING, LoopWrapperState.RUNNING_MANUALLY}
205
+
206
+ @property
207
+ def shutdown(self) -> bool:
208
+ return self._state is LoopWrapperState.SHUTDOWN
209
+
210
+ @property
211
+ def tasks_limit(self) -> int | None:
212
+ return self._limit
213
+
214
+ def run(self, *, close_loop: bool = True) -> typing.NoReturn: # type: ignore
215
+ if self.running:
216
+ raise RuntimeError("Loop wrapper already running.")
217
+
218
+ self._state = LoopWrapperState.RUNNING_MANUALLY
219
+ with self._wrap_loop(close_loop=close_loop):
220
+ run_task(self._run())
221
+
222
+ @typing.overload
223
+ def bind_loop(self, *, loop_factory: LoopFactory) -> typing.Self: ...
224
+
225
+ @typing.overload
226
+ def bind_loop(self, *, loop: asyncio.AbstractEventLoop) -> typing.Self: ...
227
+
228
+ def bind_loop(
229
+ self,
230
+ *,
231
+ loop_factory: LoopFactory | None = None,
232
+ loop: asyncio.AbstractEventLoop | None = None,
233
+ ) -> typing.Self:
234
+ assert loop is not None or loop_factory is not None
235
+
236
+ if self.running:
237
+ logger.warning("Cannot bind a new event loop to running loop wrapper.")
238
+ return self
239
+
240
+ old_loop = self._loop
241
+ self._loop = loop_factory() if loop_factory else loop or self._loop
242
+
243
+ if old_loop is not self._loop:
244
+ self._create_task(self._run_async_event_loop())
245
+
246
+ return self
247
+
248
+ def limit(self, value: int, /) -> typing.Self:
249
+ if self._limit is not None:
250
+ raise ValueError("Cannot reset limit value.")
251
+
252
+ self._limit = value
253
+ self._semaphore = asyncio.Semaphore(value)
254
+ return self
255
+
256
+ def add_task(self, task: Task[..., typing.Any], /) -> None:
257
+ coro_task = to_coroutine_task(task)
258
+ return self._create_task(coro_task) if self.running else self._tasks.append(coro_task)
259
+
260
+ async def create_task(self, task: Task[..., typing.Any], /) -> None:
261
+ if not self.running:
262
+ self.add_task(task)
263
+ return
264
+
265
+ coro_task = to_coroutine_task(task)
266
+ if self._semaphore is not None:
267
+ await self._create_task_with_semaphore(coro_task)
268
+ else:
269
+ self._create_task(coro_task)
270
+
271
+ @typing.overload
272
+ def timer[**P, R](self, delta: datetime.timedelta, /) -> DelayedFunctionDecorator[P, R]: ...
273
+
274
+ @typing.overload
275
+ def timer[**P, R](
276
+ self,
277
+ *,
278
+ days: int = ...,
279
+ hours: int = ...,
280
+ minutes: int = ...,
281
+ seconds: float = ...,
282
+ ) -> DelayedFunctionDecorator[P, R]: ...
283
+
284
+ def timer(
285
+ self,
286
+ delta: datetime.timedelta | None = None,
287
+ *,
288
+ days: int = 0,
289
+ hours: int = 0,
290
+ minutes: int = 0,
291
+ seconds: float = 0.0,
292
+ ) -> DelayedFunctionDecorator[..., typing.Any]:
293
+ return self._get_delayed_task_decorator(
294
+ repeat=False,
295
+ days=days,
296
+ hours=hours,
297
+ minutes=minutes,
298
+ seconds=delta or seconds,
299
+ )
300
+
301
+ @typing.overload
302
+ def interval[**P, R](self, delta: datetime.timedelta, /) -> DelayedFunctionDecorator[P, R]: ...
303
+
304
+ @typing.overload
305
+ def interval[**P, R](
306
+ self,
307
+ *,
308
+ days: int = ...,
309
+ hours: int = ...,
310
+ minutes: int = ...,
311
+ seconds: float = ...,
312
+ ) -> DelayedFunctionDecorator[P, R]: ...
313
+
314
+ def interval(
315
+ self,
316
+ delta: datetime.timedelta | None = None,
317
+ *,
318
+ days: int = 0,
319
+ hours: int = 0,
320
+ minutes: int = 0,
321
+ seconds: float = 0.0,
322
+ ) -> DelayedFunctionDecorator[..., typing.Any]:
323
+ return self._get_delayed_task_decorator(
324
+ repeat=True,
325
+ days=days,
326
+ hours=hours,
327
+ minutes=minutes,
328
+ seconds=delta or seconds,
329
+ )
330
+
331
+
332
+ __all__ = ("DelayedTask", "LoopWrapper", "to_coroutine_task")
@@ -0,0 +1,32 @@
1
+ from telegrinder.tools.magic.annotations import Annotations, get_generic_parameters
2
+ from telegrinder.tools.magic.dictionary import extract, join_dicts
3
+ from telegrinder.tools.magic.function import (
4
+ Bundle,
5
+ bundle,
6
+ function_context,
7
+ get_default_args,
8
+ get_func_annotations,
9
+ get_func_parameters,
10
+ resolve_arg_names,
11
+ resolve_kwonly_arg_names,
12
+ resolve_posonly_arg_names,
13
+ )
14
+ from telegrinder.tools.magic.shortcut import Shortcut, shortcut
15
+
16
+ __all__ = (
17
+ "Annotations",
18
+ "Bundle",
19
+ "Shortcut",
20
+ "bundle",
21
+ "extract",
22
+ "function_context",
23
+ "get_default_args",
24
+ "get_func_annotations",
25
+ "get_func_parameters",
26
+ "get_generic_parameters",
27
+ "join_dicts",
28
+ "resolve_arg_names",
29
+ "resolve_kwonly_arg_names",
30
+ "resolve_posonly_arg_names",
31
+ "shortcut",
32
+ )
@@ -0,0 +1,165 @@
1
+ import dataclasses
2
+ import sys
3
+ import types
4
+ import typing as _typing
5
+ from functools import cached_property
6
+
7
+ import typing_extensions as typing
8
+ from fntypes.option import Nothing, Option, Some
9
+
10
+ from telegrinder.tools.global_context.global_context import GlobalContext, ctx_var
11
+
12
+ type TypeParameter = typing.Union[
13
+ typing.TypeVar,
14
+ typing.TypeVarTuple,
15
+ typing.ParamSpec,
16
+ _typing.TypeVar,
17
+ _typing.TypeVarTuple,
18
+ _typing.ParamSpec,
19
+ ]
20
+ type TypeParameters = tuple[TypeParameter, ...]
21
+ type SupportsAnnotations = type[typing.Any] | types.ModuleType | typing.Callable[..., typing.Any]
22
+
23
+ _CACHED_ANNOTATIONS: typing.Final[GlobalContext] = GlobalContext(
24
+ "cached_annotations",
25
+ annotations=ctx_var(default_factory=dict, const=True),
26
+ )
27
+
28
+
29
+ def _cache_annotations(obj: SupportsAnnotations, annotations: dict[str, typing.Any], /) -> None:
30
+ _CACHED_ANNOTATIONS.annotations[obj] = annotations
31
+
32
+
33
+ def _get_cached_annotations(obj: SupportsAnnotations, /) -> dict[str, typing.Any] | None:
34
+ return _CACHED_ANNOTATIONS.annotations.get(obj)
35
+
36
+
37
+ @dataclasses.dataclass
38
+ class Annotations:
39
+ obj: SupportsAnnotations
40
+
41
+ @cached_property
42
+ def forward_ref_parameters(self) -> dict[str, typing.Any]:
43
+ parameters = dict[str, typing.Any](
44
+ is_argument=False,
45
+ is_class=False,
46
+ module=None,
47
+ )
48
+
49
+ if isinstance(self.obj, type):
50
+ parameters["is_class"] = True
51
+ parameters["module"] = (
52
+ sys.modules[module] if (module := getattr(self.obj, "__module__", None)) is not None else None
53
+ )
54
+ elif isinstance(self.obj, types.ModuleType):
55
+ parameters["module"] = self.obj
56
+ elif callable(self.obj):
57
+ parameters["is_argument"] = True
58
+
59
+ return parameters
60
+
61
+ @cached_property
62
+ def generic_parameters(self) -> Option[dict[TypeParameter, typing.Any]]:
63
+ return get_generic_parameters(self.obj)
64
+
65
+ @classmethod
66
+ def from_obj(cls, obj: typing.Any, /) -> typing.Self:
67
+ if not isinstance(obj, type | types.ModuleType | typing.Callable):
68
+ obj = type(obj)
69
+
70
+ return cls(obj)
71
+
72
+ @typing.overload
73
+ def get(
74
+ self,
75
+ *,
76
+ ignore_failed_evals: bool = True,
77
+ allow_return_type: bool = False,
78
+ cache: bool = False,
79
+ ) -> dict[str, typing.Any | typing.ForwardRef]: ...
80
+
81
+ @typing.overload
82
+ def get(
83
+ self,
84
+ *,
85
+ exclude_forward_refs: typing.Literal[True],
86
+ ignore_failed_evals: bool = True,
87
+ allow_return_type: bool = False,
88
+ cache: bool = False,
89
+ ) -> dict[str, typing.Any]: ...
90
+
91
+ def get(
92
+ self,
93
+ *,
94
+ exclude_forward_refs: bool = False,
95
+ ignore_failed_evals: bool = True,
96
+ allow_return_type: bool = True,
97
+ cache: bool = False,
98
+ ) -> dict[str, typing.Any]:
99
+ if (cached_annotations := _get_cached_annotations(self.obj)) is not None:
100
+ return cached_annotations
101
+
102
+ annotations = dict[str, typing.Any]()
103
+ for name, annotation in typing.get_annotations(
104
+ obj=self.obj,
105
+ format=typing.Format.FORWARDREF,
106
+ ).items():
107
+ if isinstance(annotation, str):
108
+ annotation = typing.ForwardRef(annotation, **self.forward_ref_parameters)
109
+
110
+ if not isinstance(annotation, typing.ForwardRef):
111
+ annotations[name] = annotation
112
+ continue
113
+
114
+ try:
115
+ value = typing.evaluate_forward_ref(
116
+ forward_ref=annotation,
117
+ owner=self.obj,
118
+ format=typing.Format.VALUE,
119
+ )
120
+ except NameError:
121
+ if not ignore_failed_evals:
122
+ raise
123
+
124
+ value = annotation
125
+
126
+ if isinstance(value, typing.ForwardRef) and exclude_forward_refs:
127
+ continue
128
+
129
+ annotations[name] = value
130
+
131
+ if not allow_return_type:
132
+ annotations.pop("return", None)
133
+
134
+ if cache:
135
+ _cache_annotations(self.obj, annotations)
136
+
137
+ return annotations
138
+
139
+
140
+ def get_generic_parameters(obj: typing.Any, /) -> Option[dict[TypeParameter, typing.Any]]:
141
+ origin_obj = _typing.get_origin(obj)
142
+ args = _typing.get_args(obj)
143
+ parameters: TypeParameters = getattr(origin_obj or obj, "__parameters__")
144
+
145
+ if not parameters:
146
+ return Nothing()
147
+
148
+ index = 0
149
+ generic_alias_args = dict[TypeParameter, _typing.Any]()
150
+
151
+ for parameter in parameters:
152
+ if isinstance(parameter, _typing.TypeVarTuple):
153
+ stop_index = len(args) - index
154
+ generic_alias_args[parameter] = args[index:stop_index]
155
+ index = stop_index
156
+ continue
157
+
158
+ arg = args[index] if index < len(args) else None
159
+ generic_alias_args[parameter] = arg
160
+ index += 1
161
+
162
+ return Some(generic_alias_args)
163
+
164
+
165
+ __all__ = ("Annotations", "get_generic_parameters")
@@ -0,0 +1,20 @@
1
+ import typing
2
+
3
+
4
+ def join_dicts[Key: typing.Hashable, Value](
5
+ left_dict: dict[Key, typing.Any],
6
+ right_dict: dict[typing.Any, Value],
7
+ /,
8
+ ) -> dict[Key, Value]:
9
+ return {left_key: right_dict[right_key] for left_key, right_key in left_dict.items()}
10
+
11
+
12
+ def extract[Key: typing.Hashable, Value](
13
+ keys: typing.Iterable[Key],
14
+ mapping: typing.Mapping[Key, Value],
15
+ /,
16
+ ) -> dict[Key, Value]:
17
+ return {key: mapping[key] for key in keys if key in mapping}
18
+
19
+
20
+ __all__ = ("extract", "join_dicts")