telegrinder 0.1.dev164__py3-none-any.whl → 0.1.dev166__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 (93) hide show
  1. telegrinder/__init__.py +58 -0
  2. telegrinder/api/abc.py +1 -1
  3. telegrinder/api/api.py +8 -6
  4. telegrinder/api/error.py +2 -3
  5. telegrinder/bot/__init__.py +14 -0
  6. telegrinder/bot/bot.py +13 -2
  7. telegrinder/bot/cute_types/__init__.py +4 -0
  8. telegrinder/bot/cute_types/base.py +22 -13
  9. telegrinder/bot/cute_types/chat_join_request.py +63 -0
  10. telegrinder/bot/cute_types/chat_member_updated.py +244 -0
  11. telegrinder/bot/cute_types/message.py +33 -6
  12. telegrinder/bot/cute_types/update.py +5 -4
  13. telegrinder/bot/cute_types/utils.py +39 -17
  14. telegrinder/bot/dispatch/__init__.py +9 -1
  15. telegrinder/bot/dispatch/composition.py +10 -8
  16. telegrinder/bot/dispatch/context.py +9 -10
  17. telegrinder/bot/dispatch/dispatch.py +29 -19
  18. telegrinder/bot/dispatch/handler/abc.py +1 -0
  19. telegrinder/bot/dispatch/handler/func.py +29 -6
  20. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  21. telegrinder/bot/dispatch/middleware/abc.py +2 -4
  22. telegrinder/bot/dispatch/process.py +4 -3
  23. telegrinder/bot/dispatch/return_manager/__init__.py +1 -1
  24. telegrinder/bot/dispatch/return_manager/abc.py +39 -21
  25. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -2
  26. telegrinder/bot/dispatch/return_manager/inline_query.py +4 -2
  27. telegrinder/bot/dispatch/return_manager/message.py +12 -6
  28. telegrinder/bot/dispatch/view/__init__.py +8 -2
  29. telegrinder/bot/dispatch/view/abc.py +26 -20
  30. telegrinder/bot/dispatch/view/box.py +72 -1
  31. telegrinder/bot/dispatch/view/callback_query.py +1 -3
  32. telegrinder/bot/dispatch/view/chat_join_request.py +17 -0
  33. telegrinder/bot/dispatch/view/chat_member.py +26 -0
  34. telegrinder/bot/dispatch/view/message.py +23 -1
  35. telegrinder/bot/dispatch/view/raw.py +116 -0
  36. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -1
  37. telegrinder/bot/dispatch/waiter_machine/machine.py +73 -19
  38. telegrinder/bot/dispatch/waiter_machine/middleware.py +3 -3
  39. telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -3
  40. telegrinder/bot/polling/polling.py +17 -1
  41. telegrinder/bot/rules/__init__.py +20 -12
  42. telegrinder/bot/rules/abc.py +0 -9
  43. telegrinder/bot/rules/adapter/event.py +31 -22
  44. telegrinder/bot/rules/callback_data.py +15 -20
  45. telegrinder/bot/rules/chat_join.py +47 -0
  46. telegrinder/bot/rules/enum_text.py +7 -2
  47. telegrinder/bot/rules/inline.py +3 -3
  48. telegrinder/bot/rules/is_from.py +36 -50
  49. telegrinder/bot/rules/markup.py +1 -1
  50. telegrinder/bot/rules/message.py +17 -0
  51. telegrinder/bot/rules/message_entities.py +1 -1
  52. telegrinder/bot/rules/start.py +6 -4
  53. telegrinder/bot/rules/text.py +2 -1
  54. telegrinder/bot/rules/update.py +16 -0
  55. telegrinder/bot/scenario/checkbox.py +9 -7
  56. telegrinder/client/aiohttp.py +4 -4
  57. telegrinder/model.py +43 -19
  58. telegrinder/modules.py +16 -32
  59. telegrinder/msgspec_utils.py +48 -36
  60. telegrinder/node/__init__.py +12 -12
  61. telegrinder/node/attachment.py +15 -5
  62. telegrinder/node/base.py +24 -16
  63. telegrinder/node/composer.py +8 -6
  64. telegrinder/node/container.py +1 -1
  65. telegrinder/node/rule.py +4 -4
  66. telegrinder/node/source.py +4 -2
  67. telegrinder/node/tools/generator.py +1 -1
  68. telegrinder/tools/__init__.py +2 -1
  69. telegrinder/tools/error_handler/abc.py +4 -3
  70. telegrinder/tools/error_handler/error_handler.py +22 -16
  71. telegrinder/tools/formatting/html.py +20 -18
  72. telegrinder/tools/formatting/links.py +12 -6
  73. telegrinder/tools/formatting/spec_html_formats.py +5 -6
  74. telegrinder/tools/global_context/abc.py +5 -1
  75. telegrinder/tools/global_context/global_context.py +2 -2
  76. telegrinder/tools/global_context/telegrinder_ctx.py +1 -1
  77. telegrinder/tools/i18n/base.py +4 -3
  78. telegrinder/tools/i18n/simple.py +1 -3
  79. telegrinder/tools/keyboard.py +1 -1
  80. telegrinder/tools/loop_wrapper/__init__.py +2 -2
  81. telegrinder/tools/loop_wrapper/abc.py +1 -4
  82. telegrinder/tools/loop_wrapper/loop_wrapper.py +90 -36
  83. telegrinder/tools/magic.py +1 -1
  84. telegrinder/types/__init__.py +206 -0
  85. telegrinder/types/enums.py +34 -0
  86. telegrinder/types/methods.py +52 -47
  87. telegrinder/types/objects.py +531 -88
  88. telegrinder/verification_utils.py +33 -0
  89. {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/METADATA +1 -1
  90. telegrinder-0.1.dev166.dist-info/RECORD +136 -0
  91. telegrinder-0.1.dev164.dist-info/RECORD +0 -127
  92. {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/LICENSE +0 -0
  93. {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/WHEEL +0 -0
@@ -15,15 +15,14 @@ else:
15
15
 
16
16
  datetime = type("datetime", (dt,), {})
17
17
 
18
-
19
18
  class OptionMeta(type):
20
19
  def __instancecheck__(cls, __instance: typing.Any) -> bool:
21
20
  return isinstance(__instance, fntypes.option.Some | fntypes.option.Nothing)
22
21
 
23
-
24
22
  class Option(typing.Generic[Value], metaclass=OptionMeta):
25
23
  pass
26
24
 
25
+
27
26
  T = typing.TypeVar("T")
28
27
  Ts = typing.TypeVarTuple("Ts")
29
28
 
@@ -51,14 +50,13 @@ def msgspec_convert(obj: typing.Any, t: type[T]) -> Result[T, msgspec.Validation
51
50
  def option_dec_hook(tp: type[Option[typing.Any]], obj: typing.Any) -> Option[typing.Any]:
52
51
  if obj is None:
53
52
  return Nothing
54
- generic_args = typing.get_args(tp)
55
- value_type: typing.Any | type[typing.Any] = typing.Any if not generic_args else generic_args[0]
53
+ (value_type,) = typing.get_args(tp) or (typing.Any,)
56
54
  return msgspec_convert({"value": obj}, fntypes.option.Some[value_type]).unwrap()
57
55
 
58
56
 
59
57
  def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
60
58
  union_types = typing.get_args(tp)
61
-
59
+
62
60
  if isinstance(obj, dict):
63
61
  struct_fields_match_sums: dict[type[msgspec.Struct], int] = {
64
62
  m: sum(1 for k in obj if k in m.__struct_fields__)
@@ -68,15 +66,23 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
68
66
  union_types = tuple(t for t in union_types if t not in struct_fields_match_sums)
69
67
  reverse = False
70
68
 
71
- if len(set(struct_fields_match_sums.values())) != len(struct_fields_match_sums.values()):
72
- struct_fields_match_sums = {m: len(m.__struct_fields__) for m in struct_fields_match_sums}
69
+ if len(set(struct_fields_match_sums.values())) != len(
70
+ struct_fields_match_sums.values()
71
+ ):
72
+ struct_fields_match_sums = {
73
+ m: len(m.__struct_fields__) for m in struct_fields_match_sums
74
+ }
73
75
  reverse = True
74
76
 
75
77
  union_types = (
76
- *sorted(struct_fields_match_sums, key=lambda k: struct_fields_match_sums[k], reverse=reverse),
78
+ *sorted(
79
+ struct_fields_match_sums,
80
+ key=lambda k: struct_fields_match_sums[k],
81
+ reverse=reverse,
82
+ ),
77
83
  *union_types,
78
84
  )
79
-
85
+
80
86
  for t in union_types:
81
87
  match msgspec_convert(obj, t):
82
88
  case Ok(value):
@@ -93,7 +99,7 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
93
99
  class Decoder:
94
100
  """Class `Decoder` for `msgspec` module with decode hook
95
101
  for objects with the specified type.
96
-
102
+
97
103
  ```
98
104
  import enum
99
105
 
@@ -124,12 +130,18 @@ class Decoder:
124
130
  datetime: lambda t, obj: t.fromtimestamp(obj),
125
131
  }
126
132
 
133
+ def __repr__(self) -> str:
134
+ return "<{}: dec_hooks={!r}>".format(
135
+ self.__class__.__name__,
136
+ self.dec_hooks,
137
+ )
138
+
127
139
  def add_dec_hook(self, t: T): # type: ignore
128
140
  def decorator(func: DecHook[T]) -> DecHook[T]:
129
141
  return self.dec_hooks.setdefault(get_origin(t), func) # type: ignore
130
-
142
+
131
143
  return decorator
132
-
144
+
133
145
  def dec_hook(self, tp: type[typing.Any], obj: object) -> object:
134
146
  origin_type = t if isinstance((t := get_origin(tp)), type) else type(t)
135
147
  if origin_type not in self.dec_hooks:
@@ -138,7 +150,7 @@ class Decoder:
138
150
  "You can implement decode hook for this type."
139
151
  )
140
152
  return self.dec_hooks[origin_type](tp, obj)
141
-
153
+
142
154
  def convert(
143
155
  self,
144
156
  obj: object,
@@ -158,14 +170,12 @@ class Decoder:
158
170
  builtin_types=builtin_types,
159
171
  str_keys=str_keys,
160
172
  )
161
-
173
+
162
174
  @typing.overload
163
- def decode(self, buf: str | bytes) -> typing.Any:
164
- ...
165
-
175
+ def decode(self, buf: str | bytes) -> typing.Any: ...
176
+
166
177
  @typing.overload
167
- def decode(self, buf: str | bytes, *, strict: bool = True) -> typing.Any:
168
- ...
178
+ def decode(self, buf: str | bytes, *, strict: bool = True) -> typing.Any: ...
169
179
 
170
180
  @typing.overload
171
181
  def decode(
@@ -174,8 +184,7 @@ class Decoder:
174
184
  *,
175
185
  type: type[T],
176
186
  strict: bool = True,
177
- ) -> T:
178
- ...
187
+ ) -> T: ...
179
188
 
180
189
  def decode(
181
190
  self,
@@ -194,7 +203,7 @@ class Decoder:
194
203
 
195
204
  class Encoder:
196
205
  """Class `Encoder` for `msgspec` module with encode hooks for objects.
197
-
206
+
198
207
  ```
199
208
  from datetime import datetime as dt
200
209
 
@@ -216,13 +225,19 @@ class Encoder:
216
225
  datetime: lambda date: int(date.timestamp()),
217
226
  }
218
227
 
228
+ def __repr__(self) -> str:
229
+ return "<{}: enc_hooks={!r}>".format(
230
+ self.__class__.__name__,
231
+ self.enc_hooks,
232
+ )
233
+
219
234
  def add_dec_hook(self, t: type[T]):
220
235
  def decorator(func: EncHook[T]) -> EncHook[T]:
221
236
  encode_hook = self.enc_hooks.setdefault(get_origin(t), func)
222
237
  return func if encode_hook is not func else encode_hook
223
-
238
+
224
239
  return decorator
225
-
240
+
226
241
  def enc_hook(self, obj: object) -> object:
227
242
  origin_type = get_origin(obj.__class__)
228
243
  if origin_type not in self.enc_hooks:
@@ -233,16 +248,13 @@ class Encoder:
233
248
  return self.enc_hooks[origin_type](obj)
234
249
 
235
250
  @typing.overload
236
- def encode(self, obj: typing.Any) -> str:
237
- ...
238
-
251
+ def encode(self, obj: typing.Any) -> str: ...
252
+
239
253
  @typing.overload
240
- def encode(self, obj: typing.Any, *, as_str: typing.Literal[True]) -> str:
241
- ...
254
+ def encode(self, obj: typing.Any, *, as_str: typing.Literal[True]) -> str: ...
242
255
 
243
256
  @typing.overload
244
- def encode(self, obj: typing.Any, *, as_str: typing.Literal[False]) -> bytes:
245
- ...
257
+ def encode(self, obj: typing.Any, *, as_str: typing.Literal[False]) -> bytes: ...
246
258
 
247
259
  def encode(self, obj: typing.Any, *, as_str: bool = True) -> str | bytes:
248
260
  buf = msgspec.json.encode(obj, enc_hook=self.enc_hook)
@@ -256,14 +268,14 @@ encoder: typing.Final[Encoder] = Encoder()
256
268
  __all__ = (
257
269
  "Decoder",
258
270
  "Encoder",
259
- "Option",
260
271
  "Nothing",
272
+ "Option",
273
+ "datetime",
274
+ "decoder",
275
+ "encoder",
261
276
  "get_origin",
262
- "repr_type",
263
277
  "msgspec_convert",
264
278
  "option_dec_hook",
279
+ "repr_type",
265
280
  "variative_dec_hook",
266
- "datetime",
267
- "decoder",
268
- "encoder",
269
281
  )
@@ -10,22 +10,22 @@ from .tools import generate
10
10
  from .update import UpdateNode
11
11
 
12
12
  __all__ = (
13
- "Node",
14
- "DataNode",
15
- "ScalarNode",
16
13
  "Attachment",
17
- "Photo",
18
- "Video",
19
- "Text",
20
14
  "Audio",
21
- "UpdateNode",
22
- "compose_node",
23
15
  "ComposeError",
16
+ "ContainerNode",
17
+ "DataNode",
24
18
  "MessageNode",
25
- "Source",
26
- "NodeSession",
19
+ "Node",
27
20
  "NodeCollection",
28
- "ContainerNode",
29
- "generate",
21
+ "NodeSession",
22
+ "Photo",
30
23
  "RuleContext",
24
+ "ScalarNode",
25
+ "Source",
26
+ "Text",
27
+ "UpdateNode",
28
+ "Video",
29
+ "compose_node",
30
+ "generate",
31
31
  )
@@ -14,11 +14,21 @@ from .message import MessageNode
14
14
  class Attachment(DataNode):
15
15
  attachment_type: typing.Literal["audio", "document", "photo", "poll", "video"]
16
16
  _: dataclasses.KW_ONLY
17
- audio: Option[telegrinder.types.Audio] = dataclasses.field(default_factory=lambda: Nothing())
18
- document: Option[telegrinder.types.Document] = dataclasses.field(default_factory=lambda: Nothing())
19
- photo: Option[list[telegrinder.types.PhotoSize]] = dataclasses.field(default_factory=lambda: Nothing())
20
- poll: Option[telegrinder.types.Poll] = dataclasses.field(default_factory=lambda: Nothing())
21
- video: Option[telegrinder.types.Video] = dataclasses.field(default_factory=lambda: Nothing())
17
+ audio: Option[telegrinder.types.Audio] = dataclasses.field(
18
+ default_factory=lambda: Nothing()
19
+ )
20
+ document: Option[telegrinder.types.Document] = dataclasses.field(
21
+ default_factory=lambda: Nothing()
22
+ )
23
+ photo: Option[list[telegrinder.types.PhotoSize]] = dataclasses.field(
24
+ default_factory=lambda: Nothing()
25
+ )
26
+ poll: Option[telegrinder.types.Poll] = dataclasses.field(
27
+ default_factory=lambda: Nothing()
28
+ )
29
+ video: Option[telegrinder.types.Video] = dataclasses.field(
30
+ default_factory=lambda: Nothing()
31
+ )
22
32
 
23
33
  @classmethod
24
34
  async def compose(cls, message: MessageNode) -> "Attachment":
telegrinder/node/base.py CHANGED
@@ -2,11 +2,13 @@ import abc
2
2
  import inspect
3
3
  import typing
4
4
 
5
- ComposeResult: typing.TypeAlias = typing.Coroutine[typing.Any, typing.Any, typing.Any] | typing.AsyncGenerator[typing.Any, None]
5
+ ComposeResult: typing.TypeAlias = (
6
+ typing.Coroutine[typing.Any, typing.Any, typing.Any]
7
+ | typing.AsyncGenerator[typing.Any, None]
8
+ )
6
9
 
7
10
 
8
- class ComposeError(BaseException):
9
- ...
11
+ class ComposeError(BaseException): ...
10
12
 
11
13
 
12
14
  class Node(abc.ABC):
@@ -14,7 +16,9 @@ class Node(abc.ABC):
14
16
 
15
17
  @classmethod
16
18
  @abc.abstractmethod
17
- def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
19
+ def compose(
20
+ cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
21
+ ) -> ComposeResult:
18
22
  pass
19
23
 
20
24
  @classmethod
@@ -24,7 +28,7 @@ class Node(abc.ABC):
24
28
  @classmethod
25
29
  def get_sub_nodes(cls) -> dict[str, type[typing.Self]]:
26
30
  parameters = inspect.signature(cls.compose).parameters
27
-
31
+
28
32
  sub_nodes = {}
29
33
  for name, param in parameters.items():
30
34
  if param.annotation is inspect._empty:
@@ -32,11 +36,11 @@ class Node(abc.ABC):
32
36
  node = param.annotation
33
37
  sub_nodes[name] = node
34
38
  return sub_nodes
35
-
39
+
36
40
  @classmethod
37
41
  def as_node(cls) -> type[typing.Self]:
38
42
  return cls
39
-
43
+
40
44
  @classmethod
41
45
  def is_generator(cls) -> bool:
42
46
  return inspect.isasyncgenfunction(cls.compose)
@@ -48,14 +52,18 @@ class DataNode(Node, abc.ABC):
48
52
  @typing.dataclass_transform()
49
53
  @classmethod
50
54
  @abc.abstractmethod
51
- async def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
55
+ async def compose(
56
+ cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
57
+ ) -> ComposeResult:
52
58
  pass
53
59
 
54
60
 
55
61
  class ScalarNodeProto(Node, abc.ABC):
56
62
  @classmethod
57
63
  @abc.abstractmethod
58
- async def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
64
+ async def compose(
65
+ cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
66
+ ) -> ComposeResult:
59
67
  pass
60
68
 
61
69
 
@@ -64,9 +72,9 @@ SCALAR_NODE = type("ScalarNode", (), {"node": "scalar"})
64
72
 
65
73
  if typing.TYPE_CHECKING:
66
74
 
67
- class ScalarNode(ScalarNodeProto, abc.ABC):
75
+ class ScalarNode(ScalarNodeProto, abc.ABC):
68
76
  pass
69
-
77
+
70
78
  else:
71
79
 
72
80
  def create_node(cls, bases, dct):
@@ -75,8 +83,8 @@ else:
75
83
 
76
84
  def create_class(name, bases, dct):
77
85
  return type(
78
- "Scalar",
79
- (SCALAR_NODE,),
86
+ "Scalar",
87
+ (SCALAR_NODE,),
80
88
  {"as_node": classmethod(lambda cls: create_node(cls, bases, dct))},
81
89
  )
82
90
 
@@ -85,9 +93,9 @@ else:
85
93
 
86
94
 
87
95
  __all__ = (
88
- "ScalarNode",
89
- "SCALAR_NODE",
96
+ "ComposeError",
90
97
  "DataNode",
91
98
  "Node",
92
- "ComposeError",
99
+ "SCALAR_NODE",
100
+ "ScalarNode",
93
101
  )
@@ -6,7 +6,7 @@ from telegrinder.node import Node
6
6
 
7
7
  class NodeSession:
8
8
  def __init__(
9
- self,
9
+ self,
10
10
  value: typing.Any,
11
11
  subnodes: dict[str, typing.Self],
12
12
  generator: typing.AsyncGenerator[typing.Any, None] | None = None,
@@ -14,11 +14,11 @@ class NodeSession:
14
14
  self.value = value
15
15
  self.subnodes = subnodes
16
16
  self.generator = generator
17
-
17
+
18
18
  async def close(self, with_value: typing.Any | None = None) -> None:
19
19
  for subnode in self.subnodes.values():
20
20
  await subnode.close()
21
-
21
+
22
22
  if self.generator is None:
23
23
  return
24
24
  try:
@@ -27,7 +27,9 @@ class NodeSession:
27
27
  self.generator = None
28
28
 
29
29
  def __repr__(self) -> str:
30
- return f"<NodeSession: {self.value}" + ("ACTIVE>" if self.generator else ">")
30
+ return f"<{self.__class__.__name__}: {self.value}" + (
31
+ "ACTIVE>" if self.generator else ">"
32
+ )
31
33
 
32
34
 
33
35
  class NodeCollection:
@@ -36,7 +38,7 @@ class NodeCollection:
36
38
 
37
39
  def values(self) -> dict[str, typing.Any]:
38
40
  return {name: session.value for name, session in self.sessions.items()}
39
-
41
+
40
42
  async def close_all(self, with_value: typing.Any | None = None) -> None:
41
43
  for session in self.sessions.values():
42
44
  await session.close(with_value)
@@ -64,7 +66,7 @@ async def compose_node(
64
66
  else:
65
67
  generator = None
66
68
  value = await _node.compose(**context.values()) # type: ignore
67
-
69
+
68
70
  return NodeSession(value, context.sessions, generator)
69
71
 
70
72
 
@@ -13,7 +13,7 @@ class ContainerNode(Node):
13
13
  @classmethod
14
14
  def get_sub_nodes(cls) -> dict[str, type["Node"]]:
15
15
  return {f"node_{i}": node_t for i, node_t in enumerate(cls.linked_nodes)}
16
-
16
+
17
17
  @classmethod
18
18
  def link_nodes(cls, linked_nodes: list[type[Node]]) -> type["ContainerNode"]:
19
19
  return type("_ContainerNode", (cls,), {"linked_nodes": linked_nodes})
telegrinder/node/rule.py CHANGED
@@ -26,7 +26,7 @@ class RuleContext(dict):
26
26
  @classmethod
27
27
  def as_node(cls) -> type[typing.Self]:
28
28
  return cls
29
-
29
+
30
30
  @classmethod
31
31
  def get_sub_nodes(cls) -> dict:
32
32
  return {"update": UpdateNode}
@@ -37,16 +37,16 @@ class RuleContext(dict):
37
37
 
38
38
  def __new__(cls, *rules: ABCRule) -> type[Node]:
39
39
  return type("_RuleNode", (cls,), {"dataclass": dict, "rules": rules}) # type: ignore
40
-
40
+
41
41
  def __class_getitem__(cls, item: tuple[ABCRule, ...]) -> typing.Self:
42
42
  if not isinstance(item, tuple):
43
43
  item = (item,)
44
44
  return cls(*item)
45
-
45
+
46
46
  @staticmethod
47
47
  def generate_dataclass(cls_: type["RuleContext"]): # noqa: ANN205
48
48
  return dataclasses.dataclass(type(cls_.__name__, (object,), dict(cls_.__dict__)))
49
-
49
+
50
50
  def __init_subclass__(cls) -> None:
51
51
  if cls.__name__ == "_RuleNode":
52
52
  return
@@ -21,9 +21,11 @@ class Source(DataNode):
21
21
  chat=message.chat,
22
22
  thread_id=message.message_thread_id.unwrap_or_none(),
23
23
  )
24
-
24
+
25
25
  async def send(self, text: str) -> Message:
26
- result = await self.api.send_message(self.chat.id, message_thread_id=self.thread_id, text=text)
26
+ result = await self.api.send_message(
27
+ self.chat.id, message_thread_id=self.thread_id, text=text
28
+ )
27
29
  return result.unwrap()
28
30
 
29
31
 
@@ -20,7 +20,7 @@ def error_on_none(value: T | None) -> T:
20
20
 
21
21
 
22
22
  def generate(
23
- subnodes: tuple[type[Node], ...],
23
+ subnodes: tuple[type[Node], ...],
24
24
  func: typing.Callable[..., typing.Any],
25
25
  casts: tuple[typing.Callable, ...] = (cast_false_to_none, error_on_none),
26
26
  ) -> type[ContainerNode]:
@@ -66,7 +66,7 @@ from .keyboard import (
66
66
  Keyboard,
67
67
  RowButtons,
68
68
  )
69
- from .loop_wrapper import ABCLoopWrapper, DelayedTask, LoopWrapper
69
+ from .loop_wrapper import ABCLoopWrapper, DelayedTask, Lifespan, LoopWrapper
70
70
  from .magic import magic_bundle, resolve_arg_names
71
71
  from .parse_mode import ParseMode
72
72
 
@@ -98,6 +98,7 @@ __all__ = (
98
98
  "Keyboard",
99
99
  "KeyboardSetBase",
100
100
  "KeyboardSetYAML",
101
+ "Lifespan",
101
102
  "Link",
102
103
  "LoopWrapper",
103
104
  "Mention",
@@ -4,10 +4,9 @@ from abc import ABC, abstractmethod
4
4
  from fntypes.result import Result
5
5
 
6
6
  from telegrinder.api import ABCAPI
7
- from telegrinder.bot.cute_types import BaseCute
8
7
  from telegrinder.bot.dispatch.context import Context
9
8
 
10
- EventT = typing.TypeVar("EventT", bound=BaseCute)
9
+ EventT = typing.TypeVar("EventT")
11
10
  Handler = typing.Callable[typing.Concatenate[EventT, ...], typing.Awaitable[typing.Any]]
12
11
 
13
12
 
@@ -17,7 +16,9 @@ class ABCErrorHandler(ABC, typing.Generic[EventT]):
17
16
  self,
18
17
  *args: typing.Any,
19
18
  **kwargs: typing.Any,
20
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Callable[..., typing.Any]]:
19
+ ) -> typing.Callable[
20
+ [typing.Callable[..., typing.Any]], typing.Callable[..., typing.Any]
21
+ ]:
21
22
  """Decorator for registering callback as an error handler."""
22
23
 
23
24
  @abstractmethod
@@ -13,7 +13,9 @@ from .error import CatcherError
13
13
 
14
14
  F = typing.TypeVar("F", bound="FuncCatcher")
15
15
  ExceptionT = typing.TypeVar("ExceptionT", bound=BaseException, contravariant=True)
16
- FuncCatcher = typing.Callable[typing.Concatenate[ExceptionT, ...], typing.Awaitable[typing.Any]]
16
+ FuncCatcher = typing.Callable[
17
+ typing.Concatenate[ExceptionT, ...], typing.Awaitable[typing.Any]
18
+ ]
17
19
 
18
20
 
19
21
  @dataclasses.dataclass(frozen=True, repr=False)
@@ -71,7 +73,7 @@ class Catcher(typing.Generic[EventT]):
71
73
  )
72
74
  logger.debug("Failed to match exception {!r}!", exception.__class__.__name__)
73
75
  return Error(exception)
74
-
76
+
75
77
  def match_exception(self, exception: BaseException) -> bool:
76
78
  for exc in self.exceptions:
77
79
  if isinstance(exc, type) and type(exception) is exc:
@@ -84,10 +86,11 @@ class Catcher(typing.Generic[EventT]):
84
86
  class ErrorHandler(ABCErrorHandler[EventT]):
85
87
  def __init__(self, catcher: Catcher[EventT] | None = None, /) -> None:
86
88
  self.catcher = catcher
87
-
89
+
88
90
  def __repr__(self) -> str:
89
91
  return (
90
- "<ErrorHandler: exceptions_handled=[{}], catcher={!r}>".format(
92
+ "<{}: exceptions_handled=[{}], catcher={!r}>".format(
93
+ self.__class__.__name__,
91
94
  ", ".join(
92
95
  e.__name__ if isinstance(e, type) else repr(e)
93
96
  for e in self.catcher.exceptions
@@ -95,7 +98,7 @@ class ErrorHandler(ABCErrorHandler[EventT]):
95
98
  self.catcher,
96
99
  )
97
100
  if self.catcher is not None
98
- else "<ErrorHandler: No catcher>"
101
+ else "<{}: No catcher>".format(self.__class__.__name__)
99
102
  )
100
103
 
101
104
  def __call__(
@@ -121,8 +124,9 @@ class ErrorHandler(ABCErrorHandler[EventT]):
121
124
  ignore_errors=ignore_errors,
122
125
  )
123
126
  return func
127
+
124
128
  return decorator
125
-
129
+
126
130
  async def process(
127
131
  self,
128
132
  handler: Handler[EventT],
@@ -135,23 +139,25 @@ class ErrorHandler(ABCErrorHandler[EventT]):
135
139
  try:
136
140
  return await self.catcher(handler, event, api, ctx)
137
141
  except BaseException as exc:
138
- return Error(CatcherError(
139
- exc,
140
- "Exception {!r} was occurred during the running catcher {!r}.".format(
141
- repr(exc), self.catcher.func.__name__
142
+ return Error(
143
+ CatcherError(
144
+ exc,
145
+ "Exception {!r} was occurred during the running catcher {!r}.".format(
146
+ repr(exc), self.catcher.func.__name__
147
+ ),
142
148
  )
143
- ))
144
-
149
+ )
150
+
145
151
  def process_catcher_error(self, error: CatcherError) -> Result[None, str]:
146
152
  assert self.catcher is not None
147
-
153
+
148
154
  if self.catcher.raise_exception:
149
155
  raise error.exc from None
150
156
  if not self.catcher.ignore_errors:
151
157
  return Error(error.error)
152
158
  if self.catcher.logging:
153
159
  logger.error(error.error)
154
-
160
+
155
161
  return Ok(None)
156
162
 
157
163
  async def run(
@@ -163,13 +169,13 @@ class ErrorHandler(ABCErrorHandler[EventT]):
163
169
  ) -> Result[typing.Any, typing.Any]:
164
170
  if not self.catcher:
165
171
  return Ok(await handler(event, **magic_bundle(handler, ctx))) # type: ignore
166
-
172
+
167
173
  match await self.process(handler, event, api, ctx):
168
174
  case Ok(_) as ok:
169
175
  return ok
170
176
  case Error(exc) as err:
171
177
  if isinstance(exc, CatcherError):
172
- return self.process_catcher_error(exc)
178
+ return self.process_catcher_error(exc)
173
179
  if self.catcher.ignore_errors:
174
180
  return Ok(None)
175
181
  return err