telegrinder 0.1.dev171__py3-none-any.whl → 0.2.0.post1__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 (94) hide show
  1. telegrinder/__init__.py +2 -2
  2. telegrinder/api/__init__.py +1 -2
  3. telegrinder/api/api.py +3 -3
  4. telegrinder/api/token.py +36 -0
  5. telegrinder/bot/__init__.py +12 -6
  6. telegrinder/bot/bot.py +12 -5
  7. telegrinder/bot/cute_types/__init__.py +7 -7
  8. telegrinder/bot/cute_types/base.py +17 -43
  9. telegrinder/bot/cute_types/callback_query.py +5 -6
  10. telegrinder/bot/cute_types/chat_join_request.py +4 -5
  11. telegrinder/bot/cute_types/chat_member_updated.py +3 -4
  12. telegrinder/bot/cute_types/inline_query.py +3 -4
  13. telegrinder/bot/cute_types/message.py +9 -10
  14. telegrinder/bot/cute_types/update.py +13 -13
  15. telegrinder/bot/cute_types/utils.py +1 -1
  16. telegrinder/bot/dispatch/__init__.py +9 -9
  17. telegrinder/bot/dispatch/abc.py +2 -2
  18. telegrinder/bot/dispatch/context.py +11 -2
  19. telegrinder/bot/dispatch/dispatch.py +18 -33
  20. telegrinder/bot/dispatch/handler/__init__.py +3 -3
  21. telegrinder/bot/dispatch/handler/abc.py +3 -3
  22. telegrinder/bot/dispatch/handler/func.py +17 -12
  23. telegrinder/bot/dispatch/handler/message_reply.py +6 -7
  24. telegrinder/bot/dispatch/middleware/__init__.py +1 -1
  25. telegrinder/bot/dispatch/process.py +30 -11
  26. telegrinder/bot/dispatch/return_manager/__init__.py +4 -4
  27. telegrinder/bot/dispatch/return_manager/callback_query.py +1 -2
  28. telegrinder/bot/dispatch/return_manager/inline_query.py +1 -2
  29. telegrinder/bot/dispatch/return_manager/message.py +1 -2
  30. telegrinder/bot/dispatch/view/__init__.py +8 -8
  31. telegrinder/bot/dispatch/view/abc.py +9 -4
  32. telegrinder/bot/dispatch/view/box.py +2 -2
  33. telegrinder/bot/dispatch/view/callback_query.py +1 -2
  34. telegrinder/bot/dispatch/view/chat_join_request.py +1 -2
  35. telegrinder/bot/dispatch/view/chat_member.py +16 -2
  36. telegrinder/bot/dispatch/view/inline_query.py +1 -2
  37. telegrinder/bot/dispatch/view/message.py +1 -2
  38. telegrinder/bot/dispatch/view/raw.py +8 -10
  39. telegrinder/bot/dispatch/waiter_machine/__init__.py +3 -3
  40. telegrinder/bot/dispatch/waiter_machine/machine.py +10 -6
  41. telegrinder/bot/dispatch/waiter_machine/short_state.py +2 -2
  42. telegrinder/bot/polling/abc.py +1 -1
  43. telegrinder/bot/polling/polling.py +3 -3
  44. telegrinder/bot/rules/__init__.py +20 -20
  45. telegrinder/bot/rules/abc.py +50 -40
  46. telegrinder/bot/rules/adapter/__init__.py +5 -5
  47. telegrinder/bot/rules/adapter/abc.py +6 -3
  48. telegrinder/bot/rules/adapter/errors.py +2 -1
  49. telegrinder/bot/rules/adapter/event.py +27 -15
  50. telegrinder/bot/rules/adapter/node.py +28 -22
  51. telegrinder/bot/rules/adapter/raw_update.py +13 -5
  52. telegrinder/bot/rules/callback_data.py +12 -6
  53. telegrinder/bot/rules/chat_join.py +4 -4
  54. telegrinder/bot/rules/func.py +1 -1
  55. telegrinder/bot/rules/inline.py +3 -3
  56. telegrinder/bot/rules/markup.py +3 -1
  57. telegrinder/bot/rules/message_entities.py +1 -1
  58. telegrinder/bot/rules/text.py +1 -2
  59. telegrinder/bot/rules/update.py +1 -2
  60. telegrinder/bot/scenario/abc.py +2 -2
  61. telegrinder/bot/scenario/checkbox.py +1 -2
  62. telegrinder/bot/scenario/choice.py +1 -2
  63. telegrinder/model.py +9 -2
  64. telegrinder/msgspec_utils.py +55 -55
  65. telegrinder/node/__init__.py +1 -3
  66. telegrinder/node/base.py +20 -85
  67. telegrinder/node/command.py +3 -3
  68. telegrinder/node/composer.py +71 -74
  69. telegrinder/node/container.py +3 -3
  70. telegrinder/node/event.py +45 -33
  71. telegrinder/node/me.py +3 -4
  72. telegrinder/node/polymorphic.py +12 -6
  73. telegrinder/node/rule.py +1 -9
  74. telegrinder/node/scope.py +9 -1
  75. telegrinder/node/source.py +11 -0
  76. telegrinder/node/update.py +6 -2
  77. telegrinder/rules.py +59 -0
  78. telegrinder/tools/error_handler/abc.py +2 -2
  79. telegrinder/tools/error_handler/error_handler.py +5 -5
  80. telegrinder/tools/global_context/global_context.py +1 -1
  81. telegrinder/tools/keyboard.py +1 -1
  82. telegrinder/tools/loop_wrapper/loop_wrapper.py +9 -9
  83. telegrinder/tools/magic.py +64 -19
  84. telegrinder/types/__init__.py +1 -0
  85. telegrinder/types/enums.py +1 -0
  86. telegrinder/types/methods.py +78 -11
  87. telegrinder/types/objects.py +46 -24
  88. telegrinder/verification_utils.py +1 -3
  89. {telegrinder-0.1.dev171.dist-info → telegrinder-0.2.0.post1.dist-info}/METADATA +1 -1
  90. telegrinder-0.2.0.post1.dist-info/RECORD +145 -0
  91. telegrinder/api/abc.py +0 -79
  92. telegrinder-0.1.dev171.dist-info/RECORD +0 -145
  93. {telegrinder-0.1.dev171.dist-info → telegrinder-0.2.0.post1.dist-info}/LICENSE +0 -0
  94. {telegrinder-0.1.dev171.dist-info → telegrinder-0.2.0.post1.dist-info}/WHEEL +0 -0
