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
@@ -1,258 +1,184 @@
1
- import dataclasses
2
- import inspect
3
- import typing
4
- from functools import wraps
5
-
6
- import msgspec
7
- import typing_extensions
8
- from fntypes.result import Result
9
-
10
- from telegrinder.api.api import API
11
- from telegrinder.model import Model, get_params
12
-
13
- F = typing.TypeVar("F", bound=typing.Callable[..., typing.Any])
14
- Cute = typing.TypeVar("Cute", bound="BaseCute")
15
- Update = typing_extensions.TypeVar("Update", bound=Model)
16
- CtxAPI = typing_extensions.TypeVar("CtxAPI", bound=API, default=API)
17
-
18
- Executor: typing.TypeAlias = typing.Callable[
19
- [Cute, str, dict[str, typing.Any]],
20
- typing.Awaitable[Result[typing.Any, typing.Any]],
21
- ]
22
-
23
- if typing.TYPE_CHECKING:
24
-
25
- class BaseCute(Model, typing.Generic[Update, CtxAPI]):
26
- api: API
27
-
28
- @classmethod
29
- def from_update(cls, update: Update, bound_api: API) -> typing.Self: ...
30
-
31
- @property
32
- def ctx_api(self) -> CtxAPI: ...
33
-
34
- def to_dict(
35
- self,
36
- *,
37
- exclude_fields: set[str] | None = None,
38
- ) -> dict[str, typing.Any]:
39
- """
40
- :param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
41
- :return: A dictionary representation of this cute model.
42
- """
43
-
44
- ...
45
-
46
- def to_full_dict(
47
- self,
48
- *,
49
- exclude_fields: set[str] | None = None,
50
- ) -> dict[str, typing.Any]:
51
- """
52
- :param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
53
- :return: A dictionary representation of this model including all models, structs, custom types.
54
- """
55
-
56
- ...
57
-
58
- else:
59
- import msgspec
60
- from fntypes.co import Nothing, Some, Variative
61
-
62
- from telegrinder.msgspec_utils import Option, decoder, encoder
63
- from telegrinder.msgspec_utils import get_class_annotations as _get_class_annotations
64
-
65
- def _get_cute_from_generic(generic_args):
66
- for arg in generic_args:
67
- orig_arg = typing.get_origin(arg) or arg
68
-
69
- if not isinstance(orig_arg, type):
70
- continue
71
- if orig_arg in (Variative, Some, Option):
72
- return _get_cute_from_generic(typing.get_args(arg))
73
- if issubclass(arg, BaseCute):
74
- return arg
75
-
76
- return None
77
-
78
- def _get_cute_annotations(annotations):
79
- cute_annotations = {}
80
-
81
- for key, hint in annotations.items():
82
- if not isinstance(hint, type):
83
- if (cute := _get_cute_from_generic(typing.get_args(hint))) is not None:
84
- cute_annotations[key] = cute
85
-
86
- elif issubclass(hint, BaseCute):
87
- cute_annotations[key] = hint
88
-
89
- return cute_annotations
90
-
91
- def _get_value(value):
92
- while isinstance(value, Variative | Some):
93
- if isinstance(value, Variative):
94
- value = value.v
95
- if isinstance(value, Some):
96
- value = value.value
97
- return value
98
-
99
- class BaseCute(typing.Generic[Update, CtxAPI]):
100
- def __init_subclass__(cls, *args, **kwargs):
101
- super().__init_subclass__(*args, **kwargs)
102
-
103
- if not cls.__bases__ or not issubclass(cls.__bases__[0], BaseCute):
104
- return
105
-
106
- cls.__is_solved_annotations__ = False
107
- cls.__cute_annotations__ = None
108
-
109
- @classmethod
110
- def from_update(cls, update, bound_api):
111
- if not cls.__is_solved_annotations__:
112
- cls.__is_solved_annotations__ = True
113
- cls.__annotations__ = _get_class_annotations(cls)
114
-
115
- if cls.__cute_annotations__ is None:
116
- cls.__cute_annotations__ = _get_cute_annotations(cls.__annotations__)
117
-
118
- return cls(
119
- **{
120
- field: decoder.convert(
121
- cls.__cute_annotations__[field].from_update(_get_value(value), bound_api=bound_api),
122
- type=cls.__annotations__[field],
123
- )
124
- if field in cls.__cute_annotations__ and not isinstance(value, Nothing)
125
- else value
126
- for field, value in update.to_dict().items()
127
- },
128
- api=bound_api,
129
- )
130
-
131
- @property
132
- def ctx_api(self):
133
- return self.api
134
-
135
- def _to_dict(self, dct_name, exclude_fields, full):
136
- if dct_name not in self.__dict__:
137
- self.__dict__[dct_name] = (
138
- msgspec.structs.asdict(self)
139
- if not full
140
- else encoder.to_builtins(
141
- {
142
- k: field.to_dict(exclude_fields=exclude_fields)
143
- if isinstance(field := _get_value(getattr(self, k)), BaseCute)
144
- else field
145
- for k in self.__struct_fields__
146
- if k not in exclude_fields
147
- },
148
- order="deterministic",
149
- )
150
- )
151
-
152
- if not exclude_fields:
153
- return self.__dict__[dct_name]
154
-
155
- return {key: value for key, value in self.__dict__[dct_name].items() if key not in exclude_fields}
156
-
157
- def to_dict(self, *, exclude_fields=None):
158
- exclude_fields = exclude_fields or set()
159
- return self._to_dict("model_as_dict", exclude_fields={"api"} | exclude_fields, full=False)
160
-
161
- def to_full_dict(self, *, exclude_fields=None):
162
- exclude_fields = exclude_fields or set()
163
- return self._to_dict("model_as_full_dict", exclude_fields={"api"} | exclude_fields, full=True)
164
-
165
-
166
- def compose_method_params(
167
- params: dict[str, typing.Any],
168
- update: Cute,
169
- *,
170
- default_params: set[str | tuple[str, str]] | None = None,
171
- validators: dict[str, typing.Callable[[Cute], bool]] | None = None,
172
- ) -> dict[str, typing.Any]:
173
- """Compose method `params` from `update` by `default_params` and `validators`.
174
-
175
- :param params: Method params.
176
- :param update: Update object.
177
- :param default_params: Default params. \
178
- (`str`) - Attribute name to be get from `update` if param is undefined. \
179
- (`tuple[str, str]`): tuple[0] - Parameter name to be set in `params`, \
180
- tuple[1] - attribute name to be get from `update`.
181
- :param validators: Validators mapping (`str, Callable`), key - `Parameter name` \
182
- for which the validator will be applied, value - `Validator`, if returned `True` \
183
- parameter will be set, otherwise will not.
184
- :return: Composed params.
185
- """
186
-
187
- default_params = default_params or set()
188
- validators = validators or {}
189
-
190
- for param in default_params:
191
- param_name = param if isinstance(param, str) else param[0]
192
- if param_name not in params:
193
- if param_name in validators and not validators[param_name](update):
194
- continue
195
- params[param_name] = getattr(update, param if isinstance(param, str) else param[1])
196
-
197
- return params
198
-
199
-
200
- def shortcut(
201
- method_name: str,
202
- *,
203
- executor: Executor[Cute] | None = None,
204
- custom_params: set[str] | None = None,
205
- ):
206
- def wrapper(func: F) -> F:
207
- @wraps(func)
208
- async def inner(
209
- self: Cute,
210
- *args: typing.Any,
211
- **kwargs: typing.Any,
212
- ) -> typing.Any:
213
- if executor is None:
214
- return await func(self, *args, **kwargs)
215
-
216
- if not hasattr(func, "_signature_params"):
217
- setattr(
218
- func,
219
- "_signature_params",
220
- {k: p for k, p in inspect.signature(func).parameters.items() if k != "self"},
221
- )
222
-
223
- signature_params: dict[str, inspect.Parameter] = getattr(func, "_signature_params")
224
- params: dict[str, typing.Any] = {}
225
- index = 0
226
-
227
- for k, p in signature_params.items():
228
- if p.kind in (p.POSITIONAL_OR_KEYWORD, p.POSITIONAL_ONLY) and len(args) > index:
229
- params[k] = args[index]
230
- index += 1
231
- continue
232
-
233
- if p.kind in (p.VAR_KEYWORD, p.VAR_POSITIONAL):
234
- params[k] = kwargs.copy() if p.kind is p.VAR_KEYWORD else args[index:]
235
- continue
236
-
237
- params[k] = kwargs.pop(k, p.default) if p.default is not p.empty else kwargs.pop(k)
238
-
239
- return await executor(self, method_name, get_params(params))
240
-
241
- inner.__shortcut__ = Shortcut( # type: ignore
242
- method_name=method_name,
243
- executor=executor,
244
- custom_params=custom_params or set(),
245
- )
246
- return inner # type: ignore
247
-
248
- return wrapper
249
-
250
-
251
- @dataclasses.dataclass(slots=True, frozen=True)
252
- class Shortcut:
253
- method_name: str
254
- executor: Executor | None = dataclasses.field(default=None, kw_only=True)
255
- custom_params: set[str] = dataclasses.field(default_factory=lambda: set(), kw_only=True)
256
-
257
-
258
- __all__ = ("BaseCute", "Shortcut", "compose_method_params", "shortcut")
1
+ import typing
2
+
3
+ import msgspec
4
+
5
+ from telegrinder.api.api import API
6
+ from telegrinder.model import Model, is_none
7
+
8
+
9
+ def compose_method_params[Cute: BaseCute](
10
+ params: dict[str, typing.Any],
11
+ update: Cute,
12
+ *,
13
+ default_params: set[str | tuple[str, str]] | None = None,
14
+ validators: dict[str, typing.Callable[[Cute], bool]] | None = None,
15
+ ) -> dict[str, typing.Any]:
16
+ default_params = default_params or set()
17
+ validators = validators or {}
18
+
19
+ for param in default_params:
20
+ param_name = param if isinstance(param, str) else param[0]
21
+ if param_name not in params:
22
+ if param_name in validators and not validators[param_name](update):
23
+ continue
24
+ params[param_name] = getattr(update, param if isinstance(param, str) else param[1])
25
+
26
+ return params
27
+
28
+
29
+ if typing.TYPE_CHECKING:
30
+ from telegrinder.node.base import Node
31
+
32
+ class BaseCute[Update: Model](Model):
33
+ api: API
34
+
35
+ @classmethod
36
+ def as_node(cls) -> type[Node]: ...
37
+
38
+ @classmethod
39
+ def from_update(cls, update: Update, bound_api: API) -> typing.Self: ...
40
+
41
+ @property
42
+ def ctx_api(self) -> API: ...
43
+
44
+ def to_dict(
45
+ self,
46
+ *,
47
+ exclude_fields: set[str] | None = None,
48
+ ) -> dict[str, typing.Any]:
49
+ """:param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
50
+ :return: A dictionary representation of this cute model.
51
+ """
52
+ ...
53
+
54
+ def to_full_dict(
55
+ self,
56
+ *,
57
+ exclude_fields: set[str] | None = None,
58
+ ) -> dict[str, typing.Any]:
59
+ """:param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
60
+ :return: A dictionary representation of this model including all models, structs, custom types.
61
+ """
62
+ ...
63
+
64
+ else:
65
+ import msgspec
66
+ from fntypes.co import Nothing, Some, Variative
67
+
68
+ from telegrinder.msgspec_utils import Option, decoder, encoder, struct_as_dict
69
+ from telegrinder.msgspec_utils import get_class_annotations as _get_class_annotations
70
+
71
+ def _get_cute_from_generic(generic_args, /):
72
+ for arg in generic_args:
73
+ orig_arg = typing.get_origin(arg) or arg
74
+
75
+ if not isinstance(orig_arg, type):
76
+ continue
77
+ if orig_arg in (Variative, Some, Option):
78
+ return _get_cute_from_generic(typing.get_args(arg))
79
+ if issubclass(arg, BaseCute):
80
+ return arg
81
+
82
+ return None
83
+
84
+ def _get_cute_annotations(annotations, /):
85
+ cute_annotations = {}
86
+
87
+ for key, hint in annotations.items():
88
+ if not isinstance(hint, type):
89
+ if (cute := _get_cute_from_generic(typing.get_args(hint))) is not None:
90
+ cute_annotations[key] = cute
91
+
92
+ elif issubclass(hint, BaseCute):
93
+ cute_annotations[key] = hint
94
+
95
+ return cute_annotations
96
+
97
+ def _validate_value(value, /):
98
+ while isinstance(value, Variative | Some):
99
+ if isinstance(value, Variative):
100
+ value = value.v
101
+ if isinstance(value, Some):
102
+ value = value.value
103
+ return value
104
+
105
+ class BaseCute[Update]:
106
+ api: API
107
+
108
+ def __init_subclass__(cls, *args, **kwargs):
109
+ super().__init_subclass__(*args, **kwargs)
110
+
111
+ if not cls.__bases__ or not issubclass(cls.__bases__[0], BaseCute):
112
+ return
113
+
114
+ cls.__is_solved_annotations__ = False
115
+ cls.__cute_annotations__ = None
116
+ cls.__event_node__ = None
117
+
118
+ @classmethod
119
+ def as_node(cls):
120
+ if cls.__event_node__ is None:
121
+ from telegrinder.node.event import _EventNode
122
+
123
+ cls.__event_node__ = _EventNode[cls]
124
+ return cls.__event_node__
125
+
126
+ @classmethod
127
+ def from_update(cls, update, bound_api):
128
+ if not cls.__is_solved_annotations__:
129
+ cls.__is_solved_annotations__ = True
130
+ cls.__annotations__ = _get_class_annotations(cls)
131
+
132
+ if cls.__cute_annotations__ is None:
133
+ cls.__cute_annotations__ = _get_cute_annotations(cls.__annotations__)
134
+
135
+ return cls(
136
+ **{
137
+ field: decoder.convert(
138
+ cls.__cute_annotations__[field].from_update(_validate_value(value), bound_api=bound_api),
139
+ type=cls.__annotations__[field],
140
+ )
141
+ if field in cls.__cute_annotations__ and not isinstance(value, Nothing | msgspec.UnsetType)
142
+ else value
143
+ for field, value in update.to_dict().items()
144
+ },
145
+ api=bound_api,
146
+ )
147
+
148
+ @property
149
+ def ctx_api(self):
150
+ return self.api
151
+
152
+ def _to_dict(self, dct_name, exclude_fields, full):
153
+ if dct_name not in self.__dict__:
154
+ self.__dict__[dct_name] = (
155
+ struct_as_dict(self)
156
+ if not full
157
+ else encoder.to_builtins(
158
+ {
159
+ k: field.to_dict(exclude_fields=exclude_fields)
160
+ if isinstance(field, BaseCute)
161
+ else field
162
+ for k in self.__struct_fields__
163
+ if k not in exclude_fields
164
+ and not isinstance(field := _validate_value(getattr(self, k)), msgspec.UnsetType)
165
+ and not is_none(field)
166
+ },
167
+ order="deterministic",
168
+ )
169
+ )
170
+
171
+ if not exclude_fields:
172
+ return self.__dict__[dct_name]
173
+
174
+ return {key: value for key, value in self.__dict__[dct_name].items() if key not in exclude_fields}
175
+
176
+ def to_dict(self, *, exclude_fields=None, full=False):
177
+ exclude_fields = exclude_fields or set()
178
+ return self._to_dict("model_as_dict", exclude_fields={"api"} | exclude_fields, full=full)
179
+
180
+ def to_full_dict(self, *, exclude_fields=None):
181
+ return self.to_dict(exclude_fields=exclude_fields, full=True)
182
+
183
+
184
+ __all__ = ("BaseCute", "compose_method_params")