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
@@ -1,14 +1,11 @@
1
1
  import dataclasses
2
2
  import typing
3
3
 
4
- from fntypes.option import Nothing, Option
4
+ from fntypes.option import Nothing, Option, Some
5
5
 
6
6
  from telegrinder.api.api import API
7
- from telegrinder.bot.cute_types import ChatJoinRequestCute
8
- from telegrinder.node.base import ComposeError, DataNode, ScalarNode
9
- from telegrinder.node.callback_query import CallbackQueryNode
10
- from telegrinder.node.event import EventNode
11
- from telegrinder.node.message import MessageNode
7
+ from telegrinder.bot.cute_types import CallbackQueryCute, ChatJoinRequestCute, MessageCute, PreCheckoutQueryCute
8
+ from telegrinder.node.base import ComposeError, DataNode, scalar_node
12
9
  from telegrinder.node.polymorphic import Polymorphic, impl
13
10
  from telegrinder.types.objects import Chat, Message, User
14
11
 
@@ -16,56 +13,75 @@ from telegrinder.types.objects import Chat, Message, User
16
13
  @dataclasses.dataclass(kw_only=True, slots=True)
17
14
  class Source(Polymorphic, DataNode):
18
15
  api: API
19
- chat: Chat
20
16
  from_user: User
21
- thread_id: Option[int] = dataclasses.field(default_factory=lambda: Nothing())
17
+ chat: Option[Chat] = dataclasses.field(default_factory=Nothing)
18
+ thread_id: Option[int] = dataclasses.field(default_factory=Nothing)
22
19
 
23
20
  @impl
24
- def compose_message(cls, message: MessageNode) -> typing.Self:
21
+ def compose_message(cls, message: MessageCute) -> typing.Self:
25
22
  return cls(
26
23
  api=message.ctx_api,
27
- chat=message.chat,
28
- from_user=message.from_.expect(ComposeError("MessageNode has no from_user")),
24
+ from_user=message.from_user,
25
+ chat=Some(message.chat),
29
26
  thread_id=message.message_thread_id,
30
27
  )
31
28
 
32
29
  @impl
33
- def compose_callback_query(cls, callback_query: CallbackQueryNode) -> typing.Self:
30
+ def compose_callback_query(cls, callback_query: CallbackQueryCute) -> typing.Self:
34
31
  return cls(
35
32
  api=callback_query.ctx_api,
36
- chat=callback_query.chat.expect(ComposeError("CallbackQueryNode has no chat")),
37
33
  from_user=callback_query.from_user,
34
+ chat=callback_query.chat,
38
35
  thread_id=callback_query.message_thread_id,
39
36
  )
40
37
 
41
38
  @impl
42
- def compose_chat_join_request(cls, chat_join_request: EventNode[ChatJoinRequestCute]) -> typing.Self:
39
+ def compose_chat_join_request(cls, chat_join_request: ChatJoinRequestCute) -> typing.Self:
43
40
  return cls(
44
41
  api=chat_join_request.ctx_api,
45
- chat=chat_join_request.chat,
46
42
  from_user=chat_join_request.from_user,
43
+ chat=Some(chat_join_request.chat),
47
44
  thread_id=Nothing(),
48
45
  )
49
46
 
50
- async def send(self, text: str) -> Message:
47
+ @impl
48
+ def compose_pre_checkout_query(cls, pre_checkout_query: PreCheckoutQueryCute) -> typing.Self:
49
+ return cls(
50
+ api=pre_checkout_query.ctx_api,
51
+ from_user=pre_checkout_query.from_user,
52
+ chat=Nothing(),
53
+ thread_id=Nothing(),
54
+ )
55
+
56
+ async def send(self, text: str, **kwargs: typing.Any) -> Message:
51
57
  result = await self.api.send_message(
52
- chat_id=self.chat.id,
58
+ chat_id=self.chat.map_or(self.from_user.id, lambda chat: chat.id).unwrap(),
53
59
  message_thread_id=self.thread_id.unwrap_or_none(),
54
60
  text=text,
61
+ **kwargs,
55
62
  )
