telegrinder 0.1.dev19__py3-none-any.whl → 0.1.dev158__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 (136) hide show
  1. telegrinder/__init__.py +129 -22
  2. telegrinder/api/__init__.py +11 -2
  3. telegrinder/api/abc.py +25 -9
  4. telegrinder/api/api.py +29 -24
  5. telegrinder/api/error.py +14 -4
  6. telegrinder/api/response.py +11 -7
  7. telegrinder/bot/__init__.py +68 -7
  8. telegrinder/bot/bot.py +30 -24
  9. telegrinder/bot/cute_types/__init__.py +11 -1
  10. telegrinder/bot/cute_types/base.py +47 -0
  11. telegrinder/bot/cute_types/callback_query.py +64 -14
  12. telegrinder/bot/cute_types/inline_query.py +22 -16
  13. telegrinder/bot/cute_types/message.py +163 -43
  14. telegrinder/bot/cute_types/update.py +23 -0
  15. telegrinder/bot/dispatch/__init__.py +56 -3
  16. telegrinder/bot/dispatch/abc.py +9 -7
  17. telegrinder/bot/dispatch/composition.py +74 -0
  18. telegrinder/bot/dispatch/context.py +71 -0
  19. telegrinder/bot/dispatch/dispatch.py +86 -49
  20. telegrinder/bot/dispatch/handler/__init__.py +3 -0
  21. telegrinder/bot/dispatch/handler/abc.py +11 -5
  22. telegrinder/bot/dispatch/handler/func.py +41 -32
  23. telegrinder/bot/dispatch/handler/message_reply.py +46 -0
  24. telegrinder/bot/dispatch/middleware/__init__.py +2 -0
  25. telegrinder/bot/dispatch/middleware/abc.py +10 -4
  26. telegrinder/bot/dispatch/process.py +53 -49
  27. telegrinder/bot/dispatch/return_manager/__init__.py +19 -0
  28. telegrinder/bot/dispatch/return_manager/abc.py +95 -0
  29. telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
  30. telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
  31. telegrinder/bot/dispatch/return_manager/message.py +25 -0
  32. telegrinder/bot/dispatch/view/__init__.py +14 -2
  33. telegrinder/bot/dispatch/view/abc.py +121 -2
  34. telegrinder/bot/dispatch/view/box.py +38 -0
  35. telegrinder/bot/dispatch/view/callback_query.py +13 -38
  36. telegrinder/bot/dispatch/view/inline_query.py +11 -38
  37. telegrinder/bot/dispatch/view/message.py +11 -46
  38. telegrinder/bot/dispatch/waiter_machine/__init__.py +9 -0
  39. telegrinder/bot/dispatch/waiter_machine/machine.py +116 -0
  40. telegrinder/bot/dispatch/waiter_machine/middleware.py +76 -0
  41. telegrinder/bot/dispatch/waiter_machine/short_state.py +37 -0
  42. telegrinder/bot/polling/__init__.py +2 -0
  43. telegrinder/bot/polling/abc.py +11 -4
  44. telegrinder/bot/polling/polling.py +89 -40
  45. telegrinder/bot/rules/__init__.py +92 -5
  46. telegrinder/bot/rules/abc.py +81 -63
  47. telegrinder/bot/rules/adapter/__init__.py +11 -0
  48. telegrinder/bot/rules/adapter/abc.py +21 -0
  49. telegrinder/bot/rules/adapter/errors.py +5 -0
  50. telegrinder/bot/rules/adapter/event.py +43 -0
  51. telegrinder/bot/rules/adapter/raw_update.py +24 -0
  52. telegrinder/bot/rules/callback_data.py +159 -38
  53. telegrinder/bot/rules/command.py +116 -0
  54. telegrinder/bot/rules/enum_text.py +28 -0
  55. telegrinder/bot/rules/func.py +17 -17
  56. telegrinder/bot/rules/fuzzy.py +13 -10
  57. telegrinder/bot/rules/inline.py +61 -0
  58. telegrinder/bot/rules/integer.py +12 -7
  59. telegrinder/bot/rules/is_from.py +148 -7
  60. telegrinder/bot/rules/markup.py +21 -18
  61. telegrinder/bot/rules/mention.py +17 -0
  62. telegrinder/bot/rules/message_entities.py +33 -0
  63. telegrinder/bot/rules/regex.py +27 -19
  64. telegrinder/bot/rules/rule_enum.py +74 -0
  65. telegrinder/bot/rules/start.py +42 -0
  66. telegrinder/bot/rules/text.py +23 -14
  67. telegrinder/bot/scenario/__init__.py +2 -0
  68. telegrinder/bot/scenario/abc.py +12 -5
  69. telegrinder/bot/scenario/checkbox.py +48 -30
  70. telegrinder/bot/scenario/choice.py +16 -10
  71. telegrinder/client/__init__.py +2 -0
  72. telegrinder/client/abc.py +8 -21
  73. telegrinder/client/aiohttp.py +30 -21
  74. telegrinder/model.py +68 -37
  75. telegrinder/modules.py +189 -21
  76. telegrinder/msgspec_json.py +14 -0
  77. telegrinder/msgspec_utils.py +207 -0
  78. telegrinder/node/__init__.py +31 -0
  79. telegrinder/node/attachment.py +71 -0
  80. telegrinder/node/base.py +93 -0
  81. telegrinder/node/composer.py +71 -0
  82. telegrinder/node/container.py +22 -0
  83. telegrinder/node/message.py +18 -0
  84. telegrinder/node/rule.py +56 -0
  85. telegrinder/node/source.py +31 -0
  86. telegrinder/node/text.py +13 -0
  87. telegrinder/node/tools/__init__.py +3 -0
  88. telegrinder/node/tools/generator.py +40 -0
  89. telegrinder/node/update.py +12 -0
  90. telegrinder/rules.py +1 -1
  91. telegrinder/tools/__init__.py +165 -4
  92. telegrinder/tools/buttons.py +75 -51
  93. telegrinder/tools/error_handler/__init__.py +8 -0
  94. telegrinder/tools/error_handler/abc.py +30 -0
  95. telegrinder/tools/error_handler/error_handler.py +156 -0
  96. telegrinder/tools/formatting/__init__.py +81 -3
  97. telegrinder/tools/formatting/html.py +283 -37
  98. telegrinder/tools/formatting/links.py +32 -0
  99. telegrinder/tools/formatting/spec_html_formats.py +121 -0
  100. telegrinder/tools/global_context/__init__.py +12 -0
  101. telegrinder/tools/global_context/abc.py +66 -0
  102. telegrinder/tools/global_context/global_context.py +451 -0
  103. telegrinder/tools/global_context/telegrinder_ctx.py +25 -0
  104. telegrinder/tools/i18n/__init__.py +12 -0
  105. telegrinder/tools/i18n/base.py +31 -0
  106. telegrinder/tools/i18n/middleware/__init__.py +3 -0
  107. telegrinder/tools/i18n/middleware/base.py +26 -0
  108. telegrinder/tools/i18n/simple.py +48 -0
  109. telegrinder/tools/inline_query.py +684 -0
  110. telegrinder/tools/kb_set/__init__.py +2 -0
  111. telegrinder/tools/kb_set/base.py +3 -0
  112. telegrinder/tools/kb_set/yaml.py +28 -17
  113. telegrinder/tools/keyboard.py +84 -62
  114. telegrinder/tools/loop_wrapper/__init__.py +4 -0
  115. telegrinder/tools/loop_wrapper/abc.py +18 -0
  116. telegrinder/tools/loop_wrapper/loop_wrapper.py +132 -0
  117. telegrinder/tools/magic.py +48 -23
  118. telegrinder/tools/parse_mode.py +1 -2
  119. telegrinder/types/__init__.py +1 -0
  120. telegrinder/types/enums.py +651 -0
  121. telegrinder/types/methods.py +3933 -1128
  122. telegrinder/types/objects.py +4755 -1633
  123. {telegrinder-0.1.dev19.dist-info → telegrinder-0.1.dev158.dist-info}/LICENSE +2 -1
  124. telegrinder-0.1.dev158.dist-info/METADATA +108 -0
  125. telegrinder-0.1.dev158.dist-info/RECORD +126 -0
  126. {telegrinder-0.1.dev19.dist-info → telegrinder-0.1.dev158.dist-info}/WHEEL +1 -1
  127. telegrinder/bot/dispatch/waiter.py +0 -37
  128. telegrinder/result.py +0 -38
  129. telegrinder/tools/formatting/abc.py +0 -52
  130. telegrinder/tools/formatting/markdown.py +0 -57
  131. telegrinder/typegen/__init__.py +0 -1
  132. telegrinder/typegen/__main__.py +0 -3
  133. telegrinder/typegen/nicification.py +0 -20
  134. telegrinder/typegen/schema_generator.py +0 -259
  135. telegrinder-0.1.dev19.dist-info/METADATA +0 -22
  136. telegrinder-0.1.dev19.dist-info/RECORD +0 -74
