telegrinder 0.1.dev168__py3-none-any.whl → 0.1.dev170__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 (100) hide show
  1. telegrinder/__init__.py +9 -3
  2. telegrinder/bot/__init__.py +7 -5
  3. telegrinder/bot/cute_types/base.py +12 -14
  4. telegrinder/bot/cute_types/callback_query.py +54 -43
  5. telegrinder/bot/cute_types/chat_join_request.py +8 -7
  6. telegrinder/bot/cute_types/chat_member_updated.py +23 -17
  7. telegrinder/bot/cute_types/inline_query.py +1 -1
  8. telegrinder/bot/cute_types/message.py +331 -183
  9. telegrinder/bot/cute_types/update.py +4 -8
  10. telegrinder/bot/cute_types/utils.py +1 -5
  11. telegrinder/bot/dispatch/__init__.py +2 -3
  12. telegrinder/bot/dispatch/abc.py +4 -0
  13. telegrinder/bot/dispatch/context.py +9 -4
  14. telegrinder/bot/dispatch/dispatch.py +35 -33
  15. telegrinder/bot/dispatch/handler/func.py +34 -13
  16. telegrinder/bot/dispatch/handler/message_reply.py +6 -3
  17. telegrinder/bot/dispatch/middleware/abc.py +4 -4
  18. telegrinder/bot/dispatch/process.py +40 -13
  19. telegrinder/bot/dispatch/return_manager/abc.py +12 -12
  20. telegrinder/bot/dispatch/return_manager/callback_query.py +1 -3
  21. telegrinder/bot/dispatch/return_manager/inline_query.py +1 -3
  22. telegrinder/bot/dispatch/view/abc.py +37 -45
  23. telegrinder/bot/dispatch/view/box.py +66 -50
  24. telegrinder/bot/dispatch/view/message.py +1 -5
  25. telegrinder/bot/dispatch/view/raw.py +6 -6
  26. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -1
  27. telegrinder/bot/dispatch/waiter_machine/machine.py +77 -35
  28. telegrinder/bot/dispatch/waiter_machine/middleware.py +31 -7
  29. telegrinder/bot/dispatch/waiter_machine/short_state.py +17 -8
  30. telegrinder/bot/polling/polling.py +4 -4
  31. telegrinder/bot/rules/__init__.py +9 -6
  32. telegrinder/bot/rules/abc.py +99 -22
  33. telegrinder/bot/rules/adapter/__init__.py +4 -1
  34. telegrinder/bot/rules/adapter/abc.py +11 -6
  35. telegrinder/bot/rules/adapter/errors.py +1 -2
  36. telegrinder/bot/rules/adapter/event.py +14 -9
  37. telegrinder/bot/rules/adapter/node.py +42 -0
  38. telegrinder/bot/rules/callback_data.py +4 -4
  39. telegrinder/bot/rules/chat_join.py +3 -2
  40. telegrinder/bot/rules/command.py +26 -14
  41. telegrinder/bot/rules/enum_text.py +5 -5
  42. telegrinder/bot/rules/func.py +6 -6
  43. telegrinder/bot/rules/fuzzy.py +5 -7
  44. telegrinder/bot/rules/inline.py +4 -5
  45. telegrinder/bot/rules/integer.py +10 -8
  46. telegrinder/bot/rules/is_from.py +63 -91
  47. telegrinder/bot/rules/markup.py +5 -5
  48. telegrinder/bot/rules/mention.py +4 -4
  49. telegrinder/bot/rules/message.py +1 -1
  50. telegrinder/bot/rules/node.py +27 -0
  51. telegrinder/bot/rules/regex.py +5 -5
  52. telegrinder/bot/rules/rule_enum.py +4 -4
  53. telegrinder/bot/rules/start.py +5 -5
  54. telegrinder/bot/rules/text.py +9 -13
  55. telegrinder/bot/rules/update.py +4 -4
  56. telegrinder/bot/scenario/__init__.py +3 -3
  57. telegrinder/bot/scenario/choice.py +2 -3
  58. telegrinder/model.py +51 -16
  59. telegrinder/modules.py +14 -6
  60. telegrinder/msgspec_utils.py +67 -23
  61. telegrinder/node/__init__.py +26 -8
  62. telegrinder/node/attachment.py +13 -9
  63. telegrinder/node/base.py +27 -14
  64. telegrinder/node/callback_query.py +18 -0
  65. telegrinder/node/command.py +29 -0
  66. telegrinder/node/composer.py +119 -30
  67. telegrinder/node/me.py +14 -0
  68. telegrinder/node/message.py +2 -4
  69. telegrinder/node/polymorphic.py +44 -0
  70. telegrinder/node/rule.py +26 -22
  71. telegrinder/node/scope.py +36 -0
  72. telegrinder/node/source.py +37 -10
  73. telegrinder/node/text.py +11 -5
  74. telegrinder/node/tools/__init__.py +2 -2
  75. telegrinder/node/tools/generator.py +6 -6
  76. telegrinder/tools/__init__.py +8 -13
  77. telegrinder/tools/buttons.py +23 -17
  78. telegrinder/tools/error_handler/error_handler.py +11 -14
  79. telegrinder/tools/formatting/__init__.py +0 -6
  80. telegrinder/tools/formatting/html.py +10 -12
  81. telegrinder/tools/formatting/links.py +0 -5
  82. telegrinder/tools/formatting/spec_html_formats.py +0 -11
  83. telegrinder/tools/global_context/abc.py +1 -3
  84. telegrinder/tools/global_context/global_context.py +6 -16
  85. telegrinder/tools/i18n/simple.py +1 -3
  86. telegrinder/tools/kb_set/yaml.py +1 -2
  87. telegrinder/tools/keyboard.py +7 -8
  88. telegrinder/tools/limited_dict.py +1 -1
  89. telegrinder/tools/loop_wrapper/loop_wrapper.py +6 -5
  90. telegrinder/tools/magic.py +27 -5
  91. telegrinder/types/__init__.py +20 -0
  92. telegrinder/types/enums.py +37 -31
  93. telegrinder/types/methods.py +608 -327
  94. telegrinder/types/objects.py +1139 -716
  95. {telegrinder-0.1.dev168.dist-info → telegrinder-0.1.dev170.dist-info}/LICENSE +1 -1
  96. {telegrinder-0.1.dev168.dist-info → telegrinder-0.1.dev170.dist-info}/METADATA +6 -5
  97. telegrinder-0.1.dev170.dist-info/RECORD +143 -0
  98. telegrinder/bot/dispatch/composition.py +0 -88
  99. telegrinder-0.1.dev168.dist-info/RECORD +0 -137
  100. {telegrinder-0.1.dev168.dist-info → telegrinder-0.1.dev170.dist-info}/WHEEL +0 -0
