telegrinder 0.4.2__py3-none-any.whl → 0.5.1__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 (233) hide show
  1. telegrinder/__init__.py +37 -55
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +6 -4
  4. telegrinder/api/api.py +100 -26
  5. telegrinder/api/error.py +42 -8
  6. telegrinder/api/response.py +4 -1
  7. telegrinder/api/token.py +2 -2
  8. telegrinder/bot/__init__.py +9 -25
  9. telegrinder/bot/bot.py +31 -25
  10. telegrinder/bot/cute_types/__init__.py +0 -0
  11. telegrinder/bot/cute_types/base.py +103 -61
  12. telegrinder/bot/cute_types/callback_query.py +447 -400
  13. telegrinder/bot/cute_types/chat_join_request.py +59 -62
  14. telegrinder/bot/cute_types/chat_member_updated.py +154 -157
  15. telegrinder/bot/cute_types/inline_query.py +41 -44
  16. telegrinder/bot/cute_types/message.py +98 -67
  17. telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
  18. telegrinder/bot/cute_types/update.py +1 -8
  19. telegrinder/bot/cute_types/utils.py +1 -1
  20. telegrinder/bot/dispatch/__init__.py +10 -15
  21. telegrinder/bot/dispatch/abc.py +12 -11
  22. telegrinder/bot/dispatch/action.py +104 -0
  23. telegrinder/bot/dispatch/context.py +32 -26
  24. telegrinder/bot/dispatch/dispatch.py +61 -134
  25. telegrinder/bot/dispatch/handler/__init__.py +2 -0
  26. telegrinder/bot/dispatch/handler/abc.py +10 -8
  27. telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
  28. telegrinder/bot/dispatch/handler/base.py +10 -33
  29. telegrinder/bot/dispatch/handler/document_reply.py +2 -3
  30. telegrinder/bot/dispatch/handler/func.py +55 -87
  31. telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
  32. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  33. telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
  34. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
  35. telegrinder/bot/dispatch/handler/video_reply.py +2 -3
  36. telegrinder/bot/dispatch/middleware/__init__.py +0 -0
  37. telegrinder/bot/dispatch/middleware/abc.py +79 -55
  38. telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
  39. telegrinder/bot/dispatch/process.py +84 -105
  40. telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
  41. telegrinder/bot/dispatch/return_manager/abc.py +102 -65
  42. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
  43. telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
  44. telegrinder/bot/dispatch/return_manager/message.py +8 -10
  45. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
  46. telegrinder/bot/dispatch/view/__init__.py +4 -4
  47. telegrinder/bot/dispatch/view/abc.py +6 -16
  48. telegrinder/bot/dispatch/view/base.py +54 -178
  49. telegrinder/bot/dispatch/view/box.py +19 -18
  50. telegrinder/bot/dispatch/view/callback_query.py +4 -8
  51. telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
  52. telegrinder/bot/dispatch/view/chat_member.py +5 -25
  53. telegrinder/bot/dispatch/view/error.py +9 -0
  54. telegrinder/bot/dispatch/view/inline_query.py +4 -8
  55. telegrinder/bot/dispatch/view/message.py +5 -25
  56. telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
  57. telegrinder/bot/dispatch/view/raw.py +3 -109
  58. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
  59. telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
  60. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
  61. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
  62. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
  63. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  64. telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
  65. telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
  66. telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
  67. telegrinder/bot/polling/__init__.py +0 -0
  68. telegrinder/bot/polling/abc.py +0 -0
  69. telegrinder/bot/polling/polling.py +209 -88
  70. telegrinder/bot/rules/__init__.py +3 -16
  71. telegrinder/bot/rules/abc.py +42 -122
  72. telegrinder/bot/rules/callback_data.py +29 -49
  73. telegrinder/bot/rules/chat_join.py +5 -23
  74. telegrinder/bot/rules/command.py +8 -4
  75. telegrinder/bot/rules/enum_text.py +3 -4
  76. telegrinder/bot/rules/func.py +7 -14
  77. telegrinder/bot/rules/fuzzy.py +3 -4
  78. telegrinder/bot/rules/inline.py +8 -20
  79. telegrinder/bot/rules/integer.py +2 -3
  80. telegrinder/bot/rules/is_from.py +12 -11
  81. telegrinder/bot/rules/logic.py +11 -5
  82. telegrinder/bot/rules/markup.py +22 -14
  83. telegrinder/bot/rules/mention.py +8 -7
  84. telegrinder/bot/rules/message_entities.py +8 -4
  85. telegrinder/bot/rules/node.py +23 -12
  86. telegrinder/bot/rules/payload.py +5 -4
  87. telegrinder/bot/rules/payment_invoice.py +6 -21
  88. telegrinder/bot/rules/regex.py +2 -4
  89. telegrinder/bot/rules/rule_enum.py +8 -7
  90. telegrinder/bot/rules/start.py +5 -6
  91. telegrinder/bot/rules/state.py +1 -1
  92. telegrinder/bot/rules/text.py +4 -15
  93. telegrinder/bot/rules/update.py +3 -4
  94. telegrinder/bot/scenario/__init__.py +0 -0
  95. telegrinder/bot/scenario/abc.py +6 -5
  96. telegrinder/bot/scenario/checkbox.py +1 -1
  97. telegrinder/bot/scenario/choice.py +30 -39
  98. telegrinder/client/__init__.py +3 -5
  99. telegrinder/client/abc.py +11 -6
  100. telegrinder/client/aiohttp.py +141 -27
  101. telegrinder/client/form_data.py +1 -1
  102. telegrinder/model.py +61 -89
  103. telegrinder/modules.py +325 -102
  104. telegrinder/msgspec_utils/__init__.py +40 -0
  105. telegrinder/msgspec_utils/abc.py +18 -0
  106. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  107. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  108. telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
  109. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  110. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  111. telegrinder/msgspec_utils/decoder.py +389 -0
  112. telegrinder/msgspec_utils/encoder.py +206 -0
  113. telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
  114. telegrinder/msgspec_utils/tools.py +75 -0
  115. telegrinder/node/__init__.py +24 -7
  116. telegrinder/node/attachment.py +1 -0
  117. telegrinder/node/base.py +154 -72
  118. telegrinder/node/callback_query.py +5 -5
  119. telegrinder/node/collection.py +39 -0
  120. telegrinder/node/command.py +1 -2
  121. telegrinder/node/composer.py +121 -72
  122. telegrinder/node/container.py +11 -8
  123. telegrinder/node/context.py +48 -0
  124. telegrinder/node/either.py +27 -40
  125. telegrinder/node/error.py +41 -0
  126. telegrinder/node/event.py +37 -11
  127. telegrinder/node/exceptions.py +7 -0
  128. telegrinder/node/file.py +0 -0
  129. telegrinder/node/i18n.py +108 -0
  130. telegrinder/node/me.py +3 -2
  131. telegrinder/node/payload.py +1 -1
  132. telegrinder/node/polymorphic.py +63 -28
  133. telegrinder/node/reply_message.py +12 -0
  134. telegrinder/node/rule.py +6 -13
  135. telegrinder/node/scope.py +14 -5
  136. telegrinder/node/session.py +53 -0
  137. telegrinder/node/source.py +41 -9
  138. telegrinder/node/text.py +1 -2
  139. telegrinder/node/tools/__init__.py +0 -0
  140. telegrinder/node/tools/generator.py +3 -5
  141. telegrinder/node/utility.py +16 -0
  142. telegrinder/py.typed +0 -0
  143. telegrinder/rules.py +0 -0
  144. telegrinder/tools/__init__.py +48 -88
  145. telegrinder/tools/aio.py +103 -0
  146. telegrinder/tools/callback_data_serialization/__init__.py +5 -0
  147. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
  148. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
  149. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
  150. telegrinder/tools/final.py +21 -0
  151. telegrinder/tools/formatting/__init__.py +2 -18
  152. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  153. telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
  154. telegrinder/tools/formatting/deep_links/parsing.py +90 -0
  155. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  156. telegrinder/tools/formatting/html_formatter.py +18 -45
  157. telegrinder/tools/fullname.py +83 -0
  158. telegrinder/tools/global_context/__init__.py +4 -3
  159. telegrinder/tools/global_context/abc.py +17 -14
  160. telegrinder/tools/global_context/builtin_context.py +39 -0
  161. telegrinder/tools/global_context/global_context.py +138 -39
  162. telegrinder/tools/input_file_directory.py +0 -0
  163. telegrinder/tools/keyboard/__init__.py +39 -0
  164. telegrinder/tools/keyboard/abc.py +159 -0
  165. telegrinder/tools/keyboard/base.py +77 -0
  166. telegrinder/tools/keyboard/buttons/__init__.py +14 -0
  167. telegrinder/tools/keyboard/buttons/base.py +18 -0
  168. telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
  169. telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
  170. telegrinder/tools/keyboard/buttons/tools.py +18 -0
  171. telegrinder/tools/keyboard/data.py +20 -0
  172. telegrinder/tools/keyboard/keyboard.py +131 -0
  173. telegrinder/tools/keyboard/static_keyboard.py +83 -0
  174. telegrinder/tools/lifespan.py +87 -51
  175. telegrinder/tools/limited_dict.py +4 -1
  176. telegrinder/tools/loop_wrapper.py +332 -0
  177. telegrinder/tools/magic/__init__.py +32 -0
  178. telegrinder/tools/magic/annotations.py +165 -0
  179. telegrinder/tools/magic/dictionary.py +20 -0
  180. telegrinder/tools/magic/function.py +246 -0
  181. telegrinder/tools/magic/shortcut.py +111 -0
  182. telegrinder/tools/parse_mode.py +9 -3
  183. telegrinder/tools/singleton/__init__.py +4 -0
  184. telegrinder/tools/singleton/abc.py +14 -0
  185. telegrinder/tools/singleton/singleton.py +18 -0
  186. telegrinder/tools/state_storage/__init__.py +0 -0
  187. telegrinder/tools/state_storage/abc.py +6 -1
  188. telegrinder/tools/state_storage/memory.py +1 -1
  189. telegrinder/tools/strings.py +0 -0
  190. telegrinder/types/__init__.py +307 -268
  191. telegrinder/types/enums.py +68 -37
  192. telegrinder/types/input_file.py +3 -3
  193. telegrinder/types/methods.py +5699 -5055
  194. telegrinder/types/methods_utils.py +62 -0
  195. telegrinder/types/objects.py +1782 -994
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.1.dist-info/METADATA +162 -0
  198. telegrinder-0.5.1.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/licenses/LICENSE +2 -2
  200. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
  201. telegrinder/bot/rules/id.py +0 -24
  202. telegrinder/bot/rules/message.py +0 -15
  203. telegrinder/client/sonic.py +0 -212
  204. telegrinder/msgspec_utils.py +0 -478
  205. telegrinder/tools/adapter/__init__.py +0 -19
  206. telegrinder/tools/adapter/abc.py +0 -49
  207. telegrinder/tools/adapter/dataclass.py +0 -56
  208. telegrinder/tools/adapter/errors.py +0 -5
  209. telegrinder/tools/adapter/event.py +0 -61
  210. telegrinder/tools/adapter/node.py +0 -46
  211. telegrinder/tools/adapter/raw_event.py +0 -27
  212. telegrinder/tools/adapter/raw_update.py +0 -30
  213. telegrinder/tools/callback_data_serilization/__init__.py +0 -5
  214. telegrinder/tools/error_handler/__init__.py +0 -10
  215. telegrinder/tools/error_handler/abc.py +0 -30
  216. telegrinder/tools/error_handler/error.py +0 -9
  217. telegrinder/tools/error_handler/error_handler.py +0 -179
  218. telegrinder/tools/formatting/spec_html_formats.py +0 -75
  219. telegrinder/tools/functional.py +0 -8
  220. telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
  221. telegrinder/tools/i18n/__init__.py +0 -12
  222. telegrinder/tools/i18n/abc.py +0 -32
  223. telegrinder/tools/i18n/middleware/__init__.py +0 -3
  224. telegrinder/tools/i18n/middleware/abc.py +0 -22
  225. telegrinder/tools/i18n/simple.py +0 -43
  226. telegrinder/tools/keyboard.py +0 -132
  227. telegrinder/tools/loop_wrapper/__init__.py +0 -4
  228. telegrinder/tools/loop_wrapper/abc.py +0 -20
  229. telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
  230. telegrinder/tools/magic.py +0 -344
  231. telegrinder-0.4.2.dist-info/METADATA +0 -151
  232. telegrinder-0.4.2.dist-info/RECORD +0 -182
  233. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/WHEEL +0 -0