@@ -0,0 +1,25 @@
1
+ from telegrinder.bot.cute_types import MessageCute
2
+ from telegrinder.bot.dispatch.context import Context
3
+
4
+ from .abc import BaseReturnManager, register_manager
5
+
6
+
7
+ class MessageReturnManager(BaseReturnManager[MessageCute]):
8
+ @register_manager(str)
9
+ @staticmethod
10
+ async def str_manager(value: str, event: MessageCute, ctx: Context) -> None:
11
+ await event.answer(value)
12
+
13
+ @register_manager(list | tuple)
14
+ @staticmethod
15
+ async def seq_manager(value: list[str] | tuple[str, ...], event: MessageCute, ctx: Context) -> None:
16
+ for message in value:
17
+ await event.answer(message)
18
+
19
+ @register_manager(dict)
20
+ @staticmethod
21
+ async def dict_manager(value: dict, event: MessageCute, ctx: Context) -> None:
22
+ await event.answer(**value)
23
+
24
+
25
+ __all__ = ("MessageReturnManager",)
@@ -1,4 +1,16 @@
1
- from .abc import ABCView
2
- from .message import MessageView
1
+ from .abc import ABCStateView, ABCView, BaseStateView, BaseView
2
+ from .box import ViewBox
3
3
  from .callback_query import CallbackQueryView
