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
@@ -2,24 +2,22 @@ import dataclasses
2
2
  import typing
3
3
 
4
4
  from telegrinder.bot.dispatch.context import Context
5
- from telegrinder.node import Source
6
5
  from telegrinder.node.command import CommandInfo, single_split
7
6
  from telegrinder.node.me import Me
7
+ from telegrinder.node.source import Source
8
+ from telegrinder.types.enums import ChatType
8
9
 
9
- from ...types import ChatType
10
10
  from .abc import ABCRule
11
11
 
12
- Validator = typing.Callable[[str], typing.Any | None]
12
+ Validator: typing.TypeAlias = typing.Callable[[str], typing.Any | None]
13
13
 
14
14
 
15
- @dataclasses.dataclass(frozen=True)
15
+ @dataclasses.dataclass(frozen=True, slots=True)
16
16
  class Argument:
17
17
  name: str
18
18
  validators: list[Validator] = dataclasses.field(default_factory=lambda: [])
19
19
  optional: bool = dataclasses.field(default=False, kw_only=True)
20
20
 
21
- # NOTE: add optional param `description`
22
-
23
21
  def check(self, data: str) -> typing.Any | None:
24
22
  for validator in self.validators:
25
23
  data = validator(data) # type: ignore
@@ -79,7 +77,7 @@ class Command(ABCRule):
79
77
 
80
78
  return self.parse_arguments(arguments[1:], s)
81
79
 
82
- def parse_arguments(self, arguments: list[Argument], s: str) -> dict | None:
80
+ def parse_arguments(self, arguments: list[Argument], s: str) -> dict[str, typing.Any] | None:
83
81
  if not arguments:
84
82
  return {} if not s else None
85
83
 
@@ -2,7 +2,7 @@ import inspect
2
2
  import typing
3
3
 
4
4
  from telegrinder.bot.dispatch.context import Context
5
- from telegrinder.types import Update
5
+ from telegrinder.types.objects import Update
6
6
 
7
7
  from .abc import ABCAdapter, ABCRule, AdaptTo, RawUpdateAdapter
8
8
 
@@ -12,7 +12,7 @@ class FuncRule(ABCRule, typing.Generic[AdaptTo]):
12
12
  self,
13
13
  func: typing.Callable[[AdaptTo, Context], typing.Awaitable[bool] | bool],
14
14
  adapter: ABCAdapter[Update, AdaptTo] | None = None,
15
- ):
15
+ ) -> None:
16
16
  self.func = func
17
17
  self.adapter = adapter or RawUpdateAdapter() # type: ignore
18
18
 
@@ -7,7 +7,7 @@ from .abc import ABCRule
7
7
 
8
8
 
9
9
  class FuzzyText(ABCRule):
10
- def __init__(self, texts: str | list[str], min_ratio: float = 0.7):
10
+ def __init__(self, texts: str | list[str], min_ratio: float = 0.7) -> None:
11
11
  if isinstance(texts, str):
12
12
  texts = [texts]
13
13
  self.texts = texts
@@ -21,7 +21,7 @@ class InlineQueryRule(ABCRule[InlineQuery], abc.ABC):
21
21
 
22
22
 
23
23
  class HasLocation(InlineQueryRule):
24
- async def check(self, query: InlineQuery, ctx: Context) -> bool:
24
+ async def check(self, query: InlineQuery) -> bool:
25
25
  return bool(query.location)
26
26
 
27
27
 
@@ -29,7 +29,7 @@ class InlineQueryChatType(InlineQueryRule):
29
29
  def __init__(self, chat_type: ChatType, /) -> None:
30
30
  self.chat_type = chat_type
31
31
 
32
- async def check(self, query: InlineQuery, ctx: Context) -> bool:
32
+ async def check(self, query: InlineQuery) -> bool:
33
33
  return query.chat_type.map(lambda x: x == self.chat_type).unwrap_or(False)
34
34
 
35
35
 
@@ -40,7 +40,7 @@ class InlineQueryText(InlineQueryRule):
40
40
  ]
41
41
  self.lower_case = lower_case
42
42
 
43
- async def check(self, query: InlineQuery, ctx: Context) -> bool:
43
+ async def check(self, query: InlineQuery) -> bool:
44
44
  return (query.query.lower() if self.lower_case else query.query) in self.texts
45
45
 
46
46
 
