telegrinder 0.3.4.post1__py3-none-any.whl → 0.4.1__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 (169) hide show
  1. telegrinder/__init__.py +30 -31
  2. telegrinder/api/__init__.py +2 -1
  3. telegrinder/api/api.py +28 -20
  4. telegrinder/api/error.py +8 -4
  5. telegrinder/api/response.py +2 -2
  6. telegrinder/api/token.py +2 -2
  7. telegrinder/bot/__init__.py +6 -0
  8. telegrinder/bot/bot.py +38 -31
  9. telegrinder/bot/cute_types/__init__.py +2 -0
  10. telegrinder/bot/cute_types/base.py +55 -129
  11. telegrinder/bot/cute_types/callback_query.py +76 -61
  12. telegrinder/bot/cute_types/chat_join_request.py +4 -3
  13. telegrinder/bot/cute_types/chat_member_updated.py +28 -31
  14. telegrinder/bot/cute_types/inline_query.py +5 -4
  15. telegrinder/bot/cute_types/message.py +555 -602
  16. telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
  17. telegrinder/bot/cute_types/update.py +20 -12
  18. telegrinder/bot/cute_types/utils.py +3 -36
  19. telegrinder/bot/dispatch/__init__.py +4 -0
  20. telegrinder/bot/dispatch/abc.py +8 -9
  21. telegrinder/bot/dispatch/context.py +5 -7
  22. telegrinder/bot/dispatch/dispatch.py +85 -33
  23. telegrinder/bot/dispatch/handler/abc.py +5 -6
  24. telegrinder/bot/dispatch/handler/audio_reply.py +2 -2
  25. telegrinder/bot/dispatch/handler/base.py +3 -3
  26. telegrinder/bot/dispatch/handler/document_reply.py +2 -2
  27. telegrinder/bot/dispatch/handler/func.py +36 -42
  28. telegrinder/bot/dispatch/handler/media_group_reply.py +5 -4
  29. telegrinder/bot/dispatch/handler/message_reply.py +2 -2
  30. telegrinder/bot/dispatch/handler/photo_reply.py +2 -2
  31. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -2
  32. telegrinder/bot/dispatch/handler/video_reply.py +2 -2
  33. telegrinder/bot/dispatch/middleware/abc.py +83 -8
  34. telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
  35. telegrinder/bot/dispatch/process.py +44 -50
  36. telegrinder/bot/dispatch/return_manager/__init__.py +2 -0
  37. telegrinder/bot/dispatch/return_manager/abc.py +6 -10
  38. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
  39. telegrinder/bot/dispatch/view/__init__.py +2 -0
  40. telegrinder/bot/dispatch/view/abc.py +10 -6
  41. telegrinder/bot/dispatch/view/base.py +81 -50
  42. telegrinder/bot/dispatch/view/box.py +20 -9
  43. telegrinder/bot/dispatch/view/callback_query.py +3 -4
  44. telegrinder/bot/dispatch/view/chat_join_request.py +2 -7
  45. telegrinder/bot/dispatch/view/chat_member.py +3 -5
  46. telegrinder/bot/dispatch/view/inline_query.py +3 -4
  47. telegrinder/bot/dispatch/view/message.py +3 -4
  48. telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
  49. telegrinder/bot/dispatch/view/raw.py +42 -40
  50. telegrinder/bot/dispatch/waiter_machine/actions.py +5 -4
  51. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +0 -0
  52. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +0 -0
  53. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +9 -7
  54. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  55. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +3 -2
  56. telegrinder/bot/dispatch/waiter_machine/machine.py +113 -34
  57. telegrinder/bot/dispatch/waiter_machine/middleware.py +15 -10
  58. telegrinder/bot/dispatch/waiter_machine/short_state.py +7 -18
  59. telegrinder/bot/polling/polling.py +62 -54
  60. telegrinder/bot/rules/__init__.py +24 -1
  61. telegrinder/bot/rules/abc.py +17 -10
  62. telegrinder/bot/rules/callback_data.py +20 -61
  63. telegrinder/bot/rules/chat_join.py +6 -4
  64. telegrinder/bot/rules/command.py +4 -4
  65. telegrinder/bot/rules/enum_text.py +1 -4
  66. telegrinder/bot/rules/func.py +5 -3
  67. telegrinder/bot/rules/fuzzy.py +1 -1
  68. telegrinder/bot/rules/id.py +24 -0
  69. telegrinder/bot/rules/inline.py +6 -4
  70. telegrinder/bot/rules/integer.py +2 -1
  71. telegrinder/bot/rules/logic.py +18 -0
  72. telegrinder/bot/rules/markup.py +5 -6
  73. telegrinder/bot/rules/message.py +2 -4
  74. telegrinder/bot/rules/message_entities.py +1 -3
  75. telegrinder/bot/rules/node.py +15 -9
  76. telegrinder/bot/rules/payload.py +81 -0
  77. telegrinder/bot/rules/payment_invoice.py +29 -0
  78. telegrinder/bot/rules/regex.py +5 -6
  79. telegrinder/bot/rules/state.py +1 -3
  80. telegrinder/bot/rules/text.py +10 -5
  81. telegrinder/bot/rules/update.py +0 -0
  82. telegrinder/bot/scenario/abc.py +2 -4
  83. telegrinder/bot/scenario/checkbox.py +12 -14
  84. telegrinder/bot/scenario/choice.py +6 -9
  85. telegrinder/client/__init__.py +9 -1
  86. telegrinder/client/abc.py +35 -10
  87. telegrinder/client/aiohttp.py +28 -24
  88. telegrinder/client/form_data.py +31 -0
  89. telegrinder/client/sonic.py +212 -0
  90. telegrinder/model.py +38 -145
  91. telegrinder/modules.py +3 -1
  92. telegrinder/msgspec_utils.py +136 -68
  93. telegrinder/node/__init__.py +74 -13
  94. telegrinder/node/attachment.py +92 -16
  95. telegrinder/node/base.py +196 -68
  96. telegrinder/node/callback_query.py +17 -16
  97. telegrinder/node/command.py +3 -2
  98. telegrinder/node/composer.py +40 -75
  99. telegrinder/node/container.py +13 -7
  100. telegrinder/node/either.py +82 -0
  101. telegrinder/node/event.py +20 -31
  102. telegrinder/node/file.py +51 -0
  103. telegrinder/node/me.py +4 -5
  104. telegrinder/node/payload.py +78 -0
  105. telegrinder/node/polymorphic.py +28 -9
  106. telegrinder/node/rule.py +2 -6
  107. telegrinder/node/scope.py +4 -6
  108. telegrinder/node/source.py +37 -21
  109. telegrinder/node/text.py +20 -8
  110. telegrinder/node/tools/generator.py +7 -11
  111. telegrinder/py.typed +0 -0
  112. telegrinder/rules.py +0 -61
  113. telegrinder/tools/__init__.py +97 -38
  114. telegrinder/tools/adapter/__init__.py +19 -0
  115. telegrinder/tools/adapter/abc.py +49 -0
  116. telegrinder/tools/adapter/dataclass.py +56 -0
  117. telegrinder/{bot/rules → tools}/adapter/event.py +8 -10
  118. telegrinder/{bot/rules → tools}/adapter/node.py +8 -10
  119. telegrinder/{bot/rules → tools}/adapter/raw_event.py +2 -2
  120. telegrinder/{bot/rules → tools}/adapter/raw_update.py +2 -2
  121. telegrinder/tools/buttons.py +52 -26
  122. telegrinder/tools/callback_data_serilization/__init__.py +5 -0
  123. telegrinder/tools/callback_data_serilization/abc.py +51 -0
  124. telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
  125. telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
  126. telegrinder/tools/error_handler/abc.py +4 -7
  127. telegrinder/tools/error_handler/error.py +0 -0
  128. telegrinder/tools/error_handler/error_handler.py +34 -48
  129. telegrinder/tools/formatting/__init__.py +57 -37
  130. telegrinder/tools/formatting/deep_links.py +541 -0
  131. telegrinder/tools/formatting/{html.py → html_formatter.py} +51 -79
  132. telegrinder/tools/formatting/spec_html_formats.py +14 -60
  133. telegrinder/tools/functional.py +1 -5
  134. telegrinder/tools/global_context/global_context.py +26 -51
  135. telegrinder/tools/global_context/telegrinder_ctx.py +3 -3
  136. telegrinder/tools/i18n/abc.py +0 -0
  137. telegrinder/tools/i18n/middleware/abc.py +3 -6
  138. telegrinder/tools/input_file_directory.py +30 -0
  139. telegrinder/tools/keyboard.py +9 -9
  140. telegrinder/tools/lifespan.py +105 -0
  141. telegrinder/tools/limited_dict.py +5 -10
  142. telegrinder/tools/loop_wrapper/abc.py +7 -2
  143. telegrinder/tools/loop_wrapper/loop_wrapper.py +40 -95
  144. telegrinder/tools/magic.py +236 -60
  145. telegrinder/tools/state_storage/__init__.py +0 -0
  146. telegrinder/tools/state_storage/abc.py +5 -9
  147. telegrinder/tools/state_storage/memory.py +1 -1
  148. telegrinder/tools/strings.py +13 -0
  149. telegrinder/types/__init__.py +8 -0
  150. telegrinder/types/enums.py +31 -21
  151. telegrinder/types/input_file.py +51 -0
  152. telegrinder/types/methods.py +531 -109
  153. telegrinder/types/objects.py +934 -826
  154. telegrinder/verification_utils.py +0 -2
  155. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.1.dist-info}/LICENSE +2 -2
  156. telegrinder-0.4.1.dist-info/METADATA +143 -0
  157. telegrinder-0.4.1.dist-info/RECORD +182 -0
  158. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.1.dist-info}/WHEEL +1 -1
  159. telegrinder/bot/rules/adapter/__init__.py +0 -17
  160. telegrinder/bot/rules/adapter/abc.py +0 -31
  161. telegrinder/node/message.py +0 -14
  162. telegrinder/node/update.py +0 -15
  163. telegrinder/tools/formatting/links.py +0 -38
  164. telegrinder/tools/kb_set/__init__.py +0 -4
  165. telegrinder/tools/kb_set/base.py +0 -15
  166. telegrinder/tools/kb_set/yaml.py +0 -63
  167. telegrinder-0.3.4.post1.dist-info/METADATA +0 -110
  168. telegrinder-0.3.4.post1.dist-info/RECORD +0 -165
  169. /telegrinder/{bot/rules → tools}/adapter/errors.py +0 -0
