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.

Files changed (192) hide show
  1. telegrinder/__init__.py +148 -149
  2. telegrinder/api/__init__.py +9 -8
  3. telegrinder/api/api.py +101 -93
  4. telegrinder/api/error.py +20 -16
  5. telegrinder/api/response.py +20 -20
  6. telegrinder/api/token.py +36 -36
  7. telegrinder/bot/__init__.py +72 -66
  8. telegrinder/bot/bot.py +83 -76
  9. telegrinder/bot/cute_types/__init__.py +19 -17
  10. telegrinder/bot/cute_types/base.py +184 -258
  11. telegrinder/bot/cute_types/callback_query.py +400 -385
  12. telegrinder/bot/cute_types/chat_join_request.py +62 -61
  13. telegrinder/bot/cute_types/chat_member_updated.py +157 -160
  14. telegrinder/bot/cute_types/inline_query.py +44 -43
  15. telegrinder/bot/cute_types/message.py +2590 -2637
  16. telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
  17. telegrinder/bot/cute_types/update.py +112 -104
  18. telegrinder/bot/cute_types/utils.py +62 -95
  19. telegrinder/bot/dispatch/__init__.py +59 -55
  20. telegrinder/bot/dispatch/abc.py +76 -77
  21. telegrinder/bot/dispatch/context.py +96 -98
  22. telegrinder/bot/dispatch/dispatch.py +254 -202
  23. telegrinder/bot/dispatch/handler/__init__.py +13 -13
  24. telegrinder/bot/dispatch/handler/abc.py +23 -24
  25. telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
  26. telegrinder/bot/dispatch/handler/base.py +57 -57
  27. telegrinder/bot/dispatch/handler/document_reply.py +44 -44
  28. telegrinder/bot/dispatch/handler/func.py +129 -135
  29. telegrinder/bot/dispatch/handler/media_group_reply.py +44 -43
  30. telegrinder/bot/dispatch/handler/message_reply.py +36 -36
  31. telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
  32. telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
  33. telegrinder/bot/dispatch/handler/video_reply.py +44 -44
  34. telegrinder/bot/dispatch/middleware/__init__.py +3 -3
  35. telegrinder/bot/dispatch/middleware/abc.py +97 -22
  36. telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
  37. telegrinder/bot/dispatch/process.py +151 -157
  38. telegrinder/bot/dispatch/return_manager/__init__.py +15 -13
  39. telegrinder/bot/dispatch/return_manager/abc.py +104 -108
  40. telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
  41. telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
  42. telegrinder/bot/dispatch/return_manager/message.py +36 -36
  43. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
  44. telegrinder/bot/dispatch/view/__init__.py +15 -13
  45. telegrinder/bot/dispatch/view/abc.py +45 -41
  46. telegrinder/bot/dispatch/view/base.py +231 -200
  47. telegrinder/bot/dispatch/view/box.py +140 -129
  48. telegrinder/bot/dispatch/view/callback_query.py +16 -17
  49. telegrinder/bot/dispatch/view/chat_join_request.py +11 -16
  50. telegrinder/bot/dispatch/view/chat_member.py +37 -39
  51. telegrinder/bot/dispatch/view/inline_query.py +16 -17
  52. telegrinder/bot/dispatch/view/message.py +43 -44
  53. telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
  54. telegrinder/bot/dispatch/view/raw.py +116 -114
  55. telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
  56. telegrinder/bot/dispatch/waiter_machine/actions.py +14 -13
  57. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
  58. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
  59. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +59 -57
  60. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
  61. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +20 -19
  62. telegrinder/bot/dispatch/waiter_machine/machine.py +251 -172
  63. telegrinder/bot/dispatch/waiter_machine/middleware.py +94 -89
  64. telegrinder/bot/dispatch/waiter_machine/short_state.py +57 -68
  65. telegrinder/bot/polling/__init__.py +4 -4
  66. telegrinder/bot/polling/abc.py +25 -25
  67. telegrinder/bot/polling/polling.py +139 -131
  68. telegrinder/bot/rules/__init__.py +85 -62
  69. telegrinder/bot/rules/abc.py +213 -206
  70. telegrinder/bot/rules/callback_data.py +122 -163
  71. telegrinder/bot/rules/chat_join.py +45 -43
  72. telegrinder/bot/rules/command.py +126 -126
  73. telegrinder/bot/rules/enum_text.py +33 -36
  74. telegrinder/bot/rules/func.py +28 -26
  75. telegrinder/bot/rules/fuzzy.py +24 -24
  76. telegrinder/bot/rules/id.py +24 -0
  77. telegrinder/bot/rules/inline.py +58 -56
  78. telegrinder/bot/rules/integer.py +21 -20
  79. telegrinder/bot/rules/is_from.py +127 -127
  80. telegrinder/bot/rules/logic.py +18 -0
  81. telegrinder/bot/rules/markup.py +42 -43
  82. telegrinder/bot/rules/mention.py +14 -14
  83. telegrinder/bot/rules/message.py +15 -17
  84. telegrinder/bot/rules/message_entities.py +33 -35
  85. telegrinder/bot/rules/node.py +33 -27
  86. telegrinder/bot/rules/payload.py +81 -0
  87. telegrinder/bot/rules/payment_invoice.py +29 -0
  88. telegrinder/bot/rules/regex.py +36 -37
  89. telegrinder/bot/rules/rule_enum.py +72 -72
  90. telegrinder/bot/rules/start.py +42 -42
  91. telegrinder/bot/rules/state.py +35 -37
  92. telegrinder/bot/rules/text.py +38 -33
  93. telegrinder/bot/rules/update.py +15 -15
  94. telegrinder/bot/scenario/__init__.py +5 -5
  95. telegrinder/bot/scenario/abc.py +17 -19
  96. telegrinder/bot/scenario/checkbox.py +174 -176
  97. telegrinder/bot/scenario/choice.py +48 -51
  98. telegrinder/client/__init__.py +12 -4
  99. telegrinder/client/abc.py +100 -75
  100. telegrinder/client/aiohttp.py +134 -130
  101. telegrinder/client/form_data.py +31 -0
  102. telegrinder/client/sonic.py +212 -0
  103. telegrinder/model.py +208 -315
  104. telegrinder/modules.py +239 -237
  105. telegrinder/msgspec_json.py +14 -14
  106. telegrinder/msgspec_utils.py +478 -410
  107. telegrinder/node/__init__.py +86 -25
  108. telegrinder/node/attachment.py +163 -87
  109. telegrinder/node/base.py +288 -160
  110. telegrinder/node/callback_query.py +54 -53
  111. telegrinder/node/command.py +34 -33
  112. telegrinder/node/composer.py +163 -198
  113. telegrinder/node/container.py +33 -27
  114. telegrinder/node/either.py +82 -0
  115. telegrinder/node/event.py +54 -65
  116. telegrinder/node/file.py +51 -0
  117. telegrinder/node/me.py +15 -16
  118. telegrinder/node/payload.py +78 -0
  119. telegrinder/node/polymorphic.py +67 -48
  120. telegrinder/node/rule.py +72 -76
  121. telegrinder/node/scope.py +36 -38
  122. telegrinder/node/source.py +87 -71
  123. telegrinder/node/text.py +53 -41
  124. telegrinder/node/tools/__init__.py +3 -3
  125. telegrinder/node/tools/generator.py +36 -40
  126. telegrinder/py.typed +0 -0
  127. telegrinder/rules.py +1 -62
  128. telegrinder/tools/__init__.py +152 -93
  129. telegrinder/tools/adapter/__init__.py +19 -0
  130. telegrinder/tools/adapter/abc.py +49 -0
  131. telegrinder/tools/adapter/dataclass.py +56 -0
  132. telegrinder/{bot/rules → tools}/adapter/errors.py +5 -5
  133. telegrinder/{bot/rules → tools}/adapter/event.py +63 -65
  134. telegrinder/{bot/rules → tools}/adapter/node.py +46 -48
  135. telegrinder/{bot/rules → tools}/adapter/raw_event.py +27 -27
  136. telegrinder/{bot/rules → tools}/adapter/raw_update.py +30 -30
  137. telegrinder/tools/buttons.py +106 -80
  138. telegrinder/tools/callback_data_serilization/__init__.py +5 -0
  139. telegrinder/tools/callback_data_serilization/abc.py +51 -0
  140. telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
  141. telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
  142. telegrinder/tools/error_handler/__init__.py +7 -7
  143. telegrinder/tools/error_handler/abc.py +30 -33
  144. telegrinder/tools/error_handler/error.py +9 -9
  145. telegrinder/tools/error_handler/error_handler.py +179 -193
  146. telegrinder/tools/formatting/__init__.py +83 -63
  147. telegrinder/tools/formatting/deep_links.py +541 -0
  148. telegrinder/tools/formatting/{html.py → html_formatter.py} +266 -294
  149. telegrinder/tools/formatting/spec_html_formats.py +71 -117
  150. telegrinder/tools/functional.py +8 -12
  151. telegrinder/tools/global_context/__init__.py +7 -7
  152. telegrinder/tools/global_context/abc.py +63 -63
  153. telegrinder/tools/global_context/global_context.py +387 -412
  154. telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
  155. telegrinder/tools/i18n/__init__.py +7 -7
  156. telegrinder/tools/i18n/abc.py +30 -30
  157. telegrinder/tools/i18n/middleware/__init__.py +3 -3
  158. telegrinder/tools/i18n/middleware/abc.py +22 -25
  159. telegrinder/tools/i18n/simple.py +43 -43
  160. telegrinder/tools/input_file_directory.py +30 -0
  161. telegrinder/tools/keyboard.py +128 -128
  162. telegrinder/tools/lifespan.py +105 -0
  163. telegrinder/tools/limited_dict.py +32 -37
  164. telegrinder/tools/loop_wrapper/__init__.py +4 -4
  165. telegrinder/tools/loop_wrapper/abc.py +20 -15
  166. telegrinder/tools/loop_wrapper/loop_wrapper.py +169 -224
  167. telegrinder/tools/magic.py +307 -157
  168. telegrinder/tools/parse_mode.py +6 -6
  169. telegrinder/tools/state_storage/__init__.py +4 -4
  170. telegrinder/tools/state_storage/abc.py +31 -35
  171. telegrinder/tools/state_storage/memory.py +25 -25
  172. telegrinder/tools/strings.py +13 -0
  173. telegrinder/types/__init__.py +268 -260
  174. telegrinder/types/enums.py +711 -701
  175. telegrinder/types/input_file.py +51 -0
  176. telegrinder/types/methods.py +5055 -4633
  177. telegrinder/types/objects.py +7058 -6950
  178. telegrinder/verification_utils.py +30 -32
  179. {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +22 -22
  180. telegrinder-0.4.0.dist-info/METADATA +144 -0
  181. telegrinder-0.4.0.dist-info/RECORD +182 -0
  182. {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
  183. telegrinder/bot/rules/adapter/__init__.py +0 -17
  184. telegrinder/bot/rules/adapter/abc.py +0 -31
  185. telegrinder/node/message.py +0 -14
  186. telegrinder/node/update.py +0 -15
  187. telegrinder/tools/formatting/links.py +0 -38
  188. telegrinder/tools/kb_set/__init__.py +0 -4
  189. telegrinder/tools/kb_set/base.py +0 -15
  190. telegrinder/tools/kb_set/yaml.py +0 -63
  191. telegrinder-0.3.4.dist-info/METADATA +0 -110
  192. 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 typing
2
- from collections import UserDict, deque
3
-
4
- KT = typing.TypeVar("KT")
5
- VT = typing.TypeVar("VT")
6
-
7
-
8
- class LimitedDict(UserDict[KT, VT]):
9
- def __init__(self, *, maxlimit: int = 1000) -> None:
10
- super().__init__()
11
- self.maxlimit = maxlimit
12
- self.queue: deque[KT] = deque(maxlen=maxlimit)
13
-
14
- def set(self, key: KT, value: VT, /) -> VT | None:
15
- """Set item in the dictionary.
16
- Returns a value that was deleted when the limit in the dictionary
17
- was reached, otherwise None.
18
- """
19
-
20
- deleted_item = None
21
- if len(self.queue) >= self.maxlimit:
22
- deleted_item = self.pop(self.queue.popleft(), None)
23
- if key not in self.queue:
24
- self.queue.append(key)
25
- super().__setitem__(key, value)
26
- return deleted_item
27
-
28
- def __setitem__(self, key: KT, value: VT, /) -> None:
29
- self.set(key, value)
30
-
31
- def __delitem__(self, key: KT) -> None:
32
- if key in self.queue:
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
- @abstractmethod
7
- def add_task(self, task: typing.Any) -> None:
8
- pass
9
-
10
- @abstractmethod
11
- def run_event_loop(self) -> None:
12
- pass
13
-
14
-
15
- __all__ = ("ABCLoopWrapper",)
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 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 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")