@@ -1,5 +1,5 @@
1
- from .abc import ABCRule, AndRule, NotRule, OrRule
2
- from .callback_data import (
1
+ from telegrinder.bot.rules.abc import ABCRule, AndRule, NotRule, OrRule
2
+ from telegrinder.bot.rules.callback_data import (
3
3
  CallbackDataEq,
4
4
  CallbackDataJsonEq,
5
5
  CallbackDataJsonModel,
@@ -9,25 +9,25 @@ from .callback_data import (
9
9
  CallbackQueryRule,
10
10
  HasData,
11
11
  )
12
- from .chat_join import (
12
+ from telegrinder.bot.rules.chat_join import (
13
13
  ChatJoinRequestRule,
14
14
  HasInviteLink,
15
15
  InviteLinkByCreator,
16
16
  InviteLinkName,
17
17
  )
18
- from .command import Argument, Command
19
- from .enum_text import EnumTextRule
20
- from .func import FuncRule
21
- from .fuzzy import FuzzyText
22
- from .inline import (
18
+ from telegrinder.bot.rules.command import Argument, Command
19
+ from telegrinder.bot.rules.enum_text import EnumTextRule
20
+ from telegrinder.bot.rules.func import FuncRule
21
+ from telegrinder.bot.rules.fuzzy import FuzzyText
22
+ from telegrinder.bot.rules.inline import (
23
23
  HasLocation,
24
24
  InlineQueryChatType,
25
25
  InlineQueryMarkup,
26
26
  InlineQueryRule,
27
27
  InlineQueryText,
28
28
  )
29
- from .integer import IntegerInRange, IsInteger
30
- from .is_from import (
29
+ from telegrinder.bot.rules.integer import IntegerInRange, IsInteger
30
+ from telegrinder.bot.rules.is_from import (
31
31
  IsBot,
32
32
  IsChat,
33
33
  IsChatId,
@@ -45,16 +45,16 @@ from .is_from import (
45
45
  IsUser,
46
46
  IsUserId,
47
47
  )
48
- from .markup import Markup
49
- from .mention import HasMention
50
- from .message import MessageRule
51
- from .message_entities import HasEntities, MessageEntities
52
- from .node import NodeRule
53
- from .regex import Regex
54
- from .rule_enum import RuleEnum
55
- from .start import StartCommand
56
- from .text import HasText, Text
57
- from .update import IsUpdateType
48
+ from telegrinder.bot.rules.markup import Markup
49
+ from telegrinder.bot.rules.mention import HasMention
50
+ from telegrinder.bot.rules.message import MessageRule
51
+ from telegrinder.bot.rules.message_entities import HasEntities, MessageEntities
52
+ from telegrinder.bot.rules.node import NodeRule
53
+ from telegrinder.bot.rules.regex import Regex
54
+ from telegrinder.bot.rules.rule_enum import RuleEnum
55
+ from telegrinder.bot.rules.start import StartCommand
56
+ from telegrinder.bot.rules.text import HasText, Text
57
+ from telegrinder.bot.rules.update import IsUpdateType
58
58
 
59
59
  __all__ = (
60
60
  "ABCRule",
@@ -11,7 +11,12 @@ from telegrinder.bot.rules.adapter import ABCAdapter, RawUpdateAdapter
11
11
  from telegrinder.bot.rules.adapter.node import Event
12
12
  from telegrinder.node.base import Node, get_nodes, is_node
13
13
  from telegrinder.tools.i18n.base import ABCTranslator
14
- from telegrinder.tools.magic import cache_translation, get_annotations, get_cached_translation
14
+ from telegrinder.tools.magic import (
15
+ cache_translation,
16
+ get_annotations,
17
+ get_cached_translation,
18
+ get_default_args,
19
+ )
15
20
  from telegrinder.types.objects import Update as UpdateObject
16
21
 
17
22
  if typing.TYPE_CHECKING:
@@ -37,51 +42,16 @@ def with_caching_translations(func: typing.Callable[..., typing.Any]):
37
42
 
38
43
 
39
44
  class ABCRule(ABC, typing.Generic[AdaptTo]):
40
- adapter: ABCAdapter[UpdateObject, AdaptTo] = RawUpdateAdapter() # type: ignore
45
+ adapter: ABCAdapter[UpdateObject, AdaptTo]
41
46
  requires: list["ABCRule"] = []
42
47
 
48
+ if not typing.TYPE_CHECKING:
49
+ adapter = RawUpdateAdapter()
50
+
43
51
  @abstractmethod
44
52
  async def check(self, event: AdaptTo, *, ctx: Context) -> bool:
45
53
  pass
46
54
 
47
- @cached_property
48
- def required_nodes(self) -> dict[str, type[Node]]:
49
- return get_nodes(self.check)
50
-
51
- async def bounding_check(
52
- self,
53
- adapted_value: AdaptTo,
54
- ctx: Context,
55
- node_col: "NodeCollection | None" = None,
56
- ) -> bool:
57
- kw = {}
58
- node_col_values = node_col.values() if node_col is not None else {}
59
-
60
- for i, (k, v) in enumerate(get_annotations(self.check).items()):
61
- if (isinstance(adapted_value, Event) and not i) or (
62
- isinstance(v, type) and isinstance(adapted_value, v)
63
- ):
64
- kw[k] = adapted_value if not isinstance(adapted_value, Event) else adapted_value.obj
65
- elif is_node(v):
66
- assert k in node_col_values, "Node is undefined, error while bounding."
67
- kw[k] = node_col_values[k]
68
- elif k in ctx:
69
- kw[k] = ctx[k]
70
- elif v is Context:
71
- kw[k] = ctx
72
- else:
73
- raise LookupError(
74
- f"Cannot bound {k!r} of type {v!r} to '{self.__class__.__name__}.check()', because it cannot be resolved."
75
- )
76
-
77
- return await self.check(**kw)
78
-
79
- def optional(self) -> "ABCRule":
80
- return self | Always()
81
-
82
- def should_fail(self) -> "ABCRule":
83
- return self & Never()
84
-
85
55
  def __init_subclass__(cls, requires: list["ABCRule"] | None = None) -> None:
86
56
  """Merges requirements from inherited classes and rule-specific requirements."""
87
57
 
@@ -132,6 +102,46 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
132
102
  self.adapter,
133
103
  )
134
104
 
105
+ @cached_property
106
+ def required_nodes(self) -> dict[str, type[Node]]:
107
+ return get_nodes(self.check)
108
+
109
+ def as_optional(self) -> "ABCRule":
110
+ return self | Always()
111
+
112
+ def should_fail(self) -> "ABCRule":
113
+ return self & Never()
114
+
115
+ async def bounding_check(
116
+ self,
117
+ adapted_value: AdaptTo,
118
+ ctx: Context,
119
+ node_col: "NodeCollection | None" = None,
120
+ ) -> bool:
121
+ kw = {}
122
+ node_col_values = node_col.values if node_col is not None else {}
123
+ temp_ctx = get_default_args(self.check) | ctx
124
+
125
+ for i, (k, v) in enumerate(get_annotations(self.check).items()):
126
+ if (isinstance(adapted_value, Event) and not i) or (
127
+ isinstance(v, type) and isinstance(adapted_value, v)
128
+ ):
129
+ kw[k] = adapted_value if not isinstance(adapted_value, Event) else adapted_value.obj
130
+ elif is_node(v):
131
+ assert k in node_col_values, "Node is undefined, error while bounding."
132
+ kw[k] = node_col_values[k]
133
+ elif k in temp_ctx:
134
+ kw[k] = temp_ctx[k]
135
+ elif v is Context:
136
+ kw[k] = ctx
137
+ else:
138
+ raise LookupError(
139
+ f"Cannot bound {k!r} of type {v!r} to '{self.__class__.__qualname__}.check()', "
140
+ "because it cannot be resolved."
141
+ )
142
+
143
+ return await self.check(**kw)
144
+
135
145
  async def translate(self, translator: ABCTranslator) -> typing.Self:
136
146
  return self
137
147
 
@@ -1,8 +1,8 @@
1
- from .abc import ABCAdapter, Event
2
- from .errors import AdapterError
3
- from .event import EventAdapter
4
- from .node import NodeAdapter
5
- from .raw_update import RawUpdateAdapter
1
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
2
+ from telegrinder.bot.rules.adapter.errors import AdapterError
3
+ from telegrinder.bot.rules.adapter.event import EventAdapter
4
+ from telegrinder.bot.rules.adapter.node import NodeAdapter
5
+ from telegrinder.bot.rules.adapter.raw_update import RawUpdateAdapter
6
6
 
7
7
  __all__ = (
8
8
  "ABCAdapter",
@@ -4,7 +4,8 @@ import typing
4
4
 
5
5
  from fntypes.result import Result
6
6
 
7
- from telegrinder.api.abc import ABCAPI
7
+ from telegrinder.api import API
8
+ from telegrinder.bot.dispatch.context import Context
8
9
  from telegrinder.bot.rules.adapter.errors import AdapterError
9
10
  from telegrinder.model import Model
10
11
 
@@ -13,12 +14,14 @@ To = typing.TypeVar("To")
13
14
 
14
15
 
15
16
  class ABCAdapter(abc.ABC, typing.Generic[From, To]):
17
+ ADAPTED_VALUE_KEY: str | None = None
18
+
16
19
  @abc.abstractmethod
17
- async def adapt(self, api: ABCAPI, update: From) -> Result[To, AdapterError]:
20
+ async def adapt(self, api: API, update: From, context: Context) -> Result[To, AdapterError]:
18
21
  pass
19
22
 
20
23
 
21
- @dataclasses.dataclass
24
+ @dataclasses.dataclass(slots=True)
22
25
  class Event(typing.Generic[To]):
23
26
  obj: To
24
27
 
@@ -1,4 +1,5 @@
1
- class AdapterError(RuntimeError): ...
1
+ class AdapterError(RuntimeError):
2
+ pass
2
3
 
3
4
 
4
5
  __all__ = ("AdapterError",)
@@ -2,8 +2,9 @@ import typing
2
2
 
3
3
  from fntypes.result import Error, Ok, Result
4
4
 
5
- from telegrinder.api.abc import ABCAPI
5
+ from telegrinder.api import API
6
6
  from telegrinder.bot.cute_types.base import BaseCute
7
+ from telegrinder.bot.dispatch.context import Context
7
8
  from telegrinder.bot.rules.adapter.abc import ABCAdapter
8
9
  from telegrinder.bot.rules.adapter.errors import AdapterError
9
10
  from telegrinder.msgspec_utils import Nothing
@@ -14,6 +15,8 @@ ToCute = typing.TypeVar("ToCute", bound=BaseCute)
14
15
 
15
16
 
16
17
  class EventAdapter(ABCAdapter[Update, ToCute]):
18
+ ADAPTED_VALUE_KEY: str = "_adapted_cute_event"
19
+
17
20
  def __init__(self, event: UpdateType | type[Model], cute_model: type[ToCute]) -> None:
18
21
  self.event = event
19
22
  self.cute_model = cute_model
@@ -29,36 +32,45 @@ class EventAdapter(ABCAdapter[Update, ToCute]):
29
32
  else:
30
33
  raw_update_type = self.event.__name__
31
34
 
32
- return "<{}: adapt {} -> {}>".format(
35
+ return "<{}: adapt Update -> {} -> {}>".format(
33
36
  self.__class__.__name__,
34
37
  raw_update_type,
35
38
  self.cute_model.__name__,
36
39
  )
37
40
 
38
- async def adapt(self, api: ABCAPI, update: Update) -> Result[ToCute, AdapterError]:
39
- update_dct = update.to_dict()
41
+ async def adapt(self, api: API, update: Update, context: Context) -> Result[ToCute, AdapterError]:
42
+ if self.ADAPTED_VALUE_KEY in context:
43
+ return Ok(context[self.ADAPTED_VALUE_KEY])
44
+
40
45
  if isinstance(self.event, UpdateType):
41
46
  if update.update_type != self.event:
42
47
  return Error(
43
48
  AdapterError(f"Update is not of event type {self.event!r}."),
44
49
  )
45
50
 
46
- if (event := getattr(update, self.event.value, Nothing)) is Nothing:
51
+ if isinstance(event := getattr(update, self.event.value, Nothing), type(Nothing)):
47
52
  return Error(
48
53
  AdapterError(f"Update is not an {self.event!r}."),
49
54
  )
50
55
 
51
- return Ok(
52
- self.cute_model.from_update(
53
- getattr(update, self.event.value).unwrap(),
54
- bound_api=api,
55
- ),
56
- )
56
+ event = event.unwrap()
57
+
58
+ if type(event) is self.cute_model:
59
+ adapted = event
60
+ else:
61
+ adapted = self.cute_model.from_update(event, bound_api=api)
62
+ else:
63
+ event = getattr(update, update.update_type.value).unwrap()
64
+ if not update.update_type or not issubclass(type(event), self.event):
65
+ return Error(AdapterError(f"Update is not an {self.event.__name__!r}."))
66
+
67
+ if type(event) is self.cute_model:
68
+ adapted = event
69
+ else:
70
+ adapted = self.cute_model.from_update(event, bound_api=api)
57
71
 
58
- event = getattr(update, update.update_type.value).unwrap()
59
- if not update.update_type or not issubclass(event.__class__, self.event):
60
- return Error(AdapterError(f"Update is not an {self.event.__name__!r}."))
61
- return Ok(self.cute_model.from_update(event, bound_api=api))
72
+ context[self.ADAPTED_VALUE_KEY] = adapted
73
+ return Ok(adapted) # type: ignore
62
74
 
63
75
 
64
76
  __all__ = ("EventAdapter",)
@@ -1,17 +1,18 @@
1
- import typing
2
-
1
+ import typing_extensions as typing
3
2
  from fntypes.result import Error, Ok, Result
4
3
 
5
- from telegrinder.api.abc import ABCAPI
6
- from telegrinder.bot.cute_types.update import UpdateCute
4
+ from telegrinder.api import API
7
5
  from telegrinder.bot.dispatch.context import Context
8
6
  from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
9
7
  from telegrinder.bot.rules.adapter.errors import AdapterError
10
- from telegrinder.node.base import ComposeError
11
- from telegrinder.node.composer import NodeSession, compose_node
8
+ from telegrinder.msgspec_utils import repr_type
9
+ from telegrinder.node.composer import NodeSession, compose_nodes
12
10
  from telegrinder.types.objects import Update
13
11
 
14
- Ts = typing.TypeVarTuple("Ts")
12
+ if typing.TYPE_CHECKING:
13
+ from telegrinder.node.base import Node
14
+
15
+ Ts = typing.TypeVarTuple("Ts", default=typing.Unpack[tuple[type["Node"], ...]])
15
16
 
16
17
 
17
18
  class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
@@ -19,24 +20,29 @@ class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
19
20
  self.nodes = nodes
20
21
 
21
22
  def __repr__(self) -> str:
22
- return "<{}: adapt Update -> {}>".format(
23
+ return "<{}: adapt Update -> ({})>".format(
23
24
  self.__class__.__name__,
24
- Update.__name__,
25
- ", ".join(node.__name__ for node in self.nodes), # type: ignore
25
+ ", ".join(repr_type(node) for node in self.nodes),
26
+ )
27
+
28
+ async def adapt(
29
+ self,
30
+ api: API,
31
+ update: Update,
32
+ context: Context,
33
+ ) -> Result[Event[tuple[*Ts]], AdapterError]:
34
+ result = await compose_nodes(
35
+ nodes={str(i): typing.cast(type["Node"], node) for i, node in enumerate(self.nodes)},
36
+ ctx=context,
37
+ data={Update: update, API: api},
26
38
  )
27
39
 
28
- async def adapt(self, api: ABCAPI, update: Update) -> Result[Event[tuple[*Ts]], AdapterError]:
29
- update_cute = UpdateCute.from_update(update, api)
30
- node_sessions: list[NodeSession] = []
31
- for node_t in self.nodes:
32
- try:
33
- # FIXME: adapters should have context
34
- node_sessions.append(await compose_node(node_t, update_cute, Context(raw_update=update))) # type: ignore
35
- except ComposeError:
36
- for session in node_sessions:
37
- await session.close(with_value=None)
38
- return Error(AdapterError(f"Couldn't compose nodes, error on {node_t}"))
39
- return Ok(Event(tuple(node_sessions))) # type: ignore
40
+ match result:
41
+ case Ok(collection):
42
+ sessions: list[NodeSession] = list(collection.sessions.values())
43
+ return Ok(Event(tuple(sessions))) # type: ignore
44
+ case Error(err):
45
+ return Error(AdapterError(f"Couldn't compose nodes, error: {err}."))
40
46
 
41
47
 
42
48
  __all__ = ("NodeAdapter",)
@@ -1,24 +1,32 @@
1
1
  from fntypes.result import Ok, Result
2
2
 
3
- from telegrinder.api.abc import ABCAPI
3
+ from telegrinder.api import API
4
4
  from telegrinder.bot.cute_types.update import UpdateCute
5
+ from telegrinder.bot.dispatch.context import Context
5
6
  from telegrinder.bot.rules.adapter.abc import ABCAdapter
6
7
  from telegrinder.bot.rules.adapter.errors import AdapterError
7
8
  from telegrinder.types.objects import Update
8
9
 
9
10
 
10
11
  class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
12
+ ADAPTED_VALUE_KEY: str = "_adapted_update_cute"
13
+
11
14
  def __repr__(self) -> str:
12
15
  return f"<{self.__class__.__name__}: adapt Update -> UpdateCute>"
13
16
 
14
17
  async def adapt(
15
18
  self,
16
- api: ABCAPI,
19
+ api: API,
17
20
  update: Update,
21
+ context: Context,
18
22
  ) -> Result[UpdateCute, AdapterError]:
19
- if not isinstance(update, UpdateCute):
20
- return Ok(UpdateCute.from_update(update, api))
21
- return Ok(update)
23
+ if self.ADAPTED_VALUE_KEY not in context:
24
+ context[self.ADAPTED_VALUE_KEY] = (
25
+ UpdateCute.from_update(update, api)
26
+ if not isinstance(update, UpdateCute)
27
+ else update
28
+ )
29
+ return Ok(context[self.ADAPTED_VALUE_KEY])
22
30
 
23
31
 
24
32
  __all__ = ("RawUpdateAdapter",)
@@ -26,12 +26,12 @@ class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
26
26
  adapter: EventAdapter[CallbackQuery] = EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery)
27
27
 
28
28
  @abc.abstractmethod
29
- async def check(self, event: CallbackQuery, ctx: Context) -> bool:
29
+ async def check(self, event: CallbackQuery, context: Context) -> bool:
30
30
  pass
31
31
 
32
32
 
33
33
  class HasData(CallbackQueryRule):
34
- async def check(self, event: CallbackQuery, ctx: Context) -> bool:
34
+ async def check(self, event: CallbackQuery) -> bool:
35
35
  return bool(event.data.unwrap_or_none())
36
36
 
37
37
 
@@ -122,7 +122,7 @@ class CallbackDataEq(CallbackQueryDataRule):
122
122
  def __init__(self, value: str, /) -> None:
123
123
  self.value = value
124
124
 
125
- async def check(self, event: CallbackQuery, ctx: Context) -> bool:
125
+ async def check(self, event: CallbackQuery) -> bool:
126
126
  return event.data.unwrap() == self.value
127
127
 
128
128
 
@@ -130,17 +130,23 @@ class CallbackDataJsonEq(CallbackQueryDataRule):
130
130
  def __init__(self, d: dict[str, typing.Any], /) -> None:
131
131
  self.d = d
132
132
 
133
- async def check(self, event: CallbackQuery, ctx: Context) -> bool:
133
+ async def check(self, event: CallbackQuery) -> bool:
134
134
  return event.decode_callback_data().unwrap_or_none() == self.d
135
135
 
136
136
 
137
137
  class CallbackDataJsonModel(CallbackQueryDataRule):
138
- def __init__(self, model: type[msgspec.Struct] | type[DataclassInstance], /) -> None:
138
+ def __init__(
139
+ self,
140
+ model: type[msgspec.Struct] | type[DataclassInstance],
141
+ *,
142
+ alias: str | None = None,
143
+ ) -> None:
139
144
  self.model = model
145
+ self.alias = alias or "data"
140
146
 
141
147
  async def check(self, event: CallbackQuery, ctx: Context) -> bool:
142
148
  with suppress(BaseException):
143
- ctx.data = decoder.decode(event.data.unwrap().encode(), type=self.model)
149
+ ctx.set(self.alias, decoder.decode(event.data.unwrap().encode(), type=self.model))
144
150
  return True
145
151
  return False
146
152
 
@@ -15,12 +15,12 @@ class ChatJoinRequestRule(ABCRule[ChatJoinRequest], requires=[]):
15
15
  adapter: EventAdapter[ChatJoinRequest] = EventAdapter(UpdateType.CHAT_JOIN_REQUEST, ChatJoinRequest)
16
16
 
17
17
  @abc.abstractmethod
18
- async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
18
+ async def check(self, event: ChatJoinRequest, context: Context) -> bool:
19
19
  pass
20
20
 
21
21
 
22
22
  class HasInviteLink(ChatJoinRequestRule):
23
- async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
23
+ async def check(self, event: ChatJoinRequest) -> bool:
24
24
  return bool(event.invite_link)
25
25
 
26
26
 
@@ -28,7 +28,7 @@ class InviteLinkName(ChatJoinRequestRule, requires=[HasInviteLink()]):
28
28
  def __init__(self, name: str, /) -> None:
29
29
  self.name = name
30
30
 
31
- async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
31
+ async def check(self, event: ChatJoinRequest) -> bool:
32
32
  return event.invite_link.unwrap().name.unwrap_or_none() == self.name
33
33
 
34
34
 
@@ -36,7 +36,7 @@ class InviteLinkByCreator(ChatJoinRequestRule, requires=[HasInviteLink()]):
36
36
  def __init__(self, creator_id: int, /) -> None:
37
37
  self.creator_id = creator_id
38
38
 
39
- async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
39
+ async def check(self, event: ChatJoinRequest) -> bool:
40
40
  return event.invite_link.unwrap().creator.id == self.creator_id
41
41
 
42
42
 
@@ -12,7 +12,7 @@ class FuncRule(ABCRule, typing.Generic[AdaptTo]):
12
12
  self,
13
13
  func: typing.Callable[[AdaptTo, Context], typing.Awaitable[bool] | bool],
14
14
  adapter: ABCAdapter[Update, AdaptTo] | None = None,
15
- ):
15
+ ) -> None:
16
16
  self.func = func
17
17
  self.adapter = adapter or RawUpdateAdapter() # type: ignore
18
18
 
@@ -21,7 +21,7 @@ class InlineQueryRule(ABCRule[InlineQuery], abc.ABC):
21
21
 
22
22
 
23
23
  class HasLocation(InlineQueryRule):
24
- async def check(self, query: InlineQuery, ctx: Context) -> bool:
24
+ async def check(self, query: InlineQuery) -> bool:
25
25
  return bool(query.location)
26
26
 
27
27
 
@@ -29,7 +29,7 @@ class InlineQueryChatType(InlineQueryRule):
29
29
  def __init__(self, chat_type: ChatType, /) -> None:
30
30
  self.chat_type = chat_type
31
31
 
32
- async def check(self, query: InlineQuery, ctx: Context) -> bool:
32
+ async def check(self, query: InlineQuery) -> bool:
33
33
  return query.chat_type.map(lambda x: x == self.chat_type).unwrap_or(False)
34
34
 
35
35
 
@@ -40,7 +40,7 @@ class InlineQueryText(InlineQueryRule):
40
40
  ]
41
41
  self.lower_case = lower_case
42
42
 
43
- async def check(self, query: InlineQuery, ctx: Context) -> bool:
43
+ async def check(self, query: InlineQuery) -> bool:
44
44
  return (query.query.lower() if self.lower_case else query.query) in self.texts
45
45
 
46
46
 
@@ -9,7 +9,7 @@ from telegrinder.tools.global_context.telegrinder_ctx import TelegrinderContext
9
9
  from .abc import ABCRule
10
10
 
11
11
  PatternLike: typing.TypeAlias = str | vbml.Pattern
12
- global_ctx = TelegrinderContext()
12
+ global_ctx: typing.Final[TelegrinderContext] = TelegrinderContext()
13
13
 
14
14
 
15
15
  def check_string(patterns: list[vbml.Pattern], s: str, ctx: Context) -> bool:
@@ -24,6 +24,8 @@ def check_string(patterns: list[vbml.Pattern], s: str, ctx: Context) -> bool:
24
24
 
25
25
 
26
26
  class Markup(ABCRule):
27
+ """Markup Language. See [VBML docs](https://github.com/tesseradecade/vbml/blob/master/docs/index.md)"""
28
+
27
29
  def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
28
30
  if not isinstance(patterns, list):
29
31
  patterns = [patterns]
@@ -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, ctx: Context) -> bool:
13
+ async def check(self, message: Message) -> bool:
14
14
  return bool(message.entities)
15
15
 
16
16
 
@@ -1,5 +1,4 @@
1
1
  from telegrinder import node
2
- from telegrinder.bot.dispatch.context import Context
3
2
  from telegrinder.tools.i18n.base import ABCTranslator
4
3
 
5
4
  from .abc import ABCRule, with_caching_translations
@@ -18,7 +17,7 @@ class Text(ABCRule):
18
17
  self.texts = texts if not ignore_case else list(map(str.lower, texts))
19
18
  self.ignore_case = ignore_case
20
19
 
21
- async def check(self, text: node.text.Text, ctx: Context) -> bool:
20
+ async def check(self, text: node.text.Text) -> bool:
22
21
  return (text if not self.ignore_case else text.lower()) in self.texts
23
22
 
24
23
  @with_caching_translations
@@ -1,5 +1,4 @@
1
1
  from telegrinder.bot.cute_types.update import UpdateCute
2
- from telegrinder.bot.dispatch.context import Context
3
2
  from telegrinder.types.enums import UpdateType
4
3
 
5
4
  from .abc import ABCRule
@@ -9,7 +8,7 @@ class IsUpdateType(ABCRule):
9
8
  def __init__(self, update_type: UpdateType, /) -> None:
10
9
  self.update_type = update_type
11
10
 
12
- async def check(self, event: UpdateCute, ctx: Context) -> bool:
11
+ async def check(self, event: UpdateCute) -> bool:
13
12
  return event.update_type == self.update_type
14
13
 
15
14
 
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
4
4
  from telegrinder.bot.cute_types.base import BaseCute
5
5
 
6
6
  if typing.TYPE_CHECKING:
7
- from telegrinder.api import ABCAPI
7
+ from telegrinder.api import API
8
8
  from telegrinder.bot.dispatch.view.abc import ABCStateView
9
9
 
10
10
  EventT = typing.TypeVar("EventT", bound=BaseCute)
@@ -12,7 +12,7 @@ EventT = typing.TypeVar("EventT", bound=BaseCute)
12
12
 
13
13
  class ABCScenario(ABC, typing.Generic[EventT]):
14
14
  @abstractmethod
15
- def wait(self, api: "ABCAPI", view: "ABCStateView[EventT]") -> typing.Any:
15
+ def wait(self, api: "API", view: "ABCStateView[EventT]") -> typing.Any:
16
16
  pass
17
17
 
18
18
 
@@ -4,12 +4,11 @@ import typing
4
4
 
5
5
  from telegrinder.bot.cute_types import CallbackQueryCute
6
6
  from telegrinder.bot.dispatch.waiter_machine import WaiterMachine
7
+ from telegrinder.bot.scenario.abc import ABCScenario
7
8
  from telegrinder.tools import InlineButton, InlineKeyboard
8
9
  from telegrinder.tools.parse_mode import ParseMode
9
10
  from telegrinder.types.objects import InlineKeyboardMarkup
10
11
 
11
- from .abc import ABCScenario
12
-
13
12
  if typing.TYPE_CHECKING:
14
13
  from telegrinder.api import API
15
14
  from telegrinder.bot.dispatch.view.abc import BaseStateView
@@ -1,8 +1,7 @@
1
1
  import typing
2
2
 
3
3
  from telegrinder.bot.cute_types import CallbackQueryCute
4
-
5
- from .checkbox import Checkbox
4
+ from telegrinder.bot.scenario.checkbox import Checkbox
6
5
 
7
6
  if typing.TYPE_CHECKING:
8
7
  from telegrinder.api import API