telegrinder 0.3.4__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 (192) hide show
  1. telegrinder/__init__.py +148 -149
  2. telegrinder/api/__init__.py +9 -8
  3. telegrinder/api/api.py +101 -93
  4. telegrinder/api/error.py +20 -16
  5. telegrinder/api/response.py +20 -20
  6. telegrinder/api/token.py +36 -36
  7. telegrinder/bot/__init__.py +72 -66
  8. telegrinder/bot/bot.py +83 -76
  9. telegrinder/bot/cute_types/__init__.py +19 -17
  10. telegrinder/bot/cute_types/base.py +184 -258
  11. telegrinder/bot/cute_types/callback_query.py +400 -385
  12. telegrinder/bot/cute_types/chat_join_request.py +62 -61
  13. telegrinder/bot/cute_types/chat_member_updated.py +157 -160
  14. telegrinder/bot/cute_types/inline_query.py +44 -43
  15. telegrinder/bot/cute_types/message.py +2590 -2637
  16. telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
  17. telegrinder/bot/cute_types/update.py +112 -104
  18. telegrinder/bot/cute_types/utils.py +62 -95
  19. telegrinder/bot/dispatch/__init__.py +59 -55
  20. telegrinder/bot/dispatch/abc.py +76 -77
  21. telegrinder/bot/dispatch/context.py +96 -98
  22. telegrinder/bot/dispatch/dispatch.py +254 -202
  23. telegrinder/bot/dispatch/handler/__init__.py +13 -13
  24. telegrinder/bot/dispatch/handler/abc.py +23 -24
  25. telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
  26. telegrinder/bot/dispatch/handler/base.py +57 -57
  27. telegrinder/bot/dispatch/handler/document_reply.py +44 -44
  28. telegrinder/bot/dispatch/handler/func.py +129 -135
  29. telegrinder/bot/dispatch/handler/media_group_reply.py +44 -43
  30. telegrinder/bot/dispatch/handler/message_reply.py +36 -36
  31. telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
  32. telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
  33. telegrinder/bot/dispatch/handler/video_reply.py +44 -44
  34. telegrinder/bot/dispatch/middleware/__init__.py +3 -3
  35. telegrinder/bot/dispatch/middleware/abc.py +97 -22
  36. telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
  37. telegrinder/bot/dispatch/process.py +151 -157
  38. telegrinder/bot/dispatch/return_manager/__init__.py +15 -13
  39. telegrinder/bot/dispatch/return_manager/abc.py +104 -108
  40. telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
  41. telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
  42. telegrinder/bot/dispatch/return_manager/message.py +36 -36
  43. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
  44. telegrinder/bot/dispatch/view/__init__.py +15 -13
  45. telegrinder/bot/dispatch/view/abc.py +45 -41
  46. telegrinder/bot/dispatch/view/base.py +231 -200
  47. telegrinder/bot/dispatch/view/box.py +140 -129
  48. telegrinder/bot/dispatch/view/callback_query.py +16 -17
  49. telegrinder/bot/dispatch/view/chat_join_request.py +11 -16
  50. telegrinder/bot/dispatch/view/chat_member.py +37 -39
  51. telegrinder/bot/dispatch/view/inline_query.py +16 -17
  52. telegrinder/bot/dispatch/view/message.py +43 -44
  53. telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
  54. telegrinder/bot/dispatch/view/raw.py +116 -114
  55. telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
  56. telegrinder/bot/dispatch/waiter_machine/actions.py +14 -13
  57. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
  58. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +55 -55
  59. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +59 -57
  60. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +51 -51
  61. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +20 -19
  62. telegrinder/bot/dispatch/waiter_machine/machine.py +251 -172
  63. telegrinder/bot/dispatch/waiter_machine/middleware.py +94 -89
  64. telegrinder/bot/dispatch/waiter_machine/short_state.py +57 -68
  65. telegrinder/bot/polling/__init__.py +4 -4
  66. telegrinder/bot/polling/abc.py +25 -25
  67. telegrinder/bot/polling/polling.py +139 -131
  68. telegrinder/bot/rules/__init__.py +85 -62
  69. telegrinder/bot/rules/abc.py +213 -206
  70. telegrinder/bot/rules/callback_data.py +122 -163
  71. telegrinder/bot/rules/chat_join.py +45 -43
  72. telegrinder/bot/rules/command.py +126 -126
  73. telegrinder/bot/rules/enum_text.py +33 -36
  74. telegrinder/bot/rules/func.py +28 -26
  75. telegrinder/bot/rules/fuzzy.py +24 -24
  76. telegrinder/bot/rules/id.py +24 -0
  77. telegrinder/bot/rules/inline.py +58 -56
  78. telegrinder/bot/rules/integer.py +21 -20
  79. telegrinder/bot/rules/is_from.py +127 -127
  80. telegrinder/bot/rules/logic.py +18 -0
  81. telegrinder/bot/rules/markup.py +42 -43
  82. telegrinder/bot/rules/mention.py +14 -14
  83. telegrinder/bot/rules/message.py +15 -17
  84. telegrinder/bot/rules/message_entities.py +33 -35
  85. telegrinder/bot/rules/node.py +33 -27
  86. telegrinder/bot/rules/payload.py +81 -0
  87. telegrinder/bot/rules/payment_invoice.py +29 -0
  88. telegrinder/bot/rules/regex.py +36 -37
  89. telegrinder/bot/rules/rule_enum.py +72 -72
  90. telegrinder/bot/rules/start.py +42 -42
  91. telegrinder/bot/rules/state.py +35 -37
  92. telegrinder/bot/rules/text.py +38 -33
  93. telegrinder/bot/rules/update.py +15 -15
  94. telegrinder/bot/scenario/__init__.py +5 -5
  95. telegrinder/bot/scenario/abc.py +17 -19
  96. telegrinder/bot/scenario/checkbox.py +174 -176
  97. telegrinder/bot/scenario/choice.py +48 -51
  98. telegrinder/client/__init__.py +12 -4
  99. telegrinder/client/abc.py +100 -75
  100. telegrinder/client/aiohttp.py +134 -130
  101. telegrinder/client/form_data.py +31 -0
  102. telegrinder/client/sonic.py +212 -0
  103. telegrinder/model.py +208 -315
  104. telegrinder/modules.py +239 -237
  105. telegrinder/msgspec_json.py +14 -14
  106. telegrinder/msgspec_utils.py +478 -410
  107. telegrinder/node/__init__.py +86 -25
  108. telegrinder/node/attachment.py +163 -87
  109. telegrinder/node/base.py +288 -160
  110. telegrinder/node/callback_query.py +54 -53
  111. telegrinder/node/command.py +34 -33
  112. telegrinder/node/composer.py +163 -198
  113. telegrinder/node/container.py +33 -27
  114. telegrinder/node/either.py +82 -0
  115. telegrinder/node/event.py +54 -65
  116. telegrinder/node/file.py +51 -0
  117. telegrinder/node/me.py +15 -16
  118. telegrinder/node/payload.py +78 -0
  119. telegrinder/node/polymorphic.py +67 -48
  120. telegrinder/node/rule.py +72 -76
  121. telegrinder/node/scope.py +36 -38
  122. telegrinder/node/source.py +87 -71
  123. telegrinder/node/text.py +53 -41
  124. telegrinder/node/tools/__init__.py +3 -3
  125. telegrinder/node/tools/generator.py +36 -40
  126. telegrinder/py.typed +0 -0
  127. telegrinder/rules.py +1 -62
  128. telegrinder/tools/__init__.py +152 -93
  129. telegrinder/tools/adapter/__init__.py +19 -0
  130. telegrinder/tools/adapter/abc.py +49 -0
  131. telegrinder/tools/adapter/dataclass.py +56 -0
  132. telegrinder/{bot/rules → tools}/adapter/errors.py +5 -5
  133. telegrinder/{bot/rules → tools}/adapter/event.py +63 -65
  134. telegrinder/{bot/rules → tools}/adapter/node.py +46 -48
  135. telegrinder/{bot/rules → tools}/adapter/raw_event.py +27 -27
  136. telegrinder/{bot/rules → tools}/adapter/raw_update.py +30 -30
  137. telegrinder/tools/buttons.py +106 -80
  138. telegrinder/tools/callback_data_serilization/__init__.py +5 -0
  139. telegrinder/tools/callback_data_serilization/abc.py +51 -0
  140. telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
  141. telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
  142. telegrinder/tools/error_handler/__init__.py +7 -7
  143. telegrinder/tools/error_handler/abc.py +30 -33
  144. telegrinder/tools/error_handler/error.py +9 -9
  145. telegrinder/tools/error_handler/error_handler.py +179 -193
  146. telegrinder/tools/formatting/__init__.py +83 -63
  147. telegrinder/tools/formatting/deep_links.py +541 -0
  148. telegrinder/tools/formatting/{html.py → html_formatter.py} +266 -294
  149. telegrinder/tools/formatting/spec_html_formats.py +71 -117
  150. telegrinder/tools/functional.py +8 -12
  151. telegrinder/tools/global_context/__init__.py +7 -7
  152. telegrinder/tools/global_context/abc.py +63 -63
  153. telegrinder/tools/global_context/global_context.py +387 -412
  154. telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
  155. telegrinder/tools/i18n/__init__.py +7 -7
  156. telegrinder/tools/i18n/abc.py +30 -30
  157. telegrinder/tools/i18n/middleware/__init__.py +3 -3
  158. telegrinder/tools/i18n/middleware/abc.py +22 -25
  159. telegrinder/tools/i18n/simple.py +43 -43
  160. telegrinder/tools/input_file_directory.py +30 -0
  161. telegrinder/tools/keyboard.py +128 -128
  162. telegrinder/tools/lifespan.py +105 -0
  163. telegrinder/tools/limited_dict.py +32 -37
  164. telegrinder/tools/loop_wrapper/__init__.py +4 -4
  165. telegrinder/tools/loop_wrapper/abc.py +20 -15
  166. telegrinder/tools/loop_wrapper/loop_wrapper.py +169 -224
  167. telegrinder/tools/magic.py +307 -157
  168. telegrinder/tools/parse_mode.py +6 -6
  169. telegrinder/tools/state_storage/__init__.py +4 -4
  170. telegrinder/tools/state_storage/abc.py +31 -35
  171. telegrinder/tools/state_storage/memory.py +25 -25
  172. telegrinder/tools/strings.py +13 -0
  173. telegrinder/types/__init__.py +268 -260
  174. telegrinder/types/enums.py +711 -701
  175. telegrinder/types/input_file.py +51 -0
  176. telegrinder/types/methods.py +5055 -4633
  177. telegrinder/types/objects.py +7058 -6950
  178. telegrinder/verification_utils.py +30 -32
  179. {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +22 -22
  180. telegrinder-0.4.0.dist-info/METADATA +144 -0
  181. telegrinder-0.4.0.dist-info/RECORD +182 -0
  182. {telegrinder-0.3.4.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
  183. telegrinder/bot/rules/adapter/__init__.py +0 -17
  184. telegrinder/bot/rules/adapter/abc.py +0 -31
  185. telegrinder/node/message.py +0 -14
  186. telegrinder/node/update.py +0 -15
  187. telegrinder/tools/formatting/links.py +0 -38
  188. telegrinder/tools/kb_set/__init__.py +0 -4
  189. telegrinder/tools/kb_set/base.py +0 -15
  190. telegrinder/tools/kb_set/yaml.py +0 -63
  191. telegrinder-0.3.4.dist-info/METADATA +0 -110
  192. telegrinder-0.3.4.dist-info/RECORD +0 -165
@@ -1,168 +1,318 @@
1
- import enum
2
- import types
3
- import typing
4
- from functools import wraps
5
-
6
- if typing.TYPE_CHECKING:
7
- from telegrinder.bot.rules.abc import ABCRule
8
- from telegrinder.node.polymorphic import Polymorphic
9
-
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]
18
-
19
- TRANSLATIONS_KEY: typing.Final[str] = "_translations"
20
- IMPL_MARK: typing.Final[str] = "_is_impl"
21
-
22
-
23
- def cache_magic_value(mark_key: str, /):
24
- def inner(func: "F") -> "F":
25
- @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]
30
-
31
- return wrapper # type: ignore
32
-
33
- return inner
34
-
35
-
36
- def resolve_arg_names(func: FuncType, start_idx: int = 1) -> tuple[str, ...]:
37
- return func.__code__.co_varnames[start_idx : func.__code__.co_argcount]
38
-
39
-
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
-
46
- defaults = func.__defaults__
47
- if not defaults:
48
- return {}
49
-
50
- return {k: defaults[i] for i, k in enumerate(resolve_arg_names(func, start_idx=0)[-len(defaults) :])}
51
-
52
-
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")
57
- return annotations
58
-
59
-
60
- def to_str(s: str | enum.Enum) -> str:
61
- if isinstance(s, enum.Enum):
62
- return str(s.value)
63
- return s
64
-
65
-
66
- @typing.overload
67
- def magic_bundle(handler: FuncType, kw: dict[str, typing.Any]) -> dict[str, typing.Any]: ...
68
-
69
-
70
- @typing.overload
71
- def magic_bundle(handler: FuncType, kw: dict[enum.Enum, typing.Any]) -> dict[str, typing.Any]: ...
72
-
73
-
74
- @typing.overload
75
- def magic_bundle(
76
- handler: FuncType,
77
- kw: dict[str, typing.Any],
78
- *,
79
- start_idx: int = 1,
80
- bundle_ctx: bool = True,
81
- ) -> dict[str, typing.Any]: ...
82
-
83
-
84
- @typing.overload
85
- def magic_bundle(
86
- handler: FuncType,
87
- kw: dict[enum.Enum, typing.Any],
88
- *,
89
- start_idx: int = 1,
90
- bundle_ctx: bool = True,
91
- ) -> dict[str, typing.Any]: ...
92
-
93
-
94
- @typing.overload
95
- def magic_bundle(
96
- handler: FuncType,
97
- kw: dict[type, typing.Any],
98
- *,
99
- typebundle: typing.Literal[True] = True,
100
- ) -> dict[str, typing.Any]: ...
101
-
102
-
103
- def magic_bundle(
104
- handler: FuncType,
105
- kw: dict[typing.Any, typing.Any],
106
- *,
107
- start_idx: int = 1,
108
- bundle_ctx: bool = True,
109
- typebundle: bool = False,
110
- ) -> dict[str, typing.Any]:
111
- 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
123
- return args
124
-
125
-
126
- def get_cached_translation(rule: "T", locale: str) -> "T | None":
127
- return getattr(rule, TRANSLATIONS_KEY, {}).get(locale)
128
-
129
-
130
- def cache_translation(base_rule: "T", locale: str, translated_rule: "T") -> None:
131
- translations = getattr(base_rule, TRANSLATIONS_KEY, {})
132
- translations[locale] = translated_rule
133
- setattr(base_rule, TRANSLATIONS_KEY, translations)
134
-
135
-
136
- @typing.cast(typing.Callable[..., Impl], lambda f: f)
137
- def impl(method: typing.Callable[..., typing.Any]):
138
- setattr(method, IMPL_MARK, True)
139
- return classmethod(method)
140
-
141
-
142
- def get_impls(cls: type["Polymorphic"]) -> list[typing.Callable[..., typing.Any]]:
143
- moprh_impls = getattr(cls, "__morph_impls__", None)
144
- if moprh_impls is not None:
145
- 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)
152
- return impls
153
-
154
-
155
- __all__ = (
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import dataclasses
5
+ import enum
6
+ import inspect
7
+ import types
8
+ import typing
9
+ from collections import OrderedDict
10
+ from functools import wraps
11
+
12
+ from fntypes import Result
13
+
14
+ from telegrinder.model import get_params
15
+
16
+ if typing.TYPE_CHECKING:
17
+ from telegrinder.bot.rules.abc import ABCRule
18
+ from telegrinder.node.polymorphic import Polymorphic
19
+
20
+ type Impl = type[classmethod]
21
+ type FuncType = types.FunctionType | typing.Callable[..., typing.Any]
22
+
23
+ type Executor[T] = typing.Callable[
24
+ [T, str, dict[str, typing.Any]],
25
+ typing.Awaitable[Result[typing.Any, typing.Any]],
26
+ ]
27
+
28
+ TRANSLATIONS_KEY: typing.Final[str] = "_translations"
29
+ IMPL_MARK: typing.Final[str] = "_is_impl"
30
+
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
+
39
+ def cache_magic_value(mark_key: str, /):
40
+ def inner[Func: typing.Callable[..., typing.Any]](func: Func) -> Func:
41
+ @wraps(func)
42
+ def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
43
+ if mark_key not in args[0].__dict__:
44
+ args[0].__dict__[mark_key] = func(*args, **kwargs)
45
+ return args[0].__dict__[mark_key]
46
+
47
+ return wrapper # type: ignore
48
+
49
+ return inner
50
+
51
+
52
+ def resolve_arg_names(func: FuncType, start_idx: int = 1) -> tuple[str, ...]:
53
+ return func.__code__.co_varnames[start_idx : func.__code__.co_argcount]
54
+
55
+
56
+ @cache_magic_value("__default_args__")
57
+ def get_default_args(func: FuncType) -> dict[str, typing.Any]:
58
+ kwdefaults = func.__kwdefaults__
59
+ if kwdefaults:
60
+ return kwdefaults
61
+
62
+ defaults = func.__defaults__
63
+ if not defaults:
64
+ return {}
65
+
66
+ return {k: defaults[i] for i, k in enumerate(resolve_arg_names(func, start_idx=0)[-len(defaults) :])}
67
+
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
+
90
+ def get_annotations(func: FuncType, *, return_type: bool = False) -> dict[str, typing.Any]:
91
+ annotations = func.__annotations__
92
+ if not return_type:
93
+ annotations.pop("return", None)
94
+ return annotations
95
+
96
+
97
+ def to_str(s: str | enum.Enum) -> str:
98
+ if isinstance(s, enum.Enum):
99
+ return str(s.value)
100
+ return s
101
+
102
+
103
+ @typing.overload
104
+ def magic_bundle(function: FuncType, kw: dict[str, typing.Any]) -> dict[str, typing.Any]: ...
105
+
106
+
107
+ @typing.overload
108
+ def magic_bundle(function: FuncType, kw: dict[enum.Enum, typing.Any]) -> dict[str, typing.Any]: ...
109
+
110
+
111
+ @typing.overload
112
+ def magic_bundle(
113
+ function: FuncType,
114
+ kw: dict[str, typing.Any],
115
+ *,
116
+ start_idx: int = 1,
117
+ bundle_ctx: bool = True,
118
+ ) -> dict[str, typing.Any]: ...
119
+
120
+
121
+ @typing.overload
122
+ def magic_bundle(
123
+ function: FuncType,
124
+ kw: dict[enum.Enum, typing.Any],
125
+ *,
126
+ start_idx: int = 1,
127
+ bundle_ctx: bool = True,
128
+ ) -> dict[str, typing.Any]: ...
129
+
130
+
131
+ @typing.overload
132
+ def magic_bundle(
133
+ function: FuncType,
134
+ kw: dict[type[typing.Any], typing.Any],
135
+ *,
136
+ typebundle: typing.Literal[True] = True,
137
+ ) -> dict[str, typing.Any]: ...
138
+
139
+
140
+ def magic_bundle(
141
+ function: FuncType,
142
+ kw: dict[typing.Any, typing.Any],
143
+ *,
144
+ start_idx: int = 1,
145
+ bundle_ctx: bool = True,
146
+ typebundle: bool = False,
147
+ ) -> dict[str, typing.Any]:
148
+ # Bundle considering the function annotations
149
+ if typebundle:
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}
159
+ if "ctx" in names and bundle_ctx:
160
+ args["ctx"] = kw
161
+
162
+ return args
163
+
164
+
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:
173
+ return getattr(rule, TRANSLATIONS_KEY, {}).get(locale)
174
+
175
+
176
+ def cache_translation[Rule: ABCRule](
177
+ base_rule: Rule,
178
+ locale: str,
179
+ translated_rule: Rule,
180
+ ) -> None:
181
+ translations = getattr(base_rule, TRANSLATIONS_KEY, {})
182
+ translations[locale] = translated_rule
183
+ setattr(base_rule, TRANSLATIONS_KEY, translations)
184
+
185
+
186
+ @typing.cast(typing.Callable[..., Impl], lambda f: f)
187
+ def impl(method: typing.Callable[..., typing.Any]):
188
+ setattr(method, IMPL_MARK, True)
189
+ return classmethod(method)
190
+
191
+
192
+ def get_impls(cls: type[Polymorphic]) -> list[typing.Callable[..., typing.Any]]:
193
+ moprh_impls = getattr(cls, "__morph_impls__", None)
194
+ if moprh_impls is not None:
195
+ return moprh_impls
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
+
205
+ setattr(cls, "__morph_impls__", impls)
206
+ return impls
207
+
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
+
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",
167
- "to_str",
168
- )
316
+ "shortcut",
317
+ "to_str",
318
+ )
@@ -1,6 +1,6 @@
1
- class ParseMode:
2
- MARKDOWNV2 = "MarkdownV2"
3
- HTML = "HTML"
4
-
5
-
6
- __all__ = ("ParseMode",)
1
+ class ParseMode:
2
+ MARKDOWNV2 = "MarkdownV2"
3
+ HTML = "HTML"
4
+
5
+
6
+ __all__ = ("ParseMode",)
@@ -1,4 +1,4 @@
1
- from telegrinder.tools.state_storage.abc import ABCStateStorage, StateData
2
- from telegrinder.tools.state_storage.memory import MemoryStateStorage
3
-
4
- __all__ = ("ABCStateStorage", "MemoryStateStorage", "StateData")
1
+ from telegrinder.tools.state_storage.abc import ABCStateStorage, StateData
2
+ from telegrinder.tools.state_storage.memory import MemoryStateStorage
3
+
4
+ __all__ = ("ABCStateStorage", "MemoryStateStorage", "StateData")
@@ -1,35 +1,31 @@
1
- import abc
2
- import enum
3
- import typing
4
- from dataclasses import dataclass
5
-
6
- from fntypes import Option
7
-
8
- from telegrinder.bot.rules.state import State, StateMeta
9
-
10
- Payload = typing.TypeVar("Payload")
11
-
12
-
13
- @dataclass(frozen=True, slots=True)
14
- class StateData(typing.Generic[Payload]):
15
- key: str | enum.Enum
16
- payload: Payload
17
-
18
-
19
- class ABCStateStorage(abc.ABC, typing.Generic[Payload]):
20
- @abc.abstractmethod
21
- async def get(self, user_id: int) -> Option[StateData[Payload]]: ...
22
-
23
- @abc.abstractmethod
24
- async def delete(self, user_id: int) -> None: ...
25
-
26
- @abc.abstractmethod
27
- async def set(self, user_id: int, key: str | enum.Enum, payload: Payload) -> None: ...
28
-
29
- def State(self, key: str | StateMeta | enum.Enum = StateMeta.ANY, /) -> State[Payload]: # noqa: N802
30
- """Can be used as a shortcut to get a state rule dependant on current storage."""
31
-
32
- return State(storage=self, key=key)
33
-
34
-
35
- __all__ = ("ABCStateStorage", "StateData")
1
+ import abc
2
+ import dataclasses
3
+ import enum
4
+
5
+ from fntypes.option import Option
6
+
7
+ from telegrinder.bot.rules.state import State, StateMeta
8
+
9
+
10
+ @dataclasses.dataclass(frozen=True, slots=True)
11
+ class StateData[Payload]:
12
+ key: str | enum.Enum
13
+ payload: Payload
14
+
15
+
16
+ class ABCStateStorage[Payload](abc.ABC):
17
+ @abc.abstractmethod
18
+ async def get(self, user_id: int) -> Option[StateData[Payload]]: ...
19
+
20
+ @abc.abstractmethod
21
+ async def delete(self, user_id: int) -> None: ...
22
+
23
+ @abc.abstractmethod
24
+ async def set(self, user_id: int, key: str | enum.Enum, payload: Payload) -> None: ...
25
+
26
+ def State(self, key: str | StateMeta | enum.Enum = StateMeta.ANY, /) -> State[Payload]: # noqa: N802
27
+ """Can be used as a shortcut to get a state rule dependant on current storage."""
28
+ return State(storage=self, key=key)
29
+
30
+
31
+ __all__ = ("ABCStateStorage", "StateData")
@@ -1,25 +1,25 @@
1
- import typing
2
-
3
- from fntypes.option import Option
4
-
5
- from telegrinder.tools.functional import from_optional
6
- from telegrinder.tools.state_storage.abc import ABCStateStorage, StateData
7
-
8
- Payload: typing.TypeAlias = dict[str, typing.Any]
9
-
10
-
11
- class MemoryStateStorage(ABCStateStorage[Payload]):
12
- def __init__(self) -> None:
13
- self.storage: dict[int, StateData[Payload]] = {}
14
-
15
- async def get(self, user_id: int) -> Option[StateData[Payload]]:
16
- return from_optional(self.storage.get(user_id))
17
-
18
- async def set(self, user_id: int, key: str, payload: Payload) -> None:
19
- self.storage[user_id] = StateData(key, payload)
20
-
21
- async def delete(self, user_id: int) -> None:
22
- self.storage.pop(user_id)
23
-
24
-
25
- __all__ = ("MemoryStateStorage",)
1
+ import typing
2
+
3
+ from fntypes.option import Option
4
+
5
+ from telegrinder.tools.functional import from_optional
6
+ from telegrinder.tools.state_storage.abc import ABCStateStorage, StateData
7
+
8
+ type Payload = dict[str, typing.Any]
9
+
10
+
11
+ class MemoryStateStorage(ABCStateStorage[Payload]):
12
+ def __init__(self) -> None:
13
+ self.storage: dict[int, StateData[Payload]] = {}
14
+
15
+ async def get(self, user_id: int) -> Option[StateData[Payload]]:
16
+ return from_optional(self.storage.get(user_id))
17
+
18
+ async def set(self, user_id: int, key: str, payload: Payload) -> None:
19
+ self.storage[user_id] = StateData(key, payload)
20
+
21
+ async def delete(self, user_id: int) -> None:
22
+ self.storage.pop(user_id)
23
+
24
+
25
+ __all__ = ("MemoryStateStorage",)
@@ -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",)