telegrinder 0.1.dev170__py3-none-any.whl → 0.2.0__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 (116) hide show
  1. telegrinder/__init__.py +2 -2
  2. telegrinder/api/__init__.py +1 -2
  3. telegrinder/api/api.py +15 -6
  4. telegrinder/api/error.py +2 -1
  5. telegrinder/api/token.py +36 -0
  6. telegrinder/bot/__init__.py +12 -6
  7. telegrinder/bot/bot.py +18 -6
  8. telegrinder/bot/cute_types/__init__.py +7 -7
  9. telegrinder/bot/cute_types/base.py +122 -20
  10. telegrinder/bot/cute_types/callback_query.py +10 -6
  11. telegrinder/bot/cute_types/chat_join_request.py +4 -5
  12. telegrinder/bot/cute_types/chat_member_updated.py +4 -6
  13. telegrinder/bot/cute_types/inline_query.py +3 -4
  14. telegrinder/bot/cute_types/message.py +32 -21
  15. telegrinder/bot/cute_types/update.py +51 -4
  16. telegrinder/bot/cute_types/utils.py +3 -466
  17. telegrinder/bot/dispatch/__init__.py +10 -11
  18. telegrinder/bot/dispatch/abc.py +8 -5
  19. telegrinder/bot/dispatch/context.py +17 -8
  20. telegrinder/bot/dispatch/dispatch.py +71 -48
  21. telegrinder/bot/dispatch/handler/__init__.py +3 -3
  22. telegrinder/bot/dispatch/handler/abc.py +4 -4
  23. telegrinder/bot/dispatch/handler/func.py +46 -22
  24. telegrinder/bot/dispatch/handler/message_reply.py +6 -7
  25. telegrinder/bot/dispatch/middleware/__init__.py +1 -1
  26. telegrinder/bot/dispatch/middleware/abc.py +2 -2
  27. telegrinder/bot/dispatch/process.py +38 -19
  28. telegrinder/bot/dispatch/return_manager/__init__.py +4 -4
  29. telegrinder/bot/dispatch/return_manager/abc.py +3 -3
  30. telegrinder/bot/dispatch/return_manager/callback_query.py +1 -2
  31. telegrinder/bot/dispatch/return_manager/inline_query.py +1 -2
  32. telegrinder/bot/dispatch/return_manager/message.py +1 -2
  33. telegrinder/bot/dispatch/view/__init__.py +8 -8
  34. telegrinder/bot/dispatch/view/abc.py +18 -16
  35. telegrinder/bot/dispatch/view/box.py +75 -64
  36. telegrinder/bot/dispatch/view/callback_query.py +1 -2
  37. telegrinder/bot/dispatch/view/chat_join_request.py +1 -2
  38. telegrinder/bot/dispatch/view/chat_member.py +16 -2
  39. telegrinder/bot/dispatch/view/inline_query.py +1 -2
  40. telegrinder/bot/dispatch/view/message.py +12 -5
  41. telegrinder/bot/dispatch/view/raw.py +9 -8
  42. telegrinder/bot/dispatch/waiter_machine/__init__.py +3 -3
  43. telegrinder/bot/dispatch/waiter_machine/machine.py +12 -8
  44. telegrinder/bot/dispatch/waiter_machine/middleware.py +1 -1
  45. telegrinder/bot/dispatch/waiter_machine/short_state.py +4 -3
  46. telegrinder/bot/polling/abc.py +1 -1
  47. telegrinder/bot/polling/polling.py +6 -6
  48. telegrinder/bot/rules/__init__.py +20 -20
  49. telegrinder/bot/rules/abc.py +57 -43
  50. telegrinder/bot/rules/adapter/__init__.py +5 -5
  51. telegrinder/bot/rules/adapter/abc.py +6 -3
  52. telegrinder/bot/rules/adapter/errors.py +2 -1
  53. telegrinder/bot/rules/adapter/event.py +28 -13
  54. telegrinder/bot/rules/adapter/node.py +28 -22
  55. telegrinder/bot/rules/adapter/raw_update.py +13 -5
  56. telegrinder/bot/rules/callback_data.py +4 -4
  57. telegrinder/bot/rules/chat_join.py +4 -4
  58. telegrinder/bot/rules/command.py +5 -7
  59. telegrinder/bot/rules/func.py +2 -2
  60. telegrinder/bot/rules/fuzzy.py +1 -1
  61. telegrinder/bot/rules/inline.py +3 -3
  62. telegrinder/bot/rules/integer.py +1 -2
  63. telegrinder/bot/rules/markup.py +5 -3
  64. telegrinder/bot/rules/message_entities.py +2 -2
  65. telegrinder/bot/rules/node.py +2 -2
  66. telegrinder/bot/rules/regex.py +1 -1
  67. telegrinder/bot/rules/rule_enum.py +1 -1
  68. telegrinder/bot/rules/text.py +1 -2
  69. telegrinder/bot/rules/update.py +1 -2
  70. telegrinder/bot/scenario/abc.py +2 -2
  71. telegrinder/bot/scenario/checkbox.py +3 -4
  72. telegrinder/bot/scenario/choice.py +1 -2
  73. telegrinder/model.py +89 -45
  74. telegrinder/modules.py +3 -3
  75. telegrinder/msgspec_utils.py +85 -57
  76. telegrinder/node/__init__.py +17 -10
  77. telegrinder/node/attachment.py +19 -16
  78. telegrinder/node/base.py +46 -22
  79. telegrinder/node/callback_query.py +5 -9
  80. telegrinder/node/command.py +6 -2
  81. telegrinder/node/composer.py +102 -77
  82. telegrinder/node/container.py +3 -3
  83. telegrinder/node/event.py +68 -0
  84. telegrinder/node/me.py +3 -0
  85. telegrinder/node/message.py +6 -10
  86. telegrinder/node/polymorphic.py +15 -10
  87. telegrinder/node/rule.py +20 -6
  88. telegrinder/node/scope.py +9 -1
  89. telegrinder/node/source.py +21 -11
  90. telegrinder/node/text.py +4 -4
  91. telegrinder/node/update.py +7 -4
  92. telegrinder/py.typed +0 -0
  93. telegrinder/rules.py +59 -0
  94. telegrinder/tools/__init__.py +2 -2
  95. telegrinder/tools/buttons.py +5 -10
  96. telegrinder/tools/error_handler/abc.py +2 -2
  97. telegrinder/tools/error_handler/error.py +2 -0
  98. telegrinder/tools/error_handler/error_handler.py +6 -6
  99. telegrinder/tools/formatting/spec_html_formats.py +10 -10
  100. telegrinder/tools/global_context/__init__.py +2 -2
  101. telegrinder/tools/global_context/global_context.py +3 -3
  102. telegrinder/tools/global_context/telegrinder_ctx.py +4 -4
  103. telegrinder/tools/keyboard.py +3 -3
  104. telegrinder/tools/loop_wrapper/loop_wrapper.py +47 -13
  105. telegrinder/tools/magic.py +96 -18
  106. telegrinder/types/__init__.py +1 -0
  107. telegrinder/types/enums.py +2 -0
  108. telegrinder/types/methods.py +91 -15
  109. telegrinder/types/objects.py +49 -24
  110. telegrinder/verification_utils.py +1 -3
  111. {telegrinder-0.1.dev170.dist-info → telegrinder-0.2.0.dist-info}/METADATA +2 -2
  112. telegrinder-0.2.0.dist-info/RECORD +145 -0
  113. telegrinder/api/abc.py +0 -73
  114. telegrinder-0.1.dev170.dist-info/RECORD +0 -143
  115. {telegrinder-0.1.dev170.dist-info → telegrinder-0.2.0.dist-info}/LICENSE +0 -0
  116. {telegrinder-0.1.dev170.dist-info → telegrinder-0.2.0.dist-info}/WHEEL +0 -0
