telegrinder 0.3.4__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.

Files changed (165) hide show
  1. telegrinder/__init__.py +144 -144
  2. telegrinder/api/__init__.py +8 -8
  3. telegrinder/api/api.py +93 -93
  4. telegrinder/api/error.py +16 -16
  5. telegrinder/api/response.py +20 -20
  6. telegrinder/api/token.py +36 -36
  7. telegrinder/bot/__init__.py +66 -66
  8. telegrinder/bot/bot.py +76 -76
  9. telegrinder/bot/cute_types/__init__.py +17 -17
  10. telegrinder/bot/cute_types/base.py +258 -258
  11. telegrinder/bot/cute_types/callback_query.py +385 -385
  12. telegrinder/bot/cute_types/chat_join_request.py +61 -61
  13. telegrinder/bot/cute_types/chat_member_updated.py +160 -160
  14. telegrinder/bot/cute_types/inline_query.py +43 -43
  15. telegrinder/bot/cute_types/message.py +2637 -2637
  16. telegrinder/bot/cute_types/update.py +104 -104
  17. telegrinder/bot/cute_types/utils.py +95 -95
  18. telegrinder/bot/dispatch/__init__.py +55 -55
  19. telegrinder/bot/dispatch/abc.py +77 -77
  20. telegrinder/bot/dispatch/context.py +98 -98
  21. telegrinder/bot/dispatch/dispatch.py +202 -202
  22. telegrinder/bot/dispatch/handler/__init__.py +13 -13
  23. telegrinder/bot/dispatch/handler/abc.py +24 -24
  24. telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
  25. telegrinder/bot/dispatch/handler/base.py +57 -57
  26. telegrinder/bot/dispatch/handler/document_reply.py +44 -44
  27. telegrinder/bot/dispatch/handler/func.py +135 -135
  28. telegrinder/bot/dispatch/handler/media_group_reply.py +43 -43
  29. telegrinder/bot/dispatch/handler/message_reply.py +36 -36
  30. telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
  31. telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
  32. telegrinder/bot/dispatch/handler/video_reply.py +44 -44
  33. telegrinder/bot/dispatch/middleware/__init__.py +3 -3
  34. telegrinder/bot/dispatch/middleware/abc.py +22 -22
  35. telegrinder/bot/dispatch/process.py +157 -157
  36. telegrinder/bot/dispatch/return_manager/__init__.py +13 -13
  37. telegrinder/bot/dispatch/return_manager/abc.py +108 -108
  38. telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
  39. telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
  40. telegrinder/bot/dispatch/return_manager/message.py +36 -36
  41. telegrinder/bot/dispatch/view/__init__.py +13 -13
  42. telegrinder/bot/dispatch/view/abc.py +41 -41
  43. telegrinder/bot/dispatch/view/base.py +200 -200
  44. telegrinder/bot/dispatch/view/box.py +129 -129
  45. telegrinder/bot/dispatch/view/callback_query.py +17 -17
  46. telegrinder/bot/dispatch/view/chat_join_request.py +16 -16
  47. telegrinder/bot/dispatch/view/chat_member.py +39 -39
  48. telegrinder/bot/dispatch/view/inline_query.py +17 -17
  49. telegrinder/bot/dispatch/view/message.py +44 -44
  50. telegrinder/bot/dispatch/view/raw.py +114 -114
  51. telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
  52. telegrinder/bot/dispatch/waiter_machine/actions.py +13 -13
  53. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
  54. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
  55. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +57 -57
  56. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
  57. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
  58. telegrinder/bot/dispatch/waiter_machine/machine.py +172 -172
  59. telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
  60. telegrinder/bot/dispatch/waiter_machine/short_state.py +68 -68
  61. telegrinder/bot/polling/__init__.py +4 -4
  62. telegrinder/bot/polling/abc.py +25 -25
  63. telegrinder/bot/polling/polling.py +131 -131
  64. telegrinder/bot/rules/__init__.py +62 -62
  65. telegrinder/bot/rules/abc.py +206 -206
  66. telegrinder/bot/rules/adapter/__init__.py +17 -17
  67. telegrinder/bot/rules/adapter/abc.py +31 -31
  68. telegrinder/bot/rules/adapter/errors.py +5 -5
  69. telegrinder/bot/rules/adapter/event.py +65 -65
  70. telegrinder/bot/rules/adapter/node.py +48 -48
  71. telegrinder/bot/rules/adapter/raw_event.py +27 -27
  72. telegrinder/bot/rules/adapter/raw_update.py +30 -30
  73. telegrinder/bot/rules/callback_data.py +163 -163
  74. telegrinder/bot/rules/chat_join.py +43 -43
  75. telegrinder/bot/rules/command.py +126 -126
  76. telegrinder/bot/rules/enum_text.py +36 -36
  77. telegrinder/bot/rules/func.py +26 -26
  78. telegrinder/bot/rules/fuzzy.py +24 -24
  79. telegrinder/bot/rules/inline.py +56 -56
  80. telegrinder/bot/rules/integer.py +20 -20
  81. telegrinder/bot/rules/is_from.py +127 -127
  82. telegrinder/bot/rules/markup.py +43 -43
  83. telegrinder/bot/rules/mention.py +14 -14
  84. telegrinder/bot/rules/message.py +17 -17
  85. telegrinder/bot/rules/message_entities.py +35 -35
  86. telegrinder/bot/rules/node.py +27 -27
  87. telegrinder/bot/rules/regex.py +37 -37
  88. telegrinder/bot/rules/rule_enum.py +72 -72
  89. telegrinder/bot/rules/start.py +42 -42
  90. telegrinder/bot/rules/state.py +37 -37
  91. telegrinder/bot/rules/text.py +33 -33
  92. telegrinder/bot/rules/update.py +15 -15
  93. telegrinder/bot/scenario/__init__.py +5 -5
  94. telegrinder/bot/scenario/abc.py +19 -19
  95. telegrinder/bot/scenario/checkbox.py +176 -176
  96. telegrinder/bot/scenario/choice.py +51 -51
  97. telegrinder/client/__init__.py +4 -4
  98. telegrinder/client/abc.py +75 -75
  99. telegrinder/client/aiohttp.py +130 -130
  100. telegrinder/model.py +313 -313
  101. telegrinder/modules.py +237 -237
  102. telegrinder/msgspec_json.py +14 -14
  103. telegrinder/msgspec_utils.py +410 -410
  104. telegrinder/node/__init__.py +20 -20
  105. telegrinder/node/attachment.py +87 -87
  106. telegrinder/node/base.py +157 -157
  107. telegrinder/node/callback_query.py +53 -53
  108. telegrinder/node/command.py +33 -33
  109. telegrinder/node/composer.py +198 -198
  110. telegrinder/node/container.py +27 -27
  111. telegrinder/node/event.py +65 -65
  112. telegrinder/node/me.py +16 -16
  113. telegrinder/node/message.py +14 -14
  114. telegrinder/node/polymorphic.py +48 -48
  115. telegrinder/node/rule.py +76 -76
  116. telegrinder/node/scope.py +38 -38
  117. telegrinder/node/source.py +71 -71
  118. telegrinder/node/text.py +41 -41
  119. telegrinder/node/tools/__init__.py +3 -3
  120. telegrinder/node/tools/generator.py +40 -40
  121. telegrinder/node/update.py +15 -15
  122. telegrinder/rules.py +5 -5
  123. telegrinder/tools/__init__.py +74 -74
  124. telegrinder/tools/buttons.py +79 -79
  125. telegrinder/tools/error_handler/__init__.py +7 -7
  126. telegrinder/tools/error_handler/abc.py +33 -33
  127. telegrinder/tools/error_handler/error.py +9 -9
  128. telegrinder/tools/error_handler/error_handler.py +193 -193
  129. telegrinder/tools/formatting/__init__.py +46 -46
  130. telegrinder/tools/formatting/html.py +283 -283
  131. telegrinder/tools/formatting/links.py +33 -33
  132. telegrinder/tools/formatting/spec_html_formats.py +111 -111
  133. telegrinder/tools/functional.py +12 -12
  134. telegrinder/tools/global_context/__init__.py +7 -7
  135. telegrinder/tools/global_context/abc.py +63 -63
  136. telegrinder/tools/global_context/global_context.py +412 -412
  137. telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
  138. telegrinder/tools/i18n/__init__.py +7 -7
  139. telegrinder/tools/i18n/abc.py +30 -30
  140. telegrinder/tools/i18n/middleware/__init__.py +3 -3
  141. telegrinder/tools/i18n/middleware/abc.py +25 -25
  142. telegrinder/tools/i18n/simple.py +43 -43
  143. telegrinder/tools/kb_set/__init__.py +4 -4
  144. telegrinder/tools/kb_set/base.py +15 -15
  145. telegrinder/tools/kb_set/yaml.py +63 -63
  146. telegrinder/tools/keyboard.py +128 -128
  147. telegrinder/tools/limited_dict.py +37 -37
  148. telegrinder/tools/loop_wrapper/__init__.py +4 -4
  149. telegrinder/tools/loop_wrapper/abc.py +15 -15
  150. telegrinder/tools/loop_wrapper/loop_wrapper.py +224 -224
  151. telegrinder/tools/magic.py +157 -157
  152. telegrinder/tools/parse_mode.py +6 -6
  153. telegrinder/tools/state_storage/__init__.py +4 -4
  154. telegrinder/tools/state_storage/abc.py +35 -35
  155. telegrinder/tools/state_storage/memory.py +25 -25
  156. telegrinder/types/__init__.py +260 -260
  157. telegrinder/types/enums.py +701 -701
  158. telegrinder/types/methods.py +4633 -4633
  159. telegrinder/types/objects.py +6950 -6950
  160. telegrinder/verification_utils.py +32 -32
  161. {telegrinder-0.3.4.dist-info → telegrinder-0.3.4.post1.dist-info}/LICENSE +22 -22
  162. {telegrinder-0.3.4.dist-info → telegrinder-0.3.4.post1.dist-info}/METADATA +1 -1
  163. telegrinder-0.3.4.post1.dist-info/RECORD +165 -0
  164. telegrinder-0.3.4.dist-info/RECORD +0 -165
  165. {telegrinder-0.3.4.dist-info → telegrinder-0.3.4.post1.dist-info}/WHEEL +0 -0
telegrinder/model.py CHANGED
@@ -1,314 +1,314 @@
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__ = (
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
312
  "DataConverter",
313
313
  "MODEL_CONFIG",
314
314
  "Model",
@@ -316,5 +316,5 @@ __all__ = (
316
316
  "Proxy",
317
317
  "full_result",
318
318
  "generate_random_id",
319
- "get_params",
320
- )
319
+ "get_params",
320
+ )