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