56
63
  return result.unwrap()
57
64
 
58
65
 
59
- class ChatSource(ScalarNode, Chat):
66
+ @scalar_node
67
+ class ChatSource:
60
68
  @classmethod
61
69
  def compose(cls, source: Source) -> Chat:
62
- return source.chat
70
+ return source.chat.expect(ComposeError("Source has no chat."))
63
71
 
64
72
 
65
- class UserSource(ScalarNode, User):
73
+ @scalar_node
74
+ class UserSource:
66
75
  @classmethod
67
76
  def compose(cls, source: Source) -> User:
68
77
  return source.from_user
69
78
 
70
79
 
71
- __all__ = ("ChatSource", "Source", "UserSource")
80
+ @scalar_node
81
+ class UserId:
82
+ @classmethod
83
+ def compose(cls, user: UserSource) -> int:
84
+ return user.id
85
+
86
+
87
+ __all__ = ("ChatSource", "Source", "UserId", "UserSource")
telegrinder/node/text.py CHANGED
@@ -1,20 +1,32 @@
1
1
  import typing
2
2
 
3
- from telegrinder.node.base import ComposeError, FactoryNode, ScalarNode
4
- from telegrinder.node.message import MessageNode
3
+ from telegrinder.bot.cute_types.message import MessageCute
4
+ from telegrinder.node.base import ComposeError, FactoryNode, scalar_node
5
+ from telegrinder.node.either import Either
5
6
 
6
7
 
7
- class Text(ScalarNode, str):
8
+ @scalar_node
9
+ class Caption:
8
10
  @classmethod
9
- def compose(cls, message: MessageNode) -> str:
11
+ def compose(cls, message: MessageCute) -> str:
12
+ if not message.caption:
13
+ raise ComposeError("Message has no caption.")
14
+ return message.caption.unwrap()
15
+
16
+
17
+ @scalar_node
18
+ class Text:
19
+ @classmethod
20
+ def compose(cls, message: MessageCute) -> str:
10
21
  if not message.text:
11
22
  raise ComposeError("Message has no text.")
12
23
  return message.text.unwrap()
13
24
 
14
25
 
15
- class TextInteger(ScalarNode, int):
26
+ @scalar_node
27
+ class TextInteger:
16
28
  @classmethod
17
- def compose(cls, text: Text) -> int:
29
+ def compose(cls, text: Either[Text, Caption]) -> int:
18
30
  if not text.isdigit():
19
31
  raise ComposeError("Text is not digit.")
20
32
  return int(text)
@@ -35,7 +47,7 @@ else:
35
47
  def compose(cls, text: Text) -> str:
36
48
  if text in cls.texts:
37
49
  return text
38
- raise ComposeError("Text matching failed.")
50
+ raise ComposeError("Text mismatched literal.")
39
51
 
40
52
 
41
- __all__ = ("Text", "TextInteger", "TextLiteral")
53
+ __all__ = ("Caption", "Text", "TextInteger", "TextLiteral")
@@ -1,40 +1,36 @@
1
1
  import inspect
2
2
  import typing
3
3
 
4
- from telegrinder.node.base import ComposeError, Node
4
+ from telegrinder.node.base import ComposeError, IsNode, Node
5
5
  from telegrinder.node.container import ContainerNode
6
6
 
7
- T = typing.TypeVar("T")
8
7
 
9
-
10
- def cast_false_to_none(value: T) -> T | None:
8
+ def cast_false_to_none[Value](value: Value) -> Value | None:
11
9
  if value is False:
12
10
  return None
13
11
  return value
14
12
 
15
13
 
16
- def error_on_none(value: T | None) -> T:
14
+ def error_on_none[Value](value: Value | None) -> Value:
17
15
  if value is None:
18
16
  raise ComposeError
19
17
  return value
20
18
 
21
19
 