4
4
  from .inline_query import InlineQueryView
5
+ from .message import MessageView
6
+
7
+ __all__ = (
8
+ "ABCView",
9
+ "ABCStateView",
10
+ "BaseView",
11
+ "BaseStateView",
12
+ "CallbackQueryView",
13
+ "InlineQueryView",
14
+ "MessageView",
15
+ "ViewBox",
16
+ )
@@ -1,6 +1,21 @@
1
+ import typing
1
2
  from abc import ABC, abstractmethod
3
+
4
+ from fntypes.co import Nothing, Some
5
+
2
6
  from telegrinder.api.abc import ABCAPI
3
- from telegrinder.types import Update
7
+ from telegrinder.bot.cute_types.base import BaseCute
8
+ from telegrinder.bot.dispatch.handler.abc import ABCHandler
9
+ from telegrinder.bot.dispatch.handler.func import ErrorHandlerT, FuncHandler
10
+ from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
11
+ from telegrinder.bot.dispatch.process import process_inner
12
+ from telegrinder.bot.dispatch.return_manager.abc import ABCReturnManager
13
+ from telegrinder.bot.rules.abc import ABCRule
14
+ from telegrinder.model import Model
15
+ from telegrinder.msgspec_utils import Option
16
+ from telegrinder.types.objects import Update
17
+
18
+ EventType = typing.TypeVar("EventType", bound=BaseCute)
4
19
 
5
20
 
6
21
  class ABCView(ABC):
@@ -13,5 +28,109 @@ class ABCView(ABC):
13
28
  pass
14
29
 
15
30
  @abstractmethod
16
- async def load(self, external: "ABCView"):
31
+ def load(self, external: typing.Self) -> None:
17
32
  pass
