telegrinder 0.3.4.post1__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 (169) hide show
  1. telegrinder/__init__.py +30 -31
  2. telegrinder/api/__init__.py +2 -1
  3. telegrinder/api/api.py +28 -20
  4. telegrinder/api/error.py +8 -4
  5. telegrinder/api/response.py +2 -2
  6. telegrinder/api/token.py +2 -2
  7. telegrinder/bot/__init__.py +6 -0
  8. telegrinder/bot/bot.py +38 -31
  9. telegrinder/bot/cute_types/__init__.py +2 -0
  10. telegrinder/bot/cute_types/base.py +54 -128
  11. telegrinder/bot/cute_types/callback_query.py +76 -61
  12. telegrinder/bot/cute_types/chat_join_request.py +4 -3
  13. telegrinder/bot/cute_types/chat_member_updated.py +28 -31
  14. telegrinder/bot/cute_types/inline_query.py +5 -4
  15. telegrinder/bot/cute_types/message.py +555 -602
  16. telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
  17. telegrinder/bot/cute_types/update.py +20 -12
  18. telegrinder/bot/cute_types/utils.py +3 -36
  19. telegrinder/bot/dispatch/__init__.py +4 -0
  20. telegrinder/bot/dispatch/abc.py +8 -9
  21. telegrinder/bot/dispatch/context.py +5 -7
  22. telegrinder/bot/dispatch/dispatch.py +85 -33
  23. telegrinder/bot/dispatch/handler/abc.py +5 -6
  24. telegrinder/bot/dispatch/handler/audio_reply.py +2 -2
  25. telegrinder/bot/dispatch/handler/base.py +3 -3
  26. telegrinder/bot/dispatch/handler/document_reply.py +2 -2
  27. telegrinder/bot/dispatch/handler/func.py +36 -42
  28. telegrinder/bot/dispatch/handler/media_group_reply.py +5 -4
  29. telegrinder/bot/dispatch/handler/message_reply.py +2 -2
  30. telegrinder/bot/dispatch/handler/photo_reply.py +2 -2
  31. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -2
  32. telegrinder/bot/dispatch/handler/video_reply.py +2 -2
  33. telegrinder/bot/dispatch/middleware/abc.py +83 -8
  34. telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
  35. telegrinder/bot/dispatch/process.py +44 -50
  36. telegrinder/bot/dispatch/return_manager/__init__.py +2 -0
  37. telegrinder/bot/dispatch/return_manager/abc.py +6 -10
  38. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
  39. telegrinder/bot/dispatch/view/__init__.py +2 -0
  40. telegrinder/bot/dispatch/view/abc.py +10 -6
  41. telegrinder/bot/dispatch/view/base.py +81 -50
  42. telegrinder/bot/dispatch/view/box.py +20 -9
  43. telegrinder/bot/dispatch/view/callback_query.py +3 -4
  44. telegrinder/bot/dispatch/view/chat_join_request.py +2 -7
  45. telegrinder/bot/dispatch/view/chat_member.py +3 -5
  46. telegrinder/bot/dispatch/view/inline_query.py +3 -4
  47. telegrinder/bot/dispatch/view/message.py +3 -4
  48. telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
  49. telegrinder/bot/dispatch/view/raw.py +42 -40
  50. telegrinder/bot/dispatch/waiter_machine/actions.py +5 -4
  51. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +0 -0
  52. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +0 -0
  53. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +9 -7
  54. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  55. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +3 -2
  56. telegrinder/bot/dispatch/waiter_machine/machine.py +113 -34
  57. telegrinder/bot/dispatch/waiter_machine/middleware.py +15 -10
  58. telegrinder/bot/dispatch/waiter_machine/short_state.py +7 -18
  59. telegrinder/bot/polling/polling.py +62 -54
  60. telegrinder/bot/rules/__init__.py +24 -1
  61. telegrinder/bot/rules/abc.py +17 -10
  62. telegrinder/bot/rules/callback_data.py +20 -61
  63. telegrinder/bot/rules/chat_join.py +6 -4
  64. telegrinder/bot/rules/command.py +4 -4
  65. telegrinder/bot/rules/enum_text.py +1 -4
  66. telegrinder/bot/rules/func.py +5 -3
  67. telegrinder/bot/rules/fuzzy.py +1 -1
  68. telegrinder/bot/rules/id.py +24 -0
  69. telegrinder/bot/rules/inline.py +6 -4
  70. telegrinder/bot/rules/integer.py +2 -1
  71. telegrinder/bot/rules/logic.py +18 -0
  72. telegrinder/bot/rules/markup.py +5 -6
  73. telegrinder/bot/rules/message.py +2 -4
  74. telegrinder/bot/rules/message_entities.py +1 -3
  75. telegrinder/bot/rules/node.py +15 -9
  76. telegrinder/bot/rules/payload.py +81 -0
  77. telegrinder/bot/rules/payment_invoice.py +29 -0
  78. telegrinder/bot/rules/regex.py +5 -6
  79. telegrinder/bot/rules/state.py +1 -3
  80. telegrinder/bot/rules/text.py +10 -5
  81. telegrinder/bot/rules/update.py +0 -0
  82. telegrinder/bot/scenario/abc.py +2 -4
  83. telegrinder/bot/scenario/checkbox.py +12 -14
  84. telegrinder/bot/scenario/choice.py +6 -9
  85. telegrinder/client/__init__.py +9 -1
  86. telegrinder/client/abc.py +35 -10
  87. telegrinder/client/aiohttp.py +28 -24
  88. telegrinder/client/form_data.py +31 -0
  89. telegrinder/client/sonic.py +212 -0
  90. telegrinder/model.py +38 -145
  91. telegrinder/modules.py +3 -1
  92. telegrinder/msgspec_utils.py +136 -68
  93. telegrinder/node/__init__.py +74 -13
  94. telegrinder/node/attachment.py +92 -16
  95. telegrinder/node/base.py +196 -68
  96. telegrinder/node/callback_query.py +17 -16
  97. telegrinder/node/command.py +3 -2
  98. telegrinder/node/composer.py +40 -75
  99. telegrinder/node/container.py +13 -7
  100. telegrinder/node/either.py +82 -0
  101. telegrinder/node/event.py +20 -31
  102. telegrinder/node/file.py +51 -0
  103. telegrinder/node/me.py +4 -5
  104. telegrinder/node/payload.py +78 -0
  105. telegrinder/node/polymorphic.py +27 -8
  106. telegrinder/node/rule.py +2 -6
  107. telegrinder/node/scope.py +4 -6
  108. telegrinder/node/source.py +37 -21
  109. telegrinder/node/text.py +20 -8
  110. telegrinder/node/tools/generator.py +7 -11
  111. telegrinder/py.typed +0 -0
  112. telegrinder/rules.py +0 -61
  113. telegrinder/tools/__init__.py +97 -38
  114. telegrinder/tools/adapter/__init__.py +19 -0
  115. telegrinder/tools/adapter/abc.py +49 -0
  116. telegrinder/tools/adapter/dataclass.py +56 -0
  117. telegrinder/{bot/rules → tools}/adapter/event.py +8 -10
  118. telegrinder/{bot/rules → tools}/adapter/node.py +8 -10
  119. telegrinder/{bot/rules → tools}/adapter/raw_event.py +2 -2
  120. telegrinder/{bot/rules → tools}/adapter/raw_update.py +2 -2
  121. telegrinder/tools/buttons.py +52 -26
  122. telegrinder/tools/callback_data_serilization/__init__.py +5 -0
  123. telegrinder/tools/callback_data_serilization/abc.py +51 -0
  124. telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
  125. telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
  126. telegrinder/tools/error_handler/abc.py +4 -7
  127. telegrinder/tools/error_handler/error.py +0 -0
  128. telegrinder/tools/error_handler/error_handler.py +34 -48
  129. telegrinder/tools/formatting/__init__.py +57 -37
  130. telegrinder/tools/formatting/deep_links.py +541 -0
  131. telegrinder/tools/formatting/{html.py → html_formatter.py} +51 -79
  132. telegrinder/tools/formatting/spec_html_formats.py +14 -60
  133. telegrinder/tools/functional.py +1 -5
  134. telegrinder/tools/global_context/global_context.py +26 -51
  135. telegrinder/tools/global_context/telegrinder_ctx.py +3 -3
  136. telegrinder/tools/i18n/abc.py +0 -0
  137. telegrinder/tools/i18n/middleware/abc.py +3 -6
  138. telegrinder/tools/input_file_directory.py +30 -0
  139. telegrinder/tools/keyboard.py +9 -9
  140. telegrinder/tools/lifespan.py +105 -0
  141. telegrinder/tools/limited_dict.py +5 -10
  142. telegrinder/tools/loop_wrapper/abc.py +7 -2
  143. telegrinder/tools/loop_wrapper/loop_wrapper.py +40 -95
  144. telegrinder/tools/magic.py +184 -34
  145. telegrinder/tools/state_storage/__init__.py +0 -0
  146. telegrinder/tools/state_storage/abc.py +5 -9
  147. telegrinder/tools/state_storage/memory.py +1 -1
  148. telegrinder/tools/strings.py +13 -0
  149. telegrinder/types/__init__.py +8 -0
  150. telegrinder/types/enums.py +31 -21
  151. telegrinder/types/input_file.py +51 -0
  152. telegrinder/types/methods.py +531 -109
  153. telegrinder/types/objects.py +934 -826
  154. telegrinder/verification_utils.py +0 -2
  155. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +2 -2
  156. telegrinder-0.4.0.dist-info/METADATA +144 -0
  157. telegrinder-0.4.0.dist-info/RECORD +182 -0
  158. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
  159. telegrinder/bot/rules/adapter/__init__.py +0 -17
  160. telegrinder/bot/rules/adapter/abc.py +0 -31
  161. telegrinder/node/message.py +0 -14
  162. telegrinder/node/update.py +0 -15
  163. telegrinder/tools/formatting/links.py +0 -38
  164. telegrinder/tools/kb_set/__init__.py +0 -4
  165. telegrinder/tools/kb_set/base.py +0 -15
  166. telegrinder/tools/kb_set/yaml.py +0 -63
  167. telegrinder-0.3.4.post1.dist-info/METADATA +0 -110
  168. telegrinder-0.3.4.post1.dist-info/RECORD +0 -165
  169. /telegrinder/{bot/rules → tools}/adapter/errors.py +0 -0
