telegrinder 0.4.2__py3-none-any.whl → 0.5.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 (233) hide show
  1. telegrinder/__init__.py +37 -55
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +6 -4
  4. telegrinder/api/api.py +100 -26
  5. telegrinder/api/error.py +42 -8
  6. telegrinder/api/response.py +4 -1
  7. telegrinder/api/token.py +2 -2
  8. telegrinder/bot/__init__.py +9 -25
  9. telegrinder/bot/bot.py +31 -25
  10. telegrinder/bot/cute_types/__init__.py +0 -0
  11. telegrinder/bot/cute_types/base.py +103 -61
  12. telegrinder/bot/cute_types/callback_query.py +447 -400
  13. telegrinder/bot/cute_types/chat_join_request.py +59 -62
  14. telegrinder/bot/cute_types/chat_member_updated.py +154 -157
  15. telegrinder/bot/cute_types/inline_query.py +41 -44
  16. telegrinder/bot/cute_types/message.py +2621 -2590
  17. telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
  18. telegrinder/bot/cute_types/update.py +1 -8
  19. telegrinder/bot/cute_types/utils.py +1 -1
  20. telegrinder/bot/dispatch/__init__.py +10 -15
  21. telegrinder/bot/dispatch/abc.py +12 -11
  22. telegrinder/bot/dispatch/action.py +104 -0
  23. telegrinder/bot/dispatch/context.py +32 -26
  24. telegrinder/bot/dispatch/dispatch.py +61 -134
  25. telegrinder/bot/dispatch/handler/__init__.py +2 -0
  26. telegrinder/bot/dispatch/handler/abc.py +10 -8
  27. telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
  28. telegrinder/bot/dispatch/handler/base.py +10 -33
  29. telegrinder/bot/dispatch/handler/document_reply.py +2 -3
  30. telegrinder/bot/dispatch/handler/func.py +55 -87
  31. telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
  32. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  33. telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
  34. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
  35. telegrinder/bot/dispatch/handler/video_reply.py +2 -3
  36. telegrinder/bot/dispatch/middleware/__init__.py +0 -0
  37. telegrinder/bot/dispatch/middleware/abc.py +79 -55
  38. telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
  39. telegrinder/bot/dispatch/process.py +84 -105
  40. telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
  41. telegrinder/bot/dispatch/return_manager/abc.py +102 -65
  42. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
  43. telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
  44. telegrinder/bot/dispatch/return_manager/message.py +8 -10
  45. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
  46. telegrinder/bot/dispatch/view/__init__.py +4 -4
  47. telegrinder/bot/dispatch/view/abc.py +6 -16
  48. telegrinder/bot/dispatch/view/base.py +54 -178
  49. telegrinder/bot/dispatch/view/box.py +19 -18
  50. telegrinder/bot/dispatch/view/callback_query.py +4 -8
  51. telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
  52. telegrinder/bot/dispatch/view/chat_member.py +5 -25
  53. telegrinder/bot/dispatch/view/error.py +9 -0
  54. telegrinder/bot/dispatch/view/inline_query.py +4 -8
  55. telegrinder/bot/dispatch/view/message.py +5 -25
  56. telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
  57. telegrinder/bot/dispatch/view/raw.py +3 -109
  58. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
  59. telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
  60. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
  61. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
  62. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
  63. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  64. telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
  65. telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
  66. telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
  67. telegrinder/bot/polling/__init__.py +0 -0
  68. telegrinder/bot/polling/abc.py +0 -0
  69. telegrinder/bot/polling/polling.py +209 -88
  70. telegrinder/bot/rules/__init__.py +3 -16
  71. telegrinder/bot/rules/abc.py +42 -122
  72. telegrinder/bot/rules/callback_data.py +29 -49
  73. telegrinder/bot/rules/chat_join.py +5 -23
  74. telegrinder/bot/rules/command.py +8 -4
  75. telegrinder/bot/rules/enum_text.py +3 -4
  76. telegrinder/bot/rules/func.py +7 -14
  77. telegrinder/bot/rules/fuzzy.py +3 -4
  78. telegrinder/bot/rules/inline.py +8 -20
  79. telegrinder/bot/rules/integer.py +2 -3
  80. telegrinder/bot/rules/is_from.py +12 -11
  81. telegrinder/bot/rules/logic.py +11 -5
  82. telegrinder/bot/rules/markup.py +22 -14
  83. telegrinder/bot/rules/mention.py +8 -7
  84. telegrinder/bot/rules/message_entities.py +8 -4
  85. telegrinder/bot/rules/node.py +23 -12
  86. telegrinder/bot/rules/payload.py +5 -4
  87. telegrinder/bot/rules/payment_invoice.py +6 -21
  88. telegrinder/bot/rules/regex.py +2 -4
  89. telegrinder/bot/rules/rule_enum.py +8 -7
  90. telegrinder/bot/rules/start.py +5 -6
  91. telegrinder/bot/rules/state.py +1 -1
  92. telegrinder/bot/rules/text.py +4 -15
  93. telegrinder/bot/rules/update.py +3 -4
  94. telegrinder/bot/scenario/__init__.py +0 -0
  95. telegrinder/bot/scenario/abc.py +6 -5
  96. telegrinder/bot/scenario/checkbox.py +1 -1
  97. telegrinder/bot/scenario/choice.py +30 -39
  98. telegrinder/client/__init__.py +3 -5
  99. telegrinder/client/abc.py +11 -6
  100. telegrinder/client/aiohttp.py +141 -27
  101. telegrinder/client/form_data.py +1 -1
  102. telegrinder/model.py +61 -89
  103. telegrinder/modules.py +325 -102
  104. telegrinder/msgspec_utils/__init__.py +40 -0
  105. telegrinder/msgspec_utils/abc.py +18 -0
  106. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  107. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  108. telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
  109. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  110. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  111. telegrinder/msgspec_utils/decoder.py +389 -0
  112. telegrinder/msgspec_utils/encoder.py +206 -0
  113. telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
  114. telegrinder/msgspec_utils/tools.py +75 -0
  115. telegrinder/node/__init__.py +24 -7
  116. telegrinder/node/attachment.py +1 -0
  117. telegrinder/node/base.py +154 -72
  118. telegrinder/node/callback_query.py +5 -5
  119. telegrinder/node/collection.py +39 -0
  120. telegrinder/node/command.py +1 -2
  121. telegrinder/node/composer.py +121 -72
  122. telegrinder/node/container.py +11 -8
  123. telegrinder/node/context.py +48 -0
  124. telegrinder/node/either.py +27 -40
  125. telegrinder/node/error.py +41 -0
  126. telegrinder/node/event.py +37 -11
  127. telegrinder/node/exceptions.py +7 -0
  128. telegrinder/node/file.py +0 -0
  129. telegrinder/node/i18n.py +108 -0
  130. telegrinder/node/me.py +3 -2
  131. telegrinder/node/payload.py +1 -1
  132. telegrinder/node/polymorphic.py +63 -28
  133. telegrinder/node/reply_message.py +12 -0
  134. telegrinder/node/rule.py +6 -13
  135. telegrinder/node/scope.py +14 -5
  136. telegrinder/node/session.py +53 -0
  137. telegrinder/node/source.py +41 -9
  138. telegrinder/node/text.py +1 -2
  139. telegrinder/node/tools/__init__.py +0 -0
  140. telegrinder/node/tools/generator.py +3 -5
  141. telegrinder/node/utility.py +16 -0
  142. telegrinder/py.typed +0 -0
  143. telegrinder/rules.py +0 -0
  144. telegrinder/tools/__init__.py +48 -88
  145. telegrinder/tools/aio.py +103 -0
  146. telegrinder/tools/callback_data_serialization/__init__.py +5 -0
  147. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
  148. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
  149. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
  150. telegrinder/tools/final.py +21 -0
  151. telegrinder/tools/formatting/__init__.py +2 -18
  152. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  153. telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
  154. telegrinder/tools/formatting/deep_links/parsing.py +90 -0
  155. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  156. telegrinder/tools/formatting/html_formatter.py +18 -45
  157. telegrinder/tools/fullname.py +83 -0
  158. telegrinder/tools/global_context/__init__.py +4 -3
  159. telegrinder/tools/global_context/abc.py +17 -14
  160. telegrinder/tools/global_context/builtin_context.py +39 -0
  161. telegrinder/tools/global_context/global_context.py +138 -39
  162. telegrinder/tools/input_file_directory.py +0 -0
  163. telegrinder/tools/keyboard/__init__.py +39 -0
  164. telegrinder/tools/keyboard/abc.py +159 -0
  165. telegrinder/tools/keyboard/base.py +77 -0
  166. telegrinder/tools/keyboard/buttons/__init__.py +14 -0
  167. telegrinder/tools/keyboard/buttons/base.py +18 -0
  168. telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
  169. telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
  170. telegrinder/tools/keyboard/buttons/tools.py +18 -0
  171. telegrinder/tools/keyboard/data.py +20 -0
  172. telegrinder/tools/keyboard/keyboard.py +131 -0
  173. telegrinder/tools/keyboard/static_keyboard.py +83 -0
  174. telegrinder/tools/lifespan.py +87 -51
  175. telegrinder/tools/limited_dict.py +4 -1
  176. telegrinder/tools/loop_wrapper.py +332 -0
  177. telegrinder/tools/magic/__init__.py +32 -0
  178. telegrinder/tools/magic/annotations.py +165 -0
  179. telegrinder/tools/magic/dictionary.py +20 -0
  180. telegrinder/tools/magic/function.py +246 -0
  181. telegrinder/tools/magic/shortcut.py +111 -0
  182. telegrinder/tools/parse_mode.py +9 -3
  183. telegrinder/tools/singleton/__init__.py +4 -0
  184. telegrinder/tools/singleton/abc.py +14 -0
  185. telegrinder/tools/singleton/singleton.py +18 -0
  186. telegrinder/tools/state_storage/__init__.py +0 -0
  187. telegrinder/tools/state_storage/abc.py +6 -1
  188. telegrinder/tools/state_storage/memory.py +1 -1
  189. telegrinder/tools/strings.py +0 -0
  190. telegrinder/types/__init__.py +307 -268
  191. telegrinder/types/enums.py +64 -37
  192. telegrinder/types/input_file.py +3 -3
  193. telegrinder/types/methods.py +5699 -5055
  194. telegrinder/types/methods_utils.py +62 -0
  195. telegrinder/types/objects.py +7846 -7058
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.0.dist-info/METADATA +162 -0
  198. telegrinder-0.5.0.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.dist-info}/licenses/LICENSE +2 -2
  200. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
  201. telegrinder/bot/rules/id.py +0 -24
  202. telegrinder/bot/rules/message.py +0 -15
  203. telegrinder/client/sonic.py +0 -212
  204. telegrinder/msgspec_utils.py +0 -478
  205. telegrinder/tools/adapter/__init__.py +0 -19
  206. telegrinder/tools/adapter/abc.py +0 -49
  207. telegrinder/tools/adapter/dataclass.py +0 -56
  208. telegrinder/tools/adapter/errors.py +0 -5
  209. telegrinder/tools/adapter/event.py +0 -61
  210. telegrinder/tools/adapter/node.py +0 -46
  211. telegrinder/tools/adapter/raw_event.py +0 -27
  212. telegrinder/tools/adapter/raw_update.py +0 -30
  213. telegrinder/tools/callback_data_serilization/__init__.py +0 -5
  214. telegrinder/tools/error_handler/__init__.py +0 -10
  215. telegrinder/tools/error_handler/abc.py +0 -30
  216. telegrinder/tools/error_handler/error.py +0 -9
  217. telegrinder/tools/error_handler/error_handler.py +0 -179
  218. telegrinder/tools/formatting/spec_html_formats.py +0 -75
  219. telegrinder/tools/functional.py +0 -8
  220. telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
  221. telegrinder/tools/i18n/__init__.py +0 -12
  222. telegrinder/tools/i18n/abc.py +0 -32
  223. telegrinder/tools/i18n/middleware/__init__.py +0 -3
  224. telegrinder/tools/i18n/middleware/abc.py +0 -22
  225. telegrinder/tools/i18n/simple.py +0 -43
  226. telegrinder/tools/keyboard.py +0 -132
  227. telegrinder/tools/loop_wrapper/__init__.py +0 -4
  228. telegrinder/tools/loop_wrapper/abc.py +0 -20
  229. telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
  230. telegrinder/tools/magic.py +0 -344
  231. telegrinder-0.4.2.dist-info/METADATA +0 -151
  232. telegrinder-0.4.2.dist-info/RECORD +0 -182
  233. {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,246 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import enum
5
+ import inspect
6
+ import types
7
+ import typing
8
+ from functools import cached_property, wraps
9
+
10
+ from telegrinder.tools.magic.annotations import Annotations
11
+
12
+ type Function[**P, R] = typing.Callable[P, R]
13
+ type AnyFunction = Function[..., typing.Any]
14
+
15
+
16
+ def _to_str(obj: typing.Any, /) -> str:
17
+ if isinstance(obj, enum.Enum):
18
+ return str(obj.value)
19
+ return str(obj) if not isinstance(obj, str) else obj
20
+
21
+
22
+ def _resolve_arg_names(
23
+ func: AnyFunction,
24
+ /,
25
+ *,
26
+ start_idx: int,
27
+ stop_idx: int,
28
+ exclude: set[str] | None = None,
29
+ ) -> tuple[str, ...]:
30
+ exclude = exclude or set()
31
+ varnames = func.__code__.co_varnames[start_idx:stop_idx]
32
+ return tuple(name for name in varnames if name not in exclude)
33
+
34
+
35
+ class FunctionParameters(typing.TypedDict):
36
+ args: list[tuple[str, typing.Any | inspect.Parameter.empty]]
37
+ kwargs: list[tuple[str, typing.Any | inspect.Parameter.empty]]
38
+ var_star_args: typing.NotRequired[str]
39
+ var_star_kwargs: typing.NotRequired[str]
40
+
41
+
42
+ @dataclasses.dataclass(frozen=True, repr=False)
43
+ class Bundle[R]:
44
+ function: Function[..., R]
45
+ start_idx: int
46
+ context: types.MappingProxyType[str, typing.Any]
47
+
48
+ @cached_property
49
+ def args(self) -> tuple[typing.Any, ...]:
50
+ return tuple(
51
+ self.context[name]
52
+ for name in resolve_posonly_arg_names(self.function, start_idx=self.start_idx)
53
+ if name in self.context
54
+ )
55
+
56
+ @cached_property
57
+ def kwargs(self) -> dict[str, typing.Any]:
58
+ posonly_arg_names = resolve_posonly_arg_names(self.function, start_idx=self.start_idx)
59
+ return {name: value for name, value in self.context.items() if name not in posonly_arg_names}
60
+
61
+ @classmethod
62
+ def from_context(
63
+ cls,
64
+ function: Function[..., R],
65
+ context: typing.Mapping[str, typing.Any],
66
+ /,
67
+ *,
68
+ start_idx: int = 1,
69
+ ) -> typing.Self:
70
+ return cls(function, start_idx, types.MappingProxyType(context))
71
+
72
+ def __repr__(self) -> str:
73
+ return "<{}: function={!r} bundle args={!r} kwargs={!r}>".format(
74
+ type(self).__name__,
75
+ self.function,
76
+ self.args,
77
+ self.kwargs,
78
+ )
79
+
80
+ def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> R:
81
+ return self.function(*args, *self.args, **kwargs, **self.kwargs)
82
+
83
+ def __and__(self, other: object, /) -> typing.Self:
84
+ if not isinstance(other, Bundle):
85
+ return NotImplemented
86
+
87
+ if self.function != other.function:
88
+ raise ValueError(
89
+ f"Cannot merge contexts because bundle {other!r} is intended for a different function.",
90
+ )
91
+
92
+ return dataclasses.replace(
93
+ self,
94
+ context=types.MappingProxyType(mapping=self.context | other.context),
95
+ )
96
+
97
+ def __iand__(self, other: object, /) -> typing.Self:
98
+ return self.__and__(other)
99
+
100
+
101
+ def function_context[**P, R](key: str, /) -> Function[[Function[P, R]], Function[P, R]]:
102
+ @lambda wrapper: typing.cast("Function[[Function[P, R]], Function[P, R]]", wrapper)
103
+ def wrapper(func: Function[typing.Concatenate[AnyFunction, P], R], /) -> AnyFunction:
104
+ @wraps(func)
105
+ def inner(passed_function: AnyFunction, /, *args: P.args, **kwargs: P.kwargs) -> R:
106
+ sentinel = object()
107
+ context: dict[str, typing.Any] = passed_function.__dict__.setdefault("__function_context__", {})
108
+
109
+ if (value := context.get(key, sentinel)) is not sentinel:
110
+ return value
111
+
112
+ context[key] = result = func(passed_function, *args, **kwargs)
113
+ return result
114
+
115
+ return inner
116
+
117
+ return wrapper
118
+
119
+
120
+ def resolve_arg_names(
121
+ func: AnyFunction,
122
+ /,
123
+ *,
124
+ start_idx: int = 1,
125
+ exclude: set[str] | None = None,
126
+ ) -> tuple[str, ...]:
127
+ return _resolve_arg_names(
128
+ func,
129
+ start_idx=start_idx,
130
+ stop_idx=func.__code__.co_argcount + func.__code__.co_kwonlyargcount,
131
+ exclude=exclude,
132
+ )
133
+
134
+
135
+ def resolve_kwonly_arg_names(
136
+ func: AnyFunction,
137
+ /,
138
+ *,
139
+ start_idx: int = 1,
140
+ exclude: set[str] | None = None,
141
+ ) -> tuple[str, ...]:
142
+ return _resolve_arg_names(
143
+ func,
144
+ start_idx=func.__code__.co_argcount + start_idx,
145
+ stop_idx=func.__code__.co_argcount + func.__code__.co_kwonlyargcount,
146
+ exclude=exclude,
147
+ )
148
+
149
+
150
+ def resolve_posonly_arg_names(
151
+ func: AnyFunction,
152
+ /,
153
+ *,
154
+ start_idx: int = 1,
155
+ exclude: set[str] | None = None,
156
+ ) -> tuple[str, ...]:
157
+ return _resolve_arg_names(
158
+ func,
159
+ start_idx=start_idx,
160
+ stop_idx=func.__code__.co_posonlyargcount,
161
+ exclude=exclude,
162
+ )
163
+
164
+
165
+ @function_context("default_arguments")
166
+ def get_default_args(func: AnyFunction, /) -> dict[str, typing.Any]:
167
+ parameters = get_func_parameters(func)
168
+ return {
169
+ key: value
170
+ for sublist in (parameters["args"], parameters["kwargs"])
171
+ for key, value in sublist
172
+ if value is not inspect.Parameter.empty
173
+ }
174
+
175
+
176
+ @function_context("signature_parameters")
177
+ def get_func_parameters(func: AnyFunction, /) -> FunctionParameters:
178
+ func_params = FunctionParameters(args=[], kwargs=[])
179
+
180
+ for k, p in inspect.signature(func).parameters.items():
181
+ if k in ("self", "cls"):
182
+ continue
183
+
184
+ match p.kind:
185
+ case p.POSITIONAL_OR_KEYWORD | p.POSITIONAL_ONLY:
186
+ func_params["args"].append((k, p.default))
187
+ case p.KEYWORD_ONLY:
188
+ func_params["kwargs"].append((k, p.default))
189
+ case p.VAR_POSITIONAL:
190
+ func_params["var_star_args"] = k
191
+ case p.VAR_KEYWORD:
192
+ func_params["var_star_kwargs"] = k
193
+
194
+ return func_params
195
+
196
+
197
+ @function_context("resolved_annotations")
198
+ def get_func_annotations(func: AnyFunction, /) -> typing.Mapping[str, typing.Any]:
199
+ return Annotations(obj=func).get(
200
+ ignore_failed_evals=True,
201
+ exclude_forward_refs=True,
202
+ allow_return_type=False,
203
+ cache=False,
204
+ )
205
+
206
+
207
+ def bundle[R](
208
+ function: Function[..., R],
209
+ context: dict[typing.Any, typing.Any],
210
+ /,
211
+ *,
212
+ start_idx: int = 1,
213
+ bundle_kwargs: bool = False,
214
+ typebundle: bool = False,
215
+ omit_defaults: bool = False,
216
+ ) -> Bundle[R]:
217
+ if typebundle:
218
+ return Bundle.from_context(
219
+ function,
220
+ {name: context[ann] for name, ann in get_func_annotations(function).items() if ann in context},
221
+ start_idx=start_idx,
222
+ )
223
+
224
+ kwargs = {_to_str(k): v for k, v in context.items()}
225
+ if not bundle_kwargs or "var_star_kwargs" not in get_func_parameters(function):
226
+ names = resolve_arg_names(function, start_idx=start_idx, exclude={"cls", "self"})
227
+ kwargs = {k: v for k, v in kwargs.items() if k in names}
228
+
229
+ return Bundle.from_context(
230
+ function,
231
+ kwargs if omit_defaults else get_default_args(function) | kwargs,
232
+ start_idx=start_idx,
233
+ )
234
+
235
+
236
+ __all__ = (
237
+ "Bundle",
238
+ "bundle",
239
+ "function_context",
240
+ "get_default_args",
241
+ "get_func_annotations",
242
+ "get_func_parameters",
243
+ "resolve_arg_names",
244
+ "resolve_kwonly_arg_names",
245
+ "resolve_posonly_arg_names",
246
+ )
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import inspect
5
+ import typing
6
+ from collections import OrderedDict
7
+ from functools import wraps
8
+
9
+ from fntypes.result import Result
10
+
11
+ from telegrinder.tools.magic.function import get_func_parameters
12
+ from telegrinder.types.methods_utils import get_params
13
+
14
+ type Executor[T] = typing.Callable[
15
+ [T, str, dict[str, typing.Any]],
16
+ typing.Awaitable[Result[typing.Any, APIError]],
17
+ ]
18
+ type CuteMethod[T, **P, R] = typing.Callable[
19
+ typing.Concatenate[T, P],
20
+ typing.Awaitable[Result[R, APIError]],
21
+ ]
22
+
23
+ if typing.TYPE_CHECKING:
24
+ from telegrinder.api.error import APIError
25
+
26
+
27
+ class ShortcutMethod[T, **P, R](typing.Protocol):
28
+ __name__: str
29
+ __shortcut__: Shortcut[T]
30
+
31
+ async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Result[R, APIError]: ...
32
+
33
+
34
+ @dataclasses.dataclass(slots=True, frozen=True)
35
+ class Shortcut[T]:
36
+ method_name: str
37
+ executor: Executor[T] | None = dataclasses.field(default=None, kw_only=True)
38
+ custom_params: set[str] = dataclasses.field(default_factory=lambda: set[str](), kw_only=True)
39
+
40
+
41
+ @typing.overload
42
+ def shortcut[T, **P, R](
43
+ method_name: str,
44
+ /,
45
+ *,
46
+ custom_params: set[str] = ...,
47
+ ) -> typing.Callable[[CuteMethod[T, P, R]], ShortcutMethod[T, P, R]]: ...
48
+
49
+
50
+ @typing.overload
51
+ def shortcut[T, **P, R](
52
+ method_name: str,
53
+ /,
54
+ *,
55
+ executor: Executor[T],
56
+ custom_params: set[str] = ...,
57
+ ) -> typing.Callable[[CuteMethod[T, P, R]], ShortcutMethod[T, P, R]]: ...
58
+
59
+
60
+ def shortcut[T, **P, R](
61
+ method_name: str,
62
+ /,
63
+ *,
64
+ executor: Executor[T] | None = None,
65
+ custom_params: set[str] | None = None,
66
+ ) -> typing.Callable[[CuteMethod[T, P, R]], ShortcutMethod[T, P, R]]:
67
+ """Decorate a cute method as a shortcut."""
68
+
69
+ def wrapper(func: CuteMethod[T, P, R]) -> ShortcutMethod[T, P, R]:
70
+ @wraps(func)
71
+ async def inner(
72
+ self: T,
73
+ *args: P.args,
74
+ **kwargs: P.kwargs,
75
+ ) -> Result[R, typing.Any]:
76
+ if executor is None:
77
+ return await func(self, *args, **kwargs)
78
+
79
+ params: dict[str, typing.Any] = OrderedDict()
80
+ func_params = get_func_parameters(func)
81
+
82
+ for index, (arg, default) in enumerate(func_params["args"]):
83
+ if len(args) > index:
84
+ params[arg] = args[index]
85
+ elif default is not inspect.Parameter.empty:
86
+ params[arg] = default
87
+
88
+ if var_args := func_params.get("var_star_args"):
89
+ params[var_args] = args[len(func_params["args"]) :]
90
+
91
+ for kwarg, default in func_params["kwargs"]:
92
+ params[kwarg] = (
93
+ kwargs.pop(kwarg, default) if default is not inspect.Parameter.empty else kwargs.pop(kwarg)
94
+ )
95
+
96
+ if var_kwargs := func_params.get("var_star_kwargs"):
97
+ params[var_kwargs] = kwargs.copy()
98
+
99
+ return await executor(self, method_name, get_params(params))
100
+
101
+ inner.__shortcut__ = Shortcut( # type: ignore
102
+ method_name=method_name,
103
+ executor=executor,
104
+ custom_params=custom_params or set(),
105
+ )
106
+ return inner # type: ignore
107
+
108
+ return wrapper
109
+
110
+
111
+ __all__ = ("Shortcut", "shortcut")
@@ -1,6 +1,12 @@
1
- class ParseMode:
2
- MARKDOWNV2 = "MarkdownV2"
3
- HTML = "HTML"
1
+ import typing
2
+
3
+ from telegrinder.tools.final import Final
4
+
5
+
6
+ @typing.final
7
+ class ParseMode(Final):
8
+ MARKDOWNV2: typing.Final[str] = "MarkdownV2"
9
+ HTML: typing.Final[str] = "HTML"
4
10
 
5
11
 
6
12
  __all__ = ("ParseMode",)
@@ -0,0 +1,4 @@
1
+ from telegrinder.tools.singleton.abc import ABCSingleton, ABCSingletonMeta
2
+ from telegrinder.tools.singleton.singleton import Singleton, SingletonMeta
3
+
4
+ __all__ = ("ABCSingleton", "ABCSingletonMeta", "Singleton", "SingletonMeta")
@@ -0,0 +1,14 @@
1
+ import abc
2
+
3
+ from telegrinder.tools.singleton.singleton import SingletonMeta
4
+
5
+
6
+ class ABCSingletonMeta(abc.ABCMeta, SingletonMeta):
7
+ pass
8
+
9
+
10
+ class ABCSingleton(metaclass=ABCSingletonMeta):
11
+ pass
12
+
13
+
14
+ __all__ = ("ABCSingleton", "ABCSingletonMeta")
@@ -0,0 +1,18 @@
1
+ import typing
2
+
3
+
4
+ class SingletonMeta(type):
5
+ if not typing.TYPE_CHECKING:
6
+ __instance = None
7
+
8
+ def __call__(cls, *args, **kwargs):
9
+ if cls.__instance is None:
10
+ cls.__instance = super().__call__(*args, **kwargs)
11
+ return cls.__instance
12
+
13
+
14
+ class Singleton(metaclass=SingletonMeta):
15
+ pass
16
+
17
+
18
+ __all__ = ("Singleton", "SingletonMeta")
File without changes
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import abc
2
4
  import dataclasses
3
5
  import enum
@@ -7,11 +9,14 @@ from fntypes.option import Option
7
9
  from telegrinder.bot.rules.state import State, StateMeta
8
10
 
9
11
 
10
- @dataclasses.dataclass(frozen=True, slots=True)
12
+ @dataclasses.dataclass
11
13
  class StateData[Payload]:
12
14
  key: str | enum.Enum
13
15
  payload: Payload
14
16
 
17
+ async def save(self, user_id: int, storage: ABCStateStorage[Payload]) -> None:
18
+ await storage.set(user_id, key=self.key, payload=self.payload)
19
+
15
20
 
16
21
  class ABCStateStorage[Payload](abc.ABC):
17
22
  @abc.abstractmethod
@@ -1,8 +1,8 @@
1
1
  import typing
2
2
 
3
+ from fntypes.misc import from_optional
3
4
  from fntypes.option import Option
4
5
 
5
- from telegrinder.tools.functional import from_optional
6
6
  from telegrinder.tools.state_storage.abc import ABCStateStorage, StateData
7
7
 
8
8
  type Payload = dict[str, typing.Any]
File without changes