telegrinder 0.3.4.post1__py3-none-any.whl → 0.4.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 (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 +54 -128
  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 +27 -8
  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 +184 -34
  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.0.dist-info}/LICENSE +2 -2
  156. telegrinder-0.4.0.dist-info/METADATA +144 -0
  157. telegrinder-0.4.0.dist-info/RECORD +182 -0
  158. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.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,27 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import dataclasses
1
5
  import enum
6
+ import inspect
2
7
  import types
3
8
  import typing
9
+ from collections import OrderedDict
4
10
  from functools import wraps
5
11
 
12
+ from fntypes import Result
13
+
14
+ from telegrinder.model import get_params
15
+
6
16
  if typing.TYPE_CHECKING:
7
17
  from telegrinder.bot.rules.abc import ABCRule
8
18
  from telegrinder.node.polymorphic import Polymorphic
9
19
 
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
- )
20
+ type Impl = type[classmethod]
21
+ type FuncType = types.FunctionType | typing.Callable[..., typing.Any]
15
22
 
16
- Impl: typing.TypeAlias = type[classmethod]
17
- FuncType: typing.TypeAlias = types.FunctionType | typing.Callable[..., typing.Any]
23
+ type Executor[T] = typing.Callable[
24
+ [T, str, dict[str, typing.Any]],
25
+ typing.Awaitable[Result[typing.Any, typing.Any]],
26
+ ]
18
27
 
19
28
  TRANSLATIONS_KEY: typing.Final[str] = "_translations"
20
29
  IMPL_MARK: typing.Final[str] = "_is_impl"
21
30
 
22
31
 
32
+ @dataclasses.dataclass(slots=True, frozen=True)
33
+ class Shortcut[T]:
34
+ method_name: str
35
+ executor: Executor[T] | None = dataclasses.field(default=None, kw_only=True)
36
+ custom_params: set[str] = dataclasses.field(default_factory=lambda: set(), kw_only=True)
37
+
38
+
23
39
  def cache_magic_value(mark_key: str, /):
24
- def inner(func: "F") -> "F":
40
+ def inner[Func: typing.Callable[..., typing.Any]](func: Func) -> Func:
25
41
  @wraps(func)
26
42
  def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
27
43
  if mark_key not in args[0].__dict__:
@@ -50,10 +66,31 @@ def get_default_args(func: FuncType) -> dict[str, typing.Any]:
50
66
  return {k: defaults[i] for i, k in enumerate(resolve_arg_names(func, start_idx=0)[-len(defaults) :])}
51
67
 
52
68
 
69
+ @cache_magic_value("__func_parameters__")
70
+ def get_func_parameters(func: FuncType, /) -> FuncParams:
71
+ func_params: FuncParams = {"args": [], "kwargs": []}
72
+
73
+ for k, p in inspect.signature(func).parameters.items():
74
+ if k in ("self", "cls"):
75
+ continue
76
+
77
+ match p.kind:
78
+ case p.POSITIONAL_OR_KEYWORD | p.POSITIONAL_ONLY:
79
+ func_params["args"].append((k, p.default))
80
+ case p.KEYWORD_ONLY:
81
+ func_params["kwargs"].append((k, p.default))
82
+ case p.VAR_POSITIONAL:
83
+ func_params["var_args"] = k
84
+ case p.VAR_KEYWORD:
85
+ func_params["var_kwargs"] = k
86
+
87
+ return func_params
88
+
89
+
53
90
  def get_annotations(func: FuncType, *, return_type: bool = False) -> dict[str, typing.Any]:
54
91
  annotations = func.__annotations__
55
- if not return_type and "return" in func.__annotations__:
56
- annotations.pop("return")
92
+ if not return_type:
93
+ annotations.pop("return", None)
57
94
  return annotations
58
95
 
59
96
 
@@ -64,16 +101,16 @@ def to_str(s: str | enum.Enum) -> str:
64
101
 
65
102
 
66
103
  @typing.overload
67
- def magic_bundle(handler: FuncType, kw: dict[str, typing.Any]) -> dict[str, typing.Any]: ...
104
+ def magic_bundle(function: FuncType, kw: dict[str, typing.Any]) -> dict[str, typing.Any]: ...
68
105
 
69
106
 
70
107
  @typing.overload
71
- def magic_bundle(handler: FuncType, kw: dict[enum.Enum, typing.Any]) -> dict[str, typing.Any]: ...
108
+ def magic_bundle(function: FuncType, kw: dict[enum.Enum, typing.Any]) -> dict[str, typing.Any]: ...
72
109
 
73
110
 
74
111
  @typing.overload
75
112
  def magic_bundle(
76
- handler: FuncType,
113
+ function: FuncType,
77
114
  kw: dict[str, typing.Any],
78
115
  *,
79
116
  start_idx: int = 1,
@@ -83,7 +120,7 @@ def magic_bundle(
83
120
 
84
121
  @typing.overload
85
122
  def magic_bundle(
86
- handler: FuncType,
123
+ function: FuncType,
87
124
  kw: dict[enum.Enum, typing.Any],
88
125
  *,
89
126
  start_idx: int = 1,
@@ -93,41 +130,54 @@ def magic_bundle(
93
130
 
94
131
  @typing.overload
95
132
  def magic_bundle(
96
- handler: FuncType,
97
- kw: dict[type, typing.Any],
133
+ function: FuncType,
134
+ kw: dict[type[typing.Any], typing.Any],
98
135
  *,
99
136
  typebundle: typing.Literal[True] = True,
100
137
  ) -> dict[str, typing.Any]: ...
101
138
 
102
139
 
103
140
  def magic_bundle(
104
- handler: FuncType,
141
+ function: FuncType,
105
142
  kw: dict[typing.Any, typing.Any],
106
143
  *,
107
144
  start_idx: int = 1,
108
145
  bundle_ctx: bool = True,
109
146
  typebundle: bool = False,
110
147
  ) -> dict[str, typing.Any]:
148
+ # Bundle considering the function annotations
111
149
  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})
150
+ return {name: kw[t] for name, t in get_annotations(function, return_type=False).items() if t in kw}
151
+
152
+ names = resolve_arg_names(function, start_idx=start_idx)
153
+ # Determine if the function is only have the **kwargs parameter, then bundle all kw
154
+ if "var_kwargs" in get_func_parameters(function) and not names:
155
+ return {to_str(k): v for k, v in kw.items()}
156
+
157
+ # Bundle considering the function parameters and defaults
158
+ args = get_default_args(function) | {n: v for k, v in kw.items() if (n := to_str(k)) in names}
121
159
  if "ctx" in names and bundle_ctx:
122
160
  args["ctx"] = kw
161
+
123
162
  return args
124
163
 
125
164
 
126
- def get_cached_translation(rule: "T", locale: str) -> "T | None":
165
+ def join_dicts[Key: typing.Hashable, Value](
166
+ left_dict: dict[Key, typing.Any],
167
+ right_dict: dict[typing.Any, Value],
168
+ ) -> dict[Key, Value]:
169
+ return {key: right_dict[type_key] for key, type_key in left_dict.items()}
170
+
171
+
172
+ def get_cached_translation[Rule: ABCRule](rule: Rule, locale: str) -> Rule | None:
127
173
  return getattr(rule, TRANSLATIONS_KEY, {}).get(locale)
128
174
 
129
175
 
130
- def cache_translation(base_rule: "T", locale: str, translated_rule: "T") -> None:
176
+ def cache_translation[Rule: ABCRule](
177
+ base_rule: Rule,
178
+ locale: str,
179
+ translated_rule: Rule,
180
+ ) -> None:
131
181
  translations = getattr(base_rule, TRANSLATIONS_KEY, {})
132
182
  translations[locale] = translated_rule
133
183
  setattr(base_rule, TRANSLATIONS_KEY, translations)
@@ -139,30 +189,130 @@ def impl(method: typing.Callable[..., typing.Any]):
139
189
  return classmethod(method)
140
190
 
141
191
 
142
- def get_impls(cls: type["Polymorphic"]) -> list[typing.Callable[..., typing.Any]]:
192
+ def get_impls(cls: type[Polymorphic]) -> list[typing.Callable[..., typing.Any]]:
143
193
  moprh_impls = getattr(cls, "__morph_impls__", None)
144
194
  if moprh_impls is not None:
145
195
  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
- ]
196
+
197
+ impls = []
198
+ for cls_ in cls.mro():
199
+ impls += [
200
+ func.__func__
201
+ for func in vars(cls_).values()
202
+ if isinstance(func, classmethod) and getattr(func.__func__, IMPL_MARK, False)
203
+ ]
204
+
151
205
  setattr(cls, "__morph_impls__", impls)
152
206
  return impls
153
207
 
154
208
 
209
+ class FuncParams(typing.TypedDict, total=True):
210
+ args: list[tuple[str, typing.Any | inspect.Parameter.empty]]
211
+ kwargs: list[tuple[str, typing.Any | inspect.Parameter.empty]]
212
+ var_args: typing.NotRequired[str]
213
+ var_kwargs: typing.NotRequired[str]
214
+
215
+
216
+ def shortcut[T](
217
+ method_name: str,
218
+ *,
219
+ executor: Executor[T] | None = None,
220
+ custom_params: set[str] | None = None,
221
+ ):
222
+ """Decorate a cute method as a shortcut."""
223
+
224
+ def wrapper[F: typing.Callable[..., typing.Any]](func: F) -> F:
225
+ @wraps(func)
226
+ async def inner(
227
+ self: T,
228
+ *args: typing.Any,
229
+ **kwargs: typing.Any,
230
+ ) -> typing.Any:
231
+ if executor is None:
232
+ return await func(self, *args, **kwargs)
233
+
234
+ params: dict[str, typing.Any] = OrderedDict()
235
+ func_params = get_func_parameters(func)
236
+
237
+ for index, (arg, default) in enumerate(func_params["args"]):
238
+ if len(args) > index:
239
+ params[arg] = args[index]
240
+ elif default is not inspect.Parameter.empty:
241
+ params[arg] = default
242
+
243
+ if var_args := func_params.get("var_args"):
244
+ params[var_args] = args[len(func_params["args"]) :]
245
+
246
+ for kwarg, default in func_params["kwargs"]:
247
+ params[kwarg] = (
248
+ kwargs.pop(kwarg, default) if default is not inspect.Parameter.empty else kwargs.pop(kwarg)
249
+ )
250
+
251
+ if var_kwargs := func_params.get("var_kwargs"):
252
+ params[var_kwargs] = kwargs.copy()
253
+
254
+ return await executor(self, method_name, get_params(params))
255
+
256
+ inner.__shortcut__ = Shortcut( # type: ignore
257
+ method_name=method_name,
258
+ executor=executor,
259
+ custom_params=custom_params or set(),
260
+ )
261
+ return inner # type: ignore
262
+
263
+ return wrapper
264
+
265
+
266
+ # Source code: https://github.com/facebookincubator/later/blob/main/later/task.py#L75
267
+ async def cancel_future(fut: asyncio.Future[typing.Any], /) -> None:
268
+ if fut.done():
269
+ return
270
+
271
+ fut.cancel()
272
+ exc: asyncio.CancelledError | None = None
273
+
274
+ while not fut.done():
275
+ shielded = asyncio.shield(fut)
276
+ try:
277
+ await asyncio.wait([shielded])
278
+ except asyncio.CancelledError as ex:
279
+ exc = ex
280
+ finally:
281
+ # Insure we handle the exception/value that may exist on the shielded task
282
+ # This will prevent errors logged to the asyncio logger
283
+ if shielded.done() and not shielded.cancelled() and not shielded.exception():
284
+ shielded.result()
285
+
286
+ if fut.cancelled():
287
+ if exc is None:
288
+ return
289
+ raise exc from None
290
+
291
+ ex = fut.exception()
292
+ if ex is not None:
293
+ raise ex from None
294
+
295
+ raise asyncio.InvalidStateError(
296
+ f"Task did not raise CancelledError on cancel: {fut!r} had result {fut.result()!r}",
297
+ )
298
+
299
+
155
300
  __all__ = (
301
+ "Shortcut",
156
302
  "TRANSLATIONS_KEY",
157
303
  "cache_magic_value",
158
304
  "cache_translation",
305
+ "cancel_future",
159
306
  "get_annotations",
160
307
  "get_cached_translation",
161
308
  "get_default_args",
162
309
  "get_default_args",
310
+ "get_func_parameters",
163
311
  "get_impls",
164
312
  "impl",
313
+ "join_dicts",
165
314
  "magic_bundle",
166
315
  "resolve_arg_names",
316
+ "shortcut",
167
317
  "to_str",
168
318
  )
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",)