telegrinder 0.3.4__py3-none-any.whl → 0.3.4.post1__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 (165) hide show
  1. telegrinder/__init__.py +144 -144
  2. telegrinder/api/__init__.py +8 -8
  3. telegrinder/api/api.py +93 -93
  4. telegrinder/api/error.py +16 -16
  5. telegrinder/api/response.py +20 -20
  6. telegrinder/api/token.py +36 -36
  7. telegrinder/bot/__init__.py +66 -66
  8. telegrinder/bot/bot.py +76 -76
  9. telegrinder/bot/cute_types/__init__.py +17 -17
  10. telegrinder/bot/cute_types/base.py +258 -258
  11. telegrinder/bot/cute_types/callback_query.py +385 -385
  12. telegrinder/bot/cute_types/chat_join_request.py +61 -61
  13. telegrinder/bot/cute_types/chat_member_updated.py +160 -160
  14. telegrinder/bot/cute_types/inline_query.py +43 -43
  15. telegrinder/bot/cute_types/message.py +2637 -2637
  16. telegrinder/bot/cute_types/update.py +104 -104
  17. telegrinder/bot/cute_types/utils.py +95 -95
  18. telegrinder/bot/dispatch/__init__.py +55 -55
  19. telegrinder/bot/dispatch/abc.py +77 -77
  20. telegrinder/bot/dispatch/context.py +98 -98
  21. telegrinder/bot/dispatch/dispatch.py +202 -202
  22. telegrinder/bot/dispatch/handler/__init__.py +13 -13
  23. telegrinder/bot/dispatch/handler/abc.py +24 -24
  24. telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
  25. telegrinder/bot/dispatch/handler/base.py +57 -57
  26. telegrinder/bot/dispatch/handler/document_reply.py +44 -44
  27. telegrinder/bot/dispatch/handler/func.py +135 -135
  28. telegrinder/bot/dispatch/handler/media_group_reply.py +43 -43
  29. telegrinder/bot/dispatch/handler/message_reply.py +36 -36
  30. telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
  31. telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
  32. telegrinder/bot/dispatch/handler/video_reply.py +44 -44
  33. telegrinder/bot/dispatch/middleware/__init__.py +3 -3
  34. telegrinder/bot/dispatch/middleware/abc.py +22 -22
  35. telegrinder/bot/dispatch/process.py +157 -157
  36. telegrinder/bot/dispatch/return_manager/__init__.py +13 -13
  37. telegrinder/bot/dispatch/return_manager/abc.py +108 -108
  38. telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
  39. telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
  40. telegrinder/bot/dispatch/return_manager/message.py +36 -36
  41. telegrinder/bot/dispatch/view/__init__.py +13 -13
  42. telegrinder/bot/dispatch/view/abc.py +41 -41
  43. telegrinder/bot/dispatch/view/base.py +200 -200
  44. telegrinder/bot/dispatch/view/box.py +129 -129
  45. telegrinder/bot/dispatch/view/callback_query.py +17 -17
  46. telegrinder/bot/dispatch/view/chat_join_request.py +16 -16
  47. telegrinder/bot/dispatch/view/chat_member.py +39 -39
  48. telegrinder/bot/dispatch/view/inline_query.py +17 -17
  49. telegrinder/bot/dispatch/view/message.py +44 -44
  50. telegrinder/bot/dispatch/view/raw.py +114 -114
  51. telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
  52. telegrinder/bot/dispatch/waiter_machine/actions.py +13 -13
  53. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
  54. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
  55. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +57 -57
  56. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
  57. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
  58. telegrinder/bot/dispatch/waiter_machine/machine.py +172 -172
  59. telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
  60. telegrinder/bot/dispatch/waiter_machine/short_state.py +68 -68
  61. telegrinder/bot/polling/__init__.py +4 -4
  62. telegrinder/bot/polling/abc.py +25 -25
  63. telegrinder/bot/polling/polling.py +131 -131
  64. telegrinder/bot/rules/__init__.py +62 -62
  65. telegrinder/bot/rules/abc.py +206 -206
  66. telegrinder/bot/rules/adapter/__init__.py +17 -17
  67. telegrinder/bot/rules/adapter/abc.py +31 -31
  68. telegrinder/bot/rules/adapter/errors.py +5 -5
  69. telegrinder/bot/rules/adapter/event.py +65 -65
  70. telegrinder/bot/rules/adapter/node.py +48 -48
  71. telegrinder/bot/rules/adapter/raw_event.py +27 -27
  72. telegrinder/bot/rules/adapter/raw_update.py +30 -30
  73. telegrinder/bot/rules/callback_data.py +163 -163
  74. telegrinder/bot/rules/chat_join.py +43 -43
  75. telegrinder/bot/rules/command.py +126 -126
  76. telegrinder/bot/rules/enum_text.py +36 -36
  77. telegrinder/bot/rules/func.py +26 -26
  78. telegrinder/bot/rules/fuzzy.py +24 -24
  79. telegrinder/bot/rules/inline.py +56 -56
  80. telegrinder/bot/rules/integer.py +20 -20
  81. telegrinder/bot/rules/is_from.py +127 -127
  82. telegrinder/bot/rules/markup.py +43 -43
  83. telegrinder/bot/rules/mention.py +14 -14
  84. telegrinder/bot/rules/message.py +17 -17
  85. telegrinder/bot/rules/message_entities.py +35 -35
  86. telegrinder/bot/rules/node.py +27 -27
  87. telegrinder/bot/rules/regex.py +37 -37
  88. telegrinder/bot/rules/rule_enum.py +72 -72
  89. telegrinder/bot/rules/start.py +42 -42
  90. telegrinder/bot/rules/state.py +37 -37
  91. telegrinder/bot/rules/text.py +33 -33
  92. telegrinder/bot/rules/update.py +15 -15
  93. telegrinder/bot/scenario/__init__.py +5 -5
  94. telegrinder/bot/scenario/abc.py +19 -19
  95. telegrinder/bot/scenario/checkbox.py +176 -176
  96. telegrinder/bot/scenario/choice.py +51 -51
  97. telegrinder/client/__init__.py +4 -4
  98. telegrinder/client/abc.py +75 -75
  99. telegrinder/client/aiohttp.py +130 -130
  100. telegrinder/model.py +313 -313
  101. telegrinder/modules.py +237 -237
  102. telegrinder/msgspec_json.py +14 -14
  103. telegrinder/msgspec_utils.py +410 -410
  104. telegrinder/node/__init__.py +20 -20
  105. telegrinder/node/attachment.py +87 -87
  106. telegrinder/node/base.py +157 -157
  107. telegrinder/node/callback_query.py +53 -53
  108. telegrinder/node/command.py +33 -33
  109. telegrinder/node/composer.py +198 -198
  110. telegrinder/node/container.py +27 -27
  111. telegrinder/node/event.py +65 -65
  112. telegrinder/node/me.py +16 -16
  113. telegrinder/node/message.py +14 -14
  114. telegrinder/node/polymorphic.py +48 -48
  115. telegrinder/node/rule.py +76 -76
  116. telegrinder/node/scope.py +38 -38
  117. telegrinder/node/source.py +71 -71
  118. telegrinder/node/text.py +41 -41
  119. telegrinder/node/tools/__init__.py +3 -3
  120. telegrinder/node/tools/generator.py +40 -40
  121. telegrinder/node/update.py +15 -15
  122. telegrinder/rules.py +5 -5
  123. telegrinder/tools/__init__.py +74 -74
  124. telegrinder/tools/buttons.py +79 -79
  125. telegrinder/tools/error_handler/__init__.py +7 -7
  126. telegrinder/tools/error_handler/abc.py +33 -33
  127. telegrinder/tools/error_handler/error.py +9 -9
  128. telegrinder/tools/error_handler/error_handler.py +193 -193
  129. telegrinder/tools/formatting/__init__.py +46 -46
  130. telegrinder/tools/formatting/html.py +283 -283
  131. telegrinder/tools/formatting/links.py +33 -33
  132. telegrinder/tools/formatting/spec_html_formats.py +111 -111
  133. telegrinder/tools/functional.py +12 -12
  134. telegrinder/tools/global_context/__init__.py +7 -7
  135. telegrinder/tools/global_context/abc.py +63 -63
  136. telegrinder/tools/global_context/global_context.py +412 -412
  137. telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
  138. telegrinder/tools/i18n/__init__.py +7 -7
  139. telegrinder/tools/i18n/abc.py +30 -30
  140. telegrinder/tools/i18n/middleware/__init__.py +3 -3
  141. telegrinder/tools/i18n/middleware/abc.py +25 -25
  142. telegrinder/tools/i18n/simple.py +43 -43
  143. telegrinder/tools/kb_set/__init__.py +4 -4
  144. telegrinder/tools/kb_set/base.py +15 -15
  145. telegrinder/tools/kb_set/yaml.py +63 -63
  146. telegrinder/tools/keyboard.py +128 -128
  147. telegrinder/tools/limited_dict.py +37 -37
  148. telegrinder/tools/loop_wrapper/__init__.py +4 -4
  149. telegrinder/tools/loop_wrapper/abc.py +15 -15
  150. telegrinder/tools/loop_wrapper/loop_wrapper.py +224 -224
  151. telegrinder/tools/magic.py +157 -157
  152. telegrinder/tools/parse_mode.py +6 -6
  153. telegrinder/tools/state_storage/__init__.py +4 -4
  154. telegrinder/tools/state_storage/abc.py +35 -35
  155. telegrinder/tools/state_storage/memory.py +25 -25
  156. telegrinder/types/__init__.py +260 -260
  157. telegrinder/types/enums.py +701 -701
  158. telegrinder/types/methods.py +4633 -4633
  159. telegrinder/types/objects.py +6950 -6950
  160. telegrinder/verification_utils.py +32 -32
  161. {telegrinder-0.3.4.dist-info → telegrinder-0.3.4.post1.dist-info}/LICENSE +22 -22
  162. {telegrinder-0.3.4.dist-info → telegrinder-0.3.4.post1.dist-info}/METADATA +1 -1
  163. telegrinder-0.3.4.post1.dist-info/RECORD +165 -0
  164. telegrinder-0.3.4.dist-info/RECORD +0 -165
  165. {telegrinder-0.3.4.dist-info → telegrinder-0.3.4.post1.dist-info}/WHEEL +0 -0