@@ -0,0 +1,42 @@
1
+ import typing
2
+
3
+ from fntypes.result import Result
4
+
5
+ from telegrinder.api.api import API
6
+ from telegrinder.api.error import APIError
7
+ from telegrinder.bot.cute_types.base import BaseCute, compose_method_params
8
+ from telegrinder.model import get_params
9
+ from telegrinder.tools.magic import shortcut
10
+ from telegrinder.types.objects import PreCheckoutQuery, User
11
+
12
+
13
+ class PreCheckoutQueryCute(BaseCute[PreCheckoutQuery], PreCheckoutQuery, kw_only=True):
14
+ api: API
15
+
16
+ @property
17
+ def from_user(self) -> User:
18
+ return self.from_
19
+
20
+ @shortcut("answer_pre_checkout_query", custom_params={"pre_checkout_query_id"})
21
+ async def answer(
22
+ self,
23
+ ok: bool,
24
+ *,
25
+ error_message: str | None = None,
26
+ pre_checkout_query_id: str | None = None,
27
+ **other: typing.Any,
28
+ ) -> Result[bool, APIError]:
29
+ """Shortcut `API.answer_pre_checkout_query()`, see the [documentation](https://core.telegram.org/bots/api#answerprecheckoutquery)
30
+
31
+ Once the user has confirmed their payment and shipping details, the Bot
32
+ API sends the final confirmation in the form of an Update with the field pre_checkout_query.
33
+ Use this method to respond to such pre-checkout queries. On success, True
34
+ is returned. Note: The Bot API must receive an answer within 10 seconds after
35
+ the pre-checkout query was sent."""
36
+ params = compose_method_params(
37
+ get_params(locals()), self, default_params={("pre_checkout_query_id", "id")}
38
+ )
39
+ return await self.ctx_api.answer_pre_checkout_query(**params)
40
+
41
+
42
+ __all__ = ("PreCheckoutQueryCute",)
@@ -9,7 +9,8 @@ from telegrinder.bot.cute_types.chat_join_request import ChatJoinRequestCute
9
9
  from telegrinder.bot.cute_types.chat_member_updated import ChatMemberUpdatedCute
