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
telegrinder/model.py CHANGED
@@ -1,320 +1,213 @@
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",
1
+ import keyword
2
+ import typing
3
+
4
+ import msgspec
5
+ from fntypes.co import Nothing, Result, Some
6
+
7
+ from telegrinder.msgspec_utils import decoder, encoder, struct_as_dict
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from telegrinder.api.error import APIError
11
+
12
+ MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
13
+ "dict": True,
14
+ "rename": {kw + "_": kw for kw in keyword.kwlist},
15
+ }
16
+ UNSET = typing.cast(typing.Any, msgspec.UNSET)
17
+ """Docs: https://jcristharif.com/msgspec/api.html#unset
18
+
19
+ During decoding, if a field isn't explicitly set in the model,
20
+ the default value of `UNSET` will be set instead. This lets downstream
21
+ consumers determine whether a field was left unset, or explicitly set a value."""
22
+
23
+
24
+ def full_result[T](
25
+ result: Result[msgspec.Raw, "APIError"],
26
+ full_t: type[T],
27
+ ) -> Result[T, "APIError"]:
28
+ return result.map(lambda v: decoder.decode(v, type=full_t))
29
+
30
+
31
+ def is_none(value: typing.Any, /) -> typing.TypeGuard[None | Nothing]:
32
+ return value is None or isinstance(value, Nothing)
33
+
34
+
35
+ def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
36
+ validated_params = {}
37
+ for k, v in (
38
+ *params.pop("other", {}).items(),
39
+ *params.items(),
40
+ ):
41
+ if isinstance(v, Proxy):
42
+ v = v.get()
43
+ if k == "self" or is_none(v):
44
+ continue
45
+ validated_params[k] = v.unwrap() if isinstance(v, Some) else v
46
+ return validated_params
47
+
48
+
49
+ if typing.TYPE_CHECKING:
50
+
51
+ @typing.overload
52
+ def field(*, name: str | None = ...) -> typing.Any: ...
53
+
54
+ @typing.overload
55
+ def field(*, default: typing.Any, name: str | None = ...) -> typing.Any: ...
56
+
57
+ @typing.overload
58
+ def field(
59
+ *,
60
+ default_factory: typing.Callable[[], typing.Any],
61
+ name: str | None = None,
62
+ ) -> typing.Any: ...
63
+
64
+ @typing.overload
65
+ def field(
66
+ *,
67
+ converter: typing.Callable[[typing.Any], typing.Any],
68
+ name: str | None = ...,
69
+ ) -> typing.Any: ...
70
+
71
+ @typing.overload
72
+ def field(
73
+ *,
74
+ default: typing.Any,
75
+ converter: typing.Callable[[typing.Any], typing.Any],
76
+ name: str | None = ...,
77
+ ) -> typing.Any: ...
78
+
79
+ @typing.overload
80
+ def field(
81
+ *,
82
+ default_factory: typing.Callable[[], typing.Any],
83
+ converter: typing.Callable[[typing.Any], typing.Any],
84
+ name: str | None = None,
85
+ ) -> typing.Any: ...
86
+
87
+ def field(
88
+ *,
89
+ default=...,
90
+ default_factory=...,
91
+ name=...,
92
+ converter=...,
93
+ ) -> typing.Any: ...
94
+
95
+ class From[T]:
96
+ def __new__(cls, _: T, /) -> typing.Any: ...
97
+ else:
98
+ from msgspec import field as _field
99
+
100
+ type From[T] = T
101
+
102
+ def field(**kwargs):
103
+ kwargs.pop("converter", None)
104
+ return _field(**kwargs)
105
+
106
+
107
+ @typing.dataclass_transform(field_specifiers=(field,))
108
+ class Model(msgspec.Struct, **MODEL_CONFIG):
109
+ if not typing.TYPE_CHECKING:
110
+
111
+ def __post_init__(self):
112
+ for field in self.__struct_fields__:
113
+ if is_none(getattr(self, field)):
114
+ setattr(self, field, UNSET)
115
+
116
+ def __getattribute__(self, name, /):
117
+ val = super().__getattribute__(name)
118
+ return Nothing() if val is UNSET else val
119
+
120
+ @classmethod
121
+ def from_data[**P, T](cls: typing.Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
122
+ return decoder.convert(msgspec.structs.asdict(cls(*args, **kwargs)), type=cls) # type: ignore
123
+
124
+ @classmethod
125
+ def from_dict(cls, obj: dict[str, typing.Any], /) -> typing.Self:
126
+ return decoder.convert(obj, type=cls)
127
+
128
+ @classmethod
129
+ def from_raw(cls, raw: str | bytes, /) -> typing.Self:
130
+ return decoder.decode(raw, type=cls)
131
+
132
+ def _to_dict(
133
+ self,
134
+ dct_name: str,
135
+ exclude_fields: set[str],
136
+ full: bool,
137
+ ) -> dict[str, typing.Any]:
138
+ if dct_name not in self.__dict__:
139
+ self.__dict__[dct_name] = (
140
+ struct_as_dict(self)
141
+ if not full
142
+ else encoder.to_builtins(self.to_dict(exclude_fields=exclude_fields), order="deterministic")
143
+ )
144
+
145
+ if not exclude_fields:
146
+ return self.__dict__[dct_name]
147
+
148
+ return {key: value for key, value in self.__dict__[dct_name].items() if key not in exclude_fields}
149
+
150
+ def to_raw(self) -> str:
151
+ return encoder.encode(self)
152
+
153
+ def to_dict(
154
+ self,
155
+ *,
156
+ exclude_fields: set[str] | None = None,
157
+ ) -> dict[str, typing.Any]:
158
+ """:param exclude_fields: Model field names to exclude from the dictionary representation of this model.
159
+ :return: A dictionary representation of this model.
160
+ """
161
+ return self._to_dict("model_as_dict", exclude_fields or set(), full=False)
162
+
163
+ def to_full_dict(
164
+ self,
165
+ *,
166
+ exclude_fields: set[str] | None = None,
167
+ ) -> dict[str, typing.Any]:
168
+ """:param exclude_fields: Model field names to exclude from the dictionary representation of this model.
169
+ :return: A dictionary representation of this model including all models, structs, custom types.
170
+ """
171
+ return self._to_dict("model_as_full_dict", exclude_fields or set(), full=True)
172
+
173
+
174
+ class Proxy[T]:
175
+ def __init__(self, cfg: "_ProxiedDict[T]", key: str) -> None:
176
+ self.key = key
177
+ self.cfg = cfg
178
+
179
+ def get(self) -> typing.Any | None:
180
+ return self.cfg._defaults.get(self.key)
181
+
182
+
183
+ class _ProxiedDict[T]:
184
+ def __init__(self, tp: type[T]) -> None:
185
+ self.type = tp
186
+ self._defaults = {}
187
+
188
+ def __setattribute__(self, name: str, value: typing.Any, /) -> None:
189
+ self._defaults[name] = value
190
+
191
+ def __getitem__(self, key: str, /) -> None:
192
+ return Proxy(self, key) # type: ignore
193
+
194
+ def __setitem__(self, key: str, value: typing.Any, /) -> None:
195
+ self._defaults[key] = value
196
+
197
+
198
+ if typing.TYPE_CHECKING:
199
+
200
+ def ProxiedDict[T](typed_dct: type[T]) -> T | _ProxiedDict[T]: # noqa: N802
201
+ ...
202
+ else:
203
+ ProxiedDict = _ProxiedDict
204
+
205
+
206
+ __all__ = (
313
207
  "MODEL_CONFIG",
314
208
  "Model",
315
209
  "ProxiedDict",
316
210
  "Proxy",
317
211
  "full_result",
318
- "generate_random_id",
319
- "get_params",
320
- )
212
+ "get_params",
213
+ )