telegrinder 0.1.dev20__py3-none-any.whl → 0.1.dev159__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 (132) 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 +47 -28
  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 +138 -0
  11. telegrinder/bot/cute_types/callback_query.py +458 -15
  12. telegrinder/bot/cute_types/inline_query.py +30 -24
  13. telegrinder/bot/cute_types/message.py +2982 -78
  14. telegrinder/bot/cute_types/update.py +30 -0
  15. telegrinder/bot/cute_types/utils.py +794 -0
  16. telegrinder/bot/dispatch/__init__.py +56 -3
  17. telegrinder/bot/dispatch/abc.py +9 -7
  18. telegrinder/bot/dispatch/composition.py +74 -0
  19. telegrinder/bot/dispatch/context.py +71 -0
  20. telegrinder/bot/dispatch/dispatch.py +86 -49
  21. telegrinder/bot/dispatch/handler/__init__.py +3 -0
  22. telegrinder/bot/dispatch/handler/abc.py +11 -5
  23. telegrinder/bot/dispatch/handler/func.py +41 -32
  24. telegrinder/bot/dispatch/handler/message_reply.py +46 -0
  25. telegrinder/bot/dispatch/middleware/__init__.py +2 -0
  26. telegrinder/bot/dispatch/middleware/abc.py +10 -4
  27. telegrinder/bot/dispatch/process.py +53 -49
  28. telegrinder/bot/dispatch/return_manager/__init__.py +19 -0
  29. telegrinder/bot/dispatch/return_manager/abc.py +95 -0
  30. telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
  31. telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
  32. telegrinder/bot/dispatch/return_manager/message.py +25 -0
  33. telegrinder/bot/dispatch/view/__init__.py +14 -2
  34. telegrinder/bot/dispatch/view/abc.py +128 -2
  35. telegrinder/bot/dispatch/view/box.py +38 -0
  36. telegrinder/bot/dispatch/view/callback_query.py +13 -39
  37. telegrinder/bot/dispatch/view/inline_query.py +11 -39
  38. telegrinder/bot/dispatch/view/message.py +11 -47
  39. telegrinder/bot/dispatch/waiter_machine/__init__.py +9 -0
  40. telegrinder/bot/dispatch/waiter_machine/machine.py +116 -0
  41. telegrinder/bot/dispatch/waiter_machine/middleware.py +76 -0
  42. telegrinder/bot/dispatch/waiter_machine/short_state.py +37 -0
  43. telegrinder/bot/polling/__init__.py +2 -0
  44. telegrinder/bot/polling/abc.py +11 -4
  45. telegrinder/bot/polling/polling.py +89 -40
  46. telegrinder/bot/rules/__init__.py +91 -5
  47. telegrinder/bot/rules/abc.py +81 -63
  48. telegrinder/bot/rules/adapter/__init__.py +11 -0
  49. telegrinder/bot/rules/adapter/abc.py +21 -0
  50. telegrinder/bot/rules/adapter/errors.py +5 -0
  51. telegrinder/bot/rules/adapter/event.py +49 -0
  52. telegrinder/bot/rules/adapter/raw_update.py +24 -0
  53. telegrinder/bot/rules/callback_data.py +159 -38
  54. telegrinder/bot/rules/command.py +116 -0
  55. telegrinder/bot/rules/enum_text.py +28 -0
  56. telegrinder/bot/rules/func.py +17 -17
  57. telegrinder/bot/rules/fuzzy.py +13 -10
  58. telegrinder/bot/rules/inline.py +61 -0
  59. telegrinder/bot/rules/integer.py +12 -7
  60. telegrinder/bot/rules/is_from.py +148 -7
  61. telegrinder/bot/rules/markup.py +21 -18
  62. telegrinder/bot/rules/mention.py +17 -0
  63. telegrinder/bot/rules/message_entities.py +33 -0
  64. telegrinder/bot/rules/regex.py +27 -19
  65. telegrinder/bot/rules/rule_enum.py +74 -0
  66. telegrinder/bot/rules/start.py +25 -13
  67. telegrinder/bot/rules/text.py +23 -14
  68. telegrinder/bot/scenario/__init__.py +2 -0
  69. telegrinder/bot/scenario/abc.py +12 -5
  70. telegrinder/bot/scenario/checkbox.py +48 -30
  71. telegrinder/bot/scenario/choice.py +16 -10
  72. telegrinder/client/__init__.py +3 -1
  73. telegrinder/client/abc.py +26 -16
  74. telegrinder/client/aiohttp.py +54 -32
  75. telegrinder/model.py +119 -40
  76. telegrinder/modules.py +189 -21
  77. telegrinder/msgspec_json.py +14 -0
  78. telegrinder/msgspec_utils.py +227 -0
  79. telegrinder/node/__init__.py +31 -0
  80. telegrinder/node/attachment.py +71 -0
  81. telegrinder/node/base.py +93 -0
  82. telegrinder/node/composer.py +71 -0
  83. telegrinder/node/container.py +22 -0
  84. telegrinder/node/message.py +18 -0
  85. telegrinder/node/rule.py +56 -0
  86. telegrinder/node/source.py +31 -0
  87. telegrinder/node/text.py +13 -0
  88. telegrinder/node/tools/__init__.py +3 -0
  89. telegrinder/node/tools/generator.py +40 -0
  90. telegrinder/node/update.py +12 -0
  91. telegrinder/rules.py +1 -1
  92. telegrinder/tools/__init__.py +138 -4
  93. telegrinder/tools/buttons.py +89 -51
  94. telegrinder/tools/error_handler/__init__.py +8 -0
  95. telegrinder/tools/error_handler/abc.py +30 -0
  96. telegrinder/tools/error_handler/error_handler.py +156 -0
  97. telegrinder/tools/formatting/__init__.py +81 -3
  98. telegrinder/tools/formatting/html.py +283 -37
  99. telegrinder/tools/formatting/links.py +32 -0
  100. telegrinder/tools/formatting/spec_html_formats.py +121 -0
  101. telegrinder/tools/global_context/__init__.py +12 -0
  102. telegrinder/tools/global_context/abc.py +66 -0
  103. telegrinder/tools/global_context/global_context.py +451 -0
  104. telegrinder/tools/global_context/telegrinder_ctx.py +25 -0
  105. telegrinder/tools/i18n/__init__.py +12 -0
  106. telegrinder/tools/i18n/base.py +31 -0
  107. telegrinder/tools/i18n/middleware/__init__.py +3 -0
  108. telegrinder/tools/i18n/middleware/base.py +26 -0
  109. telegrinder/tools/i18n/simple.py +48 -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 +653 -0
  121. telegrinder/types/methods.py +4107 -1279
  122. telegrinder/types/objects.py +4771 -1745
  123. {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev159.dist-info}/LICENSE +2 -1
  124. telegrinder-0.1.dev159.dist-info/METADATA +109 -0
  125. telegrinder-0.1.dev159.dist-info/RECORD +126 -0
  126. {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev159.dist-info}/WHEEL +1 -1
  127. telegrinder/bot/dispatch/waiter.py +0 -38
  128. telegrinder/result.py +0 -38
  129. telegrinder/tools/formatting/abc.py +0 -52
  130. telegrinder/tools/formatting/markdown.py +0 -57
  131. telegrinder-0.1.dev20.dist-info/METADATA +0 -22
  132. telegrinder-0.1.dev20.dist-info/RECORD +0 -71