@@ -1,4 +1,3 @@
1
- from telegrinder.bot.dispatch.context import Context
2
1
  from telegrinder.node.text import TextInteger
3
2
 
4
3
  from .abc import ABCRule
@@ -11,7 +10,7 @@ class IsInteger(NodeRule):
11
10
 
12
11
 
13
12
  class IntegerInRange(ABCRule):
14
- def __init__(self, rng: range):
13
+ def __init__(self, rng: range) -> None:
15
14
  self.rng = rng
16
15
 
17
16
  async def check(self, integer: TextInteger) -> bool:
@@ -4,12 +4,12 @@ import vbml
4
4
 
5
5
  from telegrinder.bot.dispatch.context import Context
6
6
  from telegrinder.node.text import Text
7
- from telegrinder.tools.global_context import TelegrinderCtx
7
+ from telegrinder.tools.global_context.telegrinder_ctx import TelegrinderContext
8
8
 
9
9
  from .abc import ABCRule
10
10
 
11
11
  PatternLike: typing.TypeAlias = str | vbml.Pattern
12
- global_ctx = TelegrinderCtx()
12
+ global_ctx: typing.Final[TelegrinderContext] = TelegrinderContext()
13
13
 
14
14
 
15
15
  def check_string(patterns: list[vbml.Pattern], s: str, ctx: Context) -> bool:
@@ -24,7 +24,9 @@ def check_string(patterns: list[vbml.Pattern], s: str, ctx: Context) -> bool:
24
24
 
25
25
 
26
26
  class Markup(ABCRule):
27
- def __init__(self, patterns: PatternLike | list[PatternLike], /):
27
+ """Markup Language. See [VBML docs](https://github.com/tesseradecade/vbml/blob/master/docs/index.md)"""
28
+
29
+ def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
28
30
  if not isinstance(patterns, list):
29
31
  patterns = [patterns]