telegrinder/rules.py CHANGED
@@ -1 +1,60 @@
1
1
  from .bot.rules import * # noqa: F403
2
+
3
+ __all__ = (
4
+ "ABCRule",
5
+ "AndRule",
6
+ "Argument",
7
+ "CallbackDataEq",
8
+ "CallbackDataJsonEq",
9
+ "CallbackDataJsonModel",
10
+ "CallbackDataMap",
11
+ "CallbackDataMarkup",
12
+ "CallbackQueryDataRule",
13
+ "CallbackQueryRule",
14
+ "ChatJoinRequestRule",
15
+ "Command",
16
+ "EnumTextRule",
17
+ "FuncRule",
18
+ "FuzzyText",
19
+ "HasData",
20
+ "HasEntities",
21
+ "HasInviteLink",
22
+ "HasLocation",
23
+ "HasMention",
24
+ "HasText",
25
+ "InlineQueryChatType",
26
+ "InlineQueryMarkup",
27
+ "InlineQueryRule",
28
+ "InlineQueryText",
29
+ "IsInteger",
30
+ "IntegerInRange",
31
+ "InviteLinkByCreator",
32
+ "InviteLinkName",
33
+ "IsBot",
34
+ "IsChat",
35
+ "IsChatId",
36
+ "IsDice",
37
+ "IsDiceEmoji",
38
+ "IsForum",
39
+ "IsForward",
40
+ "IsForwardType",
41
+ "IsGroup",
42
+ "IsLanguageCode",
43
+ "IsPremium",
44
+ "IsPrivate",
45
+ "IsReply",
46
+ "IsSuperGroup",
47
+ "IsUpdateType",
48
+ "IsUser",
49
+ "IsUserId",
50
+ "Markup",
51
+ "MessageEntities",
52
+ "MessageRule",
53
+ "NotRule",
54
+ "OrRule",
55
+ "Regex",
56
+ "RuleEnum",
57
+ "StartCommand",
58
+ "Text",
59
+ "NodeRule",
60
+ )
@@ -43,7 +43,7 @@ from .global_context import (
43
43
  CtxVar,
44
44
  GlobalContext,
45
45
  GlobalCtxVar,
46
- TelegrinderCtx,
46
+ TelegrinderContext,
47
47
  ctx_var,
48
48
  )
