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
telegrinder/node/base.py CHANGED
@@ -1,166 +1,294 @@
1
- import abc
2
- import inspect
3
- from types import AsyncGeneratorType
4
-
5
- import typing_extensions as typing
6
-
7
- from telegrinder.node.scope import NodeScope
8
- from telegrinder.tools.magic import cache_magic_value, get_annotations
9
-
10
- T = typing.TypeVar("T", default=typing.Any)
11
-
12
- ComposeResult: typing.TypeAlias = T | typing.Awaitable[T] | typing.AsyncGenerator[T, None]
13
-
14
-
15
- def is_node(maybe_node: typing.Any) -> typing.TypeGuard[type["Node"]]:
16
- maybe_node = maybe_node if isinstance(maybe_node, type) else typing.get_origin(maybe_node)
17
- return (
18
- isinstance(maybe_node, type)
19
- and issubclass(maybe_node, Node)
20
- or isinstance(maybe_node, Node)
21
- or hasattr(maybe_node, "as_node")
22
- )
23
-
24
-
25
- @cache_magic_value("__nodes__")
26
- def get_nodes(function: typing.Callable[..., typing.Any]) -> dict[str, type["Node"]]:
27
- return {k: v for k, v in get_annotations(function).items() if is_node(v)}
28
-
29
-
30
- @cache_magic_value("__is_generator__")
31
- def is_generator(
32
- function: typing.Callable[..., typing.Any],
33
- ) -> typing.TypeGuard[AsyncGeneratorType[typing.Any, None]]:
34
- return inspect.isasyncgenfunction(function)
35
-
36
-
37
- def get_node_calc_lst(node: type["Node"]) -> list[type["Node"]]:
38
- """Returns flattened list of node types in ordering required to calculate given node.
39
- Provides caching for passed node type."""
40
-
41
- if calc_lst := getattr(node, "__nodes_calc_lst__", None):
42
- return calc_lst
43
- nodes_lst: list[type[Node]] = []
44
- for node_type in node.as_node().get_subnodes().values():
45
- nodes_lst.extend(get_node_calc_lst(node_type))
46
- calc_lst = [*nodes_lst, node]
47
- setattr(node, "__nodes_calc_lst__", calc_lst)
48
- return calc_lst
49
-
50
-
51
- class ComposeError(BaseException):
52
- pass
53
-
54
-
55
- class Node(abc.ABC):
56
- node: str = "node"
57
- scope: NodeScope = NodeScope.PER_EVENT
58
-
59
- @classmethod
60
- @abc.abstractmethod
61
- def compose(cls, *args, **kwargs) -> ComposeResult:
62
- pass
63
-
64
- @classmethod
65
- def compose_error(cls, error: str | None = None) -> typing.NoReturn:
66
- raise ComposeError(error)
67
-
68
- @classmethod
69
- def get_subnodes(cls) -> dict[str, type["Node"]]:
70
- return get_nodes(cls.compose)
71
-
72
- @classmethod
73
- def as_node(cls) -> type[typing.Self]:
74
- return cls
75
-
76
- @classmethod
77
- def is_generator(cls) -> bool:
78
- return is_generator(cls.compose)
79
-
80
-
81
- @typing.dataclass_transform(kw_only_default=True)
82
- class FactoryNode(Node, abc.ABC):
83
- node = "factory"
84
-
85
- @classmethod
86
- @abc.abstractmethod
87
- def compose(cls, *args, **kwargs) -> ComposeResult:
88
- pass
89
-
90
- def __new__(cls, **context: typing.Any) -> typing.Self:
91
- namespace = dict(**cls.__dict__)
92
- namespace.pop("__new__", None)
93
- return type(cls.__name__, (cls,), context | namespace) # type: ignore
94
-
95
-
96
- @typing.dataclass_transform()
97
- class DataNode(Node, abc.ABC):
98
- node = "data"
99
-
100
- @classmethod
101
- @abc.abstractmethod
102
- def compose(cls, *args, **kwargs) -> ComposeResult[typing.Self]:
103
- pass
104
-
105
-
106
- class ScalarNodeProto(Node, abc.ABC):
107
- @classmethod
108
- @abc.abstractmethod
109
- def compose(cls, *args, **kwargs) -> ComposeResult:
110
- pass
111
-
112
-
113
- SCALAR_NODE = type("ScalarNode", (), {"node": "scalar"})
114
-
115
-
116
- if typing.TYPE_CHECKING:
117
-
118
- class ScalarNode(ScalarNodeProto, abc.ABC):
119
- pass
120
-
121
- else:
122
-
123
- def __init_subclass__(cls, *args, **kwargs): # noqa: N807
124
- if any(issubclass(base, ScalarNodeProto) for base in cls.__bases__ if base is not ScalarNode):
125
- raise RuntimeError("Scalar nodes do not support inheritance.")
126
-
127
- def _as_node(cls, bases, dct):
128
- if not hasattr(cls, "_scalar_node_type"):
129
- dct.update(cls.__dict__)
130
- scalar_node_type = type(cls.__name__, bases, dct)
131
- setattr(cls, "_scalar_node_type", scalar_node_type)
132
- return scalar_node_type
133
- return getattr(cls, "_scalar_node_type")
134
-
135
- def create_class(name, bases, dct):
136
- return type(
137
- "Scalar",
138
- (SCALAR_NODE,),
139
- {
140
- "as_node": classmethod(lambda cls: _as_node(cls, bases, dct)),
141
- "scope": Node.scope,
142
- "__init_subclass__": __init_subclass__,
143
- },
144
- )
145
-
146
- class ScalarNode(ScalarNodeProto, abc.ABC, metaclass=create_class):
147
- pass
148
-
149
-
150
- class Name(ScalarNode, str):
151
- @classmethod
152
- def compose(cls) -> str: ...
153
-
154
-
155
- __all__ = (
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import inspect
5
+ from collections import deque
6
+ from types import AsyncGeneratorType, CodeType, resolve_bases
7
+
8
+ import typing_extensions as typing
9
+
10
+ from telegrinder.node.scope import NodeScope
11
+ from telegrinder.tools.magic import cache_magic_value, get_annotations
12
+ from telegrinder.tools.strings import to_pascal_case
13
+
14
+ if typing.TYPE_CHECKING:
15
+ from telegrinder.node.tools.generator import generate_node
16
+ else:
17
+
18
+ def generate_node(*args, **kwargs):
19
+ globalns = globals()
20
+ if "__generate_node" not in globalns:
21
+ import telegrinder.node.tools.generator
22
+
23
+ globals()["__generate_node"] = telegrinder.node.tools.generator.generate_node
24
+
25
+ return globals()["__generate_node"](*args, **kwargs)
26
+
27
+
28
+ type NodeType = Node | NodeProto[typing.Any]
29
+ type IsNode = NodeType | type[NodeType]
30
+
31
+ T = typing.TypeVar("T", default=typing.Any)
32
+
33
+ ComposeResult: typing.TypeAlias = T | typing.Awaitable[T] | typing.AsyncGenerator[T, None]
34
+
35
+ UNWRAPPED_NODE_KEY = "__unwrapped_node__"
36
+
37
+
38
+ @typing.overload
39
+ def is_node(maybe_node: type[typing.Any], /) -> typing.TypeIs[type[NodeType]]: ...
40
+
41
+
42
+ @typing.overload
43
+ def is_node(maybe_node: typing.Any, /) -> typing.TypeIs[NodeType]: ...
44
+
45
+
46
+ def is_node(maybe_node: typing.Any, /) -> bool:
47
+ if isinstance(maybe_node, typing.TypeAliasType):
48
+ maybe_node = maybe_node.__value__
49
+ if not isinstance(maybe_node, type):
50
+ maybe_node = typing.get_origin(maybe_node) or maybe_node
51
+
52
+ return (
53
+ hasattr(maybe_node, "as_node")
54
+ or isinstance(maybe_node, type)
55
+ and issubclass(maybe_node, (Node, NodeProto))
56
+ or not isinstance(maybe_node, type)
57
+ and isinstance(maybe_node, (Node, NodeProto))
58
+ )
59
+
60
+
61
+ @typing.overload
62
+ def as_node(maybe_node: type[typing.Any], /) -> type[NodeType]: ...
63
+
64
+
65
+ @typing.overload
66
+ def as_node(maybe_node: typing.Any, /) -> NodeType: ...
67
+
68
+
69
+ @typing.overload
70
+ def as_node(*maybe_nodes: type[typing.Any]) -> tuple[type[NodeType], ...]: ...
71
+
72
+
73
+ @typing.overload
74
+ def as_node(*maybe_nodes: typing.Any) -> tuple[NodeType, ...]: ...
75
+
76
+
77
+ @typing.overload
78
+ def as_node(*maybe_nodes: type[typing.Any] | typing.Any) -> tuple[IsNode, ...]: ...
79
+
80
+
81
+ def as_node(*maybe_nodes: typing.Any) -> typing.Any | tuple[typing.Any, ...]:
82
+ for maybe_node in maybe_nodes:
83
+ if not is_node(maybe_node):
84
+ is_type = isinstance(maybe_node, type)
85
+ raise LookupError(
86
+ f"{'Type of' if is_type else 'Object of type'} "
87
+ f"{maybe_node.__name__ if is_type else maybe_node.__class__.__name__!r} "
88
+ "cannot be resolved as Node."
89
+ )
90
+ return maybe_nodes[0] if len(maybe_nodes) == 1 else maybe_nodes
91
+
92
+
93
+ @cache_magic_value("__nodes__")
94
+ def get_nodes(function: typing.Callable[..., typing.Any], /) -> dict[str, type[NodeType]]:
95
+ return {k: v.as_node() for k, v in get_annotations(function).items() if is_node(v)}
96
+
97
+
98
+ @cache_magic_value("__is_generator__")
99
+ def is_generator(
100
+ function: typing.Callable[..., typing.Any],
101
+ /,
102
+ ) -> typing.TypeGuard[AsyncGeneratorType[typing.Any, None]]:
103
+ return inspect.isasyncgenfunction(function)
104
+
105
+
106
+ def unwrap_node(node: type[NodeType], /) -> tuple[type[NodeType], ...]:
107
+ """Unwrap node as flattened tuple of node types in ordering required to calculate given node.
108
+
109
+ Provides caching for passed node type.
110
+ """
111
+ if (unwrapped := getattr(node, UNWRAPPED_NODE_KEY, None)) is not None:
112
+ return unwrapped
113
+
114
+ stack = deque([(node, node.get_subnodes().values())])
115
+ visited = list[type[NodeType]]()
116
+
117
+ while stack:
118
+ parent, child_nodes = stack.pop()
119
+
120
+ if parent not in visited:
121
+ visited.insert(0, parent)
122
+
123
+ for child in child_nodes:
124
+ stack.append((child, child.get_subnodes().values()))
125
+
126
+ unwrapped = tuple(visited)
127
+ setattr(node, UNWRAPPED_NODE_KEY, unwrapped)
128
+ return unwrapped
129
+
130
+
131
+ class ComposeError(BaseException):
132
+ pass
133
+
134
+
135
+ @typing.runtime_checkable
136
+ class Composable[R](typing.Protocol):
137
+ @classmethod
138
+ def compose(cls, *args: typing.Any, **kwargs: typing.Any) -> ComposeResult[R]: ...
139
+
140
+
141
+ class NodeImpersonation(typing.Protocol):
142
+ @classmethod
143
+ def as_node(cls) -> type[NodeProto[typing.Any]]: ...
144
+
145
+
146
+ class NodeComposeFunction[R](typing.Protocol):
147
+ __name__: str
148
+ __code__: CodeType
149
+
150
+ def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> ComposeResult[R]: ...
151
+
152
+
153
+ @typing.runtime_checkable
154
+ class NodeProto[R](Composable[R], NodeImpersonation, typing.Protocol):
155
+ @classmethod
156
+ def get_subnodes(cls) -> dict[str, type[NodeType]]: ...
157
+
158
+ @classmethod
159
+ def is_generator(cls) -> bool: ...
160
+
161
+
162
+ class Node(abc.ABC):
163
+ node: str = "node"
164
+ scope: NodeScope = NodeScope.PER_EVENT
165
+
166
+ @classmethod
167
+ @abc.abstractmethod
168
+ def compose(cls, *args, **kwargs) -> ComposeResult:
169
+ pass
170
+
171
+ @classmethod
172
+ def get_subnodes(cls) -> dict[str, type[NodeType]]:
173
+ return get_nodes(cls.compose)
174
+
175
+ @classmethod
176
+ def as_node(cls) -> type[typing.Self]:
177
+ return cls
178
+
179
+ @classmethod
180
+ def is_generator(cls) -> bool:
181
+ return is_generator(cls.compose)
182
+
183
+
184
+ class scalar_node[T]: # noqa: N801
185
+ @typing.overload
186
+ def __new__(cls, x: NodeComposeFunction[Composable[T]], /) -> type[T]: ...
187
+
188
+ @typing.overload
189
+ def __new__(cls, x: NodeComposeFunction[T], /) -> type[T]: ...
190
+
191
+ @typing.overload
192
+ def __new__(
193
+ cls,
194
+ /,
195
+ *,
196
+ scope: NodeScope,
197
+ ) -> typing.Callable[[NodeComposeFunction[Composable[T]] | NodeComposeFunction[T]], type[T]]: ...
198
+
199
+ def __new__(cls, x=None, /, *, scope=NodeScope.PER_EVENT) -> typing.Any:
200
+ def inner(node_or_func, /) -> typing.Any:
201
+ namespace = {"node": "scalar", "scope": scope, "__module__": node_or_func.__module__}
202
+
203
+ if isinstance(node_or_func, type):
204
+ bases: list[type[typing.Any]] = [node_or_func]
205
+ node_bases = resolve_bases(node_or_func.__bases__)
206
+ if not any(issubclass(base, Node) for base in node_bases if isinstance(base, type)):
207
+ bases.append(Node)
208
+ return type(node_or_func.__name__, tuple(bases), namespace)
209
+ else:
210
+ base_node = generate_node(
211
+ func=node_or_func,
212
+ subnodes=tuple(get_nodes(node_or_func).values()),
213
+ )
214
+ return type(to_pascal_case(node_or_func.__name__), (base_node,), namespace)
215
+
216
+ return inner if x is None else inner(x)
217
+
218
+
219
+ @typing.dataclass_transform(kw_only_default=True)
220
+ class FactoryNode(Node, abc.ABC):
221
+ node = "factory"
222
+
223
+ @classmethod
224
+ @abc.abstractmethod
225
+ def compose(cls, *args, **kwargs) -> ComposeResult:
226
+ pass
227
+
228
+ def __new__(cls, **context: typing.Any) -> type[typing.Self]:
229
+ namespace = dict(**cls.__dict__)
230
+ namespace.pop("__new__", None)
231
+ return type(cls.__name__, (cls,), namespace | context) # type: ignore
232
+
233
+
234
+ @typing.dataclass_transform()
235
+ class DataNode(Node, abc.ABC):
236
+ node = "data"
237
+
238
+ @classmethod
239
+ @abc.abstractmethod
240
+ def compose(cls, *args, **kwargs) -> ComposeResult[typing.Self]:
241
+ pass
242
+
243
+
244
+ @scalar_node(scope=NodeScope.PER_CALL)
245
+ class Name:
246
+ @classmethod
247
+ def compose(cls) -> str: ...
248
+
249
+
250
+ class GlobalNode[Value](Node):
251
+ scope = NodeScope.GLOBAL
252
+
253
+ @classmethod
254
+ def set(cls, value: Value, /) -> None:
255
+ setattr(cls, "_value", value)
256
+
257
+ @typing.overload
258
+ @classmethod
259
+ def get(cls) -> Value: ...
260
+
261
+ @typing.overload
262
+ @classmethod
263
+ def get[Default](cls, *, default: Default) -> Value | Default: ...
264
+
265
+ @classmethod
266
+ def get(cls, **kwargs: typing.Any) -> typing.Any:
267
+ sentinel = object()
268
+ default = kwargs.pop("default", sentinel)
269
+ return getattr(cls, "_value") if default is sentinel else getattr(cls, "_value", default)
270
+
271
+ @classmethod
272
+ def unset(cls) -> None:
273
+ if hasattr(cls, "_value"):
274
+ delattr(cls, "_value")
275
+
276
+
277
+ __all__ = (
278
+ "Composable",
156
279
  "ComposeError",
157
280
  "DataNode",
158
281
  "FactoryNode",
282
+ "GlobalNode",
283
+ "IsNode",
159
284
  "Name",
160
285
  "Node",
161
- "SCALAR_NODE",
162
- "ScalarNode",
163
- "ScalarNodeProto",
286
+ "NodeImpersonation",
287
+ "NodeProto",
288
+ "NodeType",
289
+ "as_node",
164
290
  "get_nodes",
165
- "is_node",
166
- )
291
+ "is_node",
292
+ "scalar_node",
293
+ "unwrap_node",
294
+ )
@@ -1,53 +1,54 @@
1
- import typing
2
-
3
- from fntypes.result import Error, Ok
4
-
5
- from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
6
- from telegrinder.msgspec_utils import msgspec_convert
7
- from telegrinder.node.base import ComposeError, FactoryNode, Name, ScalarNode
8
- from telegrinder.node.update import UpdateNode
9
-
10
- FieldType = typing.TypeVar("FieldType")
11
-
12
-
13
- class CallbackQueryNode(ScalarNode, CallbackQueryCute):
14
- @classmethod
15
- def compose(cls, update: UpdateNode) -> CallbackQueryCute:
16
- if not update.callback_query:
17
- raise ComposeError("Update is not a callback_query.")
18
- return update.callback_query.unwrap()
19
-
20
-
21
- class CallbackQueryData(ScalarNode, dict[str, typing.Any]):
22
- @classmethod
23
- def compose(cls, callback_query: CallbackQueryNode) -> dict[str, typing.Any]:
24
- return callback_query.decode_callback_data().expect(
25
- ComposeError("Cannot complete decode callback query data.")
26
- )
27
-
28
-
29
- class _Field(FactoryNode):
30
- field_type: type[typing.Any]
31
-
32
- def __class_getitem__(cls, field_type: type[typing.Any], /) -> typing.Self:
33
- return cls(field_type=field_type)
34
-
35
- @classmethod
36
- def compose(cls, callback_query_data: CallbackQueryData, data_name: Name) -> typing.Any:
37
- if data := callback_query_data.get(data_name):
38
- match msgspec_convert(data, cls.field_type):
39
- case Ok(value):
40
- return value
41
- case Error(err):
42
- raise ComposeError(err)
43
-
44
- raise ComposeError(f"Cannot find callback data with name {data_name!r}.")
45
-
46
-
47
- if typing.TYPE_CHECKING:
48
- Field = typing.Annotated[FieldType, ...]
49
- else:
50
- Field = _Field
51
-
52
-
53
- __all__ = ("CallbackQueryData", "CallbackQueryNode", "Field")
1
+ import typing
2
+
3
+ from fntypes.result import Error, Ok
4
+
5
+ from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
6
+ from telegrinder.msgspec_utils import msgspec_convert
7
+ from telegrinder.node.base import ComposeError, FactoryNode, Name, scalar_node
8
+
9
+
10
+ @scalar_node
11
+ class CallbackQueryData:
12
+ @classmethod
13
+ def compose(cls, callback_query: CallbackQueryCute) -> str:
14
+ return callback_query.data.expect(ComposeError("Cannot complete decode callback query data."))
15
+
16
+
17
+ @scalar_node
18
+ class CallbackQueryDataJson:
19
+ @classmethod
20
+ def compose(cls, callback_query: CallbackQueryCute) -> dict:
21
+ return callback_query.decode_data().expect(
22
+ ComposeError("Cannot complete decode callback query data."),
23
+ )
24
+
25
+
26
+ class _Field(FactoryNode):
27
+ field_type: type[typing.Any]
28
+
29
+ def __class_getitem__(cls, field_type: type[typing.Any], /) -> typing.Self:
30
+ return cls(field_type=field_type)
31
+
32
+ @classmethod
33
+ def compose(cls, callback_query_data: CallbackQueryDataJson, data_name: Name) -> typing.Any:
34
+ if data := callback_query_data.get(data_name):
35
+ match msgspec_convert(data, cls.field_type):
36
+ case Ok(value):
37
+ return value
38
+ case Error(err):
39
+ raise ComposeError(err)
40
+
41
+ raise ComposeError(f"Cannot find callback data with name {data_name!r}.")
42
+
43
+
44
+ if typing.TYPE_CHECKING:
45
+ type Field[FieldType] = typing.Annotated[FieldType, ...]
46
+ else:
47
+ Field = _Field
48
+
49
+
50
+ __all__ = (
51
+ "CallbackQueryData",
52
+ "CallbackQueryDataJson",
53
+ "Field",
54
+ )
@@ -1,33 +1,34 @@
1
- import typing
2
- from dataclasses import dataclass, field
3
-
4
- from fntypes.option import Nothing, Option, Some
5
-
6
- from telegrinder.node.base import DataNode
7
- from telegrinder.node.text import Text
8
-
9
-
10
- def single_split(s: str, separator: str) -> tuple[str, str]:
11
- left, *right = s.split(separator, 1)
12
- return left, (right[0] if right else "")
13
-
14
-
15
- def cut_mention(text: str) -> tuple[str, Option[str]]:
16
- left, right = single_split(text, "@")
17
- return left, Some(right) if right else Nothing()
18
-
19
-
20
- @dataclass(slots=True)
21
- class CommandInfo(DataNode):
22
- name: str
23
- arguments: str
24
- mention: Option[str] = field(default_factory=Nothing)
25
-
26
- @classmethod
27
- def compose(cls, text: Text) -> typing.Self:
28
- name, arguments = single_split(text, separator=" ")
29
- name, mention = cut_mention(name)
30
- return cls(name, arguments, mention)
31
-
32
-
33
- __all__ = ("CommandInfo", "cut_mention", "single_split")
1
+ import typing
2
+ from dataclasses import dataclass, field
3
+
4
+ from fntypes.option import Nothing, Option, Some
5
+
6
+ from telegrinder.node.base import DataNode
7
+ from telegrinder.node.either import Either
8
+ from telegrinder.node.text import Caption, Text
9
+
10
+
11
+ def single_split(s: str, separator: str) -> tuple[str, str]:
12
+ left, *right = s.split(separator, 1)
13
+ return left, (right[0] if right else "")
14
+
15
+
16
+ def cut_mention(text: str) -> tuple[str, Option[str]]:
17
+ left, right = single_split(text, "@")
18
+ return left, Some(right) if right else Nothing()
19
+
20
+
21
+ @dataclass(slots=True)
22
+ class CommandInfo(DataNode):
23
+ name: str
24
+ arguments: str
25
+ mention: Option[str] = field(default_factory=Nothing)
26
+
27
+ @classmethod
28
+ def compose(cls, text: Either[Text, Caption]) -> typing.Self:
29
+ name, arguments = single_split(text, separator=" ")
30
+ name, mention = cut_mention(name)
31
+ return cls(name, arguments, mention)
32
+
33
+
34
+ __all__ = ("CommandInfo", "cut_mention", "single_split")