telegrinder 0.3.4__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +148 -149
- telegrinder/api/__init__.py +9 -8
- telegrinder/api/api.py +101 -93
- telegrinder/api/error.py +20 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +72 -66
- telegrinder/bot/bot.py +83 -76
- telegrinder/bot/cute_types/__init__.py +19 -17
- telegrinder/bot/cute_types/base.py +184 -258
- telegrinder/bot/cute_types/callback_query.py +400 -385
- telegrinder/bot/cute_types/chat_join_request.py +62 -61
- telegrinder/bot/cute_types/chat_member_updated.py +157 -160
- telegrinder/bot/cute_types/inline_query.py +44 -43
- telegrinder/bot/cute_types/message.py +2590 -2637
- telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
- telegrinder/bot/cute_types/update.py +112 -104
- telegrinder/bot/cute_types/utils.py +62 -95
- telegrinder/bot/dispatch/__init__.py +59 -55
- telegrinder/bot/dispatch/abc.py +76 -77
- telegrinder/bot/dispatch/context.py +96 -98
- telegrinder/bot/dispatch/dispatch.py +254 -202
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +23 -24
- telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
- telegrinder/bot/dispatch/handler/base.py +57 -57
- telegrinder/bot/dispatch/handler/document_reply.py +44 -44
- telegrinder/bot/dispatch/handler/func.py +129 -135
- telegrinder/bot/dispatch/handler/media_group_reply.py +44 -43
- telegrinder/bot/dispatch/handler/message_reply.py +36 -36
- telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
- telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
- telegrinder/bot/dispatch/handler/video_reply.py +44 -44
- telegrinder/bot/dispatch/middleware/__init__.py +3 -3
- telegrinder/bot/dispatch/middleware/abc.py +97 -22
- telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
- telegrinder/bot/dispatch/process.py +151 -157
- telegrinder/bot/dispatch/return_manager/__init__.py +15 -13
- telegrinder/bot/dispatch/return_manager/abc.py +104 -108
- telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
- telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
- telegrinder/bot/dispatch/return_manager/message.py +36 -36
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -13
- telegrinder/bot/dispatch/view/abc.py +45 -41
- telegrinder/bot/dispatch/view/base.py +231 -200
- telegrinder/bot/dispatch/view/box.py +140 -129
- telegrinder/bot/dispatch/view/callback_query.py +16 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +11 -16
- telegrinder/bot/dispatch/view/chat_member.py +37 -39
- telegrinder/bot/dispatch/view/inline_query.py +16 -17
- telegrinder/bot/dispatch/view/message.py +43 -44
- telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
- telegrinder/bot/dispatch/view/raw.py +116 -114
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +14 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +59 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +20 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +251 -172
- telegrinder/bot/dispatch/waiter_machine/middleware.py +94 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +57 -68
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +139 -131
- telegrinder/bot/rules/__init__.py +85 -62
- telegrinder/bot/rules/abc.py +213 -206
- telegrinder/bot/rules/callback_data.py +122 -163
- telegrinder/bot/rules/chat_join.py +45 -43
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +33 -36
- telegrinder/bot/rules/func.py +28 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/id.py +24 -0
- telegrinder/bot/rules/inline.py +58 -56
- telegrinder/bot/rules/integer.py +21 -20
- telegrinder/bot/rules/is_from.py +127 -127
- telegrinder/bot/rules/logic.py +18 -0
- telegrinder/bot/rules/markup.py +42 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +15 -17
- telegrinder/bot/rules/message_entities.py +33 -35
- telegrinder/bot/rules/node.py +33 -27
- telegrinder/bot/rules/payload.py +81 -0
- telegrinder/bot/rules/payment_invoice.py +29 -0
- telegrinder/bot/rules/regex.py +36 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +35 -37
- telegrinder/bot/rules/text.py +38 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +17 -19
- telegrinder/bot/scenario/checkbox.py +174 -176
- telegrinder/bot/scenario/choice.py +48 -51
- telegrinder/client/__init__.py +12 -4
- telegrinder/client/abc.py +100 -75
- telegrinder/client/aiohttp.py +134 -130
- telegrinder/client/form_data.py +31 -0
- telegrinder/client/sonic.py +212 -0
- telegrinder/model.py +208 -315
- telegrinder/modules.py +239 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +478 -410
- telegrinder/node/__init__.py +86 -25
- telegrinder/node/attachment.py +163 -87
- telegrinder/node/base.py +288 -160
- telegrinder/node/callback_query.py +54 -53
- telegrinder/node/command.py +34 -33
- telegrinder/node/composer.py +163 -198
- telegrinder/node/container.py +33 -27
- telegrinder/node/either.py +82 -0
- telegrinder/node/event.py +54 -65
- telegrinder/node/file.py +51 -0
- telegrinder/node/me.py +15 -16
- telegrinder/node/payload.py +78 -0
- telegrinder/node/polymorphic.py +67 -48
- telegrinder/node/rule.py +72 -76
- telegrinder/node/scope.py +36 -38
- telegrinder/node/source.py +87 -71
- telegrinder/node/text.py +53 -41
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +36 -40
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -62
- telegrinder/tools/__init__.py +152 -93
- telegrinder/tools/adapter/__init__.py +19 -0
- telegrinder/tools/adapter/abc.py +49 -0
- telegrinder/tools/adapter/dataclass.py +56 -0
- telegrinder/{bot/rules → tools}/adapter/errors.py +5 -5
- telegrinder/{bot/rules → tools}/adapter/event.py +63 -65
- telegrinder/{bot/rules → tools}/adapter/node.py +46 -48
- telegrinder/{bot/rules → tools}/adapter/raw_event.py +27 -27
- telegrinder/{bot/rules → tools}/adapter/raw_update.py +30 -30
- telegrinder/tools/buttons.py +106 -80
- telegrinder/tools/callback_data_serilization/__init__.py +5 -0
- telegrinder/tools/callback_data_serilization/abc.py +51 -0
- telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
- telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
- telegrinder/tools/error_handler/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +30 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +179 -193
- telegrinder/tools/formatting/__init__.py +83 -63
- telegrinder/tools/formatting/deep_links.py +541 -0
- telegrinder/tools/formatting/{html.py → html_formatter.py} +266 -294
- telegrinder/tools/formatting/spec_html_formats.py +71 -117
- telegrinder/tools/functional.py +8 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +387 -412
- telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
- telegrinder/tools/i18n/__init__.py +7 -7
- telegrinder/tools/i18n/abc.py +30 -30
- telegrinder/tools/i18n/middleware/__init__.py +3 -3
- telegrinder/tools/i18n/middleware/abc.py +22 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard.py +128 -128
- telegrinder/tools/lifespan.py +105 -0
- telegrinder/tools/limited_dict.py +32 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +20 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +169 -224
- telegrinder/tools/magic.py +307 -157
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +31 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/tools/strings.py +13 -0
- telegrinder/types/__init__.py +268 -260
- telegrinder/types/enums.py +711 -701
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +5055 -4633
- telegrinder/types/objects.py +7058 -6950
- telegrinder/verification_utils.py +30 -32
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +22 -22
- telegrinder-0.4.0.dist-info/METADATA +144 -0
- telegrinder-0.4.0.dist-info/RECORD +182 -0
- {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
- telegrinder/bot/rules/adapter/__init__.py +0 -17
- telegrinder/bot/rules/adapter/abc.py +0 -31
- telegrinder/node/message.py +0 -14
- telegrinder/node/update.py +0 -15
- telegrinder/tools/formatting/links.py +0 -38
- telegrinder/tools/kb_set/__init__.py +0 -4
- telegrinder/tools/kb_set/base.py +0 -15
- telegrinder/tools/kb_set/yaml.py +0 -63
- telegrinder-0.3.4.dist-info/METADATA +0 -110
- telegrinder-0.3.4.dist-info/RECORD +0 -165
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import dataclasses
|
|
3
|
+
import datetime
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
type CoroutineTask[T] = typing.Coroutine[typing.Any, typing.Any, T]
|
|
7
|
+
type CoroutineFunc[**P, T] = typing.Callable[P, CoroutineTask[T]]
|
|
8
|
+
type Task[**P, T] = CoroutineFunc[P, T] | CoroutineTask[T] | DelayedTask[typing.Callable[P, CoroutineTask[T]]]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_tasks(
|
|
12
|
+
tasks: list[CoroutineTask[typing.Any]],
|
|
13
|
+
/,
|
|
14
|
+
) -> None:
|
|
15
|
+
loop = asyncio.get_event_loop()
|
|
16
|
+
while tasks:
|
|
17
|
+
loop.run_until_complete(tasks.pop(0))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def to_coroutine_task[**P, T](task: Task[P, T], /) -> CoroutineTask[T]:
|
|
21
|
+
if asyncio.iscoroutinefunction(task) or isinstance(task, DelayedTask):
|
|
22
|
+
task = task()
|
|
23
|
+
elif not asyncio.iscoroutine(task):
|
|
24
|
+
raise TypeError("Task should be coroutine or coroutine function.")
|
|
25
|
+
return task
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclasses.dataclass(slots=True)
|
|
29
|
+
class DelayedTask[Handler: CoroutineFunc[..., typing.Any]]:
|
|
30
|
+
handler: Handler
|
|
31
|
+
seconds: float | datetime.timedelta
|
|
32
|
+
repeat: bool = dataclasses.field(default=False, kw_only=True)
|
|
33
|
+
_cancelled: bool = dataclasses.field(default=False, init=False, repr=False)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def is_cancelled(self) -> bool:
|
|
37
|
+
return self._cancelled
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def delay(self) -> float:
|
|
41
|
+
return self.seconds if isinstance(self.seconds, int | float) else self.seconds.total_seconds()
|
|
42
|
+
|
|
43
|
+
async def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
|
|
44
|
+
while not self.is_cancelled:
|
|
45
|
+
await asyncio.sleep(self.delay)
|
|
46
|
+
if self.is_cancelled:
|
|
47
|
+
break
|
|
48
|
+
try:
|
|
49
|
+
await self.handler(*args, **kwargs)
|
|
50
|
+
finally:
|
|
51
|
+
if not self.repeat:
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
def cancel(self) -> None:
|
|
55
|
+
if not self._cancelled:
|
|
56
|
+
self._cancelled = True
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
|
|
60
|
+
class Lifespan:
|
|
61
|
+
startup_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
62
|
+
shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
63
|
+
|
|
64
|
+
def on_startup[**P, T](self, task: Task[P, T], /) -> Task[P, T]:
|
|
65
|
+
self.startup_tasks.append(to_coroutine_task(task))
|
|
66
|
+
return task
|
|
67
|
+
|
|
68
|
+
def on_shutdown[**P, T](self, task: Task[P, T], /) -> Task[P, T]:
|
|
69
|
+
self.shutdown_tasks.append(to_coroutine_task(task))
|
|
70
|
+
return task
|
|
71
|
+
|
|
72
|
+
def start(self) -> None:
|
|
73
|
+
run_tasks(self.startup_tasks)
|
|
74
|
+
|
|
75
|
+
def shutdown(self) -> None:
|
|
76
|
+
run_tasks(self.shutdown_tasks)
|
|
77
|
+
|
|
78
|
+
def __enter__(self) -> None:
|
|
79
|
+
self.start()
|
|
80
|
+
|
|
81
|
+
def __exit__(self) -> None:
|
|
82
|
+
self.shutdown()
|
|
83
|
+
|
|
84
|
+
async def __aenter__(self) -> None:
|
|
85
|
+
for task in self.startup_tasks:
|
|
86
|
+
await task
|
|
87
|
+
|
|
88
|
+
async def __aexit__(self, *args) -> None:
|
|
89
|
+
for task in self.shutdown_tasks:
|
|
90
|
+
await task
|
|
91
|
+
|
|
92
|
+
def __add__(self, other: typing.Self, /) -> typing.Self:
|
|
93
|
+
return self.__class__(
|
|
94
|
+
startup_tasks=self.startup_tasks + other.startup_tasks,
|
|
95
|
+
shutdown_tasks=self.shutdown_tasks + other.shutdown_tasks,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
__all__ = (
|
|
100
|
+
"CoroutineTask",
|
|
101
|
+
"DelayedTask",
|
|
102
|
+
"Lifespan",
|
|
103
|
+
"run_tasks",
|
|
104
|
+
"to_coroutine_task",
|
|
105
|
+
)
|
|
@@ -1,37 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
self.queue.remove(key)
|
|
34
|
-
return super().__delitem__(key)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
__all__ = ("LimitedDict",)
|
|
1
|
+
from collections import UserDict, deque
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LimitedDict[Key, Value](UserDict[Key, Value]):
|
|
5
|
+
def __init__(self, *, maxlimit: int = 1000) -> None:
|
|
6
|
+
super().__init__()
|
|
7
|
+
self.maxlimit = maxlimit
|
|
8
|
+
self.queue: deque[Key] = deque(maxlen=maxlimit)
|
|
9
|
+
|
|
10
|
+
def set(self, key: Key, value: Value, /) -> Value | None:
|
|
11
|
+
"""Set item in the dictionary.
|
|
12
|
+
Returns a value that was deleted when the limit in the dictionary
|
|
13
|
+
was reached, otherwise None.
|
|
14
|
+
"""
|
|
15
|
+
deleted_item = None
|
|
16
|
+
if len(self.queue) >= self.maxlimit:
|
|
17
|
+
deleted_item = self.pop(self.queue.popleft(), None)
|
|
18
|
+
if key not in self.queue:
|
|
19
|
+
self.queue.append(key)
|
|
20
|
+
super().__setitem__(key, value)
|
|
21
|
+
return deleted_item
|
|
22
|
+
|
|
23
|
+
def __setitem__(self, key: Key, value: Value, /) -> None:
|
|
24
|
+
self.set(key, value)
|
|
25
|
+
|
|
26
|
+
def __delitem__(self, key: Key) -> None:
|
|
27
|
+
if key in self.queue:
|
|
28
|
+
self.queue.remove(key)
|
|
29
|
+
return super().__delitem__(key)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__all__ = ("LimitedDict",)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .abc import ABCLoopWrapper
|
|
2
|
-
from .loop_wrapper import DelayedTask, Lifespan, LoopWrapper
|
|
3
|
-
|
|
4
|
-
__all__ = ("ABCLoopWrapper", "DelayedTask", "Lifespan", "LoopWrapper")
|
|
1
|
+
from .abc import ABCLoopWrapper
|
|
2
|
+
from .loop_wrapper import DelayedTask, Lifespan, LoopWrapper
|
|
3
|
+
|
|
4
|
+
__all__ = ("ABCLoopWrapper", "DelayedTask", "Lifespan", "LoopWrapper")
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
from abc import ABC, abstractmethod
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class ABCLoopWrapper(ABC):
|
|
6
|
-
@
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import typing
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ABCLoopWrapper(ABC):
|
|
6
|
+
@property
|
|
7
|
+
@abstractmethod
|
|
8
|
+
def is_running(self) -> bool:
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def add_task(self, task: typing.Any, /) -> None:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def run_event_loop(self) -> typing.NoReturn:
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ("ABCLoopWrapper",)
|
|
@@ -1,224 +1,169 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import contextlib
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from telegrinder.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
self.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
self
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
*,
|
|
171
|
-
days: int = 0,
|
|
172
|
-
hours: int = 0,
|
|
173
|
-
minutes: int = 0,
|
|
174
|
-
seconds: float | datetime.timedelta = 0,
|
|
175
|
-
) -> typing.Callable[..., typing.Any]:
|
|
176
|
-
if isinstance(seconds, datetime.timedelta):
|
|
177
|
-
seconds = seconds.total_seconds()
|
|
178
|
-
|
|
179
|
-
seconds += minutes * 60
|
|
180
|
-
seconds += hours * 60 * 60
|
|
181
|
-
seconds += days * 24 * 60 * 60
|
|
182
|
-
|
|
183
|
-
def decorator(func: CoroFunc) -> CoroFunc:
|
|
184
|
-
self.add_task(DelayedTask(func, seconds, repeat=False))
|
|
185
|
-
return func
|
|
186
|
-
|
|
187
|
-
return decorator
|
|
188
|
-
|
|
189
|
-
@typing.overload
|
|
190
|
-
def interval(self, *, seconds: datetime.timedelta) -> typing.Callable[..., typing.Any]: ...
|
|
191
|
-
|
|
192
|
-
@typing.overload
|
|
193
|
-
def interval(
|
|
194
|
-
self,
|
|
195
|
-
*,
|
|
196
|
-
days: int = 0,
|
|
197
|
-
hours: int = 0,
|
|
198
|
-
minutes: int = 0,
|
|
199
|
-
seconds: float = 0,
|
|
200
|
-
) -> typing.Callable[..., typing.Any]: ...
|
|
201
|
-
|
|
202
|
-
def interval(
|
|
203
|
-
self,
|
|
204
|
-
*,
|
|
205
|
-
days: int = 0,
|
|
206
|
-
hours: int = 0,
|
|
207
|
-
minutes: int = 0,
|
|
208
|
-
seconds: float | datetime.timedelta = 0,
|
|
209
|
-
) -> typing.Callable[..., typing.Any]:
|
|
210
|
-
if isinstance(seconds, datetime.timedelta):
|
|
211
|
-
seconds = seconds.total_seconds()
|
|
212
|
-
|
|
213
|
-
seconds += minutes * 60
|
|
214
|
-
seconds += hours * 60 * 60
|
|
215
|
-
seconds += days * 24 * 60 * 60
|
|
216
|
-
|
|
217
|
-
def decorator(func: CoroFunc) -> CoroFunc:
|
|
218
|
-
self.add_task(DelayedTask(func, seconds, repeat=True))
|
|
219
|
-
return func
|
|
220
|
-
|
|
221
|
-
return decorator
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
__all__ = ("DelayedTask", "Lifespan", "LoopWrapper", "to_coroutine_task")
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import datetime
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from telegrinder.modules import logger
|
|
7
|
+
from telegrinder.tools.lifespan import (
|
|
8
|
+
CoroutineFunc,
|
|
9
|
+
CoroutineTask,
|
|
10
|
+
DelayedTask,
|
|
11
|
+
Lifespan,
|
|
12
|
+
Task,
|
|
13
|
+
to_coroutine_task,
|
|
14
|
+
)
|
|
15
|
+
from telegrinder.tools.loop_wrapper.abc import ABCLoopWrapper
|
|
16
|
+
from telegrinder.tools.magic import cancel_future
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LoopWrapper(ABCLoopWrapper):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
tasks: list[CoroutineTask[typing.Any]] | None = None,
|
|
24
|
+
lifespan: Lifespan | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
self.tasks: list[CoroutineTask[typing.Any]] = tasks or []
|
|
27
|
+
self.lifespan = lifespan or Lifespan()
|
|
28
|
+
self._loop: asyncio.AbstractEventLoop | None = None
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def is_running(self) -> bool:
|
|
32
|
+
if self._loop is None:
|
|
33
|
+
return False
|
|
34
|
+
return self._loop.is_running()
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def loop(self) -> asyncio.AbstractEventLoop:
|
|
38
|
+
assert self._loop is not None, "Loop is not set."
|
|
39
|
+
return self._loop
|
|
40
|
+
|
|
41
|
+
def __repr__(self) -> str:
|
|
42
|
+
return "<{}: loop={!r} with tasks={!r}, lifespan={!r}>".format(
|
|
43
|
+
self.__class__.__name__,
|
|
44
|
+
self._loop,
|
|
45
|
+
self.tasks,
|
|
46
|
+
self.lifespan,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def _run_tasks(self) -> None:
|
|
50
|
+
async with asyncio.TaskGroup() as tg:
|
|
51
|
+
while self.tasks:
|
|
52
|
+
tg.create_task(self.tasks.pop(0))
|
|
53
|
+
|
|
54
|
+
def run_event_loop(self) -> typing.NoReturn: # type: ignore
|
|
55
|
+
if not self.tasks:
|
|
56
|
+
logger.warning("Run loop without tasks!")
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
self._loop = asyncio.get_running_loop()
|
|
60
|
+
except RuntimeError:
|
|
61
|
+
self._loop = asyncio.get_event_loop()
|
|
62
|
+
|
|
63
|
+
self.lifespan.start()
|
|
64
|
+
self._loop.create_task(self._run_tasks())
|
|
65
|
+
|
|
66
|
+
tasks = asyncio.all_tasks(self._loop)
|
|
67
|
+
try:
|
|
68
|
+
while tasks:
|
|
69
|
+
tasks_results, _ = self._loop.run_until_complete(
|
|
70
|
+
asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION),
|
|
71
|
+
)
|
|
72
|
+
for task_result in tasks_results:
|
|
73
|
+
try:
|
|
74
|
+
task_result.result()
|
|
75
|
+
except BaseException:
|
|
76
|
+
logger.exception("Traceback message below:")
|
|
77
|
+
tasks = asyncio.all_tasks(self._loop)
|
|
78
|
+
except KeyboardInterrupt:
|
|
79
|
+
print() # blank print for ^C
|
|
80
|
+
logger.info("Caught KeyboardInterrupt, cancellation...")
|
|
81
|
+
self.complete_tasks(tasks)
|
|
82
|
+
finally:
|
|
83
|
+
self.lifespan.shutdown()
|
|
84
|
+
if self._loop.is_running():
|
|
85
|
+
self._loop.close()
|
|
86
|
+
|
|
87
|
+
def add_task(self, task: Task[..., typing.Any], /) -> None:
|
|
88
|
+
task = to_coroutine_task(task)
|
|
89
|
+
|
|
90
|
+
if self._loop is not None and self._loop.is_running():
|
|
91
|
+
self._loop.create_task(task)
|
|
92
|
+
else:
|
|
93
|
+
self.tasks.append(task)
|
|
94
|
+
|
|
95
|
+
def complete_tasks(self, tasks: set[asyncio.Task[typing.Any]], /) -> None:
|
|
96
|
+
tasks = tasks | asyncio.all_tasks(self.loop)
|
|
97
|
+
with contextlib.suppress(asyncio.CancelledError, asyncio.InvalidStateError):
|
|
98
|
+
self.loop.run_until_complete(cancel_future(asyncio.gather(*tasks, return_exceptions=True)))
|
|
99
|
+
|
|
100
|
+
@typing.overload
|
|
101
|
+
def timer(self, *, seconds: datetime.timedelta) -> typing.Callable[..., typing.Any]: ...
|
|
102
|
+
|
|
103
|
+
@typing.overload
|
|
104
|
+
def timer(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
days: int = 0,
|
|
108
|
+
hours: int = 0,
|
|
109
|
+
minutes: int = 0,
|
|
110
|
+
seconds: float = 0,
|
|
111
|
+
) -> typing.Callable[..., typing.Any]: ...
|
|
112
|
+
|
|
113
|
+
def timer(
|
|
114
|
+
self,
|
|
115
|
+
*,
|
|
116
|
+
days: int = 0,
|
|
117
|
+
hours: int = 0,
|
|
118
|
+
minutes: int = 0,
|
|
119
|
+
seconds: float | datetime.timedelta = 0,
|
|
120
|
+
) -> typing.Callable[..., typing.Any]:
|
|
121
|
+
if isinstance(seconds, datetime.timedelta):
|
|
122
|
+
seconds = seconds.total_seconds()
|
|
123
|
+
|
|
124
|
+
seconds += minutes * 60
|
|
125
|
+
seconds += hours * 60 * 60
|
|
126
|
+
seconds += days * 24 * 60 * 60
|
|
127
|
+
|
|
128
|
+
def decorator[Func: CoroutineFunc[..., typing.Any]](func: Func) -> Func:
|
|
129
|
+
self.add_task(DelayedTask(func, seconds, repeat=False))
|
|
130
|
+
return func
|
|
131
|
+
|
|
132
|
+
return decorator
|
|
133
|
+
|
|
134
|
+
@typing.overload
|
|
135
|
+
def interval(self, *, seconds: datetime.timedelta) -> typing.Callable[..., typing.Any]: ...
|
|
136
|
+
|
|
137
|
+
@typing.overload
|
|
138
|
+
def interval(
|
|
139
|
+
self,
|
|
140
|
+
*,
|
|
141
|
+
days: int = 0,
|
|
142
|
+
hours: int = 0,
|
|
143
|
+
minutes: int = 0,
|
|
144
|
+
seconds: float = 0,
|
|
145
|
+
) -> typing.Callable[..., typing.Any]: ...
|
|
146
|
+
|
|
147
|
+
def interval(
|
|
148
|
+
self,
|
|
149
|
+
*,
|
|
150
|
+
days: int = 0,
|
|
151
|
+
hours: int = 0,
|
|
152
|
+
minutes: int = 0,
|
|
153
|
+
seconds: float | datetime.timedelta = 0,
|
|
154
|
+
) -> typing.Callable[..., typing.Any]:
|
|
155
|
+
if isinstance(seconds, datetime.timedelta):
|
|
156
|
+
seconds = seconds.total_seconds()
|
|
157
|
+
|
|
158
|
+
seconds += minutes * 60
|
|
159
|
+
seconds += hours * 60 * 60
|
|
160
|
+
seconds += days * 24 * 60 * 60
|
|
161
|
+
|
|
162
|
+
def decorator[Func: CoroutineFunc[..., typing.Any]](func: Func) -> Func:
|
|
163
|
+
self.add_task(DelayedTask(func, seconds, repeat=True))
|
|
164
|
+
return func
|
|
165
|
+
|
|
166
|
+
return decorator
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
__all__ = ("DelayedTask", "LoopWrapper", "to_coroutine_task")
|