10
10
  from telegrinder.bot.cute_types.inline_query import InlineQueryCute
11
11
  from telegrinder.bot.cute_types.message import MessageCute
12
- from telegrinder.model import From, field
12
+ from telegrinder.bot.cute_types.pre_checkout_query import PreCheckoutQueryCute
13
+ from telegrinder.model import UNSET, From, field
13
14
  from telegrinder.msgspec_utils import Option
14
15
  from telegrinder.types.objects import *
15
16
 
@@ -20,13 +21,13 @@ class UpdateCute(BaseCute[Update], Update, kw_only=True):
20
21
  api: API
21
22
 
22
23
  message: Option[MessageCute] = field(
23
- default=Nothing(),
24
+ default=UNSET,
24
25
  converter=From[MessageCute | None],
25
26
  )
26
27
  """Optional. New incoming message of any kind - text, photo, sticker, etc."""
27
28
 
28
29
  edited_message: Option[MessageCute] = field(
29
- default=Nothing(),
30
+ default=UNSET,
30
31
  converter=From[MessageCute | None],
31
32
  )
32
33
  """Optional. New version of a message that is known to the bot and was edited.
@@ -34,14 +35,14 @@ class UpdateCute(BaseCute[Update], Update, kw_only=True):
34
35
  either unavailable or not actively used by your bot."""
35
36
 
36
37
  channel_post: Option[MessageCute] = field(
37
- default=Nothing(),
38
+ default=UNSET,
38
39
  converter=From[MessageCute | None],
39
40
  )
40
41
  """Optional. New incoming channel post of any kind - text, photo, sticker,
41
42
  etc."""
42
43
 
43
44
  edited_channel_post: Option[MessageCute] = field(
44
- default=Nothing(),
45
+ default=UNSET,
45
46
  converter=From[MessageCute | None],
46
47
  )
47
48
  """Optional. New version of a channel post that is known to the bot and was edited.
@@ -49,31 +50,31 @@ class UpdateCute(BaseCute[Update], Update, kw_only=True):
49
50
  either unavailable or not actively used by your bot."""
50
51
 
51
52
  business_message: Option[MessageCute] = field(
52
- default=Nothing(),
53
+ default=UNSET,
53
54
  converter=From[MessageCute | None],
54
55
  )
55
56
  """Optional. New message from a connected business account."""
56
57
 
57
58
  edited_business_message: Option[MessageCute] = field(
58
- default=Nothing(),
59
+ default=UNSET,
59
60
  converter=From[MessageCute | None],
60
61
  )
