telegrinder 0.4.2__py3-none-any.whl → 0.5.1__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 +98 -67
  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 +68 -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 +1782 -994
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.1.dist-info/METADATA +162 -0
  198. telegrinder-0.5.1.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.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.1.dist-info}/WHEEL +0 -0
@@ -1,116 +1,10 @@
1
- import typing
2
-
3
- from telegrinder.api.api import API
4
- from telegrinder.bot.cute_types.base import BaseCute
5
- from telegrinder.bot.cute_types.update import UpdateCute
6
- from telegrinder.bot.dispatch.context import Context
7
- from telegrinder.bot.dispatch.handler.func import Func, FuncHandler
8
- from telegrinder.bot.dispatch.process import process_inner
9
1
  from telegrinder.bot.dispatch.view.abc import ABCEventRawView
10
2
  from telegrinder.bot.dispatch.view.base import BaseView
11
- from telegrinder.bot.rules.abc import ABCRule
12
- from telegrinder.tools.error_handler.error_handler import ABCErrorHandler, ErrorHandler
13
- from telegrinder.types.enums import UpdateType
14
- from telegrinder.types.objects import Update
15
-
16
-
17
- class RawEventView(ABCEventRawView[UpdateCute], BaseView[UpdateCute]):
18
- @typing.overload
19
- def __call__[**P, R](
20
- self,
21
- *rules: ABCRule,
22
- update_type: UpdateType,
23
- final: bool = True,
24
- ) -> typing.Callable[
25
- [Func[P, R]],
26
- FuncHandler[BaseCute[typing.Any], Func[P, R], ErrorHandler[BaseCute[typing.Any]]],
27
- ]: ...
28
-
29
- @typing.overload
30
- def __call__[**P, Dataclass, R](
31
- self,
32
- *rules: ABCRule,
33
- dataclass: type[Dataclass],
34
- final: bool = True,
35
- ) -> typing.Callable[[Func[P, R]], FuncHandler[Dataclass, Func[P, R], ErrorHandler[Dataclass]]]: ...
36
-
37
- @typing.overload
38
- def __call__[**P, Dataclass, R](
39
- self,
40
- *rules: ABCRule,
41
- update_type: UpdateType,
42
- dataclass: type[Dataclass],
43
- final: bool = True,
44
- ) -> typing.Callable[[Func[P, R]], FuncHandler[Dataclass, Func[P, R], ErrorHandler[Dataclass]]]: ...
45
-
46
- @typing.overload
47
- def __call__[**P, ErrorHandlerT: ABCErrorHandler, R](
48
- self,
49
- *rules: ABCRule,
50
- error_handler: ErrorHandlerT,
51
- final: bool = True,
52
- ) -> typing.Callable[
53
- [Func[P, R]],
54
- FuncHandler[BaseCute[typing.Any], Func[P, R], ErrorHandlerT],
55
- ]: ...
56
-
57
- @typing.overload
58
- def __call__[**P, ErrorHandlerT: ABCErrorHandler, R](
59
- self,
60
- *rules: ABCRule,
61
- error_handler: ErrorHandlerT,
62
- update_type: UpdateType,
63
- final: bool = True,
64
- ) -> typing.Callable[
65
- [Func[P, R]],
66
- FuncHandler[BaseCute[typing.Any], Func[P, R], ErrorHandlerT],
67
- ]: ...
68
-
69
- @typing.overload
70
- def __call__[**P, Dataclass, ErrorHandlerT: ABCErrorHandler, R](
71
- self,
72
- *rules: ABCRule,
73
- update_type: UpdateType,
74
- dataclass: type[Dataclass],
75
- error_handler: ErrorHandlerT,
76
- final: bool = True,
77
- ) -> typing.Callable[[Func[P, R]], FuncHandler[Dataclass, Func[P, R], ErrorHandlerT]]: ...
78
-
79
- def __call__(
80
- self,
81
- *rules: ABCRule,
82
- update_type: UpdateType | None = None,
83
- dataclass: type[typing.Any] | None = None,
84
- error_handler: ABCErrorHandler | None = None,
85
- final: bool = True,
86
- ) -> typing.Callable[..., typing.Any]:
87
- def wrapper(func: typing.Callable[..., typing.Any]):
88
- func_handler = FuncHandler(
89
- func,
90
- rules=[*self.auto_rules, *rules],
91
- final=final,
92
- dataclass=dataclass,
93
- error_handler=error_handler or ErrorHandler(),
94
- update_type=update_type,
95
- )
96
- self.handlers.append(func_handler)
97
- return func_handler
98
-
99
- return wrapper
100
3
 