@@ -1,224 +1,224 @@
1
- import asyncio
2
- import contextlib
3
- import dataclasses
4
- import datetime
5
- import typing
6
-
7
- from telegrinder.modules import logger
8
- from telegrinder.tools.loop_wrapper.abc import ABCLoopWrapper
9
-
10
- T = typing.TypeVar("T")
11
- P = typing.ParamSpec("P")
12
- CoroFunc = typing.TypeVar("CoroFunc", bound="CoroutineFunc[..., typing.Any]")
13
-
14
- CoroutineTask: typing.TypeAlias = typing.Coroutine[typing.Any, typing.Any, T]
15
- CoroutineFunc: typing.TypeAlias = typing.Callable[P, CoroutineTask[T]]
16
- Task: typing.TypeAlias = (
17
- "CoroutineFunc[P, T] | CoroutineTask[T] | DelayedTask[typing.Callable[P, CoroutineTask[T]]]"
18
- )
19
-
20
-
21
- def run_tasks(
22
- tasks: list[CoroutineTask[typing.Any]],
23
- loop: asyncio.AbstractEventLoop,
24
- ) -> None:
25
- while tasks:
26
- loop.run_until_complete(tasks.pop(0))
27
-
28
-
29
- def to_coroutine_task(task: Task) -> CoroutineTask[typing.Any]:
30
- if asyncio.iscoroutinefunction(task) or isinstance(task, DelayedTask):
31
- task = task()
32
- elif not asyncio.iscoroutine(task):
33
- raise TypeError("Task should be coroutine or coroutine function.")
34
- return task
35
-
36
-
37
- @dataclasses.dataclass(slots=True)
38
- class DelayedTask(typing.Generic[CoroFunc]):
39
- handler: CoroFunc
40
- seconds: float
41
- repeat: bool = dataclasses.field(default=False, kw_only=True)
42
- _cancelled: bool = dataclasses.field(default=False, init=False, repr=False)
43
-
44
- @property
45
- def is_cancelled(self) -> bool:
46
- return self._cancelled
47
-
48
- async def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
49
- while not self.is_cancelled:
50
- await asyncio.sleep(self.seconds)
51
- if self.is_cancelled:
52
- break
53
- try:
54
- await self.handler(*args, **kwargs)
55
- finally:
56
- if not self.repeat:
57
- break
58
-
59
- def cancel(self) -> None:
60
- if not self._cancelled:
61
- self._cancelled = True
62
-
63
-
64
- @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
65
- class Lifespan:
66
- startup_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
67
- shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
68
-
69
- def on_startup(self, task: Task, /) -> Task:
70
- self.startup_tasks.append(to_coroutine_task(task))
71
- return task
72
-
73
- def on_shutdown(self, task: Task, /) -> Task:
74
- self.shutdown_tasks.append(to_coroutine_task(task))
75
- return task
76
-
77
- def start(self, loop: asyncio.AbstractEventLoop, /) -> None:
78
- run_tasks(self.startup_tasks, loop)
79
-
80
- def shutdown(self, loop: asyncio.AbstractEventLoop, /) -> None:
81
- run_tasks(self.shutdown_tasks, loop)
82
-
83
-
84
- class LoopWrapper(ABCLoopWrapper):
85
- def __init__(
86
- self,
87
- *,
88
- tasks: list[CoroutineTask[typing.Any]] | None = None,
89
- lifespan: Lifespan | None = None,
90
- event_loop: asyncio.AbstractEventLoop | None = None,
91
- ) -> None:
92
- self.tasks: list[CoroutineTask[typing.Any]] = tasks or []
93
- self.lifespan = lifespan or Lifespan()
94
- self._loop = event_loop
95
-
96
- @property
97
- def loop(self) -> asyncio.AbstractEventLoop:
98
- assert self._loop is not None
99
- return self._loop
100
-
101
- def __repr__(self) -> str:
102
- return "<{}: loop={!r} with tasks={!r}, lifespan={!r}>".format(
103
- self.__class__.__name__,
104
- self._loop,
105
- self.tasks,
106
- self.lifespan,
107
- )
108
-
109
- def run_event_loop(self) -> None:
110
- if not self.tasks:
111
- logger.warning("You run loop with 0 tasks!")
112
-
113
- self._loop = asyncio.new_event_loop() if self._loop is None else self._loop
114
- self.lifespan.start(self._loop)
115
-
116
- while self.tasks:
117
- self._loop.create_task(self.tasks.pop(0))
118
-
119
- tasks = asyncio.all_tasks(self._loop)
120
- try:
121
- while tasks:
122
- tasks_results, _ = self._loop.run_until_complete(
123
- asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION),
124
- )
125
- for task_result in tasks_results:
126
- try:
127
- task_result.result()
128
- except BaseException as ex:
129
- logger.exception(ex)
130
- tasks = asyncio.all_tasks(self._loop)
131
- except KeyboardInterrupt:
132
- print() # blank print for ^C
133
- logger.info("Caught KeyboardInterrupt, cancellation...")
134
- self.complete_tasks(tasks)
135
- finally:
136
- self.lifespan.shutdown(self._loop)
137
- if self._loop.is_running():
138
- self._loop.close()
139
-
140
- def add_task(self, task: Task) -> None:
141
- task = to_coroutine_task(task)
142
-
143
- if self._loop is not None and self._loop.is_running():
144
- self._loop.create_task(task)
145
- else:
146
- self.tasks.append(task)
147
-
148
- def complete_tasks(self, tasks: set[asyncio.Task[typing.Any]]) -> None:
149
- tasks = tasks | asyncio.all_tasks(self.loop)
150
- task_to_cancel = asyncio.gather(*tasks, return_exceptions=True)
151
- task_to_cancel.cancel()
152
- with contextlib.suppress(asyncio.CancelledError):
153
- self.loop.run_until_complete(task_to_cancel)
154
-
155
- @typing.overload
156
- def timer(self, *, seconds: datetime.timedelta) -> typing.Callable[..., typing.Any]: ...
157
-
158
- @typing.overload
159
- def timer(
160
- self,
161
- *,
162
- days: int = 0,
163
- hours: int = 0,
164
- minutes: int = 0,
165
- seconds: float = 0,
166
- ) -> typing.Callable[..., typing.Any]: ...
167
-
168
- def timer(
169
- self,
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 dataclasses
4
+ import datetime
5
+ import typing
6
+
7
+ from telegrinder.modules import logger
8
+ from telegrinder.tools.loop_wrapper.abc import ABCLoopWrapper
9
+
10
+ T = typing.TypeVar("T")
11
+ P = typing.ParamSpec("P")
12
+ CoroFunc = typing.TypeVar("CoroFunc", bound="CoroutineFunc[..., typing.Any]")
13
+
14
+ CoroutineTask: typing.TypeAlias = typing.Coroutine[typing.Any, typing.Any, T]
15
+ CoroutineFunc: typing.TypeAlias = typing.Callable[P, CoroutineTask[T]]
16
+ Task: typing.TypeAlias = (
17
+ "CoroutineFunc[P, T] | CoroutineTask[T] | DelayedTask[typing.Callable[P, CoroutineTask[T]]]"
18
+ )
19
+
20
+
21
+ def run_tasks(
22
+ tasks: list[CoroutineTask[typing.Any]],
23
+ loop: asyncio.AbstractEventLoop,
24
+ ) -> None:
25
+ while tasks:
26
+ loop.run_until_complete(tasks.pop(0))
27
+
28
+
29
+ def to_coroutine_task(task: Task) -> CoroutineTask[typing.Any]:
30
+ if asyncio.iscoroutinefunction(task) or isinstance(task, DelayedTask):
31
+ task = task()
32
+ elif not asyncio.iscoroutine(task):
33
+ raise TypeError("Task should be coroutine or coroutine function.")
34
+ return task
35
+
36
+
37
+ @dataclasses.dataclass(slots=True)
38
+ class DelayedTask(typing.Generic[CoroFunc]):
39
+ handler: CoroFunc
40
+ seconds: float
41
+ repeat: bool = dataclasses.field(default=False, kw_only=True)
42
+ _cancelled: bool = dataclasses.field(default=False, init=False, repr=False)
43
+
44
+ @property
45
+ def is_cancelled(self) -> bool:
46
+ return self._cancelled
47
+
48
+ async def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
49
+ while not self.is_cancelled:
50
+ await asyncio.sleep(self.seconds)
51
+ if self.is_cancelled:
52
+ break
53
+ try:
54
+ await self.handler(*args, **kwargs)
55
+ finally:
56
+ if not self.repeat:
57
+ break
58
+
59
+ def cancel(self) -> None:
60
+ if not self._cancelled:
61
+ self._cancelled = True
62
+
63
+
64
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
65
+ class Lifespan:
66
+ startup_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
67
+ shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
68
+
69
+ def on_startup(self, task: Task, /) -> Task:
70
+ self.startup_tasks.append(to_coroutine_task(task))
71
+ return task
72
+
73
+ def on_shutdown(self, task: Task, /) -> Task:
74
+ self.shutdown_tasks.append(to_coroutine_task(task))
75
+ return task
76
+
77
+ def start(self, loop: asyncio.AbstractEventLoop, /) -> None:
78
+ run_tasks(self.startup_tasks, loop)
79
+
80
+ def shutdown(self, loop: asyncio.AbstractEventLoop, /) -> None:
81
+ run_tasks(self.shutdown_tasks, loop)
82
+
83
+
84
+ class LoopWrapper(ABCLoopWrapper):
85
+ def __init__(
86
+ self,
87
+ *,
88
+ tasks: list[CoroutineTask[typing.Any]] | None = None,
89
+ lifespan: Lifespan | None = None,
90
+ event_loop: asyncio.AbstractEventLoop | None = None,
91
+ ) -> None:
92
+ self.tasks: list[CoroutineTask[typing.Any]] = tasks or []
93
+ self.lifespan = lifespan or Lifespan()
94
+ self._loop = event_loop
95
+
96
+ @property
97
+ def loop(self) -> asyncio.AbstractEventLoop:
98
+ assert self._loop is not None
99
+ return self._loop
100
+
101
+ def __repr__(self) -> str:
102
+ return "<{}: loop={!r} with tasks={!r}, lifespan={!r}>".format(
103
+ self.__class__.__name__,
104
+ self._loop,
105
+ self.tasks,
106
+ self.lifespan,
107
+ )
108
+
109
+ def run_event_loop(self) -> None:
110
+ if not self.tasks:
111
+ logger.warning("You run loop with 0 tasks!")
112
+
113
+ self._loop = asyncio.new_event_loop() if self._loop is None else self._loop
114
+ self.lifespan.start(self._loop)
115
+
116
+ while self.tasks:
117
+ self._loop.create_task(self.tasks.pop(0))
118
+
119
+ tasks = asyncio.all_tasks(self._loop)
120
+ try:
121
+ while tasks:
122
+ tasks_results, _ = self._loop.run_until_complete(
123
+ asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION),
124
+ )
125
+ for task_result in tasks_results:
126
+ try:
127
+ task_result.result()
128
+ except BaseException as ex:
129
+ logger.exception(ex)
130
+ tasks = asyncio.all_tasks(self._loop)
131
+ except KeyboardInterrupt:
132
+ print() # blank print for ^C
133
+ logger.info("Caught KeyboardInterrupt, cancellation...")
134
+ self.complete_tasks(tasks)
135
+ finally:
136
+ self.lifespan.shutdown(self._loop)
137
+ if self._loop.is_running():
138
+ self._loop.close()
139
+
140
+ def add_task(self, task: Task) -> None:
141
+ task = to_coroutine_task(task)
142
+
143
+ if self._loop is not None and self._loop.is_running():
144
+ self._loop.create_task(task)
145
+ else:
146
+ self.tasks.append(task)
147
+
148
+ def complete_tasks(self, tasks: set[asyncio.Task[typing.Any]]) -> None:
149
+ tasks = tasks | asyncio.all_tasks(self.loop)
150
+ task_to_cancel = asyncio.gather(*tasks, return_exceptions=True)
151
+ task_to_cancel.cancel()
152
+ with contextlib.suppress(asyncio.CancelledError):
153
+ self.loop.run_until_complete(task_to_cancel)
154
+
155
+ @typing.overload
156
+ def timer(self, *, seconds: datetime.timedelta) -> typing.Callable[..., typing.Any]: ...
157
+
158
+ @typing.overload
159
+ def timer(
160
+ self,
161
+ *,
162
+ days: int = 0,
163
+ hours: int = 0,
164
+ minutes: int = 0,
165
+ seconds: float = 0,
166
+ ) -> typing.Callable[..., typing.Any]: ...
167
+
168
+ def timer(
169
+ self,
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")