33
+
34
+
35
+ class ABCStateView(ABCView, typing.Generic[EventType]):
36
+ @abstractmethod
37
+ def get_state_key(self, event: EventType) -> int | None:
38
+ pass
39
+
40
+ def __repr__(self) -> str:
41
+ return "<{!r}: {}>".format(
42
+ self.__class__.__name__,
43
+ ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
44
+ )
45
+
46
+
47
+ class BaseView(ABCView, typing.Generic[EventType]):
48
+ auto_rules: list[ABCRule[EventType]]
49
+ handlers: list[ABCHandler[EventType]]
50
+ middlewares: list[ABCMiddleware[EventType]]
51
+ return_manager: ABCReturnManager[EventType]
52
+
53
+ @classmethod
54
+ def get_event_type(cls) -> Option[type[EventType]]:
55
+ for base in cls.__dict__.get("__orig_bases__", ()):
56
+ if issubclass(typing.get_origin(base) or base, ABCView):
57
+ for generic_type in typing.get_args(base):
58
+ if issubclass(typing.get_origin(generic_type) or generic_type, BaseCute):
59
+ return Some(generic_type)
60
+ return Nothing()
61
+
62
+ @classmethod
63
+ def get_event_raw(cls, update: Update) -> Option[Model]:
64
+ match cls.get_event_type():
65
+ case Some(event_type):
66
+ for field in update.__struct_fields__:
67
+ event_raw = getattr(update, field)
68
+ if isinstance(event_raw, Some | Nothing):
69
+ event_raw = event_raw.unwrap_or_none()
70
+ if event_raw is not None and issubclass(event_type, event_raw.__class__):
71
+ return Some(event_raw)
72
+ return Nothing()
73
+
74
+ def __call__(
75
+ self,
76
+ *rules: ABCRule[EventType],
77
+ is_blocking: bool = True,
78
+ error_handler: ErrorHandlerT | None = None,
79
+ ):
80
+ def wrapper(
81
+ func: typing.Callable[
82
+ typing.Concatenate[EventType, ...],
83
+ typing.Coroutine,
84
+ ]
85
+ ):
86
+ func_handler = FuncHandler(
87
+ func,
88
+ [*self.auto_rules, *rules],
89
+ is_blocking,
90
+ dataclass=None,
91
+ error_handler=error_handler,
92
+ )
93
+ self.handlers.append(func_handler)
94
+ return func_handler
95
+
96
+ return wrapper
97
+
98
+ def register_middleware(self, *args: typing.Any, **kwargs: typing.Any):
99
+ def wrapper(cls: type[ABCMiddleware[EventType]]):
100
+ self.middlewares.append(cls(*args, **kwargs))
101
+ return cls
102
+
103
+ return wrapper
104
+
105
+ async def check(self, event: Update) -> bool:
106
+ return bool(self.get_event_raw(event))
107
+
108
+ async def process(self, event: Update, api: ABCAPI) -> bool:
109
+ event_raw = self.get_event_raw(event).unwrap()
110
+ event_type = self.get_event_type().unwrap()
111
+ return await process_inner(
112
+ event_type(**event_raw.to_dict(), api=api),
113
+ event,
114
+ self.middlewares,
115
+ self.handlers,
116
+ self.return_manager,
117
+ )
118
+
119
+ def load(self, external: typing.Self) -> None:
120
+ self.auto_rules.extend(external.auto_rules)
121
+ self.handlers.extend(external.handlers)
122
+ self.middlewares.extend(external.middlewares)
123
+
124
+
125
+ class BaseStateView(ABCStateView[EventType], BaseView[EventType], ABC, typing.Generic[EventType]):
126
+ @abstractmethod
127
+ def get_state_key(self, event: EventType) -> int | None:
128
+ pass
129
+
130
+
131
+ __all__ = (
132
+ "ABCView",
133
+ "ABCStateView",
134
+ "BaseView",
135
+ "BaseStateView",
136
+ )
@@ -0,0 +1,38 @@
1
+ import dataclasses
2
+
3
+ import typing_extensions as typing
4
+
5
+ from .abc import ABCView
6
+ from .callback_query import CallbackQueryView
7
+ from .inline_query import InlineQueryView
8
+ from .message import MessageView
9
+
10
+ CallbackQueryViewT = typing.TypeVar(
11
+ "CallbackQueryViewT", bound=ABCView, default=CallbackQueryView
12
+ )
13
+ InlineQueryViewT = typing.TypeVar(
14
+ "InlineQueryViewT", bound=ABCView, default=InlineQueryView
15
+ )
16
+ MessageViewT = typing.TypeVar("MessageViewT", bound=ABCView, default=MessageView)
17
+
18
+
19
+ @dataclasses.dataclass(kw_only=True)
20
+ class ViewBox(typing.Generic[CallbackQueryViewT, InlineQueryViewT, MessageViewT]):
21
+ callback_query: CallbackQueryViewT = dataclasses.field( # type: ignore
22
+ default_factory=lambda: CallbackQueryView(),
23
+ )
24
+ inline_query: InlineQueryViewT = dataclasses.field( # type: ignore
25
+ default_factory=lambda: InlineQueryView(),
26
+ )
27
+ message: MessageViewT = dataclasses.field( # type: ignore
28
+ default_factory=lambda: MessageView(),
29
+ )
30
+
31
+ def get_views(self) -> dict[str, ABCView]:
32
+ return {
33
+ name: view for name, view in self.__dict__.items()
34
+ if isinstance(view, ABCView)
35
+ }
36
+
37
+
38
+ __all__ = ("ViewBox",)
@@ -1,45 +1,20 @@
1
- from .abc import ABCView
2
- from telegrinder.bot.dispatch.handler import ABCHandler, FuncHandler
3
- from telegrinder.bot.dispatch.waiter import Waiter
4
- from telegrinder.bot.dispatch.middleware import ABCMiddleware
5
- from telegrinder.bot.rules import ABCRule
6
- from telegrinder.bot.cute_types import CallbackQueryCute
7
- from telegrinder.api.abc import ABCAPI
8
- from telegrinder.bot.dispatch.waiter import WithWaiter
9
- from telegrinder.bot.dispatch.process import process_waiters, process_inner
10
- from telegrinder.types import Update
11
- import typing
12
-
1
+ from fntypes.option import Some
13
2
 
