telegrinder 1.0.0rc1__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.
Files changed (215) hide show
  1. telegrinder/__init__.py +258 -0
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +15 -0
  4. telegrinder/api/api.py +175 -0
  5. telegrinder/api/error.py +50 -0
  6. telegrinder/api/response.py +23 -0
  7. telegrinder/api/token.py +30 -0
  8. telegrinder/api/validators.py +30 -0
  9. telegrinder/bot/__init__.py +144 -0
  10. telegrinder/bot/bot.py +70 -0
  11. telegrinder/bot/cute_types/__init__.py +41 -0
  12. telegrinder/bot/cute_types/base.py +228 -0
  13. telegrinder/bot/cute_types/base.pyi +49 -0
  14. telegrinder/bot/cute_types/business_connection.py +9 -0
  15. telegrinder/bot/cute_types/business_messages_deleted.py +9 -0
  16. telegrinder/bot/cute_types/callback_query.py +248 -0
  17. telegrinder/bot/cute_types/chat_boost_removed.py +9 -0
  18. telegrinder/bot/cute_types/chat_boost_updated.py +9 -0
  19. telegrinder/bot/cute_types/chat_join_request.py +59 -0
  20. telegrinder/bot/cute_types/chat_member_updated.py +158 -0
  21. telegrinder/bot/cute_types/chosen_inline_result.py +11 -0
  22. telegrinder/bot/cute_types/inline_query.py +41 -0
  23. telegrinder/bot/cute_types/message.py +2809 -0
  24. telegrinder/bot/cute_types/message_reaction_count_updated.py +9 -0
  25. telegrinder/bot/cute_types/message_reaction_updated.py +9 -0
  26. telegrinder/bot/cute_types/paid_media_purchased.py +11 -0
  27. telegrinder/bot/cute_types/poll.py +9 -0
  28. telegrinder/bot/cute_types/poll_answer.py +9 -0
  29. telegrinder/bot/cute_types/pre_checkout_query.py +36 -0
  30. telegrinder/bot/cute_types/shipping_query.py +11 -0
  31. telegrinder/bot/cute_types/update.py +209 -0
  32. telegrinder/bot/cute_types/utils.py +141 -0
  33. telegrinder/bot/dispatch/__init__.py +99 -0
  34. telegrinder/bot/dispatch/abc.py +74 -0
  35. telegrinder/bot/dispatch/action.py +99 -0
  36. telegrinder/bot/dispatch/context.py +162 -0
  37. telegrinder/bot/dispatch/dispatch.py +362 -0
  38. telegrinder/bot/dispatch/handler/__init__.py +23 -0
  39. telegrinder/bot/dispatch/handler/abc.py +25 -0
  40. telegrinder/bot/dispatch/handler/audio_reply.py +43 -0
  41. telegrinder/bot/dispatch/handler/base.py +34 -0
  42. telegrinder/bot/dispatch/handler/document_reply.py +43 -0
  43. telegrinder/bot/dispatch/handler/func.py +73 -0
  44. telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
  45. telegrinder/bot/dispatch/handler/message_reply.py +35 -0
  46. telegrinder/bot/dispatch/handler/photo_reply.py +43 -0
  47. telegrinder/bot/dispatch/handler/sticker_reply.py +36 -0
  48. telegrinder/bot/dispatch/handler/video_reply.py +43 -0
  49. telegrinder/bot/dispatch/middleware/__init__.py +13 -0
  50. telegrinder/bot/dispatch/middleware/abc.py +112 -0
  51. telegrinder/bot/dispatch/middleware/box.py +32 -0
  52. telegrinder/bot/dispatch/middleware/filter.py +88 -0
  53. telegrinder/bot/dispatch/middleware/media_group.py +69 -0
  54. telegrinder/bot/dispatch/process.py +93 -0
  55. telegrinder/bot/dispatch/return_manager/__init__.py +21 -0
  56. telegrinder/bot/dispatch/return_manager/abc.py +107 -0
  57. telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
  58. telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
  59. telegrinder/bot/dispatch/return_manager/message.py +34 -0
  60. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +19 -0
  61. telegrinder/bot/dispatch/return_manager/utils.py +20 -0
  62. telegrinder/bot/dispatch/router/__init__.py +4 -0
  63. telegrinder/bot/dispatch/router/abc.py +15 -0
  64. telegrinder/bot/dispatch/router/base.py +154 -0
  65. telegrinder/bot/dispatch/view/__init__.py +15 -0
  66. telegrinder/bot/dispatch/view/abc.py +15 -0
  67. telegrinder/bot/dispatch/view/base.py +226 -0
  68. telegrinder/bot/dispatch/view/box.py +207 -0
  69. telegrinder/bot/dispatch/view/media_group.py +25 -0
  70. telegrinder/bot/dispatch/waiter_machine/__init__.py +25 -0
  71. telegrinder/bot/dispatch/waiter_machine/actions.py +16 -0
  72. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +13 -0
  73. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +53 -0
  74. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +61 -0
  75. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +49 -0
  76. telegrinder/bot/dispatch/waiter_machine/machine.py +264 -0
  77. telegrinder/bot/dispatch/waiter_machine/middleware.py +77 -0
  78. telegrinder/bot/dispatch/waiter_machine/short_state.py +105 -0
  79. telegrinder/bot/polling/__init__.py +4 -0
  80. telegrinder/bot/polling/abc.py +25 -0
  81. telegrinder/bot/polling/error_handler.py +93 -0
  82. telegrinder/bot/polling/polling.py +167 -0
  83. telegrinder/bot/polling/utils.py +12 -0
  84. telegrinder/bot/rules/__init__.py +166 -0
  85. telegrinder/bot/rules/abc.py +150 -0
  86. telegrinder/bot/rules/button.py +20 -0
  87. telegrinder/bot/rules/callback_data.py +109 -0
  88. telegrinder/bot/rules/chat_join.py +28 -0
  89. telegrinder/bot/rules/chat_member_updated.py +145 -0
  90. telegrinder/bot/rules/command.py +137 -0
  91. telegrinder/bot/rules/enum_text.py +29 -0
  92. telegrinder/bot/rules/func.py +21 -0
  93. telegrinder/bot/rules/fuzzy.py +21 -0
  94. telegrinder/bot/rules/inline.py +45 -0
  95. telegrinder/bot/rules/integer.py +19 -0
  96. telegrinder/bot/rules/is_from.py +213 -0
  97. telegrinder/bot/rules/logic.py +22 -0
  98. telegrinder/bot/rules/magic.py +60 -0
  99. telegrinder/bot/rules/markup.py +51 -0
  100. telegrinder/bot/rules/media.py +13 -0
  101. telegrinder/bot/rules/mention.py +15 -0
  102. telegrinder/bot/rules/message_entities.py +37 -0
  103. telegrinder/bot/rules/node.py +43 -0
  104. telegrinder/bot/rules/payload.py +89 -0
  105. telegrinder/bot/rules/payment_invoice.py +14 -0
  106. telegrinder/bot/rules/regex.py +34 -0
  107. telegrinder/bot/rules/rule_enum.py +71 -0
  108. telegrinder/bot/rules/start.py +73 -0
  109. telegrinder/bot/rules/state.py +35 -0
  110. telegrinder/bot/rules/text.py +27 -0
  111. telegrinder/bot/rules/update.py +14 -0
  112. telegrinder/bot/scenario/__init__.py +5 -0
  113. telegrinder/bot/scenario/abc.py +16 -0
  114. telegrinder/bot/scenario/checkbox.py +183 -0
  115. telegrinder/bot/scenario/choice.py +44 -0
  116. telegrinder/client/__init__.py +11 -0
  117. telegrinder/client/abc.py +136 -0
  118. telegrinder/client/form_data.py +34 -0
  119. telegrinder/client/rnet.py +198 -0
  120. telegrinder/model.py +133 -0
  121. telegrinder/model.pyi +57 -0
  122. telegrinder/modules.py +1081 -0
  123. telegrinder/msgspec_utils/__init__.py +42 -0
  124. telegrinder/msgspec_utils/abc.py +16 -0
  125. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  126. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  127. telegrinder/msgspec_utils/custom_types/enum_meta.py +61 -0
  128. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  129. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  130. telegrinder/msgspec_utils/decoder.py +388 -0
  131. telegrinder/msgspec_utils/encoder.py +204 -0
  132. telegrinder/msgspec_utils/json.py +15 -0
  133. telegrinder/msgspec_utils/tools.py +80 -0
  134. telegrinder/node/__init__.py +80 -0
  135. telegrinder/node/compose.py +193 -0
  136. telegrinder/node/nodes/__init__.py +96 -0
  137. telegrinder/node/nodes/attachment.py +169 -0
  138. telegrinder/node/nodes/callback_query.py +25 -0
  139. telegrinder/node/nodes/channel.py +97 -0
  140. telegrinder/node/nodes/command.py +33 -0
  141. telegrinder/node/nodes/error.py +43 -0
  142. telegrinder/node/nodes/event.py +70 -0
  143. telegrinder/node/nodes/file.py +39 -0
  144. telegrinder/node/nodes/global_node.py +66 -0
  145. telegrinder/node/nodes/i18n.py +110 -0
  146. telegrinder/node/nodes/me.py +26 -0
  147. telegrinder/node/nodes/message_entities.py +15 -0
  148. telegrinder/node/nodes/payload.py +84 -0
  149. telegrinder/node/nodes/reply_message.py +14 -0
  150. telegrinder/node/nodes/source.py +172 -0
  151. telegrinder/node/nodes/state_mutator.py +71 -0
  152. telegrinder/node/nodes/text.py +62 -0
  153. telegrinder/node/scope.py +88 -0
  154. telegrinder/node/utils.py +38 -0
  155. telegrinder/py.typed +0 -0
  156. telegrinder/rules.py +1 -0
  157. telegrinder/tools/__init__.py +183 -0
  158. telegrinder/tools/aio.py +147 -0
  159. telegrinder/tools/final.py +21 -0
  160. telegrinder/tools/formatting/__init__.py +85 -0
  161. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  162. telegrinder/tools/formatting/deep_links/links.py +468 -0
  163. telegrinder/tools/formatting/deep_links/parsing.py +88 -0
  164. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  165. telegrinder/tools/formatting/html.py +241 -0
  166. telegrinder/tools/fullname.py +82 -0
  167. telegrinder/tools/global_context/__init__.py +13 -0
  168. telegrinder/tools/global_context/abc.py +63 -0
  169. telegrinder/tools/global_context/builtin_context.py +45 -0
  170. telegrinder/tools/global_context/global_context.py +614 -0
  171. telegrinder/tools/input_file_directory.py +30 -0
  172. telegrinder/tools/keyboard/__init__.py +6 -0
  173. telegrinder/tools/keyboard/abc.py +84 -0
  174. telegrinder/tools/keyboard/base.py +108 -0
  175. telegrinder/tools/keyboard/button.py +181 -0
  176. telegrinder/tools/keyboard/data.py +31 -0
  177. telegrinder/tools/keyboard/keyboard.py +160 -0
  178. telegrinder/tools/keyboard/utils.py +95 -0
  179. telegrinder/tools/lifespan.py +188 -0
  180. telegrinder/tools/limited_dict.py +35 -0
  181. telegrinder/tools/loop_wrapper.py +271 -0
  182. telegrinder/tools/magic/__init__.py +29 -0
  183. telegrinder/tools/magic/annotations.py +172 -0
  184. telegrinder/tools/magic/descriptors.py +57 -0
  185. telegrinder/tools/magic/function.py +254 -0
  186. telegrinder/tools/magic/inspect.py +16 -0
  187. telegrinder/tools/magic/shortcut.py +107 -0
  188. telegrinder/tools/member_descriptor_proxy.py +95 -0
  189. telegrinder/tools/parse_mode.py +12 -0
  190. telegrinder/tools/serialization/__init__.py +5 -0
  191. telegrinder/tools/serialization/abc.py +34 -0
  192. telegrinder/tools/serialization/json_ser.py +60 -0
  193. telegrinder/tools/serialization/msgpack_ser.py +197 -0
  194. telegrinder/tools/serialization/utils.py +18 -0
  195. telegrinder/tools/singleton/__init__.py +4 -0
  196. telegrinder/tools/singleton/abc.py +14 -0
  197. telegrinder/tools/singleton/singleton.py +18 -0
  198. telegrinder/tools/state_mutator/__init__.py +4 -0
  199. telegrinder/tools/state_mutator/mutation.py +85 -0
  200. telegrinder/tools/state_storage/__init__.py +4 -0
  201. telegrinder/tools/state_storage/abc.py +38 -0
  202. telegrinder/tools/state_storage/memory.py +27 -0
  203. telegrinder/tools/strings.py +22 -0
  204. telegrinder/types/__init__.py +323 -0
  205. telegrinder/types/enums.py +754 -0
  206. telegrinder/types/input_file.py +51 -0
  207. telegrinder/types/methods.py +6143 -0
  208. telegrinder/types/methods_utils.py +66 -0
  209. telegrinder/types/objects.py +8184 -0
  210. telegrinder/types/webapp.py +129 -0
  211. telegrinder/verification_utils.py +35 -0
  212. telegrinder-1.0.0rc1.dist-info/METADATA +166 -0
  213. telegrinder-1.0.0rc1.dist-info/RECORD +215 -0
  214. telegrinder-1.0.0rc1.dist-info/WHEEL +4 -0
  215. telegrinder-1.0.0rc1.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,57 @@