@@ -1,5 +1,58 @@
1
1
  from .abc import ABCDispatch
2
- from .dispatch import ABCDispatch, Dispatch, ABCRule
3
- from .handler import ABCHandler, FuncHandler
2
+ from .composition import CompositionDispatch
3
+ from .context import Context
4
+ from .dispatch import ABCRule, Dispatch, TelegrinderCtx
5
+ from .handler import ABCHandler, FuncHandler, MessageReplyHandler
4
6
  from .middleware import ABCMiddleware
5
- from .view import ABCView, MessageView, CallbackQueryView, InlineQueryView
7
+ from .process import check_rule, process_inner
8
+ from .return_manager import (
9
+ ABCReturnManager,
10
+ BaseReturnManager,
11
+ CallbackQueryReturnManager,
12
+ InlineQueryReturnManager,
13
+ Manager,
14
+ MessageReturnManager,
15
+ register_manager,
16
+ )
17
+ from .view import (
18
+ ABCStateView,
19
+ ABCView,
20
+ BaseStateView,
21
+ BaseView,
22
+ CallbackQueryView,
23
+ InlineQueryView,
24
+ MessageView,
25
+ ViewBox,
26
+ )
27
+ from .waiter_machine import WaiterMachine
28
+
29
+ __all__ = (
30
+ "ABCDispatch",
31
+ "ABCHandler",
32
+ "ABCMiddleware",
33
+ "ABCReturnManager",
34
+ "ABCRule",
35
+ "ABCStateView",
36
+ "ABCView",
37
+ "BaseReturnManager",
38
+ "BaseStateView",
39
+ "BaseView",
40
+ "CallbackQueryReturnManager",
41
+ "CallbackQueryView",
42
+ "CompositionDispatch",
43
+ "Context",
44
+ "Dispatch",
45
+ "FuncHandler",
46
+ "InlineQueryReturnManager",
47
+ "InlineQueryView",
48
+ "Manager",
49
+ "MessageReplyHandler",
50
+ "MessageReturnManager",
51
+ "MessageView",
52
+ "TelegrinderCtx",
53
+ "ViewBox",
54
+ "WaiterMachine",
55
+ "check_rule",
56
+ "process_inner",
57
+ "register_manager",
58
+ )
@@ -1,19 +1,21 @@
1
+ import typing
1
2
  from abc import ABC, abstractmethod
