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
@@ -1,258 +1,258 @@
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 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")