@@ -6,6 +6,7 @@ from telegrinder.bot.dispatch.context import Context
6
6
  from telegrinder.bot.dispatch.handler.func import FuncHandler
7
7
  from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
8
8
  from telegrinder.bot.dispatch.view.abc import ABCStateView
9
+ from telegrinder.bot.dispatch.waiter_machine.short_state import ShortStateContext
9
10
 
10
11
  if typing.TYPE_CHECKING:
11
12
  from .machine import WaiterMachine
@@ -27,7 +28,7 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
27
28
  if not self.view or not hasattr(self.view, "get_state_key"):
28
29
  raise RuntimeError(
29
30
  "WaiterMiddleware cannot be used inside a view which doesn't "
30
- "provide get_state_key (ABCStateView Protocol)."
31
+ "provide get_state_key (ABCStateView interface)."
31
32
  )
32
33
 
33
34
  view_name = self.view.__class__.__name__
@@ -41,13 +42,36 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
41
42
  short_state: "ShortState[EventType] | None" = self.machine.storage[view_name].get(key)
42
43
  if not short_state:
43
44
  return True
44
-
45
+
45
46
  preset_context = Context(short_state=short_state)
46
- if (
47
- short_state.expiration_date is not None
48
- and datetime.datetime.now() >= short_state.expiration_date
47
+ if short_state.context is not None:
48
+ preset_context.update(short_state.context.context)
49
+
50
+ if short_state.expiration_date is not None and datetime.datetime.now() >= short_state.expiration_date:
51
+ await self.machine.drop(
52
+ self.view,
53
+ short_state.key,
54
+ event,
55
+ ctx.raw_update,
56
+ **preset_context.copy(),
57
+ )
58
+ return True
59
+
60
+ # before running the handler we check if the user wants to exit waiting
61
+ if short_state.exit_behaviour is not None and await self.machine.call_behaviour(
62
+ self.view,
63
+ event,
64
+ ctx.raw_update,
65
+ behaviour=short_state.exit_behaviour,
66
+ **preset_context,
49
67
  ):
50
- await self.machine.drop(self.view, short_state.key, ctx.raw_update, **preset_context.copy())
68
+ await self.machine.drop(
69
+ self.view,
70
+ short_state.key,
71
+ event,
72
+ ctx.raw_update,
73
+ **preset_context.copy(),
74
+ )
51
75
  return True
52
76
 
53
77
  handler = FuncHandler(
@@ -78,7 +102,7 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
78
102
  short_state: "ShortState[EventType]",
79
103
  ctx: Context,
80
104
  ) -> None:
81
- setattr(short_state.event, "context", (event, ctx))
105
+ short_state.context = ShortStateContext(event, ctx)
82
106
  short_state.event.set()
83
107
 
84
108
 
@@ -5,6 +5,7 @@ import typing
5
5
 
6
6
  from telegrinder.api import ABCAPI
7
7
  from telegrinder.bot.cute_types import BaseCute
8
+ from telegrinder.bot.dispatch.context import Context
8
9
  from telegrinder.bot.dispatch.handler.abc import ABCHandler
9
10
  from telegrinder.bot.rules.abc import ABCRule
10
11
  from telegrinder.model import Model
@@ -18,23 +19,31 @@ EventModel = typing.TypeVar("EventModel", bound=BaseCute)
18
19
  Behaviour: typing.TypeAlias = ABCHandler[T] | None
19
20
 
20
21
 
22
+ class ShortStateContext(typing.Generic[EventModel], typing.NamedTuple):
23
+ event: EventModel
24
+ context: Context
25
+
26
+
21
27
  @dataclasses.dataclass
22
28
  class ShortState(typing.Generic[EventModel]):
23
29
  key: "Identificator"
24
30
  ctx_api: ABCAPI
25
31
  event: asyncio.Event
26
- rules: tuple[ABCRule[EventModel], ...]
27
- _: dataclasses.KW_ONLY
32
+ rules: tuple[ABCRule, ...]
28
33
  expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
29
34
  default=None,
35
+ kw_only=True,
30
36
  )
