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.

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 +2621 -2590
  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 +64 -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 +7846 -7058
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.0.dist-info/METADATA +162 -0
  198. telegrinder-0.5.0.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.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.0.dist-info}/WHEEL +0 -0
@@ -1,22 +0,0 @@
1
- from abc import abstractmethod
2
-
3
- from telegrinder.bot.cute_types.base import BaseCute
4
- from telegrinder.bot.dispatch.context import Context
5
- from telegrinder.bot.dispatch.middleware import ABCMiddleware
6
- from telegrinder.tools.i18n import ABCI18n, I18nEnum
7
-
8
-
9
- class ABCTranslatorMiddleware[Event: BaseCute](ABCMiddleware[Event]):
10
- def __init__(self, i18n: ABCI18n) -> None:
11
- self.i18n = i18n
12
-
13
- @abstractmethod
14
- async def get_locale(self, event: Event) -> str:
15
- pass
16
-
17
- async def pre(self, event: Event, ctx: Context) -> bool:
18
- ctx[I18nEnum.I18N] = self.i18n.get_translator_by_locale(await self.get_locale(event))
19
- return True
20
-
21
-
22
- __all__ = ("ABCTranslatorMiddleware",)
@@ -1,43 +0,0 @@
1
- """This is an implementation of GNU gettext (pyBabel)."""
2
-
3
- import gettext
4
- import os
5
-
6
- from telegrinder.tools.i18n.abc import ABCI18n, ABCTranslator
7
-
8
-
9
- class SimpleTranslator(ABCTranslator):
10
- def __init__(self, locale: str, g: gettext.GNUTranslations) -> None:
11
- self.g = g
12
- super().__init__(locale)
13
-
14
- def get(self, __key: str, *args: object, **kwargs: object) -> str:
15
- return self.g.gettext(__key).format(*args, **kwargs)
16
-
17
-
18
- class SimpleI18n(ABCI18n):
19
- def __init__(self, folder: str, domain: str, default_locale: str) -> None:
20
- self.folder = folder
21
- self.domain = domain
22
- self.default_locale = default_locale
23
- self.translators = self._load_translators()
24
-
25
- def _load_translators(self) -> dict[str, gettext.GNUTranslations]:
26
- result = {}
27
- for name in os.listdir(self.folder):
28
- if not os.path.isdir(os.path.join(self.folder, name)):
29
- continue
30
-
31
- mo_path = os.path.join(self.folder, name, "LC_MESSAGES", f"{self.domain}.mo")
32
- if os.path.exists(mo_path):
33
- with open(mo_path, "rb") as f:
34
- result[name] = gettext.GNUTranslations(f)
35
- elif os.path.exists(mo_path[:-2] + "po"):
36
- raise FileNotFoundError(".po files should be compiled first")
37
- return result
38
-
39
- def get_translator_by_locale(self, locale: str) -> "SimpleTranslator":
40
- return SimpleTranslator(locale, self.translators.get(locale, self.translators[self.default_locale]))
41
-
42
-
43
- __all__ = ("SimpleI18n", "SimpleTranslator")
@@ -1,132 +0,0 @@
1
- import dataclasses
2
- import typing
3
- from abc import ABC, abstractmethod
4
-
5
- from fntypes.option import Some
6
-
7
- from telegrinder.model import is_none
8
- from telegrinder.types.objects import (
9
- InlineKeyboardMarkup,
10
- ReplyKeyboardMarkup,
11
- ReplyKeyboardRemove,
12
- )
13
-
14
- from .buttons import BaseButton, Button, InlineButton, RowButtons
15
-
16
- type DictStrAny = dict[str, typing.Any]
17
- type AnyMarkup = InlineKeyboardMarkup | ReplyKeyboardMarkup
18
-
19
-
20
- def copy_keyboard(keyboard: list[list[DictStrAny]]) -> list[list[DictStrAny]]:
21
- return [row.copy() for row in keyboard]
22
-
23
-
24
- @dataclasses.dataclass(kw_only=True, slots=True)
25
- class KeyboardModel:
26
- resize_keyboard: bool
27
- one_time_keyboard: bool
28
- selective: bool
29
- is_persistent: bool
30
- keyboard: list[list[DictStrAny]]
31
-
32
-
33
- class ABCMarkup[KeyboardButton: BaseButton](ABC):
34
- BUTTON: type[KeyboardButton]
35
- keyboard: list[list[DictStrAny]]
36
-
37
- @abstractmethod
38
- def dict(self) -> DictStrAny:
39
- pass
40
-
41
- @abstractmethod
42
- def get_markup(self) -> AnyMarkup:
43
- pass
44
-
45
- @classmethod
46
- def get_empty_markup(cls) -> AnyMarkup:
47
- return cls().get_markup()
48
-
49
- def add(self, row_or_button: RowButtons[KeyboardButton] | KeyboardButton, /) -> typing.Self:
50
- if not self.keyboard:
51
- self.row()
52
-
53
- if isinstance(row_or_button, RowButtons):
54
- self.keyboard[-1].extend(row_or_button.get_data())
55
- if row_or_button.auto_row:
56
- self.row()
57
- return self
58
-
59
- self.keyboard[-1].append(row_or_button.get_data())
60
- return self
61
-
62
- def row(self) -> typing.Self:
63
- if len(self.keyboard) and not len(self.keyboard[-1]):
64
- return self
65
-
66
- self.keyboard.append([])
67
- return self
68
-
69
- def format(self, **format_data: str) -> typing.Self:
70
- copy_keyboard = self.__class__()
71
- for row in self.keyboard:
72
- for button in row:
73
- copy_button = button.copy()
74
- copy_button["text"] = copy_button["text"].format(**format_data)
75
- copy_keyboard.add(self.BUTTON(**copy_button))
76
- copy_keyboard.row()
77
- return copy_keyboard
78
-
79
- def merge(self, other: typing.Self) -> typing.Self:
80
- self.keyboard.extend(copy_keyboard(other.keyboard))
81
- return self
82
-
83
-
84
- @dataclasses.dataclass(kw_only=True, slots=True)
85
- class Keyboard(ABCMarkup[Button], KeyboardModel):
86
- BUTTON = Button
87
-
88
- keyboard: list[list[DictStrAny]] = dataclasses.field(
89
- default_factory=lambda: [[]],
90
- init=False,
91
- )
92
- resize_keyboard: bool = dataclasses.field(default=True)
93
- one_time_keyboard: bool = dataclasses.field(default=False)
94
- selective: bool = dataclasses.field(default=False)
95
- is_persistent: bool = dataclasses.field(default=False)
96
-
97
- def dict(self) -> DictStrAny:
98
- self.keyboard = [row for row in self.keyboard if row]
99
- return {
100
- k: v.unwrap() if v and isinstance(v, Some) else v
101
- for k, v in dataclasses.asdict(self).items()
102
- if not is_none(v)
103
- }
104
-
105
- def get_markup(self) -> ReplyKeyboardMarkup:
106
- return ReplyKeyboardMarkup(**self.dict())
107
-
108
- def keyboard_remove(self, *, selective: bool = False) -> ReplyKeyboardRemove:
109
- return ReplyKeyboardRemove(remove_keyboard=True, selective=selective)
110
-
111
-
112
- class InlineKeyboard(ABCMarkup[InlineButton]):
113
- BUTTON = InlineButton
114
-
115
- def __init__(self) -> None:
116
- self.keyboard = [[]]
117
-
118
- def dict(self) -> DictStrAny:
119
- self.keyboard = [row for row in self.keyboard if row]
120
- return dict(inline_keyboard=self.keyboard)
121
-
122
- def get_markup(self) -> InlineKeyboardMarkup:
123
- return InlineKeyboardMarkup(**self.dict())
124
-
125
-
126
- __all__ = (
127
- "ABCMarkup",
128
- "InlineKeyboard",
129
- "Keyboard",
130
- "KeyboardModel",
131
- "copy_keyboard",
132
- )
@@ -1,4 +0,0 @@
1
- from .abc import ABCLoopWrapper
2
- from .loop_wrapper import DelayedTask, Lifespan, LoopWrapper
3
-
4
- __all__ = ("ABCLoopWrapper", "DelayedTask", "Lifespan", "LoopWrapper")
@@ -1,20 +0,0 @@
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,169 +0,0 @@
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")