101
- async def check(self, event: Update) -> bool:
102
- return bool(self.handlers) or bool(self.middlewares)
103
4
 
104
- async def process(self, event: Update, api: API, context: Context) -> bool:
105
- return await process_inner(
106
- api,
107
- UpdateCute.from_update(event, bound_api=api),
108
- event,
109
- context,
110
- self.middlewares,
111
- self.handlers,
112
- self.return_manager,
113
- )
5
+ class RawEventView(ABCEventRawView, BaseView):
6
+ def __init__(self) -> None:
7
+ super().__init__()
114
8
 
115
9
 
116
10
  __all__ = ("RawEventView",)
@@ -6,9 +6,8 @@ from telegrinder.bot.dispatch.waiter_machine.hasher import (
6
6
  MESSAGE_FROM_USER_IN_CHAT,
7
7
  MESSAGE_IN_CHAT,
8
8
  Hasher,
9
- StateViewHasher,
10
9
  )
11
- from telegrinder.bot.dispatch.waiter_machine.machine import WaiterMachine, clear_wm_storage_worker
10
+ from telegrinder.bot.dispatch.waiter_machine.machine import WaiterMachine
12
11
  from telegrinder.bot.dispatch.waiter_machine.middleware import WaiterMiddleware
13
12
  from telegrinder.bot.dispatch.waiter_machine.short_state import ShortState
14
13
 
@@ -16,13 +15,11 @@ __all__ = (
16
15
  "CALLBACK_QUERY_FOR_MESSAGE",
17
16
  "CALLBACK_QUERY_FROM_CHAT",
18
17
  "CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE",
19
- "Hasher",
20
18
  "MESSAGE_FROM_USER",
21
19
  "MESSAGE_FROM_USER_IN_CHAT",
22
20
  "MESSAGE_IN_CHAT",
21
+ "Hasher",
23
22
  "ShortState",
24
- "StateViewHasher",
25
23
  "WaiterMachine",
26
24
  "WaiterMiddleware",
27
- "clear_wm_storage_worker",
28
25
  )
@@ -1,14 +1,16 @@
1
1
  import typing
2
2
 
3
- from telegrinder.bot.cute_types import BaseCute
4
3
  from telegrinder.bot.dispatch.handler.abc import ABCHandler
5
4
 
6
5
  from .short_state import ShortState
7
6
 
7
+ if typing.TYPE_CHECKING:
8
+ from telegrinder.bot.cute_types.base import BaseCute
8
9
 
9
- class WaiterActions[Event: BaseCute](typing.TypedDict):
10
- on_miss: typing.NotRequired[ABCHandler[Event]]
11
- on_drop: typing.NotRequired[typing.Callable[[ShortState[Event]], None]]
10
+
11
+ class WaiterActions[Event: BaseCute[typing.Any]](typing.TypedDict):
12
+ on_miss: typing.NotRequired[ABCHandler]
13
+ on_drop: typing.NotRequired[typing.Callable[[ShortState], None]]
12
14
 
13
15
 
14
16
  __all__ = ("WaiterActions",)
@@ -1,15 +1,13 @@
1
1
  from .callback import CALLBACK_QUERY_FOR_MESSAGE, CALLBACK_QUERY_FROM_CHAT, CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE
2
2
  from .hasher import Hasher
3
3
  from .message import MESSAGE_FROM_USER, MESSAGE_FROM_USER_IN_CHAT, MESSAGE_IN_CHAT
4
- from .state import StateViewHasher
5
4
 
