telegrinder 0.3.0.post2__py3-none-any.whl → 0.3.2__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 (164) 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 +11 -11
  10. telegrinder/bot/cute_types/base.py +258 -234
  11. telegrinder/bot/cute_types/callback_query.py +382 -382
  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 +53 -53
  15. telegrinder/bot/cute_types/message.py +2631 -2631
  16. telegrinder/bot/cute_types/update.py +75 -75
  17. telegrinder/bot/cute_types/utils.py +92 -92
  18. telegrinder/bot/dispatch/__init__.py +55 -55
  19. telegrinder/bot/dispatch/abc.py +77 -77
  20. telegrinder/bot/dispatch/context.py +92 -92
  21. telegrinder/bot/dispatch/dispatch.py +202 -201
  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 +128 -123
  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 +16 -16
  35. telegrinder/bot/dispatch/process.py +132 -132
  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 -211
  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 -118
  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 +57 -57
  55. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +57 -57
  56. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +53 -53
  57. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
  58. telegrinder/bot/dispatch/waiter_machine/machine.py +168 -170
  59. telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
  60. telegrinder/bot/dispatch/waiter_machine/short_state.py +65 -65
  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 +238 -233
  66. telegrinder/bot/rules/adapter/__init__.py +9 -9
  67. telegrinder/bot/rules/adapter/abc.py +29 -29
  68. telegrinder/bot/rules/adapter/errors.py +5 -5
  69. telegrinder/bot/rules/adapter/event.py +76 -76
  70. telegrinder/bot/rules/adapter/node.py +48 -48
  71. telegrinder/bot/rules/adapter/raw_update.py +30 -30
  72. telegrinder/bot/rules/callback_data.py +171 -171
  73. telegrinder/bot/rules/chat_join.py +48 -48
  74. telegrinder/bot/rules/command.py +126 -126
  75. telegrinder/bot/rules/enum_text.py +36 -33
  76. telegrinder/bot/rules/func.py +26 -26
  77. telegrinder/bot/rules/fuzzy.py +24 -24
  78. telegrinder/bot/rules/inline.py +60 -60
  79. telegrinder/bot/rules/integer.py +20 -20
  80. telegrinder/bot/rules/is_from.py +146 -146
  81. telegrinder/bot/rules/markup.py +43 -43
  82. telegrinder/bot/rules/mention.py +14 -14
  83. telegrinder/bot/rules/message.py +17 -17
  84. telegrinder/bot/rules/message_entities.py +35 -35
  85. telegrinder/bot/rules/node.py +27 -27
  86. telegrinder/bot/rules/regex.py +37 -37
  87. telegrinder/bot/rules/rule_enum.py +72 -72
  88. telegrinder/bot/rules/start.py +42 -42
  89. telegrinder/bot/rules/state.py +37 -37
  90. telegrinder/bot/rules/text.py +33 -33
  91. telegrinder/bot/rules/update.py +15 -15
  92. telegrinder/bot/scenario/__init__.py +5 -5
  93. telegrinder/bot/scenario/abc.py +19 -19
  94. telegrinder/bot/scenario/checkbox.py +167 -139
  95. telegrinder/bot/scenario/choice.py +46 -44
  96. telegrinder/client/__init__.py +4 -4
  97. telegrinder/client/abc.py +75 -75
  98. telegrinder/client/aiohttp.py +130 -130
  99. telegrinder/model.py +244 -244
  100. telegrinder/modules.py +237 -237
  101. telegrinder/msgspec_json.py +14 -14
  102. telegrinder/msgspec_utils.py +410 -410
  103. telegrinder/node/__init__.py +20 -20
  104. telegrinder/node/attachment.py +92 -92
  105. telegrinder/node/base.py +143 -144
  106. telegrinder/node/callback_query.py +14 -14
  107. telegrinder/node/command.py +33 -33
  108. telegrinder/node/composer.py +196 -184
  109. telegrinder/node/container.py +27 -27
  110. telegrinder/node/event.py +71 -73
  111. telegrinder/node/me.py +16 -16
  112. telegrinder/node/message.py +14 -14
  113. telegrinder/node/polymorphic.py +48 -52
  114. telegrinder/node/rule.py +76 -76
  115. telegrinder/node/scope.py +38 -38
  116. telegrinder/node/source.py +71 -71
  117. telegrinder/node/text.py +21 -21
  118. telegrinder/node/tools/__init__.py +3 -3
  119. telegrinder/node/tools/generator.py +40 -40
  120. telegrinder/node/update.py +15 -15
  121. telegrinder/rules.py +0 -0
  122. telegrinder/tools/__init__.py +74 -74
  123. telegrinder/tools/buttons.py +79 -79
  124. telegrinder/tools/error_handler/__init__.py +7 -7
  125. telegrinder/tools/error_handler/abc.py +33 -33
  126. telegrinder/tools/error_handler/error.py +9 -9
  127. telegrinder/tools/error_handler/error_handler.py +193 -193
  128. telegrinder/tools/formatting/__init__.py +46 -46
  129. telegrinder/tools/formatting/html.py +308 -308
  130. telegrinder/tools/formatting/links.py +33 -33
  131. telegrinder/tools/formatting/spec_html_formats.py +111 -111
  132. telegrinder/tools/functional.py +12 -12
  133. telegrinder/tools/global_context/__init__.py +7 -7
  134. telegrinder/tools/global_context/abc.py +63 -63
  135. telegrinder/tools/global_context/global_context.py +412 -412
  136. telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
  137. telegrinder/tools/i18n/__init__.py +12 -12
  138. telegrinder/tools/i18n/abc.py +32 -32
  139. telegrinder/tools/i18n/middleware/__init__.py +3 -3
  140. telegrinder/tools/i18n/middleware/abc.py +25 -25
  141. telegrinder/tools/i18n/simple.py +43 -43
  142. telegrinder/tools/kb_set/__init__.py +4 -4
  143. telegrinder/tools/kb_set/base.py +15 -15
  144. telegrinder/tools/kb_set/yaml.py +63 -63
  145. telegrinder/tools/keyboard.py +128 -128
  146. telegrinder/tools/limited_dict.py +37 -37
  147. telegrinder/tools/loop_wrapper/__init__.py +4 -4
  148. telegrinder/tools/loop_wrapper/abc.py +15 -15
  149. telegrinder/tools/loop_wrapper/loop_wrapper.py +216 -216
  150. telegrinder/tools/magic.py +168 -164
  151. telegrinder/tools/parse_mode.py +6 -6
  152. telegrinder/tools/state_storage/__init__.py +4 -4
  153. telegrinder/tools/state_storage/abc.py +35 -35
  154. telegrinder/tools/state_storage/memory.py +25 -25
  155. telegrinder/types/__init__.py +6 -6
  156. telegrinder/types/enums.py +672 -672
  157. telegrinder/types/methods.py +4633 -4633
  158. telegrinder/types/objects.py +6317 -6317
  159. telegrinder/verification_utils.py +32 -32
  160. {telegrinder-0.3.0.post2.dist-info → telegrinder-0.3.2.dist-info}/LICENSE +22 -22
  161. {telegrinder-0.3.0.post2.dist-info → telegrinder-0.3.2.dist-info}/METADATA +1 -1
  162. telegrinder-0.3.2.dist-info/RECORD +164 -0
  163. telegrinder-0.3.0.post2.dist-info/RECORD +0 -164
  164. {telegrinder-0.3.0.post2.dist-info → telegrinder-0.3.2.dist-info}/WHEEL +0 -0
