telegrinder 0.3.0.post1__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of telegrinder might be problematic. Click here for more details.

Files changed (73) hide show
  1. telegrinder/__init__.py +1 -1
  2. telegrinder/bot/cute_types/callback_query.py +2 -14
  3. telegrinder/bot/cute_types/chat_join_request.py +1 -1
  4. telegrinder/bot/cute_types/chat_member_updated.py +1 -1
  5. telegrinder/bot/cute_types/inline_query.py +1 -6
  6. telegrinder/bot/cute_types/message.py +1 -21
  7. telegrinder/bot/cute_types/update.py +1 -1
  8. telegrinder/bot/dispatch/abc.py +45 -3
  9. telegrinder/bot/dispatch/dispatch.py +8 -8
  10. telegrinder/bot/dispatch/handler/func.py +8 -10
  11. telegrinder/bot/dispatch/process.py +1 -1
  12. telegrinder/bot/dispatch/view/base.py +31 -20
  13. telegrinder/bot/dispatch/view/raw.py +20 -16
  14. telegrinder/bot/dispatch/waiter_machine/actions.py +3 -0
  15. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +15 -18
  16. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +21 -13
  17. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +15 -16
  18. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +6 -6
  19. telegrinder/bot/dispatch/waiter_machine/machine.py +24 -30
  20. telegrinder/bot/dispatch/waiter_machine/short_state.py +10 -4
  21. telegrinder/bot/rules/abc.py +42 -7
  22. telegrinder/bot/rules/adapter/raw_update.py +1 -3
  23. telegrinder/bot/rules/callback_data.py +7 -7
  24. telegrinder/bot/rules/chat_join.py +5 -5
  25. telegrinder/bot/rules/command.py +1 -1
  26. telegrinder/bot/rules/enum_text.py +4 -1
  27. telegrinder/bot/rules/fuzzy.py +1 -1
  28. telegrinder/bot/rules/inline.py +6 -7
  29. telegrinder/bot/rules/integer.py +1 -1
  30. telegrinder/bot/rules/is_from.py +20 -20
  31. telegrinder/bot/rules/markup.py +6 -3
  32. telegrinder/bot/rules/mention.py +1 -1
  33. telegrinder/bot/rules/message.py +2 -2
  34. telegrinder/bot/rules/message_entities.py +2 -2
  35. telegrinder/bot/rules/node.py +1 -1
  36. telegrinder/bot/rules/regex.py +1 -1
  37. telegrinder/bot/rules/start.py +2 -2
  38. telegrinder/bot/rules/text.py +6 -4
  39. telegrinder/bot/rules/update.py +1 -1
  40. telegrinder/bot/scenario/checkbox.py +9 -1
  41. telegrinder/msgspec_utils.py +11 -3
  42. telegrinder/node/attachment.py +6 -6
  43. telegrinder/node/base.py +17 -11
  44. telegrinder/node/callback_query.py +1 -1
  45. telegrinder/node/command.py +1 -1
  46. telegrinder/node/composer.py +5 -2
  47. telegrinder/node/container.py +1 -1
  48. telegrinder/node/event.py +1 -1
  49. telegrinder/node/message.py +1 -1
  50. telegrinder/node/polymorphic.py +6 -3
  51. telegrinder/node/rule.py +1 -1
  52. telegrinder/node/source.py +5 -7
  53. telegrinder/node/text.py +2 -2
  54. telegrinder/node/tools/generator.py +1 -2
  55. telegrinder/node/update.py +3 -3
  56. telegrinder/rules.py +2 -0
  57. telegrinder/tools/buttons.py +4 -4
  58. telegrinder/tools/error_handler/abc.py +7 -7
  59. telegrinder/tools/error_handler/error_handler.py +58 -47
  60. telegrinder/tools/formatting/html.py +0 -2
  61. telegrinder/tools/functional.py +3 -0
  62. telegrinder/tools/global_context/telegrinder_ctx.py +2 -0
  63. telegrinder/tools/i18n/__init__.py +1 -1
  64. telegrinder/tools/i18n/{base.py → abc.py} +0 -0
  65. telegrinder/tools/i18n/middleware/__init__.py +1 -1
  66. telegrinder/tools/i18n/middleware/{base.py → abc.py} +3 -2
  67. telegrinder/tools/i18n/simple.py +11 -12
  68. telegrinder/tools/keyboard.py +9 -9
  69. telegrinder/tools/magic.py +8 -4
  70. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/METADATA +1 -1
  71. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/RECORD +73 -73
  72. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/LICENSE +0 -0
  73. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/WHEEL +0 -0
