telegrinder 0.4.2__py3-none-any.whl → 0.5.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 (233) hide show
  1. telegrinder/__init__.py +37 -55
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +6 -4
  4. telegrinder/api/api.py +100 -26
  5. telegrinder/api/error.py +42 -8
  6. telegrinder/api/response.py +4 -1
  7. telegrinder/api/token.py +2 -2
  8. telegrinder/bot/__init__.py +9 -25
  9. telegrinder/bot/bot.py +31 -25
  10. telegrinder/bot/cute_types/__init__.py +0 -0
  11. telegrinder/bot/cute_types/base.py +103 -61
  12. telegrinder/bot/cute_types/callback_query.py +447 -400
  13. telegrinder/bot/cute_types/chat_join_request.py +59 -62
  14. telegrinder/bot/cute_types/chat_member_updated.py +154 -157
  15. telegrinder/bot/cute_types/inline_query.py +41 -44
  16. telegrinder/bot/cute_types/message.py +2621 -2590
  17. telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
  18. telegrinder/bot/cute_types/update.py +1 -8
  19. telegrinder/bot/cute_types/utils.py +1 -1
  20. telegrinder/bot/dispatch/__init__.py +10 -15
  21. telegrinder/bot/dispatch/abc.py +12 -11
  22. telegrinder/bot/dispatch/action.py +104 -0
  23. telegrinder/bot/dispatch/context.py +32 -26
  24. telegrinder/bot/dispatch/dispatch.py +61 -134
  25. telegrinder/bot/dispatch/handler/__init__.py +2 -0
  26. telegrinder/bot/dispatch/handler/abc.py +10 -8
  27. telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
  28. telegrinder/bot/dispatch/handler/base.py +10 -33
  29. telegrinder/bot/dispatch/handler/document_reply.py +2 -3
  30. telegrinder/bot/dispatch/handler/func.py +55 -87
  31. telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
  32. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  33. telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
  34. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
  35. telegrinder/bot/dispatch/handler/video_reply.py +2 -3
  36. telegrinder/bot/dispatch/middleware/__init__.py +0 -0
  37. telegrinder/bot/dispatch/middleware/abc.py +79 -55
  38. telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
  39. telegrinder/bot/dispatch/process.py +84 -105
  40. telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
  41. telegrinder/bot/dispatch/return_manager/abc.py +102 -65
  42. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
  43. telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
  44. telegrinder/bot/dispatch/return_manager/message.py +8 -10
  45. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
  46. telegrinder/bot/dispatch/view/__init__.py +4 -4
  47. telegrinder/bot/dispatch/view/abc.py +6 -16
  48. telegrinder/bot/dispatch/view/base.py +54 -178
  49. telegrinder/bot/dispatch/view/box.py +19 -18
  50. telegrinder/bot/dispatch/view/callback_query.py +4 -8
  51. telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
  52. telegrinder/bot/dispatch/view/chat_member.py +5 -25
  53. telegrinder/bot/dispatch/view/error.py +9 -0
  54. telegrinder/bot/dispatch/view/inline_query.py +4 -8
  55. telegrinder/bot/dispatch/view/message.py +5 -25
  56. telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
  57. telegrinder/bot/dispatch/view/raw.py +3 -109
  58. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
  59. telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
  60. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
  61. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
  62. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
  63. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  64. telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
  65. telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
  66. telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
  67. telegrinder/bot/polling/__init__.py +0 -0
  68. telegrinder/bot/polling/abc.py +0 -0
  69. telegrinder/bot/polling/polling.py +209 -88
  70. telegrinder/bot/rules/__init__.py +3 -16
  71. telegrinder/bot/rules/abc.py +42 -122
  72. telegrinder/bot/rules/callback_data.py +29 -49
  73. telegrinder/bot/rules/chat_join.py +5 -23
  74. telegrinder/bot/rules/command.py +8 -4
  75. telegrinder/bot/rules/enum_text.py +3 -4
  76. telegrinder/bot/rules/func.py +7 -14
  77. telegrinder/bot/rules/fuzzy.py +3 -4
  78. telegrinder/bot/rules/inline.py +8 -20
  79. telegrinder/bot/rules/integer.py +2 -3
  80. telegrinder/bot/rules/is_from.py +12 -11
  81. telegrinder/bot/rules/logic.py +11 -5
  82. telegrinder/bot/rules/markup.py +22 -14
  83. telegrinder/bot/rules/mention.py +8 -7
  84. telegrinder/bot/rules/message_entities.py +8 -4
  85. telegrinder/bot/rules/node.py +23 -12
  86. telegrinder/bot/rules/payload.py +5 -4
  87. telegrinder/bot/rules/payment_invoice.py +6 -21
  88. telegrinder/bot/rules/regex.py +2 -4
  89. telegrinder/bot/rules/rule_enum.py +8 -7
  90. telegrinder/bot/rules/start.py +5 -6
  91. telegrinder/bot/rules/state.py +1 -1
  92. telegrinder/bot/rules/text.py +4 -15
  93. telegrinder/bot/rules/update.py +3 -4
  94. telegrinder/bot/scenario/__init__.py +0 -0
  95. telegrinder/bot/scenario/abc.py +6 -5
  96. telegrinder/bot/scenario/checkbox.py +1 -1
  97. telegrinder/bot/scenario/choice.py +30 -39
  98. telegrinder/client/__init__.py +3 -5
  99. telegrinder/client/abc.py +11 -6
  100. telegrinder/client/aiohttp.py +141 -27
  101. telegrinder/client/form_data.py +1 -1
  102. telegrinder/model.py +61 -89
  103. telegrinder/modules.py +325 -102
  104. telegrinder/msgspec_utils/__init__.py +40 -0
  105. telegrinder/msgspec_utils/abc.py +18 -0
  106. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  107. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  108. telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
  109. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  110. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  111. telegrinder/msgspec_utils/decoder.py +389 -0
  112. telegrinder/msgspec_utils/encoder.py +206 -0
  113. telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
  114. telegrinder/msgspec_utils/tools.py +75 -0
  115. telegrinder/node/__init__.py +24 -7
  116. telegrinder/node/attachment.py +1 -0
  117. telegrinder/node/base.py +154 -72
  118. telegrinder/node/callback_query.py +5 -5
  119. telegrinder/node/collection.py +39 -0
  120. telegrinder/node/command.py +1 -2
  121. telegrinder/node/composer.py +121 -72
  122. telegrinder/node/container.py +11 -8
  123. telegrinder/node/context.py +48 -0
  124. telegrinder/node/either.py +27 -40
  125. telegrinder/node/error.py +41 -0
  126. telegrinder/node/event.py +37 -11
  127. telegrinder/node/exceptions.py +7 -0
  128. telegrinder/node/file.py +0 -0
  129. telegrinder/node/i18n.py +108 -0
  130. telegrinder/node/me.py +3 -2
  131. telegrinder/node/payload.py +1 -1
  132. telegrinder/node/polymorphic.py +63 -28
  133. telegrinder/node/reply_message.py +12 -0
  134. telegrinder/node/rule.py +6 -13
  135. telegrinder/node/scope.py +14 -5
  136. telegrinder/node/session.py +53 -0
  137. telegrinder/node/source.py +41 -9
  138. telegrinder/node/text.py +1 -2
  139. telegrinder/node/tools/__init__.py +0 -0
  140. telegrinder/node/tools/generator.py +3 -5
  141. telegrinder/node/utility.py +16 -0
  142. telegrinder/py.typed +0 -0
  143. telegrinder/rules.py +0 -0
  144. telegrinder/tools/__init__.py +48 -88
  145. telegrinder/tools/aio.py +103 -0
  146. telegrinder/tools/callback_data_serialization/__init__.py +5 -0
  147. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
  148. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
  149. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
  150. telegrinder/tools/final.py +21 -0
  151. telegrinder/tools/formatting/__init__.py +2 -18
  152. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  153. telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
  154. telegrinder/tools/formatting/deep_links/parsing.py +90 -0
  155. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  156. telegrinder/tools/formatting/html_formatter.py +18 -45
  157. telegrinder/tools/fullname.py +83 -0
  158. telegrinder/tools/global_context/__init__.py +4 -3
  159. telegrinder/tools/global_context/abc.py +17 -14
  160. telegrinder/tools/global_context/builtin_context.py +39 -0
  161. telegrinder/tools/global_context/global_context.py +138 -39
  162. telegrinder/tools/input_file_directory.py +0 -0
  163. telegrinder/tools/keyboard/__init__.py +39 -0
  164. telegrinder/tools/keyboard/abc.py +159 -0
  165. telegrinder/tools/keyboard/base.py +77 -0
  166. telegrinder/tools/keyboard/buttons/__init__.py +14 -0
  167. telegrinder/tools/keyboard/buttons/base.py +18 -0
  168. telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
  169. telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
  170. telegrinder/tools/keyboard/buttons/tools.py +18 -0
  171. telegrinder/tools/keyboard/data.py +20 -0
  172. telegrinder/tools/keyboard/keyboard.py +131 -0
  173. telegrinder/tools/keyboard/static_keyboard.py +83 -0
  174. telegrinder/tools/lifespan.py +87 -51
  175. telegrinder/tools/limited_dict.py +4 -1
  176. telegrinder/tools/loop_wrapper.py +332 -0
  177. telegrinder/tools/magic/__init__.py +32 -0
  178. telegrinder/tools/magic/annotations.py +165 -0
  179. telegrinder/tools/magic/dictionary.py +20 -0
  180. telegrinder/tools/magic/function.py +246 -0
  181. telegrinder/tools/magic/shortcut.py +111 -0
  182. telegrinder/tools/parse_mode.py +9 -3
  183. telegrinder/tools/singleton/__init__.py +4 -0
  184. telegrinder/tools/singleton/abc.py +14 -0
  185. telegrinder/tools/singleton/singleton.py +18 -0
  186. telegrinder/tools/state_storage/__init__.py +0 -0
  187. telegrinder/tools/state_storage/abc.py +6 -1
  188. telegrinder/tools/state_storage/memory.py +1 -1
  189. telegrinder/tools/strings.py +0 -0
  190. telegrinder/types/__init__.py +307 -268
  191. telegrinder/types/enums.py +64 -37
  192. telegrinder/types/input_file.py +3 -3
  193. telegrinder/types/methods.py +5699 -5055
  194. telegrinder/types/methods_utils.py +62 -0
  195. telegrinder/types/objects.py +7846 -7058
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.0.dist-info/METADATA +162 -0
  198. telegrinder-0.5.0.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.dist-info}/licenses/LICENSE +2 -2
  200. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
  201. telegrinder/bot/rules/id.py +0 -24
  202. telegrinder/bot/rules/message.py +0 -15
  203. telegrinder/client/sonic.py +0 -212
  204. telegrinder/msgspec_utils.py +0 -478
  205. telegrinder/tools/adapter/__init__.py +0 -19
  206. telegrinder/tools/adapter/abc.py +0 -49
  207. telegrinder/tools/adapter/dataclass.py +0 -56
  208. telegrinder/tools/adapter/errors.py +0 -5
  209. telegrinder/tools/adapter/event.py +0 -61
  210. telegrinder/tools/adapter/node.py +0 -46
  211. telegrinder/tools/adapter/raw_event.py +0 -27
  212. telegrinder/tools/adapter/raw_update.py +0 -30
  213. telegrinder/tools/callback_data_serilization/__init__.py +0 -5
  214. telegrinder/tools/error_handler/__init__.py +0 -10
  215. telegrinder/tools/error_handler/abc.py +0 -30
  216. telegrinder/tools/error_handler/error.py +0 -9
  217. telegrinder/tools/error_handler/error_handler.py +0 -179
  218. telegrinder/tools/formatting/spec_html_formats.py +0 -75
  219. telegrinder/tools/functional.py +0 -8
  220. telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
  221. telegrinder/tools/i18n/__init__.py +0 -12
  222. telegrinder/tools/i18n/abc.py +0 -32
  223. telegrinder/tools/i18n/middleware/__init__.py +0 -3
  224. telegrinder/tools/i18n/middleware/abc.py +0 -22
  225. telegrinder/tools/i18n/simple.py +0 -43
  226. telegrinder/tools/keyboard.py +0 -132
  227. telegrinder/tools/loop_wrapper/__init__.py +0 -4
  228. telegrinder/tools/loop_wrapper/abc.py +0 -20
  229. telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
  230. telegrinder/tools/magic.py +0 -344
  231. telegrinder-0.4.2.dist-info/METADATA +0 -151
  232. telegrinder-0.4.2.dist-info/RECORD +0 -182
  233. {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.dist-info}/WHEEL +0 -0