61
62
  """Optional. New version of a message from a connected business account."""
62
63
 
63
64
  inline_query: Option[InlineQueryCute] = field(
64
- default=Nothing(),
65
+ default=UNSET,
65
66
  converter=From[InlineQueryCute | None],
66
67
  )
67
68
  """Optional. New incoming inline query."""
68
69
 
69
70
  callback_query: Option[CallbackQueryCute] = field(
70
- default=Nothing(),
71
+ default=UNSET,
71
72
  converter=From[CallbackQueryCute | None],
72
73
  )
73
74
  """Optional. New incoming callback query."""
74
75
 
75
76
  my_chat_member: Option[ChatMemberUpdatedCute] = field(
76
- default=Nothing(),
77
+ default=UNSET,
77
78
  converter=From[ChatMemberUpdatedCute | None],
78
79
  )
79
80
  """Optional. The bot's chat member status was updated in a chat. For private
@@ -81,7 +82,7 @@ class UpdateCute(BaseCute[Update], Update, kw_only=True):
81
82
  by the user."""
82
83
 
83
84
  chat_member: Option[ChatMemberUpdatedCute] = field(
84
- default=Nothing(),
85
+ default=UNSET,
85
86
  converter=From[ChatMemberUpdatedCute | None],
86
87
  )
87
88
  """Optional. A chat member's status was updated in a chat. The bot must be an
@@ -89,12 +90,19 @@ class UpdateCute(BaseCute[Update], Update, kw_only=True):
89
90
  the list of allowed_updates to receive these updates."""
90
91
 
91
92
  chat_join_request: Option[ChatJoinRequestCute] = field(
92
- default=Nothing(),
93
+ default=UNSET,
93
94
  converter=From[ChatJoinRequestCute | None],
94
95
  )
95
96
  """Optional. A request to join the chat has been sent. The bot must have the can_invite_users
96
97
  administrator right in the chat to receive these updates."""
97
98
 
99
+ pre_checkout_query: Option[PreCheckoutQueryCute] = field(
100
+ default=UNSET,
101
+ converter=From[PreCheckoutQueryCute | None],
102
+ )
103
+ """Optional. New incoming pre-checkout query. Contains full information
104
+ about checkout."""
105
+
98
106
  def get_event(self, event_model: type[EventModel]) -> Option[EventModel]:
99
107
  if isinstance(self.incoming_update, event_model):
100
108
  return Some(self.incoming_update)
@@ -8,15 +8,13 @@ from telegrinder.types.objects import (
8
8
  InputMediaDocument,
9
9
  InputMediaPhoto,
10
10
  InputMediaVideo,
11
- LinkPreviewOptions,
12
11
  MessageEntity,
13
12
  ReactionEmoji,
14
13
  ReactionType,
15
14
  ReactionTypeEmoji,
16
- ReplyParameters,
17
15
  )
18
16
 