3
+
2
4
  from telegrinder.api.abc import ABCAPI
5
+ from telegrinder.tools.global_context import ABCGlobalContext
3
6
  from telegrinder.types import Update
4
- from .view.abc import ABCView
5
- import typing
6
7
 
7
8
 
8
9
  class ABCDispatch(ABC):
9
- @abstractmethod
10
- def feed(self, event: Update, api: ABCAPI) -> bool:
11
- pass
10
+ global_context: ABCGlobalContext
12
11
 
13
12
  @abstractmethod
14
- def load(self, external: "ABCDispatch"):
13
+ async def feed(self, event: Update, api: ABCAPI) -> bool:
15
14
  pass
16
15
 
17
16
  @abstractmethod
18
- def mount(self, view_t: typing.Type["ABCView"], name: str):
17
+ def load(self, external: typing.Self):
19
18
  pass
19
+
20
+
21
+ __all__ = ("ABCDispatch",)
@@ -0,0 +1,74 @@
1
+ import inspect
2
+ import typing
3
+
4
+ from telegrinder.api.abc import ABCAPI
5
+ from telegrinder.bot.cute_types import UpdateCute
6
+ from telegrinder.bot.dispatch.abc import ABCDispatch
7
+ from telegrinder.node import (
8
+ ComposeError,
9
+ ContainerNode,
10
+ Node,
11
+ NodeCollection,
12
+ NodeSession,
13
+ compose_node,
14
+ )
15
+ from telegrinder.tools import magic_bundle
16
+ from telegrinder.types import Update
17
+
18
+
19
+ class Composition:
20
+ nodes: dict[str, type[Node]]
21
+
22
+ def __init__(self, func: typing.Callable, is_blocking: bool) -> None:
23
+ self.func = func
24
+ self.nodes = {
25
+ name: parameter.annotation
26
+ for name, parameter in inspect.signature(func).parameters.items()
27
+ }
28
+ self.is_blocking = is_blocking
29
+
30
+ async def compose_nodes(self, update: UpdateCute) -> NodeCollection | None:
31
+ nodes: dict[str, NodeSession] = {}
32
+ for name, node_t in self.nodes.items():
33
+ try:
34
+ nodes[name] = await compose_node(node_t, update)
35
+ except ComposeError as err:
36
+ await NodeCollection(nodes).close_all()
37
+ return None
38
+ return NodeCollection(nodes)
39
+
40
+ async def __call__(self, **kwargs) -> typing.Any:
41
+ return await self.func(**magic_bundle(self.func, kwargs, start_idx=0, bundle_ctx=False)) # type: ignore
42
+
43
+
44
+ class CompositionDispatch(ABCDispatch):
45
+ def __init__(self) -> None:
46
+ self.compositions: list[Composition] = []
47
+
48
+ async def feed(self, event: Update, api: ABCAPI) -> bool:
49
+ update = UpdateCute(**event.to_dict(), api=api)
50
+ is_found = False
51
+ for composition in self.compositions:
52
+ nodes = await composition.compose_nodes(update)
53
+ if nodes is not None:
54
+ result = await composition(**nodes.values())
55
+ await nodes.close_all(with_value=result)
56
+ if composition.is_blocking:
57
+ return True
58
+ is_found = True
59
+ return is_found
60
+
61
+ def load(self, external: typing.Self):
62
+ self.compositions.extend(external.compositions)
63
+
64
+ def __call__(self, *container_nodes: type[Node], is_blocking: bool = True):
65
+ def wrapper(func: typing.Callable):
66
+ composition = Composition(func, is_blocking)
67
+ if container_nodes:
68
+ composition.nodes["container"] = ContainerNode.link_nodes(list(container_nodes))
69
+ self.compositions.append(composition)
70
+ return func
71
+ return wrapper
72
+
73
+
74
+ __all__ = ("Composition", "CompositionDispatch")
@@ -0,0 +1,71 @@
1
+ import enum
2
+ import typing
3
+
4
+ from telegrinder.types import Update
5
+
6
+ T = typing.TypeVar("T")
7
+
8
+ Key: typing.TypeAlias = str | enum.Enum
9
+ AnyValue: typing.TypeAlias = typing.Any
10
+
11
+
12
+ @typing.dataclass_transform(kw_only_default=True, order_default=True)
13
+ class Context(dict[str, AnyValue]):
14
+ """Context class for rules and middlewares.
15
+ ```
16
+ class MyRule(ABCRule[T]):
17
+ adapter: ABCAdapter[Update, T] = RawUpdateAdapter()
18
+
19
+ async def check(self, event: T, ctx: Context) -> bool:
20
+ ctx.set("value", (await event.ctx_api.get_me()).unwrap())
21
+ return True
22
+ ```
23
+ """
24
+
25
+ raw_update: Update
26
+
27
+ def __init__(self, **kwargs: AnyValue) -> None:
28
+ cls_vars = vars(self.__class__)
29
+ defaults = {}
30
+ for k in self.__class__.__annotations__:
31
+ if k in cls_vars:
32
+ defaults[k] = cls_vars[k]
33
+ delattr(self.__class__, k)
34
+ dict.__init__(self, **defaults | kwargs)
35
+
36
+ def __setitem__(self, __key: Key, __value: AnyValue) -> None:
37
+ dict.__setitem__(self, self.key_to_str(__key), __value)
38
+
39
+ def __getitem__(self, __key: Key) -> AnyValue:
40
+ return dict.__getitem__(self, self.key_to_str(__key))
41
+
42
+ def __delitem__(self, __key: Key) -> None:
43
+ dict.__delitem__(self, self.key_to_str(__key))
44
+
45
+ def __setattr__(self, __name: str, __value: AnyValue) -> None:
46
+ self.__setitem__(__name, __value)
47
+
48
+ def __getattr__(self, __name: str) -> AnyValue:
49
+ return self.__getitem__(__name)
50
+
51
+ def __delattr__(self, __name: str) -> None:
52
+ self.__delitem__(__name)
53
+
54
+ @staticmethod
55
+ def key_to_str(key: Key) -> str:
56
+ return key if isinstance(key, str) else str(key.value)
57
+
58
+ def copy(self) -> typing.Self:
59
+ return self.__class__(**self)
60
+
61
+ def set(self, key: Key, value: AnyValue) -> None:
62
+ self[key] = value
63
+
64
+ def get(self, key: Key, default: T | None = None) -> T | AnyValue:
65
+ return dict.get(self, key, default)
66
+
67
+ def delete(self, key: Key) -> None:
68
+ del self[key]
69
+
70
+
71
+ __all__ = ("Context",)
@@ -1,68 +1,76 @@
1
1
  import asyncio