14
- class CallbackQueryView(ABCView, WithWaiter[int, CallbackQueryCute]):
15
- def __init__(self):
16
- self.handlers: typing.List[ABCHandler[CallbackQueryCute]] = []
17
- self.middlewares: typing.List[ABCMiddleware[CallbackQueryCute]] = []
18
- self.short_waiters: typing.Dict[int, Waiter] = {}
19
-
20
- def __call__(self, *rules: ABCRule, is_blocking: bool = True):
21
- def wrapper(func: typing.Callable[..., typing.Coroutine]):
22
- self.handlers.append(
23
- FuncHandler(func, list(rules), is_blocking, dataclass=None)
24
- )
25
- return func
3
+ from telegrinder.bot.cute_types import CallbackQueryCute
4
+ from telegrinder.bot.dispatch.return_manager import CallbackQueryReturnManager
26
5
 
27
- return wrapper
6
+ from .abc import BaseStateView
28
7
 
29
- async def check(self, event: Update) -> bool:
30
- return bool(event.callback_query)
31
8
 
32
- async def process(self, event: Update, api: ABCAPI):
33
- query = CallbackQueryCute(**event.callback_query.to_dict(), api=api)
9
+ class CallbackQueryView(BaseStateView[CallbackQueryCute]):
10
+ def __init__(self) -> None:
11
+ self.auto_rules = []
12
+ self.handlers = []
13
+ self.middlewares = []
14
+ self.return_manager = CallbackQueryReturnManager()
34
15
 
35
- if await process_waiters(
36
- self.short_waiters, query.message.message_id, query, event, query.answer
37
- ):
38
- return
16
+ def get_state_key(self, event: CallbackQueryCute) -> int | None:
17
+ return event.message.map(lambda variative: variative.v.message_id).unwrap_or_none()
39
18
 
40
- return await process_inner(query, event, self.middlewares, self.handlers)
41
19
 
42
- def load(self, external: "CallbackQueryView"):
43
- self.handlers.extend(external.handlers)
44
- self.middlewares.extend(external.middlewares)
45
- external.short_waiters = self.short_waiters
20
+ __all__ = ("CallbackQueryView",)
@@ -1,45 +1,18 @@
1
- from .abc import ABCView
2
- from telegrinder.bot.dispatch.handler import ABCHandler, FuncHandler
3
- from telegrinder.bot.dispatch.waiter import Waiter
4
- from telegrinder.bot.dispatch.middleware import ABCMiddleware
5
- from telegrinder.bot.rules import ABCRule
6
1
  from telegrinder.bot.cute_types import InlineQueryCute
7
- from telegrinder.api.abc import ABCAPI
8
- from telegrinder.bot.dispatch.waiter import WithWaiter
9
- from telegrinder.bot.dispatch.process import process_waiters, process_inner
10
- from telegrinder.types import Update
11
- import typing
2
+ from telegrinder.bot.dispatch.return_manager import InlineQueryReturnManager
12
3
 
4
+ from .abc import BaseStateView
13
5
 
14
- class InlineQueryView(ABCView, WithWaiter[str, InlineQueryCute]):
15
- def __init__(self):
16
- self.handlers: typing.List[ABCHandler[InlineQueryCute]] = []
17
- self.middlewares: typing.List[ABCMiddleware[InlineQueryCute]] = []
18
- self.short_waiters: typing.Dict[str, Waiter] = {}
19
6
 
20
- def __call__(self, *rules: ABCRule, is_blocking: bool = True):
21
- def wrapper(func: typing.Callable[..., typing.Coroutine]):
22
- self.handlers.append(
23
- FuncHandler(func, list(rules), is_blocking, dataclass=None)
24
- )
25
- return func
7
+ class InlineQueryView(BaseStateView[InlineQueryCute]):
8
+ def __init__(self) -> None:
9
+ self.auto_rules = []
10
+ self.handlers = []
11
+ self.middlewares = []
12
+ self.return_manager = InlineQueryReturnManager()
26
13
 