31
- default_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
32
- on_drop_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
33
- expiration_date: datetime.datetime | None = dataclasses.field(init=False)
37
+ default_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None, kw_only=True)
38
+ on_drop_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None, kw_only=True)
39
+ exit_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None, kw_only=True)
40
+ expiration_date: datetime.datetime | None = dataclasses.field(init=False, kw_only=True)
41
+ context: ShortStateContext[EventModel] | None = dataclasses.field(default=None, init=False, kw_only=True)
34
42
 
35
43
  def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
36
- self.expiration_date = (datetime.datetime.now() - expiration) if expiration is not None else None
37
-
44
+ self.creation_date = datetime.datetime.now()
45
+ self.expiration_date = (self.creation_date + expiration) if expiration is not None else None
46
+
38
47
  def cancel(self) -> None:
39
48
  """Cancel schedule waiters."""
40
49
 
@@ -46,4 +55,4 @@ class ShortState(typing.Generic[EventModel]):
46
55
  future.cancel()
47
56
 
48
57
 
49
- __all__ = ("ShortState",)
58
+ __all__ = ("ShortState", "ShortStateContext")
@@ -97,7 +97,7 @@ class Polling(ABCPolling):
97
97
  except InvalidTokenError as e:
98
98
  logger.error(e)
99
99
  self.stop()
100
- exit(6)
100
+ exit(3)
101
101
  except asyncio.CancelledError:
102
102
  logger.info("Caught cancel, polling stopping...")
103
103
  self.stop()