19
- InputMedia: typing.TypeAlias = typing.Union[
17
+ type InputMedia = typing.Union[
20
18
  InputMediaAnimation,
21
19
  InputMediaAudio,
22
20
  InputMediaDocument,
@@ -45,36 +43,10 @@ def compose_reactions(
45
43
  if isinstance(emoji, ReactionEmoji)
46
44
  else (ReactionTypeEmoji(ReactionEmoji(emoji)) if isinstance(emoji, str) else emoji)
47
45
  )
48
- for emoji in ([reactions] if isinstance(reactions, str) else reactions) # type: ignore
46
+ for emoji in reactions
49
47
  ]
50
48
 
51
49
 
52
- def compose_reply_params(
53
- message_id: int | None,
54
- chat_id: int | str | None,
55
- *,
56
- allow_sending_without_reply: bool | None = None,
57
- quote: str | None = None,
58
- quote_parse_mode: str | None = None,
59
- quote_entities: list[MessageEntity] | None = None,
60
- quote_position: int | None = None,
61
- **other: typing.Any,
62
- ) -> ReplyParameters:
63
- return ReplyParameters(**get_params(locals()))
64
-
65
-
66
- def compose_link_preview_options(
67
- *,
68
- is_disabled: bool | None = None,
69
- url: str | None = None,
70
- prefer_small_media: bool | None = None,
71
- prefer_large_media: bool | None = None,
72
- show_above_text: bool | None = None,
73
- **other: typing.Any,
74
- ) -> LinkPreviewOptions:
75
- return LinkPreviewOptions(**get_params(locals()))
76
-
77
-
78
50
  def input_media(
79
51
  type: typing.Literal["animation", "audio", "document", "photo", "video"],
80
52
  media: str | InputFile,
@@ -87,9 +59,4 @@ def input_media(
87
59
  return INPUT_MEDIA_TYPES[type](**get_params(locals()))
88
60
 
89
61
 
90
- __all__ = (
91
- "compose_link_preview_options",
92
- "compose_reactions",
93
- "compose_reply_params",
94
- "input_media",
95
- )
62
+ __all__ = ("compose_reactions", "input_media")
@@ -21,6 +21,7 @@ from telegrinder.bot.dispatch.return_manager import (
21
21
  InlineQueryReturnManager,
22
22
  Manager,
23
23
  MessageReturnManager,
24
+ PreCheckoutQueryManager,
24
25
  register_manager,
25
26
  )
26
27
  from telegrinder.bot.dispatch.view import (
@@ -33,6 +34,7 @@ from telegrinder.bot.dispatch.view import (
33
34
  ChatMemberView,
34
35
  InlineQueryView,
35
36
  MessageView,
37
+ PreCheckoutQueryView,
36
38
  RawEventView,
37
39
  ViewBox,
38
40
  )
@@ -84,6 +86,8 @@ __all__ = (
84
86
  "MessageReturnManager",
85
87
  "MessageView",
86
88
  "PhotoReplyHandler",
89
+ "PreCheckoutQueryManager",
90
+ "PreCheckoutQueryView",
87
91
  "RawEventView",
88
92
  "ShortState",
89
93
  "StateViewHasher",
@@ -8,10 +8,10 @@ from abc import ABC, abstractmethod
8
8
  from fntypes.option import Option
9
9
 
10
10
  from telegrinder.api.api import API
11
- from telegrinder.tools.global_context.abc import ABCGlobalContext
12
11
  from telegrinder.types.objects import Update
13
12
 
14
- T = typing.TypeVar("T")
13
+ if typing.TYPE_CHECKING:
14
+ from telegrinder.bot.dispatch.view.abc import ABCView
15
15
 
16
16
 
17
17
  class PathExistsError(BaseException):
@@ -19,21 +19,20 @@ class PathExistsError(BaseException):
19
19
 
20
20
 
21
21
  class ABCDispatch(ABC):
22
- @property
23
22
  @abstractmethod
24
- def global_context(self) -> ABCGlobalContext:
23
+ async def feed(self, event: Update, api: API[typing.Any]) -> bool:
25
24
  pass
26
25
 
27
26
  @abstractmethod
28
- async def feed(self, event: Update, api: API) -> bool:
27
+ def load(self, external: typing.Self) -> None:
29
28
  pass
30
29
 
31
30
  @abstractmethod
32
- def load(self, external: typing.Self) -> None:
31
+ def get_view[T](self, of_type: type[T]) -> Option[T]:
33
32
  pass
34
33
 
35
34
  @abstractmethod
36
- def get_view(self, of_type: type[T]) -> Option[T]:
35
+ def get_views(self) -> dict[str, "ABCView"]:
37
36
  pass
38
37
 
39
38
  def load_many(self, *externals: typing.Self) -> None:
@@ -43,8 +42,8 @@ class ABCDispatch(ABC):
43
42
  def load_from_dir(self, directory: str | pathlib.Path) -> bool:
44
43
  """Loads dispatchers from a directory containing Python modules where global variables
45
44
  are declared with instances of dispatch.
46
- Returns True if dispatchers were found, otherwise False."""
47
-
45
+ Returns True if dispatchers were found, otherwise False.
46
+ """
48
47
  directory = pathlib.Path(directory)
49
48
 
50
49
  if not directory.exists():
@@ -9,10 +9,8 @@ from telegrinder.types.objects import Update
9
9
  if typing.TYPE_CHECKING:
10
10
  from telegrinder.node.composer import NodeCollection
11
11
 
12
- T = typing.TypeVar("T")
13
-
14
- Key: typing.TypeAlias = str | enum.Enum
15
- AnyValue: typing.TypeAlias = typing.Any
12
+ type Key = str | enum.Enum
13
+ type AnyValue = typing.Any
16
14
 
17
15
 
18
16
  class Context(dict[str, AnyValue]):
@@ -78,15 +76,15 @@ class Context(dict[str, AnyValue]):
78
76
  def get(self, key: Key) -> AnyValue | None: ...
79
77
 
80
78
  @typing.overload
81
- def get(self, key: Key, default: T) -> T | AnyValue: ...
79
+ def get[T](self, key: Key, default: T) -> T | AnyValue: ...
82
80
 
83
81
  @typing.overload
84
82
  def get(self, key: Key, default: None = None) -> AnyValue | None: ...
85
83
 
86
- def get(self, key: Key, default: T | None = None) -> T | AnyValue | None:
84
+ def get[T](self, key: Key, default: T | None = None) -> T | AnyValue | None:
87
85
  return dict.get(self, key, default)
88
86
 
89
- def get_or_set(self, key: Key, default: T) -> T:
87
+ def get_or_set[T](self, key: Key, default: T) -> T:
90
88
  if key not in self:
91
89
  self.set(key, default)
92
90
  return self.get(key, default)
@@ -1,20 +1,25 @@
1
+ from __future__ import annotations
2
+
1
3
  import dataclasses
2
4
 
3
5
  import typing_extensions as typing
4
6
  from fntypes import Nothing, Option, Some
5
7
  from vbml.patcher import Patcher
6
8
 
7
- from telegrinder.api.api import API
8
- from telegrinder.bot.cute_types.base import BaseCute
9
- from telegrinder.bot.cute_types.update import UpdateCute
9
+ from telegrinder.api.api import API, HTTPClient
10
10
  from telegrinder.bot.dispatch.abc import ABCDispatch
11
+ from telegrinder.bot.dispatch.context import Context
11
12
  from telegrinder.bot.dispatch.handler.func import ErrorHandlerT, Func, FuncHandler
13
+ from telegrinder.bot.dispatch.middleware.abc import run_middleware
14
+ from telegrinder.bot.dispatch.middleware.global_middleware import GlobalMiddleware
15
+ from telegrinder.bot.dispatch.view.abc import ABCView
12
16
  from telegrinder.bot.dispatch.view.box import (
13
17
  CallbackQueryView,
14
18
  ChatJoinRequestView,
15
19
  ChatMemberView,
16
20
  InlineQueryView,
17
21
  MessageView,
22
+ PreCheckoutQueryView,
18
23
  RawEventView,
19
24
  ViewBox,
20
25
  )
@@ -25,11 +30,13 @@ from telegrinder.types.enums import UpdateType
25
30
  from telegrinder.types.objects import Update
26
31
 
27
32
  if typing.TYPE_CHECKING:
33
+ from telegrinder.bot.cute_types.base import BaseCute
34
+ from telegrinder.bot.cute_types.update import UpdateCute
28
35
  from telegrinder.bot.rules.abc import ABCRule
29
36
 
30
37
  T = typing.TypeVar("T", default=typing.Any)
31
38
  R = typing.TypeVar("R", covariant=True, default=typing.Any)
32
- Event = typing.TypeVar("Event", bound=BaseCute)
39
+ Event = typing.TypeVar("Event", bound="BaseCute")
33
40
  P = typing.ParamSpec("P", default=...)
34
41
 
35
42
  DEFAULT_DATACLASS: typing.Final[type[Update]] = Update
@@ -44,12 +51,26 @@ class Dispatch(
44
51
  ChatMemberView,
45
52
  InlineQueryView,
46
53
  MessageView,
54
+ PreCheckoutQueryView,
55
+ RawEventView,
56
+ ],
57
+ typing.Generic[
58
+ HTTPClient,
59
+ CallbackQueryView,
60
+ ChatJoinRequestView,
61
+ ChatMemberView,
62
+ InlineQueryView,
63
+ MessageView,
64
+ PreCheckoutQueryView,
47
65
  RawEventView,
48
66
  ],
49
67
  ):
50
68
  _global_context: TelegrinderContext = dataclasses.field(
51
69
  init=False,
52
- default_factory=lambda: TelegrinderContext(),
70
+ default_factory=TelegrinderContext,
71
+ )
72
+ global_middleware: "GlobalMiddleware" = dataclasses.field(
73
+ default_factory=lambda: GlobalMiddleware(),
53
74
  )
54
75
 
55
76
  def __repr__(self) -> str:
@@ -62,31 +83,30 @@ class Dispatch(
62
83
  @property
63
84
  def patcher(self) -> Patcher:
64
85
  """Alias `patcher` to get `vbml.Patcher` from the global context."""
65
-
66
86
  return self.global_context.vbml_patcher
67
87
 
68
88
  @typing.overload
69
89
  def handle(
70
90
  self,
71
91
  *rules: "ABCRule",
72
- is_blocking: bool = True,
73
- ) -> typing.Callable[[Func[P, R]], FuncHandler[UpdateCute, Func[P, R], ErrorHandler[UpdateCute]]]: ...
92
+ final: bool = True,
93
+ ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandler[UpdateCute]]]: ...
74
94
 
75
95
  @typing.overload
76
96
  def handle(
77
97
  self,
78
98
  *rules: "ABCRule",
79
99
  dataclass: type[T],
80
- is_blocking: bool = True,
81
- ) -> typing.Callable[[Func[P, R]], FuncHandler[UpdateCute, Func[P, R], ErrorHandler[T]]]: ...
100
+ final: bool = True,
101
+ ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandler[T]]]: ...
82
102
 
83
103
  @typing.overload
84
104
  def handle(
85
105
  self,
86
106
  *rules: "ABCRule",
87
107
  update_type: UpdateType,
88
- is_blocking: bool = True,
89
- ) -> typing.Callable[[Func[P, R]], FuncHandler[UpdateCute, Func[P, R], ErrorHandler[UpdateCute]]]: ...
108
+ final: bool = True,
109
+ ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandler[UpdateCute]]]: ...
90
110
 
91
111
  @typing.overload
92
112
  def handle(
@@ -94,16 +114,16 @@ class Dispatch(
94
114
  *rules: "ABCRule",
95
115
  dataclass: type[T],
96
116
  update_type: UpdateType,
97
- is_blocking: bool = True,
98
- ) -> typing.Callable[[Func[P, R]], FuncHandler[UpdateCute, Func[P, R], ErrorHandler[T]]]: ...
117
+ final: bool = True,
118
+ ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandler[T]]]: ...
99
119
 
100
120
  @typing.overload
101
121
  def handle(
102
122
  self,
103
123
  *rules: "ABCRule",
104
124
  error_handler: ErrorHandlerT,
105
- is_blocking: bool = True,
106
- ) -> typing.Callable[[Func[P, R]], FuncHandler[UpdateCute, Func[P, R], ErrorHandlerT]]: ...
125
+ final: bool = True,
126
+ ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandlerT]]: ...
107
127
 
108
128
  @typing.overload
109
129
  def handle(
@@ -111,27 +131,27 @@ class Dispatch(
111
131
  *rules: "ABCRule",
112
132
  update_type: UpdateType,
113
133
  error_handler: ErrorHandlerT,
114
- is_blocking: bool = True,
115
- ) -> typing.Callable[[Func[P, R]], FuncHandler[UpdateCute, Func[P, R], ErrorHandlerT]]: ...
134
+ final: bool = True,
135
+ ) -> typing.Callable[[Func[P, R]], FuncHandler["UpdateCute", Func[P, R], ErrorHandlerT]]: ...
116
136
 
117
137
  @typing.overload
118
138
  def handle(
119
139
  self,
120
140
  *rules: "ABCRule",
121
- dataclass: type[typing.Any],
141
+ dataclass: type[T],
122
142
  error_handler: ErrorHandlerT,
123
- is_blocking: bool = True,
124
- ) -> typing.Callable[[Func[P, R]], FuncHandler[UpdateCute, Func[P, R], ErrorHandlerT]]: ...
143
+ final: bool = True,
144
+ ) -> typing.Callable[[Func[P, R]], FuncHandler[T, Func[P, R], ErrorHandlerT]]: ...
125
145
 
126
146
  @typing.overload
127
147
  def handle(
128
148
  self,
129
149
  *rules: "ABCRule",
130
- dataclass: type[typing.Any],
150
+ dataclass: type[T],
131
151
  update_type: UpdateType,
132
152
  error_handler: ErrorHandlerT,
133
- is_blocking: bool = True,
134
- ) -> typing.Callable[[Func[P, R]], FuncHandler[UpdateCute, Func[P, R], ErrorHandlerT]]: ...
153
+ final: bool = True,
154
+ ) -> typing.Callable[[Func[P, R]], FuncHandler[T, Func[P, R], ErrorHandlerT]]: ...
135
155
 
136
156
  @typing.overload
137
157
  def handle(
@@ -140,8 +160,8 @@ class Dispatch(
140
160
  update_type: UpdateType | None = None,
141
161
  dataclass: type[T] = DEFAULT_DATACLASS,
142
162
  error_handler: typing.Literal[None] = None,
143
- is_blocking: bool = True,
144
- ) -> typing.Callable[[Func[P, R]], FuncHandler[UpdateCute, Func[P, R], ErrorHandler[T]]]: ...
163
+ final: bool = True,
164
+ ) -> typing.Callable[[Func[P, R]], FuncHandler[T, Func[P, R], ErrorHandler[T]]]: ...
145
165
 
146
166
  def handle(
147
167
  self,
@@ -149,13 +169,13 @@ class Dispatch(
149
169
  update_type: UpdateType | None = None,
150
170
  dataclass: type[typing.Any] = DEFAULT_DATACLASS,
151
171
  error_handler: ErrorHandlerT | None = None,
152
- is_blocking: bool = True,
172
+ final: bool = True,
153
173
  ) -> typing.Callable[..., typing.Any]:
154
174
  def wrapper(func):
155
175
  handler = FuncHandler(
156
176
  func,
157
177
  list(rules),
158
- is_blocking=is_blocking,
178
+ final=final,
159
179
  dataclass=dataclass,
160
180
  error_handler=error_handler or ErrorHandler(),
161
181
  update_type=update_type,
@@ -165,12 +185,27 @@ class Dispatch(
165
185
 
166
186
  return wrapper
167
187
 
168
- async def feed(self, event: Update, api: API) -> bool:
188
+ async def feed(self, event: Update, api: API[HTTPClient]) -> bool:
169
189
  logger.debug(
170
190
  "Processing update (update_id={}, update_type={!r})",
171
191
  event.update_id,
172
192
  event.update_type.name,
173
193
  )
194
+ context = Context(raw_update=event)
195
+
196
+ if (
197
+ await run_middleware(
198
+ self.global_middleware.pre,
199
+ api,
200
+ event, # type: ignore
201
+ raw_event=event,
202
+ ctx=context,
203
+ adapter=self.global_middleware.adapter,
204
+ )
205
+ is False
206
+ ):
207
+ return False
208
+
174
209
  for view in self.get_views().values():
175
210
  if await view.check(event):
176
211
  logger.debug(
@@ -179,23 +214,40 @@ class Dispatch(
179
214
  event.update_type.name,
180
215
  view,
181
216
  )
182
- if await view.process(event, api):
217
+ if await view.process(event, api, context):
183
218
  return True
219
+
220
+ await run_middleware(
221
+ self.global_middleware.post,
222
+ api,
223
+ event,
224
+ raw_event=event,
225
+ ctx=context,
226
+ adapter=self.global_middleware.adapter,
227
+ )
228
+
184
229
  return False
185
230
 
186
231
  def load(self, external: typing.Self) -> None:
187
- view_external = external.get_views()
232
+ views_external = external.get_views()
233
+
188
234
  for name, view in self.get_views().items():
189
- assert name in view_external, f"View {name!r} is undefined in external dispatch."
190
- view.load(view_external[name])
235
+ assert name in views_external, f"View {name!r} is undefined in external dispatch."
236
+ view.load(views_external[name])
191
237
  setattr(external, name, view)
192
238
 
239
+ self.global_middleware.filters.difference_update(external.global_middleware.filters)
240
+
193
241
  def get_view(self, of_type: type[T]) -> Option[T]:
194
242
  for view in self.get_views().values():
195
243
  if isinstance(view, of_type):
196
244
  return Some(view)
197
245
  return Nothing()
198
246
 
247
+ def get_views(self) -> dict[str, ABCView]:
248
+ """Get all views."""
249
+ return {name: view for name, view in self.__dict__.items() if isinstance(view, ABCView)}
250
+
199
251
  __call__ = handle
200
252
 
201
253
 
@@ -3,21 +3,20 @@ from abc import ABC, abstractmethod
3
3
 
4
4
  from telegrinder.api import API
5
5
  from telegrinder.bot.dispatch.context import Context
6
- from telegrinder.model import Model
6
+ from telegrinder.tools.adapter.abc import ABCAdapter
7
7
  from telegrinder.types.objects import Update
8
8
 
9
- T = typing.TypeVar("T", bound=Model)
10
9
 
11
-
12
- class ABCHandler(ABC, typing.Generic[T]):
13
- is_blocking: bool
10
+ class ABCHandler[Event](ABC):
11
+ final: bool
12
+ adapter: ABCAdapter[Update, Event] | None = None
14
13
 
15
14
  @abstractmethod
16
15
  async def check(self, api: API, event: Update, ctx: Context | None = None) -> bool:
17
16
  pass
18
17
 
19
18
  @abstractmethod
20
- async def run(self, api: API, event: T, ctx: Context) -> typing.Any:
19
+ async def run(self, api: API, event: Event, ctx: Context) -> typing.Any:
21
20
  pass
22
21
 
23
22
 
@@ -15,7 +15,7 @@ class AudioReplyHandler(BaseReplyHandler):
15
15
  *rules: ABCRule,
16
16
  caption: str | None = None,
17
17
  parse_mode: str | None = None,
18
- is_blocking: bool = True,
18
+ final: bool = True,
19
19
  as_reply: bool = False,
20
20
  preset_context: Context | None = None,
21
21
  **default_params: typing.Any,
@@ -25,7 +25,7 @@ class AudioReplyHandler(BaseReplyHandler):
25
25
  self.caption = caption
26
26
  super().__init__(
27
27
  *rules,
28
- is_blocking=is_blocking,
28
+ final=final,
29
29
  as_reply=as_reply,
30
30
  preset_context=preset_context,
31
31
  **default_params,
@@ -13,7 +13,7 @@ from telegrinder.bot.rules.abc import ABCRule
13
13
  from telegrinder.modules import logger
14
14
  from telegrinder.types.objects import Update
15
15
 
16
- APIMethod: typing.TypeAlias = typing.Callable[
16
+ type APIMethod = typing.Callable[
17
17
  typing.Concatenate[MessageCute, ...], typing.Awaitable[Result[typing.Any, APIError]]
18
18
  ]
19
19
 
@@ -22,14 +22,14 @@ class BaseReplyHandler(ABCHandler[MessageCute], abc.ABC):
22
22
  def __init__(
23
23
  self,
24
24
  *rules: ABCRule,
25
- is_blocking: bool = True,
25
+ final: bool = True,
26
26
  as_reply: bool = False,
27
27
  preset_context: Context | None = None,
28
28
  **default_params: typing.Any,
29
29
  ) -> None:
30
30
  self.rules = list(rules)
31
31
  self.as_reply = as_reply
32
- self.is_blocking = is_blocking
32
+ self.final = final
33
33
  self.default_params = default_params
34
34
  self.preset_context = preset_context or Context()
35
35
 
@@ -15,7 +15,7 @@ class DocumentReplyHandler(BaseReplyHandler):
15
15
  *rules: ABCRule,
16
16
  caption: str | None = None,
17
17
  parse_mode: str | None = None,
18
- is_blocking: bool = True,
18
+ final: bool = True,
19
19
  as_reply: bool = False,
20
20
  preset_context: Context | None = None,
21
21
  **default_params: typing.Any,
@@ -25,7 +25,7 @@ class DocumentReplyHandler(BaseReplyHandler):
25
25
  self.caption = caption
26
26
  super().__init__(
27
27
  *rules,
28
- is_blocking=is_blocking,
28
+ final=final,
29
29
  as_reply=as_reply,
30
30
  preset_context=preset_context,
31
31
  **default_params,