@@ -1,212 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import io
4
- import ssl
5
- import typing
6
-
7
- import certifi
8
-
9
- import telegrinder.msgspec_json as json
10
- from telegrinder.client.abc import ABCClient
11
-
12
- if typing.TYPE_CHECKING:
13
- from aiosonic import Connection, HTTPClient, HttpResponse, MultipartForm, Proxy, TCPConnector, Timeouts
14
-
15
- type Data = dict[str, typing.Any] | MultipartForm
16
- type Response = HttpResponse
17
-
18
- AIOSONIC_OBJECTS = (
19
- "Connection",
20
- "HTTPClient",
21
- "HttpResponse",
22
- "Proxy",
23
- "TCPConnector",
24
- "Timeouts",
25
- )
26
-
27
-
28
- def init_aiosonic_module(client_class: type[ABCClient[typing.Any]], /) -> None:
29
- try:
30
- import aiosonic
31
- import aiosonic.exceptions
32
- except ImportError:
33
- raise ImportError(
34
- "Module 'aiosonic' is not installed. You can install as follows: pip install aiosonic"
35
- ) from None
36
-
37
- globalns = globals()
38
- for name in AIOSONIC_OBJECTS:
39
- globalns.setdefault(name, getattr(aiosonic, name))
40
-
41
- if "MultipartForm" not in globalns:
42
- globalns["MultipartForm"] = type("MultiPartForm", (_MultipartForm, aiosonic.MultipartForm), {})
43
-
44
- if not client_class.CONNECTION_TIMEOUT_ERRORS:
45
- client_class.CONNECTION_TIMEOUT_ERRORS = (
46
- aiosonic.exceptions.ConnectTimeout,
47
- aiosonic.exceptions.RequestTimeout,
48
- TimeoutError,
49
- )
50
-
51
- if not client_class.CLIENT_CONNECTION_ERRORS:
52
- client_class.CLIENT_CONNECTION_ERRORS = (
53
- aiosonic.exceptions.ConnectionDisconnected,
54
- aiosonic.exceptions.ConnectionPoolAcquireTimeout,
55
- )
56
-
57
-
58
- class _MultipartForm(MultipartForm if typing.TYPE_CHECKING else object):
59
- async def _generate_chunks(self) -> typing.AsyncGenerator[bytes, None]:
60
- for field in self.fields:
61
- yield (f"--{self.boundary}\r\n").encode()
62
-
63
- if isinstance(field[1], io.IOBase):
64
- yield (
65
- "Content-Type: application/octet-stream\r\n"
66
- "Content-Disposition: form-data; " + f'name="{field[0]}"; filename="{field[2]}"\r\n\r\n'
67
- ).encode()
68
-
69
- async for data in self._read_file(field[1]):
70
- yield data + b"\r\n"
71
-
72
- field[1].close()
73
- else:
74
- yield (
75
- "Content-Type: text/plain; charset=utf-8\r\n"
76
- f'Content-Disposition: form-data; name="{field[0]}"\r\n\r\n'
77
- ).encode()
78
- yield field[1].encode() + b"\r\n"
79
-
80
- yield (f"--{self.boundary}--").encode()
81
-
82
- async def get_body_size(self) -> tuple[bytes, int]:
83
- if not self.fields:
84
- return b"", 0
85
- return await super().get_body_size()
86
-
87
- def get_headers(self, size: int | None = None) -> dict[str, str]:
88
- if not self.fields:
89
- return {"Content-Type": "application/x-www-form-urlencoded"}
90
- return super().get_headers(size)
91
-
92
-
93
- class AiosonicClient(ABCClient["MultipartForm"]):
94
- """HTTP client based on `aiosonic` module."""
95
-
96
- def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None:
97
- init_aiosonic_module(cls)
98
- return super().__init_subclass__(*args, **kwargs)
99
-
100
- def __init__(
101
- self,
102
- *,
103
- verify_ssl: bool = True,
104
- tpc_pool_size: int = 25,
105
- tpc_timeouts: Timeouts | None = None,
106
- proxy: Proxy | None = None,
107
- conn_max_requests: int = 100,
108
- use_dns_cache: bool = True,
109
- handle_cookies: bool = False,
110
- ttl_dns_cache: int = 10000,
111
- **kwargs: typing.Any,
112
- ) -> None:
113
- init_aiosonic_module(self.__class__)
114
- self.ssl = ssl.create_default_context(cafile=certifi.where())
115
- self.proxy = proxy
116
- self.verify_ssl = verify_ssl
117
- self.handle_cookies = handle_cookies
118
- self.tpc_timeouts = tpc_timeouts or Timeouts()
119
- self.tcp_connector = TCPConnector(
120
- pool_size=tpc_pool_size,
121
- timeouts=self.tpc_timeouts,
122
- connection_cls=Connection,
123
- conn_max_requests=conn_max_requests,
124
- use_dns_cache=use_dns_cache,
125
- ttl_dns_cache=ttl_dns_cache,
126
- **kwargs,
127
- )
128
- self.client: HTTPClient | None = None
129
-
130
- def __repr__(self) -> str:
131
- return "<{}: proxy={!r}, tpc_timeouts={!r}, tcp_connector={!r}, client={!r}>".format(
132
- self.__class__.__name__,
133
- self.proxy,
134
- self.tpc_timeouts,
135
- self.tcp_connector,
136
- self.client,
137
- )
138
-
139
- @classmethod
140
- def multipart_form_factory(cls) -> MultipartForm:
141
- return MultipartForm()
142
-
143
- async def request_raw(
144
- self,
145
- url: str,
146
- method: str = "GET",
147
- data: Data | None = None,
148
- **kwargs: typing.Any,
149
- ) -> Response:
150
- if self.client is None:
151
- self.client = HTTPClient(
152
- connector=self.tcp_connector,
153
- handle_cookies=self.handle_cookies,
154
- verify_ssl=self.verify_ssl,
155
- proxy=self.proxy,
156
- )
157
-
158
- return await self.client.request(
159
- url=url,
160
- method=method,
161
- data=data,
162
- json_serializer=json.dumps,
163
- ssl=self.ssl,
164
- **kwargs,
165
- )
166
-
167
- async def request_text(
168
- self,
169
- url: str,
170
- method: str = "GET",
171
- data: Data | None = None,
172
- **kwargs: typing.Any,
173
- ) -> str:
174
- response = await self.request_raw(url, method, data, **kwargs)
175
- return await response.text()
176
-
177
- async def request_json(
178
- self,
179
- url: str,
180
- method: str = "GET",
181
- data: Data | None = None,
182
- **kwargs: typing.Any,
183
- ) -> dict[str, typing.Any]:
184
- return json.loads(await self.request_content(url, method, data, **kwargs))
185
-
186
- async def request_bytes(
187
- self,
188
- url: str,
189
- method: str = "GET",
190
- data: Data | None = None,
191
- **kwargs: typing.Any,
192
- ) -> bytes:
193
- response = await self.request_raw(url, method, data, **kwargs)
194
- return response.body
195
-
196
- async def request_content(
197
- self,
198
- url: str,
199
- method: str = "GET",
200
- data: Data | None = None,
201
- **kwargs: typing.Any,
202
- ) -> bytes:
203
- response = await self.request_raw(url, method, data, **kwargs)
204
- return await response.content()
205
-
206
- async def close(self) -> None:
207
- if self.client is not None:
208
- await self.client.connector.cleanup()
209
- self.client = None
210
-
211
-
212
- __all__ = ("AiosonicClient",)
@@ -1,478 +0,0 @@
1
- import typing
2
- from contextlib import contextmanager
3
-
4
- import fntypes.option
5
- import fntypes.result
6
- import msgspec
7
- from fntypes.co import Error, Ok, Variative
8
-
9
- if typing.TYPE_CHECKING:
10
- from datetime import datetime
11
-
12
- from fntypes.option import Option
13
-
14
- def get_class_annotations(obj: typing.Any, /) -> dict[str, typing.Any]: ...
15
-
16
- def get_type_hints(obj: typing.Any, /) -> dict[str, typing.Any]: ...
17
-
18
- else:
19
- from datetime import datetime as dt
20
-
21
- from msgspec._utils import get_class_annotations, get_type_hints
22
-
23
- datetime = type("datetime", (dt,), {})
24
-
25
- class OptionMeta(type):
26
- def __instancecheck__(cls, __instance: typing.Any) -> bool:
27
- return isinstance(__instance, (fntypes.option.Some | fntypes.option.Nothing, msgspec.UnsetType))
28
-
29
- class Option[Value](metaclass=OptionMeta):
30
- pass
31
-
32
-
33
- type DecHook[T] = typing.Callable[typing.Concatenate[type[T], typing.Any, ...], typing.Any]
34
- type EncHook[T] = typing.Callable[typing.Concatenate[T, ...], typing.Any]
35
-
36
-
37
- def get_origin[T](t: type[T]) -> type[T]:
38
- return typing.cast(T, typing.get_origin(t)) or t
39
-
40
-
41
- def repr_type(t: typing.Any) -> str:
42
- return getattr(t, "__name__", repr(get_origin(t)))
43
-
44
-
45
- def is_common_type(type_: typing.Any) -> typing.TypeGuard[type[typing.Any]]:
46
- if not isinstance(type_, type):
47
- return False
48
- return (
49
- type_ in (str, int, float, bool, None, Variative)
50
- or issubclass(type_, msgspec.Struct)
51
- or hasattr(type_, "__dataclass_fields__")
52
- )
53
-
54
-
55
- def struct_as_dict(struct: msgspec.Struct, /) -> dict[str, typing.Any]:
56
- return {
57
- k: v
58
- for k, v in msgspec.structs.asdict(struct).items()
59
- if not isinstance(v, msgspec.UnsetType | type(None) | fntypes.option.Nothing)
60
- }
61
-
62
-
63
- def type_check(obj: typing.Any, t: typing.Any) -> bool:
64
- return (
65
- isinstance(obj, t)
66
- if isinstance(t, type) and issubclass(t, msgspec.Struct)
67
- else type(obj) in t
68
- if isinstance(t, tuple)
69
- else type(obj) is t
70
- )
71
-
72
-
73
- def msgspec_convert[T](obj: typing.Any, t: type[T]) -> fntypes.result.Result[T, str]:
74
- try:
75
- return Ok(decoder.convert(obj, type=t, strict=True))
76
- except msgspec.ValidationError:
77
- return Error(
78
- "Expected object of type `{}`, got `{}`.".format(
79
- repr_type(t),
80
- repr_type(type(obj)),
81
- )
82
- )
83
-
84
-
85
- def msgspec_to_builtins(
86
- obj: typing.Any,
87
- *,
88
- str_keys: bool = False,
89
- builtin_types: typing.Iterable[type[typing.Any]] | None = None,
90
- order: typing.Literal["deterministic", "sorted"] | None = None,
91
- ) -> fntypes.result.Result[typing.Any, msgspec.ValidationError]:
92
- try:
93
- return Ok(encoder.to_builtins(**locals()))
94
- except msgspec.ValidationError as exc:
95
- return Error(exc)
96
-
97
-
98
- def option_dec_hook(
99
- tp: type[Option[typing.Any]],
100
- obj: typing.Any,
101
- ) -> fntypes.option.Option[typing.Any] | msgspec.UnsetType:
102
- if obj is msgspec.UNSET:
103
- return obj
104
-
105
- if obj is None or isinstance(obj, fntypes.option.Nothing):
106
- return fntypes.option.Nothing()
107
-
108
- (value_type,) = typing.get_args(tp) or (typing.Any,)
109
- orig_value_type = typing.get_origin(value_type) or value_type
110
- orig_obj = obj
111
-
112
- if not isinstance(orig_obj, dict | list) and is_common_type(orig_value_type):
113
- if orig_value_type is Variative:
114
- obj = value_type(orig_obj) # type: ignore
115
- orig_value_type = typing.get_args(value_type)
116
-
117
- if not type_check(orig_obj, orig_value_type):
118
- raise TypeError(f"Expected `{repr_type(orig_value_type)}`, got `{repr_type(type(orig_obj))}`.")
119
-
120
- return fntypes.option.Some(obj)
121
-
122
- return fntypes.option.Some(decoder.convert(orig_obj, type=value_type))
123
-
124
-
125
- def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
126
- union_types = typing.get_args(tp)
127
-
128
- if isinstance(obj, dict):
129
- models_struct_fields: dict[type[msgspec.Struct], int] = {
130
- m: sum(1 for k in obj if k in m.__struct_fields__)
131
- for m in union_types
132
- if issubclass(get_origin(m), msgspec.Struct)
133
- }
134
- union_types = tuple(t for t in union_types if t not in models_struct_fields)
135
- reverse = False
136
-
137
- if len(set(models_struct_fields.values())) != len(models_struct_fields.values()):
138
- models_struct_fields = {m: len(m.__struct_fields__) for m in models_struct_fields}
139
- reverse = True
140
-
141
- union_types = (
142
- *sorted(
143
- models_struct_fields,
144
- key=lambda k: models_struct_fields[k],
145
- reverse=reverse,
146
- ),
147
- *union_types,
148
- )
149
-
150
- for t in union_types:
151
- if not isinstance(obj, dict | list) and is_common_type(t) and type_check(obj, t):
152
- return tp(obj)
153
- match msgspec_convert(obj, t):
154
- case Ok(value):
155
- return tp(value)
156
-
157
- raise TypeError(
158
- "Object of type `{}` does not belong to types `{}`".format(
159
- repr_type(obj.__class__),
160
- " | ".join(map(repr_type, union_types)),
161
- )
162
- )
163
-
164
-
165
- class Decoder:
166
- """Class `Decoder` for `msgspec` module with decode hook
167
- for objects with the specified type.
168
-
169
- ```
170
- import enum
171
-
172
- from datetime import datetime as dt
173
-
174
- class Digit(enum.IntEnum):
175
- ONE = 1
176
- TWO = 2
177
- THREE = 3
178
-
179
- decoder = Decoder()
180
- decoder.dec_hooks[dt] = lambda t, timestamp: t.fromtimestamp(timestamp)
181
-
182
- decoder.dec_hook(dt, 1713354732) #> datetime.datetime(2024, 4, 17, 14, 52, 12)
183
-
184
- decoder.convert("123", type=int, strict=False) #> 123
185
- decoder.convert(1, type=Digit) #> <Digit.ONE: 1>
186
-
187
- decoder.decode(b'{"digit":3}', type=dict[str, Digit]) #> {'digit': <Digit.THREE: 3>}
188
- ```
189
- """
190
-
191
- def __init__(self) -> None:
192
- self.dec_hooks: dict[typing.Any, DecHook[typing.Any]] = {
193
- Option: option_dec_hook,
194
- Variative: variative_dec_hook,
195
- datetime: lambda t, obj: t.fromtimestamp(obj),
196
- fntypes.option.Some: option_dec_hook,
197
- fntypes.option.Nothing: option_dec_hook,
198
- }
199
-
200
- def __repr__(self) -> str:
201
- return "<{}: dec_hooks={!r}>".format(
202
- self.__class__.__name__,
203
- self.dec_hooks,
204
- )
205
-
206
- @typing.overload
207
- def __call__[T](
208
- self,
209
- type: type[T],
210
- context: dict[str, typing.Any] | None = None,
211
- ) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
212
-
213
- @typing.overload
214
- def __call__(
215
- self,
216
- type: typing.Any,
217
- context: dict[str, typing.Any] | None = None,
218
- ) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
219
-
220
- @typing.overload
221
- def __call__[T](
222
- self,
223
- type: type[T],
224
- *,
225
- strict: bool = True,
226
- context: dict[str, typing.Any] | None = None,
227
- ) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
228
-
229
- @typing.overload
230
- def __call__(
231
- self,
232
- type: typing.Any,
233
- *,
234
- strict: bool = True,
235
- context: dict[str, typing.Any] | None = None,
236
- ) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
237
-
238
- @contextmanager
239
- def __call__(self, type=object, *, strict=True, context=None):
240
- """Context manager returns an `msgspec.json.Decoder` object with the `dec_hook`."""
241
- dec_obj = msgspec.json.Decoder(
242
- type=typing.Any if type is object else type,
243
- strict=strict,
244
- dec_hook=self.dec_hook(context),
245
- )
246
- yield dec_obj
247
-
248
- def add_dec_hook[T](self, t: type[T], /):
249
- def decorator(func: DecHook[T]) -> DecHook[T]:
250
- return self.dec_hooks.setdefault(get_origin(t), func)
251
-
252
- return decorator
253
-
254
- def dec_hook(self, context: dict[str, typing.Any] | None = None):
255
- from telegrinder.tools.magic import magic_bundle
256
-
257
- def inner(tp: type[typing.Any], obj: object) -> typing.Any:
258
- origin_type = t if isinstance((t := get_origin(tp)), type) else type(t)
259
- if origin_type not in self.dec_hooks:
260
- raise TypeError(
261
- f"Unknown type `{repr_type(origin_type)}`. You can implement decode hook for this type."
262
- )
263
- dec_hook_func = self.dec_hooks[origin_type]
264
- kwargs = magic_bundle(dec_hook_func, context or {}, start_idx=2)
265
- return dec_hook_func(tp, obj, **kwargs)
266
-
267
- return inner
268
-
269
- def convert[T](
270
- self,
271
- obj: object,
272
- *,
273
- type: type[T] = dict,
274
- strict: bool = True,
275
- from_attributes: bool = False,
276
- builtin_types: typing.Iterable[type[typing.Any]] | None = None,
277
- str_keys: bool = False,
278
- context: dict[str, typing.Any] | None = None,
279
- ) -> T:
280
- return msgspec.convert(
281
- obj,
282
- type,
283
- strict=strict,
284
- from_attributes=from_attributes,
285
- dec_hook=self.dec_hook(context),
286
- builtin_types=builtin_types,
287
- str_keys=str_keys,
288
- )
289
-
290
- @typing.overload
291
- def decode(
292
- self,
293
- buf: str | bytes,
294
- *,
295
- context: dict[str, typing.Any] | None = None,
296
- ) -> typing.Any: ...
297
-
298
- @typing.overload
299
- def decode[T](
300
- self,
301
- buf: str | bytes,
302
- *,
303
- type: type[T],
304
- context: dict[str, typing.Any] | None = None,
305
- ) -> T: ...
306
-
307
- @typing.overload
308
- def decode(
309
- self,
310
- buf: str | bytes,
311
- *,
312
- type: typing.Any,
313
- context: dict[str, typing.Any] | None = None,
314
- ) -> typing.Any: ...
315
-
316
- @typing.overload
317
- def decode[T](
318
- self,
319
- buf: str | bytes,
320
- *,
321
- type: type[T],
322
- strict: bool = True,
323
- context: dict[str, typing.Any] | None = None,
324
- ) -> T: ...
325
-
326
- @typing.overload
327
- def decode(
328
- self,
329
- buf: str | bytes,
330
- *,
331
- type: typing.Any,
332
- strict: bool = True,
333
- context: dict[str, typing.Any] | None = None,
334
- ) -> typing.Any: ...
335
-
336
- def decode(self, buf, *, type=object, strict=True, context=None):
337
- return msgspec.json.decode(
338
- buf,
339
- type=typing.Any if type is object else type,
340
- strict=strict,
341
- dec_hook=self.dec_hook(context),
342
- )
343
-
344
-
345
- class Encoder:
346
- """Class `Encoder` for `msgspec` module with encode hooks for objects.
347
-
348
- ```
349
- from datetime import datetime as dt
350
-
351
- encoder = Encoder()
352
- encoder.enc_hooks[dt] = lambda d: int(d.timestamp())
353
-
354
- encoder.enc_hook(dt.now()) #> 1713354732
355
- encoder.encode({'digit': Digit.ONE}) #> '{"digit":1}'
356
- ```
357
- """
358
-
359
- def __init__(self) -> None:
360
- self.enc_hooks: dict[typing.Any, EncHook[typing.Any]] = {
361
- fntypes.option.Some: lambda opt: opt.value,
362
- fntypes.option.Nothing: lambda _: None,
363
- Variative: lambda variative: variative.v,
364
- datetime: lambda date: int(date.timestamp()),
365
- }
366
-
367
- def __repr__(self) -> str:
368
- return "<{}: enc_hooks={!r}>".format(
369
- self.__class__.__name__,
370
- self.enc_hooks,
371
- )
372
-
373
- @contextmanager
374
- def __call__(
375
- self,
376
- *,
377
- decimal_format: typing.Literal["string", "number"] = "string",
378
- uuid_format: typing.Literal["canonical", "hex"] = "canonical",
379
- order: typing.Literal[None, "deterministic", "sorted"] = None,
380
- context: dict[str, typing.Any] | None = None,
381
- ) -> typing.Generator[msgspec.json.Encoder, typing.Any, None]:
382
- """Context manager returns an `msgspec.json.Encoder` object with the `enc_hook`."""
383
- enc_obj = msgspec.json.Encoder(enc_hook=self.enc_hook(context))
384
- yield enc_obj
385
-
386
- def add_enc_hook[T](self, t: type[T], /):
387
- def decorator(func: EncHook[T]) -> EncHook[T]:
388
- encode_hook = self.enc_hooks.setdefault(get_origin(t), func)
389
- return func if encode_hook is not func else encode_hook
390
-
391
- return decorator
392
-
393
- def enc_hook(self, context: dict[str, typing.Any] | None = None):
394
- from telegrinder.tools.magic import magic_bundle
395
-
396
- def inner(obj: typing.Any) -> typing.Any:
397
- origin_type = get_origin(obj.__class__)
398
- if origin_type not in self.enc_hooks:
399
- raise NotImplementedError(
400
- f"Not implemented encode hook for object of type `{repr_type(origin_type)}`.",
401
- )
402
- enc_hook_func = self.enc_hooks[origin_type]
403
- kwargs = magic_bundle(enc_hook_func, context or {}, start_idx=1)
404
- return enc_hook_func(obj, **kwargs)
405
-
406
- return inner
407
-
408
- @typing.overload
409
- def encode(
410
- self,
411
- obj: typing.Any,
412
- *,
413
- context: dict[str, typing.Any] | None = None,
414
- ) -> str: ...
415
-
416
- @typing.overload
417
- def encode(
418
- self,
419
- obj: typing.Any,
420
- *,
421
- as_str: typing.Literal[True],
422
- context: dict[str, typing.Any] | None = None,
423
- ) -> str: ...
424
-
425
- @typing.overload
426
- def encode(
427
- self,
428
- obj: typing.Any,
429
- *,
430
- as_str: typing.Literal[False],
431
- context: dict[str, typing.Any] | None = None,
432
- ) -> bytes: ...
433
-
434
- def encode(
435
- self,
436
- obj: typing.Any,
437
- *,
438
- as_str: bool = True,
439
- context: dict[str, typing.Any] | None = None,
440
- ) -> str | bytes:
441
- buf = msgspec.json.encode(obj, enc_hook=self.enc_hook(context))
442
- return buf.decode() if as_str else buf
443
-
444
- def to_builtins(
445
- self,
446
- obj: typing.Any,
447
- *,
448
- str_keys: bool = False,
449
- builtin_types: typing.Iterable[type[typing.Any]] | None = None,
450
- order: typing.Literal["deterministic", "sorted"] | None = None,
451
- context: dict[str, typing.Any] | None = None,
452
- ) -> typing.Any:
453
- return msgspec.to_builtins(
454
- obj,
455
- str_keys=str_keys,
456
- builtin_types=builtin_types,
457
- enc_hook=self.enc_hook(context),
458
- order=order,
459
- )
460
-
461
-
462
- decoder: typing.Final[Decoder] = Decoder()
463
- encoder: typing.Final[Encoder] = Encoder()
464
-
465
-
466
- __all__ = (
467
- "Decoder",
468
- "Encoder",
469
- "Option",
470
- "datetime",
471
- "decoder",
472
- "encoder",
473
- "get_class_annotations",
474
- "get_type_hints",
475
- "msgspec_convert",
476
- "msgspec_to_builtins",
477
- "struct_as_dict",
478
- )
@@ -1,19 +0,0 @@
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
- )