telegrinder/modules.py CHANGED
@@ -3,22 +3,39 @@ import typing
3
3
 
4
4
  from choicelib import choice_in_order
5
5
 
6
- import telegrinder.msgspec_json as json
6
+ from telegrinder.msgspec_utils import json
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from logging import Handler as LoggingBasicHandler
10
+
11
+ from loguru import AsyncHandlerConfig as LoguruAsyncHandlerConfig # type: ignore
12
+ from loguru import BasicHandlerConfig as LoguruBasicHandlerConfig # type: ignore
13
+ from loguru import FileHandlerConfig as LoguruFileHandlerConfig # type: ignore
14
+ from loguru import HandlerConfig as LoguruHandlerConfig # type: ignore
15
+ else:
16
+ LoguruAsyncHandlerConfig = LoguruBasicHandlerConfig = LoguruFileHandlerConfig = dict
17
+
18
+ type _LoggerHandler = LoggingBasicHandler | LoguruHandlerConfig
19
+
20
+
21
+ def _remove_handlers(logger: typing.Any, /) -> None:
22
+ for hdlr in logger.handlers[:]:
23
+ logger.removeHandler(hdlr)
7
24
 
8
25
 
9
26
  @typing.runtime_checkable
10
27
  class LoggerModule(typing.Protocol):