49
49
  from .i18n import (
@@ -110,7 +110,7 @@ __all__ = (
110
110
  "SpecialFormat",
111
111
  "StartBotLink",
112
112
  "StartGroupLink",
113
- "TelegrinderCtx",
113
+ "TelegrinderContext",
114
114
  "TgEmoji",
115
115
  "escape",
116
116
  "get_channel_boost_link",
@@ -3,25 +3,20 @@ import typing
3
3
 
4
4
  import msgspec
5
5
 
6
- from telegrinder.model import encoder
7
- from telegrinder.types import (
6
+ from telegrinder.msgspec_utils import DataclassInstance, encoder
7
+ from telegrinder.types.objects import (
8
8
  CallbackGame,
9
9
  KeyboardButtonPollType,
10
10
  KeyboardButtonRequestChat,
11
11
  KeyboardButtonRequestUsers,
12
+ LoginUrl,
12
13
  SwitchInlineQueryChosenChat,
13
14
  WebAppInfo,
14
15
  )
15
- from telegrinder.types.objects import LoginUrl
16
16
 
17
17
  ButtonT = typing.TypeVar("ButtonT", bound="BaseButton")
18
18
 
19
19
 
20
- @typing.runtime_checkable
21
- class DataclassInstance(typing.Protocol):
22
- __dataclass_fields__: typing.ClassVar[dict[str, dataclasses.Field[typing.Any]]]
23
-
24
-
25
20
  @dataclasses.dataclass
26
21
  class BaseButton:
27
22
  def get_data(self) -> dict[str, typing.Any]:
@@ -44,7 +39,7 @@ class RowButtons(typing.Generic[ButtonT]):
44
39
  return [b.get_data() for b in self.buttons]
45
40
 
46
41
 
47
- @dataclasses.dataclass
42
+ @dataclasses.dataclass(slots=True)
48
43
  class Button(BaseButton):
49
44
  text: str
50
45
  request_contact: bool = dataclasses.field(default=False, kw_only=True)
@@ -61,7 +56,7 @@ class Button(BaseButton):
61
56
  web_app: dict[str, typing.Any] | WebAppInfo | None = dataclasses.field(default=None, kw_only=True)
62
57
 
63
58
 
64
- @dataclasses.dataclass
59
+ @dataclasses.dataclass(slots=True)
65
60
  class InlineButton(BaseButton):
66
61
  text: str
67
62
  url: str | None = dataclasses.field(default=None, kw_only=True)
@@ -3,7 +3,7 @@ from abc import ABC, abstractmethod
3
3
 
4
4
  from fntypes.result import Result
5
5
 
6
- from telegrinder.api import ABCAPI
6
+ from telegrinder.api import API
7
7
  from telegrinder.bot.dispatch.context import Context
8
8
 
9
9
  EventT = typing.TypeVar("EventT")
@@ -24,7 +24,7 @@ class ABCErrorHandler(ABC, typing.Generic[EventT]):
24
24
  self,
25
25
  handler: Handler[EventT],
26
26
  event: EventT,
27
- api: ABCAPI,
27
+ api: API,
28
28
  ctx: Context,
29
29
  ) -> Result[typing.Any, typing.Any]:
30
30
  """Run error handler."""
@@ -1,4 +1,6 @@
1
1
  class CatcherError(BaseException):
2
+ __slots__ = ("exc", "message")
3
+
2
4
  def __init__(self, exc: BaseException, message: str) -> None:
3
5
  self.exc = exc
4
6
  self.message = message
@@ -3,7 +3,7 @@ import typing
3
3
 
4
4
  from fntypes.result import Error, Ok, Result
5
5
 
6
- from telegrinder.api import ABCAPI
6
+ from telegrinder.api import API
7
7
  from telegrinder.bot.dispatch.context import Context
8
8
  from telegrinder.modules import logger
9
9
  from telegrinder.tools.magic import magic_bundle
@@ -16,7 +16,7 @@ ExceptionT = typing.TypeVar("ExceptionT", bound=BaseException, contravariant=Tru
16
16
  FuncCatcher = typing.Callable[typing.Concatenate[ExceptionT, ...], typing.Awaitable[typing.Any]]
17
17
 
18
18
 
19
- @dataclasses.dataclass(frozen=True, repr=False)
19
+ @dataclasses.dataclass(frozen=True, repr=False, slots=True)
20
20
  class Catcher(typing.Generic[EventT]):
21
21
  func: FuncCatcher[BaseException]
22
22
  exceptions: list[type[BaseException] | BaseException] = dataclasses.field(
@@ -39,7 +39,7 @@ class Catcher(typing.Generic[EventT]):
39
39
  self,
40
40
  handler: Handler[EventT],
41
41
  event: EventT,
42
- api: ABCAPI,
42
+ api: API,
43
43
  ctx: Context,
44
44
  ) -> Result[typing.Any, BaseException]:
45
45
  try:
@@ -49,7 +49,7 @@ class Catcher(typing.Generic[EventT]):
49
49
 
50
50
  async def process_exception(
51
51
  self,
52
- api: ABCAPI,
52
+ api: API,
53
53
  event: EventT,
54
54
  ctx: Context,
55
55
  exception: BaseException,
@@ -125,7 +125,7 @@ class ErrorHandler(ABCErrorHandler[EventT]):
125
125
  self,
126
126
  handler: Handler[EventT],
127
127
  event: EventT,
128
- api: ABCAPI,
128
+ api: API,
129
129
  ctx: Context,
130
130
  ) -> Result[typing.Any, BaseException]:
131
131
  assert self.catcher is not None
@@ -159,7 +159,7 @@ class ErrorHandler(ABCErrorHandler[EventT]):
159
159
  self,
160
160
  handler: Handler[EventT],
161
161
  event: EventT,
162
- api: ABCAPI,
162
+ api: API,
163
163
  ctx: Context,
164
164
  ) -> Result[typing.Any, BaseException]:
165
165
  if not self.catcher:
@@ -24,7 +24,7 @@ def is_spec_format(obj: typing.Any) -> typing.TypeGuard[SpecialFormat]:
24
24
  )
25
25
 
26
26
 
27
- @dataclasses.dataclass(repr=False)
27
+ @dataclasses.dataclass(repr=False, slots=True)
28
28
  class BaseSpecFormat:
29
29
  __formatter_name__: typing.ClassVar[str] = dataclasses.field(init=False, repr=False)
30
30
 
@@ -32,7 +32,7 @@ class BaseSpecFormat:
32
32
  return f"<Special formatter {self.__class__.__name__!r} -> {self.__formatter_name__!r}>"
33
33
 
34
34
 
35
- @dataclasses.dataclass(repr=False)
35
+ @dataclasses.dataclass(repr=False, slots=True)
36
36
  class ChannelBoostLink(BaseSpecFormat):
37
37
  __formatter_name__ = "channel_boost_link"
38
38
 
@@ -40,7 +40,7 @@ class ChannelBoostLink(BaseSpecFormat):
40
40
  string: str | None = None
41
41
 
42
42
 
43
- @dataclasses.dataclass(repr=False)
43
+ @dataclasses.dataclass(repr=False, slots=True)
44
44
  class InviteChatLink(BaseSpecFormat):
45
45
  __formatter_name__ = "invite_chat_link"
46
46
 
@@ -48,7 +48,7 @@ class InviteChatLink(BaseSpecFormat):
48
48
  string: str | None = None
49
49
 
50
50
 
51
- @dataclasses.dataclass(repr=False)
51
+ @dataclasses.dataclass(repr=False, slots=True)
52
52
  class Mention(BaseSpecFormat):
53
53
  __formatter_name__ = "mention"
54
54
 
@@ -56,7 +56,7 @@ class Mention(BaseSpecFormat):
56
56
  user_id: int
57
57
 
58
58
 
59
- @dataclasses.dataclass(repr=False)
59
+ @dataclasses.dataclass(repr=False, slots=True)
60
60
  class Link(BaseSpecFormat):
61
61
  __formatter_name__ = "link"
62
62
 
@@ -64,7 +64,7 @@ class Link(BaseSpecFormat):
64
64
  string: str | None = None
65
65
 
66
66
 
67
- @dataclasses.dataclass(repr=False)
67
+ @dataclasses.dataclass(repr=False, slots=True)
68
68
  class PreCode(BaseSpecFormat):
69
69
  __formatter_name__ = "pre_code"
70
70
 
@@ -72,7 +72,7 @@ class PreCode(BaseSpecFormat):
72
72
  lang: str | ProgrammingLanguage | None = None
73
73
 
74
74
 
75
- @dataclasses.dataclass(repr=False)
75
+ @dataclasses.dataclass(repr=False, slots=True)
76
76
  class TgEmoji(BaseSpecFormat):
77
77
  __formatter_name__ = "tg_emoji"
78
78
 
@@ -80,7 +80,7 @@ class TgEmoji(BaseSpecFormat):
80
80
  emoji_id: int
81
81
 
82
82
 
83
- @dataclasses.dataclass(repr=False)
83
+ @dataclasses.dataclass(repr=False, slots=True)
84
84
  class StartBotLink(BaseSpecFormat):
85
85
  __formatter_name__ = "start_bot_link"
86
86
 
@@ -89,7 +89,7 @@ class StartBotLink(BaseSpecFormat):
89
89
  string: str | None
90
90
 
91
91
 
92
- @dataclasses.dataclass(repr=False)
92
+ @dataclasses.dataclass(repr=False, slots=True)
93
93
  class StartGroupLink(BaseSpecFormat):
94
94
  __formatter_name__ = "start_group_link"
95
95
 
@@ -98,7 +98,7 @@ class StartGroupLink(BaseSpecFormat):
98
98
  string: str | None = None
99
99
 
100
100
 
101
- @dataclasses.dataclass(repr=False)
101
+ @dataclasses.dataclass(repr=False, slots=True)
102
102
  class ResolveDomain(BaseSpecFormat):
103
103
  __formatter_name__ = "resolve_domain"
104
104
 
@@ -1,12 +1,12 @@
1
1
  from .abc import ABCGlobalContext, CtxVar, GlobalCtxVar
2
2
  from .global_context import GlobalContext, ctx_var
3
- from .telegrinder_ctx import TelegrinderCtx
3
+ from .telegrinder_ctx import TelegrinderContext
4
4
 
5
5
  __all__ = (
6
6
  "ABCGlobalContext",
7
7
  "CtxVar",
8
8
  "GlobalContext",
9
9
  "GlobalCtxVar",
10
- "TelegrinderCtx",
10
+ "TelegrinderContext",
11
11
  "ctx_var",
12
12
  )
@@ -81,7 +81,7 @@ def ctx_var(value: T, *, const: bool = False) -> T:
81
81
  return typing.cast(T, CtxVar(value, const=const))
82
82
 
83
83
 
84
- @dataclasses.dataclass(frozen=True, eq=False)
84
+ @dataclasses.dataclass(frozen=True, eq=False, slots=True)
85
85
  class RootAttr:
86
86
  name: str
87
87
  can_be_read: bool = dataclasses.field(default=True, kw_only=True)
@@ -91,7 +91,7 @@ class RootAttr:
91
91
  return self.name == __value
92
92
 
93
93
 
94
- @dataclasses.dataclass(repr=False, frozen=True)
94
+ @dataclasses.dataclass(repr=False, frozen=True, slots=True)
95
95
  class Storage:
96
96
  _storage: dict[str, "GlobalContext"] = dataclasses.field(
97
97
  default_factory=lambda: {},
@@ -334,7 +334,7 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
334
334
  def get(self, var_name, var_value_type=object): # type: ignore
335
335
  """Get context variable by name."""
336
336
 
337
- var_value_type = typing.Any if var_value_type is object else type
337
+ var_value_type = typing.Any if var_value_type is object else var_value_type
338
338
  generic_types = typing.get_args(get_orig_class(self))
339
339
  if generic_types and var_value_type is object:
340
340
  var_value_type = generic_types[0]
@@ -5,14 +5,14 @@ import vbml
5
5
  from telegrinder.tools.global_context import GlobalContext, ctx_var
6
6
 
7
7
 
8
- class TelegrinderCtx(GlobalContext):
8
+ class TelegrinderContext(GlobalContext):
9
9
  """Basic type-hinted telegrinder context with context name `"telegrinder"`.
10
10
 
11
11
  You can use this class or GlobalContext:
12
12
  ```
13
- from telegrinder.tools.global_context import GlobalContext, TelegrinderCtx
13
+ from telegrinder.tools.global_context import GlobalContext, TelegrinderContext
14
14
 
15
- ctx1 = TelegrinderCtx()
15
+ ctx1 = TelegrinderContext()
16
16
  ctx2 = GlobalContext("telegrinder") # same, but without the type-hints
17
17
  assert ctx1 == ctx2 # ok
18
18
  ```"""
@@ -22,4 +22,4 @@ class TelegrinderCtx(GlobalContext):
22
22
  vbml_patcher: typing.ClassVar[vbml.Patcher] = ctx_var(vbml.Patcher(), const=True)
23
23
 
24
24
 
25
- __all__ = ("TelegrinderCtx",)
25
+ __all__ = ("TelegrinderContext",)
@@ -17,7 +17,7 @@ DictStrAny: typing.TypeAlias = dict[str, typing.Any]
17
17
  AnyMarkup: typing.TypeAlias = InlineKeyboardMarkup | ReplyKeyboardMarkup
18
18
 
19
19
 
20
- @dataclasses.dataclass(kw_only=True)
20
+ @dataclasses.dataclass(kw_only=True, slots=True)
21
21
  class KeyboardModel:
22
22
  resize_keyboard: bool
23
23
  one_time_keyboard: bool
@@ -77,7 +77,7 @@ class ABCMarkup(ABC, typing.Generic[ButtonT]):
77
77
  return self
78
78
 
79
79
 
80
- @dataclasses.dataclass(kw_only=True)
80
+ @dataclasses.dataclass(kw_only=True, slots=True)
81
81
  class Keyboard(ABCMarkup[Button], KeyboardModel):
82
82
  BUTTON = Button
83
83
 
@@ -94,7 +94,7 @@ class Keyboard(ABCMarkup[Button], KeyboardModel):
94
94
  self.keyboard = [row for row in self.keyboard if row]
95
95
  return {
96
96
  k: v.unwrap() if v and isinstance(v, Some) else v
97
- for k, v in self.__dict__.items()
97
+ for k, v in dataclasses.asdict(self).items()
98
98
  if type(v) not in (NoneType, Nothing)
99
99
  }
100
100
 
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import contextlib
3
3
  import dataclasses
4
+ import datetime
4
5
  import typing
5
6
 
6
7
  from telegrinder.modules import logger
@@ -32,7 +33,7 @@ def to_coroutine_task(task: Task) -> CoroutineTask[typing.Any]:
32
33
  return task
33
34
 
34
35
 
35
- @dataclasses.dataclass
36
+ @dataclasses.dataclass(slots=True)
36
37
  class DelayedTask(typing.Generic[CoroFunc]):
37
38
  handler: CoroFunc
38
39
  seconds: float
@@ -60,23 +61,23 @@ class DelayedTask(typing.Generic[CoroFunc]):
60
61
  self._cancelled = True
61
62
 
62
63
 
63
- @dataclasses.dataclass(kw_only=True)
64
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
64
65
  class Lifespan:
65
66
  startup_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
66
67
  shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
67
68
 
68
- def on_startup(self, task_or_func: Task) -> Task:
69
- self.startup_tasks.append(to_coroutine_task(task_or_func))
70
- return task_or_func
69
+ def on_startup(self, task: Task, /) -> Task:
70
+ self.startup_tasks.append(to_coroutine_task(task))
71
+ return task
71
72
 
72
- def on_shutdown(self, task_or_func: Task) -> Task:
73
- self.shutdown_tasks.append(to_coroutine_task(task_or_func))
74
- return task_or_func
73
+ def on_shutdown(self, task: Task, /) -> Task:
74
+ self.shutdown_tasks.append(to_coroutine_task(task))
75
+ return task
75
76
 
76
- def start(self, loop: asyncio.AbstractEventLoop) -> None:
77
+ def start(self, loop: asyncio.AbstractEventLoop, /) -> None:
77
78
  run_tasks(self.startup_tasks, loop)
78
79
 
79
- def shutdown(self, loop: asyncio.AbstractEventLoop) -> None:
80
+ def shutdown(self, loop: asyncio.AbstractEventLoop, /) -> None:
80
81
  run_tasks(self.shutdown_tasks, loop)
81
82
 
82
83
 
@@ -86,10 +87,11 @@ class LoopWrapper(ABCLoopWrapper):
86
87
  *,
87
88
  tasks: list[CoroutineTask[typing.Any]] | None = None,
88
89
  lifespan: Lifespan | None = None,
90
+ event_loop: asyncio.AbstractEventLoop | None = None,
89
91
  ) -> None:
90
92
  self.tasks: list[CoroutineTask[typing.Any]] = tasks or []
91
93
  self.lifespan = lifespan or Lifespan()
92
- self._loop = asyncio.new_event_loop()
94
+ self._loop = event_loop or asyncio.new_event_loop()
93
95
 
94
96
  def __repr__(self) -> str:
95
97
  return "<{}: loop={!r} with tasks={!r}, lifespan={!r}>".format(
@@ -143,6 +145,10 @@ class LoopWrapper(ABCLoopWrapper):
143
145
  with contextlib.suppress(asyncio.CancelledError):
144
146
  self._loop.run_until_complete(task_to_cancel)
145
147
 
148
+ @typing.overload
149
+ def timer(self, *, seconds: datetime.timedelta) -> typing.Callable[..., typing.Any]: ...
150
+
151
+ @typing.overload
146
152
  def timer(
147
153
  self,
148
154
  *,
@@ -150,7 +156,19 @@ class LoopWrapper(ABCLoopWrapper):
150
156
  hours: int = 0,
151
157
  minutes: int = 0,
152
158
  seconds: float = 0,
153
- ):
159
+ ) -> typing.Callable[..., typing.Any]: ...
160
+
161
+ def timer(
162
+ self,
163
+ *,
164
+ days: int = 0,
165
+ hours: int = 0,
166
+ minutes: int = 0,
167
+ seconds: float | datetime.timedelta = 0,
168
+ ) -> typing.Callable[..., typing.Any]:
169
+ if isinstance(seconds, datetime.timedelta):
170
+ seconds = seconds.total_seconds()
171
+
154
172
  seconds += minutes * 60
155
173
  seconds += hours * 60 * 60
156
174
  seconds += days * 24 * 60 * 60
@@ -161,6 +179,10 @@ class LoopWrapper(ABCLoopWrapper):
161
179
 
162
180
  return decorator
163
181
 
182
+ @typing.overload
183
+ def interval(self, *, seconds: datetime.timedelta) -> typing.Callable[..., typing.Any]: ...
184
+
185
+ @typing.overload
164
186
  def interval(
165
187
  self,
166
188
  *,
@@ -168,7 +190,19 @@ class LoopWrapper(ABCLoopWrapper):
168
190
  hours: int = 0,
169
191
  minutes: int = 0,
170
192
  seconds: float = 0,
171
- ):
193
+ ) -> typing.Callable[..., typing.Any]: ...
194
+
195
+ def interval(
196
+ self,
197
+ *,
198
+ days: int = 0,
199
+ hours: int = 0,
200
+ minutes: int = 0,
201
+ seconds: float | datetime.timedelta = 0,
202
+ ) -> typing.Callable[..., typing.Any]:
203
+ if isinstance(seconds, datetime.timedelta):
204
+ seconds = seconds.total_seconds()
205
+
172
206
  seconds += minutes * 60