@@ -108,14 +108,14 @@ class Polling(ABCPolling):
108
108
  self.max_reconnetions,
109
109
  )
110
110
  self.stop()
111
- exit(9)
111
+ exit(6)
112
112
  else:
113
113
  logger.warning(
114
- "Server disconnected, waiting 5 seconds to reconnetion...",
114
+ "Server disconnected, waiting 5 seconds to reconnet...",
115
115
  )
116
116
  reconn_counter += 1
117
117
  await asyncio.sleep(self.reconnection_timeout)
118
- except aiohttp.ClientConnectorError:
118
+ except (aiohttp.ClientConnectorError, aiohttp.ClientOSError):
119
119
  logger.error("Client connection failed, attempted to reconnect...")
120
120
  await asyncio.sleep(self.reconnection_timeout)
121
121
  except BaseException as e:
@@ -26,11 +26,12 @@ from .inline import (
26
26
  InlineQueryRule,
27
27
  InlineQueryText,
28
28
  )
29
- from .integer import Integer, IntegerInRange
29
+ from .integer import IntegerInRange, IsInteger
30
30
  from .is_from import (
31
31
  IsBot,
32
32
  IsChat,
33
33
  IsChatId,
34
+ IsDice,
34
35
  IsDiceEmoji,
35
36
  IsForum,
36
37
  IsForward,
@@ -48,11 +49,12 @@ from .markup import Markup
48
49
  from .mention import HasMention
49
50
  from .message import MessageRule
50
51
  from .message_entities import HasEntities, MessageEntities
52
+ from .node import NodeRule
51
53
  from .regex import Regex
52
54
  from .rule_enum import RuleEnum
53
55
  from .start import StartCommand
54
- from .text import HasText, Text, TextMessageRule
55
- from .update import IsUpdate
56
+ from .text import HasText, Text
57
+ from .update import IsUpdateType
56
58
 
57
59
  __all__ = (
58
60
  "ABCRule",
@@ -80,13 +82,14 @@ __all__ = (
80
82
  "InlineQueryMarkup",
81
83
  "InlineQueryRule",
82
84
  "InlineQueryText",
83
- "Integer",
85
+ "IsInteger",
84
86
  "IntegerInRange",
85
87
  "InviteLinkByCreator",
86
88
  "InviteLinkName",
87
89
  "IsBot",
88
90
  "IsChat",
89
91
  "IsChatId",
92
+ "IsDice",
90
93
  "IsDiceEmoji",
91
94
  "IsForum",
92
95
  "IsForward",
@@ -97,7 +100,7 @@ __all__ = (
97
100
  "IsPrivate",
98
101
  "IsReply",
99
102
  "IsSuperGroup",
100
- "IsUpdate",
103
+ "IsUpdateType",
101
104
  "IsUser",
102
105
  "IsUserId",
103
106
  "Markup",
@@ -109,5 +112,5 @@ __all__ = (
109
112
  "RuleEnum",
110
113
  "StartCommand",
111
114
  "Text",
112
- "TextMessageRule",
115
+ "NodeRule",
113
116
  )
@@ -1,25 +1,29 @@
1
1
  import inspect
2
- import typing
3
2
  from abc import ABC, abstractmethod
4
3
 
5
- from telegrinder.bot.cute_types import BaseCute, MessageCute, UpdateCute
4
+ import typing_extensions as typing
5
+
6
+ from telegrinder.bot.cute_types import MessageCute, UpdateCute
6
7
  from telegrinder.bot.dispatch.context import Context
7
8
  from telegrinder.bot.dispatch.process import check_rule
8
- from telegrinder.bot.rules.adapter import ABCAdapter, EventAdapter, RawUpdateAdapter
9
+ from telegrinder.bot.rules.adapter import ABCAdapter, RawUpdateAdapter
10
+ from telegrinder.bot.rules.adapter.node import Event
11
+ from telegrinder.node.base import Node, is_node
12
+ from telegrinder.node.composer import NodeCollection
9
13
  from telegrinder.tools.i18n.base import ABCTranslator
10
- from telegrinder.tools.magic import cache_translation, get_cached_translation
14
+ from telegrinder.tools.magic import cache_translation, get_annotations, get_cached_translation
11
15
  from telegrinder.types.objects import Update as UpdateObject
12
16
 
13
- T = typing.TypeVar("T", bound=BaseCute)
17
+ AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any)
14
18
 
15
19
  Message: typing.TypeAlias = MessageCute
16
20
  Update: typing.TypeAlias = UpdateCute
17
21
 
18
22
 
19
- def with_caching_translations(func):
23
+ def with_caching_translations(func: typing.Callable[..., typing.Any]):
20
24
  """Should be used as decorator for .translate method. Caches rule translations."""
21
25
 
22
- async def wrapper(self: "ABCRule[typing.Any]", translator: ABCTranslator):
26
+ async def wrapper(self: "ABCRule", translator: ABCTranslator):
23
27
  if translation := get_cached_translation(self, translator.locale):
24
28
  return translation
25
29
  translation = await func(self, translator)
@@ -29,15 +33,52 @@ def with_caching_translations(func):
29
33
  return wrapper
30
34
 
31
35
 
32
- class ABCRule(ABC, typing.Generic[T]):
33
- adapter: ABCAdapter[UpdateObject, T] = RawUpdateAdapter() # type: ignore
34
- requires: list["ABCRule[T]"] = []
36
+ class ABCRule(ABC, typing.Generic[AdaptTo]):
37
+ adapter: ABCAdapter[UpdateObject, AdaptTo] = RawUpdateAdapter() # type: ignore
38
+ requires: list["ABCRule"] = []
35
39
 
36
40
  @abstractmethod
37
- async def check(self, event: T, ctx: Context) -> bool:
41
+ async def check(self, event: AdaptTo, *, ctx: Context) -> bool:
38
42
  pass
39
43
 
40
- def __init_subclass__(cls, requires: list["ABCRule[T]"] | None = None):
44
+ def get_required_nodes(self) -> dict[str, type[Node]]:
45
+ return {k: v for k, v in get_annotations(self.check).items() if is_node(v)}
46
+
47
+ async def bounding_check(
48
+ self,
49
+ adapted_value: AdaptTo,
50
+ ctx: Context,
51
+ node_col: NodeCollection | None = None,
52
+ ) -> bool:
53
+ kw = {}
54
+ node_col_values = node_col.values() if node_col is not None else {}
55
+
56
+ for i, (k, v) in enumerate(get_annotations(self.check).items()):
57
+ if (isinstance(adapted_value, Event) and not i) or (
58
+ isinstance(v, type) and isinstance(adapted_value, v)
59
+ ):
60
+ kw[k] = adapted_value if not isinstance(adapted_value, Event) else adapted_value.obj
61
+ elif is_node(v):
62
+ assert k in node_col_values, "Node is undefined, error while bounding."
63
+ kw[k] = node_col_values[k]
64
+ elif k in ctx:
65
+ kw[k] = ctx[k]
66
+ elif v is Context:
67
+ kw[k] = ctx
68
+ else:
69
+ raise LookupError(
70
+ f"Cannot bound {k!r} of type {v!r} to '{self.__class__.__name__}.check()', because it cannot be resolved."
71
+ )
72
+
73
+ return await self.check(**kw)
74
+
75
+ def optional(self) -> "ABCRule":
76
+ return self | Always()
77
+
78
+ def should_fail(self) -> "ABCRule":
79
+ return self & Never()
80
+
81
+ def __init_subclass__(cls, requires: list["ABCRule"] | None = None) -> None:
41
82
  """Merges requirements from inherited classes and rule-specific requirements."""
42
83
 
43
84
  requirements = []
@@ -48,17 +89,41 @@ class ABCRule(ABC, typing.Generic[T]):
48
89
  requirements.extend(requires or ())
49
90
  cls.requires = list(dict.fromkeys(requirements))
50
91
 
51
- def __and__(self, other: "ABCRule[T]"):
92
+ def __and__(self, other: "ABCRule") -> "AndRule":
93
+ """And Rule.
94
+
95
+ ```python
96
+ rule = HasText() & HasCaption()
97
+ rule #> AndRule(HasText(), HasCaption()) -> True if all rules in an AndRule are True, otherwise False.
98
+ ```
99
+ """
100
+
52
101
  return AndRule(self, other)
53
102
 
54
- def __or__(self, other: "ABCRule[T]"):
103
+ def __or__(self, other: "ABCRule") -> "OrRule":
104
+ """Or Rule.
105
+
106
+ ```python
107
+ rule = HasText() | HasCaption()
108
+ rule #> OrRule(HasText(), HasCaption()) -> True if any rule in an OrRule are True, otherwise False.
109
+ ```
110
+ """
111
+
55
112
  return OrRule(self, other)
56
113
 
57
- def __neg__(self) -> "ABCRule[T]":
114
+ def __invert__(self) -> "NotRule":
115
+ """Not Rule.
116
+
117
+ ```python
118
+ rule = ~HasText()
119
+ rule # NotRule(HasText()) -> True if rule returned False, otherwise False.
120
+ ```
121
+ """
122
+
58
123
  return NotRule(self)
59
124
 
60
125
  def __repr__(self) -> str:
61
- return "<rule: {!r}, adapter: {!r}>".format(
126
+ return "<{}: adapter={!r}>".format(
62
127
  self.__class__.__name__,
63
128
  self.adapter,
64
129
  )
@@ -67,8 +132,8 @@ class ABCRule(ABC, typing.Generic[T]):
67
132
  return self
68
133
 
69
134
 
70
- class AndRule(ABCRule[T]):
71
- def __init__(self, *rules: ABCRule[T]):
135
+ class AndRule(ABCRule):
136
+ def __init__(self, *rules: ABCRule[AdaptTo]) -> None:
72
137
  self.rules = rules
73
138
 
74
139
  async def check(self, event: Update, ctx: Context) -> bool:
@@ -80,8 +145,8 @@ class AndRule(ABCRule[T]):
80
145
  return True
81
146
 
82
147
 
83
- class OrRule(ABCRule[T]):
84
- def __init__(self, *rules: ABCRule[T]):
148
+ class OrRule(ABCRule):
149
+ def __init__(self, *rules: ABCRule) -> None:
85
150
  self.rules = rules
86
151
 
87
152
  async def check(self, event: Update, ctx: Context) -> bool:
@@ -93,8 +158,8 @@ class OrRule(ABCRule[T]):
93
158
  return False
94
159
 
95
160
 
96
- class NotRule(ABCRule[T]):
97
- def __init__(self, rule: ABCRule[T]):
161
+ class NotRule(ABCRule):
162
+ def __init__(self, rule: ABCRule) -> None:
98
163
  self.rule = rule
99
164
 
100
165
  async def check(self, event: Update, ctx: Context) -> bool:
@@ -102,10 +167,22 @@ class NotRule(ABCRule[T]):
102
167
  return not await check_rule(event.ctx_api, self.rule, event, ctx_copy)
103
168
 
104
169
 
170
+ class Never(ABCRule):
171
+ async def check(self) -> typing.Literal[False]:
172
+ return False
173
+
174
+
175
+ class Always(ABCRule):
176
+ async def check(self) -> typing.Literal[True]:
177
+ return True
178
+
179
+
105
180
  __all__ = (
106
181
  "ABCRule",
107
182
  "AndRule",
108
183
  "NotRule",
109
184
  "OrRule",
110
185
  "with_caching_translations",
186
+ "Never",
187
+ "Always",
111
188
  )
@@ -1,11 +1,14 @@
1
- from .abc import ABCAdapter
1
+ from .abc import ABCAdapter, Event
2
2
  from .errors import AdapterError
3
3
  from .event import EventAdapter
4
+ from .node import NodeAdapter
4
5
  from .raw_update import RawUpdateAdapter
5
6
 
6
7
  __all__ = (
7
8
  "ABCAdapter",
8
9
  "AdapterError",
9
10
  "EventAdapter",
11
+ "NodeAdapter",
10
12
  "RawUpdateAdapter",
13
+ "Event",
11
14
  )
@@ -1,21 +1,26 @@
1
1
  import abc
2
+ import dataclasses
2
3
  import typing
3
4
 
4
5
  from fntypes.result import Result
5
6
 
6
7
  from telegrinder.api.abc import ABCAPI
7
- from telegrinder.bot.cute_types import BaseCute
8
8
  from telegrinder.bot.rules.adapter.errors import AdapterError
9
9
  from telegrinder.model import Model
10
10
 
11
- UpdateT = typing.TypeVar("UpdateT", bound=Model)
12
- CuteT = typing.TypeVar("CuteT", bound=BaseCute)
11
+ From = typing.TypeVar("From", bound=Model)
12
+ To = typing.TypeVar("To")
13
13
 
14
14
 
15
- class ABCAdapter(abc.ABC, typing.Generic[UpdateT, CuteT]):
15
+ class ABCAdapter(abc.ABC, typing.Generic[From, To]):
16
16
  @abc.abstractmethod
17
- async def adapt(self, api: ABCAPI, update: UpdateT) -> Result[CuteT, AdapterError]:
17
+ async def adapt(self, api: ABCAPI, update: From) -> Result[To, AdapterError]:
18
18
  pass
19
19
 
20
20
 
21
- __all__ = ("ABCAdapter",)
21
+ @dataclasses.dataclass
22
+ class Event(typing.Generic[To]):
23
+ obj: To
24
+
25
+
26
+ __all__ = ("ABCAdapter", "Event")
@@ -1,5 +1,4 @@
1
- class AdapterError(RuntimeError):
2
- pass
1
+ class AdapterError(RuntimeError): ...
3
2
 
4
3
 
5
4
  __all__ = ("AdapterError",)
@@ -7,13 +7,14 @@ from telegrinder.bot.cute_types import BaseCute
7
7
  from telegrinder.bot.rules.adapter.abc import ABCAdapter
8
8
  from telegrinder.bot.rules.adapter.errors import AdapterError
9
9
  from telegrinder.msgspec_utils import Nothing
10
+ from telegrinder.types.enums import UpdateType
10
11
  from telegrinder.types.objects import Model, Update
11
12
 
12
- CuteT = typing.TypeVar("CuteT", bound=BaseCute)
13
+ ToCute = typing.TypeVar("ToCute", bound=BaseCute)
13
14
 
14
15
 
15
- class EventAdapter(ABCAdapter[Update, CuteT]):
16
- def __init__(self, event: str | type[Model], cute_model: type[CuteT]) -> None:
16
+ class EventAdapter(ABCAdapter[Update, ToCute]):
17
+ def __init__(self, event: UpdateType | type[Model], cute_model: type[ToCute]) -> None:
17
18
  self.event = event
18
19
  self.cute_model = cute_model
19
20
 
@@ -27,27 +28,31 @@ class EventAdapter(ABCAdapter[Update, CuteT]):
27
28
  )
28
29
  else:
29
30
  raw_update_type = self.event.__name__
31
+
30
32
  return "<{}: adapt {} -> {}>".format(
31
33
  self.__class__.__name__,
32
34
  raw_update_type,
33
35
  self.cute_model.__name__,
34
36
  )
35
37
 
36
- async def adapt(self, api: ABCAPI, update: Update) -> Result[CuteT, AdapterError]:
38
+ async def adapt(self, api: ABCAPI, update: Update) -> Result[ToCute, AdapterError]:
37
39
  update_dct = update.to_dict()
38
- if isinstance(self.event, str):
39
- if self.event not in update_dct:
40
+ if isinstance(self.event, UpdateType):
41
+ if update.update_type != self.event:
40
42
  return Error(
41
43
  AdapterError(f"Update is not of event type {self.event!r}."),
42
44
  )
43
- if update_dct[self.event] is Nothing:
45
+
46
+ if update_dct[self.event.value] is Nothing:
44
47
  return Error(
45
48
  AdapterError(f"Update is not an {self.event!r}."),
46
49
  )
50
+
47
51
  return Ok(
48
- self.cute_model.from_update(update_dct[self.event].unwrap(), bound_api=api),
52
+ self.cute_model.from_update(update_dct[self.event.value].unwrap(), bound_api=api),
49
53
  )
50
- event = update_dct[update.update_type.unwrap()].unwrap()
54
+
55
+ event = update_dct[update.update_type.value].unwrap()
51
56
  if not update.update_type or not issubclass(event.__class__, self.event):
52
57
  return Error(AdapterError(f"Update is not an {self.event.__name__!r}."))
53
58
  return Ok(self.cute_model.from_update(event, bound_api=api))
@@ -0,0 +1,42 @@
1
+ import typing
2
+
3
+ from fntypes.result import Error, Ok, Result
4
+
5
+ from telegrinder.api.abc import ABCAPI
6
+ from telegrinder.bot.cute_types.update import UpdateCute
7
+ from telegrinder.bot.dispatch.context import Context
8
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
9
+ from telegrinder.bot.rules.adapter.errors import AdapterError
10
+ from telegrinder.node.base import ComposeError
11
+ from telegrinder.node.composer import NodeSession, compose_node
12
+ from telegrinder.types.objects import Update
13
+
14
+ Ts = typing.TypeVarTuple("Ts")
15
+
16
+
17
+ class NodeAdapter(typing.Generic[*Ts], ABCAdapter[Update, Event[tuple[*Ts]]]):
18
+ def __init__(self, *nodes: *Ts) -> None:
19
+ self.nodes = nodes
20
+
21
+ def __repr__(self) -> str:
22
+ return "<{}: adapt Update -> {}>".format(
23
+ self.__class__.__name__,
24
+ Update.__name__,
25
+ ", ".join(node.__name__ for node in self.nodes), # type: ignore
26
+ )
27
+
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())) # 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
+
41
+
42
+ __all__ = ("NodeAdapter",)
@@ -10,6 +10,7 @@ from telegrinder.bot.dispatch.context import Context
10
10
  from telegrinder.bot.rules.adapter import EventAdapter
11
11
  from telegrinder.model import decoder
12
12
  from telegrinder.tools.buttons import DataclassInstance
13
+ from telegrinder.types.enums import UpdateType
13
14
 
14
15
  from .abc import ABCRule
15
16
  from .markup import Markup, PatternLike, check_string
@@ -22,7 +23,7 @@ CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapSt
22
23
 
23
24
 
24
25
  class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
25
- adapter = EventAdapter("callback_query", CallbackQuery)
26
+ adapter: EventAdapter[CallbackQuery] = EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery)
26
27
 