30
32
  self.patterns = [
@@ -10,12 +10,12 @@ Entity: typing.TypeAlias = str | MessageEntityType
10
10
 
11
11
 
12
12
  class HasEntities(MessageRule):
13
- async def check(self, message: Message, ctx: Context) -> bool:
13
+ async def check(self, message: Message) -> bool:
14
14
  return bool(message.entities)
15
15
 
16
16
 
17
17
  class MessageEntities(MessageRule, requires=[HasEntities()]):
18
- def __init__(self, entities: Entity | list[Entity]):
18
+ def __init__(self, entities: Entity | list[Entity], /) -> None:
19
19
  self.entities = [entities] if not isinstance(entities, list) else entities
20
20
 
21
21
  async def check(self, message: Message, ctx: Context) -> bool:
@@ -1,7 +1,7 @@
1
1
  import typing
2
2
 
3
3
  from telegrinder.bot.dispatch.context import Context
4
- from telegrinder.node import Node
4
+ from telegrinder.node.base import Node
5
5
 
6
6
  from .abc import ABCRule
7
7
  from .adapter.node import NodeAdapter
@@ -15,7 +15,7 @@ class NodeRule(ABCRule[tuple[Node, ...]]):
15
15
 
16
16
  @property
17
17
  def adapter(self) -> NodeAdapter:
18
- return NodeAdapter(*self.nodes)
18
+ return NodeAdapter(*self.nodes) # type: ignore
19
19
 
20
20
  async def check(self, resolved_nodes: tuple[Node, ...], ctx: Context) -> typing.Literal[True]:
21
21
  for i, node in enumerate(resolved_nodes):
@@ -10,7 +10,7 @@ PatternLike: typing.TypeAlias = str | typing.Pattern[str]
10
10
 
11
11
 
12
12
  class Regex(ABCRule):
13
- def __init__(self, regexp: PatternLike | list[PatternLike]):
13
+ def __init__(self, regexp: PatternLike | list[PatternLike]) -> None:
14
14
  self.regexp: list[re.Pattern[str]] = []
15
15
  match regexp:
16
16
  case re.Pattern() as pattern:
@@ -7,7 +7,7 @@ from .abc import ABCRule, Update, check_rule
7
7
  from .func import FuncRule
8
8
 
9
9
 
10
- @dataclasses.dataclass
10
+ @dataclasses.dataclass(slots=True)
11
11
  class RuleEnumState:
12
12
  name: str
13
13
  rule: ABCRule
@@ -1,5 +1,4 @@
1
1
  from telegrinder import node
2
- from telegrinder.bot.dispatch.context import Context
3
2
  from telegrinder.tools.i18n.base import ABCTranslator
4
3
 
5
4
  from .abc import ABCRule, with_caching_translations
@@ -18,7 +17,7 @@ class Text(ABCRule):
18
17
  self.texts = texts if not ignore_case else list(map(str.lower, texts))
19
18
  self.ignore_case = ignore_case
20
19
 
21
- async def check(self, text: node.text.Text, ctx: Context) -> bool:
20
+ async def check(self, text: node.text.Text) -> bool:
22
21
  return (text if not self.ignore_case else text.lower()) in self.texts
23
22
 
24
23
  @with_caching_translations
@@ -1,5 +1,4 @@
1
1
  from telegrinder.bot.cute_types.update import UpdateCute
2
- from telegrinder.bot.dispatch.context import Context
3
2
  from telegrinder.types.enums import UpdateType
4
3
 
5
4
  from .abc import ABCRule
@@ -9,7 +8,7 @@ class IsUpdateType(ABCRule):
9
8
  def __init__(self, update_type: UpdateType, /) -> None:
10
9
  self.update_type = update_type
11
10
 
12
- async def check(self, event: UpdateCute, ctx: Context) -> bool:
11
+ async def check(self, event: UpdateCute) -> bool:
13
12
  return event.update_type == self.update_type
14
13
 
15
14
 
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
4
4
  from telegrinder.bot.cute_types.base import BaseCute
5
5
 
6
6
  if typing.TYPE_CHECKING:
7
- from telegrinder.api import ABCAPI
7
+ from telegrinder.api import API
8
8
  from telegrinder.bot.dispatch.view.abc import ABCStateView
9
9
 
10
10
  EventT = typing.TypeVar("EventT", bound=BaseCute)
@@ -12,7 +12,7 @@ EventT = typing.TypeVar("EventT", bound=BaseCute)
12
12
 
13
13
  class ABCScenario(ABC, typing.Generic[EventT]):
14
14
  @abstractmethod
15
- def wait(self, api: "ABCAPI", view: "ABCStateView[EventT]") -> typing.Any:
15
+ def wait(self, api: "API", view: "ABCStateView[EventT]") -> typing.Any:
16
16
  pass
17
17
 
18
18
 
@@ -4,18 +4,17 @@ import typing
4
4
 
5
5
  from telegrinder.bot.cute_types import CallbackQueryCute
6
6
  from telegrinder.bot.dispatch.waiter_machine import WaiterMachine
7
+ from telegrinder.bot.scenario.abc import ABCScenario
7
8
  from telegrinder.tools import InlineButton, InlineKeyboard
8
9
  from telegrinder.tools.parse_mode import ParseMode
9
10
  from telegrinder.types.objects import InlineKeyboardMarkup
10
11
 
11
- from .abc import ABCScenario
12
-
13
12
  if typing.TYPE_CHECKING:
14
13
  from telegrinder.api import API
15
14
  from telegrinder.bot.dispatch.view.abc import BaseStateView
16
15
 
17
16
 
18
- @dataclasses.dataclass
17
+ @dataclasses.dataclass(slots=True)
19
18
  class Choice:
20
19
  name: str
21
20
  is_picked: bool
@@ -114,7 +113,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
114
113
  api: "API",
115
114
  view: "BaseStateView[CallbackQueryCute]",
116
115
  ) -> tuple[dict[str, bool], int]:
117
- assert len(self.choices) > 1
116
+ assert len(self.choices) > 0
118
117
  message = (
119
118
  await api.send_message(
120
119
  chat_id=self.chat_id,
@@ -1,8 +1,7 @@
1
1
  import typing
2
2
 
3
3
  from telegrinder.bot.cute_types import CallbackQueryCute
4
-
5
- from .checkbox import Checkbox
4
+ from telegrinder.bot.scenario.checkbox import Checkbox
6
5
 
7
6
  if typing.TYPE_CHECKING:
8
7
  from telegrinder.api import API
telegrinder/model.py CHANGED
@@ -9,14 +9,13 @@ from types import NoneType
9
9
  import msgspec
10
10
  from fntypes.co import Nothing, Result, Some
11
11
 
12
- from .msgspec_utils import decoder, encoder, get_origin
12
+ from telegrinder.msgspec_utils import decoder, encoder, get_origin
13
13
 
14
14
  if typing.TYPE_CHECKING:
15
15
  from telegrinder.api.error import APIError
16
16
 
17
17
  T = typing.TypeVar("T")
18
18
 
19
-
20
19
  MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
21
20
  "omit_defaults": True,
22
21
  "dict": True,
@@ -59,49 +58,85 @@ def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
59
58
  return validated_params
60
59
 
61
60
 
61
+ def encode_name(name: str) -> str:
62
+ # TODO
63
+ return MODEL_CONFIG.get("rename", {}).get(name, name)
64
+
65
+
62
66
  class Model(msgspec.Struct, **MODEL_CONFIG):
67
+ @classmethod
68
+ def from_data(cls, data: dict[str, typing.Any]) -> typing.Self:
69
+ return decoder.convert(data, type=cls)
70
+
63
71
  @classmethod
64
72
  def from_bytes(cls, data: bytes) -> typing.Self:
65
73
  return decoder.decode(data, type=cls)
66
74
 
75
+ def _to_dict(
76
+ self,
77
+ dct_name: str,
78
+ exclude_fields: set[str],
79
+ full: bool,
80
+ ) -> dict[str, typing.Any]:
81
+ if dct_name not in self.__dict__:
82
+ self.__dict__[dct_name] = (
83
+ msgspec.structs.asdict(self)
84
+ if not full
85
+ else encoder.to_builtins(self.to_dict(exclude_fields=exclude_fields), order="deterministic")
86
+ )
87
+
88
+ if not exclude_fields:
89
+ return self.__dict__[dct_name]
90
+
91
+ return {key: value for key, value in self.__dict__[dct_name].items() if key not in exclude_fields}
92
+
67
93
  def to_dict(
68
94
  self,
69
95
  *,
70
96
  exclude_fields: set[str] | None = None,
71
97
  ) -> dict[str, typing.Any]:
72
- exclude_fields = exclude_fields or set()
73
- if "model_as_dict" not in self.__dict__:
74
- self.__dict__["model_as_dict"] = msgspec.structs.asdict(self)
75
- return {
76
- key: value for key, value in self.__dict__["model_as_dict"].items() if key not in exclude_fields
77
- }
98
+ """
99
+ :param exclude_fields: Model field names to exclude from the dictionary representation of this model.
100
+ :return: A dictionary representation of this model.
101
+ """
78
102
 
103
+ return self._to_dict("model_as_dict", exclude_fields or set(), full=False)
79
104
 
80
- @dataclasses.dataclass(kw_only=True)
105
+ def to_full_dict(
106
+ self,
107
+ *,
108
+ exclude_fields: set[str] | None = None,
109
+ ) -> dict[str, typing.Any]:
110
+ """
111
+ :param exclude_fields: Model field names to exclude from the dictionary representation of this model.
112
+ :return: A dictionary representation of this model including all models, structs, custom types.
113
+ """
114
+
115
+ return self._to_dict("model_as_full_dict", exclude_fields or set(), full=True)
116
+
117
+
118
+ @dataclasses.dataclass(kw_only=True, frozen=True, slots=True, repr=False)
81
119
  class DataConverter:
82
- files: dict[str, tuple[str, bytes]] = dataclasses.field(default_factory=lambda: {})
120
+ _converters: dict[type[typing.Any], typing.Callable[..., typing.Any]] = dataclasses.field(
121
+ init=False,
122
+ default_factory=lambda: {},
123
+ )
124
+ _files: dict[str, tuple[str, bytes]] = dataclasses.field(default_factory=lambda: {})
83
125
 
84
126
  def __repr__(self) -> str:
85
127
  return "<{}: {}>".format(
86
128
  self.__class__.__name__,
87
- ", ".join(f"{k}={v.__name__!r}" for k, v in self.converters.items()),
129
+ ", ".join(f"{k}={v.__name__!r}" for k, v in self._converters.items()),
88
130
  )
89
131
 
90
- @property
91
- def converters(self) -> dict[type[typing.Any], typing.Callable[..., typing.Any]]:
92
- return {
93
- get_origin(value.__annotations__["data"]): value
94
- for key, value in vars(self.__class__).items()
95
- if key.startswith("convert_") and callable(value)
96
- }
97
-
98
- @staticmethod
99
- def convert_enum(data: enum.Enum, _: bool = True) -> typing.Any:
100
- return data.value
101
-
102
- @staticmethod
103
- def convert_datetime(data: datetime, _: bool = True) -> int:
104
- return int(data.timestamp())
132
+ def __post_init__(self) -> None:
133
+ self._converters.update(
134
+ {
135
+ get_origin(value.__annotations__["data"]): value
136
+ for key, value in vars(self.__class__).items()
137
+ if key.startswith("convert_") and callable(value)
138
+ }
139
+ )
105
140
 
106
141
  def __call__(self, data: typing.Any, *, serialize: bool = True) -> typing.Any:
107
142
  converter = self.get_converter(get_origin(type(data)))
@@ -111,9 +146,25 @@ class DataConverter:
111
146
  return converter(self, data, serialize)
112
147
  return data
113
148
 
149
+ @property
150
+ def converters(self) -> dict[type[typing.Any], typing.Callable[..., typing.Any]]:
151
+ return self._converters.copy()
152
+
153
+ @property
154
+ def files(self) -> dict[str, tuple[str, bytes]]:
155
+ return self._files.copy()
156
+
157
+ @staticmethod
158
+ def convert_enum(data: enum.Enum, _: bool = False) -> typing.Any:
159
+ return data.value
160
+
161
+ @staticmethod
162
+ def convert_datetime(data: datetime, _: bool = False) -> int:
163
+ return int(data.timestamp())
164
+
114
165
  def get_converter(self, t: type[typing.Any]):
115
- for type, converter in self.converters.items():
116
- if issubclass(t, type):
166
+ for type_, converter in self._converters.items():
167
+ if issubclass(t, type_):
117
168
  return converter
118
169
  return None
119
170
 
@@ -142,25 +193,18 @@ class DataConverter:
142
193
  converted_lst = [self(x, serialize=False) for x in data]
143
194
  return encoder.encode(converted_lst) if serialize is True else converted_lst
144
195
 
145
- def convert_tpl(
146
- self,
147
- data: tuple[typing.Any, ...],
148
- _: bool = True,
149
- ) -> str | tuple[typing.Any, ...]:
150
- if (
151
- isinstance(data, tuple)
152
- and len(data) == 2
153
- and isinstance(data[0], str)
154
- and isinstance(data[1], bytes)
155
- ):
156
- attach_name = secrets.token_urlsafe(16)
157
- self.files[attach_name] = data
158
- return "attach://{}".format(attach_name)
196
+ def convert_tpl(self, data: tuple[typing.Any, ...], _: bool = False) -> str | tuple[typing.Any, ...]:
197
+ match data:
198
+ case (str(filename), bytes(content)):
199
+ attach_name = secrets.token_urlsafe(16)
200
+ self._files[attach_name] = (filename, content)
201
+ return "attach://{}".format(attach_name)
202
+
159
203
  return data
160
204
 
161
205
 
162
206
  class Proxy:
163
- def __init__(self, cfg: "_ProxiedDict", key: str):
207
+ def __init__(self, cfg: "_ProxiedDict", key: str) -> None:
164
208
  self.key = key
165
209
  self.cfg = cfg
166
210
 
@@ -193,11 +237,11 @@ else:
193
237
 
194
238
 
195
239
  __all__ = (
196
- "Proxy",
197
240
  "DataConverter",
198
- "ProxiedDict",
199
241
  "MODEL_CONFIG",
200
242
  "Model",
243
+ "ProxiedDict",
244
+ "Proxy",
201
245
  "full_result",
202
246
  "get_params",
203
247
  )
telegrinder/modules.py CHANGED
@@ -108,8 +108,8 @@ elif logging_module == "logging":
108
108
  "level": "green",
109
109
  "level_module": "blue",
110
110
  "level_func": "cyan",
111
- "level_lineno": "green",
112
- "level_message": "white",
111
+ "level_lineno": "white",
112
+ "level_message": "green",
113
113
  },
114
114
  "DEBUG": {
115
115
  "level": "blue",
@@ -232,7 +232,7 @@ def _set_logger_level(level):
232
232
  if logging_module == "logging":
233
233
  import logging
234
234
 
235
- logging.getLogger("telegrinder").setLevel(logging.getLevelName(level))
235
+ logging.getLogger("telegrinder").setLevel(level)
236
236
  elif logging_module == "loguru":
237
237
  import loguru # type: ignore
238
238