173
207
  seconds += hours * 60 * 60
174
208
  seconds += days * 24 * 60 * 60
@@ -2,11 +2,17 @@ import enum
2
2
  import inspect
3
3
  import types
4
4
  import typing
5
+ from functools import wraps
5
6
 
6
7
  if typing.TYPE_CHECKING:
7
8
  from telegrinder.bot.rules.abc import ABCRule
9
+ from telegrinder.node.base import Node
8
10
 
9
11
  T = typing.TypeVar("T", bound=ABCRule)
12
+ F = typing.TypeVar(
13
+ "F",
14
+ bound=typing.Callable[typing.Concatenate[typing.Callable[..., typing.Any], ...], typing.Any],
15
+ )
10
16
 
11
17
  Impl: typing.TypeAlias = type[classmethod]
12
18
  FuncType: typing.TypeAlias = types.FunctionType | typing.Callable[..., typing.Any]
@@ -15,21 +21,36 @@ TRANSLATIONS_KEY: typing.Final[str] = "_translations"
15
21
  IMPL_MARK: typing.Final[str] = "_is_impl"
16
22
 
17
23
 
24
+ def cache_magic_value(mark_key: str, /):
25
+ def inner(func: "F") -> "F":
26
+
27
+ @wraps(func)
28
+ def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
29
+ if mark_key not in args[0].__dict__:
30
+ args[0].__dict__[mark_key] = func(*args, **kwargs)
31
+ return args[0].__dict__[mark_key]
32
+
33
+ return wrapper # type: ignore
34
+ return inner
35
+
36
+
18
37
  def resolve_arg_names(func: FuncType, start_idx: int = 1) -> tuple[str, ...]:
19
38
  return func.__code__.co_varnames[start_idx : func.__code__.co_argcount]
20
39
 
21
40
 
41
+ @cache_magic_value("__default_args__")
22
42
  def get_default_args(func: FuncType) -> dict[str, typing.Any]:
23
43
  fspec = inspect.getfullargspec(func)
24
- return dict(zip(fspec.args[::-1], (fspec.defaults or ())[::-1]))
44
+ if not fspec.defaults:
45
+ return {}
46
+ return dict(zip(fspec.args[-len(fspec.defaults):], fspec.defaults))
25
47
 
26
48
 
27
- def get_annotations(func: FuncType) -> dict[str, typing.Any]:
28
- return {
29
- name: parameter.annotation
30
- for name, parameter in inspect.signature(func).parameters.items()
31
- if parameter.annotation is not inspect._empty
32
- }
49
+ def get_annotations(func: FuncType, *, return_type: bool = False) -> dict[str, typing.Any]:
50
+ annotations = func.__annotations__
51
+ if not return_type and "return" in func.__annotations__:
52
+ annotations.pop("return")
53
+ return annotations
33
54
 
34
55
 
35
56
  def to_str(s: str | enum.Enum) -> str:
@@ -38,13 +59,64 @@ def to_str(s: str | enum.Enum) -> str:
38
59
  return s