2
+ import dataclasses
3
+ import typing
4
+
5
+ from vbml.patcher import Patcher
2
6
 
3
- from .abc import ABCDispatch
4
- from telegrinder.bot.rules import ABCRule
5
- from .handler import ABCHandler, FuncHandler
6
- from telegrinder.types import Update
7
7
  from telegrinder.api.abc import ABCAPI
8
+ from telegrinder.bot.cute_types.base import BaseCute
9
+ from telegrinder.bot.rules import ABCRule
8
10
  from telegrinder.modules import logger
9
- from .view import ABCView, MessageView, CallbackQueryView, InlineQueryView
10
- import typing
11
+ from telegrinder.tools.global_context import TelegrinderCtx
12
+ from telegrinder.types import Update
13
+
14
+ from .abc import ABCDispatch
15
+ from .handler import ABCHandler, FuncHandler
16
+ from .handler.func import ErrorHandlerT
17
+ from .view.box import CallbackQueryViewT, InlineQueryViewT, MessageViewT, ViewBox
11
18
 
12
19
  T = typing.TypeVar("T")
13
20
 
21
+ Event = typing.TypeVar("Event", bound=BaseCute)
22
+ R = typing.TypeVar("R")
23
+ P = typing.ParamSpec("P")
24
+
14
25
  DEFAULT_DATACLASS = Update