27
- return wrapper
14
+ def get_state_key(self, event: InlineQueryCute) -> int | None:
15
+ return event.from_.id
28
16
 
29
- def load(self, external: "InlineQueryView"):
30
- self.handlers.extend(external.handlers)
31
- self.middlewares.extend(external.middlewares)
32
- external.short_waiters = self.short_waiters
33
17
 
34
- async def check(self, event: Update) -> bool:
35
- return bool(event.inline_query)
36
-
37
- async def process(self, event: Update, api: ABCAPI):
38
- query = InlineQueryCute(**event.inline_query.to_dict(), api=api)
39
-
40
- if await process_waiters(
41
- self.short_waiters, query.id, query, event, query.answer
42
- ):
43
- return
44
-
45
- return await process_inner(query, event, self.middlewares, self.handlers)
18
+ __all__ = ("InlineQueryView",)
@@ -1,53 +1,18 @@
1
- from .abc import ABCView
2
- from telegrinder.bot.dispatch.handler import ABCHandler, FuncHandler
3
- from telegrinder.bot.dispatch.waiter import Waiter
4
- from telegrinder.bot.dispatch.middleware import ABCMiddleware
5
- from telegrinder.bot.rules import ABCRule
6
1
  from telegrinder.bot.cute_types import MessageCute
7
- from telegrinder.api.abc import ABCAPI
8
- from telegrinder.bot.dispatch.waiter import WithWaiter, DefaultWaiterHandler
9
- from telegrinder.bot.dispatch.process import process_waiters, process_inner
10
- from telegrinder.types import Update
11
- import typing
2
+ from telegrinder.bot.dispatch.return_manager import MessageReturnManager
12
3
 
4
+ from .abc import BaseStateView
13
5
 
14
- class MessageView(ABCView, WithWaiter[int, MessageCute]):
15
- def __init__(self):
16
- self.handlers: typing.List[ABCHandler[MessageCute]] = []
17
- self.middlewares: typing.List[ABCMiddleware[MessageCute]] = []
18
- self.short_waiters: typing.Dict[int, Waiter] = {}
19
6
 
20
- def __call__(self, *rules: ABCRule, is_blocking: bool = True):
21
- def wrapper(func: typing.Callable[..., typing.Coroutine]):
22
- self.handlers.append(
23
- FuncHandler(func, list(rules), is_blocking, dataclass=None)
24
- )
25
- return func
7
+ class MessageView(BaseStateView[MessageCute]):
8
+ def __init__(self) -> None:
9
+ self.auto_rules = []
10
+ self.handlers = []
11
+ self.middlewares = []
12
+ self.return_manager = MessageReturnManager()
26
13
 
27
- return wrapper
14
+ def get_state_key(self, event: MessageCute) -> int | None:
15
+ return event.chat.id
28
16
 
29
- def load(self, external: "MessageView"):
30
- self.handlers.extend(external.handlers)
31
- self.middlewares.extend(external.middlewares)
32
- external.short_waiters = self.short_waiters
33
17
 