6
5
  __all__ = (
7
6
  "CALLBACK_QUERY_FOR_MESSAGE",
8
7
  "CALLBACK_QUERY_FROM_CHAT",
9
8
  "CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE",
10
- "Hasher",
11
9
  "MESSAGE_FROM_USER",
12
10
  "MESSAGE_FROM_USER_IN_CHAT",
13
11
  "MESSAGE_IN_CHAT",
14
- "StateViewHasher",
12
+ "Hasher",
15
13
  )
@@ -10,7 +10,7 @@ def from_chat_hash(chat_id: int) -> int:
10
10
 
11
11
 
12
12
  def get_chat_from_event(event: CallbackQuery) -> int | None:
13
- return event.chat.and_then(lambda chat: Some(chat.id)).unwrap_or_none()
13
+ return event.chat.then(lambda chat: Some(chat.id)).unwrap_or_none()
14
14
 
15
15
 
16
16
  def for_message_hash(message_id: int) -> int:
@@ -1,27 +1,28 @@
1
+ from __future__ import annotations
2
+
1
3
  import typing
2
4
  from functools import cached_property
3
5
 
6
+ from fntypes.misc import from_optional
4
7
  from fntypes.option import Option
5
8
 
6
9
  from telegrinder.bot.cute_types import BaseCute
7
10
  from telegrinder.bot.dispatch.view.base import BaseView
8
- from telegrinder.tools.functional import from_optional
11
+
12
+ type HasherWithData[Event: BaseCute, Data] = tuple[Hasher[Event, Data], Data]
9
13
 
10
14
  Event = typing.TypeVar("Event", bound=BaseCute, covariant=True)
11
15
  Data = typing.TypeVar("Data", covariant=True)
12
16
 
13
17
 
14
- def _echo[T](__x: T) -> T:
18
+ def ECHO[T](__x: T) -> T: # noqa
15
19
  return __x
16
20
 
17
21
 
18
- ECHO = _echo
19
-
20
-
21
22
  class Hasher(typing.Generic[Event, Data]):
22
23
  def __init__(
23
24
  self,
24
- view_class: type[BaseView[Event]],
25
+ view_class: type[BaseView],
25
26
  get_hash_from_data: typing.Callable[[Data], typing.Hashable | None] | None = None,
26
27
  get_data_from_event: typing.Callable[[Event], Data | None] | None = None,
27
28
  ) -> None:
@@ -29,6 +30,9 @@ class Hasher(typing.Generic[Event, Data]):
29
30
  self._get_hash_from_data = get_hash_from_data
30
31
  self._get_data_from_event = get_data_from_event
31
32
 
33
+ def __call__[D](self: "Hasher[Event, D]", data: D, /) -> HasherWithData[Event, D]:
34
+ return (self, data)
35
+
32
36
  def __hash__(self) -> int:
33
37
  return hash(self.name)
34
38
 
@@ -53,7 +57,7 @@ class Hasher(typing.Generic[Event, Data]):
53
57
  self: "Hasher[E, Data]",
54
58
  event: E,
55
59
  ) -> Option[typing.Hashable]:
56
- return self.get_data_from_event(event).and_then(self.get_hash_from_data) # type: ignore
60
+ return self.get_data_from_event(event).then(self.get_hash_from_data) # type: ignore
57
61
 
58
62
 
59
63
  __all__ = ("Hasher",)
File without changes
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import datetime
3
5
  import typing
@@ -5,18 +7,19 @@ import typing
5
7
  from telegrinder.bot.cute_types.base import BaseCute
6
8
  from telegrinder.bot.dispatch.abc import ABCDispatch
7
9
  from telegrinder.bot.dispatch.context import Context
8
- from telegrinder.bot.dispatch.view.base import BaseStateView, BaseView
9
- from telegrinder.bot.dispatch.waiter_machine.middleware import INITIATOR_CONTEXT_KEY, WaiterMiddleware
10
+ from telegrinder.bot.dispatch.view.base import BaseView
11
+ from telegrinder.bot.dispatch.waiter_machine.middleware import WaiterMiddleware
10
12
  from telegrinder.bot.dispatch.waiter_machine.short_state import (
11
13
  ShortState,
12
14
  ShortStateContext,
13
15
  )