@@ -1,59 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import dataclasses
1
5
  import enum
2
- import types
3
- import typing
6
+ import inspect
7
+ from collections import OrderedDict
4
8
  from functools import wraps
5
9
 
10
+ import typing_extensions as typing
11
+ from fntypes import Result
12
+
13
+ from telegrinder.model import get_params
14
+
6
15
  if typing.TYPE_CHECKING:
7
16
  from telegrinder.bot.rules.abc import ABCRule
8
17
  from telegrinder.node.polymorphic import Polymorphic
9
18
 
10
- T = typing.TypeVar("T", bound=ABCRule)
11
- F = typing.TypeVar(
12
- "F",
13
- bound=typing.Callable[typing.Concatenate[typing.Callable[..., typing.Any], ...], typing.Any],
14
- )
15
-
16
- Impl: typing.TypeAlias = type[classmethod]
17
- FuncType: typing.TypeAlias = types.FunctionType | typing.Callable[..., typing.Any]
19
+ type Impl = type[classmethod]
20
+ type Function = typing.Callable[..., typing.Any]
21
+ type Executor[T] = typing.Callable[
22
+ [T, str, dict[str, typing.Any]],
23
+ typing.Awaitable[Result[typing.Any, typing.Any]],
24
+ ]
18
25
 