39
60
 
40
61
 
62
+ @typing.overload
63
+ def magic_bundle(handler: FuncType, kw: dict[str, typing.Any]) -> dict[str, typing.Any]:
64
+ ...
65
+
66
+
67
+ @typing.overload
68
+ def magic_bundle(handler: FuncType, kw: dict[enum.Enum, typing.Any]) -> dict[str, typing.Any]:
69
+ ...
70
+
71
+
72
+ @typing.overload
41
73
  def magic_bundle(
42
74
  handler: FuncType,
43
- kw: dict[str | enum.Enum, typing.Any],
75
+ kw: dict[str, typing.Any],
44
76
  *,
45
77
  start_idx: int = 1,
46
78
  bundle_ctx: bool = True,
47
79
  ) -> dict[str, typing.Any]:
80
+ ...
81
+
82
+
83
+ @typing.overload
84
+ def magic_bundle(
85
+ handler: FuncType,
86
+ kw: dict[enum.Enum, typing.Any],
87
+ *,
88
+ start_idx: int = 1,
89
+ bundle_ctx: bool = True,
90
+ ) -> dict[str, typing.Any]:
91
+ ...
92
+
93
+
94
+ @typing.overload
95
+ def magic_bundle(
96
+ handler: FuncType,
97
+ kw: dict[type, typing.Any],
98
+ *,
99
+ typebundle: typing.Literal[True] = True,
100
+ ) -> dict[str, typing.Any]:
101
+ ...
102
+
103
+
104
+ def magic_bundle(
105
+ handler: FuncType,
106
+ kw: dict[typing.Any, typing.Any],
107
+ *,
108
+ start_idx: int = 1,
109
+ bundle_ctx: bool = True,
110
+ typebundle: bool = False,
111
+ ) -> dict[str, typing.Any]:
112
+
113
+ if typebundle:
114
+ types = get_annotations(handler, return_type=False)
115
+ bundle: dict[str, typing.Any] = {}
116
+ for name, type in types.items():
117
+ bundle[name] = kw[type]
118
+ return bundle
119
+
48
120
  names = resolve_arg_names(handler, start_idx=start_idx)