11
- def debug(self, __msg: object, *args: object, **kwargs: object) -> None: ...
28
+ def debug(self, __msg: typing.Any, *args: typing.Any, **kwargs: typing.Any) -> None: ...
12
29
 
13
- def info(self, __msg: object, *args: object, **kwargs: object) -> None: ...
30
+ def info(self, __msg: typing.Any, *args: typing.Any, **kwargs: typing.Any) -> None: ...
14
31
 
15
- def warning(self, __msg: object, *args: object, **kwargs: object) -> None: ...
32
+ def warning(self, __msg: typing.Any, *args: typing.Any, **kwargs: typing.Any) -> None: ...
16
33
 
17
- def error(self, __msg: object, *args: object, **kwargs: object) -> None: ...
34
+ def error(self, __msg: typing.Any, *args: typing.Any, **kwargs: typing.Any) -> None: ...
18
35
 
19
- def critical(self, __msg: object, *args: object, **kwargs: object) -> None: ...
36
+ def critical(self, __msg: typing.Any, *args: typing.Any, **kwargs: typing.Any) -> None: ...
20
37
 
21
- def exception(self, __msg: object, *args: object, **kwargs: object) -> None: ...
38
+ def exception(self, __msg: typing.Any, *args: typing.Any, **kwargs: typing.Any) -> None: ...
22
39
 
23
40
  if typing.TYPE_CHECKING:
24
41
 
@@ -35,11 +52,14 @@ class LoggerModule(typing.Protocol):
35
52
  /,
36
53
  ) -> None: ...
37
54
 