15
26
 
16
27
 
17
- class Dispatch(ABCDispatch):
18
- def __init__(self):
19
- self.default_handlers: typing.List[ABCHandler] = []
20
- self.message = MessageView()
21
- self.callback_query = CallbackQueryView()
22
- self.inline_query = InlineQueryView()
23
- self.views = ["message", "callback_query", "inline_query"]
28
+ @dataclasses.dataclass(repr=False, kw_only=True)
29
+ class Dispatch(
30
+ ABCDispatch,
31
+ ViewBox[CallbackQueryViewT, InlineQueryViewT, MessageViewT],
32
+ ):
33
+ global_context: TelegrinderCtx = dataclasses.field(
34
+ init=False,
35
+ default_factory=lambda: TelegrinderCtx(),
36
+ )
37
+ default_handlers: list[ABCHandler] = dataclasses.field(
38
+ init=False,
39
+ default_factory=lambda: [],
40
+ )
41
+
42
+ def __repr__(self) -> str:
43
+ return "Dispatch(%s)" % ", ".join(
44
+ f"{k}={v!r}" for k, v in self.__dict__.items()
45
+ )
46
+
47
+ @property
48
+ def patcher(self) -> Patcher:
49
+ """Alias `patcher` to get `vbml.Patcher` from the global context"""
50
+ return self.global_context.vbml_patcher
24
51
 
25
52
  def handle(
26
53
  self,
27
54
  *rules: ABCRule,
28
55
  is_blocking: bool = True,
29
- dataclass: typing.Any = DEFAULT_DATACLASS,
56
+ dataclass: type[typing.Any] = DEFAULT_DATACLASS,
57
+ error_handler: ErrorHandlerT | None = None,
30
58
  ):