19
26
  TRANSLATIONS_KEY: typing.Final[str] = "_translations"
27
+ MORPH_IMPLEMENTATIONS_KEY = "__morph_implementations__"
20
28
  IMPL_MARK: typing.Final[str] = "_is_impl"
29
+ CONTEXT_KEYS: typing.Final[tuple[str, ...]] = ("ctx", "context")
30
+
31
+
32
+ class FuncParams(typing.TypedDict, total=True):
33
+ args: list[tuple[str, typing.Any | inspect.Parameter.empty]]
34
+ kwargs: list[tuple[str, typing.Any | inspect.Parameter.empty]]
35
+ var_args: typing.NotRequired[str]
36
+ var_kwargs: typing.NotRequired[str]
37
+
38
+
39
+ @dataclasses.dataclass(slots=True, frozen=True)
40
+ class Shortcut[T]:
41
+ method_name: str
42
+ executor: Executor[T] | None = dataclasses.field(default=None, kw_only=True)
43
+ custom_params: set[str] = dataclasses.field(default_factory=lambda: set[str](), kw_only=True)
21
44
 
22
45
 
23
46
  def cache_magic_value(mark_key: str, /):
24
- def inner(func: "F") -> "F":
47
+ def inner[Func: typing.Callable[..., typing.Any]](func: Func) -> Func:
25
48
  @wraps(func)
26
- def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
27
- if mark_key not in args[0].__dict__:
28
- args[0].__dict__[mark_key] = func(*args, **kwargs)
29
- return args[0].__dict__[mark_key]
49
+ def wrapper(
50
+ f: Function | None = None,
51
+ /,
52
+ *args: typing.Any,
53
+ **kwargs: typing.Any,
54
+ ) -> typing.Any:
55
+ if f is not None and mark_key not in f.__dict__:
56
+ f.__dict__[mark_key] = func(f, *args, **kwargs)
57
+ return f.__dict__[mark_key]
30
58
 
31
59
  return wrapper # type: ignore
32
60
 
33
61
  return inner
34
62
 
35
63
 
36
- def resolve_arg_names(func: FuncType, start_idx: int = 1) -> tuple[str, ...]:
64
+ def resolve_arg_names(func: Function, start_idx: int = 1) -> tuple[str, ...]:
37
65
  return func.__code__.co_varnames[start_idx : func.__code__.co_argcount]
38
66
 
39
67
 
40
- @cache_magic_value("__default_args__")
41
- def get_default_args(func: FuncType) -> dict[str, typing.Any]:
42
- kwdefaults = func.__kwdefaults__
43
- if kwdefaults:
44
- return kwdefaults
45
-
68
+ @cache_magic_value("__default_arguments__")
69
+ def get_default_args(func: Function, /) -> dict[str, typing.Any]:
46
70
  defaults = func.__defaults__
71
+ kwdefaults = {} if not func.__kwdefaults__ else func.__kwdefaults__.copy()
47
72
  if not defaults:
48
- return {}
73
+ return kwdefaults
74
+ return {
75
+ k: defaults[i] for i, k in enumerate(resolve_arg_names(func, start_idx=0)[-len(defaults) :])
76
+ } | kwdefaults
77
+
78
+
79
+ @cache_magic_value("__function_parameters__")
80
+ def get_func_parameters(func: Function, /) -> FuncParams:
81
+ func_params: FuncParams = {"args": [], "kwargs": []}
82
+
83
+ for k, p in inspect.signature(func).parameters.items():
84
+ if k in ("self", "cls"):
85
+ continue
86
+
87
+ match p.kind:
88
+ case p.POSITIONAL_OR_KEYWORD | p.POSITIONAL_ONLY:
89
+ func_params["args"].append((k, p.default))
90
+ case p.KEYWORD_ONLY:
91
+ func_params["kwargs"].append((k, p.default))
92
+ case p.VAR_POSITIONAL:
93
+ func_params["var_args"] = k
94
+ case p.VAR_KEYWORD:
95
+ func_params["var_kwargs"] = k
49
96
 
50
- return {k: defaults[i] for i, k in enumerate(resolve_arg_names(func, start_idx=0)[-len(defaults) :])}
97
+ return func_params
51
98
 
52
99
 
53
- def get_annotations(func: FuncType, *, return_type: bool = False) -> dict[str, typing.Any]:
54
- annotations = func.__annotations__
55
- if not return_type and "return" in func.__annotations__:
56
- annotations.pop("return")
100
+ def get_annotations(func: Function, *, return_type: bool = False) -> dict[str, typing.Any]:
101
+ annotations = func.__annotations__.copy()
102
+ if not return_type:
103
+ annotations.pop("return", None)
57
104
  return annotations
58
105
 
59
106
 
@@ -64,70 +111,106 @@ def to_str(s: str | enum.Enum) -> str:
64
111
 
65
112
 
66
113
  @typing.overload
67
- def magic_bundle(handler: FuncType, kw: dict[str, typing.Any]) -> dict[str, typing.Any]: ...
114
+ def magic_bundle(
115
+ function: Function,
116
+ kw: dict[str, typing.Any],
117
+ *,
118
+ start_idx: int = 1,
119
+ omit_defaults: bool = False,
120
+ ) -> dict[str, typing.Any]: ...
68
121
 
69
122
 
70
123
  @typing.overload
71
- def magic_bundle(handler: FuncType, kw: dict[enum.Enum, typing.Any]) -> dict[str, typing.Any]: ...
124
+ def magic_bundle(
125
+ function: Function,
126
+ kw: dict[enum.Enum, typing.Any],
127
+ *,
128
+ start_idx: int = 1,
129
+ omit_defaults: bool = False,
130
+ ) -> dict[str, typing.Any]: ...
72
131
 
73
132
 
74
133
  @typing.overload
75
134
  def magic_bundle(
76
- handler: FuncType,
135
+ function: Function,
77
136
  kw: dict[str, typing.Any],
78
137
  *,
79
138
  start_idx: int = 1,
80
- bundle_ctx: bool = True,
139
+ bundle_ctx: bool = False,
140
+ omit_defaults: bool = False,
81
141
  ) -> dict[str, typing.Any]: ...
82
142
 
83
143
 
84
144
  @typing.overload
85
145
  def magic_bundle(
86
- handler: FuncType,
146
+ function: Function,
87
147
  kw: dict[enum.Enum, typing.Any],
88
148
  *,
89
149
  start_idx: int = 1,
90
- bundle_ctx: bool = True,
150
+ bundle_ctx: bool = False,
151
+ omit_defaults: bool = False,
91
152
  ) -> dict[str, typing.Any]: ...
92
153
 
93
154
 
94
155
  @typing.overload
95
156
  def magic_bundle(
96
- handler: FuncType,
97
- kw: dict[type, typing.Any],
157
+ function: Function,
158
+ kw: dict[type[typing.Any], typing.Any],
98
159
  *,
160
+ start_idx: int = 1,
99
161
  typebundle: typing.Literal[True] = True,
162
+ omit_defaults: bool = False,
100
163
  ) -> dict[str, typing.Any]: ...
101
164
 
102
165
 
103
166
  def magic_bundle(
104
- handler: FuncType,
167
+ function: Function,
105
168
  kw: dict[typing.Any, typing.Any],
106
169
  *,
107
170
  start_idx: int = 1,
108
- bundle_ctx: bool = True,
171
+ bundle_ctx: bool = False,
109
172
  typebundle: bool = False,
173
+ omit_defaults: bool = False,
110
174
  ) -> dict[str, typing.Any]:
175
+ # Bundle considering the function annotations
111
176
  if typebundle:
112
- types = get_annotations(handler, return_type=False)
113
- bundle: dict[str, typing.Any] = {}
114
- for name, type in types.items():
115
- bundle[name] = kw[type]
116
- return bundle
117
-
118
- names = resolve_arg_names(handler, start_idx=start_idx)
119
- args = get_default_args(handler)
120
- args.update({to_str(k): v for k, v in kw.items() if to_str(k) in names})
121
- if "ctx" in names and bundle_ctx:
122
- args["ctx"] = kw
177
+ return {name: kw[t] for name, t in get_annotations(function, return_type=False).items() if t in kw}
178
+
179
+ names = resolve_arg_names(function, start_idx=start_idx)
180
+ # Determine if the function is only have the **kwargs parameter, then bundle all kw
181
+ if "var_kwargs" in get_func_parameters(function) and not names:
182
+ return {to_str(k): v for k, v in kw.items()}
183
+
184
+ # Bundle considering the function parameters and defaults (if do not omit defaults)
185
+ defaults = {} if omit_defaults else get_default_args(function)
186
+ args = defaults | {n: v for k, v in kw.items() if (n := to_str(k)) in names}
187
+
188
+ # Bundle a context if context key specified in `names`
189
+ if bundle_ctx:
190
+ for key in CONTEXT_KEYS:
191
+ if key in names:
192
+ args[key] = kw
193
+ break
194
+
123
195
  return args
124
196
 
125
197
 
126
- def get_cached_translation(rule: "T", locale: str) -> "T | None":
198
+ def join_dicts[Key: typing.Hashable, Value](
199
+ left_dict: dict[Key, typing.Any],
200
+ right_dict: dict[typing.Any, Value],
201
+ ) -> dict[Key, Value]:
202
+ return {key: right_dict[type_key] for key, type_key in left_dict.items()}
203
+
204
+
205
+ def get_cached_translation[Rule: ABCRule](rule: Rule, locale: str) -> Rule | None:
127
206
  return getattr(rule, TRANSLATIONS_KEY, {}).get(locale)
128
207
 
129
208
 
130
- def cache_translation(base_rule: "T", locale: str, translated_rule: "T") -> None:
209
+ def cache_translation[Rule: ABCRule](
210
+ base_rule: Rule,
211
+ locale: str,
212
+ translated_rule: Rule,
213
+ ) -> None:
131
214
  translations = getattr(base_rule, TRANSLATIONS_KEY, {})
132
215
  translations[locale] = translated_rule
133
216
  setattr(base_rule, TRANSLATIONS_KEY, translations)
@@ -139,30 +222,123 @@ def impl(method: typing.Callable[..., typing.Any]):
139
222
  return classmethod(method)
140
223
 
141
224
 
142
- def get_impls(cls: type["Polymorphic"]) -> list[typing.Callable[..., typing.Any]]:
143
- moprh_impls = getattr(cls, "__morph_impls__", None)
225
+ def get_polymorphic_implementations(cls: type[Polymorphic], /) -> list[typing.Callable[..., typing.Any]]:
226
+ moprh_impls = getattr(cls, MORPH_IMPLEMENTATIONS_KEY, None)
144
227
  if moprh_impls is not None:
145
228
  return moprh_impls
146
- impls = [
147
- func.__func__
148
- for func in vars(cls).values()
149
- if isinstance(func, classmethod) and getattr(func.__func__, IMPL_MARK, False)
150
- ]
151
- setattr(cls, "__morph_impls__", impls)
229
+
230
+ impls = []
231
+ for cls_ in cls.mro():
232
+ impls += [
233
+ obj.__func__
234
+ for obj in vars(cls_).values()
235
+ if isinstance(obj, classmethod) and getattr(obj.__func__, IMPL_MARK, False)
236
+ ]
237
+
238
+ setattr(cls, MORPH_IMPLEMENTATIONS_KEY, impls)
152
239
  return impls
153
240
 
154
241
 