55
+ def set_new_handler(self, new_handler: _LoggerHandler, /) -> None: ...
56
+
38
57
 
39
58
  logger: LoggerModule
40
59
  logging_level = os.getenv("LOGGER_LEVEL", default="DEBUG").upper()
41
- logging_module = choice_in_order(["loguru"], default="logging", do_import=False)
42
- asyncio_module = choice_in_order(["uvloop"], default="asyncio", do_import=False)
60
+ logging_module = choice_in_order(["structlog", "loguru"], default="logging", do_import=False)
61
+ asyncio_module = choice_in_order(["uvloop", "winloop"], default="asyncio", do_import=False)
62
+
43
63
 
44
64
  if logging_module == "loguru":
45
65
  import os
@@ -63,6 +83,166 @@ if logging_module == "loguru":
63
83
  level=logging_level,
64
84
  )
65
85
 
86
+ elif logging_module == "structlog":
87
+ import logging
88
+ import re
89
+ import sys
90
+ import typing
91
+ from contextlib import suppress
92
+
93
+ import colorama
94
+ import structlog # type: ignore
95
+
96
+ LEVELS_COLORS = dict(
97
+ debug=colorama.Fore.LIGHTBLUE_EX,
98
+ info=colorama.Fore.LIGHTGREEN_EX,
99
+ warning=colorama.Fore.LIGHTYELLOW_EX,
100
+ error=colorama.Fore.LIGHTRED_EX,
101
+ critical=colorama.Fore.LIGHTRED_EX,
102
+ )
103
+
104
+ class SLF4JStyleFormatter:
105
+ TOKENS = frozenset(("{}", "{!s}", "{!r}", "{!a}"))
106
+ TOKENS_PATTERN = re.compile(r"({!r}|{})")
107
+
108
+ def __init__(self, *, remove_positional_args: bool = True) -> None:
109
+ self.remove_positional_args = remove_positional_args
110
+
111
+ def __call__(
112
+ self,
113
+ logger: typing.Any,
114
+ method_name: str,
115
+ event_dict: dict[str, typing.Any],
116
+ ) -> dict[str, typing.Any]:
117
+ args = event_dict.get("positional_args")
118
+ if not args:
119
+ return event_dict
120
+
121
+ event = event_dict.get("event", "")
122
+ if not isinstance(event, str):
123
+ return event_dict
124
+
125
+ log_level = event_dict.get("level", "debug")
126
+ with suppress(TypeError, ValueError, IndexError):
127
+ if "{}" in event or "{!r}" in event:
128
+ event_dict["event"] = self._safe_format_braces(event, args, log_level)
129
+ elif len(args) == 1 and isinstance(args[0], dict):
130
+ formatted = event % args[0]
131
+ event_dict["event"] = self._highlight_values(
132
+ formatted, args[0].values(), log_level, percent_style=True
133
+ )
134
+ else:
135
+ formatted = event % args
136
+ event_dict["event"] = self._highlight_values(formatted, args, log_level, percent_style=True)
137
+
138
+ if self.remove_positional_args and "positional_args" in event_dict:
139
+ del event_dict["positional_args"]
140
+
141
+ return event_dict
142
+
143
+ def _colorize(self, value: typing.Any, log_level: str) -> str:
144
+ return f"{LEVELS_COLORS[log_level]}{value}{colorama.Fore.RESET}"
145
+
146
+ def _safe_format_braces(
147
+ self,
148
+ message: str,
149
+ args: tuple[typing.Any, ...],
150
+ log_level: str,
151
+ ) -> str:
152
+ tokens = self.TOKENS_PATTERN.split(message)
153
+ result = []
154
+ arg_index = 0
155
+
156
+ for token in tokens:
157
+ if token in self.TOKENS and arg_index < len(args):
158
+ result.append(
159
+ self._colorize(
160
+ str(args[arg_index]) if token != "{!r}" else repr(args[arg_index]),
161
+ log_level,
162
+ ),
163
+ )
164
+ arg_index += 1
165
+ else:
166
+ result.append(token)
167
+
168
+ return "".join(result)
169
+
170
+ def _highlight_values(
171
+ self,
172
+ full_message: str,
173
+ values: typing.Iterable[typing.Any],
174
+ log_level: str,
175
+ percent_style: bool = False,
176
+ ) -> str:
177
+ for v in values:
178
+ with suppress(Exception):
179
+ raw = repr(v) if percent_style and ("%r" in full_message) else str(v)
180
+ pattern = re.compile(rf"(?<!%)\b{re.escape(raw)}\b")
181
+ full_message = pattern.sub(
182
+ lambda m: self._colorize(m.group(0), log_level), full_message, count=1
183
+ )
184
+
185
+ return full_message
186
+
187
+ class LogLevelColumnFormatter:
188
+ def __call__(self, key: str, value: typing.Any) -> str:
189
+ color = LEVELS_COLORS[value]
190
+ return f"[{color}{value:^12}{colorama.Fore.RESET}]"
191
+
192
+ class Filter(logging.Filter):
193
+ def filter(self, record: logging.LogRecord) -> bool:
194
+ level_color = LEVELS_COLORS[record.levelname.lower()]
195
+ location = (
196
+ f"{colorama.Fore.LIGHTCYAN_EX}{record.module}{colorama.Fore.RESET}:"
197
+ f"{level_color}{record.funcName}{colorama.Fore.RESET}:"
198
+ f"{colorama.Fore.LIGHTMAGENTA_EX}{record.lineno}{colorama.Fore.RESET} "
199
+ )
200
+ record.location = location
201
+ return True
202
+
203
+ def configure_logging() -> None:
204
+ console_renderer = structlog.dev.ConsoleRenderer(colors=True)
205
+
206
+ for column in console_renderer._columns:
207
+ if column.key == "level":
208
+ column.formatter = LogLevelColumnFormatter()
209
+ break
210
+
211
+ structlog.configure( # type: ignore
212
+ processors=[
213
+ structlog.stdlib.filter_by_level,
214
+ structlog.stdlib.add_log_level,
215
+ SLF4JStyleFormatter(), # type: ignore
216
+ structlog.processors.StackInfoRenderer(),
217
+ structlog.processors.format_exc_info,
218
+ structlog.processors.UnicodeDecoder(),
219
+ console_renderer,
220
+ ],
221
+ context_class=dict,
222
+ logger_factory=structlog.stdlib.LoggerFactory(),
223
+ wrapper_class=structlog.stdlib.BoundLogger,
224
+ cache_logger_on_first_use=True,
225
+ )
226
+ fmt = (
227
+ f"[{colorama.Fore.LIGHTBLUE_EX}{{name}}{colorama.Style.RESET_ALL}] "
228
+ f"{colorama.Fore.LIGHTWHITE_EX}{{location}}{colorama.Style.RESET_ALL}"
229
+ f"[{colorama.Fore.LIGHTBLACK_EX}{{asctime}}{colorama.Style.RESET_ALL}] "
230
+ f"{colorama.Fore.LIGHTWHITE_EX}~{colorama.Style.RESET_ALL} {{message}}"
231
+ )
232
+
233
+ telegrinder_logger = logging.getLogger("telegrinder")
234
+ telegrinder_logger.setLevel(logging_level)
235
+
236
+ handler = logging.StreamHandler(sys.stderr)
237
+ handler.addFilter(Filter())
238
+ formatter = logging.Formatter(fmt, datefmt="%Y-%m-%d %H:%M:%S", style="{")
239
+ handler.setFormatter(formatter)
240
+ _remove_handlers(telegrinder_logger)
241
+ telegrinder_logger.addHandler(handler)
242
+
243
+ configure_logging()
244
+ logger = structlog.get_logger("telegrinder") # type: ignore
245
+
66
246
  elif logging_module == "logging":