34
- async def check(self, event: Update) -> bool:
35
- return bool(event.message)
36
-
37
- async def process(self, event: Update, api: ABCAPI):
38
- msg = MessageCute(**event.message.to_dict(), api=api)
39
-
40
- if await process_waiters(
41
- self.short_waiters, msg.chat.id, msg, event, msg.answer
42
- ):
43
- return
44
-
45
- return await process_inner(msg, event, self.middlewares, self.handlers)
46
-
47
- async def wait_for_message(
48
- self,
49
- chat_id: int,
50
- *rules: ABCRule,
51
- default: typing.Optional[typing.Union[DefaultWaiterHandler, str]] = None
52
- ) -> typing.Tuple["MessageCute", dict]:
53
- return await self.wait_for_answer(chat_id, *rules, default=default)
18
+ __all__ = ("MessageView",)
@@ -0,0 +1,9 @@
1
+ from .machine import WaiterMachine
2
+ from .middleware import WaiterMiddleware
3
+ from .short_state import ShortState
4
+
5
+ __all__ = (
6
+ "ShortState",
7
+ "WaiterMachine",
8
+ "WaiterMiddleware",
9
+ )
@@ -0,0 +1,116 @@
1
+ import asyncio
2
+ import datetime
3
+ import typing
4
+
5
+ from telegrinder.api.abc import ABCAPI
6
+ from telegrinder.bot.dispatch.context import Context
7
+ from telegrinder.bot.rules.abc import ABCRule
8
+
9
+ from .middleware import WaiterMiddleware
10
+ from .short_state import Behaviour, EventModel, ShortState
11
+
12
+ Identificator: typing.TypeAlias = str | int
13
+ Storage: typing.TypeAlias = dict[str, dict[Identificator, "ShortState"]]
14
+
15
+ if typing.TYPE_CHECKING:
16
+ from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
17
+
18
+
19
+ class WaiterMachine:
20
+ def __init__(self) -> None:
21
+ self.storage: Storage = {}
22
+
23
+ async def drop(
24
+ self,
25
+ state_view: "ABCStateView[EventModel]",
26
+ id: Identificator,
27
+ **context,
28
+ ) -> None:
29
+ view_name = state_view.__class__.__name__
30
+ if view_name not in self.storage:
31
+ raise LookupError("No record of view {!r} found".format(view_name))
32
+
33
+ short_state = self.storage[view_name].pop(id, None)
34
+ if not short_state:
35
+ raise LookupError(
36
+ "Waiter with identificator {} is not found for view {!r}".format(
37
+ id,
38
+ view_name,
39
+ )
40
+ )
41
+
42
+ waiters = typing.cast(
43
+ typing.Iterable[asyncio.Future[typing.Any]],
44
+ short_state.event._waiters # type: ignore
45
+ )
46
+ for future in waiters:
47
+ future.cancel()
48
+
49
+ await self.call_behaviour(
50
+ state_view,
51
+ short_state.on_drop_behaviour,
52
+ short_state.event,
53
+ **context,
54
+ )
55
+
56
+ async def wait(
57
+ self,
58
+ state_view: "BaseStateView[EventModel]",
59
+ linked: EventModel | tuple[ABCAPI, Identificator],
60
+ *rules: ABCRule[EventModel],
61
+ default: Behaviour = None,
62
+ on_drop: Behaviour = None,
63
+ expiration: datetime.timedelta | int | None = None,
64
+ ) -> tuple[EventModel, Context]:
65
+ if isinstance(expiration, int):
66
+ expiration = datetime.timedelta(seconds=expiration)
67
+
68
+ api: ABCAPI
69
+ key: Identificator
70
+ event = asyncio.Event()
71
+ if isinstance(linked, tuple):
72
+ api, key = linked
73
+ else:
74
+ api, key = linked.ctx_api, state_view.get_state_key(linked) # type: ignore
75
+ if not key:
76
+ raise RuntimeError("Unable to get state key.")
77
+
78
+ short_state = ShortState(
79
+ key,
80
+ ctx_api=api,
81
+ event=event,
82
+ rules=rules,
83
+ expiration=expiration,
84
+ default_behaviour=default,
85
+ on_drop_behaviour=on_drop,
86
+ )
87
+
88
+ view_name = state_view.__class__.__name__
89
+ if view_name not in self.storage:
90
+ state_view.middlewares.insert(0, WaiterMiddleware(self, state_view))
91
+ self.storage[view_name] = {}
92
+
93
+ self.storage[view_name][key] = short_state
94
+
95
+ await event.wait()
96
+
97
+ e, ctx = getattr(event, "context")
98
+ self.storage[view_name].pop(key)
99
+
100
+ return e, ctx
101
+
102
+ async def call_behaviour(
103
+ self,
104
+ view: "ABCStateView[EventModel]",
105
+ behaviour: Behaviour,
106
+ event: asyncio.Event | EventModel,
107
+ **context,
108
+ ) -> None:
109
+ if behaviour is None:
110
+ return
111
+ # TODO: add behaviour check
112
+ # TODO: support view as a behaviour
113
+ await behaviour.run(event)
114
+
115
+
116
+ __all__ = ("WaiterMachine",)
@@ -0,0 +1,76 @@
1
+ import datetime
2
+ import typing
3
+
4
+ from telegrinder.bot.cute_types.base import BaseCute
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.dispatch.handler.func import FuncHandler
7
+ from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
8
+ from telegrinder.bot.dispatch.view.abc import ABCStateView
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from .machine import WaiterMachine
12
+ from .short_state import ShortState
13
+
14
+ EventType = typing.TypeVar("EventType", bound=BaseCute)
15
+
16
+
17
+ class WaiterMiddleware(ABCMiddleware[EventType]):
18
+ def __init__(
19
+ self,
20
+ machine: "WaiterMachine",
21
+ view: ABCStateView[EventType],
22
+ ) -> None:
23
+ self.machine = machine
24
+ self.view = view
25
+
26
+ async def pre(self, event: EventType, ctx: Context) -> bool:
27
+ if not self.view or not hasattr(self.view, "get_state_key"):
28
+ raise RuntimeError(
29
+ "WaiterMiddleware cannot be used inside a view which doesn't "
30
+ "provide get_state_key (ABCStateView Protocol)."
31
+ )
32
+
33
+ view_name = self.view.__class__.__name__
34
+ if view_name not in self.machine.storage:
35
+ return True
36
+
37
+ key = self.view.get_state_key(event)
38
+ if key is None:
39
+ raise RuntimeError("Unable to get state key.")
40
+
41
+ short_state: typing.Optional["ShortState"] = self.machine.storage[view_name].get(key)
42
+ if not short_state:
43
+ return True
44
+
45
+ if (
46
+ short_state.expiration is not None
47
+ and datetime.datetime.now() >= short_state.expiration
48
+ ):
49
+ await self.machine.drop(self.view, short_state.key)
50
+ return True
51
+
52
+ handler = FuncHandler(
53
+ self.pass_runtime, list(short_state.rules), dataclass=None
54
+ )
55
+ handler.ctx.set("short_state", short_state)
56
+ result = await handler.check(event.ctx_api, ctx.raw_update, ctx)
57
+
58
+ if result is True:
59
+ await handler.run(event)
60
+
61
+ elif short_state.default_behaviour is not None:
62
+ await self.machine.call_behaviour(
63
+ self.view,
64
+ short_state.default_behaviour,
65
+ event,
66
+ **handler.ctx,
67
+ )
68
+
69
+ return False
70
+
71
+ async def pass_runtime(self, event: EventType, short_state: "ShortState[EventType]", ctx: Context) -> None:
72
+ setattr(short_state.event, "context", (event, ctx))
73
+ short_state.event.set()
74
+
75
+
76
+ __all__ = ("WaiterMiddleware",)
@@ -0,0 +1,37 @@
1
+ import asyncio
2
+ import datetime
3
+ import typing
4
+
5
+ from telegrinder.api.abc import ABCAPI
6
+ from telegrinder.bot.cute_types import BaseCute
7
+ from telegrinder.bot.dispatch.handler.abc import ABCHandler
8
+ from telegrinder.bot.rules.abc import ABCRule
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from .machine import Identificator
12
+
13
+ EventModel = typing.TypeVar("EventModel", bound=BaseCute)
14
+ Behaviour = ABCHandler | None
15
+
16
+
17
+ class ShortState(typing.Generic[EventModel]):
18
+ def __init__(
19
+ self,
20
+ key: "Identificator",
21
+ ctx_api: ABCAPI,
22
+ event: asyncio.Event,
23
+ rules: tuple[ABCRule[EventModel], ...],
24
+ expiration: datetime.timedelta | None = None,
25
+ default_behaviour: Behaviour | None = None,
26
+ on_drop_behaviour: Behaviour | None = None,
27
+ ) -> None:
28
+ self.key = key
29
+ self.ctx_api = ctx_api
30
+ self.event = event
31
+ self.rules = rules
32
+ self.default_behaviour = default_behaviour
33
+ self.expiration = (datetime.datetime.now() + expiration) if expiration else None
34
+ self.on_drop_behaviour = on_drop_behaviour
35
+
36
+
37
+ __all__ = ("ShortState",)
@@ -1,2 +1,4 @@
1
1
  from .abc import ABCPolling
2
2
  from .polling import Polling
3
+
4
+ __all__ = ("ABCPolling", "Polling")
@@ -1,18 +1,25 @@
1
1
  import typing
2
2
  from abc import ABC, abstractmethod
3
+
4
+ import msgspec
5
+
3
6
  from telegrinder.types import Update
4
- from telegrinder.model import Raw
5
7
 
6
8
 
7
9
  class ABCPolling(ABC):
10
+ offset: int
11
+
8
12
  @abstractmethod
9
- async def get_updates(self) -> typing.List[Raw]:
13
+ async def get_updates(self) -> list[msgspec.Raw]:
10
14
  pass
11
15
 
12
16
  @abstractmethod
13
- async def listen(self) -> typing.AsyncIterator[typing.List[Update]]:
14
- pass
17
+ async def listen(self) -> typing.AsyncGenerator[list[Update], None]:
18
+ yield []
15
19
 
16
20
  @abstractmethod
17
21
  def stop(self) -> None:
18
22
  pass
23
+
24
+
25
+ __all__ = ("ABCPolling",)