14
16
  from telegrinder.bot.rules.abc import ABCRule
17
+ from telegrinder.tools.global_context.builtin_context import TelegrinderContext
15
18
  from telegrinder.tools.lifespan import Lifespan
16
19
  from telegrinder.tools.limited_dict import LimitedDict
17
20
 
18
21
  from .actions import WaiterActions
19
- from .hasher import Hasher, StateViewHasher
22
+ from .hasher import Hasher
20
23
 
21
24
  type Storage[Event: BaseCute, HasherData] = dict[
22
25
  Hasher[Event, HasherData],
@@ -24,35 +27,49 @@ type Storage[Event: BaseCute, HasherData] = dict[
24
27
  ]
25
28
  type HasherWithData[Event: BaseCute, Data] = tuple[Hasher[Event, Data], Data]
26
29
 
30
+ _NODEFAULT: typing.Any = object()
31
+ MAX_STORAGE_SIZE: typing.Final[int] = 10000
32
+ ONE_MINUTE: typing.Final[datetime.timedelta] = datetime.timedelta(minutes=1)
27
33
  WEEK: typing.Final[datetime.timedelta] = datetime.timedelta(days=7)
34
+ CONTEXT: typing.Final[TelegrinderContext] = TelegrinderContext()
28
35
 
29
36
 
30
- class ContextUnpackProto[*Ts](typing.Protocol):
31
- __name__: str
32
-
33
- def __call__(self, context: Context, /) -> tuple[*Ts]: ...
37
+ async def clear_wm_storage_worker(wm: WaiterMachine, interval: float) -> None:
38
+ await wm.clear_storage()
39
+ await asyncio.sleep(interval)
34
40
 
35
41
 
36
- def unpack_to_context(context: Context) -> tuple[Context]:
42
+ def unpack_to_context(context: Context, /) -> tuple[Context]:
37
43
  return (context,)
38
44
 
39
45
 
40
- def no_unpack(_: Context) -> tuple[()]:
46
+ def no_unpack(_: Context, /) -> tuple[()]:
41
47
  return ()
42
48
 
43
49
 
50
+ class ContextUnpackProto[*Ts](typing.Protocol):
51
+ __name__: str
52
+
53
+ def __call__(self, context: Context, /) -> tuple[*Ts]: ...
54
+
55
+
44
56
  class WaiterMachine:
45
57
  def __init__(
46
58
  self,
47
59
  dispatch: ABCDispatch | None = None,
48
60
  *,
49
- max_storage_size: int = 1000,
61
+ max_storage_size: int = MAX_STORAGE_SIZE,
50
62
  base_state_lifetime: datetime.timedelta = WEEK,
63
+ clear_storage_interval: datetime.timedelta = ONE_MINUTE,
51
64
  ) -> None:
52
65
  self.dispatch = dispatch
53
66
  self.max_storage_size = max_storage_size
54
67
  self.base_state_lifetime = base_state_lifetime
55
- self.storage: Storage = {}
68
+ self.storage: Storage[typing.Any, typing.Any] = {}
69
+
70
+ CONTEXT.loop_wrapper.add_task(
71
+ clear_wm_storage_worker(self, clear_storage_interval.total_seconds()),
72
+ )
56
73
 
57
74
  def __repr__(self) -> str:
58
75
  return "<{}: with {} storage items and max_storage_size={}, base_state_lifetime={!r}>".format(
@@ -62,17 +79,12 @@ class WaiterMachine:
62
79
  self.base_state_lifetime,
63
80
  )
64
81
 
65
- def create_middleware[Event: BaseCute](self, view: BaseStateView[Event]) -> WaiterMiddleware[Event]:
66
- hasher = StateViewHasher(view)
67
- self.storage[hasher] = LimitedDict(maxlimit=self.max_storage_size)
68
- return WaiterMiddleware(self, hasher)
69
-
70
82
  async def drop_all(self) -> None:
71
83
  """Drops all waiters in storage."""
72
84
  for hasher in self.storage.copy():
73
85
  for ident, short_state in self.storage[hasher].items():
74
86
  if short_state.context:
75
- await self.drop(hasher, ident)
87
+ await self.drop(hasher, data=ident)
76
88
  else:
77
89
  await short_state.cancel()
78
90
 
@@ -101,34 +113,10 @@ class WaiterMachine:
101
113
 
102
114
  await short_state.cancel()
103
115
 
104
- async def wait_from_event[Event: BaseCute](
105
- self,
106
- view: BaseStateView[Event],
107
- event: Event,
108
- *,
109
- filter: ABCRule | None = None,
110
- release: ABCRule | None = None,
111
- lifetime: datetime.timedelta | float | None = None,
112
- lifespan: Lifespan | None = None,
113
- **actions: typing.Unpack[WaiterActions[Event]],
114
- ) -> ShortStateContext[Event]:
115
- hasher = StateViewHasher(view)
116
- return await self.wait(
117
- hasher=hasher,
118
- data=hasher.get_data_from_event(event).expect(
119
- RuntimeError("Hasher couldn't create data from event."),
120
- ),
121
- filter=filter,
122
- release=release,
123
- lifetime=lifetime,
124
- lifespan=lifespan or Lifespan(),
125
- **actions,
126
- )
127
-
128
116
  async def wait[Event: BaseCute, HasherData](
129
117
  self,
130
- hasher: Hasher[Event, HasherData],
131
- data: HasherData,
118
+ hasher: Hasher[Event, HasherData] | HasherWithData[Event, HasherData],
119
+ data: HasherData = _NODEFAULT,
132
120
  *,
133
121
  filter: ABCRule | None = None,
134
122
  release: ABCRule | None = None,
@@ -141,21 +129,25 @@ class WaiterMachine:
141
129
 
142
130
  lifespan = lifespan or Lifespan()
143
131
  event = asyncio.Event()
144
- short_state = ShortState[Event](
132
+ short_state = ShortState(
145
133
  event,
146
134
  actions,
147
135
  release=release,
148
136
  filter=filter,
149
137
  lifetime=lifetime or self.base_state_lifetime,
150
138
  )
139
+
140
+ hasher, data = hasher if not isinstance(hasher, Hasher) else (hasher, data)
141
+ assert data is not _NODEFAULT, "Hasher requires data."
151
142
  waiter_hash = hasher.get_hash_from_data(data).expect(RuntimeError("Hasher couldn't create hash."))
152
143
 
153
144
  if hasher not in self.storage:
154
145
  if self.dispatch:
155
- view: BaseView[Event] = self.dispatch.get_view(hasher.view_class).expect(
146
+ view: BaseView = self.dispatch.get_view(hasher.view_class).expect(
156
147
  RuntimeError(f"View {hasher.view_class.__name__!r} is not defined in dispatch."),
157
148
  )
158
149
  view.middlewares.insert(0, WaiterMiddleware(self, hasher))
150
+
159
151
  self.storage[hasher] = LimitedDict(maxlimit=self.max_storage_size)
160
152
 
161
153
  if (deleted_short_state := self.storage[hasher].set(waiter_hash, short_state)) is not None:
@@ -170,16 +162,16 @@ class WaiterMachine:
170
162
  raise LookupError("No context in short_state.")
171
163
  return short_state.context
172
164
 
173
- async def wait_many[RestEvent: BaseCute[typing.Any], Data, *Ts](
165
+ async def wait_many[Event: BaseCute[typing.Any], Data, *Ts](
174
166
  self,
175
- *hashers: HasherWithData[RestEvent, Data],
167
+ *hashers: HasherWithData[Event, Data],
176
168
  filter: ABCRule | None = None,
177
169
  release: ABCRule | None = None,
178
170
  lifetime: datetime.timedelta | float | None = None,
179
171
  lifespan: Lifespan | None = None,
180
172
  unpack: ContextUnpackProto[*Ts] = unpack_to_context,
181
- **actions: typing.Unpack[WaiterActions[BaseCute[typing.Any]]],
182
- ) -> tuple[HasherWithData[RestEvent, Data], RestEvent, *Ts]:
173
+ **actions: typing.Unpack[WaiterActions[Event]],
174
+ ) -> tuple[HasherWithData[Event, Data], Event, *Ts]:
183
175
  if isinstance(lifetime, int | float):
184
176
  lifetime = datetime.timedelta(seconds=lifetime)
185
177
 
@@ -192,7 +184,7 @@ class WaiterMachine:
192
184
  filter=filter,
193
185
  lifetime=lifetime or self.base_state_lifetime,
194
186
  )
195
- waiter_hashes: dict[Hasher[RestEvent, Data], typing.Hashable] = {}
187
+ waiter_hashes: dict[Hasher[Event, Data], typing.Hashable] = {}
196
188
 
197
189
  for hasher, data in hashers:
198
190
  waiter_hash = hasher.get_hash_from_data(data).expect(RuntimeError("Hasher couldn't create hash."))
@@ -216,7 +208,7 @@ class WaiterMachine:
216
208
  if short_state.context is None:
217
209
  raise LookupError("No context in short_state.")
218
210
 
219
- initiator = short_state.context.context.get(INITIATOR_CONTEXT_KEY)
211
+ initiator = short_state.context.context.get("initiator")
220
212
  if initiator is None:
221
213
  raise LookupError("Initiator not found in short_state context.")
222
214
 
@@ -239,13 +231,4 @@ class WaiterMachine:
239
231
  await self.drop(hasher, data=ident, force=True)
240
232
 
241
233
 
242
- async def clear_wm_storage_worker(
243
- wm: WaiterMachine,
244
- interval_seconds: int = 60,
245
- ) -> typing.NoReturn:
246
- while True:
247
- await wm.clear_storage()
248
- await asyncio.sleep(interval_seconds)
249
-
250
-
251
234
  __all__ = ("WaiterMachine",)
@@ -1,13 +1,15 @@
1
1
  import datetime
2
2
  import typing
3
3
 
4
- from telegrinder.bot.cute_types.base import BaseCute
4
+ from telegrinder.api.api import API
5
+ from telegrinder.bot.cute_types.update import UpdateCute
5
6
  from telegrinder.bot.dispatch.context import Context
6
7
  from telegrinder.bot.dispatch.handler.func import FuncHandler
7
8
  from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
8
9
  from telegrinder.bot.dispatch.process import check_rule
9
10
  from telegrinder.bot.dispatch.waiter_machine.short_state import ShortStateContext
10
11
  from telegrinder.modules import logger
12
+ from telegrinder.types.objects import Update
11
13
 
12
14
  from .hasher import Hasher
13
15
 
@@ -15,29 +17,29 @@ if typing.TYPE_CHECKING:
15
17
  from .machine import WaiterMachine
16
18
  from .short_state import ShortState
17
19
 
20
+ type State = ShortState[typing.Any]
18
21
 
19
- INITIATOR_CONTEXT_KEY = "initiator"
20
22
 
21
-
22
- class WaiterMiddleware[Event: BaseCute](ABCMiddleware[Event]):
23
+ class WaiterMiddleware(ABCMiddleware):
23
24
  def __init__(
24
25
  self,
25
26
  machine: "WaiterMachine",
26
- hasher: Hasher,
27
+ hasher: Hasher[typing.Any, typing.Any],
27
28
  ) -> None:
28
29
  self.machine = machine
29
30
  self.hasher = hasher
30
31
 
31
- async def pre(self, event: Event, ctx: Context) -> bool:
32
+ async def pre(self, update: UpdateCute, raw_update: Update, api: API, ctx: Context) -> bool:
32
33
  if self.hasher not in self.machine.storage:
33
34
  return True
34
35
 
36
+ event = update.incoming_update
35
37
  key = self.hasher.get_hash_from_data_from_event(event)
36
38
  if not key:
37
39
  logger.info(f"Unable to get hash from event with hasher {self.hasher!r}")
38
40
  return True
39
41
 
40
- short_state: "ShortState[Event] | None" = self.machine.storage[self.hasher].get(key.unwrap())
42
+ short_state: "ShortState | None" = self.machine.storage[self.hasher].get(key.unwrap())
41
43
  if not short_state:
42
44
  return True
43
45
 
@@ -47,12 +49,12 @@ class WaiterMiddleware[Event: BaseCute](ABCMiddleware[Event]):
47
49
 
48
50
  # Run filter rule
49
51
  if short_state.filter and not await check_rule(
50
- event.ctx_api,
52
+ api,
51
53
  short_state.filter,
52
- ctx.raw_update,
54
+ raw_update,
53
55
  preset_context,
54
56
  ):
55
- logger.debug("Filter rule {!r} failed", short_state.filter)
57
+ logger.debug("Filter rule {!r} failed!", short_state.filter)
56
58
  return True
57
59
 
58
60
  if short_state.expiration_date is not None and datetime.datetime.now() >= short_state.expiration_date:
@@ -63,31 +65,25 @@ class WaiterMiddleware[Event: BaseCute](ABCMiddleware[Event]):
63
65
  )
64
66
  return True
65
67
 
66
- handler = FuncHandler(
68
+ result = await FuncHandler(
67
69
  self.pass_runtime,
68
70
  [short_state.release] if short_state.release else [],
69
71
  preset_context=preset_context,
70
- )
71
- handler.get_name_event_param = lambda event: "event" # FIXME: HOTFIX
72
- result = await handler.check(event.ctx_api, ctx.raw_update, ctx)
73
-
74
- if result is True:
75
- await handler.run(event.api, event, ctx)
72
+ ).run(api, raw_update, ctx)
76
73
 
77
- elif on_miss := short_state.actions.get("on_miss"): # noqa: SIM102
78
- if await on_miss.check(event.ctx_api, ctx.raw_update, ctx):
79
- await on_miss.run(event.ctx_api, event, ctx)
74
+ if not result and (on_miss := short_state.actions.get("on_miss")):
75
+ await on_miss.run(api, raw_update, ctx)
80
76
 
81
77
  return False
82
78
 
83
79
  async def pass_runtime(
84
80
  self,
85
- event: Event,
86
- short_state: "ShortState[Event]",
81
+ event: UpdateCute,
87
82
  ctx: Context,
83
+ short_state: State,
88
84
  ) -> None:
89
85
  ctx.initiator = self.hasher
90
- short_state.context = ShortStateContext(event, ctx)
86
+ short_state.context = ShortStateContext(event.incoming_update, ctx) # type: ignore
91
87
  short_state.event.set()
92
88
 
93
89
 
@@ -3,22 +3,23 @@ import dataclasses
3
3
  import datetime
4
4
  import typing
5
5
 
6
- from telegrinder.bot.cute_types import BaseCute
7
6
  from telegrinder.bot.dispatch.context import Context
8
7
  from telegrinder.bot.rules.abc import ABCRule
9
- from telegrinder.tools.magic import cancel_future
8
+ from telegrinder.tools.aio import cancel_future
10
9
 
11
10
  if typing.TYPE_CHECKING:
11
+ from telegrinder.bot.cute_types.base import BaseCute
12
+
12
13
  from .actions import WaiterActions
13
14
 
14
15
 
15
- class ShortStateContext[Event: BaseCute](typing.NamedTuple):
16
+ class ShortStateContext[Event: BaseCute[typing.Any]](typing.NamedTuple):
16
17
  event: Event
17
18
  context: Context
18
19
 
19
20
 
20
21
  @dataclasses.dataclass(slots=True)
21
- class ShortState[Event: BaseCute]:
22
+ class ShortState[Event: BaseCute[typing.Any]]:
22
23
  event: asyncio.Event
23
24
  actions: "WaiterActions[Event]"
24
25
 
@@ -47,7 +48,7 @@ class ShortState[Event: BaseCute]:
47
48
  async def cancel(self) -> None:
48
49
  """Cancel schedule waiters."""
49
50
  waiters = typing.cast(
50
- typing.Iterable[asyncio.Future[typing.Any]],
51
+ "typing.Iterable[asyncio.Future[typing.Any]]",
51
52
  self.event._waiters, # type: ignore
52
53
  )
53
54
  for future in waiters:
File without changes
File without changes