22
20
  def generate_node(
23
- subnodes: tuple[type[Node], ...],
21
+ subnodes: tuple[IsNode, ...],
24
22
  func: typing.Callable[..., typing.Any],
25
23
  casts: tuple[typing.Callable[[typing.Any], typing.Any], ...] = (cast_false_to_none, error_on_none),
26
24
  ) -> type[Node]:
27
- async def compose(cls, **kw) -> typing.Any:
28
- result = func(*ContainerNode.compose(**kw)) # type: ignore
25
+ async def compose(cls, *args: typing.Any) -> typing.Any:
26
+ result = func(*args)
29
27
  if inspect.isawaitable(result):
30
28
  result = await result
31
29
  for cast in casts:
32
30
  result = cast(result)
33
31
  return result
34
32
 
35
- container = ContainerNode.link_nodes(list(subnodes))
36
- compose.__annotations__ = container.get_subnodes()
37
- return type("_ContainerNode", (container,), {"compose": classmethod(compose)})
33
+ return ContainerNode.link_nodes(linked_nodes=list(subnodes), composer=compose)
38
34
 
39
35
 
40
36
  __all__ = ("generate_node",)
telegrinder/py.typed CHANGED
File without changes
telegrinder/rules.py CHANGED
@@ -1,62 +1 @@
1
1
  from .bot.rules import * # noqa: F403
2
-
3
- __all__ = (
4
- "ABCRule",
5
- "AndRule",
6
- "Argument",
7
- "CallbackDataEq",
8
- "CallbackDataJsonEq",
9
- "CallbackDataJsonModel",
10
- "CallbackDataMap",
11
- "CallbackDataMarkup",
12
- "CallbackQueryDataRule",
13
- "CallbackQueryRule",
14
- "ChatJoinRequestRule",
15
- "Command",
16
- "EnumTextRule",
17
- "FuncRule",
18
- "FuzzyText",
19
- "HasData",
20
- "HasEntities",
21
- "HasInviteLink",
22
- "HasLocation",
23
- "HasMention",
24
- "HasText",
25
- "InlineQueryChatType",
26
- "InlineQueryMarkup",
27
- "InlineQueryRule",
28
- "InlineQueryText",
29
- "IntegerInRange",
30
- "InviteLinkByCreator",
31
- "InviteLinkName",
32
- "IsBot",
33
- "IsChat",
34
- "IsChatId",
35
- "IsDice",
36
- "IsDiceEmoji",
37
- "IsForum",
38
- "IsForward",
39
- "IsForwardType",
40
- "IsGroup",
41
- "IsInteger",
42
- "IsLanguageCode",
43
- "IsPremium",
44
- "IsPrivate",
45
- "IsReply",
46
- "IsSuperGroup",
47
- "IsUpdateType",
48
- "IsUser",
49
- "IsUserId",
50
- "Markup",
51
- "MessageEntities",
52
- "MessageRule",
53
- "NodeRule",
54
- "NotRule",
55
- "OrRule",
56
- "Regex",
57
- "RuleEnum",
58
- "StartCommand",
59
- "State",
60
- "StateMeta",
61
- "Text",
62
- )
@@ -1,43 +1,67 @@
1
+ from .adapter import (
2
+ ABCAdapter,
3
+ DataclassAdapter,
4
+ EventAdapter,
5
+ NodeAdapter,
6
+ RawEventAdapter,
7
+ RawUpdateAdapter,
8
+ )
1
9
  from .buttons import BaseButton
10
+ from .callback_data_serilization import (
11
+ ABCDataSerializer,
12
+ JSONSerializer,
13
+ MsgPackSerializer,
14
+ )
2
15
  from .error_handler import ABCErrorHandler, Catcher, CatcherError, ErrorHandler