242
+ def shortcut[T](
243
+ method_name: str,
244
+ *,
245
+ executor: Executor[T] | None = None,
246
+ custom_params: set[str] | None = None,
247
+ ):
248
+ """Decorate a cute method as a shortcut."""
249
+
250
+ def wrapper[F: typing.Callable[..., typing.Any]](func: F) -> F:
251
+ @wraps(func)
252
+ async def inner(
253
+ self: T,
254
+ *args: typing.Any,
255
+ **kwargs: typing.Any,
256
+ ) -> typing.Any:
257
+ if executor is None:
258
+ return await func(self, *args, **kwargs)
259
+
260
+ params: dict[str, typing.Any] = OrderedDict()
261
+ func_params = get_func_parameters(func)
262
+
263
+ for index, (arg, default) in enumerate(func_params["args"]):
264
+ if len(args) > index:
265
+ params[arg] = args[index]
266
+ elif default is not inspect.Parameter.empty:
267
+ params[arg] = default
268
+
269
+ if var_args := func_params.get("var_args"):
270
+ params[var_args] = args[len(func_params["args"]) :]
271
+
272
+ for kwarg, default in func_params["kwargs"]:
273
+ params[kwarg] = (
274
+ kwargs.pop(kwarg, default) if default is not inspect.Parameter.empty else kwargs.pop(kwarg)
275
+ )
276
+
277
+ if var_kwargs := func_params.get("var_kwargs"):
278
+ params[var_kwargs] = kwargs.copy()
279
+
280
+ return await executor(self, method_name, get_params(params))
281
+
282
+ inner.__shortcut__ = Shortcut( # type: ignore
283
+ method_name=method_name,
284
+ executor=executor,
285
+ custom_params=custom_params or set(),
286
+ )
287
+ return inner # type: ignore
288
+
289
+ return wrapper
290
+
291
+
292
+ # Source code: https://github.com/facebookincubator/later/blob/main/later/task.py#L75
293
+ async def cancel_future(fut: asyncio.Future[typing.Any], /) -> None:
294
+ if fut.done():
295
+ return
296
+
297
+ fut.cancel()
298
+ exc: asyncio.CancelledError | None = None
299
+
300
+ while not fut.done():
301
+ shielded = asyncio.shield(fut)
302
+ try:
303
+ await asyncio.wait([shielded])
304
+ except asyncio.CancelledError as ex:
305
+ exc = ex
306
+ finally:
307
+ # Insure we handle the exception/value that may exist on the shielded task
308
+ # This will prevent errors logged to the asyncio logger
309
+ if shielded.done() and not shielded.cancelled() and not shielded.exception():
310
+ shielded.result()
311
+
312
+ if fut.cancelled():
313
+ if exc is None:
314
+ return
315
+ raise exc from None
316
+
317
+ ex = fut.exception()
318
+ if ex is not None:
319
+ raise ex from None
320
+
321
+ raise asyncio.InvalidStateError(
322
+ f"Task did not raise CancelledError on cancel: {fut!r} had result {fut.result()!r}",
323
+ )
324
+
325
+
155
326
  __all__ = (
327
+ "Shortcut",
156
328
  "TRANSLATIONS_KEY",
157
329
  "cache_magic_value",
158
330
  "cache_translation",
331
+ "cancel_future",
159
332
  "get_annotations",
160
333
  "get_cached_translation",
161
334
  "get_default_args",
162
335
  "get_default_args",
163
- "get_impls",
336
+ "get_func_parameters",
337
+ "get_polymorphic_implementations",
164
338
  "impl",
339
+ "join_dicts",
165
340
  "magic_bundle",
166
341
  "resolve_arg_names",
342
+ "shortcut",
167
343
  "to_str",
168
344
  )
File without changes
@@ -1,22 +1,19 @@
1
1
  import abc
2
+ import dataclasses
2
3
  import enum
3
- import typing
4
- from dataclasses import dataclass
5
4
 
6
- from fntypes import Option
5
+ from fntypes.option import Option
7
6
 
8
7
  from telegrinder.bot.rules.state import State, StateMeta
9
8
 
10
- Payload = typing.TypeVar("Payload")
11
9
 
12
-
13
- @dataclass(frozen=True, slots=True)
14
- class StateData(typing.Generic[Payload]):
10
+ @dataclasses.dataclass(frozen=True, slots=True)
11
+ class StateData[Payload]:
15
12
  key: str | enum.Enum
16
13
  payload: Payload
17
14
 
18
15
 
19
- class ABCStateStorage(abc.ABC, typing.Generic[Payload]):
16
+ class ABCStateStorage[Payload](abc.ABC):
20
17
  @abc.abstractmethod
21
18
  async def get(self, user_id: int) -> Option[StateData[Payload]]: ...
22
19
 
@@ -28,7 +25,6 @@ class ABCStateStorage(abc.ABC, typing.Generic[Payload]):
28
25
 
29
26
  def State(self, key: str | StateMeta | enum.Enum = StateMeta.ANY, /) -> State[Payload]: # noqa: N802
30
27
  """Can be used as a shortcut to get a state rule dependant on current storage."""
31
-
32
28
  return State(storage=self, key=key)
33
29
 
34
30
 
@@ -5,7 +5,7 @@ from fntypes.option import Option
5
5
  from telegrinder.tools.functional import from_optional
6
6
  from telegrinder.tools.state_storage.abc import ABCStateStorage, StateData
7
7
 
8
- Payload: typing.TypeAlias = dict[str, typing.Any]
8
+ type Payload = dict[str, typing.Any]
9
9
 
10
10
 
11
11
  class MemoryStateStorage(ABCStateStorage[Payload]):
@@ -0,0 +1,13 @@
1
+ import re
2
+
3
+ PATTERN = re.compile(r"[^ a-z A-Z 0-9 \s]")
4
+
5
+
6
+ def to_pascal_case(string: str, /) -> str:
7
+ return "".join(
8
+ "".join(char.capitalize() for char in sub_str)
9
+ for sub_str in (char.split() for char in PATTERN.split(string))
10
+ )
11
+
12
+
13
+ __all__ = ("to_pascal_case",)
@@ -2,6 +2,7 @@ from telegrinder.types.enums import *
2
2
  from telegrinder.types.objects import *
3
3
 