67
247
  """
68
248
  This is a workaround for lazy formatting with {} in logging.
@@ -76,99 +256,109 @@ elif logging_module == "logging":
76
256
 
77
257
  import colorama
78
258
 
79
- colorama.just_fix_windows_console() # init & fix console
259
+ colorama.just_fix_windows_console()
260
+ colorama.init()
80
261
 
81
- FORMAT = (
82
- "<white>{name: <4} |</white> <level>{levelname: <8}</level>"
83
- " <white>|</white> <green>{asctime}</green> <white>|</white> <level_module>"
84
- "{module}</level_module><white>:</white><level_func>"
85
- "{funcName}</level_func><white>:</white><level_lineno>"
86
- "{lineno}</level_lineno><white> > </white><level_message>"
87
- "{message}</level_message>"
262
+ LOG_FORMAT = (
263
+ "<light_white>{name: <4} |</light_white> <level>{levelname: <8}</level>"
264
+ " <light_white>|</light_white> <light_green>{asctime}</light_green> <light_white>"
265
+ "|</light_white> <level_module>{module}</level_module><light_white>:</light_white>"
266
+ "<func_name>{funcName}</func_name><light_white>:</light_white><lineno>{lineno}</lineno>"
267
+ " <light_white>></light_white> <message>{message}</message>"
268
+ )
269
+ COLORS = dict(
270
+ reset=colorama.Style.RESET_ALL,
271
+ red=colorama.Fore.RED,
272
+ green=colorama.Fore.GREEN,
273
+ blue=colorama.Fore.BLUE,
274
+ white=colorama.Fore.WHITE,
275
+ yellow=colorama.Fore.YELLOW,
276
+ magenta=colorama.Fore.MAGENTA,
277
+ cyan=colorama.Fore.CYAN,
278
+ light_red=colorama.Fore.LIGHTRED_EX,
279
+ light_green=colorama.Fore.LIGHTGREEN_EX,
280
+ light_blue=colorama.Fore.LIGHTBLUE_EX,
281
+ light_white=colorama.Fore.LIGHTWHITE_EX,
282
+ light_yellow=colorama.Fore.LIGHTYELLOW_EX,
283
+ light_magenta=colorama.Fore.LIGHTMAGENTA_EX,
284
+ light_cyan=colorama.Fore.LIGHTCYAN_EX,
285
+ )
286
+ LEVEL_FORMAT_SETTINGS = dict(
287
+ DEBUG=dict(
288
+ level="light_blue",
289
+ level_module="blue",
290
+ func_name="blue",
291
+ lineno="light_yellow",
292
+ message="light_blue",
293
+ ),
294
+ INFO=dict(
295
+ level="cyan",
296
+ level_module="light_cyan",
297
+ func_name="light_cyan",
298
+ lineno="light_yellow",
299
+ message="light_green",
300
+ ),
301
+ WARNING=dict(
302
+ level="light_yellow",
303
+ level_module="light_magenta",
304
+ func_name="light_magenta",
305
+ lineno="light_blue",
306
+ message="light_yellow",
307
+ ),
308
+ ERROR=dict(
309
+ level="red",
310
+ level_module="light_yellow",
311
+ func_name="light_yellow",
312
+ lineno="light_blue",
313
+ message="light_red",
314
+ ),
315
+ CRITICAL=dict(
316
+ level="magenta",
317
+ level_module="light_red",
318
+ func_name="light_red",
319
+ lineno="light_yellow",
320
+ message="light_magenta",
321
+ ),
88
322
  )
89
- COLORS = {
90
- "red": colorama.Fore.LIGHTRED_EX,
91
- "green": colorama.Fore.LIGHTGREEN_EX,
92
- "blue": colorama.Fore.LIGHTBLUE_EX,
93
- "white": colorama.Fore.LIGHTWHITE_EX,
94
- "yellow": colorama.Fore.LIGHTYELLOW_EX,
95
- "magenta": colorama.Fore.LIGHTMAGENTA_EX,
96
- "cyan": colorama.Fore.LIGHTCYAN_EX,
97
- "reset": colorama.Style.RESET_ALL,
98
- }
99
- LEVEL_SETTINGS = {
100
- "INFO": {
101
- "level": "green",
102
- "level_module": "blue",
103
- "level_func": "cyan",
104
- "level_lineno": "white",
105
- "level_message": "green",
106
- },
107
- "DEBUG": {
108
- "level": "blue",
109
- "level_module": "yellow",
110
- "level_func": "green",
111
- "level_lineno": "cyan",
112
- "level_message": "blue",
113
- },
114
- "WARNING": {
115
- "level": "yellow",
116
- "level_module": "red",
117
- "level_func": "green",
118
- "level_lineno": "red",
119
- "level_message": "yellow",
120
- },
121
- "ERROR": {
122
- "level": "red",
123
- "level_module": "magenta",
124
- "level_func": "yellow",
125
- "level_lineno": "green",
126
- "level_message": "red",
127
- },
128
- "CRITICAL": {
129
- "level": "cyan",
130
- "level_module": "yellow",
131
- "level_func": "yellow",
132
- "level_lineno": "yellow",
133
- "level_message": "cyan",
134
- },
135
- }
136
- FORMAT = (
137
- FORMAT.replace("<white>", COLORS["white"])
138
- .replace("</white>", COLORS["reset"])
139
- .replace("<green>", COLORS["green"])
140
- .replace("</green>", COLORS["reset"])
323
+ LOG_FORMAT = (
324
+ LOG_FORMAT.replace("<light_white>", COLORS["light_white"])
325
+ .replace("</light_white>", COLORS["reset"])
326
+ .replace("<light_green>", COLORS["light_green"])
327
+ .replace("</light_green>", COLORS["reset"])
141
328
  )
142
- LEVEL_FORMATS: dict[str, str] = {}
143
- for level, settings in LEVEL_SETTINGS.items():
144
- fmt = FORMAT
329
+ LEVEL_FORMATS = dict[str, str]()
330
+
331
+ for level, settings in LEVEL_FORMAT_SETTINGS.items():
332
+ fmt = LOG_FORMAT
333
+
145
334
  for name, color in settings.items():
146
335
  fmt = fmt.replace(f"<{name}>", COLORS[color]).replace(f"</{name}>", COLORS["reset"])
336
+
147
337
  LEVEL_FORMATS[level] = fmt
148
338
 
149
339
  class TelegrinderLoggingFormatter(logging.Formatter):
150
340
  def format(self, record: logging.LogRecord) -> str:
151
341
  if not record.funcName or record.funcName == "<module>":
152
342
  record.funcName = "\b"
153
- frame = next(
154
- (
155
- frame
156
- for frame in inspect.stack()
157
- if frame.filename == record.pathname and frame.lineno == record.lineno
158
- ),
159
- None,
160
- )
161
- if frame:
162
- module = inspect.getmodule(frame.frame)
163
- record.module = module.__name__ if module else "<module>"
343
+
344
+ frame = sys._getframe(1)
345
+ while frame:
346
+ if frame.f_code.co_filename == record.pathname and frame.f_lineno == record.lineno:
347
+ break
348
+
349
+ frame = frame.f_back
350
+
351
+ if frame is not None:
352
+ record.module = frame.f_globals.get("__name__", "<module>")
353
+
164
354
  return logging.Formatter(
165
- LEVEL_FORMATS.get(record.levelname),
355
+ fmt=LEVEL_FORMATS.get(record.levelname),
166
356
  datefmt="%Y-%m-%d %H:%M:%S",
167
357
  style="{",
168
358
  ).format(record)
169
359
 
170
360
  class LogMessage:
171
- def __init__(self, fmt: typing.Any, args: typing.Any, kwargs: typing.Any) -> None:
361
+ def __init__(self, fmt: str, args: typing.Any, kwargs: typing.Any) -> None:
172
362
  self.fmt = fmt
173
363
  self.args = args
174
364
  self.kwargs = kwargs
@@ -177,41 +367,44 @@ elif logging_module == "logging":
177
367
  return self.fmt.format(*self.args, **self.kwargs)
178
368
 
179
369
  class TelegrinderLoggingStyleAdapter(logging.LoggerAdapter):
370
+ logger: logging.Logger
371
+
180
372
  def __init__(
181
373
  self,
182
- logger: LoggerModule,
183
- extra: dict[str, typing.Any] | None = None,
374
+ logger: logging.Logger,
375
+ **extra: typing.Any,
184
376
  ) -> None:
185
- super().__init__(logger, extra or {})
377
+ super().__init__(logger, extra=extra)
378
+ self.log_arg_names = frozenset(inspect.getfullargspec(self.logger._log).args[1:])
186
379
 
187
- def log(self, level: int, msg: object, *args: object, **kwargs: object) -> None:
380
+ def log(self, level: int, msg: typing.Any, *args: typing.Any, **kwargs: typing.Any) -> None:
188
381
  if self.isEnabledFor(level):
189
- kwargs.setdefault("stacklevel", 2)
190
382
  msg, args, kwargs = self.proc(msg, args, kwargs)
191
383
  self.logger._log(level, msg, args, **kwargs)
192
384
 
193
385
  def proc(
194
386
  self,
195
- msg: object,
196
- args: tuple[object, ...],
197
- kwargs: dict[str, object],
198
- ) -> tuple[LogMessage | object, tuple[object, ...], dict[str, object]]:
199
- log_kwargs = {
200
- key: kwargs[key] for key in inspect.getfullargspec(self.logger._log).args[1:] if key in kwargs
201
- }
387
+ msg: typing.Any,
388
+ args: tuple[typing.Any, ...],
389
+ kwargs: dict[str, typing.Any],
390
+ ) -> tuple[typing.Any, tuple[typing.Any, ...], dict[str, typing.Any]]:
391
+ kwargs.setdefault("stacklevel", 2)
202
392
 
203
393
  if isinstance(msg, str):
204
394
  msg = LogMessage(msg, args, kwargs)
205
395
  args = tuple()
206
- return msg, args, log_kwargs
396
+
397
+ return msg, args, {name: kwargs[name] for name in self.log_arg_names if name in kwargs}
207
398
 
208
399
  handler = logging.StreamHandler(sys.stderr)
209
400
  handler.setFormatter(TelegrinderLoggingFormatter())
210
401
  logger = logging.getLogger("telegrinder") # type: ignore
402
+ _remove_handlers(logger)
211
403
  logger.setLevel(logging_level) # type: ignore
212
404
  logger.addHandler(handler) # type: ignore
213
405
  logger = TelegrinderLoggingStyleAdapter(logger) # type: ignore
214
406
 
407
+
215
408
  if asyncio_module == "uvloop":
216
409
  import asyncio
217
410
 
@@ -219,10 +412,17 @@ if asyncio_module == "uvloop":
219
412
 
220
413
  asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) # type: ignore
221
414
 
415
+ elif asyncio_module == "winloop":
416
+ import asyncio
417
+
418
+ import winloop # type: ignore
419
+
420
+ asyncio.set_event_loop_policy(winloop.EventLoopPolicy()) # type: ignore
222
421
 
223
- def _set_logger_level(level, /):
422
+
423
+ def _set_logger_level(level: str, /) -> None:
224
424
  level = level.upper()
225
- if logging_module == "logging":
425
+ if logging_module in ("logging", "structlog"):
226
426
  import logging
227
427
 
228
428
  logging.getLogger("telegrinder").setLevel(level)
@@ -233,7 +433,30 @@ def _set_logger_level(level, /):
233
433
  loguru.logger._core.handlers[handler_id]._levelno = loguru.logger.level(level).no # type: ignore
234
434
 
235
435
 
236
- setattr(logger, "set_level", staticmethod(_set_logger_level)) # type: ignore
436
+ def _set_logger_handler(new_handler: typing.Any, /) -> None:
437
+ if logging_module in ("logging", "structlog"):
438
+ import logging
439
+
440
+ telegrinder_logger = logging.getLogger("telegrinder")
441
+ _remove_handlers(telegrinder_logger)
442
+ telegrinder_logger.addHandler(new_handler)
443
+ elif logging_module == "loguru":
444
+ import loguru # type: ignore
445
+
446
+ global handler_id # type: ignore
447
+ loguru.logger.remove(handler_id) # type: ignore
448
+ handler_id = loguru.logger.configure(handlers=(new_handler,))[0] # type: ignore
449
+
450
+
451
+ setattr(logger, "set_level", staticmethod(_set_logger_level))
452
+ setattr(logger, "set_new_handler", staticmethod(_set_logger_handler))
237
453
 
238
454
 
239
- __all__ = ("LoggerModule", "json", "logger")
455
+ __all__ = (
456
+ "LoggerModule",
457
+ "LoguruAsyncHandlerConfig",
458
+ "LoguruBasicHandlerConfig",
459
+ "LoguruFileHandlerConfig",
460
+ "json",
461
+ "logger",
462
+ )
@@ -0,0 +1,40 @@
1
+ from telegrinder.msgspec_utils.abc import SupportsCast, is_supports_cast
2
+ from telegrinder.msgspec_utils.custom_types.datetime import datetime, timedelta
3
+ from telegrinder.msgspec_utils.custom_types.enum_meta import BaseEnumMeta
4
+ from telegrinder.msgspec_utils.custom_types.literal import Literal
5
+ from telegrinder.msgspec_utils.custom_types.option import Option
6
+ from telegrinder.msgspec_utils.decoder import Decoder, convert, decoder
7
+ from telegrinder.msgspec_utils.encoder import Encoder, encoder, to_builtins
8
+ from telegrinder.msgspec_utils.json import dumps, loads
9
+ from telegrinder.msgspec_utils.tools import (
10
+ get_class_annotations,
11
+ get_origin,
12
+ get_type_hints,
13
+ is_common_type,
14
+ struct_asdict,
15
+ type_check,
16
+ )
17
+
18
+ __all__ = (
19
+ "BaseEnumMeta",
20
+ "Decoder",
21
+ "Encoder",
22
+ "Literal",
23
+ "Option",
24
+ "SupportsCast",
25
+ "convert",
26
+ "datetime",
27
+ "decoder",
28
+ "dumps",
29
+ "encoder",
30
+ "get_class_annotations",
31
+ "get_origin",
32
+ "get_type_hints",
33
+ "is_common_type",
34
+ "is_supports_cast",
35
+ "loads",
36
+ "struct_asdict",
37
+ "timedelta",
38
+ "to_builtins",
39
+ "type_check",
40
+ )
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import typing
5
+
6
+
7
+ def is_supports_cast(obj: typing.Any, /) -> typing.TypeGuard[SupportsCast]:
8
+ return isinstance(obj, SupportsCast)
9
+
10
+
11
+ @typing.runtime_checkable
12
+ class SupportsCast(typing.Protocol):
13
+ @classmethod
14
+ @abc.abstractmethod
15
+ def cast(cls, obj: typing.Any) -> typing.Self: ...
16
+
17
+
18
+ __all__ = ("SupportsCast", "is_supports_cast")
@@ -0,0 +1,6 @@
1
+ from telegrinder.msgspec_utils.custom_types.datetime import datetime, timedelta
2
+ from telegrinder.msgspec_utils.custom_types.enum_meta import BaseEnumMeta
3
+ from telegrinder.msgspec_utils.custom_types.literal import Literal
4
+ from telegrinder.msgspec_utils.custom_types.option import Option
5
+
6
+ __all__ = ("BaseEnumMeta", "Literal", "Option", "datetime", "timedelta")
@@ -0,0 +1,24 @@
1
+ import datetime as dt
2
+ import typing
3
+
4
+ from telegrinder.msgspec_utils.abc import SupportsCast
5
+
6
+
7
+ class datetime(dt.datetime, SupportsCast): # noqa: N801 # type: ignore
8
+ @classmethod
9
+ def cast(cls, obj: dt.datetime) -> typing.Self:
10
+ return cls.fromtimestamp(timestamp=obj.timestamp(), tz=obj.tzinfo)
11
+
12
+
13
+ class timedelta(dt.timedelta, SupportsCast): # noqa: N801 # type: ignore
14
+ @classmethod
15
+ def cast(cls, obj: dt.timedelta) -> typing.Self:
16
+ return cls(seconds=obj.total_seconds())
17
+
18
+
19
+ if typing.TYPE_CHECKING:
20
+ datetime: typing.TypeAlias = dt.datetime
21
+ timedelta: typing.TypeAlias = dt.timedelta
22
+
23
+
24
+ __all__ = ("datetime", "timedelta")
@@ -0,0 +1,43 @@
1
+ import enum
2
+ import typing
3
+
4
+
5
+ class BaseEnumMeta(enum.EnumMeta, type):
6
+ if typing.TYPE_CHECKING:
7
+
8
+ class BaseEnumMeta(enum.Enum): # noqa
9
+ NOT_SUPPORTED = enum.auto()
10
+
11
+ NOT_SUPPORTED: typing.Literal[BaseEnumMeta.NOT_SUPPORTED]
12
+
13
+ else:
14
+
15
+ @staticmethod
16
+ def _member_missing(cls, value):
17
+ from telegrinder.modules import logger
18
+
19
+ logger.warning(
20
+ "Unsupported value {!r} for enum of type {!r}. Probably teleginder needs to be "
21
+ "updated to support the latest version of Telegram Bot API.",
22
+ value,
23
+ cls,
24
+ )
25
+ return cls._member_map_["NOT_SUPPORTED"]
26
+
27
+ def __new__(
28
+ metacls,
29
+ cls,
30
+ bases,
31
+ classdict,
32
+ *,
33
+ boundary=None,
34
+ _simple=False,
35
+ **kwds,
36
+ ):
37
+ classdict["NOT_SUPPORTED"] = "NOT_SUPPORTED" if any(x in bases for x in (str, enum.StrEnum)) else -1
38
+ classdict["_missing_"] = classmethod(BaseEnumMeta._member_missing)
39
+ new_type = super().__new__(metacls, cls, bases, classdict, boundary=boundary, _simple=_simple, **kwds)
40
+ return new_type
41
+
42
+
43
+ __all__ = ("BaseEnumMeta",)