3
16
  from .formatting import (
4
- BaseSpecFormat,
5
- ChannelBoostLink,
17
+ Base,
18
+ BlockQuote,
6
19
  FormatString,
7
20
  HTMLFormatter,
8
- InviteChatLink,
9
21
  Link,
10
22
  Mention,
11
23
  PreCode,
12
- ResolveDomain,
13
24
  SpecialFormat,
14
- StartBotLink,
15
- StartGroupLink,
16
25
  TgEmoji,
17
26
  block_quote,
18
27
  bold,
19
- channel_boost_link,
20
28
  code_inline,
21
29
  escape,
22
- get_channel_boost_link,
23
- get_invite_chat_link,
24
- get_mention_link,
25
- get_resolve_domain_link,
26
- get_start_bot_link,
27
- get_start_group_link,
28
- invite_chat_link,
29
30
  italic,
30
31
  link,
31
32
  mention,
32
33
  pre_code,
33
- resolve_domain,
34
34
  spoiler,
35
- start_bot_link,
36
- start_group_link,
37
35
  strike,
36
+ tg_bot_attach_open_any_chat,
37
+ tg_bot_attach_open_current_chat,
38
+ tg_bot_attach_open_specific_chat,
39
+ tg_bot_start_link,
40
+ tg_bot_startchannel_link,
41
+ tg_bot_startgroup_link,
42
+ tg_chat_folder_link,
43
+ tg_chat_invite_link,
44
+ tg_direct_mini_app_link,
38
45
  tg_emoji,
46
+ tg_emoji_link,
47
+ tg_emoji_stickerset_link,
48
+ tg_invoice_link,
49
+ tg_language_pack_link,
50
+ tg_main_mini_app_link,
51
+ tg_mention_link,
52
+ tg_open_message_link,
53
+ tg_premium_multigift_link,
54
+ tg_premium_offer_link,
55
+ tg_private_channel_boost_link,
56
+ tg_private_message_link,
57
+ tg_public_channel_boost_link,
58
+ tg_public_message_link,
59
+ tg_public_username_link,
60
+ tg_share_link,
61
+ tg_story_link,
39
62
  underline,
40
63
  )
64
+ from .functional import from_optional
41
65
  from .global_context import (
42
66
  ABCGlobalContext,
43
67
  CtxVar,
@@ -54,7 +78,7 @@ from .i18n import (
54
78
  SimpleI18n,
55
79
  SimpleTranslator,
56
80
  )
57
- from .kb_set import KeyboardSetBase, KeyboardSetYAML
81
+ from .input_file_directory import InputFileDirectory
58
82
  from .keyboard import (
59
83
  AnyMarkup,
60
84
  Button,
@@ -65,11 +89,23 @@ from .keyboard import (
65
89
  )
66
90
  from .limited_dict import LimitedDict
67
91
  from .loop_wrapper import ABCLoopWrapper, DelayedTask, Lifespan, LoopWrapper
68
- from .magic import impl, magic_bundle, resolve_arg_names
92
+ from .magic import (
93
+ cancel_future,
94
+ get_annotations,
95
+ get_cached_translation,
96
+ get_default_args,
97
+ get_func_parameters,
98
+ get_impls,
99
+ impl,
100
+ magic_bundle,
101
+ resolve_arg_names,
102
+ )
69
103
  from .parse_mode import ParseMode
70
104
  from .state_storage import ABCStateStorage, MemoryStateStorage, StateData
71
105
 
72
106
  __all__ = (
107
+ "ABCAdapter",
108
+ "ABCDataSerializer",
73
109
  "ABCErrorHandler",
74
110
  "ABCGlobalContext",
75
111
  "ABCI18n",
@@ -78,15 +114,17 @@ __all__ = (
78
114
  "ABCTranslator",
79
115
  "ABCTranslatorMiddleware",
80
116
  "AnyMarkup",
117
+ "Base",
81
118
  "BaseButton",
82
- "BaseSpecFormat",
119
+ "BlockQuote",
83
120
  "Button",
84
121
  "Catcher",
85
122
  "CatcherError",
86
- "ChannelBoostLink",
87
123
  "CtxVar",
124
+ "DataclassAdapter",
88
125
  "DelayedTask",
89
126
  "ErrorHandler",
127
+ "EventAdapter",
90
128
  "FormatString",
91
129
  "GlobalContext",
92
130
  "GlobalCtxVar",
@@ -94,53 +132,74 @@ __all__ = (
94
132
  "I18nEnum",
95
133
  "InlineButton",
96
134
  "InlineKeyboard",
97
- "InviteChatLink",
135
+ "InputFileDirectory",
136
+ "JSONSerializer",
98
137
  "Keyboard",
99
- "KeyboardSetBase",
100
- "KeyboardSetYAML",
101
138
  "Lifespan",
102
139
  "LimitedDict",
103
140
  "Link",
104
141
  "LoopWrapper",
105
142
  "MemoryStateStorage",
106
143
  "Mention",
144
+ "MsgPackSerializer",
145
+ "NodeAdapter",
107
146
  "ParseMode",
108
147
  "PreCode",
109
- "ResolveDomain",
148
+ "RawEventAdapter",
149
+ "RawUpdateAdapter",
110
150
  "RowButtons",
111
151
  "SimpleI18n",
112
152
  "SimpleTranslator",
113
153
  "SpecialFormat",
114
- "StartBotLink",
115
- "StartGroupLink",
116
154
  "StateData",
117
155
  "TelegrinderContext",
118
156
  "TgEmoji",
119
157
  "block_quote",
120
158
  "bold",
121
- "channel_boost_link",
159
+ "cancel_future",
122
160
  "code_inline",
123
161
  "ctx_var",
124
162
  "escape",
125
- "get_channel_boost_link",
126
- "get_invite_chat_link",
127
- "get_mention_link",
128
- "get_resolve_domain_link",
129
- "get_start_bot_link",
130
- "get_start_group_link",
163
+ "from_optional",
164
+ "get_annotations",
165
+ "get_cached_translation",
166
+ "get_default_args",
167
+ "get_func_parameters",
168
+ "get_impls",
131
169
  "impl",
132
- "invite_chat_link",
133
170
  "italic",
134
171
  "link",
135
172
  "magic_bundle",
136
173
  "mention",
137
174
  "pre_code",
138
175
  "resolve_arg_names",
139
- "resolve_domain",
140
176
  "spoiler",
141
- "start_bot_link",
142
- "start_group_link",
143
177
  "strike",
178
+ "tg_bot_attach_open_any_chat",
179
+ "tg_bot_attach_open_current_chat",
180
+ "tg_bot_attach_open_specific_chat",
181
+ "tg_bot_start_link",
182
+ "tg_bot_startchannel_link",
183
+ "tg_bot_startgroup_link",
184
+ "tg_chat_folder_link",
185
+ "tg_chat_invite_link",
186
+ "tg_direct_mini_app_link",
144
187
  "tg_emoji",
188
+ "tg_emoji_link",
189
+ "tg_emoji_stickerset_link",
190
+ "tg_invoice_link",
191
+ "tg_language_pack_link",
192
+ "tg_main_mini_app_link",
193
+ "tg_mention_link",
194
+ "tg_open_message_link",
195
+ "tg_premium_multigift_link",
196
+ "tg_premium_offer_link",
197
+ "tg_private_channel_boost_link",
198
+ "tg_private_message_link",
199
+ "tg_public_channel_boost_link",
200
+ "tg_public_message_link",
201
+ "tg_public_username_link",
202
+ "tg_share_link",
203
+ "tg_story_link",
145
204
  "underline",
146
205
  )
@@ -0,0 +1,19 @@
1
+ from telegrinder.tools.adapter.abc import ABCAdapter, AdaptResult, Event
2
+ from telegrinder.tools.adapter.dataclass import DataclassAdapter
3
+ from telegrinder.tools.adapter.errors import AdapterError
4
+ from telegrinder.tools.adapter.event import EventAdapter
5
+ from telegrinder.tools.adapter.node import NodeAdapter
6
+ from telegrinder.tools.adapter.raw_event import RawEventAdapter
7
+ from telegrinder.tools.adapter.raw_update import RawUpdateAdapter
8
+
9
+ __all__ = (
10
+ "ABCAdapter",
11
+ "AdaptResult",
12
+ "AdapterError",
13
+ "DataclassAdapter",
14
+ "Event",
15
+ "EventAdapter",
16
+ "NodeAdapter",
17
+ "RawEventAdapter",
18
+ "RawUpdateAdapter",
19
+ )
@@ -0,0 +1,49 @@
1
+ import abc
2
+ import dataclasses
3
+ import inspect
4
+ import typing
5
+
6
+ from fntypes import Error, Nothing, Ok, Option, Some
7
+ from fntypes.result import Result
8
+
9
+ from telegrinder.modules import logger
10
+ from telegrinder.tools.adapter.errors import AdapterError
11
+
12
+ type AdaptResult[To] = Result[To, AdapterError] | typing.Awaitable[Result[To, AdapterError]]
13
+
14
+
15
+ if typing.TYPE_CHECKING:
16
+ from telegrinder.api.api import API
17
+ from telegrinder.bot.dispatch.context import Context
18
+ from telegrinder.model import Model
19
+
20
+
21
+ class ABCAdapter[From: "Model", To](abc.ABC):
22
+ ADAPTED_VALUE_KEY: str | None = None
23
+
24
+ @abc.abstractmethod
25
+ def adapt(self, api: "API", update: From, context: "Context") -> AdaptResult[To]:
26
+ pass
27
+
28
+
29
+ @dataclasses.dataclass(slots=True)
30
+ class Event[To]:
31
+ obj: To
32
+
33
+
34
+ async def run_adapter[T, U: "Model"](
35
+ adapter: "ABCAdapter[U, T]",
36
+ api: "API",
37
+ update: U,
38
+ context: "Context",
39
+ ) -> Option[T]:
40
+ adapt_result = adapter.adapt(api, update, context)
41
+ match await adapt_result if inspect.isawaitable(adapt_result) else adapt_result:
42
+ case Ok(value):
43
+ return Some(value)
44
+ case Error(err):
45
+ logger.debug("Adapter {!r} failed with error message: {!r}", adapter, str(err))
46
+ return Nothing()
47
+
48
+
49
+ __all__ = ("ABCAdapter", "AdaptResult", "Event", "run_adapter")
@@ -0,0 +1,56 @@
1
+ from fntypes.option import Nothing, Some
2
+ from fntypes.result import Error, Ok, Result
3
+
4
+ from telegrinder.api.api import API
5
+ from telegrinder.bot.cute_types.base import BaseCute
6
+ from telegrinder.bot.cute_types.update import UpdateCute
7
+ from telegrinder.bot.dispatch.context import Context
8
+ from telegrinder.tools.adapter.abc import ABCAdapter
9
+ from telegrinder.tools.adapter.errors import AdapterError
10
+ from telegrinder.types.enums import UpdateType
11
+ from telegrinder.types.objects import Update
12
+
13
+
14
+ class DataclassAdapter[Dataclass](ABCAdapter[Update, Dataclass]):
15
+ ADAPTED_VALUE_KEY: str
16
+
17
+ def __init__(
18
+ self,
19
+ dataclass: type[Dataclass],
20
+ update_type: UpdateType | None = None,
21
+ ) -> None:
22
+ self.ADAPTED_VALUE_KEY = f"_adapted_dataclass_{dataclass.__name__}"
23
+ self.dataclass = dataclass
24
+ self.update_type = update_type
25
+
26
+ def __repr__(self) -> str:
27
+ return f"<Update -> {self.dataclass.__name__}>"
28
+
29
+ def adapt(self, api: API, update: Update, context: Context) -> Result[Dataclass, AdapterError]:
30
+ if self.ADAPTED_VALUE_KEY in context:
31
+ return Ok(context[self.ADAPTED_VALUE_KEY])
32
+
33
+ update_type = (self.update_type or update.update_type).value
34
+ try:
35
+ if self.dataclass is Update:
36
+ return Ok(update) # type: ignore
37
+ elif issubclass(self.dataclass, UpdateCute):
38
+ dataclass = self.dataclass.from_update(update, bound_api=api)
39
+ else:
40
+ match getattr(update, update_type):
41
+ case Some(val):
42
+ dataclass = (
43
+ self.dataclass.from_update(val, bound_api=api)
44
+ if issubclass(self.dataclass, BaseCute)
45
+ else self.dataclass(**val.to_dict())
46
+ )
47
+ case Nothing():
48
+ return Error(AdapterError(f"Update has no event {update_type!r}."))
49
+ except Exception as e:
50
+ return Error(AdapterError(f"Cannot adapt Update to {self.dataclass!r}, error: {e!r}"))
51
+
52
+ context[self.ADAPTED_VALUE_KEY] = dataclass
53
+ return Ok(dataclass) # type: ignore
54
+
55
+
56
+ __all__ = ("DataclassAdapter",)
@@ -6,19 +6,17 @@ from telegrinder.api.api import API
6
6
  from telegrinder.bot.cute_types.base import BaseCute
7
7
  from telegrinder.bot.cute_types.update import UpdateCute
8
8
  from telegrinder.bot.dispatch.context import Context
9
- from telegrinder.bot.rules.adapter.abc import ABCAdapter
10
- from telegrinder.bot.rules.adapter.errors import AdapterError
11
- from telegrinder.bot.rules.adapter.raw_update import RawUpdateAdapter
9
+ from telegrinder.tools.adapter.abc import ABCAdapter
10
+ from telegrinder.tools.adapter.errors import AdapterError
11
+ from telegrinder.tools.adapter.raw_update import RawUpdateAdapter
12
12
  from telegrinder.types.enums import UpdateType
13
13
  from telegrinder.types.objects import Model, Update
14
14
 
15
- ToCute = typing.TypeVar("ToCute", bound=BaseCute)
16
15
 
17
-
18
- class EventAdapter(ABCAdapter[Update, ToCute]):
16
+ class EventAdapter[ToEvent: BaseCute](ABCAdapter[Update, ToEvent]):
19
17
  ADAPTED_VALUE_KEY: str = "_adapted_cute_event"
20
18
 
21
- def __init__(self, event: UpdateType | type[Model], cute_model: type[ToCute]) -> None:
19
+ def __init__(self, event: UpdateType | type[Model], cute_model: type[ToEvent]) -> None:
22
20
  self.event = event
23
21
  self.cute_model = cute_model
24
22
 
@@ -26,7 +24,7 @@ class EventAdapter(ABCAdapter[Update, ToCute]):
26
24
  raw_update_type = (
27
25
  f"Update -> {self.event.__name__}"
28
26
  if isinstance(self.event, type)
29
- else f"Update.{self.event.value}"
27
+ else f"Update.{self.event.value}" # type: ignore
30
28
  )
31
29
  return "<{}: adapt {} -> {}>".format(
32
30
  self.__class__.__name__,
@@ -43,14 +41,14 @@ class EventAdapter(ABCAdapter[Update, ToCute]):
43
41
 
44
42
  return None
45
43
 
46
- def adapt(self, api: API, update: Update, context: Context) -> Result[ToCute, AdapterError]:
44
+ def adapt(self, api: API, update: Update, context: Context) -> Result[ToEvent, AdapterError]:
47
45
  match RawUpdateAdapter().adapt(api, update, context):
48
46
  case Ok(update_cute) if event := self.get_event(update_cute):
49
47
  if self.ADAPTED_VALUE_KEY in context:
50
48
  return Ok(context[self.ADAPTED_VALUE_KEY])
51
49
 
52
50
  adapted = (
53
- typing.cast(ToCute, event)
51
+ typing.cast(ToEvent, event)
54
52
  if isinstance(event, BaseCute)
55
53
  else self.cute_model.from_update(event, bound_api=api)
56
54
  )