telegrinder 0.1.dev19__py3-none-any.whl → 0.1.dev158__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.
- telegrinder/__init__.py +129 -22
- telegrinder/api/__init__.py +11 -2
- telegrinder/api/abc.py +25 -9
- telegrinder/api/api.py +29 -24
- telegrinder/api/error.py +14 -4
- telegrinder/api/response.py +11 -7
- telegrinder/bot/__init__.py +68 -7
- telegrinder/bot/bot.py +30 -24
- telegrinder/bot/cute_types/__init__.py +11 -1
- telegrinder/bot/cute_types/base.py +47 -0
- telegrinder/bot/cute_types/callback_query.py +64 -14
- telegrinder/bot/cute_types/inline_query.py +22 -16
- telegrinder/bot/cute_types/message.py +163 -43
- telegrinder/bot/cute_types/update.py +23 -0
- telegrinder/bot/dispatch/__init__.py +56 -3
- telegrinder/bot/dispatch/abc.py +9 -7
- telegrinder/bot/dispatch/composition.py +74 -0
- telegrinder/bot/dispatch/context.py +71 -0
- telegrinder/bot/dispatch/dispatch.py +86 -49
- telegrinder/bot/dispatch/handler/__init__.py +3 -0
- telegrinder/bot/dispatch/handler/abc.py +11 -5
- telegrinder/bot/dispatch/handler/func.py +41 -32
- telegrinder/bot/dispatch/handler/message_reply.py +46 -0
- telegrinder/bot/dispatch/middleware/__init__.py +2 -0
- telegrinder/bot/dispatch/middleware/abc.py +10 -4
- telegrinder/bot/dispatch/process.py +53 -49
- telegrinder/bot/dispatch/return_manager/__init__.py +19 -0
- telegrinder/bot/dispatch/return_manager/abc.py +95 -0
- telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
- telegrinder/bot/dispatch/return_manager/message.py +25 -0
- telegrinder/bot/dispatch/view/__init__.py +14 -2
- telegrinder/bot/dispatch/view/abc.py +121 -2
- telegrinder/bot/dispatch/view/box.py +38 -0
- telegrinder/bot/dispatch/view/callback_query.py +13 -38
- telegrinder/bot/dispatch/view/inline_query.py +11 -38
- telegrinder/bot/dispatch/view/message.py +11 -46
- telegrinder/bot/dispatch/waiter_machine/__init__.py +9 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +116 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +76 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +37 -0
- telegrinder/bot/polling/__init__.py +2 -0
- telegrinder/bot/polling/abc.py +11 -4
- telegrinder/bot/polling/polling.py +89 -40
- telegrinder/bot/rules/__init__.py +92 -5
- telegrinder/bot/rules/abc.py +81 -63
- telegrinder/bot/rules/adapter/__init__.py +11 -0
- telegrinder/bot/rules/adapter/abc.py +21 -0
- telegrinder/bot/rules/adapter/errors.py +5 -0
- telegrinder/bot/rules/adapter/event.py +43 -0
- telegrinder/bot/rules/adapter/raw_update.py +24 -0
- telegrinder/bot/rules/callback_data.py +159 -38
- telegrinder/bot/rules/command.py +116 -0
- telegrinder/bot/rules/enum_text.py +28 -0
- telegrinder/bot/rules/func.py +17 -17
- telegrinder/bot/rules/fuzzy.py +13 -10
- telegrinder/bot/rules/inline.py +61 -0
- telegrinder/bot/rules/integer.py +12 -7
- telegrinder/bot/rules/is_from.py +148 -7
- telegrinder/bot/rules/markup.py +21 -18
- telegrinder/bot/rules/mention.py +17 -0
- telegrinder/bot/rules/message_entities.py +33 -0
- telegrinder/bot/rules/regex.py +27 -19
- telegrinder/bot/rules/rule_enum.py +74 -0
- telegrinder/bot/rules/start.py +42 -0
- telegrinder/bot/rules/text.py +23 -14
- telegrinder/bot/scenario/__init__.py +2 -0
- telegrinder/bot/scenario/abc.py +12 -5
- telegrinder/bot/scenario/checkbox.py +48 -30
- telegrinder/bot/scenario/choice.py +16 -10
- telegrinder/client/__init__.py +2 -0
- telegrinder/client/abc.py +8 -21
- telegrinder/client/aiohttp.py +30 -21
- telegrinder/model.py +68 -37
- telegrinder/modules.py +189 -21
- telegrinder/msgspec_json.py +14 -0
- telegrinder/msgspec_utils.py +207 -0
- telegrinder/node/__init__.py +31 -0
- telegrinder/node/attachment.py +71 -0
- telegrinder/node/base.py +93 -0
- telegrinder/node/composer.py +71 -0
- telegrinder/node/container.py +22 -0
- telegrinder/node/message.py +18 -0
- telegrinder/node/rule.py +56 -0
- telegrinder/node/source.py +31 -0
- telegrinder/node/text.py +13 -0
- telegrinder/node/tools/__init__.py +3 -0
- telegrinder/node/tools/generator.py +40 -0
- telegrinder/node/update.py +12 -0
- telegrinder/rules.py +1 -1
- telegrinder/tools/__init__.py +165 -4
- telegrinder/tools/buttons.py +75 -51
- telegrinder/tools/error_handler/__init__.py +8 -0
- telegrinder/tools/error_handler/abc.py +30 -0
- telegrinder/tools/error_handler/error_handler.py +156 -0
- telegrinder/tools/formatting/__init__.py +81 -3
- telegrinder/tools/formatting/html.py +283 -37
- telegrinder/tools/formatting/links.py +32 -0
- telegrinder/tools/formatting/spec_html_formats.py +121 -0
- telegrinder/tools/global_context/__init__.py +12 -0
- telegrinder/tools/global_context/abc.py +66 -0
- telegrinder/tools/global_context/global_context.py +451 -0
- telegrinder/tools/global_context/telegrinder_ctx.py +25 -0
- telegrinder/tools/i18n/__init__.py +12 -0
- telegrinder/tools/i18n/base.py +31 -0
- telegrinder/tools/i18n/middleware/__init__.py +3 -0
- telegrinder/tools/i18n/middleware/base.py +26 -0
- telegrinder/tools/i18n/simple.py +48 -0
- telegrinder/tools/inline_query.py +684 -0
- telegrinder/tools/kb_set/__init__.py +2 -0
- telegrinder/tools/kb_set/base.py +3 -0
- telegrinder/tools/kb_set/yaml.py +28 -17
- telegrinder/tools/keyboard.py +84 -62
- telegrinder/tools/loop_wrapper/__init__.py +4 -0
- telegrinder/tools/loop_wrapper/abc.py +18 -0
- telegrinder/tools/loop_wrapper/loop_wrapper.py +132 -0
- telegrinder/tools/magic.py +48 -23
- telegrinder/tools/parse_mode.py +1 -2
- telegrinder/types/__init__.py +1 -0
- telegrinder/types/enums.py +651 -0
- telegrinder/types/methods.py +3933 -1128
- telegrinder/types/objects.py +4755 -1633
- {telegrinder-0.1.dev19.dist-info → telegrinder-0.1.dev158.dist-info}/LICENSE +2 -1
- telegrinder-0.1.dev158.dist-info/METADATA +108 -0
- telegrinder-0.1.dev158.dist-info/RECORD +126 -0
- {telegrinder-0.1.dev19.dist-info → telegrinder-0.1.dev158.dist-info}/WHEEL +1 -1
- telegrinder/bot/dispatch/waiter.py +0 -37
- telegrinder/result.py +0 -38
- telegrinder/tools/formatting/abc.py +0 -52
- telegrinder/tools/formatting/markdown.py +0 -57
- telegrinder/typegen/__init__.py +0 -1
- telegrinder/typegen/__main__.py +0 -3
- telegrinder/typegen/nicification.py +0 -20
- telegrinder/typegen/schema_generator.py +0 -259
- telegrinder-0.1.dev19.dist-info/METADATA +0 -22
- telegrinder-0.1.dev19.dist-info/RECORD +0 -74
telegrinder/client/aiohttp.py
CHANGED
|
@@ -2,11 +2,11 @@ import ssl
|
|
|
2
2
|
import typing
|
|
3
3
|
|
|
4
4
|
import aiohttp
|
|
5
|
+
import certifi
|
|
6
|
+
from aiohttp import ClientSession, TCPConnector
|
|
5
7
|
|
|
6
8
|
from telegrinder.client.abc import ABCClient
|
|
7
|
-
from
|
|
8
|
-
from telegrinder.modules import json, JSONModule
|
|
9
|
-
import certifi
|
|
9
|
+
from telegrinder.modules import JSONModule, json
|
|
10
10
|
|
|
11
11
|
if typing.TYPE_CHECKING:
|
|
12
12
|
from aiohttp import ClientResponse
|
|
@@ -15,10 +15,10 @@ if typing.TYPE_CHECKING:
|
|
|
15
15
|
class AiohttpClient(ABCClient):
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
|
-
session:
|
|
19
|
-
json_processing_module:
|
|
20
|
-
timeout:
|
|
21
|
-
**session_params
|
|
18
|
+
session: ClientSession | None = None,
|
|
19
|
+
json_processing_module: JSONModule | None = None,
|
|
20
|
+
timeout: aiohttp.ClientTimeout | None = None,
|
|
21
|
+
**session_params,
|
|
22
22
|
):
|
|
23
23
|
self.session = session
|
|
24
24
|
self.json_processing_module = json_processing_module or json
|
|
@@ -29,8 +29,8 @@ class AiohttpClient(ABCClient):
|
|
|
29
29
|
self,
|
|
30
30
|
url: str,
|
|
31
31
|
method: str = "GET",
|
|
32
|
-
data:
|
|
33
|
-
**kwargs
|
|
32
|
+
data: dict | None = None,
|
|
33
|
+
**kwargs,
|
|
34
34
|
) -> "ClientResponse":
|
|
35
35
|
if not self.session:
|
|
36
36
|
self.session = ClientSession(
|
|
@@ -41,7 +41,11 @@ class AiohttpClient(ABCClient):
|
|
|
41
41
|
**self.session_params,
|
|
42
42
|
)
|
|
43
43
|
async with self.session.request(
|
|
44
|
-
url=url,
|
|
44
|
+
url=url,
|
|
45
|
+
method=method,
|
|
46
|
+
data=data,
|
|
47
|
+
timeout=self.timeout,
|
|
48
|
+
**kwargs,
|
|
45
49
|
) as response:
|
|
46
50
|
await response.read()
|
|
47
51
|
return response
|
|
@@ -50,32 +54,34 @@ class AiohttpClient(ABCClient):
|
|
|
50
54
|
self,
|
|
51
55
|
url: str,
|
|
52
56
|
method: str = "GET",
|
|
53
|
-
data:
|
|
54
|
-
**kwargs
|
|
57
|
+
data: dict | None = None,
|
|
58
|
+
**kwargs,
|
|
55
59
|
) -> dict:
|
|
56
60
|
response = await self.request_raw(url, method, data, **kwargs)
|
|
57
61
|
return await response.json(
|
|
58
|
-
encoding="utf-8",
|
|
62
|
+
encoding="utf-8",
|
|
63
|
+
loads=self.json_processing_module.loads,
|
|
64
|
+
content_type=None,
|
|
59
65
|
)
|
|
60
66
|
|
|
61
67
|
async def request_text(
|
|
62
68
|
self,
|
|
63
69
|
url: str,
|
|
64
70
|
method: str = "GET",
|
|
65
|
-
data:
|
|
66
|
-
**kwargs
|
|
71
|
+
data: dict | aiohttp.FormData | None = None,
|
|
72
|
+
**kwargs,
|
|
67
73
|
) -> str:
|
|
68
|
-
response = await self.request_raw(url, method, data, **kwargs)
|
|
74
|
+
response = await self.request_raw(url, method, data, **kwargs) # type: ignore
|
|
69
75
|
return await response.text(encoding="utf-8")
|
|
70
76
|
|
|
71
77
|
async def request_bytes(
|
|
72
78
|
self,
|
|
73
79
|
url: str,
|
|
74
80
|
method: str = "GET",
|
|
75
|
-
data:
|
|
76
|
-
**kwargs
|
|
81
|
+
data: dict | aiohttp.FormData | None = None,
|
|
82
|
+
**kwargs,
|
|
77
83
|
) -> bytes:
|
|
78
|
-
response = await self.request_raw(url, method, data, **kwargs)
|
|
84
|
+
response = await self.request_raw(url, method, data, **kwargs) # type: ignore
|
|
79
85
|
if response._body is None:
|
|
80
86
|
await response.read()
|
|
81
87
|
return response._body
|
|
@@ -84,8 +90,8 @@ class AiohttpClient(ABCClient):
|
|
|
84
90
|
self,
|
|
85
91
|
url: str,
|
|
86
92
|
method: str = "GET",
|
|
87
|
-
data:
|
|
88
|
-
**kwargs
|
|
93
|
+
data: dict | None = None,
|
|
94
|
+
**kwargs,
|
|
89
95
|
) -> bytes:
|
|
90
96
|
response = await self.request_raw(url, method, data, **kwargs)
|
|
91
97
|
return response._body
|
|
@@ -111,3 +117,6 @@ class AiohttpClient(ABCClient):
|
|
|
111
117
|
if self.session._connector is not None and self.session._connector_owner:
|
|
112
118
|
self.session._connector.close()
|
|
113
119
|
self.session._connector = None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
__all__ = ("AiohttpClient",)
|
telegrinder/model.py
CHANGED
|
@@ -1,65 +1,96 @@
|
|
|
1
|
-
import msgspec
|
|
2
|
-
from telegrinder.result import Result
|
|
3
|
-
from telegrinder.modules import json
|
|
4
|
-
from msgspec import Raw
|
|
5
1
|
import typing
|
|
2
|
+
from types import NoneType
|
|
3
|
+
|
|
4
|
+
import msgspec
|
|
5
|
+
from fntypes.co import Nothing, Result, Some
|
|
6
|
+
|
|
7
|
+
from .msgspec_utils import decoder, encoder
|
|
8
|
+
|
|
9
|
+
T = typing.TypeVar("T")
|
|
6
10
|
|
|
7
11
|
if typing.TYPE_CHECKING:
|
|
8
12
|
from telegrinder.api.error import APIError
|
|
13
|
+
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
|
|
16
|
+
"omit_defaults": True,
|
|
17
|
+
"dict": True,
|
|
18
|
+
"rename": {"from_": "from"},
|
|
19
|
+
}
|
|
12
20
|
|
|
13
21
|
|
|
22
|
+
@typing.overload
|
|
14
23
|
def full_result(
|
|
15
|
-
result: Result[msgspec.Raw, "APIError"], full_t:
|
|
24
|
+
result: Result[msgspec.Raw, "APIError"], full_t: type[T]
|
|
16
25
|
) -> Result[T, "APIError"]:
|
|
17
|
-
|
|
18
|
-
return result
|
|
19
|
-
return Result(True, value=msgspec.json.decode(result.value, type=full_t))
|
|
20
|
-
|
|
26
|
+
...
|
|
21
27
|
|
|
22
|
-
def convert(d: typing.Any) -> typing.Any:
|
|
23
|
-
if isinstance(d, Model):
|
|
24
|
-
return msgspec.json.encode(d).decode()
|
|
25
|
-
elif isinstance(d, dict):
|
|
26
|
-
return {k: convert(v) for k, v in d.items() if v is not None}
|
|
27
|
-
elif isinstance(d, list):
|
|
28
|
-
return json.dumps(d)
|
|
29
|
-
return d
|
|
30
28
|
|
|
29
|
+
@typing.overload
|
|
30
|
+
def full_result(
|
|
31
|
+
result: Result[msgspec.Raw, "APIError"],
|
|
32
|
+
full_t: tuple[type[T], ...],
|
|
33
|
+
) -> Result[T, "APIError"]:
|
|
34
|
+
...
|
|
31
35
|
|
|
32
|
-
model_config = {"rename": {"from_": "from"}, "omit_defaults": True}
|
|
33
36
|
|
|
37
|
+
def full_result(
|
|
38
|
+
result: Result[msgspec.Raw, "APIError"],
|
|
39
|
+
full_t: type[T] | tuple[type[T], ...],
|
|
40
|
+
) -> Result[T, "APIError"]:
|
|
41
|
+
return result.map(lambda v: decoder.decode(v, type=full_t)) # type: ignore
|
|
34
42
|
|
|
35
|
-
class Model(msgspec.Struct, **model_config):
|
|
36
|
-
_dict_cached: typing.Optional[dict] = None
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
def convert(d: typing.Any, serialize: bool = True) -> typing.Any:
|
|
45
|
+
if isinstance(d, Model):
|
|
46
|
+
converted_dct = convert(d.to_dict(), serialize=False)
|
|
47
|
+
return encoder.encode(converted_dct) if serialize is True else converted_dct
|
|
48
|
+
|
|
49
|
+
if isinstance(d, dict):
|
|
50
|
+
return {
|
|
51
|
+
k: convert(v, serialize=serialize)
|
|
52
|
+
for k, v in d.items()
|
|
53
|
+
if type(v) not in (NoneType, Nothing)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if isinstance(d, list):
|
|
57
|
+
converted_lst = [convert(x, serialize=False) for x in d]
|
|
58
|
+
return encoder.encode(converted_lst) if serialize is True else converted_lst
|
|
59
|
+
|
|
60
|
+
return d
|
|
43
61
|
|
|
44
62
|
|
|
45
|
-
def get_params(params: dict) -> dict:
|
|
63
|
+
def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
|
|
46
64
|
return {
|
|
47
|
-
k: v
|
|
65
|
+
k: v.unwrap() if v and isinstance(v, Some) else v
|
|
66
|
+
for k, v in (
|
|
48
67
|
*params.items(),
|
|
49
|
-
*params.pop("other").items()
|
|
68
|
+
*params.pop("other", {}).items(),
|
|
50
69
|
)
|
|
51
|
-
if k != "self"
|
|
52
|
-
and v is not None
|
|
70
|
+
if k != "self" and type(v) not in (NoneType, Nothing)
|
|
53
71
|
}
|
|
54
72
|
|
|
55
73
|
|
|
74
|
+
class Model(msgspec.Struct, **MODEL_CONFIG):
|
|
75
|
+
def to_dict(
|
|
76
|
+
self,
|
|
77
|
+
*,
|
|
78
|
+
exclude_fields: set[str] | None = None,
|
|
79
|
+
) -> dict[str, typing.Any]:
|
|
80
|
+
exclude_fields = exclude_fields or set()
|
|
81
|
+
if "model_as_dict" not in self.__dict__:
|
|
82
|
+
self.__dict__["model_as_dict"] = msgspec.structs.asdict(self)
|
|
83
|
+
return {
|
|
84
|
+
key: value
|
|
85
|
+
for key, value in self.__dict__["model_as_dict"].items()
|
|
86
|
+
if key not in exclude_fields
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
56
90
|
__all__ = (
|
|
91
|
+
"Model",
|
|
57
92
|
"convert",
|
|
58
|
-
"model_config",
|
|
59
|
-
"encoder",
|
|
60
93
|
"full_result",
|
|
61
|
-
"msgspec",
|
|
62
|
-
"Model",
|
|
63
|
-
"Raw",
|
|
64
94
|
"get_params",
|
|
95
|
+
"MODEL_CONFIG",
|
|
65
96
|
)
|
telegrinder/modules.py
CHANGED
|
@@ -1,39 +1,78 @@
|
|
|
1
|
-
import
|
|
1
|
+
import os
|
|
2
2
|
import typing
|
|
3
3
|
|
|
4
4
|
from choicelib import choice_in_order
|
|
5
|
-
from typing_extensions import Protocol
|
|
6
5
|
|
|
7
6
|
|
|
8
|
-
class JSONModule(Protocol):
|
|
9
|
-
def loads(self, s: str) ->
|
|
7
|
+
class JSONModule(typing.Protocol):
|
|
8
|
+
def loads(self, s: str) -> dict | list:
|
|
10
9
|
...
|
|
11
10
|
|
|
12
|
-
def dumps(self, o:
|
|
11
|
+
def dumps(self, o: dict | list) -> str:
|
|
13
12
|
...
|
|
14
13
|
|
|
15
14
|
|
|
15
|
+
class LoggerModule(typing.Protocol):
|
|
16
|
+
def debug(self, __msg: object, *args: object, **kwargs: object):
|
|
17
|
+
...
|
|
18
|
+
|
|
19
|
+
def info(self, __msg: object, *args: object, **kwargs: object):
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def warning(self, __msg: object, *args: object, **kwargs: object):
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def error(self, __msg: object, *args: object, **kwargs: object):
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
def critical(self, __msg: object, *args: object, **kwargs: object):
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def exception(self, __msg: object, *args: object, **kwargs: object):
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
def set_level(
|
|
35
|
+
self,
|
|
36
|
+
level: typing.Literal[
|
|
37
|
+
"DEBUG",
|
|
38
|
+
"INFO",
|
|
39
|
+
"WARNING",
|
|
40
|
+
"ERROR",
|
|
41
|
+
"CRITICAL",
|
|
42
|
+
"EXCEPTION",
|
|
43
|
+
],
|
|
44
|
+
) -> None:
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
logger: LoggerModule
|
|
16
49
|
json: JSONModule = choice_in_order(
|
|
17
|
-
["ujson", "hyperjson", "orjson"], do_import=True, default="
|
|
50
|
+
["ujson", "hyperjson", "orjson"], do_import=True, default="telegrinder.msgspec_json"
|
|
18
51
|
)
|
|
19
|
-
|
|
20
52
|
logging_module = choice_in_order(["loguru"], default="logging")
|
|
53
|
+
logging_level = os.getenv("LOGGER_LEVEL", default="DEBUG").upper()
|
|
21
54
|
|
|
22
55
|
if logging_module == "loguru":
|
|
23
56
|
import os
|
|
24
57
|
import sys
|
|
25
58
|
|
|
26
|
-
if not os.environ.get("LOGURU_AUTOINIT"):
|
|
27
|
-
os.environ["LOGURU_AUTOINIT"] = "0"
|
|
28
59
|
from loguru import logger # type: ignore
|
|
29
60
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
61
|
+
os.environ.setdefault("LOGURU_AUTOINIT", "0")
|
|
62
|
+
log_format = (
|
|
63
|
+
"<level>{level: <8}</level> | "
|
|
64
|
+
"<lg>{time:YYYY-MM-DD HH:mm:ss}</lg> | "
|
|
65
|
+
"<le>{name}</le>:<le>{function}</le>:"
|
|
66
|
+
"<le>{line}</le> > <lw>{message}</lw>"
|
|
67
|
+
)
|
|
68
|
+
logger.remove() # type: ignore
|
|
69
|
+
handler_id = logger.add( # type: ignore
|
|
70
|
+
sink=sys.stderr,
|
|
71
|
+
format=log_format,
|
|
72
|
+
enqueue=True,
|
|
73
|
+
colorize=True,
|
|
74
|
+
level=logging_level,
|
|
75
|
+
)
|
|
37
76
|
|
|
38
77
|
elif logging_module == "logging":
|
|
39
78
|
"""
|
|
@@ -41,8 +80,111 @@ elif logging_module == "logging":
|
|
|
41
80
|
About:
|
|
42
81
|
https://docs.python.org/3/howto/logging-cookbook.html#use-of-alternative-formatting-styles
|
|
43
82
|
"""
|
|
83
|
+
|
|
44
84
|
import inspect
|
|
45
85
|
import logging
|
|
86
|
+
import sys
|
|
87
|
+
|
|
88
|
+
import colorama
|
|
89
|
+
|
|
90
|
+
colorama.just_fix_windows_console() # init & fix console
|
|
91
|
+
|
|
92
|
+
FORMAT = (
|
|
93
|
+
"<white>{name: <4} |</white> <level>{levelname: <8}</level>"
|
|
94
|
+
" <white>|</white> <green>{asctime}</green> <white>|</white> <level_module>"
|
|
95
|
+
"{module}</level_module><white>:</white><level_func>"
|
|
96
|
+
"{funcName}</level_func><white>:</white><level_lineno>"
|
|
97
|
+
"{lineno}</level_lineno><white> > </white><level_message>"
|
|
98
|
+
"{message}</level_message>"
|
|
99
|
+
)
|
|
100
|
+
COLORS = {
|
|
101
|
+
"red": colorama.Fore.LIGHTRED_EX,
|
|
102
|
+
"green": colorama.Fore.LIGHTGREEN_EX,
|
|
103
|
+
"blue": colorama.Fore.LIGHTBLUE_EX,
|
|
104
|
+
"white": colorama.Fore.LIGHTWHITE_EX,
|
|
105
|
+
"yellow": colorama.Fore.LIGHTYELLOW_EX,
|
|
106
|
+
"magenta": colorama.Fore.LIGHTMAGENTA_EX,
|
|
107
|
+
"cyan": colorama.Fore.LIGHTCYAN_EX,
|
|
108
|
+
"reset": colorama.Style.RESET_ALL,
|
|
109
|
+
}
|
|
110
|
+
LEVEL_SETTINGS = {
|
|
111
|
+
"INFO": {
|
|
112
|
+
"level": "green",
|
|
113
|
+
"level_module": "blue",
|
|
114
|
+
"level_func": "cyan",
|
|
115
|
+
"level_lineno": "green",
|
|
116
|
+
"level_message": "white",
|
|
117
|
+
},
|
|
118
|
+
"DEBUG": {
|
|
119
|
+
"level": "blue",
|
|
120
|
+
"level_module": "yellow",
|
|
121
|
+
"level_func": "green",
|
|
122
|
+
"level_lineno": "cyan",
|
|
123
|
+
"level_message": "blue",
|
|
124
|
+
},
|
|
125
|
+
"WARNING": {
|
|
126
|
+
"level": "yellow",
|
|
127
|
+
"level_module": "red",
|
|
128
|
+
"level_func": "green",
|
|
129
|
+
"level_lineno": "red",
|
|
130
|
+
"level_message": "yellow",
|
|
131
|
+
},
|
|
132
|
+
"ERROR": {
|
|
133
|
+
"level": "red",
|
|
134
|
+
"level_module": "magenta",
|
|
135
|
+
"level_func": "yellow",
|
|
136
|
+
"level_lineno": "green",
|
|
137
|
+
"level_message": "red",
|
|
138
|
+
},
|
|
139
|
+
"CRITICAL": {
|
|
140
|
+
"level": "cyan",
|
|
141
|
+
"level_module": "yellow",
|
|
142
|
+
"level_func": "yellow",
|
|
143
|
+
"level_lineno": "yellow",
|
|
144
|
+
"level_message": "cyan",
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
FORMAT = (
|
|
148
|
+
FORMAT
|
|
149
|
+
.replace("<white>", COLORS["white"])
|
|
150
|
+
.replace("</white>", COLORS["reset"])
|
|
151
|
+
.replace("<green>", COLORS["green"])
|
|
152
|
+
.replace("</green>", COLORS["reset"])
|
|
153
|
+
)
|
|
154
|
+
LEVEL_FORMATS: dict[str, str] = {}
|
|
155
|
+
for level, settings in LEVEL_SETTINGS.items():
|
|
156
|
+
fmt = FORMAT
|
|
157
|
+
for name, color in settings.items():
|
|
158
|
+
fmt = (
|
|
159
|
+
fmt
|
|
160
|
+
.replace(f"<{name}>", COLORS[color])
|
|
161
|
+
.replace(f"</{name}>", COLORS["reset"])
|
|
162
|
+
)
|
|
163
|
+
LEVEL_FORMATS[level] = fmt
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class TelegrinderLoggingFormatter(logging.Formatter):
|
|
167
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
168
|
+
if not record.funcName or record.funcName == "<module>":
|
|
169
|
+
record.funcName = "\b"
|
|
170
|
+
frame = next(
|
|
171
|
+
(
|
|
172
|
+
frame
|
|
173
|
+
for frame in inspect.stack()
|
|
174
|
+
if frame.filename == record.pathname
|
|
175
|
+
and frame.lineno == record.lineno
|
|
176
|
+
),
|
|
177
|
+
None,
|
|
178
|
+
)
|
|
179
|
+
if frame:
|
|
180
|
+
module = inspect.getmodule(frame.frame)
|
|
181
|
+
record.module = module.__name__ if module else "<module>"
|
|
182
|
+
return logging.Formatter(
|
|
183
|
+
LEVEL_FORMATS.get(record.levelname),
|
|
184
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
185
|
+
style="{",
|
|
186
|
+
).format(record)
|
|
187
|
+
|
|
46
188
|
|
|
47
189
|
class LogMessage:
|
|
48
190
|
def __init__(self, fmt, args, kwargs):
|
|
@@ -50,15 +192,16 @@ elif logging_module == "logging":
|
|
|
50
192
|
self.args = args
|
|
51
193
|
self.kwargs = kwargs
|
|
52
194
|
|
|
53
|
-
def __str__(self):
|
|
54
|
-
return self.fmt.format(*self.args)
|
|
195
|
+
def __str__(self) -> str:
|
|
196
|
+
return self.fmt.format(*self.args, **self.kwargs)
|
|
55
197
|
|
|
56
|
-
class
|
|
198
|
+
class TelegrinderLoggingStyleAdapter(logging.LoggerAdapter):
|
|
57
199
|
def __init__(self, logger, extra=None):
|
|
58
200
|
super().__init__(logger, extra or {})
|
|
59
201
|
|
|
60
202
|
def log(self, level, msg, *args, **kwargs):
|
|
61
203
|
if self.isEnabledFor(level):
|
|
204
|
+
kwargs.setdefault("stacklevel", 2)
|
|
62
205
|
msg, args, kwargs = self.proc(msg, args, kwargs)
|
|
63
206
|
self.logger._log(level, msg, args, **kwargs)
|
|
64
207
|
|
|
@@ -68,9 +211,34 @@ elif logging_module == "logging":
|
|
|
68
211
|
for key in inspect.getfullargspec(self.logger._log).args[1:]
|
|
69
212
|
if key in kwargs
|
|
70
213
|
}
|
|
214
|
+
|
|
71
215
|
if isinstance(msg, str):
|
|
72
216
|
msg = LogMessage(msg, args, kwargs)
|
|
73
|
-
args = ()
|
|
217
|
+
args = tuple()
|
|
74
218
|
return msg, args, log_kwargs
|
|
75
219
|
|
|
76
|
-
|
|
220
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
221
|
+
handler.setFormatter(TelegrinderLoggingFormatter())
|
|
222
|
+
logger = logging.getLogger("telegrinder") # type: ignore
|
|
223
|
+
logger.setLevel(logging.getLevelName(logging_level)) # type: ignore
|
|
224
|
+
logger.addHandler(handler) # type: ignore
|
|
225
|
+
logger = TelegrinderLoggingStyleAdapter(logger) # type: ignore
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _set_logger_level(level):
|
|
229
|
+
level = level.upper()
|
|
230
|
+
if logging_module == "logging":
|
|
231
|
+
import logging
|
|
232
|
+
|
|
233
|
+
logging.getLogger("telegrinder").setLevel(logging.getLevelName(level))
|
|
234
|
+
elif logging_module == "loguru":
|
|
235
|
+
import loguru # type: ignore
|
|
236
|
+
|
|
237
|
+
if handler_id in loguru.logger._core.handlers: # type: ignore
|
|
238
|
+
loguru.logger._core.handlers[handler_id]._levelno = loguru.logger.level(level).no # type: ignore
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
setattr(logger, "set_level", staticmethod(_set_logger_level)) # type: ignore
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
__all__ = ("json", "logger")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from .msgspec_utils import decoder, encoder
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def loads(s: str | bytes) -> dict[str, typing.Any] | list[typing.Any]:
|
|
7
|
+
return decoder.decode(s, type=dict[str, typing.Any] | list[typing.Any]) # type: ignore
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def dumps(o: dict[str, typing.Any] | list[typing.Any]) -> str:
|
|
11
|
+
return encoder.encode(o)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ("dumps", "loads")
|