@@ -5,7 +5,7 @@ from .text import HasText
5
5
 
6
6
 
7
7
  class HasMention(MessageRule, requires=[HasText()]):
8
- async def check(self, message: Message) -> bool:
8
+ def check(self, message: Message) -> bool:
9
9
  if not message.entities.unwrap_or_none():
10
10
  return False
11
11
  return any(entity.type == MessageEntityType.MENTION for entity in message.entities.unwrap())
@@ -3,7 +3,7 @@ import abc
3
3
  from telegrinder.bot.dispatch.context import Context
4
4
  from telegrinder.types.objects import Message as MessageEvent
5
5
 
6
- from .abc import ABCRule, Message
6
+ from .abc import ABCRule, CheckResult, Message
7
7
  from .adapter import EventAdapter
8
8
 
9
9
 
@@ -11,7 +11,7 @@ class MessageRule(ABCRule[Message], abc.ABC):
11
11
  adapter: EventAdapter[Message] = EventAdapter(MessageEvent, Message)
12
12
 
13
13
  @abc.abstractmethod
14
- async def check(self, message: Message, ctx: Context) -> bool: ...
14
+ def check(self, message: Message, ctx: Context) -> CheckResult: ...
15
15
 
16
16
 
17
17
  __all__ = ("MessageRule",)
@@ -10,7 +10,7 @@ Entity: typing.TypeAlias = str | MessageEntityType
10
10
 
11
11
 
12
12
  class HasEntities(MessageRule):
13
- async def check(self, message: Message) -> bool:
13
+ def check(self, message: Message) -> bool:
14
14
  return bool(message.entities)
15
15
 
16
16
 
@@ -18,7 +18,7 @@ class MessageEntities(MessageRule, requires=[HasEntities()]):
18
18
  def __init__(self, entities: Entity | list[Entity], /) -> None:
19
19
  self.entities = [entities] if not isinstance(entities, list) else entities
20
20
 
21
- async def check(self, message: Message, ctx: Context) -> bool:
21
+ def check(self, message: Message, ctx: Context) -> bool:
22
22
  message_entities: list[MessageEntity] = []
23
23
  for entity in message.entities.unwrap():
24
24
  for entity_type in self.entities:
@@ -17,7 +17,7 @@ class NodeRule(ABCRule[tuple[Node, ...]]):
17
17
  def adapter(self) -> NodeAdapter:
18
18
  return NodeAdapter(*self.nodes) # type: ignore
19
19
 
20
- async def check(self, resolved_nodes: tuple[Node, ...], ctx: Context) -> typing.Literal[True]:
20
+ def check(self, resolved_nodes: tuple[Node, ...], ctx: Context) -> typing.Literal[True]:
21
21
  for i, node in enumerate(resolved_nodes):
22
22
  if key := self.node_keys[i]:
23
23
  ctx[key] = node
@@ -22,7 +22,7 @@ class Regex(ABCRule):
22
22
  re.compile(regexp) if isinstance(regexp, str) else regexp for regexp in regexp
23
23
  )
24
24
 
25
- async def check(self, text: Text, ctx: Context) -> bool:
25
+ def check(self, text: Text, ctx: Context) -> bool:
26
26
  for regexp in self.regexp:
27
27
  response = re.match(regexp, text)
28
28
  if response is not None:
@@ -12,7 +12,7 @@ from .message_entities import MessageEntities
12
12
  class StartCommand(
13
13
  MessageRule,
14
14
  requires=[
15
- IsPrivate(),
15
+ IsPrivate(),
16
16
  MessageEntities(MessageEntityType.BOT_COMMAND),
17
17
  Markup(["/start <param>", "/start"]),
18
18
  ],
@@ -28,7 +28,7 @@ class StartCommand(
28
28
  self.validator = validator
29
29
  self.alias = alias
30
30
 
31
- async def check(self, ctx: Context) -> bool:
31
+ def check(self, ctx: Context) -> bool:
32
32
  param: str | None = ctx.pop("param", None)
33
33
  validated_param = self.validator(param) if self.validator and param is not None else param
34
34
 
@@ -1,5 +1,7 @@
1
+ import typing
2
+
1
3
  from telegrinder import node
2
- from telegrinder.tools.i18n.base import ABCTranslator
4
+ from telegrinder.tools.i18n.abc import ABCTranslator
3
5
 
4
6
  from .abc import ABCRule, with_caching_translations
5
7
  from .node import NodeRule
@@ -17,12 +19,12 @@ class Text(ABCRule):
17
19
  self.texts = texts if not ignore_case else list(map(str.lower, texts))
18
20
  self.ignore_case = ignore_case
19
21
 
20
- async def check(self, text: node.text.Text) -> bool:
22
+ def check(self, text: node.text.Text) -> bool:
21
23
  return (text if not self.ignore_case else text.lower()) in self.texts
22
24
 
23
25
  @with_caching_translations
24
- async def translate(self, translator: ABCTranslator) -> "Text":
25
- return Text(
26
+ async def translate(self, translator: ABCTranslator) -> typing.Self:
27
+ return self.__class__(
26
28
  texts=[translator.get(text) for text in self.texts],
27
29
  ignore_case=self.ignore_case,
28
30
  )
@@ -8,7 +8,7 @@ class IsUpdateType(ABCRule):
8
8
  def __init__(self, update_type: UpdateType, /) -> None:
9
9
  self.update_type = update_type
10
10
 
11
- async def check(self, event: UpdateCute) -> bool:
11
+ def check(self, event: UpdateCute) -> bool:
12
12
  return event.update_type == self.update_type
13
13
 
14
14
 
@@ -35,6 +35,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
35
35
  message: str,
36
36
  *,
37
37
  ready_text: str = "Ready",
38
+ cancel_text: str | None = None,
38
39
  max_in_row: int = 3,
39
40
  ) -> None:
40
41
  self.chat_id = chat_id
@@ -44,6 +45,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
44
45
  self.max_in_row = max_in_row
45
46
  self.random_code = secrets.token_hex(8)
46
47
  self.waiter_machine = waiter_machine
48
+ self.cancel_text = cancel_text
47
49
 
48
50
  def __repr__(self) -> str:
49
51
  return (
@@ -75,6 +77,9 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
75
77
  kb.row()
76
78
 
77
79
  kb.add(InlineButton(self.ready, callback_data=self.random_code + "/ready"))
80
+ if self.cancel_text is not None:
81
+ kb.row()
82
+ kb.add(InlineButton(self.cancel_text, callback_data=self.random_code + "/cancel"))
78
83
  return kb.get_markup()
79
84
 
80
85
  def add_option(
@@ -94,6 +99,9 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
94
99
  code = cb.data.unwrap().replace(self.random_code + "/", "", 1)
95
100
  if code == "ready":
96
101
  return False
102
+ elif code == "cancel":
103
+ self.choices = []
104
+ return False
97
105
 
98
106
  for i, choice in enumerate(self.choices):
99
107
  if choice.code == code:
@@ -124,7 +132,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
124
132
  ).unwrap()
125
133
 
126
134
  while True:
127
- q, _ = await self.waiter_machine.wait(StateViewHasher(view.__class__), message.message_id)
135
+ q, _ = await self.waiter_machine.wait(StateViewHasher(view), message.message_id)
128
136
  should_continue = await self.handle(q)
129
137
  await q.answer(self.CALLBACK_ANSWER)
130
138
  if not should_continue:
@@ -210,12 +210,18 @@ class Decoder:
210
210
 
211
211
  @typing.overload
212
212
  def __call__(
213
- self, type: type[T], *, strict: bool = True
213
+ self,
214
+ type: type[T],
215
+ *,
216
+ strict: bool = True,
214
217
  ) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
215
218
 
216
219
  @typing.overload
217
220
  def __call__(
218
- self, type: typing.Any, *, strict: bool = True
221
+ self,
222
+ type: typing.Any,
223
+ *,
224
+ strict: bool = True,
219
225
  ) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
220
226
 
221
227
  @contextmanager
@@ -223,7 +229,9 @@ class Decoder:
223
229
  """Context manager returns the `msgspec.json.Decoder` object with the `dec_hook`."""
224
230
 
225
231
  dec_obj = msgspec.json.Decoder(
226
- type=typing.Any if type is object else type, strict=strict, dec_hook=self.dec_hook
232
+ type=typing.Any if type is object else type,
233
+ strict=strict,
234
+ dec_hook=self.dec_hook,
227
235
  )
228
236
  yield dec_obj
229
237
 
@@ -31,7 +31,7 @@ class Attachment(DataNode):
31
31
  )
32
32
 
33
33
  @classmethod
34
- async def compose(cls, message: MessageNode) -> "Attachment":
34
+ def compose(cls, message: MessageNode) -> "Attachment":
35
35
  for attachment_type in ("audio", "document", "photo", "poll", "video"):
36
36
  match getattr(message, attachment_type, Nothing()):
37
37
  case Some(attachment):
@@ -44,7 +44,7 @@ class Photo(DataNode):
44
44
  sizes: list[telegrinder.types.PhotoSize]
45
45
 
46
46
  @classmethod
47
- async def compose(cls, attachment: Attachment) -> typing.Self:
47
+ def compose(cls, attachment: Attachment) -> typing.Self:
48
48
  if not attachment.photo:
49
49
  raise ComposeError("Attachment is not a photo.")
50
50
  return cls(attachment.photo.unwrap())
@@ -52,7 +52,7 @@ class Photo(DataNode):
52
52
 
53
53
  class Video(ScalarNode, telegrinder.types.Video):
54
54
  @classmethod
55
- async def compose(cls, attachment: Attachment) -> telegrinder.types.Video:
55
+ def compose(cls, attachment: Attachment) -> telegrinder.types.Video:
56
56
  if not attachment.video:
57
57
  raise ComposeError("Attachment is not a video.")
58
58
  return attachment.video.unwrap()
@@ -60,7 +60,7 @@ class Video(ScalarNode, telegrinder.types.Video):
60
60
 
61
61
  class Audio(ScalarNode, telegrinder.types.Audio):
62
62
  @classmethod
63
- async def compose(cls, attachment: Attachment) -> telegrinder.types.Audio:
63
+ def compose(cls, attachment: Attachment) -> telegrinder.types.Audio:
64
64
  if not attachment.audio:
65
65
  raise ComposeError("Attachment is not an audio.")
66
66
  return attachment.audio.unwrap()
@@ -68,7 +68,7 @@ class Audio(ScalarNode, telegrinder.types.Audio):
68
68
 
69
69
  class Document(ScalarNode, telegrinder.types.Document):
70
70
  @classmethod
71
- async def compose(cls, attachment: Attachment) -> telegrinder.types.Document:
71
+ def compose(cls, attachment: Attachment) -> telegrinder.types.Document:
72
72
  if not attachment.document:
73
73
  raise ComposeError("Attachment is not a document.")
74
74
  return attachment.document.unwrap()
@@ -76,7 +76,7 @@ class Document(ScalarNode, telegrinder.types.Document):
76
76
 
77
77
  class Poll(ScalarNode, telegrinder.types.Poll):
78
78
  @classmethod
79
- async def compose(cls, attachment: Attachment) -> telegrinder.types.Poll:
79
+ def compose(cls, attachment: Attachment) -> telegrinder.types.Poll:
80
80
  if not attachment.poll:
81
81
  raise ComposeError("Attachment is not a poll.")
82
82
  return attachment.poll.unwrap()
telegrinder/node/base.py CHANGED
@@ -4,12 +4,11 @@ import typing
4
4
  from types import AsyncGeneratorType
5
5
 
6
6
  from telegrinder.node.scope import NodeScope
7
- from telegrinder.tools.magic import (
8
- cache_magic_value,
9
- get_annotations,
10
- )
7
+ from telegrinder.tools.magic import cache_magic_value, get_annotations
11
8
 
12
- ComposeResult: typing.TypeAlias = typing.Awaitable[typing.Any] | typing.AsyncGenerator[typing.Any, None]
9
+ ComposeResult: typing.TypeAlias = (
10
+ typing.Awaitable[typing.Any] | typing.AsyncGenerator[typing.Any, None] | typing.Any
11
+ )
13
12
 
14
13
 
15
14
  def is_node(maybe_node: typing.Any) -> typing.TypeGuard[type["Node"]]:
@@ -28,7 +27,9 @@ def get_nodes(function: typing.Callable[..., typing.Any]) -> dict[str, type["Nod
28
27
 
29
28
 
30
29
  @cache_magic_value("__is_generator__")
31
- def is_generator(function: typing.Callable[..., typing.Any]) -> typing.TypeGuard[AsyncGeneratorType[typing.Any, None]]:
30
+ def is_generator(
31
+ function: typing.Callable[..., typing.Any],
32
+ ) -> typing.TypeGuard[AsyncGeneratorType[typing.Any, None]]:
32
33
  return inspect.isasyncgenfunction(function)
33
34
 
34
35
 
@@ -103,20 +104,25 @@ if typing.TYPE_CHECKING:
103
104
  pass
104
105
 
105
106
  else:
107
+
106
108
  def __init_subclass__(cls, *args, **kwargs): # noqa: N807
107
- if any(issubclass(base, ScalarNode) for base in cls.__bases__ if base is not ScalarNode):
109
+ if any(issubclass(base, ScalarNodeProto) for base in cls.__bases__ if base is not ScalarNode):
108
110
  raise RuntimeError("Scalar nodes do not support inheritance.")
109
111
 
110
- def create_node(cls, bases, dct):
111
- dct.update(cls.__dict__)
112
- return type(cls.__name__, bases, dct)
112
+ def _as_node(cls, bases, dct):
113
+ if not hasattr(cls, "_scalar_node_type"):
114
+ dct.update(cls.__dict__)
115
+ scalar_node_type = type(cls.__name__, bases, dct)
116
+ setattr(cls, "_scalar_node_type", scalar_node_type)
117
+ return scalar_node_type
118
+ return getattr(cls, "_scalar_node_type")
113
119
 
114
120
  def create_class(name, bases, dct):
115
121
  return type(
116
122
  "Scalar",
117
123
  (SCALAR_NODE,),
118
124
  {
119
- "as_node": classmethod(lambda cls: create_node(cls, bases, dct)),
125
+ "as_node": classmethod(lambda cls: _as_node(cls, bases, dct)),
120
126
  "scope": Node.scope,
121
127
  "__init_subclass__": __init_subclass__,
122
128
  },
@@ -5,7 +5,7 @@ from telegrinder.node.update import UpdateNode
5
5
 
6
6
  class CallbackQueryNode(ScalarNode, CallbackQueryCute):
7
7
  @classmethod
8
- async def compose(cls, update: UpdateNode) -> CallbackQueryCute:
8
+ def compose(cls, update: UpdateNode) -> CallbackQueryCute:
9
9
  if not update.callback_query:
10
10
  raise ComposeError("Update is not a callback_query.")
11
11
  return update.callback_query.unwrap()
@@ -24,7 +24,7 @@ class CommandInfo(DataNode):
24
24
  mention: Option[str] = field(default_factory=Nothing)
25
25
 
26
26
  @classmethod
27
- async def compose(cls, text: Text) -> typing.Self:
27
+ def compose(cls, text: Text) -> typing.Self:
28
28
  name, arguments = single_split(text, separator=" ")
29
29
  name, mention = cut_mention(name)
30
30
  return cls(name, arguments, mention)
@@ -1,8 +1,9 @@
1
1
  import dataclasses
2
+ import inspect
2
3
  import typing
3
4
 
4
- from fntypes import Error, Ok, Result
5
5
  from fntypes.error import UnwrapError
6
+ from fntypes.result import Error, Ok, Result
6
7
 
7
8
  from telegrinder.api.api import API
8
9
  from telegrinder.bot.cute_types.update import Update, UpdateCute
@@ -33,7 +34,9 @@ async def compose_node(
33
34
  value = await generator.asend(None)
34
35
  else:
35
36
  generator = None
36
- value = await typing.cast(typing.Awaitable[typing.Any], node.compose(**kwargs))
37
+ value = typing.cast(typing.Awaitable[typing.Any] | typing.Any, node.compose(**kwargs))
38
+ if inspect.isawaitable(value):
39
+ value = await value
37
40
 
38
41
  return NodeSession(_node, value, {}, generator)
39
42
 
@@ -7,7 +7,7 @@ class ContainerNode(Node):
7
7
  linked_nodes: typing.ClassVar[list[type[Node]]]
8
8
 
9
9
  @classmethod
10
- async def compose(cls, **kw) -> tuple[Node, ...]:
10
+ def compose(cls, **kw) -> tuple[Node, ...]:
11
11
  return tuple(t[1] for t in sorted(kw.items(), key=lambda t: t[0]))
12
12
 
13
13
  @classmethod
telegrinder/node/event.py CHANGED
@@ -31,7 +31,7 @@ class _EventNode(Node):
31
31
  return cls(dataclass)
32
32
 
33
33
  @classmethod
34
- async def compose(cls, raw_update: UpdateNode, ctx: Context, api: API) -> "DataclassType":
34
+ def compose(cls, raw_update: UpdateNode, ctx: Context, api: API) -> "DataclassType":
35
35
  dataclass_type = typing.get_origin(cls.dataclass) or cls.dataclass
36
36
 
37
37
  try:
@@ -5,7 +5,7 @@ from telegrinder.node.update import UpdateNode
5
5
 
6
6
  class MessageNode(ScalarNode, MessageCute):
7
7
  @classmethod
8
- async def compose(cls, update: UpdateNode) -> MessageCute:
8
+ def compose(cls, update: UpdateNode) -> MessageCute:
9
9
  if not update.message:
10
10
  raise ComposeError("Update is not a message.")
11
11
  return update.message.unwrap()
@@ -13,7 +13,7 @@ from telegrinder.tools.magic import get_impls, impl
13
13
  class Polymorphic(Node):
14
14
  @classmethod
15
15
  async def compose(cls, update: UpdateNode, context: Context) -> typing.Any:
16
- logger.debug(f"Composing polimorphic node {cls.__name__!r}...")
16
+ logger.debug(f"Composing polymorphic node {cls.__name__!r}...")
17
17
  scope = getattr(cls, "scope", None)
18
18
  node_ctx = context.get_or_set(CONTEXT_STORE_NODES_KEY, {})
19
19
 
@@ -27,7 +27,10 @@ class Polymorphic(Node):
27
27
 
28
28
  # To determine whether this is a right morph, all subnodes must be resolved
29
29
  if scope is NodeScope.PER_EVENT and (cls, i) in node_ctx:
30
- logger.debug("Morph is already cached as per_event node, using its value. Impl {!r} succeeded!", impl_.__name__)
30
+ logger.debug(
31
+ "Morph is already cached as per_event node, using its value. Impl {!r} succeeded!",
32
+ impl_.__name__,
33
+ )
31
34
  res: NodeSession = node_ctx[(cls, i)]
32
35
  await node_collection.close_all()
33
36
  return res.value
@@ -40,7 +43,7 @@ class Polymorphic(Node):
40
43
  node_ctx[(cls, i)] = NodeSession(cls, result, {})
41
44
 
42
45
  await node_collection.close_all(with_value=result)
43
- logger.debug("Impl {!r} succeeded with value: {}", impl_.__name__, result)
46
+ logger.debug("Impl {!r} succeeded with value: {!r}", impl_.__name__, result)
44
47
  return result
45
48
 
46
49
  raise ComposeError("No implementation found.")
telegrinder/node/rule.py CHANGED
@@ -12,7 +12,7 @@ if typing.TYPE_CHECKING:
12
12
 
13
13
 
14
14
  class RuleChain(dict[str, typing.Any], Node):
15
- dataclass = dict
15
+ dataclass: type[typing.Any] = dict
16
16
  rules: tuple["ABCRule", ...] = ()
17
17
 
18
18
  def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None:
@@ -21,7 +21,7 @@ class Source(Polymorphic, DataNode):
21
21
  thread_id: Option[int] = dataclasses.field(default_factory=lambda: Nothing())
22
22
 
23
23
  @impl
24
- async def compose_message(cls, message: MessageNode) -> typing.Self:
24
+ def compose_message(cls, message: MessageNode) -> typing.Self:
25
25
  return cls(
26
26
  api=message.ctx_api,
27
27
  chat=message.chat,
@@ -30,7 +30,7 @@ class Source(Polymorphic, DataNode):
30
30
  )
31
31
 
32
32
  @impl
33
- async def compose_callback_query(cls, callback_query: CallbackQueryNode) -> typing.Self:
33
+ def compose_callback_query(cls, callback_query: CallbackQueryNode) -> typing.Self:
34
34
  return cls(
35
35
  api=callback_query.ctx_api,
36
36
  chat=callback_query.chat.expect(ComposeError("CallbackQueryNode has no chat")),
@@ -39,9 +39,7 @@ class Source(Polymorphic, DataNode):
39
39
  )
40
40
 
41
41
  @impl
42
- async def compose_chat_join_request(
43
- cls, chat_join_request: EventNode[ChatJoinRequestCute]
44
- ) -> typing.Self:
42
+ def compose_chat_join_request(cls, chat_join_request: EventNode[ChatJoinRequestCute]) -> typing.Self:
45
43
  return cls(
46
44
  api=chat_join_request.ctx_api,
47
45
  chat=chat_join_request.chat,
@@ -60,13 +58,13 @@ class Source(Polymorphic, DataNode):
60
58
 
61
59
  class ChatSource(ScalarNode, Chat):
62
60
  @classmethod
63
- async def compose(cls, source: Source) -> Chat:
61
+ def compose(cls, source: Source) -> Chat:
64
62
  return source.chat
65
63
 
66
64
 
67
65
  class UserSource(ScalarNode, User):
68
66
  @classmethod
69
- async def compose(cls, source: Source) -> User:
67
+ def compose(cls, source: Source) -> User:
70
68
  return source.from_user
71
69
 
72
70
 
telegrinder/node/text.py CHANGED
@@ -4,7 +4,7 @@ from telegrinder.node.message import MessageNode
4
4
 
5
5
  class Text(ScalarNode, str):
6
6
  @classmethod
7
- async def compose(cls, message: MessageNode) -> str:
7
+ def compose(cls, message: MessageNode) -> str:
8
8
  if not message.text:
9
9
  raise ComposeError("Message has no text.")
10
10
  return message.text.unwrap()
@@ -12,7 +12,7 @@ class Text(ScalarNode, str):
12
12
 
13
13
  class TextInteger(ScalarNode, int):
14
14
  @classmethod
15
- async def compose(cls, text: Text) -> int:
15
+ def compose(cls, text: Text) -> int:
16
16
  if not text.isdigit():
17
17
  raise ComposeError("Text is not digit.")
18
18
  return int(text)
@@ -25,8 +25,7 @@ def generate_node(
25
25
  casts: tuple[typing.Callable[[typing.Any], typing.Any], ...] = (cast_false_to_none, error_on_none),
26
26
  ) -> type[Node]:
27
27
  async def compose(cls, **kw) -> typing.Any:
28
- args = await ContainerNode.compose(**kw)
29
- result = func(*args) # type: ignore
28
+ result = func(*ContainerNode.compose(**kw)) # type: ignore
30
29
  if inspect.isawaitable(result):
31
30
  result = await result
32
31
  for cast in casts:
@@ -1,12 +1,12 @@
1
- from telegrinder.api import API
1
+ from telegrinder.api.api import API
2
2
  from telegrinder.bot.cute_types import UpdateCute
3
3
  from telegrinder.node.base import ScalarNode
4
- from telegrinder.types import Update
4
+ from telegrinder.types.objects import Update
5
5
 
6
6
 
7
7
  class UpdateNode(ScalarNode, UpdateCute):
8
8
  @classmethod
9
- async def compose(cls, update: Update, api: API) -> UpdateCute:
9
+ def compose(cls, update: Update, api: API) -> UpdateCute:
10
10
  if isinstance(update, UpdateCute):
11
11
  return update
12
12
  return UpdateCute.from_update(update, api)
telegrinder/rules.py CHANGED
@@ -56,5 +56,7 @@ __all__ = (
56
56
  "Regex",
57
57
  "RuleEnum",
58
58
  "StartCommand",
59
+ "State",
60
+ "StateMeta",
59
61
  "Text",
60
62
  )
@@ -14,7 +14,7 @@ from telegrinder.types.objects import (
14
14
  WebAppInfo,
15
15
  )
16
16
 
17
- ButtonT = typing.TypeVar("ButtonT", bound="BaseButton")
17
+ KeyboardButton = typing.TypeVar("KeyboardButton", bound="BaseButton")
18
18
 
19
19
 
20
20
  @dataclasses.dataclass
@@ -27,11 +27,11 @@ class BaseButton:
27
27
  }
28
28
 
29
29
 
30
- class RowButtons(typing.Generic[ButtonT]):
31
- buttons: list[ButtonT]
30
+ class RowButtons(typing.Generic[KeyboardButton]):
31
+ buttons: list[KeyboardButton]
32
32
  auto_row: bool
33
33
 
34
- def __init__(self, *buttons: ButtonT, auto_row: bool = True) -> None:
34
+ def __init__(self, *buttons: KeyboardButton, auto_row: bool = True) -> None:
35
35
  self.buttons = list(buttons)
36
36
  self.auto_row = auto_row
37
37
 
@@ -6,28 +6,28 @@ from fntypes.result import Result
6
6
  from telegrinder.api import API
7
7
  from telegrinder.bot.dispatch.context import Context
8
8
 
9
- EventT = typing.TypeVar("EventT")
10
- Handler = typing.Callable[typing.Concatenate[EventT, ...], typing.Awaitable[typing.Any]]
9
+ Event = typing.TypeVar("Event")
10
+ Handler = typing.Callable[..., typing.Awaitable[typing.Any]]
11
11
 
12
12
 
13
- class ABCErrorHandler(ABC, typing.Generic[EventT]):
13
+ class ABCErrorHandler(ABC, typing.Generic[Event]):
14
14
  @abstractmethod
15
15
  def __call__(
16
16
  self,
17
17
  *args: typing.Any,
18
18
  **kwargs: typing.Any,
19
19
  ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Callable[..., typing.Any]]:
20
- """Decorator for registering callback as an error handler."""
20
+ """Decorator for registering callback as a catcher for the error handler."""
21
21
 
22
22
  @abstractmethod
23
23
  async def run(
24
24
  self,
25
- handler: Handler[EventT],
26
- event: EventT,
25
+ handler: Handler,
26
+ event: Event,
27
27
  api: API,
28
28
  ctx: Context,
29
29
  ) -> Result[typing.Any, typing.Any]:
30
- """Run error handler."""
30
+ """Run the error handler."""
31
31
 
32
32
 
33
33
  __all__ = ("ABCErrorHandler",)