4
4
  __all__ = (
5
+ "AffiliateInfo",
5
6
  "Animation",
6
7
  "Audio",
7
8
  "BackgroundFill",
@@ -68,6 +69,7 @@ __all__ = (
68
69
  "ChosenInlineResult",
69
70
  "Contact",
70
71
  "ContentType",
72
+ "CopyTextButton",
71
73
  "Currency",
72
74
  "DefaultAccentColor",
73
75
  "Dice",
@@ -88,6 +90,8 @@ __all__ = (
88
90
  "GameHighScore",
89
91
  "GeneralForumTopicHidden",
90
92
  "GeneralForumTopicUnhidden",
93
+ "Gift",
94
+ "Gifts",
91
95
  "Giveaway",
92
96
  "GiveawayCompleted",
93
97
  "GiveawayCreated",
@@ -199,6 +203,7 @@ __all__ = (
199
203
  "PollOption",
200
204
  "PollType",
201
205
  "PreCheckoutQuery",
206
+ "PreparedInlineMessage",
202
207
  "ProgrammingLanguage",
203
208
  "ProximityAlertTriggered",
204
209
  "ReactionCount",
@@ -235,9 +240,12 @@ __all__ = (
235
240
  "TextQuote",
236
241
  "TopicIconColor",
237
242
  "TransactionPartner",
243
+ "TransactionPartnerAffiliateProgram",
244
+ "TransactionPartnerChat",
238
245
  "TransactionPartnerFragment",
239
246
  "TransactionPartnerOther",
240
247
  "TransactionPartnerTelegramAds",
248
+ "TransactionPartnerTelegramApi",
241
249
  "TransactionPartnerUser",
242
250
  "Update",
243
251
  "UpdateType",
@@ -52,7 +52,8 @@ class ChatAction(str, enum.Enum):
52
52
  - find_location for location data,
53
53
  - record_video_note or upload_video_note for video notes.
54
54
 
55
- Docs: https://core.telegram.org/bots/api#sendchataction"""
55
+ Docs: https://core.telegram.org/bots/api#sendchataction
56
+ """
56
57
 
57
58
  TYPING = "typing"
58
59
  UPLOAD_PHOTO = "upload_photo"
@@ -77,7 +78,8 @@ class ReactionEmoji(str, enum.Enum):
77
78
  `✍`, `🤗`, `🫡`, `🎅`, `🎄`, `☃`, `💅`, `🤪`, `🗿`, `🆒`, `💘`, `🙉`, `🦄`, `😘`, `💊`,
78
79
  `🙊`, `😎`, `👾`, `🤷‍♂`, `🤷`, `🤷‍♀`, `😡`.
79
80
 
80
- Docs: https://core.telegram.org/bots/api#reactiontypeemoji"""
81
+ Docs: https://core.telegram.org/bots/api#reactiontypeemoji
82
+ """
81
83
 
82
84
  THUMBS_UP = "👍"
83
85
  THUMBS_DOWN = "👎"
@@ -194,7 +196,8 @@ class TopicIconColor(int, enum.Enum):
194
196
 
195
197
  class ChatBoostSourceType(str, enum.Enum):
196
198
  """Type of ChatBoostSourceType
197
- Docs: https://core.telegram.org/bots/api#chatboostsource"""
199
+ Docs: https://core.telegram.org/bots/api#chatboostsource
200
+ """
198
201
 
199
202
  PREMIUM = "premium"
200
203
  GIFT_CODE = "gift_code"
@@ -263,7 +266,8 @@ class ContentType(str, enum.Enum):
263
266
 
264
267
  class Currency(str, enum.Enum):
265
268
  """Type of Currency.
266
- Docs: https://core.telegram.org/bots/payments#supported-currencies"""
269
+ Docs: https://core.telegram.org/bots/payments#supported-currencies
270
+ """
267
271
 
268
272
  AED = "AED"
269
273
  AFN = "AFN"
@@ -357,7 +361,8 @@ class Currency(str, enum.Enum):
357
361
 
358
362
  class InlineQueryResultType(str, enum.Enum):
359
363
  """Type of InlineQueryResultType.
360
- Docs: https://core.telegram.org/bots/api#inlinequeryresult"""
364
+ Docs: https://core.telegram.org/bots/api#inlinequeryresult
365
+ """
361
366
 
362
367
  AUDIO = "audio"
363
368
  DOCUMENT = "document"
@@ -425,7 +430,8 @@ class UpdateType(str, enum.Enum):
425
430
 
426
431
  class BotCommandScopeType(str, enum.Enum):
427
432
  """Type of BotCommandScope.
428
- Represents the scope to which bot commands are applied."""
433
+ Represents the scope to which bot commands are applied.
434
+ """
429
435
 
430
436
  DEFAULT = "default"
431
437
  ALL_PRIVATE_CHATS = "all_private_chats"
@@ -469,17 +475,17 @@ class DiceEmoji(str, enum.Enum):
469
475
 
470
476
 
471
477
  class MessageEntityType(str, enum.Enum):
472
- """Type of the entity.
473
- Currently, can be `mention` (`@username`), `hashtag`
474
- (`#hashtag`), `cashtag` (`$USD`), `bot_command` (`/start@jobs_bot`),
475
- `url` (`https://telegram.org`), `email` (`do-not-reply@telegram.org`),
476
- `phone_number` (`+1-212-555-0123`), `bold` (**bold text**), `italic`
477
- (*italic text*), `underline` (underlined text), `strikethrough` (strikethrough
478
- text), `spoiler` (spoiler message), `code` (monowidth string), `pre`
479
- (monowidth block), `text_link` (for clickable text URLs), `text_mention`
480
- (for users [without usernames](https://telegram.org/blog/edit#new-mentions)),
481
- `custom_emoji` (for inline custom emoji stickers), `blockquote` (blockquote)
482
- [docs](https://core.telegram.org/bots/api#messageentity)"""
478
+ """Type of the entity. Currently, can be `mention` (@username), `hashtag`
479
+ (#hashtag or #hashtag@chatusername), `cashtag` ($USD or $USD@chatusername),
480
+ `bot_command` (/start@jobs_bot), `url` (https://telegram.org), `email`
481
+ (do-not-reply@telegram.org), `phone_number` (+1-212-555-0123),
482
+ `bold` (bold text), `italic` (italic text), `underline` (underlined
483
+ text), `strikethrough` (strikethrough text), `spoiler` (spoiler message),
484
+ `blockquote` (block quotation), `expandable_blockquote` (collapsed-by-default
485
+ block quotation), `code` (monowidth string), `pre` (monowidth block),
486
+ `text_link` (for clickable text URLs), `text_mention` (for users without
487
+ usernames), `custom_emoji` (for inline custom emoji stickers).
488
+ """
483
489
 
484
490
  MENTION = "mention"
485
491
  HASHTAG = "hashtag"
@@ -512,7 +518,8 @@ class PollType(str, enum.Enum):
512
518
  class StickerType(str, enum.Enum):
513
519
  """Type of the sticker, currently one of `regular`, `mask`, `custom_emoji`.
514
520
  The type of the sticker is independent from its format, which is determined
515
- by the fields `is_animated` and `is_video`."""
521
+ by the fields `is_animated` and `is_video`.
522
+ """
516
523
 
517
524
  REGULAR = "regular"
518
525
  MASK = "mask"
@@ -521,7 +528,8 @@ class StickerType(str, enum.Enum):
521
528
 
522
529
  class MessageOriginType(str, enum.Enum):
523
530
  """Type of MessageOriginType
524
- Docs: https://core.telegram.org/bots/api#messageorigin"""
531
+ Docs: https://core.telegram.org/bots/api#messageorigin
532
+ """
525
533
 
526
534
  USER = "user"
527
535
  HIDDEN_USER = "hidden_user"
@@ -539,7 +547,8 @@ class StickerSetStickerType(str, enum.Enum):
539
547
 
540
548
  class MaskPositionPoint(str, enum.Enum):
541
549
  """The part of the face relative to which the mask should be placed. One of `forehead`,
542
- `eyes`, `mouth`, or `chin`."""
550
+ `eyes`, `mouth`, or `chin`.
551
+ """
543
552
 
544
553
  FOREHEAD = "forehead"
545
554
  EYES = "eyes"
@@ -552,7 +561,8 @@ class InlineQueryChatType(str, enum.Enum):
552
561
  either `sender` for a private chat with the inline query sender, `private`,
553
562
  `group`, `supergroup`, or `channel`. The chat type should be always known
554
563
  for requests sent from official clients and most third-party clients,
555
- unless the request was sent from a secret chat."""
564
+ unless the request was sent from a secret chat.
565
+ """
556
566
 
557
567
  SENDER = "sender"
558
568
  PRIVATE = "private"
@@ -0,0 +1,51 @@
1
+ import pathlib
2
+ import secrets
3
+ import typing
4
+
5
+ from telegrinder.msgspec_utils import encoder
6
+
7
+ type Files = dict[str, tuple[str, bytes]]
8
+
9
+
10
+ class InputFile:
11
+ """Object `InputFile`, see the [documentation](https://core.telegram.org/bots/api#inputfile).
12
+
13
+ This object represents the contents of a file to be uploaded. Must be posted using `multipart/form-data` in the usual way that files are uploaded via the browser.
14
+ """
15
+
16
+ __slots__ = ("filename", "data")
17
+
18
+ filename: str
19
+ """File name."""
20
+
21
+ data: bytes
22
+ """Bytes of file."""
23
+
24
+ def __init__(self, filename: str, data: bytes) -> None:
25
+ self.filename = filename
26
+ self.data = data
27
+
28
+ def __repr__(self) -> str:
29
+ return "{}(filename={!r}, data={!r})".format(
30
+ self.__class__.__name__,
31
+ self.filename,
32
+ (self.data[:30] + b"...") if len(self.data) > 30 else self.data,
33
+ )
34
+
35
+ @classmethod
36
+ def from_path(cls, path: str | pathlib.Path, /) -> typing.Self:
37
+ path = pathlib.Path(path)
38
+ return cls(path.name, path.read_bytes())
39
+
40
+ def _to_multipart(self, files: Files, /) -> str:
41
+ attach_name = secrets.token_urlsafe(16)
42
+ files[attach_name] = (self.filename, self.data)
43
+ return f"attach://{attach_name}"
44
+
45
+
46
+ @encoder.add_enc_hook(InputFile)
47
+ def encode_inputfile(inputfile: InputFile, files: Files) -> str:
48
+ return inputfile._to_multipart(files)
49
+
50
+
51
+ __all__ = ("InputFile",)