telegrinder 0.3.3.post1__py3-none-any.whl → 0.3.4.post1__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.
- telegrinder/__init__.py +144 -144
- telegrinder/api/__init__.py +8 -8
- telegrinder/api/api.py +93 -93
- telegrinder/api/error.py +16 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +66 -66
- telegrinder/bot/bot.py +76 -76
- telegrinder/bot/cute_types/__init__.py +17 -17
- telegrinder/bot/cute_types/base.py +258 -258
- telegrinder/bot/cute_types/callback_query.py +385 -385
- telegrinder/bot/cute_types/chat_join_request.py +61 -61
- telegrinder/bot/cute_types/chat_member_updated.py +160 -160
- telegrinder/bot/cute_types/inline_query.py +43 -43
- telegrinder/bot/cute_types/message.py +2637 -2637
- telegrinder/bot/cute_types/update.py +104 -109
- telegrinder/bot/cute_types/utils.py +95 -95
- telegrinder/bot/dispatch/__init__.py +55 -55
- telegrinder/bot/dispatch/abc.py +77 -77
- telegrinder/bot/dispatch/context.py +98 -98
- telegrinder/bot/dispatch/dispatch.py +202 -202
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +24 -24
- telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
- telegrinder/bot/dispatch/handler/base.py +57 -57
- telegrinder/bot/dispatch/handler/document_reply.py +44 -44
- telegrinder/bot/dispatch/handler/func.py +135 -135
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -43
- telegrinder/bot/dispatch/handler/message_reply.py +36 -36
- telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
- telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
- telegrinder/bot/dispatch/handler/video_reply.py +44 -44
- telegrinder/bot/dispatch/middleware/__init__.py +3 -3
- telegrinder/bot/dispatch/middleware/abc.py +22 -16
- telegrinder/bot/dispatch/process.py +157 -132
- telegrinder/bot/dispatch/return_manager/__init__.py +13 -13
- telegrinder/bot/dispatch/return_manager/abc.py +108 -108
- telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
- telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
- telegrinder/bot/dispatch/return_manager/message.py +36 -36
- telegrinder/bot/dispatch/view/__init__.py +13 -13
- telegrinder/bot/dispatch/view/abc.py +41 -41
- telegrinder/bot/dispatch/view/base.py +200 -200
- telegrinder/bot/dispatch/view/box.py +129 -129
- telegrinder/bot/dispatch/view/callback_query.py +17 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +16 -16
- telegrinder/bot/dispatch/view/chat_member.py +39 -39
- telegrinder/bot/dispatch/view/inline_query.py +17 -17
- telegrinder/bot/dispatch/view/message.py +44 -44
- telegrinder/bot/dispatch/view/raw.py +114 -114
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +13 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +57 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +172 -167
- telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +68 -68
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +131 -131
- telegrinder/bot/rules/__init__.py +62 -62
- telegrinder/bot/rules/abc.py +213 -213
- telegrinder/bot/rules/adapter/__init__.py +12 -9
- telegrinder/bot/rules/adapter/abc.py +31 -29
- telegrinder/bot/rules/adapter/errors.py +5 -5
- telegrinder/bot/rules/adapter/event.py +65 -67
- telegrinder/bot/rules/adapter/node.py +48 -48
- telegrinder/bot/rules/adapter/raw_event.py +27 -0
- telegrinder/bot/rules/adapter/raw_update.py +30 -30
- telegrinder/bot/rules/callback_data.py +170 -170
- telegrinder/bot/rules/chat_join.py +46 -46
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +36 -36
- telegrinder/bot/rules/func.py +26 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/inline.py +60 -60
- telegrinder/bot/rules/integer.py +20 -20
- telegrinder/bot/rules/is_from.py +127 -127
- telegrinder/bot/rules/markup.py +43 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +17 -17
- telegrinder/bot/rules/message_entities.py +35 -35
- telegrinder/bot/rules/node.py +27 -27
- telegrinder/bot/rules/regex.py +37 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +37 -37
- telegrinder/bot/rules/text.py +33 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +19 -19
- telegrinder/bot/scenario/checkbox.py +176 -167
- telegrinder/bot/scenario/choice.py +51 -46
- telegrinder/client/__init__.py +4 -4
- telegrinder/client/abc.py +75 -75
- telegrinder/client/aiohttp.py +130 -130
- telegrinder/model.py +320 -295
- telegrinder/modules.py +237 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +410 -410
- telegrinder/node/__init__.py +0 -0
- telegrinder/node/attachment.py +87 -87
- telegrinder/node/base.py +166 -166
- telegrinder/node/callback_query.py +53 -53
- telegrinder/node/command.py +33 -33
- telegrinder/node/composer.py +198 -198
- telegrinder/node/container.py +27 -27
- telegrinder/node/event.py +65 -65
- telegrinder/node/me.py +16 -16
- telegrinder/node/message.py +14 -14
- telegrinder/node/polymorphic.py +48 -48
- telegrinder/node/rule.py +76 -76
- telegrinder/node/scope.py +38 -38
- telegrinder/node/source.py +71 -71
- telegrinder/node/text.py +41 -41
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +40 -40
- telegrinder/node/update.py +15 -15
- telegrinder/rules.py +0 -0
- telegrinder/tools/__init__.py +74 -74
- telegrinder/tools/buttons.py +79 -79
- telegrinder/tools/error_handler/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +33 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +193 -193
- telegrinder/tools/formatting/__init__.py +46 -46
- telegrinder/tools/formatting/html.py +283 -283
- telegrinder/tools/formatting/links.py +33 -33
- telegrinder/tools/formatting/spec_html_formats.py +111 -111
- telegrinder/tools/functional.py +12 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +412 -412
- telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
- telegrinder/tools/i18n/__init__.py +7 -7
- telegrinder/tools/i18n/abc.py +30 -30
- telegrinder/tools/i18n/middleware/__init__.py +3 -3
- telegrinder/tools/i18n/middleware/abc.py +25 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/kb_set/__init__.py +4 -4
- telegrinder/tools/kb_set/base.py +15 -15
- telegrinder/tools/kb_set/yaml.py +63 -63
- telegrinder/tools/keyboard.py +132 -132
- telegrinder/tools/limited_dict.py +37 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +15 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +224 -224
- telegrinder/tools/magic.py +157 -157
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +35 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/types/__init__.py +260 -260
- telegrinder/types/enums.py +701 -701
- telegrinder/types/methods.py +4633 -4633
- telegrinder/types/objects.py +6950 -8561
- telegrinder/verification_utils.py +32 -32
- {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/LICENSE +22 -22
- {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/METADATA +1 -1
- telegrinder-0.3.4.post1.dist-info/RECORD +165 -0
- telegrinder-0.3.3.post1.dist-info/RECORD +0 -164
- {telegrinder-0.3.3.post1.dist-info → telegrinder-0.3.4.post1.dist-info}/WHEEL +0 -0
telegrinder/model.py
CHANGED
|
@@ -1,295 +1,320 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
name: str | None =
|
|
88
|
-
) -> typing.Any: ...
|
|
89
|
-
|
|
90
|
-
@typing.overload
|
|
91
|
-
def field(
|
|
92
|
-
*,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def field(
|
|
99
|
-
*,
|
|
100
|
-
default
|
|
101
|
-
|
|
102
|
-
name
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return "
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def
|
|
199
|
-
return
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
self
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
)
|
|
1
|
+
import base64
|
|
2
|
+
import dataclasses
|
|
3
|
+
import enum
|
|
4
|
+
import keyword
|
|
5
|
+
import os
|
|
6
|
+
import secrets
|
|
7
|
+
import typing
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from types import NoneType
|
|
10
|
+
|
|
11
|
+
import msgspec
|
|
12
|
+
from fntypes.co import Nothing, Result, Some
|
|
13
|
+
|
|
14
|
+
from telegrinder.msgspec_utils import decoder, encoder, get_origin
|
|
15
|
+
|
|
16
|
+
if typing.TYPE_CHECKING:
|
|
17
|
+
from telegrinder.api.error import APIError
|
|
18
|
+
|
|
19
|
+
T = typing.TypeVar("T")
|
|
20
|
+
P = typing.ParamSpec("P")
|
|
21
|
+
|
|
22
|
+
UnionType: typing.TypeAlias = typing.Annotated[tuple[T, ...], ...]
|
|
23
|
+
|
|
24
|
+
MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
|
|
25
|
+
"omit_defaults": True,
|
|
26
|
+
"dict": True,
|
|
27
|
+
"rename": {kw + "_": kw for kw in keyword.kwlist},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@typing.overload
|
|
32
|
+
def full_result(
|
|
33
|
+
result: Result[msgspec.Raw, "APIError"],
|
|
34
|
+
full_t: type[T],
|
|
35
|
+
) -> Result[T, "APIError"]: ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@typing.overload
|
|
39
|
+
def full_result(
|
|
40
|
+
result: Result[msgspec.Raw, "APIError"],
|
|
41
|
+
full_t: UnionType[T],
|
|
42
|
+
) -> Result[T, "APIError"]: ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def full_result(
|
|
46
|
+
result: Result[msgspec.Raw, "APIError"],
|
|
47
|
+
full_t: typing.Any,
|
|
48
|
+
) -> Result[typing.Any, "APIError"]:
|
|
49
|
+
return result.map(lambda v: decoder.decode(v, type=full_t))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def generate_random_id(length_bytes: int) -> str:
|
|
53
|
+
if length_bytes < 1 or length_bytes > 64:
|
|
54
|
+
raise ValueError("Length of bytes must be between 1 and 64.")
|
|
55
|
+
|
|
56
|
+
random_bytes = os.urandom(length_bytes)
|
|
57
|
+
random_id = base64.urlsafe_b64encode(random_bytes).rstrip(b"=").decode("utf-8")
|
|
58
|
+
return random_id
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
|
|
62
|
+
validated_params = {}
|
|
63
|
+
for k, v in (
|
|
64
|
+
*params.pop("other", {}).items(),
|
|
65
|
+
*params.items(),
|
|
66
|
+
):
|
|
67
|
+
if isinstance(v, Proxy):
|
|
68
|
+
v = v.get()
|
|
69
|
+
if k == "self" or type(v) in (NoneType, Nothing):
|
|
70
|
+
continue
|
|
71
|
+
validated_params[k] = v.unwrap() if isinstance(v, Some) else v
|
|
72
|
+
return validated_params
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if typing.TYPE_CHECKING:
|
|
76
|
+
|
|
77
|
+
@typing.overload
|
|
78
|
+
def field(*, name: str | None = ...) -> typing.Any: ...
|
|
79
|
+
|
|
80
|
+
@typing.overload
|
|
81
|
+
def field(*, default: typing.Any, name: str | None = ...) -> typing.Any: ...
|
|
82
|
+
|
|
83
|
+
@typing.overload
|
|
84
|
+
def field(
|
|
85
|
+
*,
|
|
86
|
+
default_factory: typing.Callable[[], typing.Any],
|
|
87
|
+
name: str | None = None,
|
|
88
|
+
) -> typing.Any: ...
|
|
89
|
+
|
|
90
|
+
@typing.overload
|
|
91
|
+
def field(
|
|
92
|
+
*,
|
|
93
|
+
converter: typing.Callable[[typing.Any], typing.Any],
|
|
94
|
+
name: str | None = ...,
|
|
95
|
+
) -> typing.Any: ...
|
|
96
|
+
|
|
97
|
+
@typing.overload
|
|
98
|
+
def field(
|
|
99
|
+
*,
|
|
100
|
+
default: typing.Any,
|
|
101
|
+
converter: typing.Callable[[typing.Any], typing.Any],
|
|
102
|
+
name: str | None = ...,
|
|
103
|
+
) -> typing.Any: ...
|
|
104
|
+
|
|
105
|
+
@typing.overload
|
|
106
|
+
def field(
|
|
107
|
+
*,
|
|
108
|
+
default_factory: typing.Callable[[], typing.Any],
|
|
109
|
+
converter: typing.Callable[[typing.Any], typing.Any],
|
|
110
|
+
name: str | None = None,
|
|
111
|
+
) -> typing.Any: ...
|
|
112
|
+
|
|
113
|
+
def field(
|
|
114
|
+
*,
|
|
115
|
+
default=...,
|
|
116
|
+
default_factory=...,
|
|
117
|
+
name=...,
|
|
118
|
+
converter=...,
|
|
119
|
+
) -> typing.Any: ...
|
|
120
|
+
|
|
121
|
+
class From(typing.Generic[T]):
|
|
122
|
+
def __new__(cls, _: T, /) -> typing.Any: ...
|
|
123
|
+
else:
|
|
124
|
+
from msgspec import field as _field
|
|
125
|
+
|
|
126
|
+
From = typing.Annotated[T, ...]
|
|
127
|
+
|
|
128
|
+
def field(**kwargs):
|
|
129
|
+
kwargs.pop("converter", None)
|
|
130
|
+
return _field(**kwargs)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@typing.dataclass_transform(field_specifiers=(field,))
|
|
134
|
+
class Model(msgspec.Struct, **MODEL_CONFIG):
|
|
135
|
+
@classmethod
|
|
136
|
+
def from_data(cls: typing.Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
|
|
137
|
+
return decoder.convert(msgspec.structs.asdict(cls(*args, **kwargs)), type=cls) # type: ignore
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def from_dict(cls, obj: dict[str, typing.Any], /) -> typing.Self:
|
|
141
|
+
return decoder.convert(obj, type=cls)
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def from_bytes(cls, obj: bytes, /) -> typing.Self:
|
|
145
|
+
return decoder.decode(obj, type=cls)
|
|
146
|
+
|
|
147
|
+
def _to_dict(
|
|
148
|
+
self,
|
|
149
|
+
dct_name: str,
|
|
150
|
+
exclude_fields: set[str],
|
|
151
|
+
full: bool,
|
|
152
|
+
) -> dict[str, typing.Any]:
|
|
153
|
+
if dct_name not in self.__dict__:
|
|
154
|
+
self.__dict__[dct_name] = (
|
|
155
|
+
msgspec.structs.asdict(self)
|
|
156
|
+
if not full
|
|
157
|
+
else encoder.to_builtins(self.to_dict(exclude_fields=exclude_fields), order="deterministic")
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if not exclude_fields:
|
|
161
|
+
return self.__dict__[dct_name]
|
|
162
|
+
|
|
163
|
+
return {key: value for key, value in self.__dict__[dct_name].items() if key not in exclude_fields}
|
|
164
|
+
|
|
165
|
+
def to_dict(
|
|
166
|
+
self,
|
|
167
|
+
*,
|
|
168
|
+
exclude_fields: set[str] | None = None,
|
|
169
|
+
) -> dict[str, typing.Any]:
|
|
170
|
+
"""
|
|
171
|
+
:param exclude_fields: Model field names to exclude from the dictionary representation of this model.
|
|
172
|
+
:return: A dictionary representation of this model.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
return self._to_dict("model_as_dict", exclude_fields or set(), full=False)
|
|
176
|
+
|
|
177
|
+
def to_full_dict(
|
|
178
|
+
self,
|
|
179
|
+
*,
|
|
180
|
+
exclude_fields: set[str] | None = None,
|
|
181
|
+
) -> dict[str, typing.Any]:
|
|
182
|
+
"""
|
|
183
|
+
:param exclude_fields: Model field names to exclude from the dictionary representation of this model.
|
|
184
|
+
:return: A dictionary representation of this model including all models, structs, custom types.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
return self._to_dict("model_as_full_dict", exclude_fields or set(), full=True)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclasses.dataclass(kw_only=True, frozen=True, slots=True, repr=False)
|
|
191
|
+
class DataConverter:
|
|
192
|
+
_converters: dict[type[typing.Any], typing.Callable[..., typing.Any]] = dataclasses.field(
|
|
193
|
+
init=False,
|
|
194
|
+
default_factory=lambda: {},
|
|
195
|
+
)
|
|
196
|
+
_files: dict[str, tuple[str, bytes]] = dataclasses.field(default_factory=lambda: {})
|
|
197
|
+
|
|
198
|
+
def __repr__(self) -> str:
|
|
199
|
+
return "<{}: {}>".format(
|
|
200
|
+
self.__class__.__name__,
|
|
201
|
+
", ".join(f"{k}={v.__name__!r}" for k, v in self._converters.items()),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def __post_init__(self) -> None:
|
|
205
|
+
self._converters.update(
|
|
206
|
+
{
|
|
207
|
+
get_origin(value.__annotations__["data"]): value
|
|
208
|
+
for key, value in vars(self.__class__).items()
|
|
209
|
+
if key.startswith("convert_") and callable(value)
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def __call__(self, data: typing.Any, *, serialize: bool = True) -> typing.Any:
|
|
214
|
+
converter = self.get_converter(get_origin(type(data)))
|
|
215
|
+
if converter is not None:
|
|
216
|
+
if isinstance(converter, staticmethod):
|
|
217
|
+
return converter(data, serialize)
|
|
218
|
+
return converter(self, data, serialize)
|
|
219
|
+
return data
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def converters(self) -> dict[type[typing.Any], typing.Callable[..., typing.Any]]:
|
|
223
|
+
return self._converters.copy()
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def files(self) -> dict[str, tuple[str, bytes]]:
|
|
227
|
+
return self._files.copy()
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def convert_enum(data: enum.Enum, _: bool = False) -> typing.Any:
|
|
231
|
+
return data.value
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def convert_datetime(data: datetime, _: bool = False) -> int:
|
|
235
|
+
return int(data.timestamp())
|
|
236
|
+
|
|
237
|
+
def get_converter(self, t: type[typing.Any]):
|
|
238
|
+
for type_, converter in self._converters.items():
|
|
239
|
+
if issubclass(t, type_):
|
|
240
|
+
return converter
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
def convert_model(
|
|
244
|
+
self,
|
|
245
|
+
data: Model,
|
|
246
|
+
serialize: bool = True,
|
|
247
|
+
) -> str | dict[str, typing.Any]:
|
|
248
|
+
converted_dct = self(data.to_dict(), serialize=False)
|
|
249
|
+
return encoder.encode(converted_dct) if serialize is True else converted_dct
|
|
250
|
+
|
|
251
|
+
def convert_dct(
|
|
252
|
+
self,
|
|
253
|
+
data: dict[str, typing.Any],
|
|
254
|
+
serialize: bool = True,
|
|
255
|
+
) -> dict[str, typing.Any]:
|
|
256
|
+
return {
|
|
257
|
+
k: self(v, serialize=serialize) for k, v in data.items() if type(v) not in (NoneType, Nothing)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
def convert_lst(
|
|
261
|
+
self,
|
|
262
|
+
data: list[typing.Any],
|
|
263
|
+
serialize: bool = True,
|
|
264
|
+
) -> str | list[typing.Any]:
|
|
265
|
+
converted_lst = [self(x, serialize=False) for x in data]
|
|
266
|
+
return encoder.encode(converted_lst) if serialize is True else converted_lst
|
|
267
|
+
|
|
268
|
+
def convert_tpl(self, data: tuple[typing.Any, ...], _: bool = False) -> str | tuple[typing.Any, ...]:
|
|
269
|
+
match data:
|
|
270
|
+
case (str(filename), bytes(content)):
|
|
271
|
+
attach_name = secrets.token_urlsafe(16)
|
|
272
|
+
self._files[attach_name] = (filename, content)
|
|
273
|
+
return "attach://{}".format(attach_name)
|
|
274
|
+
|
|
275
|
+
return data
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class Proxy:
|
|
279
|
+
def __init__(self, cfg: "_ProxiedDict", key: str) -> None:
|
|
280
|
+
self.key = key
|
|
281
|
+
self.cfg = cfg
|
|
282
|
+
|
|
283
|
+
def get(self) -> typing.Any | None:
|
|
284
|
+
return self.cfg._defaults.get(self.key)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class _ProxiedDict(typing.Generic[T]):
|
|
288
|
+
def __init__(self, tp: type[T]) -> None:
|
|
289
|
+
self.type = tp
|
|
290
|
+
self._defaults = {}
|
|
291
|
+
|
|
292
|
+
def __setattribute__(self, name: str, value: typing.Any, /) -> None:
|
|
293
|
+
self._defaults[name] = value
|
|
294
|
+
|
|
295
|
+
def __getitem__(self, key: str, /) -> None:
|
|
296
|
+
return Proxy(self, key) # type: ignore
|
|
297
|
+
|
|
298
|
+
def __setitem__(self, key: str, value: typing.Any, /) -> None:
|
|
299
|
+
self._defaults[key] = value
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
if typing.TYPE_CHECKING:
|
|
303
|
+
|
|
304
|
+
def ProxiedDict(typed_dct: type[T]) -> T | _ProxiedDict[T]: # noqa: N802
|
|
305
|
+
...
|
|
306
|
+
|
|
307
|
+
else:
|
|
308
|
+
ProxiedDict = _ProxiedDict
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
__all__ = (
|
|
312
|
+
"DataConverter",
|
|
313
|
+
"MODEL_CONFIG",
|
|
314
|
+
"Model",
|
|
315
|
+
"ProxiedDict",
|
|
316
|
+
"Proxy",
|
|
317
|
+
"full_result",
|
|
318
|
+
"generate_random_id",
|
|
319
|
+
"get_params",
|
|
320
|
+
)
|