telegrinder/model.py CHANGED
@@ -1,244 +1,244 @@
1
- import dataclasses
2
- import enum
3
- import keyword
4
- import secrets
5
- import typing
6
- from datetime import datetime
7
- from types import NoneType
8
-
9
- import msgspec
10
- from fntypes.co import Nothing, Result, Some
11
-
12
- from telegrinder.msgspec_utils import decoder, encoder, get_origin
13
-
14
- if typing.TYPE_CHECKING:
15
- from telegrinder.api.error import APIError
16
-
17
- T = typing.TypeVar("T")
18
-
19
- UnionType: typing.TypeAlias = typing.Annotated[tuple[T, ...], ...]
20
-
21
- MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
22
- "omit_defaults": True,
23
- "dict": True,
24
- "rename": {kw + "_": kw for kw in keyword.kwlist},
25
- }
26
-
27
-
28
- @typing.overload
29
- def full_result(
30
- result: Result[msgspec.Raw, "APIError"],
31
- full_t: type[T],
32
- ) -> Result[T, "APIError"]: ...
33
-
34
-
35
- @typing.overload
36
- def full_result(
37
- result: Result[msgspec.Raw, "APIError"],
38
- full_t: UnionType[T],
39
- ) -> Result[T, "APIError"]: ...
40
-
41
-
42
- def full_result(
43
- result: Result[msgspec.Raw, "APIError"],
44
- full_t: typing.Any,
45
- ) -> Result[typing.Any, "APIError"]:
46
- return result.map(lambda v: decoder.decode(v, type=full_t))
47
-
48
-
49
- def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
50
- validated_params = {}
51
- for k, v in (
52
- *params.pop("other", {}).items(),
53
- *params.items(),
54
- ):
55
- if isinstance(v, Proxy):
56
- v = v.get()
57
- if k == "self" or type(v) in (NoneType, Nothing):
58
- continue
59
- validated_params[k] = v.unwrap() if isinstance(v, Some) else v
60
- return validated_params
61
-
62
-
63
- class Model(msgspec.Struct, **MODEL_CONFIG):
64
- @classmethod
65
- def from_data(cls, data: dict[str, typing.Any]) -> typing.Self:
66
- return decoder.convert(data, type=cls)
67
-
68
- @classmethod
69
- def from_bytes(cls, data: bytes) -> typing.Self:
70
- return decoder.decode(data, type=cls)
71
-
72
- def _to_dict(
73
- self,
74
- dct_name: str,
75
- exclude_fields: set[str],
76
- full: bool,
77
- ) -> dict[str, typing.Any]:
78
- if dct_name not in self.__dict__:
79
- self.__dict__[dct_name] = (
80
- msgspec.structs.asdict(self)
81
- if not full
82
- else encoder.to_builtins(self.to_dict(exclude_fields=exclude_fields), order="deterministic")
83
- )
84
-
85
- if not exclude_fields:
86
- return self.__dict__[dct_name]
87
-
88
- return {key: value for key, value in self.__dict__[dct_name].items() if key not in exclude_fields}
89
-
90
- def to_dict(
91
- self,
92
- *,
93
- exclude_fields: set[str] | None = None,
94
- ) -> dict[str, typing.Any]:
95
- """
96
- :param exclude_fields: Model field names to exclude from the dictionary representation of this model.
97
- :return: A dictionary representation of this model.
98
- """
99
-
100
- return self._to_dict("model_as_dict", exclude_fields or set(), full=False)
101
-
102
- def to_full_dict(
103
- self,
104
- *,
105
- exclude_fields: set[str] | None = None,
106
- ) -> dict[str, typing.Any]:
107
- """
108
- :param exclude_fields: Model field names to exclude from the dictionary representation of this model.
109
- :return: A dictionary representation of this model including all models, structs, custom types.
110
- """
111
-
112
- return self._to_dict("model_as_full_dict", exclude_fields or set(), full=True)
113
-
114
-
115
- @dataclasses.dataclass(kw_only=True, frozen=True, slots=True, repr=False)
116
- class DataConverter:
117
- _converters: dict[type[typing.Any], typing.Callable[..., typing.Any]] = dataclasses.field(
118
- init=False,
119
- default_factory=lambda: {},
120
- )
121
- _files: dict[str, tuple[str, bytes]] = dataclasses.field(default_factory=lambda: {})
122
-
123
- def __repr__(self) -> str:
124
- return "<{}: {}>".format(
125
- self.__class__.__name__,
126
- ", ".join(f"{k}={v.__name__!r}" for k, v in self._converters.items()),
127
- )
128
-
129
- def __post_init__(self) -> None:
130
- self._converters.update(
131
- {
132
- get_origin(value.__annotations__["data"]): value
133
- for key, value in vars(self.__class__).items()
134
- if key.startswith("convert_") and callable(value)
135
- }
136
- )
137
-
138
- def __call__(self, data: typing.Any, *, serialize: bool = True) -> typing.Any:
139
- converter = self.get_converter(get_origin(type(data)))
140
- if converter is not None:
141
- if isinstance(converter, staticmethod):
142
- return converter(data, serialize)
143
- return converter(self, data, serialize)
144
- return data
145
-
146
- @property
147
- def converters(self) -> dict[type[typing.Any], typing.Callable[..., typing.Any]]:
148
- return self._converters.copy()
149
-
150
- @property
151
- def files(self) -> dict[str, tuple[str, bytes]]:
152
- return self._files.copy()
153
-
154
- @staticmethod
155
- def convert_enum(data: enum.Enum, _: bool = False) -> typing.Any:
156
- return data.value
157
-
158
- @staticmethod
159
- def convert_datetime(data: datetime, _: bool = False) -> int:
160
- return int(data.timestamp())
161
-
162
- def get_converter(self, t: type[typing.Any]):
163
- for type_, converter in self._converters.items():
164
- if issubclass(t, type_):
165
- return converter
166
- return None
167
-
168
- def convert_model(
169
- self,
170
- data: Model,
171
- serialize: bool = True,
172
- ) -> str | dict[str, typing.Any]:
173
- converted_dct = self(data.to_dict(), serialize=False)
174
- return encoder.encode(converted_dct) if serialize is True else converted_dct
175
-
176
- def convert_dct(
177
- self,
178
- data: dict[str, typing.Any],
179
- serialize: bool = True,
180
- ) -> dict[str, typing.Any]:
181
- return {
182
- k: self(v, serialize=serialize) for k, v in data.items() if type(v) not in (NoneType, Nothing)
183
- }
184
-
185
- def convert_lst(
186
- self,
187
- data: list[typing.Any],
188
- serialize: bool = True,
189
- ) -> str | list[typing.Any]:
190
- converted_lst = [self(x, serialize=False) for x in data]
191
- return encoder.encode(converted_lst) if serialize is True else converted_lst
192
-
193
- def convert_tpl(self, data: tuple[typing.Any, ...], _: bool = False) -> str | tuple[typing.Any, ...]:
194
- match data:
195
- case (str(filename), bytes(content)):
196
- attach_name = secrets.token_urlsafe(16)
197
- self._files[attach_name] = (filename, content)
198
- return "attach://{}".format(attach_name)
199
-
200
- return data
201
-
202
-
203
- class Proxy:
204
- def __init__(self, cfg: "_ProxiedDict", key: str) -> None:
205
- self.key = key
206
- self.cfg = cfg
207
-
208
- def get(self) -> typing.Any | None:
209
- return self.cfg._defaults.get(self.key)
210
-
211
-
212
- class _ProxiedDict(typing.Generic[T]):
213
- def __init__(self, tp: type[T]) -> None:
214
- self.type = tp
215
- self._defaults = {}
216
-
217
- def __setattribute__(self, name: str, value: typing.Any, /) -> None:
218
- self._defaults[name] = value
219
-
220
- def __getitem__(self, key: str, /) -> None:
221
- return Proxy(self, key) # type: ignore
222
-
223
- def __setitem__(self, key: str, value: typing.Any, /) -> None:
224
- self._defaults[key] = value
225
-
226
-
227
- if typing.TYPE_CHECKING:
228
-
229
- def ProxiedDict(typed_dct: type[T]) -> T | _ProxiedDict[T]: # noqa: N802
230
- ...
231
-
232
- else:
233
- ProxiedDict = _ProxiedDict
234
-
235
-
236
- __all__ = (
237
- "DataConverter",
238
- "MODEL_CONFIG",
239
- "Model",
240
- "ProxiedDict",
241
- "Proxy",
242
- "full_result",
243
- "get_params",
244
- )
1
+ import dataclasses
2
+ import enum
3
+ import keyword
4
+ import secrets
5
+ import typing
6
+ from datetime import datetime
7
+ from types import NoneType
8
+
9
+ import msgspec
10
+ from fntypes.co import Nothing, Result, Some
11
+
12
+ from telegrinder.msgspec_utils import decoder, encoder, get_origin
13
+
14
+ if typing.TYPE_CHECKING:
15
+ from telegrinder.api.error import APIError
16
+
17
+ T = typing.TypeVar("T")
18
+
19
+ UnionType: typing.TypeAlias = typing.Annotated[tuple[T, ...], ...]
20
+
21
+ MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
22
+ "omit_defaults": True,
23
+ "dict": True,
24
+ "rename": {kw + "_": kw for kw in keyword.kwlist},
25
+ }
26
+
27
+
28
+ @typing.overload
29
+ def full_result(
30
+ result: Result[msgspec.Raw, "APIError"],
31
+ full_t: type[T],
32
+ ) -> Result[T, "APIError"]: ...
33
+
34
+
35
+ @typing.overload
36
+ def full_result(
37
+ result: Result[msgspec.Raw, "APIError"],
38
+ full_t: UnionType[T],
39
+ ) -> Result[T, "APIError"]: ...
40
+
41
+
42
+ def full_result(
43
+ result: Result[msgspec.Raw, "APIError"],
44
+ full_t: typing.Any,
45
+ ) -> Result[typing.Any, "APIError"]:
46
+ return result.map(lambda v: decoder.decode(v, type=full_t))
47
+
48
+
49
+ def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
50
+ validated_params = {}
51
+ for k, v in (
52
+ *params.pop("other", {}).items(),
53
+ *params.items(),
54
+ ):
55
+ if isinstance(v, Proxy):
56
+ v = v.get()
57
+ if k == "self" or type(v) in (NoneType, Nothing):
58
+ continue
59
+ validated_params[k] = v.unwrap() if isinstance(v, Some) else v
60
+ return validated_params
61
+
62
+
63
+ class Model(msgspec.Struct, **MODEL_CONFIG):
64
+ @classmethod
65
+ def from_data(cls, data: dict[str, typing.Any]) -> typing.Self:
66
+ return decoder.convert(data, type=cls)
67
+
68
+ @classmethod
69
+ def from_bytes(cls, data: bytes) -> typing.Self:
70
+ return decoder.decode(data, type=cls)
71
+
72
+ def _to_dict(
73
+ self,
74
+ dct_name: str,
75
+ exclude_fields: set[str],
76
+ full: bool,
77
+ ) -> dict[str, typing.Any]:
78
+ if dct_name not in self.__dict__:
79
+ self.__dict__[dct_name] = (
80
+ msgspec.structs.asdict(self)
81
+ if not full
82
+ else encoder.to_builtins(self.to_dict(exclude_fields=exclude_fields), order="deterministic")
83
+ )
84
+
85
+ if not exclude_fields:
86
+ return self.__dict__[dct_name]
87
+
88
+ return {key: value for key, value in self.__dict__[dct_name].items() if key not in exclude_fields}
89
+
90
+ def to_dict(
91
+ self,
92
+ *,
93
+ exclude_fields: set[str] | None = None,
94
+ ) -> dict[str, typing.Any]:
95
+ """
96
+ :param exclude_fields: Model field names to exclude from the dictionary representation of this model.
97
+ :return: A dictionary representation of this model.
98
+ """
99
+
100
+ return self._to_dict("model_as_dict", exclude_fields or set(), full=False)
101
+
102
+ def to_full_dict(
103
+ self,
104
+ *,
105
+ exclude_fields: set[str] | None = None,
106
+ ) -> dict[str, typing.Any]:
107
+ """
108
+ :param exclude_fields: Model field names to exclude from the dictionary representation of this model.
109
+ :return: A dictionary representation of this model including all models, structs, custom types.
110
+ """
111
+
112
+ return self._to_dict("model_as_full_dict", exclude_fields or set(), full=True)
113
+
114
+
115
+ @dataclasses.dataclass(kw_only=True, frozen=True, slots=True, repr=False)
116
+ class DataConverter:
117
+ _converters: dict[type[typing.Any], typing.Callable[..., typing.Any]] = dataclasses.field(
118
+ init=False,
119
+ default_factory=lambda: {},
120
+ )
121
+ _files: dict[str, tuple[str, bytes]] = dataclasses.field(default_factory=lambda: {})
122
+
123
+ def __repr__(self) -> str:
124
+ return "<{}: {}>".format(
125
+ self.__class__.__name__,
126
+ ", ".join(f"{k}={v.__name__!r}" for k, v in self._converters.items()),
127
+ )
128
+
129
+ def __post_init__(self) -> None:
130
+ self._converters.update(
131
+ {
132
+ get_origin(value.__annotations__["data"]): value
133
+ for key, value in vars(self.__class__).items()
134
+ if key.startswith("convert_") and callable(value)
135
+ }
136
+ )
137
+
138
+ def __call__(self, data: typing.Any, *, serialize: bool = True) -> typing.Any:
139
+ converter = self.get_converter(get_origin(type(data)))
140
+ if converter is not None:
141
+ if isinstance(converter, staticmethod):
142
+ return converter(data, serialize)
143
+ return converter(self, data, serialize)
144
+ return data
145
+
146
+ @property
147
+ def converters(self) -> dict[type[typing.Any], typing.Callable[..., typing.Any]]:
148
+ return self._converters.copy()
149
+
150
+ @property
151
+ def files(self) -> dict[str, tuple[str, bytes]]:
152
+ return self._files.copy()
153
+
154
+ @staticmethod
155
+ def convert_enum(data: enum.Enum, _: bool = False) -> typing.Any:
156
+ return data.value
157
+
158
+ @staticmethod
159
+ def convert_datetime(data: datetime, _: bool = False) -> int:
160
+ return int(data.timestamp())
161
+
162
+ def get_converter(self, t: type[typing.Any]):
163
+ for type_, converter in self._converters.items():
164
+ if issubclass(t, type_):
165
+ return converter
166
+ return None
167
+
168
+ def convert_model(
169
+ self,
170
+ data: Model,
171
+ serialize: bool = True,
172
+ ) -> str | dict[str, typing.Any]:
173
+ converted_dct = self(data.to_dict(), serialize=False)
174
+ return encoder.encode(converted_dct) if serialize is True else converted_dct
175
+
176
+ def convert_dct(
177
+ self,
178
+ data: dict[str, typing.Any],
179
+ serialize: bool = True,
180
+ ) -> dict[str, typing.Any]:
181
+ return {
182
+ k: self(v, serialize=serialize) for k, v in data.items() if type(v) not in (NoneType, Nothing)
183
+ }
184
+
185
+ def convert_lst(
186
+ self,
187
+ data: list[typing.Any],
188
+ serialize: bool = True,
189
+ ) -> str | list[typing.Any]:
190
+ converted_lst = [self(x, serialize=False) for x in data]
191
+ return encoder.encode(converted_lst) if serialize is True else converted_lst
192
+
193
+ def convert_tpl(self, data: tuple[typing.Any, ...], _: bool = False) -> str | tuple[typing.Any, ...]:
194
+ match data:
195
+ case (str(filename), bytes(content)):
196
+ attach_name = secrets.token_urlsafe(16)
197
+ self._files[attach_name] = (filename, content)
198
+ return "attach://{}".format(attach_name)
199
+
200
+ return data
201
+
202
+
203
+ class Proxy:
204
+ def __init__(self, cfg: "_ProxiedDict", key: str) -> None:
205
+ self.key = key
206
+ self.cfg = cfg
207
+
208
+ def get(self) -> typing.Any | None:
209
+ return self.cfg._defaults.get(self.key)
210
+
211
+
212
+ class _ProxiedDict(typing.Generic[T]):
213
+ def __init__(self, tp: type[T]) -> None:
214
+ self.type = tp
215
+ self._defaults = {}
216
+
217
+ def __setattribute__(self, name: str, value: typing.Any, /) -> None:
218
+ self._defaults[name] = value
219
+
220
+ def __getitem__(self, key: str, /) -> None:
221
+ return Proxy(self, key) # type: ignore
222
+
223
+ def __setitem__(self, key: str, value: typing.Any, /) -> None:
224
+ self._defaults[key] = value
225
+
226
+
227
+ if typing.TYPE_CHECKING:
228
+
229
+ def ProxiedDict(typed_dct: type[T]) -> T | _ProxiedDict[T]: # noqa: N802
230
+ ...
231
+
232
+ else:
233
+ ProxiedDict = _ProxiedDict
234
+
235
+
236
+ __all__ = (
237
+ "DataConverter",
238
+ "MODEL_CONFIG",
239
+ "Model",
240
+ "ProxiedDict",
241
+ "Proxy",
242
+ "full_result",
243
+ "get_params",
244
+ )