31
59
  def wrapper(func: typing.Callable):
32
- self.default_handlers.append(
33
- FuncHandler(func, list(rules), is_blocking, dataclass)
60
+ handler = FuncHandler(
61
+ func, list(rules), is_blocking, dataclass, error_handler,
34
62
  )
35
- return func
63
+ self.default_handlers.append(handler)
64
+ return handler
36
65
 
37
66
  return wrapper
38
67
 
39
- def get_views(self) -> typing.Iterator[ABCView]:
40
- for view_name in self.views:
41
- view = getattr(self, view_name)
42
- assert view, f"View {view_name} is undefined in dispatch"
43
- yield view
44
-
45
- def get_view(self, view_t: typing.Type[T], name: str) -> typing.Optional[T]:
46
- if name not in self.views:
47
- return None
48
- view = getattr(self, name)
49
- assert isinstance(view, view_t)
50
- return view # type: ignore
51
-
52
- def load(self, external: "Dispatch"):
53
- for view_name in self.views:
54
- view = getattr(self, view_name)
55
- assert view, f"View {view_name} is undefined in dispatch"
56
- view_external = getattr(external, view_name)
57
- assert view_external, f"View {view_name} is undefined in external dispatch"
58
- view.load(view_external)
59
-
60
68
  async def feed(self, event: Update, api: ABCAPI) -> bool:
61
- logger.debug("processing update (update_id={})", event.update_id)
62
- for view in self.get_views():
69
+ logger.debug("Processing update (update_id={})", event.update_id)
70
+ for view in self.get_views().values():
63
71
  if await view.check(event):
64
72
  logger.debug(
65
- "update {} matched view {}",
73
+ "Update (update_id={}) matched view {!r}",
66
74
  event.update_id,
67
75
  view.__class__.__name__,
68
76
  )
@@ -70,18 +78,47 @@ class Dispatch(ABCDispatch):
70
78
  return True
71
79
 
72
80
  loop = asyncio.get_running_loop()
73
- assert loop, "No running loop"
74
-
75
81
  found = False
76
82
  for handler in self.default_handlers:
77
- result = await handler.check(api, event)
78
- if result:
83
+ if await handler.check(api, event):
79
84
  found = True
80
85
  loop.create_task(handler.run(event))
81
86
  if handler.is_blocking:
82
- return True
87
+ break
83
88
  return found
84
89
 
85
- def mount(self, view_t: typing.Type["ABCView"], name: str):
86
- self.views.append(name)
87
- setattr(self, name, view_t)
90
+ def load(self, external: typing.Self):
91
+ view_external = external.get_views()
92
+ for name, view in self.get_views().items():
93
+ assert (
94
+ name in view_external
95
+ ), f"View {name!r} is undefined in external dispatch."
96
+ view.load(view_external[name])
97
+ setattr(external, name, view)
98
+
99
+ @classmethod
100
+ def to_handler(
101
+ cls,
102
+ *rules: ABCRule[BaseCute],
103
+ is_blocking: bool = True,
104
+ error_handler: ErrorHandlerT | None = None,
105
+ ):
106
+ def wrapper(
107
+ func: typing.Callable[typing.Concatenate[T, P], typing.Awaitable[R]]
108
+ ) -> FuncHandler[
109
+ BaseCute,
110
+ typing.Callable[typing.Concatenate[T, P], typing.Awaitable[R]],
111
+ ErrorHandlerT,
112
+ ]:
113
+ return FuncHandler(
114
+ func,
115
+ list(rules),
116
+ is_blocking=is_blocking,
117
+ dataclass=None,
118
+ error_handler=error_handler,
119
+ )
120
+
121
+ return wrapper
122
+
123
+
124
+ __all__ = ("Dispatch",)
@@ -1,2 +1,5 @@
1
1
  from .abc import ABCHandler
2
2
  from .func import FuncHandler
3
+ from .message_reply import MessageReplyHandler
4
+
5
+ __all__ = ("ABCHandler", "FuncHandler", "MessageReplyHandler")
@@ -1,19 +1,25 @@
1
+ import typing
1
2
  from abc import ABC, abstractmethod
2
- from telegrinder.types import Update
3
+
3
4
  from telegrinder.api.abc import ABCAPI
4
- import typing
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.model import Model
7
+ from telegrinder.types import Update
5
8
 
6
- T = typing.TypeVar("T")
9
+ T = typing.TypeVar("T", bound=Model)
7
10
 
8
11
 
9
12
  class ABCHandler(ABC, typing.Generic[T]):
10
13
  is_blocking: bool
11
- ctx: dict
14
+ ctx: Context
12
15
 
13
16
  @abstractmethod
14
17
  async def run(self, event: T) -> typing.Any:
15
18
  pass
16
19
 
17
20
  @abstractmethod
18
- async def check(self, api: ABCAPI, event: Update) -> bool:
21
+ async def check(self, api: ABCAPI, event: Update, ctx: Context | None = None) -> bool:
19
22
  pass
23
+
24
+
25
+ __all__ = ("ABCHandler",)
@@ -1,49 +1,58 @@
1
- import typing
2
- import types
3
- from telegrinder.bot.rules import ABCRule
4
- from telegrinder.tools import magic_bundle
5
- from telegrinder.types import Update
1
+ import typing_extensions as typing
2
+
6
3
  from telegrinder.api.abc import ABCAPI
7
- from .abc import ABCHandler
4
+ from telegrinder.bot.cute_types import BaseCute
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.dispatch.process import check_rule
8
7
  from telegrinder.modules import logger
8
+ from telegrinder.tools.error_handler import ABCErrorHandler, ErrorHandler
9
+ from telegrinder.types import Update
10
+
11
+ from .abc import ABCHandler
12
+
13
+ if typing.TYPE_CHECKING:
14
+ from telegrinder.bot.rules import ABCRule
9
15
 
10
- T = typing.TypeVar("T")
16
+ F = typing.TypeVar("F", bound=typing.Callable[typing.Concatenate[typing.Any, ...], typing.Awaitable])
17
+ EventT = typing.TypeVar("EventT", bound=BaseCute)
18
+ ErrorHandlerT = typing.TypeVar("ErrorHandlerT", bound=ABCErrorHandler, default=ErrorHandler)
11
19
 
12
20
 
13
- class FuncHandler(ABCHandler, typing.Generic[T]):
21
+ class FuncHandler(ABCHandler[EventT], typing.Generic[EventT, F, ErrorHandlerT]):
14
22
  def __init__(
15
23
  self,
16
- func: typing.Union[types.FunctionType, typing.Callable],
17
- rules: typing.List[ABCRule],
24
+ func: F,
25
+ rules: list["ABCRule[EventT]"],
18
26
  is_blocking: bool = True,
19
- dataclass: typing.Optional[typing.Any] = dict,
27
+ dataclass: type[typing.Any] | None = dict,
28
+ error_handler: ErrorHandlerT | None = None,
20
29
  ):
21
30
  self.func = func
22
31
  self.is_blocking = is_blocking
23
32
  self.rules = rules
24
33
  self.dataclass = dataclass
25
- self.ctx = {}
26
-
27
- async def check(self, api: ABCAPI, event: Update) -> bool:
28
- self.ctx = {}
34
+ self.error_handler: ErrorHandlerT = error_handler or ErrorHandler() # type: ignore
35
+ self.ctx = Context()
36
+
37
+ @property
38
+ def on_error(self):
39
+ return self.error_handler.catch
40
+
41
+ async def check(self, api: ABCAPI, event: Update, ctx: Context | None = None) -> bool:
42
+ ctx = ctx or Context()
43
+ preset_ctx = self.ctx.copy()
44
+ self.ctx |= ctx
29
45
  for rule in self.rules:
30
- e = event
31
- if rule.__event__ is None:
32
- pass
33
- else:
34
- event_dict = event.to_dict()
35
- if event_dict.get(rule.__event__.name) is None:
36
- continue
37
- e = rule.__event__.dataclass(
38
- **event_dict[rule.__event__.name].to_dict(),
39
- api=api,
40
- )
41
- if not await rule.run_check(e, self.ctx):
42
- logger.debug("Rule {} failed", rule)
46
+ if not await check_rule(api, rule, event, self.ctx):
47
+ logger.debug("Rule {!r} failed!", rule)
48
+ self.ctx = preset_ctx
43
49
  return False
44
50
  return True
45
51
 
46
- async def run(self, event: T) -> typing.Any:
47
- if self.dataclass:
48
- event = self.dataclass(**event)
49
- return await self.func(event, **magic_bundle(self.func, self.ctx))
52
+ async def run(self, event: EventT) -> typing.Any:
53
+ if self.dataclass is not None:
54
+ event = self.dataclass(**event.to_dict())
55
+ return (await self.error_handler.run(self.func, event, event.api, self.ctx)).unwrap()
56
+
57
+
58
+ __all__ = ("FuncHandler",)
@@ -0,0 +1,46 @@
1
+ import typing
2
+
3
+ from telegrinder.api.abc import ABCAPI
4
+ from telegrinder.bot.cute_types import MessageCute
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.dispatch.process import check_rule
7
+ from telegrinder.bot.rules.abc import ABCRule
8
+ from telegrinder.modules import logger
9
+ from telegrinder.msgspec_utils import Nothing
10
+ from telegrinder.types.objects import Update
11
+
12
+ from .abc import ABCHandler
13
+
14
+
15
+ class MessageReplyHandler(ABCHandler[MessageCute]):
16
+ def __init__(
17
+ self,
18
+ text: str,
19
+ *rules: ABCRule[MessageCute],
20
+ as_reply: bool = False,
21
+ is_blocking: bool = True,
22
+ ):
23
+ self.text = text
24
+ self.rules = list(rules)
25
+ self.as_reply = as_reply
26
+ self.is_blocking = is_blocking
27
+ self.dataclass = MessageCute
28
+ self.ctx = Context()
29
+
30
+ async def check(self, api: ABCAPI, event: Update, ctx: Context | None = None) -> bool:
31
+ ctx = ctx or Context()
32
+ self.ctx |= ctx
33
+ for rule in self.rules:
34
+ if not await check_rule(api, rule, event, self.ctx):
35
+ logger.debug("Rule {!r} failed!", rule)
36
+ return False
37
+ return True
38
+
39
+ async def run(self, event: MessageCute) -> typing.Any:
40
+ await event.answer(
41
+ text=self.text,
42
+ reply_to_message_id=(event.message_id if self.as_reply else Nothing),
43
+ )
44
+
45
+
46
+ __all__ = ("MessageReplyHandler",)
@@ -1 +1,3 @@
1
1
  from .abc import ABCMiddleware
2
+
3
+ __all__ = ("ABCMiddleware",)
@@ -1,12 +1,18 @@
1
- from abc import ABC
2
1
  import typing
2
+ from abc import ABC
3
+
4
+ from telegrinder.bot.cute_types.base import BaseCute
5
+ from telegrinder.bot.dispatch.context import Context
3
6
 
4
- T = typing.TypeVar("T")
7
+ T = typing.TypeVar("T", bound=BaseCute)
5
8
 
6
9
 
7
10
  class ABCMiddleware(ABC, typing.Generic[T]):
8
- async def pre(self, event: T, ctx: dict) -> bool:
11
+ async def pre(self, event: T, ctx: Context) -> bool:
9
12
  ...
10
13
 
11
- async def post(self, event: T, responses: typing.List[typing.Any], ctx: dict):
14
+ async def post(self, event: T, responses: list[typing.Any], ctx: Context):
12
15
  ...
16
+
17
+
18
+ __all__ = ("ABCMiddleware",)