49
121
  args = get_default_args(handler)
50
122
  args.update({to_str(k): v for k, v in kw.items() if to_str(k) in names})
@@ -59,30 +131,36 @@ def get_cached_translation(rule: "T", locale: str) -> "T | None":
59
131
 
60
132
  def cache_translation(base_rule: "T", locale: str, translated_rule: "T") -> None:
61
133
  translations = getattr(base_rule, TRANSLATIONS_KEY, {})
62
- setattr(base_rule, TRANSLATIONS_KEY, {locale: translated_rule, **translations})
63
-
64
-
65
- def get_impls(cls: type[typing.Any]) -> list[typing.Callable[..., typing.Any]]:
66
- functions = [func.__func__ for func in vars(cls).values() if isinstance(func, classmethod)]
67
- return [impl for impl in functions if getattr(impl, IMPL_MARK, False) is True]
134
+ translations[locale] = translated_rule
135
+ setattr(base_rule, TRANSLATIONS_KEY, translations)
68
136
 
69
137
 
70
138
  @typing.cast(typing.Callable[..., Impl], lambda f: f)
71
139
  def impl(method: typing.Callable[..., typing.Any]):
72
- bound_method = classmethod(method)
73
140
  setattr(method, IMPL_MARK, True)
74
- return bound_method
141
+ return classmethod(method)
142
+
143
+
144
+ def get_impls(cls: type["Node"]) -> list[typing.Callable[..., typing.Any]]:
145
+ return [
146
+ func.__func__
147
+ for func in vars(cls).values()
148
+ if isinstance(func, classmethod) and getattr(func.__func__, IMPL_MARK, False)
149
+ ]
150
+
75
151
 
76
152
 
77
153
  __all__ = (
78
154
  "TRANSLATIONS_KEY",
155
+ "cache_magic_value",
79
156
  "cache_translation",
157
+ "get_annotations",
80
158
  "get_cached_translation",
81
159
  "get_default_args",
82
160
  "get_default_args",
83
- "magic_bundle",
84
161
  "impl",
162
+ "get_impls",
163
+ "magic_bundle",
85
164
  "resolve_arg_names",
86
165
  "to_str",
87
- "get_annotations",
88
166
  )