27
28
  @abc.abstractmethod
28
29
  async def check(self, event: CallbackQuery, ctx: Context) -> bool:
@@ -31,7 +32,7 @@ class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
31
32
 
32
33
  class HasData(CallbackQueryRule):
33
34
  async def check(self, event: CallbackQuery, ctx: Context) -> bool:
34
- return bool(event.data or event.data.unwrap())
35
+ return bool(event.data.unwrap_or_none())
35
36
 
36
37
 
37
38
  class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
@@ -98,8 +99,7 @@ class CallbackDataMap(CallbackQueryDataRule):
98
99
 
99
100
  if isinstance(validator, list):
100
101
  if not (
101
- isinstance(callback_data[key], dict)
102
- and await cls.match(callback_data[key], validator)
102
+ isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)
103
103
  ):
104
104
  return False
105
105
 
@@ -4,14 +4,15 @@ import typing
4
4
  from telegrinder.bot.cute_types import ChatJoinRequestCute
5
5
  from telegrinder.bot.dispatch.context import Context
6
6
  from telegrinder.bot.rules.adapter import EventAdapter
7
+ from telegrinder.types.enums import UpdateType
7
8
 
8
9
  from .abc import ABCRule
9
10
 
10
11
  ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
11
12
 
12
13
 
13
- class ChatJoinRequestRule(ABCRule[ChatJoinRequestCute], requires=[]):
14
- adapter = EventAdapter("chat_join_request", ChatJoinRequest)
14
+ class ChatJoinRequestRule(ABCRule[ChatJoinRequest], requires=[]):
15
+ adapter: EventAdapter[ChatJoinRequest] = EventAdapter(UpdateType.CHAT_JOIN_REQUEST, ChatJoinRequest)
15
16
 
16
17
  @abc.abstractmethod
17
18
  async def check(self, event: ChatJoinRequest, ctx: Context) -> bool: