telegrinder 0.3.4__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 (192) hide show
  1. telegrinder/__init__.py +148 -149
  2. telegrinder/api/__init__.py +9 -8
  3. telegrinder/api/api.py +101 -93
  4. telegrinder/api/error.py +20 -16
  5. telegrinder/api/response.py +20 -20
  6. telegrinder/api/token.py +36 -36
  7. telegrinder/bot/__init__.py +72 -66
  8. telegrinder/bot/bot.py +83 -76
  9. telegrinder/bot/cute_types/__init__.py +19 -17
  10. telegrinder/bot/cute_types/base.py +184 -258
  11. telegrinder/bot/cute_types/callback_query.py +400 -385
  12. telegrinder/bot/cute_types/chat_join_request.py +62 -61
  13. telegrinder/bot/cute_types/chat_member_updated.py +157 -160
  14. telegrinder/bot/cute_types/inline_query.py +44 -43
  15. telegrinder/bot/cute_types/message.py +2590 -2637
  16. telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
  17. telegrinder/bot/cute_types/update.py +112 -104
  18. telegrinder/bot/cute_types/utils.py +62 -95
  19. telegrinder/bot/dispatch/__init__.py +59 -55
  20. telegrinder/bot/dispatch/abc.py +76 -77
  21. telegrinder/bot/dispatch/context.py +96 -98
  22. telegrinder/bot/dispatch/dispatch.py +254 -202
  23. telegrinder/bot/dispatch/handler/__init__.py +13 -13
  24. telegrinder/bot/dispatch/handler/abc.py +23 -24
  25. telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
  26. telegrinder/bot/dispatch/handler/base.py +57 -57
  27. telegrinder/bot/dispatch/handler/document_reply.py +44 -44
  28. telegrinder/bot/dispatch/handler/func.py +129 -135
  29. telegrinder/bot/dispatch/handler/media_group_reply.py +44 -43
  30. telegrinder/bot/dispatch/handler/message_reply.py +36 -36
  31. telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
  32. telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
  33. telegrinder/bot/dispatch/handler/video_reply.py +44 -44
  34. telegrinder/bot/dispatch/middleware/__init__.py +3 -3
  35. telegrinder/bot/dispatch/middleware/abc.py +97 -22
  36. telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
  37. telegrinder/bot/dispatch/process.py +151 -157
  38. telegrinder/bot/dispatch/return_manager/__init__.py +15 -13
  39. telegrinder/bot/dispatch/return_manager/abc.py +104 -108
  40. telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
  41. telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
  42. telegrinder/bot/dispatch/return_manager/message.py +36 -36
  43. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
  44. telegrinder/bot/dispatch/view/__init__.py +15 -13
  45. telegrinder/bot/dispatch/view/abc.py +45 -41
  46. telegrinder/bot/dispatch/view/base.py +231 -200
  47. telegrinder/bot/dispatch/view/box.py +140 -129
  48. telegrinder/bot/dispatch/view/callback_query.py +16 -17
  49. telegrinder/bot/dispatch/view/chat_join_request.py +11 -16
  50. telegrinder/bot/dispatch/view/chat_member.py +37 -39
  51. telegrinder/bot/dispatch/view/inline_query.py +16 -17
  52. telegrinder/bot/dispatch/view/message.py +43 -44
  53. telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
  54. telegrinder/bot/dispatch/view/raw.py +116 -114
  55. telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
  56. telegrinder/bot/dispatch/waiter_machine/actions.py +14 -13
  57. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
  58. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
  59. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +59 -57
  60. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
  61. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +20 -19
  62. telegrinder/bot/dispatch/waiter_machine/machine.py +251 -172
  63. telegrinder/bot/dispatch/waiter_machine/middleware.py +94 -89
  64. telegrinder/bot/dispatch/waiter_machine/short_state.py +57 -68
  65. telegrinder/bot/polling/__init__.py +4 -4
  66. telegrinder/bot/polling/abc.py +25 -25
  67. telegrinder/bot/polling/polling.py +139 -131
  68. telegrinder/bot/rules/__init__.py +85 -62
  69. telegrinder/bot/rules/abc.py +213 -206
  70. telegrinder/bot/rules/callback_data.py +122 -163
  71. telegrinder/bot/rules/chat_join.py +45 -43
  72. telegrinder/bot/rules/command.py +126 -126
  73. telegrinder/bot/rules/enum_text.py +33 -36
  74. telegrinder/bot/rules/func.py +28 -26
  75. telegrinder/bot/rules/fuzzy.py +24 -24
  76. telegrinder/bot/rules/id.py +24 -0
  77. telegrinder/bot/rules/inline.py +58 -56
  78. telegrinder/bot/rules/integer.py +21 -20
  79. telegrinder/bot/rules/is_from.py +127 -127
  80. telegrinder/bot/rules/logic.py +18 -0
  81. telegrinder/bot/rules/markup.py +42 -43
  82. telegrinder/bot/rules/mention.py +14 -14
  83. telegrinder/bot/rules/message.py +15 -17
  84. telegrinder/bot/rules/message_entities.py +33 -35
  85. telegrinder/bot/rules/node.py +33 -27
  86. telegrinder/bot/rules/payload.py +81 -0
  87. telegrinder/bot/rules/payment_invoice.py +29 -0
  88. telegrinder/bot/rules/regex.py +36 -37
  89. telegrinder/bot/rules/rule_enum.py +72 -72
  90. telegrinder/bot/rules/start.py +42 -42
  91. telegrinder/bot/rules/state.py +35 -37
  92. telegrinder/bot/rules/text.py +38 -33
  93. telegrinder/bot/rules/update.py +15 -15
  94. telegrinder/bot/scenario/__init__.py +5 -5
  95. telegrinder/bot/scenario/abc.py +17 -19
  96. telegrinder/bot/scenario/checkbox.py +174 -176
  97. telegrinder/bot/scenario/choice.py +48 -51
  98. telegrinder/client/__init__.py +12 -4
  99. telegrinder/client/abc.py +100 -75
  100. telegrinder/client/aiohttp.py +134 -130
  101. telegrinder/client/form_data.py +31 -0
  102. telegrinder/client/sonic.py +212 -0
  103. telegrinder/model.py +208 -315
  104. telegrinder/modules.py +239 -237
  105. telegrinder/msgspec_json.py +14 -14
  106. telegrinder/msgspec_utils.py +478 -410
  107. telegrinder/node/__init__.py +86 -25
  108. telegrinder/node/attachment.py +163 -87
  109. telegrinder/node/base.py +288 -160
  110. telegrinder/node/callback_query.py +54 -53
  111. telegrinder/node/command.py +34 -33
  112. telegrinder/node/composer.py +163 -198
  113. telegrinder/node/container.py +33 -27
  114. telegrinder/node/either.py +82 -0
  115. telegrinder/node/event.py +54 -65
  116. telegrinder/node/file.py +51 -0
  117. telegrinder/node/me.py +15 -16
  118. telegrinder/node/payload.py +78 -0
  119. telegrinder/node/polymorphic.py +67 -48
  120. telegrinder/node/rule.py +72 -76
  121. telegrinder/node/scope.py +36 -38
  122. telegrinder/node/source.py +87 -71
  123. telegrinder/node/text.py +53 -41
  124. telegrinder/node/tools/__init__.py +3 -3
  125. telegrinder/node/tools/generator.py +36 -40
  126. telegrinder/py.typed +0 -0
  127. telegrinder/rules.py +1 -62
  128. telegrinder/tools/__init__.py +152 -93
  129. telegrinder/tools/adapter/__init__.py +19 -0
  130. telegrinder/tools/adapter/abc.py +49 -0
  131. telegrinder/tools/adapter/dataclass.py +56 -0
  132. telegrinder/{bot/rules → tools}/adapter/errors.py +5 -5
  133. telegrinder/{bot/rules → tools}/adapter/event.py +63 -65
  134. telegrinder/{bot/rules → tools}/adapter/node.py +46 -48
  135. telegrinder/{bot/rules → tools}/adapter/raw_event.py +27 -27
  136. telegrinder/{bot/rules → tools}/adapter/raw_update.py +30 -30
  137. telegrinder/tools/buttons.py +106 -80
  138. telegrinder/tools/callback_data_serilization/__init__.py +5 -0
  139. telegrinder/tools/callback_data_serilization/abc.py +51 -0
  140. telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
  141. telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
  142. telegrinder/tools/error_handler/__init__.py +7 -7
  143. telegrinder/tools/error_handler/abc.py +30 -33
  144. telegrinder/tools/error_handler/error.py +9 -9
  145. telegrinder/tools/error_handler/error_handler.py +179 -193
  146. telegrinder/tools/formatting/__init__.py +83 -63
  147. telegrinder/tools/formatting/deep_links.py +541 -0
  148. telegrinder/tools/formatting/{html.py → html_formatter.py} +266 -294
  149. telegrinder/tools/formatting/spec_html_formats.py +71 -117
  150. telegrinder/tools/functional.py +8 -12
  151. telegrinder/tools/global_context/__init__.py +7 -7
  152. telegrinder/tools/global_context/abc.py +63 -63
  153. telegrinder/tools/global_context/global_context.py +387 -412
  154. telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
  155. telegrinder/tools/i18n/__init__.py +7 -7
  156. telegrinder/tools/i18n/abc.py +30 -30
  157. telegrinder/tools/i18n/middleware/__init__.py +3 -3
  158. telegrinder/tools/i18n/middleware/abc.py +22 -25
  159. telegrinder/tools/i18n/simple.py +43 -43
  160. telegrinder/tools/input_file_directory.py +30 -0
  161. telegrinder/tools/keyboard.py +128 -128
  162. telegrinder/tools/lifespan.py +105 -0
  163. telegrinder/tools/limited_dict.py +32 -37
  164. telegrinder/tools/loop_wrapper/__init__.py +4 -4
  165. telegrinder/tools/loop_wrapper/abc.py +20 -15
  166. telegrinder/tools/loop_wrapper/loop_wrapper.py +169 -224
  167. telegrinder/tools/magic.py +307 -157
  168. telegrinder/tools/parse_mode.py +6 -6
  169. telegrinder/tools/state_storage/__init__.py +4 -4
  170. telegrinder/tools/state_storage/abc.py +31 -35
  171. telegrinder/tools/state_storage/memory.py +25 -25
  172. telegrinder/tools/strings.py +13 -0
  173. telegrinder/types/__init__.py +268 -260
  174. telegrinder/types/enums.py +711 -701
  175. telegrinder/types/input_file.py +51 -0
  176. telegrinder/types/methods.py +5055 -4633
  177. telegrinder/types/objects.py +7058 -6950
  178. telegrinder/verification_utils.py +30 -32
  179. {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +22 -22
  180. telegrinder-0.4.0.dist-info/METADATA +144 -0
  181. telegrinder-0.4.0.dist-info/RECORD +182 -0
  182. {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
  183. telegrinder/bot/rules/adapter/__init__.py +0 -17
  184. telegrinder/bot/rules/adapter/abc.py +0 -31
  185. telegrinder/node/message.py +0 -14
  186. telegrinder/node/update.py +0 -15
  187. telegrinder/tools/formatting/links.py +0 -38
  188. telegrinder/tools/kb_set/__init__.py +0 -4
  189. telegrinder/tools/kb_set/base.py +0 -15
  190. telegrinder/tools/kb_set/yaml.py +0 -63
  191. telegrinder-0.3.4.dist-info/METADATA +0 -110
  192. telegrinder-0.3.4.dist-info/RECORD +0 -165
@@ -1,65 +1,63 @@
1
- import typing
2
-
3
- from fntypes.result import Error, Ok, Result
4
-
5
- from telegrinder.api.api import API
6
- from telegrinder.bot.cute_types.base import BaseCute
7
- from telegrinder.bot.cute_types.update import UpdateCute
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
12
- from telegrinder.types.enums import UpdateType
13
- from telegrinder.types.objects import Model, Update
14
-
15
- ToCute = typing.TypeVar("ToCute", bound=BaseCute)
16
-
17
-
18
- class EventAdapter(ABCAdapter[Update, ToCute]):
19
- ADAPTED_VALUE_KEY: str = "_adapted_cute_event"
20
-
21
- def __init__(self, event: UpdateType | type[Model], cute_model: type[ToCute]) -> None:
22
- self.event = event
23
- self.cute_model = cute_model
24
-
25
- def __repr__(self) -> str:
26
- raw_update_type = (
27
- f"Update -> {self.event.__name__}"
28
- if isinstance(self.event, type)
29
- else f"Update.{self.event.value}"
30
- )
31
- return "<{}: adapt {} -> {}>".format(
32
- self.__class__.__name__,
33
- raw_update_type,
34
- self.cute_model.__name__,
35
- )
36
-
37
- def get_event(self, update: UpdateCute) -> Model | None:
38
- if isinstance(self.event, UpdateType) and self.event == update.update_type:
39
- return update.incoming_update
40
-
41
- if not isinstance(self.event, UpdateType) and (event := update.get_event(self.event)):
42
- return event.unwrap()
43
-
44
- return None
45
-
46
- def adapt(self, api: API, update: Update, context: Context) -> Result[ToCute, AdapterError]:
47
- match RawUpdateAdapter().adapt(api, update, context):
48
- case Ok(update_cute) if event := self.get_event(update_cute):
49
- if self.ADAPTED_VALUE_KEY in context:
50
- return Ok(context[self.ADAPTED_VALUE_KEY])
51
-
52
- adapted = (
53
- typing.cast(ToCute, event)
54
- if isinstance(event, BaseCute)
55
- else self.cute_model.from_update(event, bound_api=api)
56
- )
57
- context[self.ADAPTED_VALUE_KEY] = adapted
58
- return Ok(adapted)
59
- case Error(_) as err:
60
- return err
61
- case _:
62
- return Error(AdapterError(f"Update is not an {self.event!r}."))
63
-
64
-
65
- __all__ = ("EventAdapter",)
1
+ import typing
2
+
3
+ from fntypes.result import Error, Ok, Result
4
+
5
+ from telegrinder.api.api import API
6
+ from telegrinder.bot.cute_types.base import BaseCute
7
+ from telegrinder.bot.cute_types.update import UpdateCute
8
+ from telegrinder.bot.dispatch.context import Context
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
+ from telegrinder.types.enums import UpdateType
13
+ from telegrinder.types.objects import Model, Update
14
+
15
+
16
+ class EventAdapter[ToEvent: BaseCute](ABCAdapter[Update, ToEvent]):
17
+ ADAPTED_VALUE_KEY: str = "_adapted_cute_event"
18
+
19
+ def __init__(self, event: UpdateType | type[Model], cute_model: type[ToEvent]) -> None:
20
+ self.event = event
21
+ self.cute_model = cute_model
22
+
23
+ def __repr__(self) -> str:
24
+ raw_update_type = (
25
+ f"Update -> {self.event.__name__}"
26
+ if isinstance(self.event, type)
27
+ else f"Update.{self.event.value}" # type: ignore
28
+ )
29
+ return "<{}: adapt {} -> {}>".format(
30
+ self.__class__.__name__,
31
+ raw_update_type,
32
+ self.cute_model.__name__,
33
+ )
34
+
35
+ def get_event(self, update: UpdateCute) -> Model | None:
36
+ if isinstance(self.event, UpdateType) and self.event == update.update_type:
37
+ return update.incoming_update
38
+
39
+ if not isinstance(self.event, UpdateType) and (event := update.get_event(self.event)):
40
+ return event.unwrap()
41
+
42
+ return None
43
+
44
+ def adapt(self, api: API, update: Update, context: Context) -> Result[ToEvent, AdapterError]:
45
+ match RawUpdateAdapter().adapt(api, update, context):
46
+ case Ok(update_cute) if event := self.get_event(update_cute):
47
+ if self.ADAPTED_VALUE_KEY in context:
48
+ return Ok(context[self.ADAPTED_VALUE_KEY])
49
+
50
+ adapted = (
51
+ typing.cast(ToEvent, event)
52
+ if isinstance(event, BaseCute)
53
+ else self.cute_model.from_update(event, bound_api=api)
54
+ )
55
+ context[self.ADAPTED_VALUE_KEY] = adapted
56
+ return Ok(adapted)
57
+ case Error(_) as err:
58
+ return err
59
+ case _:
60
+ return Error(AdapterError(f"Update is not an {self.event!r}."))
61
+
62
+
63
+ __all__ = ("EventAdapter",)
@@ -1,48 +1,46 @@
1
- import typing_extensions as typing
2
- from fntypes.result import Error, Ok, Result
3
-
4
- from telegrinder.api.api import API
5
- from telegrinder.bot.dispatch.context import Context
6
- from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
7
- from telegrinder.bot.rules.adapter.errors import AdapterError
8
- from telegrinder.msgspec_utils import repr_type
9
- from telegrinder.node.composer import NodeSession, compose_nodes
10
- from telegrinder.types.objects import Update
11
-
12
- if typing.TYPE_CHECKING:
13
- from telegrinder.node.base import Node
14
-
15
- Ts = typing.TypeVarTuple("Ts", default=typing.Unpack[tuple[type["Node"], ...]])
16
-
17
-
18
- class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
19
- def __init__(self, *nodes: *Ts) -> None:
20
- self.nodes = nodes
21
-
22
- def __repr__(self) -> str:
23
- return "<{}: adapt Update -> ({})>".format(
24
- self.__class__.__name__,
25
- ", ".join(repr_type(node) for node in self.nodes),
26
- )
27
-
28
- async def adapt(
29
- self,
30
- api: API,
31
- update: Update,
32
- context: Context,
33
- ) -> Result[Event[tuple[*Ts]], AdapterError]:
34
- result = await compose_nodes(
35
- nodes={str(i): typing.cast(type["Node"], node) for i, node in enumerate(self.nodes)},
36
- ctx=context,
37
- data={Update: update, API: api},
38
- )
39
-
40
- match result:
41
- case Ok(collection):
42
- sessions: list[NodeSession] = list(collection.sessions.values())
43
- return Ok(Event(tuple(sessions))) # type: ignore
44
- case Error(err):
45
- return Error(AdapterError(f"Couldn't compose nodes, error: {err}."))
46
-
47
-
48
- __all__ = ("NodeAdapter",)
1
+ import typing_extensions as typing
2
+ from fntypes.result import Error, Ok, Result
3
+
4
+ from telegrinder.api.api import API
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.msgspec_utils import repr_type
7
+ from telegrinder.node.composer import NodeSession, compose_nodes
8
+ from telegrinder.tools.adapter.abc import ABCAdapter, Event
9
+ from telegrinder.tools.adapter.errors import AdapterError
10
+ from telegrinder.types.objects import Update
11
+
12
+ if typing.TYPE_CHECKING:
13
+ from telegrinder.node.base import IsNode
14
+
15
+
16
+ class NodeAdapter(ABCAdapter[Update, Event[tuple["IsNode", ...]]]):
17
+ def __init__(self, *nodes: "IsNode") -> None:
18
+ self.nodes = nodes
19
+
20
+ def __repr__(self) -> str:
21
+ return "<{}: adapt Update -> ({})>".format(
22
+ self.__class__.__name__,
23
+ ", ".join(repr_type(node) for node in self.nodes),
24
+ )
25
+
26
+ async def adapt(
27
+ self,
28
+ api: API,
29
+ update: Update,
30
+ context: Context,
31
+ ) -> Result[Event[tuple[NodeSession, ...]], AdapterError]:
32
+ result = await compose_nodes(
33
+ nodes={f"node_{i}": typing.cast("IsNode", node) for i, node in enumerate(self.nodes)},
34
+ ctx=context,
35
+ data={Update: update, API: api},
36
+ )
37
+
38
+ match result:
39
+ case Ok(collection):
40
+ sessions: list[NodeSession] = list(collection.sessions.values())
41
+ return Ok(Event(tuple(sessions)))
42
+ case Error(err):
43
+ return Error(AdapterError(f"Couldn't compose nodes, error: {err}."))
44
+
45
+
46
+ __all__ = ("NodeAdapter",)
@@ -1,27 +1,27 @@
1
- from fntypes.result import Error, Ok, Result
2
-
3
- from telegrinder.api.api import API
4
- from telegrinder.bot.dispatch.context import Context
5
- from telegrinder.bot.rules.adapter.abc import ABCAdapter
6
- from telegrinder.bot.rules.adapter.errors import AdapterError
7
- from telegrinder.model import Model
8
- from telegrinder.types.objects import Update
9
-
10
-
11
- class RawEventAdapter(ABCAdapter[Update, Model]):
12
- def __init__(self, event_model: type[Model], /) -> None:
13
- self.event_model = event_model
14
-
15
- def __repr__(self) -> str:
16
- return "<{}: adapt Update -> {}>".format(
17
- self.__class__.__name__,
18
- self.event_model.__name__,
19
- )
20
-
21
- def adapt(self, api: API, update: Update, context: Context) -> Result[Model, AdapterError]:
22
- if isinstance(update.incoming_update, self.event_model):
23
- return Ok(update.incoming_update)
24
- return Error(AdapterError(f"Update is not an {self.event_model.__name__!r}."))
25
-
26
-
27
- __all__ = ("RawEventAdapter",)
1
+ from fntypes.result import Error, Ok, Result
2
+
3
+ from telegrinder.api.api import API
4
+ from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.model import Model
6
+ from telegrinder.tools.adapter.abc import ABCAdapter
7
+ from telegrinder.tools.adapter.errors import AdapterError
8
+ from telegrinder.types.objects import Update
9
+
10
+
11
+ class RawEventAdapter(ABCAdapter[Update, Model]):
12
+ def __init__(self, event_model: type[Model], /) -> None:
13
+ self.event_model = event_model
14
+
15
+ def __repr__(self) -> str:
16
+ return "<{}: adapt Update -> {}>".format(
17
+ self.__class__.__name__,
18
+ self.event_model.__name__,
19
+ )
20
+
21
+ def adapt(self, api: API, update: Update, context: Context) -> Result[Model, AdapterError]:
22
+ if isinstance(update.incoming_update, self.event_model):
23
+ return Ok(update.incoming_update)
24
+ return Error(AdapterError(f"Update is not an {self.event_model.__name__!r}."))
25
+
26
+
27
+ __all__ = ("RawEventAdapter",)
@@ -1,30 +1,30 @@
1
- from fntypes.result import Ok, Result
2
-
3
- from telegrinder.api.api import API
4
- from telegrinder.bot.cute_types.update import UpdateCute
5
- from telegrinder.bot.dispatch.context import Context
6
- from telegrinder.bot.rules.adapter.abc import ABCAdapter
7
- from telegrinder.bot.rules.adapter.errors import AdapterError
8
- from telegrinder.types.objects import Update
9
-
10
-
11
- class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
12
- ADAPTED_VALUE_KEY: str = "_adapted_update_cute"
13
-
14
- def __repr__(self) -> str:
15
- return f"<{self.__class__.__name__}: adapt Update -> UpdateCute>"
16
-
17
- def adapt(
18
- self,
19
- api: API,
20
- update: Update,
21
- context: Context,
22
- ) -> Result[UpdateCute, AdapterError]:
23
- if self.ADAPTED_VALUE_KEY not in context:
24
- context[self.ADAPTED_VALUE_KEY] = (
25
- UpdateCute.from_update(update, api) if not isinstance(update, UpdateCute) else update
26
- )
27
- return Ok(context[self.ADAPTED_VALUE_KEY])
28
-
29
-
30
- __all__ = ("RawUpdateAdapter",)
1
+ from fntypes.result import Ok, Result
2
+
3
+ from telegrinder.api.api import API
4
+ from telegrinder.bot.cute_types.update import UpdateCute
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.tools.adapter.abc import ABCAdapter
7
+ from telegrinder.tools.adapter.errors import AdapterError
8
+ from telegrinder.types.objects import Update
9
+
10
+
11
+ class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
12
+ ADAPTED_VALUE_KEY: str = "_adapted_update_cute"
13
+
14
+ def __repr__(self) -> str:
15
+ return f"<{self.__class__.__name__}: adapt Update -> UpdateCute>"
16
+
17
+ def adapt(
18
+ self,
19
+ api: API,
20
+ update: Update,
21
+ context: Context,
22
+ ) -> Result[UpdateCute, AdapterError]:
23
+ if self.ADAPTED_VALUE_KEY not in context:
24
+ context[self.ADAPTED_VALUE_KEY] = (
25
+ UpdateCute.from_update(update, api) if not isinstance(update, UpdateCute) else update
26
+ )
27
+ return Ok(context[self.ADAPTED_VALUE_KEY])
28
+
29
+
30
+ __all__ = ("RawUpdateAdapter",)
@@ -1,83 +1,109 @@
1
- import dataclasses
2
- import typing
3
-
4
- import msgspec
5
-
6
- from telegrinder.msgspec_utils import DataclassInstance, encoder
7
- from telegrinder.types.objects import (
8
- CallbackGame,
9
- KeyboardButtonPollType,
10
- KeyboardButtonRequestChat,
11
- KeyboardButtonRequestUsers,
12
- LoginUrl,
13
- SwitchInlineQueryChosenChat,
14
- WebAppInfo,
15
- )
16
-
17
- KeyboardButton = typing.TypeVar("KeyboardButton", bound="BaseButton")
18
-
19
-
20
- @dataclasses.dataclass
21
- class BaseButton:
22
- def get_data(self) -> dict[str, typing.Any]:
23
- return {
24
- k: v if k != "callback_data" or isinstance(v, str) else encoder.encode(v)
25
- for k, v in dataclasses.asdict(self).items()
26
- if v is not None
27
- }
28
-
29
-
30
- class RowButtons(typing.Generic[KeyboardButton]):
31
- buttons: list[KeyboardButton]
32
- auto_row: bool
33
-
34
- def __init__(self, *buttons: KeyboardButton, auto_row: bool = True) -> None:
35
- self.buttons = list(buttons)
36
- self.auto_row = auto_row
37
-
38
- def get_data(self) -> list[dict[str, typing.Any]]:
39
- return [b.get_data() for b in self.buttons]
40
-
41
-
42
- @dataclasses.dataclass(slots=True)
43
- class Button(BaseButton):
44
- text: str
45
- request_contact: bool = dataclasses.field(default=False, kw_only=True)
46
- request_location: bool = dataclasses.field(default=False, kw_only=True)
47
- request_chat: dict[str, typing.Any] | KeyboardButtonRequestChat | None = dataclasses.field(
48
- default=None, kw_only=True
49
- )
50
- request_user: dict[str, typing.Any] | KeyboardButtonRequestUsers | None = dataclasses.field(
51
- default=None, kw_only=True
52
- )
53
- request_poll: dict[str, typing.Any] | KeyboardButtonPollType | None = dataclasses.field(
54
- default=None, kw_only=True
55
- )
56
- web_app: dict[str, typing.Any] | WebAppInfo | None = dataclasses.field(default=None, kw_only=True)
57
-
58
-
59
- @dataclasses.dataclass(slots=True)
60
- class InlineButton(BaseButton):
61
- text: str
62
- url: str | None = dataclasses.field(default=None, kw_only=True)
63
- login_url: dict[str, typing.Any] | LoginUrl | None = dataclasses.field(default=None, kw_only=True)
64
- pay: bool | None = dataclasses.field(default=None, kw_only=True)
65
- callback_data: str | dict[str, typing.Any] | DataclassInstance | msgspec.Struct | None = (
66
- dataclasses.field(default=None, kw_only=True)
67
- )
68
- callback_game: dict[str, typing.Any] | CallbackGame | None = dataclasses.field(default=None, kw_only=True)
69
- switch_inline_query: str | None = dataclasses.field(default=None, kw_only=True)
70
- switch_inline_query_current_chat: str | None = dataclasses.field(default=None, kw_only=True)
71
- switch_inline_query_chosen_chat: dict[str, typing.Any] | SwitchInlineQueryChosenChat | None = (
72
- dataclasses.field(default=None, kw_only=True)
73
- )
74
- web_app: dict[str, typing.Any] | WebAppInfo | None = dataclasses.field(default=None, kw_only=True)
75
-
76
-
77
- __all__ = (
1
+ import dataclasses
2
+ import typing
3
+
4
+ import msgspec
5
+
6
+ from telegrinder.msgspec_utils import encoder
7
+ from telegrinder.types.objects import (
8
+ CallbackGame,
9
+ CopyTextButton,
10
+ KeyboardButtonPollType,
11
+ KeyboardButtonRequestChat,
12
+ KeyboardButtonRequestUsers,
13
+ LoginUrl,
14
+ SwitchInlineQueryChosenChat,
15
+ WebAppInfo,
16
+ )
17
+
18
+ from .callback_data_serilization import ABCDataSerializer, JSONSerializer
19
+
20
+ if typing.TYPE_CHECKING:
21
+ from _typeshed import DataclassInstance
22
+
23
+ type CallbackData = str | bytes | dict[str, typing.Any] | DataclassInstance | msgspec.Struct
24
+
25
+
26
+ @dataclasses.dataclass
27
+ class BaseButton:
28
+ def get_data(self) -> dict[str, typing.Any]:
29
+ return {k: v for k, v in dataclasses.asdict(self).items() if v is not None}
30
+
31
+
32
+ class RowButtons[KeyboardButton: BaseButton]:
33
+ buttons: list[KeyboardButton]
34
+ auto_row: bool
35
+
36
+ def __init__(self, *buttons: KeyboardButton, auto_row: bool = True) -> None:
37
+ self.buttons = list(buttons)
38
+ self.auto_row = auto_row
39
+
40
+ def get_data(self) -> list[dict[str, typing.Any]]:
41
+ return [b.get_data() for b in self.buttons]
42
+
43
+
44
+ @dataclasses.dataclass
45
+ class Button(BaseButton):
46
+ text: str
47
+ request_contact: bool = dataclasses.field(default=False, kw_only=True)
48
+ request_location: bool = dataclasses.field(default=False, kw_only=True)
49
+ request_chat: KeyboardButtonRequestChat | None = dataclasses.field(
50
+ default=None,
51
+ kw_only=True,
52
+ )
53
+ request_user: KeyboardButtonRequestUsers | None = dataclasses.field(default=None, kw_only=True)
54
+ request_poll: KeyboardButtonPollType | None = dataclasses.field(
55
+ default=None,
56
+ kw_only=True,
57
+ )
58
+ web_app: WebAppInfo | None = dataclasses.field(default=None, kw_only=True)
59
+
60
+
61
+ @dataclasses.dataclass
62
+ class InlineButton(BaseButton):
63
+ text: str
64
+ url: str | None = dataclasses.field(default=None, kw_only=True)
65
+ login_url: LoginUrl | None = dataclasses.field(default=None, kw_only=True)
66
+ pay: bool | None = dataclasses.field(default=None, kw_only=True)
67
+ callback_data: CallbackData | None = dataclasses.field(default=None, kw_only=True)
68
+ callback_data_serializer: dataclasses.InitVar[ABCDataSerializer[typing.Any] | None] = dataclasses.field(
69
+ default=None,
70
+ kw_only=True,
71
+ )
72
+ callback_game: CallbackGame | None = dataclasses.field(default=None, kw_only=True)
73
+ copy_text: str | CopyTextButton | None = dataclasses.field(default=None, kw_only=True)
74
+ switch_inline_query: str | None = dataclasses.field(default=None, kw_only=True)
75
+ switch_inline_query_current_chat: str | None = dataclasses.field(default=None, kw_only=True)
76
+ switch_inline_query_chosen_chat: SwitchInlineQueryChosenChat | None = dataclasses.field(
77
+ default=None,
78
+ kw_only=True,
79
+ )
80
+ web_app: str | WebAppInfo | None = dataclasses.field(default=None, kw_only=True)
81
+
82
+ def __post_init__(self, callback_data_serializer: ABCDataSerializer[typing.Any] | None) -> None:
83
+ if (
84
+ callback_data_serializer is None
85
+ and isinstance(self.callback_data, msgspec.Struct | dict)
86
+ or dataclasses.is_dataclass(self.callback_data)
87
+ ):
88
+ callback_data_serializer = callback_data_serializer or JSONSerializer(
89
+ self.callback_data.__class__,
90
+ )
91
+
92
+ if callback_data_serializer is not None:
93
+ self.callback_data = callback_data_serializer.serialize(self.callback_data)
94
+ elif self.callback_data is not None and not isinstance(self.callback_data, str | bytes):
95
+ self.callback_data = encoder.encode(self.callback_data)
96
+
97
+ if isinstance(self.copy_text, str):
98
+ self.copy_text = CopyTextButton(text=self.copy_text)
99
+
100
+ if isinstance(self.web_app, str):
101
+ self.web_app = WebAppInfo(url=self.web_app)
102
+
103
+
104
+ __all__ = (
78
105
  "BaseButton",
79
106
  "Button",
80
- "DataclassInstance",
81
107
  "InlineButton",
82
- "RowButtons",
83
- )
108
+ "RowButtons",
109
+ )
@@ -0,0 +1,5 @@
1
+ from .abc import ABCDataSerializer
2
+ from .json_ser import JSONSerializer
3
+ from .msgpack_ser import MsgPackSerializer
4
+
5
+ __all__ = ("ABCDataSerializer", "JSONSerializer", "MsgPackSerializer")
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import typing
5
+ from functools import cached_property
6
+
7
+ import msgspec
8
+ from fntypes.result import Result
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from dataclasses import Field
12
+
13
+ from _typeshed import DataclassInstance
14
+
15
+ type ModelType = DataclassWithIdentKey | ModelWithIdentKey | msgspec.Struct | DataclassInstance
16
+
17
+
18
+ @typing.runtime_checkable
19
+ class DataclassWithIdentKey(typing.Protocol):
20
+ __key__: str
21
+ __dataclass_fields__: typing.ClassVar[dict[str, Field[typing.Any]]]
22
+
23
+
24
+ @typing.runtime_checkable
25
+ class ModelWithIdentKey(typing.Protocol):
26
+ __key__: str
27
+ __struct_fields__: typing.ClassVar[tuple[str, ...]]
28
+ __struct_config__: typing.ClassVar[msgspec.structs.StructConfig]
29
+
30
+
31
+ class ABCDataSerializer[Data](abc.ABC):
32
+ ident_key: str | None = None
33
+
34
+ @abc.abstractmethod
35
+ def __init__(self, data_type: type[Data], /) -> None:
36
+ pass
37
+
38
+ @cached_property
39
+ def key(self) -> str:
40
+ return self.ident_key + "_" if self.ident_key else ""
41
+
42
+ @abc.abstractmethod
43
+ def serialize(self, data: Data) -> str:
44
+ pass
45
+
46
+ @abc.abstractmethod
47
+ def deserialize(self, serialized_data: str) -> Result[Data, str]:
48
+ pass
49
+
50
+
51
+ __all__ = ("ABCDataSerializer",)
@@ -0,0 +1,60 @@
1
+ import typing
2
+
3
+ import msgspec
4
+ from fntypes.result import Error, Ok, Result
5
+
6
+ from telegrinder.modules import json
7
+ from telegrinder.msgspec_utils import decoder
8
+
9
+ from .abc import ABCDataSerializer, ModelType
10
+
11
+ type Json = dict[str, typing.Any] | ModelType
12
+
13
+
14
+ class JSONSerializer[JsonT: Json](ABCDataSerializer[JsonT]):
15
+ @typing.overload
16
+ def __init__(self, model_t: type[JsonT]) -> None: ...
17
+
18
+ @typing.overload
19
+ def __init__(self, model_t: type[JsonT], *, ident_key: str | None = ...) -> None: ...
20
+
21
+ def __init__(
22
+ self,
23
+ model_t: type[JsonT] = dict[str, typing.Any],
24
+ *,
25
+ ident_key: str | None = None,
26
+ ) -> None:
27
+ self.model_t = model_t
28
+ self.ident_key: str | None = ident_key or getattr(model_t, "__key__", None)
29
+
30
+ @classmethod
31
+ def serialize_from_json(cls, data: JsonT, *, ident_key: str | None = None) -> str:
32
+ return cls(data.__class__, ident_key=ident_key).serialize(data)
33
+
34
+ @classmethod
35
+ def deserialize_to_json(cls, serialized_data: str, model_t: type[JsonT]) -> Result[JsonT, str]:
36
+ return cls(model_t).deserialize(serialized_data)
37
+
38
+ def serialize(self, data: JsonT) -> str:
39
+ return self.key + json.dumps(data)
40
+
41
+ def deserialize(self, serialized_data: str) -> Result[JsonT, str]:
42
+ if self.ident_key and not serialized_data.startswith(self.key):
43
+ return Error("Data is not corresponding to key.")
44
+
45
+ data = serialized_data.removeprefix(self.key)
46
+ try:
47
+ data_obj = json.loads(data)
48
+ except (msgspec.ValidationError, msgspec.DecodeError):
49
+ return Error("Cannot decode json.")
50
+
51
+ if not issubclass(self.model_t, dict):
52
+ try:
53
+ return Ok(decoder.convert(data_obj, type=self.model_t))
54
+ except (msgspec.ValidationError, msgspec.DecodeError):
55
+ return Error("Incorrect data.")
56
+
57
+ return Ok(data_obj)
58
+
59
+
60
+ __all__ = ("JSONSerializer",)