1
+ import typing
2
+
3
+ _NOVALUE: typing.Any = object()
4
+
5
+
6
+ def private_func_name(func: typing.Callable[..., typing.Any], /) -> str:
7
+ return "__" + func.__name__
8
+
9
+
10
+ class additional_property[T, R]: # noqa: N801
11
+ def __init__(self, func: typing.Callable[[T], R], /) -> None:
12
+ self.func = func
13
+ self.func_name = private_func_name(func)
14
+
15
+ def __set__(self, instance: T, value: typing.Any, /) -> None:
16
+ instance.__dict__[self.func_name] = value
17
+
18
+ def __get__(self, instance: T | None, owner: type[T], /) -> typing.Any:
19
+ if instance is None:
20
+ raise AttributeError("Cannot access additional property from class.")
21
+
22
+ value = instance.__dict__.get(self.func_name, _NOVALUE)
23
+ if value is _NOVALUE:
24
+ value = self.func(instance)
25
+ instance.__dict__[self.func_name] = value
26
+
27
+ return value
28
+
29
+ def __delete__(self, instance: T, /) -> None:
30
+ instance.__dict__.pop(self.func_name, None)
31
+
32
+
33
+ class class_property[T]: # noqa: N801
34
+ def __init__(self, func: typing.Callable[..., T], /) -> None:
35
+ self.func = func
36
+ self.func_name = private_func_name(func)
37
+
38
+ def __get__(self, instance: typing.Any | None, owner: type[typing.Any], /) -> T:
39
+ return self.func(owner)
40
+
41
+
42
+ class class_cached_property[T]: # noqa: N801
43
+ def __init__(self, func: typing.Callable[..., T], /) -> None:
44
+ self.func = func
45
+ self.func_name = private_func_name(func)
46
+
47
+ def __get__(self, instance: typing.Any | None, owner: type[typing.Any], /) -> T:
48
+ value = getattr(owner, self.func_name, _NOVALUE)
49
+
50
+ if value is _NOVALUE:
51
+ value = self.func(owner)
52
+ setattr(owner, self.func_name, value)
53
+
54
+ return value
55
+
56
+
57
+ __all__ = ("additional_property", "class_cached_property", "class_property")
@@ -0,0 +1,254 @@
1
+ import dataclasses
2
+ import enum
3
+ import inspect
4
+ import types
5
+ import typing
6
+ from functools import cached_property, lru_cache, partial
7
+
8
+ from telegrinder.tools.magic.annotations import Annotations, MappingAnnotations
9
+
10
+ type AnyFunction = Func[..., typing.Any]
11
+ type Func[**P, R] = typing.Callable[P, R]
12
+ type Function[**P, R] = typing.Callable[
13
+ P,
14
+ typing.Union[
15
+ typing.Coroutine[typing.Any, typing.Any, R],
16
+ typing.Awaitable[R],
17
+ typing.AsyncGenerator[R, typing.Any],
18
+ typing.Generator[typing.Any, typing.Any, R],
19
+ typing.AsyncContextManager[R, typing.Any],
20
+ typing.ContextManager[R, typing.Any],
21
+ R,
22
+ ],
23
+ ]
24
+
25
+
26
+ def _to_str(obj: typing.Any, /) -> str:
27
+ if isinstance(obj, enum.Enum):
28
+ return str(obj.value)
29
+ return str(obj) if not isinstance(obj, str) else obj
30
+
31
+
32
+ @lru_cache(maxsize=1024)
33
+ def _unwrap_func(obj: typing.Any, /) -> typing.Any:
34
+ while True:
35
+ if isinstance(obj, partial):
36
+ obj = obj.func
37
+ continue
38
+
39
+ if hasattr(obj, "__wrapped__"):
40
+ obj = obj.__wrapped__
41
+ continue
42
+
43
+ break
44
+
45
+ return obj
46
+
47
+
48
+ def _resolve_arg_names(
49
+ func: AnyFunction,
50
+ /,
51
+ *,
52
+ start_idx: int,
53
+ stop_idx: int,
54
+ exclude: set[str] | None = None,
55
+ ) -> tuple[str, ...]:
56
+ exclude = exclude or set()
57
+ varnames = func.__code__.co_varnames[start_idx:stop_idx]
58
+ return tuple(name for name in varnames if name not in exclude)
59
+
60
+
61
+ class FunctionParameters(typing.TypedDict):
62
+ args: list[tuple[str, typing.Any | inspect.Parameter.empty]]
63
+ kwargs: list[tuple[str, typing.Any | inspect.Parameter.empty]]
64
+ var_star_args: typing.NotRequired[str]
65
+ var_star_kwargs: typing.NotRequired[str]
66
+
67
+
68
+ @dataclasses.dataclass(frozen=True, repr=False)
69
+ class Bundle[R]:
70
+ function: Func[..., R]
71
+ start_idx: int
72
+ context: types.MappingProxyType[str, typing.Any]
73
+
74
+ @cached_property
75
+ def args(self) -> tuple[typing.Any, ...]:
76
+ return tuple(
77
+ self.context[name]
78
+ for name in resolve_posonly_arg_names(self.function, start_idx=self.start_idx)
79
+ if name in self.context
80
+ )
81
+
82
+ @cached_property
83
+ def kwargs(self) -> dict[str, typing.Any]:
84
+ posonly_arg_names = resolve_posonly_arg_names(self.function, start_idx=self.start_idx)
85
+ return {name: value for name, value in self.context.items() if name not in posonly_arg_names}
86
+
87
+ @classmethod
88
+ def from_context(
89
+ cls,
90
+ function: Func[..., R],
91
+ context: typing.Mapping[str, typing.Any],
92
+ /,
93
+ *,
94
+ start_idx: int = 1,
95
+ ) -> typing.Self:
96
+ return cls(function, start_idx, types.MappingProxyType(context))
97
+
98
+ def __repr__(self) -> str:
99
+ return "<{}: function={!r} bundle args={!r} kwargs={!r}>".format(
100
+ type(self).__name__,
101
+ self.function,
102
+ self.args,
103
+ self.kwargs,
104
+ )
105
+
106
+ def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> R:
107
+ return self.function(*args, *self.args, **kwargs, **self.kwargs)
108
+
109
+ def __and__(self, other: object, /) -> typing.Self:
110
+ if not isinstance(other, Bundle):
111
+ return NotImplemented
112
+
113
+ if self.function != other.function:
114
+ raise ValueError(
115
+ f"Cannot merge contexts because bundle {other!r} is intended for a different function.",
116
+ )
117
+
118
+ return dataclasses.replace(
119
+ self,
120
+ context=types.MappingProxyType(mapping=self.context | other.context),
121
+ )
122
+
123
+ def __iand__(self, other: object, /) -> typing.Self:
124
+ return self.__and__(other)
125
+
126
+
127
+ def resolve_arg_names(
128
+ func: AnyFunction,
129
+ /,
130
+ *,
131
+ start_idx: int = 1,
132
+ exclude: set[str] | None = None,
133
+ ) -> tuple[str, ...]:
134
+ func = _unwrap_func(func)
135
+ return _resolve_arg_names(
136
+ func,
137
+ start_idx=start_idx,
138
+ stop_idx=func.__code__.co_argcount + func.__code__.co_kwonlyargcount,
139
+ exclude=exclude,
140
+ )
141
+
142
+
143
+ def resolve_kwonly_arg_names(
144
+ func: AnyFunction,
145
+ /,
146
+ *,
147
+ start_idx: int = 1,
148
+ exclude: set[str] | None = None,
149
+ ) -> tuple[str, ...]:
150
+ func = _unwrap_func(func)
151
+ return _resolve_arg_names(
152
+ func,
153
+ start_idx=func.__code__.co_argcount + start_idx,
154
+ stop_idx=func.__code__.co_argcount + func.__code__.co_kwonlyargcount,
155
+ exclude=exclude,
156
+ )
157
+
158
+
159
+ def resolve_posonly_arg_names(
160
+ func: AnyFunction,
161
+ /,
162
+ *,
163
+ start_idx: int = 1,
164
+ exclude: set[str] | None = None,
165
+ ) -> tuple[str, ...]:
166
+ func = _unwrap_func(func)
167
+ return _resolve_arg_names(
168
+ func,
169
+ start_idx=start_idx,
170
+ stop_idx=func.__code__.co_posonlyargcount,
171
+ exclude=exclude,
172
+ )
173
+
174
+
175
+ @lru_cache(maxsize=1024)
176
+ def get_default_args(func: AnyFunction, /) -> dict[str, typing.Any]:
177
+ parameters = get_func_parameters(func)
178
+ return {
179
+ key: value
180
+ for sublist in (parameters["args"], parameters["kwargs"])
181
+ for key, value in sublist
182
+ if value is not inspect.Parameter.empty
183
+ }
184
+
185
+
186
+ @lru_cache(maxsize=1024)
187
+ def get_func_parameters(func: AnyFunction, /) -> FunctionParameters:
188
+ func_params = FunctionParameters(args=[], kwargs=[])
189
+
190
+ for k, p in inspect.signature(func).parameters.items():
191
+ if k in ("self", "cls"):
192
+ continue
193
+
194
+ match p.kind:
195
+ case p.POSITIONAL_OR_KEYWORD | p.POSITIONAL_ONLY:
196
+ func_params["args"].append((k, p.default))
197
+ case p.KEYWORD_ONLY:
198
+ func_params["kwargs"].append((k, p.default))
199
+ case p.VAR_POSITIONAL:
200
+ func_params["var_star_args"] = k
201
+ case p.VAR_KEYWORD:
202
+ func_params["var_star_kwargs"] = k
203
+
204
+ return func_params
205
+
206
+
207
+ @lru_cache(maxsize=1024)
208
+ def get_func_annotations(func: AnyFunction, /) -> MappingAnnotations:
209
+ return Annotations(obj=func).get(
210
+ ignore_failed_evals=True,
211
+ exclude_forward_refs=True,
212
+ cache=False,
213
+ )
214
+
215
+
216
+ def bundle[R](
217
+ function: Function[..., R],
218
+ context: dict[typing.Any, typing.Any],
219
+ /,
220
+ *,
221
+ start_idx: int = 1,
222
+ bundle_kwargs: bool = False,
223
+ typebundle: bool = False,
224
+ omit_defaults: bool = False,
225
+ ) -> Bundle[R]:
226
+ if typebundle:
227
+ return Bundle.from_context(
228
+ function,
229
+ {name: context[ann] for name, ann in get_func_annotations(function).items() if ann in context},
230
+ start_idx=start_idx,
231
+ )
232
+
233
+ kwargs = {_to_str(k): v for k, v in context.items()}
234
+ if not bundle_kwargs or "var_star_kwargs" not in get_func_parameters(function):
235
+ names = resolve_arg_names(function, start_idx=start_idx, exclude={"cls", "self"})
236
+ kwargs = {k: v for k, v in kwargs.items() if k in names}
237
+
238
+ return Bundle.from_context(
239
+ function,
240
+ kwargs if omit_defaults else get_default_args(function) | kwargs,
241
+ start_idx=start_idx,
242
+ )
243
+
244
+
245
+ __all__ = (
246
+ "Bundle",
247
+ "bundle",
248
+ "get_default_args",
249
+ "get_func_annotations",
250
+ "get_func_parameters",
251
+ "resolve_arg_names",
252
+ "resolve_kwonly_arg_names",
253
+ "resolve_posonly_arg_names",
254
+ )
@@ -0,0 +1,16 @@
1
+ import sys
2
+
3
+
4
+ def get_frame_module_name() -> str:
5
+ frame = sys._getframe()
6
+
7
+ while frame:
8
+ if frame.f_globals.get("__name__", "").startswith("telegrinder"):
9
+ frame = frame.f_back
10
+ else:
11
+ break
12
+
13
+ return "<module>" if frame is None else frame.f_globals.get("__name__", "<module>")
14
+
15
+
16
+ __all__ = ("get_frame_module_name",)
@@ -0,0 +1,107 @@
1
+ import dataclasses
2
+ import inspect
3
+ import typing
4
+ from collections import OrderedDict
5
+ from functools import wraps
6
+
7
+ from kungfu.library.monad.result import Result
8
+
9
+ from telegrinder.tools.magic.function import get_func_parameters
10
+ from telegrinder.types.methods_utils import get_params
11
+
12
+ if typing.TYPE_CHECKING:
13
+ from telegrinder.api.error import APIError
14
+ from telegrinder.bot.cute_types.base import BaseCute, BaseShortcuts
15
+
16
+ type Executor[T] = typing.Callable[
17
+ [T, str, dict[str, typing.Any]],
18
+ typing.Awaitable[Result[typing.Any, APIError]],
19
+ ]
20
+ type CuteMethod[T, **P, R] = typing.Callable[
21
+ typing.Concatenate[T, P],
22
+ typing.Awaitable[Result[R, APIError]],
23
+ ]
24
+ type ShortcutMethod[**P, R] = typing.Callable[
25
+ typing.Concatenate[typing.Any, P],
26
+ typing.Coroutine[typing.Any, typing.Any, Result[R, APIError]],
27
+ ]
28
+
29
+
30
+ @dataclasses.dataclass(slots=True, frozen=True)
31
+ class Shortcut[T]:
32
+ method_name: str
33
+ executor: Executor[T] | None = dataclasses.field(default=None, kw_only=True)
34
+ custom_params: set[str] = dataclasses.field(default_factory=lambda: set[str](), kw_only=True)
35
+
36
+
37
+ @typing.overload
38
+ def shortcut[T: BaseCute, S: BaseShortcuts, **P, R](
39
+ method_name: str,
40
+ /,
41
+ *,
42
+ custom_params: set[str] = ...,
43
+ ) -> typing.Callable[[CuteMethod[T, P, R] | CuteMethod[S, P, R]], ShortcutMethod[P, R]]: ...
44
+
45
+
46
+ @typing.overload
47
+ def shortcut[T: BaseCute, S: BaseShortcuts, **P, R](
48
+ method_name: str,
49
+ /,
50
+ *,
51
+ executor: Executor[T],
52
+ custom_params: set[str] = ...,
53
+ ) -> typing.Callable[[CuteMethod[T, P, R] | CuteMethod[S, P, R]], ShortcutMethod[P, R]]: ...
54
+
55
+
56
+ def shortcut[**P, R](
57
+ method_name: str,
58
+ /,
59
+ *,
60
+ executor: Executor[typing.Any] | None = None,
61
+ custom_params: set[str] | None = None,
62
+ ) -> typing.Callable[[CuteMethod[typing.Any, P, R]], ShortcutMethod[P, R]]:
63
+ """Decorate a cute method as a shortcut."""
64
+
65
+ def wrapper(func: CuteMethod[typing.Any, P, R]) -> ShortcutMethod[P, R]:
66
+ @wraps(func)
67
+ async def inner(
68
+ self: typing.Any,
69
+ *args: P.args,
70
+ **kwargs: P.kwargs,
71
+ ) -> Result[R, typing.Any]:
72
+ if executor is None:
73
+ return await func(self, *args, **kwargs)
74
+
75
+ params: dict[str, typing.Any] = OrderedDict()
76
+ func_params = get_func_parameters(func)
77
+
78
+ for index, (arg, default) in enumerate(func_params["args"]):
79
+ if len(args) > index:
80
+ params[arg] = args[index]
81
+ elif default is not inspect.Parameter.empty:
82
+ params[arg] = default
83
+
84
+ if var_args := func_params.get("var_star_args"):
85
+ params[var_args] = args[len(func_params["args"]) :]
86
+
87
+ for kwarg, default in func_params["kwargs"]:
88
+ params[kwarg] = (
89
+ kwargs.pop(kwarg, default) if default is not inspect.Parameter.empty else kwargs.pop(kwarg)
90
+ )
91
+
92
+ if var_kwargs := func_params.get("var_star_kwargs"):
93
+ params[var_kwargs] = kwargs.copy()
94
+
95
+ return await executor(self, method_name, get_params(params))
96
+
97
+ inner.__shortcut__ = Shortcut( # type: ignore
98
+ method_name=method_name,
99
+ executor=executor,
100
+ custom_params=custom_params or set(),
101
+ )
102
+ return inner # type: ignore
103
+
104
+ return wrapper
105
+
106
+
107
+ __all__ = ("Shortcut", "shortcut")
@@ -0,0 +1,95 @@
1
+ import dataclasses
2
+ import operator
3
+ import types
4
+ import typing
5
+
6
+ type Operation = GetAttribute | GetItem | MethodCall
7
+
8
+
9
+ def evaluate_operations(
10
+ obj: typing.Any,
11
+ member_name: str,
12
+ operations: list[Operation],
13
+ /,
14
+ ) -> typing.Any:
15
+ obj = getattr(obj, member_name)
16
+
17
+ for operation in operations:
18
+ match operation:
19
+ case GetAttribute(attribute_name=attribute_name):
20
+ obj = getattr(obj, attribute_name)
21
+ case GetItem():
22
+ obj = operation[obj]
23
+ case MethodCall():
24
+ obj = operation(obj)
25
+
26
+ return obj
27
+
28
+
29
+ @dataclasses.dataclass(slots=True, frozen=True, kw_only=True)
30
+ class MethodCall:
31
+ caller: operator.methodcaller
32
+
33
+ def __call__(self, obj: typing.Any, /) -> typing.Any:
34
+ return self.caller(obj)
35
+
36
+
37
+ @dataclasses.dataclass(slots=True, frozen=True, kw_only=True)
38
+ class GetAttribute:
39
+ attribute_name: str
40
+
41
+
42
+ @dataclasses.dataclass(slots=True, frozen=True, kw_only=True)
43
+ class GetItem:
44
+ itemgetter: operator.itemgetter[typing.Any]
45
+
46
+ def __getitem__(self, obj: typing.Any, /) -> typing.Any:
47
+ return self.itemgetter(obj)
48
+
49
+
50
+ class MemberDescriptorProxy:
51
+ _operations: list[Operation]
52
+ _objclass: type[typing.Any]
53
+
54
+ __slots__ = ("_descriptor", "_operations", "_member_name", "_objclass")
55
+
56
+ def __init__(self, descriptor: types.MemberDescriptorType) -> None:
57
+ self._descriptor = descriptor
58
+ self._member_name = descriptor.__name__
59
+ self._objclass = descriptor.__objclass__
60
+ self._operations = []
61
+
62
+ def __getattr__(self, __name: str) -> typing.Any:
63
+ if __name in getattr(type(self), "__static_attributes__"):
64
+ return super().__getattribute__(__name)
65
+
66
+ self._operations.append(GetAttribute(attribute_name=__name))
67
+ return self
68
+
69
+ def __getitem__(self, __item: typing.Any) -> typing.Any:
70
+ self._operations.append(GetItem(itemgetter=operator.itemgetter(__item)))
71
+ return self
72
+
73
+ def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
74
+ if not self._operations or not isinstance(operation := self._operations.pop(-1), GetAttribute):
75
+ raise SyntaxError("No operations to call.")
76
+
77
+ self._operations.append(MethodCall(caller=operator.methodcaller(operation.attribute_name, *args, **kwargs)))
78
+ return self
79
+
80
+ def __get__(self, instance: typing.Any, owner: typing.Type[typing.Any]) -> typing.Any:
81
+ if instance is None:
82
+ return self
83
+ return self._descriptor.__get__(instance, owner)
84
+
85
+ def __set__(self, instance: typing.Any, value: typing.Any) -> None:
86
+ self._descriptor.__set__(instance, value)
87
+
88
+ def __delete__(self, instance: typing.Any) -> None:
89
+ self._descriptor.__delete__(instance)
90
+
91
+ def __repr__(self) -> str:
92
+ return self._descriptor.__repr__()
93
+
94
+
95
+ __all__ = ("MemberDescriptorProxy", "evaluate_operations")
@@ -0,0 +1,12 @@
1
+ import typing
2
+
3
+ from telegrinder.tools.final import Final
4
+
5
+
6
+ @typing.final
7
+ class ParseMode(Final):
8
+ MARKDOWNV2: typing.Final[typing.Literal["MarkdownV2"]] = "MarkdownV2"
9
+ HTML: typing.Final[typing.Literal["HTML"]] = "HTML"
10
+
11
+
12
+ __all__ = ("ParseMode",)
@@ -0,0 +1,5 @@
1
+ from telegrinder.tools.serialization.abc import ABCDataSerializer
2
+ from telegrinder.tools.serialization.json_ser import JSONSerializer
3
+ from telegrinder.tools.serialization.msgpack_ser import MsgPackSerializer
4
+
5
+ __all__ = ("ABCDataSerializer", "JSONSerializer", "MsgPackSerializer")
@@ -0,0 +1,34 @@
1
+ import abc
2
+ import typing
3
+ from functools import cached_property
4
+
5
+ import msgspec
6
+ from kungfu.library.monad.result import Result
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from _typeshed import DataclassInstance
10
+
11
+ type ModelType = msgspec.Struct | DataclassInstance
12
+
13
+
14
+ class ABCDataSerializer[Data = typing.Any](abc.ABC):
15
+ ident_key: str | None = None
16
+
17
+ @abc.abstractmethod
18
+ def __init__(self, data_type: type[Data], /) -> None:
19
+ pass
20
+
21
+ @cached_property
22
+ def key(self) -> str:
23
+ return self.ident_key + "_" if self.ident_key else ""
24
+
25
+ @abc.abstractmethod
26
+ def serialize(self, data: Data) -> str:
27
+ pass
28
+
29
+ @abc.abstractmethod
30
+ def deserialize(self, serialized_data: str) -> Result[Data, str]:
31
+ pass
32
+
33
+
34
+ __all__ = ("ABCDataSerializer",)
@@ -0,0 +1,60 @@
1
+ import typing
2
+
3
+ import msgspec
4
+ from kungfu.library.monad.result import Error, Ok, Result
5
+
6
+ from telegrinder.modules import json
7
+ from telegrinder.msgspec_utils import decoder
8
+ from telegrinder.tools.serialization.abc import ABCDataSerializer, ModelType
9
+ from telegrinder.tools.serialization.utils import get_model_ident_key
10
+
11
+ type Json = dict[str, typing.Any] | ModelType
12
+
13
+
14
+ class JSONSerializer[JsonT: Json](ABCDataSerializer[JsonT]):
15
+ @typing.overload
16
+ def __init__(self, model_t: type[JsonT]) -> None: ...
17
+
18
+ @typing.overload
19
+ def __init__(self, model_t: type[JsonT], *, ident_key: str | None = ...) -> None: ...
20
+
21
+ def __init__(
22
+ self,
23
+ model_t: type[JsonT] = dict[str, typing.Any],
24
+ *,
25
+ ident_key: str | None = None,
26
+ ) -> None:
27
+ self.model_t = model_t
28
+ self.ident_key = ident_key or get_model_ident_key(model_t)
29
+
30
+ @classmethod
31
+ def serialize_from_json(cls, data: JsonT, *, ident_key: str | None = None) -> str:
32
+ return cls(type(data), ident_key=ident_key).serialize(data)
33
+
34
+ @classmethod
35
+ def deserialize_to_json(cls, serialized_data: str, model_t: type[JsonT]) -> Result[JsonT, str]:
36
+ return cls(model_t).deserialize(serialized_data)
37
+
38
+ def serialize(self, data: JsonT) -> str:
39
+ return self.key + json.dumps(data)
40
+
41
+ def deserialize(self, serialized_data: str) -> Result[JsonT, str]:
42
+ if not serialized_data.startswith(self.key):
43
+ return Error("Data is not corresponding to key.")
44
+
45
+ data = serialized_data.removeprefix(self.key)
46
+ try:
47
+ data_obj = json.loads(data)
48
+ except (msgspec.ValidationError, msgspec.DecodeError):
49
+ return Error("Cannot decode json.")
50
+
51
+ if not issubclass(self.model_t, dict):
52
+ try:
53
+ return Ok(decoder.convert(data_obj, type=self.model_t))
54
+ except (msgspec.ValidationError, msgspec.DecodeError):
55
+ return Error("Incorrect data.")
56
+
57
+ return Ok(data_obj)
58
+
59
+
60
+ __all__ = ("JSONSerializer",)