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
@@ -19,13 +19,13 @@ FuncCatcher = typing.Callable[typing.Concatenate[ExceptionT, ...], typing.Awaita
19
19
  @dataclasses.dataclass(frozen=True, repr=False)
20
20
  class Catcher(typing.Generic[EventT]):
21
21
  func: FuncCatcher[BaseException]
22
- _: dataclasses.KW_ONLY
23
22
  exceptions: list[type[BaseException] | BaseException] = dataclasses.field(
24
23
  default_factory=lambda: [],
24
+ kw_only=True,
25
25
  )
26
- logging: bool = dataclasses.field(default=False)
27
- raise_exception: bool = dataclasses.field(default=False)
28
- ignore_errors: bool = dataclasses.field(default=False)
26
+ logging: bool = dataclasses.field(default=False, kw_only=True)
27
+ raise_exception: bool = dataclasses.field(default=False, kw_only=True)
28
+ ignore_errors: bool = dataclasses.field(default=False, kw_only=True)
29
29
 
30
30
  def __repr__(self) -> str:
31
31
  return "<Catcher: function={!r}, logging={}, raise_exception={}, ignore_errors={}>".format(
@@ -57,9 +57,8 @@ class Catcher(typing.Generic[EventT]):
57
57
  ) -> Result[typing.Any, BaseException]:
58
58
  if self.match_exception(exception):
59
59
  logger.debug(
60
- "Catcher {!r} caught an exception in handler {!r}, " "running catcher...".format(
61
- self.func.__name__,
62
- handler_name,
60
+ "Error handler caught an exception {!r} in handler {!r}, running catcher {!r}...".format(
61
+ exception, handler_name, self.func.__name__
63
62
  )
64
63
  )
65
64
  return Ok(
@@ -68,7 +67,7 @@ class Catcher(typing.Generic[EventT]):
68
67
  **magic_bundle(self.func, {"event": event, "api": api} | ctx), # type: ignore
69
68
  )
70
69
  )
71
- logger.debug("Failed to match exception {!r}!", exception.__class__.__name__)
70
+ logger.debug("Failed to match exception {!r}.", exception.__class__.__name__)
72
71
  return Error(exception)
73
72
 
74
73
  def match_exception(self, exception: BaseException) -> bool:
@@ -88,14 +87,11 @@ class ErrorHandler(ABCErrorHandler[EventT]):
88
87
  return (
89
88
  "<{}: exceptions_handled=[{}], catcher={!r}>".format(
90
89
  self.__class__.__name__,
91
- ", ".join(
92
- e.__name__ if isinstance(e, type) else repr(e)
93
- for e in self.catcher.exceptions
94
- ),
90
+ ", ".join(e.__name__ if isinstance(e, type) else repr(e) for e in self.catcher.exceptions),
95
91
  self.catcher,
96
92
  )
97
93
  if self.catcher is not None
98
- else "<{}: No catcher>".format(self.__class__.__name__)
94
+ else "<{}()>".format(self.__class__.__name__)
99
95
  )
100
96
 
101
97
  def __call__(
@@ -133,6 +129,7 @@ class ErrorHandler(ABCErrorHandler[EventT]):
133
129
  ctx: Context,
134
130
  ) -> Result[typing.Any, BaseException]:
135
131
  assert self.catcher is not None
132
+ logger.debug("Processing the error handler for handler {!r}...", handler.__name__)
136
133
 
137
134
  try:
138
135
  return await self.catcher(handler, event, api, ctx)
@@ -172,7 +169,7 @@ class ErrorHandler(ABCErrorHandler[EventT]):
172
169
  case Ok(value) as ok:
173
170
  if self.catcher.logging:
174
171
  logger.debug(
175
- "Catcher {!r} returned a value: {!r}",
172
+ "Catcher {!r} returned: {!r}",
176
173
  self.catcher.func.__name__,
177
174
  value,
178
175
  )
@@ -18,7 +18,6 @@ from .html import (
18
18
  strike,
19
19
  tg_emoji,
20
20
  underline,
21
- user_open_message,
22
21
  )
23
22
  from .links import (
24
23
  get_channel_boost_link,
@@ -27,7 +26,6 @@ from .links import (
27
26
  get_resolve_domain_link,
28
27
  get_start_bot_link,
29
28
  get_start_group_link,
30
- user_open_message_link,
31
29
  )
32
30
  from .spec_html_formats import (
33
31
  BaseSpecFormat,
@@ -41,7 +39,6 @@ from .spec_html_formats import (
41
39
  StartBotLink,
42
40
  StartGroupLink,
43
41
  TgEmoji,
44
- UserOpenMessage,
45
42
  )
46
43
 
47
44
  __all__ = (
@@ -58,7 +55,6 @@ __all__ = (
58
55
  "StartBotLink",
59
56
  "StartGroupLink",
60
57
  "TgEmoji",
61
- "UserOpenMessage",
62
58
  "block_quote",
63
59
  "bold",
64
60
  "channel_boost_link",
@@ -82,6 +78,4 @@ __all__ = (
82
78
  "strike",
83
79
  "tg_emoji",
84
80
  "underline",
85
- "user_open_message",
86
- "user_open_message_link",
87
81
  )
@@ -1,3 +1,5 @@
1
+ # NOTE: NEED REFACTORING
2
+
1
3
  import html
2
4
  import string
3
5
  import typing
@@ -13,7 +15,6 @@ from .links import (
13
15
  get_resolve_domain_link,
14
16
  get_start_bot_link,
15
17
  get_start_group_link,
16
- user_open_message_link,
17
18
  )
18
19
  from .spec_html_formats import SpecialFormat, is_spec_format
19
20
 
@@ -21,6 +22,12 @@ TAG_FORMAT = "<{tag}{data}>{content}</{tag}>"
21
22
  QUOT_MARK = '"'
22
23
 
23
24
 
25
+ class StringFormatterProto(typing.Protocol):
26
+ def format_field(self, value: typing.Any, fmt: str) -> "HTMLFormatter": ...
27
+
28
+ def format(self, __string: str, *args: object, **kwargs: object) -> "HTMLFormatter": ...
29
+
30
+
24
31
  class StringFormatter(string.Formatter):
25
32
  """String formatter, using substitutions from args and kwargs.
26
33
  The substitutions are identified by braces ('{' and '}') with
@@ -107,7 +114,7 @@ class StringFormatter(string.Formatter):
107
114
 
108
115
 
109
116
  class FormatString(str):
110
- string_formatter = StringFormatter()
117
+ STRING_FORMATTER: StringFormatterProto = StringFormatter()
111
118
 
112
119
  def __new__(cls, string: str) -> typing.Self:
113
120
  if isinstance(string, TagFormat):
@@ -131,7 +138,7 @@ class FormatString(str):
131
138
  return self.__str__()
132
139
 
133
140
  def format(self, *args: object, **kwargs: object) -> "HTMLFormatter":
134
- return self.string_formatter.format(self, *args, **kwargs)
141
+ return self.STRING_FORMATTER.format(self, *args, **kwargs)
135
142
 
136
143
 
137
144
  class EscapedString(FormatString):
@@ -273,14 +280,6 @@ def underline(string: str) -> TagFormat:
273
280
  return TagFormat(string, tag="u")
274
281
 
275
282
 
276
- def user_open_message(
277
- user_id: int,
278
- message: str | None = None,
279
- string: str | None = None,
280
- ) -> TagFormat:
281
- return link(user_open_message_link(user_id, message), string)
282
-
283
-
284
283
  __all__ = (
285
284
  "FormatString",
286
285
  "HTMLFormatter",
@@ -308,5 +307,4 @@ __all__ = (
308
307
  "strike",
309
308
  "tg_emoji",
310
309
  "underline",
311
- "user_open_message",
312
310
  )
@@ -28,10 +28,6 @@ def get_invite_chat_link(invite_link: str) -> str:
28
28
  return f"tg://join?invite={invite_link}"
29
29
 
30
30
 
31
- def user_open_message_link(user_id: int, message: str | None = None) -> str:
32
- return f"tg://openmessage?user_id={user_id}" + ("" if not message else f"&msg?text={message}")
33
-
34
-
35
31
  __all__ = (
36
32
  "get_channel_boost_link",
37
33
  "get_invite_chat_link",
@@ -39,5 +35,4 @@ __all__ = (
39
35
  "get_resolve_domain_link",
40
36
  "get_start_bot_link",
41
37
  "get_start_group_link",
42
- "user_open_message_link",
43
38
  )
@@ -13,7 +13,6 @@ SpecialFormat: typing.TypeAlias = typing.Union[
13
13
  "StartBotLink",
14
14
  "StartGroupLink",
15
15
  "TgEmoji",
16
- "UserOpenMessage",
17
16
  ]
18
17
 
19
18
 
@@ -107,15 +106,6 @@ class ResolveDomain(BaseSpecFormat):
107
106
  string: str | None = None
108
107
 
109
108
 
110
- @dataclasses.dataclass(repr=False)
111
- class UserOpenMessage(BaseSpecFormat):
112
- __formatter_name__ = "user_open_message"
113
-
114
- user_id: int
115
- message: str | None = None
116
- string: str | None = None
117
-
118
-
119
109
  __all__ = (
120
110
  "BaseSpecFormat",
121
111
  "ChannelBoostLink",
@@ -128,5 +118,4 @@ __all__ = (
128
118
  "StartBotLink",
129
119
  "StartGroupLink",
130
120
  "TgEmoji",
131
- "UserOpenMessage",
132
121
  )
@@ -35,9 +35,7 @@ class GlobalCtxVar(typing.Generic[T]):
35
35
 
36
36
  @classmethod
37
37
  def collect(cls, name: str, ctx_value: T | CtxVariable[T]) -> typing.Self:
38
- ctx_value = (
39
- CtxVar(ctx_value) if not isinstance(ctx_value, CtxVar | GlobalCtxVar) else ctx_value
40
- )
38
+ ctx_value = CtxVar(ctx_value) if not isinstance(ctx_value, CtxVar | GlobalCtxVar) else ctx_value
41
39
  params = ctx_value.__dict__
42
40
  params["name"] = name
43
41
  return cls(**params)
@@ -84,7 +84,6 @@ def ctx_var(value: T, *, const: bool = False) -> T:
84
84
  @dataclasses.dataclass(frozen=True, eq=False)
85
85
  class RootAttr:
86
86
  name: str
87
- _: dataclasses.KW_ONLY
88
87
  can_be_read: bool = dataclasses.field(default=True, kw_only=True)
89
88
  can_be_rewritten: bool = dataclasses.field(default=False, kw_only=True)
90
89
 
@@ -124,9 +123,7 @@ class Storage:
124
123
  order_default=True,
125
124
  field_specifiers=(ctx_var,),
126
125
  )
127
- class GlobalContext(
128
- ABCGlobalContext, typing.Generic[CtxValueT], dict[str, GlobalCtxVar[CtxValueT]]
129
- ):
126
+ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, GlobalCtxVar[CtxValueT]]):
130
127
  """GlobalContext.
131
128
 
132
129
  ```
@@ -315,10 +312,10 @@ class GlobalContext(
315
312
  var_value_type: type[T],
316
313
  ) -> Option[GlobalCtxVar[T]]: ...
317
314
 
318
- def pop(self, var_name: str, var_value_type: type[T] = object) -> Option[GlobalCtxVar[T]]:
315
+ def pop(self, var_name: str, var_value_type=object): # type: ignore
319
316
  """Pop context variable by name."""
320
317
 
321
- val = self.get(var_name, var_value_type)
318
+ val = self.get(var_name, var_value_type) # type: ignore
322
319
  if val:
323
320
  del self[var_name]
324
321
  return val
@@ -334,13 +331,10 @@ class GlobalContext(
334
331
  var_value_type: type[T],
335
332
  ) -> Option[GlobalCtxVar[T]]: ...
336
333
 
337
- def get(
338
- self,
339
- var_name: str,
340
- var_value_type: type[T] = object,
341
- ) -> Option[GlobalCtxVar[T]]:
334
+ def get(self, var_name, var_value_type=object): # type: ignore
342
335
  """Get context variable by name."""
343
336
 
337
+ var_value_type = typing.Any if var_value_type is object else type
344
338
  generic_types = typing.get_args(get_orig_class(self))
345
339
  if generic_types and var_value_type is object:
346
340
  var_value_type = generic_types[0]
@@ -369,11 +363,7 @@ class GlobalContext(
369
363
  var_value_type: type[T],
370
364
  ) -> Option[T]: ...
371
365
 
372
- def get_value(
373
- self,
374
- var_name: str,
375
- var_value_type: type[T] = object,
376
- ) -> Option[T]:
366
+ def get_value(self, var_name, var_value_type=object): # type: ignore
377
367
  """Get context variable value by name."""
378
368
 
379
369
  return self.get(var_name, var_value_type).map(lambda var: var.value)
@@ -29,9 +29,7 @@ class SimpleI18n(ABCI18n):
29
29
  return result
30
30
 
31
31
  def get_translator_by_locale(self, locale: str) -> "SimpleTranslator":
32
- return SimpleTranslator(
33
- locale, self.translators.get(locale, self.translators[self.default_locale])
34
- )
32
+ return SimpleTranslator(locale, self.translators.get(locale, self.translators[self.default_locale]))
35
33
 
36
34
 
37
35
  class SimpleTranslator(ABCTranslator):
@@ -44,8 +44,7 @@ class KeyboardSetYAML(KeyboardSetBase):
44
44
  or not isinstance(kb_config["buttons"], list)
45
45
  ):
46
46
  raise KeyboardSetError(
47
- "Keyboard should be dict with field buttons which must be a list, "
48
- "check documentation."
47
+ "Keyboard should be dict with field buttons which must be a list, " "check documentation."
49
48
  )
50
49
 
51
50
  buttons = kb_config.pop("buttons")
@@ -5,7 +5,6 @@ from types import NoneType
5
5
 
6
6
  from fntypes.option import Nothing, Some
7
7
 
8
- from telegrinder.msgspec_utils import Option
9
8
  from telegrinder.types.objects import (
10
9
  InlineKeyboardMarkup,
11
10
  ReplyKeyboardMarkup,
@@ -18,12 +17,12 @@ DictStrAny: typing.TypeAlias = dict[str, typing.Any]
18
17
  AnyMarkup: typing.TypeAlias = InlineKeyboardMarkup | ReplyKeyboardMarkup
19
18
 
20
19
 
21
- @dataclasses.dataclass
20
+ @dataclasses.dataclass(kw_only=True)
22
21
  class KeyboardModel:
23
- resize_keyboard: bool | Option[bool]
24
- one_time_keyboard: bool | Option[bool]
25
- selective: bool | Option[bool]
26
- is_persistent: bool | Option[bool]
22
+ resize_keyboard: bool
23
+ one_time_keyboard: bool
24
+ selective: bool
25
+ is_persistent: bool
27
26
  keyboard: list[list[DictStrAny]]
28
27
 
29
28
 
@@ -40,7 +39,7 @@ class ABCMarkup(ABC, typing.Generic[ButtonT]):
40
39
  pass
41
40
 
42
41
  @classmethod
43
- def empty(cls) -> AnyMarkup:
42
+ def get_empty_markup(cls) -> AnyMarkup:
44
43
  return cls().get_markup()
45
44
 
46
45
  def add(self, row_or_button: RowButtons[ButtonT] | ButtonT) -> typing.Self:
@@ -102,7 +101,7 @@ class Keyboard(ABCMarkup[Button], KeyboardModel):
102
101
  def get_markup(self) -> ReplyKeyboardMarkup:
103
102
  return ReplyKeyboardMarkup(**self.dict())
104
103
 
105
- def get_empty_markup(self, *, selective: bool = False) -> ReplyKeyboardRemove:
104
+ def keyboard_remove(self, *, selective: bool = False) -> ReplyKeyboardRemove:
106
105
  return ReplyKeyboardRemove(remove_keyboard=True, selective=Some(selective))
107
106
 
108
107
 
@@ -27,7 +27,7 @@ class LimitedDict(UserDict[KT, VT]):
27
27
 
28
28
  def __setitem__(self, key: KT, value: VT, /) -> None:
29
29
  self.set(key, value)
30
-
30
+
31
31
  def __delitem__(self, key: KT) -> None:
32
32
  if key in self.queue:
33
33
  self.queue.remove(key)
@@ -48,7 +48,10 @@ class DelayedTask(typing.Generic[CoroFunc]):
48
48
  await asyncio.sleep(self.seconds)
49
49
  if self.is_cancelled:
50
50
  break
51
- await self.handler(*args, **kwargs)
51
+ try:
52
+ await self.handler(*args, **kwargs)
53
+ except Exception as e:
54
+ logger.exception("Error in delayed task: {}", str(e))
52
55
  if not self.repeat:
53
56
  break
54
57
 
@@ -63,13 +66,11 @@ class Lifespan:
63
66
  shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
64
67
 
65
68
  def on_startup(self, task_or_func: Task) -> Task:
66
- task_or_func = to_coroutine_task(task_or_func)
67
- self.startup_tasks.append(task_or_func)
69
+ self.startup_tasks.append(to_coroutine_task(task_or_func))
68
70
  return task_or_func
69
71
 
70
72
  def on_shutdown(self, task_or_func: Task) -> Task:
71
- task_or_func = to_coroutine_task(task_or_func)
72
- self.shutdown_tasks.append(task_or_func)
73
+ self.shutdown_tasks.append(to_coroutine_task(task_or_func))
73
74
  return task_or_func
74
75
 
75
76
  def start(self, loop: asyncio.AbstractEventLoop) -> None:
@@ -8,8 +8,11 @@ if typing.TYPE_CHECKING:
8
8
 
9
9
  T = typing.TypeVar("T", bound=ABCRule)
10
10
 
11
+ Impl: typing.TypeAlias = type[classmethod]
11
12
  FuncType: typing.TypeAlias = types.FunctionType | typing.Callable[..., typing.Any]
13
+
12
14
  TRANSLATIONS_KEY: typing.Final[str] = "_translations"
15
+ IMPL_MARK: typing.Final[str] = "_is_impl"
13
16
 
14
17
 
15
18
  def resolve_arg_names(func: FuncType, start_idx: int = 1) -> tuple[str, ...]:
@@ -21,6 +24,14 @@ def get_default_args(func: FuncType) -> dict[str, typing.Any]:
21
24
  return dict(zip(fspec.args[::-1], (fspec.defaults or ())[::-1]))
22
25
 
23
26
 
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
+ }
33
+
34
+
24
35
  def to_str(s: str | enum.Enum) -> str:
25
36
  if isinstance(s, enum.Enum):
26
37
  return str(s.value)
@@ -42,11 +53,8 @@ def magic_bundle(
42
53
  return args
43
54
 
44
55
 
45
- def get_cached_translation(rule: "T", locale: str) -> typing.Optional["T"]:
46
- translations = getattr(rule, TRANSLATIONS_KEY, {})
47
- if not translations or locale not in translations:
48
- return None
49
- return translations[locale]
56
+ def get_cached_translation(rule: "T", locale: str) -> "T | None":
57
+ return getattr(rule, TRANSLATIONS_KEY, {}).get(locale)
50
58
 
51
59
 
52
60
  def cache_translation(base_rule: "T", locale: str, translated_rule: "T") -> None:
@@ -54,6 +62,18 @@ def cache_translation(base_rule: "T", locale: str, translated_rule: "T") -> None
54
62
  setattr(base_rule, TRANSLATIONS_KEY, {locale: translated_rule, **translations})
55
63
 
56
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]
68
+
69
+
70
+ @typing.cast(typing.Callable[..., Impl], lambda f: f)
71
+ def impl(method: typing.Callable[..., typing.Any]):
72
+ bound_method = classmethod(method)
73
+ setattr(method, IMPL_MARK, True)
74
+ return bound_method
75
+
76
+
57
77
  __all__ = (
58
78
  "TRANSLATIONS_KEY",
59
79
  "cache_translation",
@@ -61,6 +81,8 @@ __all__ = (
61
81
  "get_default_args",
62
82
  "get_default_args",
63
83
  "magic_bundle",
84
+ "impl",
64
85
  "resolve_arg_names",
65
86
  "to_str",
87
+ "get_annotations",
66
88
  )
@@ -136,6 +136,9 @@ __all__ = (
136
136
  "InputMediaPhoto",
137
137
  "InputMediaVideo",
138
138
  "InputMessageContent",
139
+ "InputPaidMedia",
140
+ "InputPaidMediaPhoto",
141
+ "InputPaidMediaVideo",
139
142
  "InputPollOption",
140
143
  "InputSticker",
141
144
  "InputTextMessageContent",
@@ -171,6 +174,11 @@ __all__ = (
171
174
  "MessageReactionUpdated",
172
175
  "Model",
173
176
  "OrderInfo",
177
+ "PaidMedia",
178
+ "PaidMediaInfo",
179
+ "PaidMediaPhoto",
180
+ "PaidMediaPreview",
181
+ "PaidMediaVideo",
174
182
  "PassportData",
175
183
  "PassportElementError",
176
184
  "PassportElementErrorDataField",
@@ -198,15 +206,22 @@ __all__ = (
198
206
  "ReactionTypeCustomEmoji",
199
207
  "ReactionTypeEmoji",
200
208
  "ReactionTypeType",
209
+ "RefundedPayment",
201
210
  "ReplyKeyboardMarkup",
202
211
  "ReplyKeyboardRemove",
203
212
  "ReplyParameters",
204
213
  "ResponseParameters",
214
+ "RevenueWithdrawalState",
215
+ "RevenueWithdrawalStateFailed",
216
+ "RevenueWithdrawalStatePending",
217
+ "RevenueWithdrawalStateSucceeded",
205
218
  "SentWebAppMessage",
206
219
  "SharedUser",
207
220
  "ShippingAddress",
208
221
  "ShippingOption",
209
222
  "ShippingQuery",
223
+ "StarTransaction",
224
+ "StarTransactions",
210
225
  "Sticker",
211
226
  "StickerFormat",
212
227
  "StickerSet",
@@ -217,6 +232,11 @@ __all__ = (
217
232
  "SwitchInlineQueryChosenChat",
218
233
  "TextQuote",
219
234
  "TopicIconColor",
235
+ "TransactionPartner",
236
+ "TransactionPartnerFragment",
237
+ "TransactionPartnerOther",
238
+ "TransactionPartnerTelegramAds",
239
+ "TransactionPartnerUser",
220
240
  "Update",
221
241
  "UpdateType",
222
242
  "User",