telegrinder 0.3.1__py3-none-any.whl → 0.3.2__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 (164) hide show
  1. telegrinder/__init__.py +144 -144
  2. telegrinder/api/__init__.py +8 -8
  3. telegrinder/api/api.py +93 -93
  4. telegrinder/api/error.py +16 -16
  5. telegrinder/api/response.py +20 -20
  6. telegrinder/api/token.py +36 -36
  7. telegrinder/bot/__init__.py +66 -66
  8. telegrinder/bot/bot.py +76 -76
  9. telegrinder/bot/cute_types/__init__.py +11 -11
  10. telegrinder/bot/cute_types/base.py +258 -234
  11. telegrinder/bot/cute_types/callback_query.py +382 -382
  12. telegrinder/bot/cute_types/chat_join_request.py +61 -61
  13. telegrinder/bot/cute_types/chat_member_updated.py +160 -160
  14. telegrinder/bot/cute_types/inline_query.py +53 -53
  15. telegrinder/bot/cute_types/message.py +2631 -2631
  16. telegrinder/bot/cute_types/update.py +75 -75
  17. telegrinder/bot/cute_types/utils.py +92 -92
  18. telegrinder/bot/dispatch/__init__.py +55 -55
  19. telegrinder/bot/dispatch/abc.py +77 -77
  20. telegrinder/bot/dispatch/context.py +92 -92
  21. telegrinder/bot/dispatch/dispatch.py +202 -201
  22. telegrinder/bot/dispatch/handler/__init__.py +13 -13
  23. telegrinder/bot/dispatch/handler/abc.py +24 -24
  24. telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
  25. telegrinder/bot/dispatch/handler/base.py +57 -57
  26. telegrinder/bot/dispatch/handler/document_reply.py +44 -44
  27. telegrinder/bot/dispatch/handler/func.py +128 -123
  28. telegrinder/bot/dispatch/handler/media_group_reply.py +43 -43
  29. telegrinder/bot/dispatch/handler/message_reply.py +36 -36
  30. telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
  31. telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
  32. telegrinder/bot/dispatch/handler/video_reply.py +44 -44
  33. telegrinder/bot/dispatch/middleware/__init__.py +3 -3
  34. telegrinder/bot/dispatch/middleware/abc.py +16 -16
  35. telegrinder/bot/dispatch/process.py +132 -132
  36. telegrinder/bot/dispatch/return_manager/__init__.py +13 -13
  37. telegrinder/bot/dispatch/return_manager/abc.py +108 -108
  38. telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
  39. telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
  40. telegrinder/bot/dispatch/return_manager/message.py +36 -36
  41. telegrinder/bot/dispatch/view/__init__.py +13 -13
  42. telegrinder/bot/dispatch/view/abc.py +41 -41
  43. telegrinder/bot/dispatch/view/base.py +200 -211
  44. telegrinder/bot/dispatch/view/box.py +129 -129
  45. telegrinder/bot/dispatch/view/callback_query.py +17 -17
  46. telegrinder/bot/dispatch/view/chat_join_request.py +16 -16
  47. telegrinder/bot/dispatch/view/chat_member.py +39 -39
  48. telegrinder/bot/dispatch/view/inline_query.py +17 -17
  49. telegrinder/bot/dispatch/view/message.py +44 -44
  50. telegrinder/bot/dispatch/view/raw.py +114 -118
  51. telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
  52. telegrinder/bot/dispatch/waiter_machine/actions.py +13 -13
  53. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
  54. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +57 -57
  55. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +57 -57
  56. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +53 -53
  57. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
  58. telegrinder/bot/dispatch/waiter_machine/machine.py +168 -170
  59. telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
  60. telegrinder/bot/dispatch/waiter_machine/short_state.py +65 -65
  61. telegrinder/bot/polling/__init__.py +4 -4
  62. telegrinder/bot/polling/abc.py +25 -25
  63. telegrinder/bot/polling/polling.py +131 -131
  64. telegrinder/bot/rules/__init__.py +62 -62
  65. telegrinder/bot/rules/abc.py +238 -238
  66. telegrinder/bot/rules/adapter/__init__.py +9 -9
  67. telegrinder/bot/rules/adapter/abc.py +29 -29
  68. telegrinder/bot/rules/adapter/errors.py +5 -5
  69. telegrinder/bot/rules/adapter/event.py +76 -76
  70. telegrinder/bot/rules/adapter/node.py +48 -48
  71. telegrinder/bot/rules/adapter/raw_update.py +30 -30
  72. telegrinder/bot/rules/callback_data.py +171 -171
  73. telegrinder/bot/rules/chat_join.py +48 -48
  74. telegrinder/bot/rules/command.py +126 -126
  75. telegrinder/bot/rules/enum_text.py +36 -36
  76. telegrinder/bot/rules/func.py +26 -26
  77. telegrinder/bot/rules/fuzzy.py +24 -24
  78. telegrinder/bot/rules/inline.py +60 -60
  79. telegrinder/bot/rules/integer.py +20 -20
  80. telegrinder/bot/rules/is_from.py +146 -146
  81. telegrinder/bot/rules/markup.py +43 -43
  82. telegrinder/bot/rules/mention.py +14 -14
  83. telegrinder/bot/rules/message.py +17 -17
  84. telegrinder/bot/rules/message_entities.py +35 -35
  85. telegrinder/bot/rules/node.py +27 -27
  86. telegrinder/bot/rules/regex.py +37 -37
  87. telegrinder/bot/rules/rule_enum.py +72 -72
  88. telegrinder/bot/rules/start.py +42 -42
  89. telegrinder/bot/rules/state.py +37 -37
  90. telegrinder/bot/rules/text.py +33 -33
  91. telegrinder/bot/rules/update.py +15 -15
  92. telegrinder/bot/scenario/__init__.py +5 -5
  93. telegrinder/bot/scenario/abc.py +19 -19
  94. telegrinder/bot/scenario/checkbox.py +167 -147
  95. telegrinder/bot/scenario/choice.py +46 -44
  96. telegrinder/client/__init__.py +4 -4
  97. telegrinder/client/abc.py +75 -75
  98. telegrinder/client/aiohttp.py +130 -130
  99. telegrinder/model.py +244 -244
  100. telegrinder/modules.py +237 -237
  101. telegrinder/msgspec_json.py +14 -14
  102. telegrinder/msgspec_utils.py +410 -410
  103. telegrinder/node/__init__.py +20 -20
  104. telegrinder/node/attachment.py +92 -92
  105. telegrinder/node/base.py +143 -144
  106. telegrinder/node/callback_query.py +14 -14
  107. telegrinder/node/command.py +33 -33
  108. telegrinder/node/composer.py +196 -184
  109. telegrinder/node/container.py +27 -27
  110. telegrinder/node/event.py +71 -73
  111. telegrinder/node/me.py +16 -16
  112. telegrinder/node/message.py +14 -14
  113. telegrinder/node/polymorphic.py +48 -52
  114. telegrinder/node/rule.py +76 -76
  115. telegrinder/node/scope.py +38 -38
  116. telegrinder/node/source.py +71 -71
  117. telegrinder/node/text.py +21 -21
  118. telegrinder/node/tools/__init__.py +3 -3
  119. telegrinder/node/tools/generator.py +40 -40
  120. telegrinder/node/update.py +15 -15
  121. telegrinder/rules.py +0 -0
  122. telegrinder/tools/__init__.py +74 -74
  123. telegrinder/tools/buttons.py +79 -79
  124. telegrinder/tools/error_handler/__init__.py +7 -7
  125. telegrinder/tools/error_handler/abc.py +33 -33
  126. telegrinder/tools/error_handler/error.py +9 -9
  127. telegrinder/tools/error_handler/error_handler.py +193 -193
  128. telegrinder/tools/formatting/__init__.py +46 -46
  129. telegrinder/tools/formatting/html.py +308 -308
  130. telegrinder/tools/formatting/links.py +33 -33
  131. telegrinder/tools/formatting/spec_html_formats.py +111 -111
  132. telegrinder/tools/functional.py +12 -12
  133. telegrinder/tools/global_context/__init__.py +7 -7
  134. telegrinder/tools/global_context/abc.py +63 -63
  135. telegrinder/tools/global_context/global_context.py +412 -412
  136. telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
  137. telegrinder/tools/i18n/__init__.py +12 -12
  138. telegrinder/tools/i18n/abc.py +32 -32
  139. telegrinder/tools/i18n/middleware/__init__.py +3 -3
  140. telegrinder/tools/i18n/middleware/abc.py +25 -25
  141. telegrinder/tools/i18n/simple.py +43 -43
  142. telegrinder/tools/kb_set/__init__.py +4 -4
  143. telegrinder/tools/kb_set/base.py +15 -15
  144. telegrinder/tools/kb_set/yaml.py +63 -63
  145. telegrinder/tools/keyboard.py +128 -128
  146. telegrinder/tools/limited_dict.py +37 -37
  147. telegrinder/tools/loop_wrapper/__init__.py +4 -4
  148. telegrinder/tools/loop_wrapper/abc.py +15 -15
  149. telegrinder/tools/loop_wrapper/loop_wrapper.py +216 -216
  150. telegrinder/tools/magic.py +168 -168
  151. telegrinder/tools/parse_mode.py +6 -6
  152. telegrinder/tools/state_storage/__init__.py +4 -4
  153. telegrinder/tools/state_storage/abc.py +35 -35
  154. telegrinder/tools/state_storage/memory.py +25 -25
  155. telegrinder/types/__init__.py +6 -6
  156. telegrinder/types/enums.py +672 -672
  157. telegrinder/types/methods.py +4633 -4633
  158. telegrinder/types/objects.py +6317 -6317
  159. telegrinder/verification_utils.py +32 -32
  160. {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/LICENSE +22 -22
  161. {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/METADATA +1 -1
  162. telegrinder-0.3.2.dist-info/RECORD +164 -0
  163. telegrinder-0.3.1.dist-info/RECORD +0 -164
  164. {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/WHEEL +0 -0
@@ -1,76 +1,76 @@
1
- import typing
2
-
3
- from fntypes.result import Error, Ok, Result
4
-
5
- from telegrinder.api import API
6
- from telegrinder.bot.cute_types.base import BaseCute
7
- from telegrinder.bot.dispatch.context import Context
8
- from telegrinder.bot.rules.adapter.abc import ABCAdapter
9
- from telegrinder.bot.rules.adapter.errors import AdapterError
10
- from telegrinder.msgspec_utils import Nothing
11
- from telegrinder.types.enums import UpdateType
12
- from telegrinder.types.objects import Model, Update
13
-
14
- ToCute = typing.TypeVar("ToCute", bound=BaseCute)
15
-
16
-
17
- class EventAdapter(ABCAdapter[Update, ToCute]):
18
- ADAPTED_VALUE_KEY: str = "_adapted_cute_event"
19
-
20
- def __init__(self, event: UpdateType | type[Model], cute_model: type[ToCute]) -> None:
21
- self.event = event
22
- self.cute_model = cute_model
23
-
24
- def __repr__(self) -> str:
25
- if isinstance(self.event, str):
26
- raw_update_type = Update.__annotations__.get(self.event, "Unknown")
27
- raw_update_type = (
28
- typing.get_args(raw_update_type)[0].__forward_arg__
29
- if typing.get_args(raw_update_type)
30
- else raw_update_type
31
- )
32
- else:
33
- raw_update_type = self.event.__name__
34
-
35
- return "<{}: adapt Update -> {} -> {}>".format(
36
- self.__class__.__name__,
37
- raw_update_type,
38
- self.cute_model.__name__,
39
- )
40
-
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
-
45
- if isinstance(self.event, UpdateType):
46
- if update.update_type != self.event:
47
- return Error(
48
- AdapterError(f"Update is not of event type {self.event!r}."),
49
- )
50
-
51
- if isinstance(event := getattr(update, self.event.value, Nothing), type(Nothing)):
52
- return Error(
53
- AdapterError(f"Update is not an {self.event!r}."),
54
- )
55
-
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)
71
-
72
- context[self.ADAPTED_VALUE_KEY] = adapted
73
- return Ok(adapted) # type: ignore
74
-
75
-
76
- __all__ = ("EventAdapter",)
1
+ import typing
2
+
3
+ from fntypes.result import Error, Ok, Result
4
+
5
+ from telegrinder.api import API
6
+ from telegrinder.bot.cute_types.base import BaseCute
7
+ from telegrinder.bot.dispatch.context import Context
8
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter
9
+ from telegrinder.bot.rules.adapter.errors import AdapterError
10
+ from telegrinder.msgspec_utils import Nothing
11
+ from telegrinder.types.enums import UpdateType
12
+ from telegrinder.types.objects import Model, Update
13
+
14
+ ToCute = typing.TypeVar("ToCute", bound=BaseCute)
15
+
16
+
17
+ class EventAdapter(ABCAdapter[Update, ToCute]):
18
+ ADAPTED_VALUE_KEY: str = "_adapted_cute_event"
19
+
20
+ def __init__(self, event: UpdateType | type[Model], cute_model: type[ToCute]) -> None:
21
+ self.event = event
22
+ self.cute_model = cute_model
23
+
24
+ def __repr__(self) -> str:
25
+ if isinstance(self.event, str):
26
+ raw_update_type = Update.__annotations__.get(self.event, "Unknown")
27
+ raw_update_type = (
28
+ typing.get_args(raw_update_type)[0].__forward_arg__
29
+ if typing.get_args(raw_update_type)
30
+ else raw_update_type
31
+ )
32
+ else:
33
+ raw_update_type = self.event.__name__
34
+
35
+ return "<{}: adapt Update -> {} -> {}>".format(
36
+ self.__class__.__name__,
37
+ raw_update_type,
38
+ self.cute_model.__name__,
39
+ )
40
+
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
+
45
+ if isinstance(self.event, UpdateType):
46
+ if update.update_type != self.event:
47
+ return Error(
48
+ AdapterError(f"Update is not of event type {self.event!r}."),
49
+ )
50
+
51
+ if isinstance(event := getattr(update, self.event.value, Nothing), type(Nothing)):
52
+ return Error(
53
+ AdapterError(f"Update is not an {self.event!r}."),
54
+ )
55
+
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)
71
+
72
+ context[self.ADAPTED_VALUE_KEY] = adapted
73
+ return Ok(adapted) # type: ignore
74
+
75
+
76
+ __all__ = ("EventAdapter",)
@@ -1,48 +1,48 @@
1
- import typing_extensions as typing
2
- from fntypes.result import Error, Ok, Result
3
-
4
- from telegrinder.api import API
5
- from telegrinder.bot.dispatch.context import Context
6
- from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
7
- from telegrinder.bot.rules.adapter.errors import AdapterError
8
- from telegrinder.msgspec_utils import repr_type
9
- from telegrinder.node.composer import NodeSession, compose_nodes
10
- from telegrinder.types.objects import Update
11
-
12
- if typing.TYPE_CHECKING:
13
- from telegrinder.node.base import Node
14
-
15
- Ts = typing.TypeVarTuple("Ts", default=typing.Unpack[tuple[type["Node"], ...]])
16
-
17
-
18
- class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
19
- def __init__(self, *nodes: *Ts) -> None:
20
- self.nodes = nodes
21
-
22
- def __repr__(self) -> str:
23
- return "<{}: adapt Update -> ({})>".format(
24
- self.__class__.__name__,
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},
38
- )
39
-
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}."))
46
-
47
-
48
- __all__ = ("NodeAdapter",)
1
+ import typing_extensions as typing
2
+ from fntypes.result import Error, Ok, Result
3
+
4
+ from telegrinder.api import API
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
7
+ from telegrinder.bot.rules.adapter.errors import AdapterError
8
+ from telegrinder.msgspec_utils import repr_type
9
+ from telegrinder.node.composer import NodeSession, compose_nodes
10
+ from telegrinder.types.objects import Update
11
+
12
+ if typing.TYPE_CHECKING:
13
+ from telegrinder.node.base import Node
14
+
15
+ Ts = typing.TypeVarTuple("Ts", default=typing.Unpack[tuple[type["Node"], ...]])
16
+
17
+
18
+ class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
19
+ def __init__(self, *nodes: *Ts) -> None:
20
+ self.nodes = nodes
21
+
22
+ def __repr__(self) -> str:
23
+ return "<{}: adapt Update -> ({})>".format(
24
+ self.__class__.__name__,
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},
38
+ )
39
+
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}."))
46
+
47
+
48
+ __all__ = ("NodeAdapter",)
@@ -1,30 +1,30 @@
1
- from fntypes.result import Ok, Result
2
-
3
- from telegrinder.api import API
4
- from telegrinder.bot.cute_types.update import UpdateCute
5
- from telegrinder.bot.dispatch.context import Context
6
- from telegrinder.bot.rules.adapter.abc import ABCAdapter
7
- from telegrinder.bot.rules.adapter.errors import AdapterError
8
- from telegrinder.types.objects import Update
9
-
10
-
11
- class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
12
- ADAPTED_VALUE_KEY: str = "_adapted_update_cute"
13
-
14
- def __repr__(self) -> str:
15
- return f"<{self.__class__.__name__}: adapt Update -> UpdateCute>"
16
-
17
- async def adapt(
18
- self,
19
- api: API,
20
- update: Update,
21
- context: Context,
22
- ) -> Result[UpdateCute, AdapterError]:
23
- if self.ADAPTED_VALUE_KEY not in context:
24
- context[self.ADAPTED_VALUE_KEY] = (
25
- UpdateCute.from_update(update, api) if not isinstance(update, UpdateCute) else update
26
- )
27
- return Ok(context[self.ADAPTED_VALUE_KEY])
28
-
29
-
30
- __all__ = ("RawUpdateAdapter",)
1
+ from fntypes.result import Ok, Result
2
+
3
+ from telegrinder.api import API
4
+ from telegrinder.bot.cute_types.update import UpdateCute
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter
7
+ from telegrinder.bot.rules.adapter.errors import AdapterError
8
+ from telegrinder.types.objects import Update
9
+
10
+
11
+ class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
12
+ ADAPTED_VALUE_KEY: str = "_adapted_update_cute"
13
+
14
+ def __repr__(self) -> str:
15
+ return f"<{self.__class__.__name__}: adapt Update -> UpdateCute>"
16
+
17
+ async def adapt(
18
+ self,
19
+ api: API,
20
+ update: Update,
21
+ context: Context,
22
+ ) -> Result[UpdateCute, AdapterError]:
23
+ if self.ADAPTED_VALUE_KEY not in context:
24
+ context[self.ADAPTED_VALUE_KEY] = (
25
+ UpdateCute.from_update(update, api) if not isinstance(update, UpdateCute) else update
26
+ )
27
+ return Ok(context[self.ADAPTED_VALUE_KEY])
28
+
29
+
30
+ __all__ = ("RawUpdateAdapter",)
@@ -1,171 +1,171 @@
1
- import abc
2
- import inspect
3
- import typing
4
- from contextlib import suppress
5
-
6
- import msgspec
7
-
8
- from telegrinder.bot.cute_types import CallbackQueryCute
9
- from telegrinder.bot.dispatch.context import Context
10
- from telegrinder.bot.rules.adapter import EventAdapter
11
- from telegrinder.model import decoder
12
- from telegrinder.tools.buttons import DataclassInstance
13
- from telegrinder.types.enums import UpdateType
14
-
15
- from .abc import ABCRule, CheckResult
16
- from .markup import Markup, PatternLike, check_string
17
-
18
- CallbackQuery: typing.TypeAlias = CallbackQueryCute
19
- Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
20
- MapDict: typing.TypeAlias = dict[str, "typing.Any | type[typing.Any] | Validator | list[MapDict] | MapDict"]
21
- CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type[typing.Any] | Validator | CallbackMap"]]
22
- CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
23
-
24
-
25
- class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
26
- adapter: EventAdapter[CallbackQuery] = EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery)
27
-
28
- @abc.abstractmethod
29
- def check(self, event: CallbackQuery, context: Context) -> CheckResult:
30
- pass
31
-
32
-
33
- class HasData(CallbackQueryRule):
34
- def check(self, event: CallbackQuery) -> bool:
35
- return bool(event.data.unwrap_or_none())
36
-
37
-
38
- class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
39
- pass
40
-
41
-
42
- class CallbackDataMap(CallbackQueryDataRule):
43
- def __init__(self, mapping: MapDict, /) -> None:
44
- self.mapping = self.transform_to_callbacks(
45
- self.transform_to_map(mapping),
46
- )
47
-
48
- @classmethod
49
- def transform_to_map(cls, mapping: MapDict) -> CallbackMap:
50
- """Transforms MapDict to CallbackMap."""
51
-
52
- callback_map = []
53
-
54
- for k, v in mapping.items():
55
- if isinstance(v, dict):
56
- v = cls.transform_to_map(v)
57
- callback_map.append((k, v))
58
-
59
- return callback_map
60
-
61
- @classmethod
62
- def transform_to_callbacks(cls, callback_map: CallbackMap) -> CallbackMapStrict:
63
- """Transforms `CallbackMap` to `CallbackMapStrict`."""
64
-
65
- callback_map_result = []
66
-
67
- for key, value in callback_map:
68
- if isinstance(value, type):
69
- validator = (lambda tp: lambda v: isinstance(v, tp))(value)
70
- elif isinstance(value, list):
71
- validator = cls.transform_to_callbacks(value)
72
- elif not callable(value):
73
- validator = (lambda val: lambda v: val == v)(value)
74
- else:
75
- validator = value
76
- callback_map_result.append((key, validator))
77
-
78
- return callback_map_result
79
-
80
- @staticmethod
81
- async def run_validator(value: typing.Any, validator: Validator) -> bool:
82
- """Run async or sync validator."""
83
-
84
- with suppress(BaseException):
85
- result = validator(value)
86
- if inspect.isawaitable(result):
87
- result = await result
88
- return result
89
-
90
- return False
91
-
92
- @classmethod
93
- async def match(cls, callback_data: dict[str, typing.Any], callback_map: CallbackMapStrict) -> bool:
94
- """Matches callback_data with callback_map recursively."""
95
-
96
- for key, validator in callback_map:
97
- if key not in callback_data:
98
- return False
99
-
100
- if isinstance(validator, list):
101
- if not (
102
- isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)
103
- ):
104
- return False
105
-
106
- elif not await cls.run_validator(callback_data[key], validator):
107
- return False
108
-
109
- return True
110
-
111
- async def check(self, event: CallbackQuery, ctx: Context) -> bool:
112
- callback_data = event.decode_callback_data().unwrap_or_none()
113
- if callback_data is None:
114
- return False
115
- if await self.match(callback_data, self.mapping):
116
- ctx.update(callback_data)
117
- return True
118
- return False
119
-
120
-
121
- class CallbackDataEq(CallbackQueryDataRule):
122
- def __init__(self, value: str, /) -> None:
123
- self.value = value
124
-
125
- def check(self, event: CallbackQuery) -> bool:
126
- return event.data.unwrap() == self.value
127
-
128
-
129
- class CallbackDataJsonEq(CallbackQueryDataRule):
130
- def __init__(self, d: dict[str, typing.Any], /) -> None:
131
- self.d = d
132
-
133
- def check(self, event: CallbackQuery) -> bool:
134
- return event.decode_callback_data().unwrap_or_none() == self.d
135
-
136
-
137
- class CallbackDataJsonModel(CallbackQueryDataRule):
138
- def __init__(
139
- self,
140
- model: type[msgspec.Struct] | type[DataclassInstance],
141
- *,
142
- alias: str | None = None,
143
- ) -> None:
144
- self.model = model
145
- self.alias = alias or "data"
146
-
147
- def check(self, event: CallbackQuery, ctx: Context) -> bool:
148
- with suppress(BaseException):
149
- ctx.set(self.alias, decoder.decode(event.data.unwrap().encode(), type=self.model))
150
- return True
151
- return False
152
-
153
-
154
- class CallbackDataMarkup(CallbackQueryDataRule):
155
- def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
156
- self.patterns = Markup(patterns).patterns
157
-
158
- def check(self, event: CallbackQuery, ctx: Context) -> bool:
159
- return check_string(self.patterns, event.data.unwrap(), ctx)
160
-
161
-
162
- __all__ = (
163
- "CallbackDataEq",
164
- "CallbackDataJsonEq",
165
- "CallbackDataJsonModel",
166
- "CallbackDataMap",
167
- "CallbackDataMarkup",
168
- "CallbackQueryDataRule",
169
- "CallbackQueryRule",
170
- "HasData",
171
- )
1
+ import abc
2
+ import inspect
3
+ import typing
4
+ from contextlib import suppress
5
+
6
+ import msgspec
7
+
8
+ from telegrinder.bot.cute_types import CallbackQueryCute
9
+ from telegrinder.bot.dispatch.context import Context
10
+ from telegrinder.bot.rules.adapter import EventAdapter
11
+ from telegrinder.model import decoder
12
+ from telegrinder.tools.buttons import DataclassInstance
13
+ from telegrinder.types.enums import UpdateType
14
+
15
+ from .abc import ABCRule, CheckResult
16
+ from .markup import Markup, PatternLike, check_string
17
+
18
+ CallbackQuery: typing.TypeAlias = CallbackQueryCute
19
+ Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
20
+ MapDict: typing.TypeAlias = dict[str, "typing.Any | type[typing.Any] | Validator | list[MapDict] | MapDict"]
21
+ CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type[typing.Any] | Validator | CallbackMap"]]
22
+ CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
23
+
24
+
25
+ class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
26
+ adapter: EventAdapter[CallbackQuery] = EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery)
27
+
28
+ @abc.abstractmethod
29
+ def check(self, event: CallbackQuery, context: Context) -> CheckResult:
30
+ pass
31
+
32
+
33
+ class HasData(CallbackQueryRule):
34
+ def check(self, event: CallbackQuery) -> bool:
35
+ return bool(event.data.unwrap_or_none())
36
+
37
+
38
+ class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
39
+ pass
40
+
41
+
42
+ class CallbackDataMap(CallbackQueryDataRule):
43
+ def __init__(self, mapping: MapDict, /) -> None:
44
+ self.mapping = self.transform_to_callbacks(
45
+ self.transform_to_map(mapping),
46
+ )
47
+
48
+ @classmethod
49
+ def transform_to_map(cls, mapping: MapDict) -> CallbackMap:
50
+ """Transforms MapDict to CallbackMap."""
51
+
52
+ callback_map = []
53
+
54
+ for k, v in mapping.items():
55
+ if isinstance(v, dict):
56
+ v = cls.transform_to_map(v)
57
+ callback_map.append((k, v))
58
+
59
+ return callback_map
60
+
61
+ @classmethod
62
+ def transform_to_callbacks(cls, callback_map: CallbackMap) -> CallbackMapStrict:
63
+ """Transforms `CallbackMap` to `CallbackMapStrict`."""
64
+
65
+ callback_map_result = []
66
+
67
+ for key, value in callback_map:
68
+ if isinstance(value, type):
69
+ validator = (lambda tp: lambda v: isinstance(v, tp))(value)
70
+ elif isinstance(value, list):
71
+ validator = cls.transform_to_callbacks(value)
72
+ elif not callable(value):
73
+ validator = (lambda val: lambda v: val == v)(value)
74
+ else:
75
+ validator = value
76
+ callback_map_result.append((key, validator))
77
+
78
+ return callback_map_result
79
+
80
+ @staticmethod
81
+ async def run_validator(value: typing.Any, validator: Validator) -> bool:
82
+ """Run async or sync validator."""
83
+
84
+ with suppress(BaseException):
85
+ result = validator(value)
86
+ if inspect.isawaitable(result):
87
+ result = await result
88
+ return result
89
+
90
+ return False
91
+
92
+ @classmethod
93
+ async def match(cls, callback_data: dict[str, typing.Any], callback_map: CallbackMapStrict) -> bool:
94
+ """Matches callback_data with callback_map recursively."""
95
+
96
+ for key, validator in callback_map:
97
+ if key not in callback_data:
98
+ return False
99
+
100
+ if isinstance(validator, list):
101
+ if not (
102
+ isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)
103
+ ):
104
+ return False
105
+
106
+ elif not await cls.run_validator(callback_data[key], validator):
107
+ return False
108
+
109
+ return True
110
+
111
+ async def check(self, event: CallbackQuery, ctx: Context) -> bool:
112
+ callback_data = event.decode_callback_data().unwrap_or_none()
113
+ if callback_data is None:
114
+ return False
115
+ if await self.match(callback_data, self.mapping):
116
+ ctx.update(callback_data)
117
+ return True
118
+ return False
119
+
120
+
121
+ class CallbackDataEq(CallbackQueryDataRule):
122
+ def __init__(self, value: str, /) -> None:
123
+ self.value = value
124
+
125
+ def check(self, event: CallbackQuery) -> bool:
126
+ return event.data.unwrap() == self.value
127
+
128
+
129
+ class CallbackDataJsonEq(CallbackQueryDataRule):
130
+ def __init__(self, d: dict[str, typing.Any], /) -> None:
131
+ self.d = d
132
+
133
+ def check(self, event: CallbackQuery) -> bool:
134
+ return event.decode_callback_data().unwrap_or_none() == self.d
135
+
136
+
137
+ class CallbackDataJsonModel(CallbackQueryDataRule):
138
+ def __init__(
139
+ self,
140
+ model: type[msgspec.Struct] | type[DataclassInstance],
141
+ *,
142
+ alias: str | None = None,
143
+ ) -> None:
144
+ self.model = model
145
+ self.alias = alias or "data"
146
+
147
+ def check(self, event: CallbackQuery, ctx: Context) -> bool:
148
+ with suppress(BaseException):
149
+ ctx.set(self.alias, decoder.decode(event.data.unwrap().encode(), type=self.model))
150
+ return True
151
+ return False
152
+
153
+
154
+ class CallbackDataMarkup(CallbackQueryDataRule):
155
+ def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
156
+ self.patterns = Markup(patterns).patterns
157
+
158
+ def check(self, event: CallbackQuery, ctx: Context) -> bool:
159
+ return check_string(self.patterns, event.data.unwrap(), ctx)
160
+
161
+
162
+ __all__ = (
163
+ "CallbackDataEq",
164
+ "CallbackDataJsonEq",
165
+ "CallbackDataJsonModel",
166
+ "CallbackDataMap",
167
+ "CallbackDataMarkup",
168
+ "CallbackQueryDataRule",
169
+ "CallbackQueryRule",
170
+ "HasData",
171
+ )