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.
- telegrinder/__init__.py +37 -55
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +6 -4
- telegrinder/api/api.py +100 -26
- telegrinder/api/error.py +42 -8
- telegrinder/api/response.py +4 -1
- telegrinder/api/token.py +2 -2
- telegrinder/bot/__init__.py +9 -25
- telegrinder/bot/bot.py +31 -25
- telegrinder/bot/cute_types/__init__.py +0 -0
- telegrinder/bot/cute_types/base.py +103 -61
- telegrinder/bot/cute_types/callback_query.py +447 -400
- telegrinder/bot/cute_types/chat_join_request.py +59 -62
- telegrinder/bot/cute_types/chat_member_updated.py +154 -157
- telegrinder/bot/cute_types/inline_query.py +41 -44
- telegrinder/bot/cute_types/message.py +2621 -2590
- telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
- telegrinder/bot/cute_types/update.py +1 -8
- telegrinder/bot/cute_types/utils.py +1 -1
- telegrinder/bot/dispatch/__init__.py +10 -15
- telegrinder/bot/dispatch/abc.py +12 -11
- telegrinder/bot/dispatch/action.py +104 -0
- telegrinder/bot/dispatch/context.py +32 -26
- telegrinder/bot/dispatch/dispatch.py +61 -134
- telegrinder/bot/dispatch/handler/__init__.py +2 -0
- telegrinder/bot/dispatch/handler/abc.py +10 -8
- telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
- telegrinder/bot/dispatch/handler/base.py +10 -33
- telegrinder/bot/dispatch/handler/document_reply.py +2 -3
- telegrinder/bot/dispatch/handler/func.py +55 -87
- telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
- telegrinder/bot/dispatch/handler/message_reply.py +2 -3
- telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
- telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
- telegrinder/bot/dispatch/handler/video_reply.py +2 -3
- telegrinder/bot/dispatch/middleware/__init__.py +0 -0
- telegrinder/bot/dispatch/middleware/abc.py +79 -55
- telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
- telegrinder/bot/dispatch/process.py +84 -105
- telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
- telegrinder/bot/dispatch/return_manager/abc.py +102 -65
- telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
- telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
- telegrinder/bot/dispatch/return_manager/message.py +8 -10
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
- telegrinder/bot/dispatch/view/__init__.py +4 -4
- telegrinder/bot/dispatch/view/abc.py +6 -16
- telegrinder/bot/dispatch/view/base.py +54 -178
- telegrinder/bot/dispatch/view/box.py +19 -18
- telegrinder/bot/dispatch/view/callback_query.py +4 -8
- telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
- telegrinder/bot/dispatch/view/chat_member.py +5 -25
- telegrinder/bot/dispatch/view/error.py +9 -0
- telegrinder/bot/dispatch/view/inline_query.py +4 -8
- telegrinder/bot/dispatch/view/message.py +5 -25
- telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
- telegrinder/bot/dispatch/view/raw.py +3 -109
- telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
- telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
- telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
- telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
- telegrinder/bot/polling/__init__.py +0 -0
- telegrinder/bot/polling/abc.py +0 -0
- telegrinder/bot/polling/polling.py +209 -88
- telegrinder/bot/rules/__init__.py +3 -16
- telegrinder/bot/rules/abc.py +42 -122
- telegrinder/bot/rules/callback_data.py +29 -49
- telegrinder/bot/rules/chat_join.py +5 -23
- telegrinder/bot/rules/command.py +8 -4
- telegrinder/bot/rules/enum_text.py +3 -4
- telegrinder/bot/rules/func.py +7 -14
- telegrinder/bot/rules/fuzzy.py +3 -4
- telegrinder/bot/rules/inline.py +8 -20
- telegrinder/bot/rules/integer.py +2 -3
- telegrinder/bot/rules/is_from.py +12 -11
- telegrinder/bot/rules/logic.py +11 -5
- telegrinder/bot/rules/markup.py +22 -14
- telegrinder/bot/rules/mention.py +8 -7
- telegrinder/bot/rules/message_entities.py +8 -4
- telegrinder/bot/rules/node.py +23 -12
- telegrinder/bot/rules/payload.py +5 -4
- telegrinder/bot/rules/payment_invoice.py +6 -21
- telegrinder/bot/rules/regex.py +2 -4
- telegrinder/bot/rules/rule_enum.py +8 -7
- telegrinder/bot/rules/start.py +5 -6
- telegrinder/bot/rules/state.py +1 -1
- telegrinder/bot/rules/text.py +4 -15
- telegrinder/bot/rules/update.py +3 -4
- telegrinder/bot/scenario/__init__.py +0 -0
- telegrinder/bot/scenario/abc.py +6 -5
- telegrinder/bot/scenario/checkbox.py +1 -1
- telegrinder/bot/scenario/choice.py +30 -39
- telegrinder/client/__init__.py +3 -5
- telegrinder/client/abc.py +11 -6
- telegrinder/client/aiohttp.py +141 -27
- telegrinder/client/form_data.py +1 -1
- telegrinder/model.py +61 -89
- telegrinder/modules.py +325 -102
- telegrinder/msgspec_utils/__init__.py +40 -0
- telegrinder/msgspec_utils/abc.py +18 -0
- telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
- telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
- telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +389 -0
- telegrinder/msgspec_utils/encoder.py +206 -0
- telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
- telegrinder/msgspec_utils/tools.py +75 -0
- telegrinder/node/__init__.py +24 -7
- telegrinder/node/attachment.py +1 -0
- telegrinder/node/base.py +154 -72
- telegrinder/node/callback_query.py +5 -5
- telegrinder/node/collection.py +39 -0
- telegrinder/node/command.py +1 -2
- telegrinder/node/composer.py +121 -72
- telegrinder/node/container.py +11 -8
- telegrinder/node/context.py +48 -0
- telegrinder/node/either.py +27 -40
- telegrinder/node/error.py +41 -0
- telegrinder/node/event.py +37 -11
- telegrinder/node/exceptions.py +7 -0
- telegrinder/node/file.py +0 -0
- telegrinder/node/i18n.py +108 -0
- telegrinder/node/me.py +3 -2
- telegrinder/node/payload.py +1 -1
- telegrinder/node/polymorphic.py +63 -28
- telegrinder/node/reply_message.py +12 -0
- telegrinder/node/rule.py +6 -13
- telegrinder/node/scope.py +14 -5
- telegrinder/node/session.py +53 -0
- telegrinder/node/source.py +41 -9
- telegrinder/node/text.py +1 -2
- telegrinder/node/tools/__init__.py +0 -0
- telegrinder/node/tools/generator.py +3 -5
- telegrinder/node/utility.py +16 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +0 -0
- telegrinder/tools/__init__.py +48 -88
- telegrinder/tools/aio.py +103 -0
- telegrinder/tools/callback_data_serialization/__init__.py +5 -0
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +2 -18
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
- telegrinder/tools/formatting/deep_links/parsing.py +90 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html_formatter.py +18 -45
- telegrinder/tools/fullname.py +83 -0
- telegrinder/tools/global_context/__init__.py +4 -3
- telegrinder/tools/global_context/abc.py +17 -14
- telegrinder/tools/global_context/builtin_context.py +39 -0
- telegrinder/tools/global_context/global_context.py +138 -39
- telegrinder/tools/input_file_directory.py +0 -0
- telegrinder/tools/keyboard/__init__.py +39 -0
- telegrinder/tools/keyboard/abc.py +159 -0
- telegrinder/tools/keyboard/base.py +77 -0
- telegrinder/tools/keyboard/buttons/__init__.py +14 -0
- telegrinder/tools/keyboard/buttons/base.py +18 -0
- telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
- telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
- telegrinder/tools/keyboard/buttons/tools.py +18 -0
- telegrinder/tools/keyboard/data.py +20 -0
- telegrinder/tools/keyboard/keyboard.py +131 -0
- telegrinder/tools/keyboard/static_keyboard.py +83 -0
- telegrinder/tools/lifespan.py +87 -51
- telegrinder/tools/limited_dict.py +4 -1
- telegrinder/tools/loop_wrapper.py +332 -0
- telegrinder/tools/magic/__init__.py +32 -0
- telegrinder/tools/magic/annotations.py +165 -0
- telegrinder/tools/magic/dictionary.py +20 -0
- telegrinder/tools/magic/function.py +246 -0
- telegrinder/tools/magic/shortcut.py +111 -0
- telegrinder/tools/parse_mode.py +9 -3
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_storage/__init__.py +0 -0
- telegrinder/tools/state_storage/abc.py +6 -1
- telegrinder/tools/state_storage/memory.py +1 -1
- telegrinder/tools/strings.py +0 -0
- telegrinder/types/__init__.py +307 -268
- telegrinder/types/enums.py +64 -37
- telegrinder/types/input_file.py +3 -3
- telegrinder/types/methods.py +5699 -5055
- telegrinder/types/methods_utils.py +62 -0
- telegrinder/types/objects.py +7846 -7058
- telegrinder/verification_utils.py +3 -1
- telegrinder-0.5.0.dist-info/METADATA +162 -0
- telegrinder-0.5.0.dist-info/RECORD +200 -0
- {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.dist-info}/licenses/LICENSE +2 -2
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
- telegrinder/bot/rules/id.py +0 -24
- telegrinder/bot/rules/message.py +0 -15
- telegrinder/client/sonic.py +0 -212
- telegrinder/msgspec_utils.py +0 -478
- telegrinder/tools/adapter/__init__.py +0 -19
- telegrinder/tools/adapter/abc.py +0 -49
- telegrinder/tools/adapter/dataclass.py +0 -56
- telegrinder/tools/adapter/errors.py +0 -5
- telegrinder/tools/adapter/event.py +0 -61
- telegrinder/tools/adapter/node.py +0 -46
- telegrinder/tools/adapter/raw_event.py +0 -27
- telegrinder/tools/adapter/raw_update.py +0 -30
- telegrinder/tools/callback_data_serilization/__init__.py +0 -5
- telegrinder/tools/error_handler/__init__.py +0 -10
- telegrinder/tools/error_handler/abc.py +0 -30
- telegrinder/tools/error_handler/error.py +0 -9
- telegrinder/tools/error_handler/error_handler.py +0 -179
- telegrinder/tools/formatting/spec_html_formats.py +0 -75
- telegrinder/tools/functional.py +0 -8
- telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
- telegrinder/tools/i18n/__init__.py +0 -12
- telegrinder/tools/i18n/abc.py +0 -32
- telegrinder/tools/i18n/middleware/__init__.py +0 -3
- telegrinder/tools/i18n/middleware/abc.py +0 -22
- telegrinder/tools/i18n/simple.py +0 -43
- telegrinder/tools/keyboard.py +0 -132
- telegrinder/tools/loop_wrapper/__init__.py +0 -4
- telegrinder/tools/loop_wrapper/abc.py +0 -20
- telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
- telegrinder/tools/magic.py +0 -344
- telegrinder-0.4.2.dist-info/METADATA +0 -151
- telegrinder-0.4.2.dist-info/RECORD +0 -182
- {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.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")
|