telegrinder 0.4.2__py3-none-any.whl → 0.5.1__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 +37 -55
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +6 -4
- telegrinder/api/api.py +100 -26
- telegrinder/api/error.py +42 -8
- telegrinder/api/response.py +4 -1
- telegrinder/api/token.py +2 -2
- telegrinder/bot/__init__.py +9 -25
- telegrinder/bot/bot.py +31 -25
- telegrinder/bot/cute_types/__init__.py +0 -0
- telegrinder/bot/cute_types/base.py +103 -61
- telegrinder/bot/cute_types/callback_query.py +447 -400
- telegrinder/bot/cute_types/chat_join_request.py +59 -62
- telegrinder/bot/cute_types/chat_member_updated.py +154 -157
- telegrinder/bot/cute_types/inline_query.py +41 -44
- telegrinder/bot/cute_types/message.py +98 -67
- telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
- telegrinder/bot/cute_types/update.py +1 -8
- telegrinder/bot/cute_types/utils.py +1 -1
- telegrinder/bot/dispatch/__init__.py +10 -15
- telegrinder/bot/dispatch/abc.py +12 -11
- telegrinder/bot/dispatch/action.py +104 -0
- telegrinder/bot/dispatch/context.py +32 -26
- telegrinder/bot/dispatch/dispatch.py +61 -134
- telegrinder/bot/dispatch/handler/__init__.py +2 -0
- telegrinder/bot/dispatch/handler/abc.py +10 -8
- telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
- telegrinder/bot/dispatch/handler/base.py +10 -33
- telegrinder/bot/dispatch/handler/document_reply.py +2 -3
- telegrinder/bot/dispatch/handler/func.py +55 -87
- telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
- telegrinder/bot/dispatch/handler/message_reply.py +2 -3
- telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
- telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
- telegrinder/bot/dispatch/handler/video_reply.py +2 -3
- telegrinder/bot/dispatch/middleware/__init__.py +0 -0
- telegrinder/bot/dispatch/middleware/abc.py +79 -55
- telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
- telegrinder/bot/dispatch/process.py +84 -105
- telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
- telegrinder/bot/dispatch/return_manager/abc.py +102 -65
- telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
- telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
- telegrinder/bot/dispatch/return_manager/message.py +8 -10
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
- telegrinder/bot/dispatch/view/__init__.py +4 -4
- telegrinder/bot/dispatch/view/abc.py +6 -16
- telegrinder/bot/dispatch/view/base.py +54 -178
- telegrinder/bot/dispatch/view/box.py +19 -18
- telegrinder/bot/dispatch/view/callback_query.py +4 -8
- telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
- telegrinder/bot/dispatch/view/chat_member.py +5 -25
- telegrinder/bot/dispatch/view/error.py +9 -0
- telegrinder/bot/dispatch/view/inline_query.py +4 -8
- telegrinder/bot/dispatch/view/message.py +5 -25
- telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
- telegrinder/bot/dispatch/view/raw.py +3 -109
- telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
- telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
- telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
- telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
- telegrinder/bot/polling/__init__.py +0 -0
- telegrinder/bot/polling/abc.py +0 -0
- telegrinder/bot/polling/polling.py +209 -88
- telegrinder/bot/rules/__init__.py +3 -16
- telegrinder/bot/rules/abc.py +42 -122
- telegrinder/bot/rules/callback_data.py +29 -49
- telegrinder/bot/rules/chat_join.py +5 -23
- telegrinder/bot/rules/command.py +8 -4
- telegrinder/bot/rules/enum_text.py +3 -4
- telegrinder/bot/rules/func.py +7 -14
- telegrinder/bot/rules/fuzzy.py +3 -4
- telegrinder/bot/rules/inline.py +8 -20
- telegrinder/bot/rules/integer.py +2 -3
- telegrinder/bot/rules/is_from.py +12 -11
- telegrinder/bot/rules/logic.py +11 -5
- telegrinder/bot/rules/markup.py +22 -14
- telegrinder/bot/rules/mention.py +8 -7
- telegrinder/bot/rules/message_entities.py +8 -4
- telegrinder/bot/rules/node.py +23 -12
- telegrinder/bot/rules/payload.py +5 -4
- telegrinder/bot/rules/payment_invoice.py +6 -21
- telegrinder/bot/rules/regex.py +2 -4
- telegrinder/bot/rules/rule_enum.py +8 -7
- telegrinder/bot/rules/start.py +5 -6
- telegrinder/bot/rules/state.py +1 -1
- telegrinder/bot/rules/text.py +4 -15
- telegrinder/bot/rules/update.py +3 -4
- telegrinder/bot/scenario/__init__.py +0 -0
- telegrinder/bot/scenario/abc.py +6 -5
- telegrinder/bot/scenario/checkbox.py +1 -1
- telegrinder/bot/scenario/choice.py +30 -39
- telegrinder/client/__init__.py +3 -5
- telegrinder/client/abc.py +11 -6
- telegrinder/client/aiohttp.py +141 -27
- telegrinder/client/form_data.py +1 -1
- telegrinder/model.py +61 -89
- telegrinder/modules.py +325 -102
- telegrinder/msgspec_utils/__init__.py +40 -0
- telegrinder/msgspec_utils/abc.py +18 -0
- telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
- telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
- telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +389 -0
- telegrinder/msgspec_utils/encoder.py +206 -0
- telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
- telegrinder/msgspec_utils/tools.py +75 -0
- telegrinder/node/__init__.py +24 -7
- telegrinder/node/attachment.py +1 -0
- telegrinder/node/base.py +154 -72
- telegrinder/node/callback_query.py +5 -5
- telegrinder/node/collection.py +39 -0
- telegrinder/node/command.py +1 -2
- telegrinder/node/composer.py +121 -72
- telegrinder/node/container.py +11 -8
- telegrinder/node/context.py +48 -0
- telegrinder/node/either.py +27 -40
- telegrinder/node/error.py +41 -0
- telegrinder/node/event.py +37 -11
- telegrinder/node/exceptions.py +7 -0
- telegrinder/node/file.py +0 -0
- telegrinder/node/i18n.py +108 -0
- telegrinder/node/me.py +3 -2
- telegrinder/node/payload.py +1 -1
- telegrinder/node/polymorphic.py +63 -28
- telegrinder/node/reply_message.py +12 -0
- telegrinder/node/rule.py +6 -13
- telegrinder/node/scope.py +14 -5
- telegrinder/node/session.py +53 -0
- telegrinder/node/source.py +41 -9
- telegrinder/node/text.py +1 -2
- telegrinder/node/tools/__init__.py +0 -0
- telegrinder/node/tools/generator.py +3 -5
- telegrinder/node/utility.py +16 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +0 -0
- telegrinder/tools/__init__.py +48 -88
- telegrinder/tools/aio.py +103 -0
- telegrinder/tools/callback_data_serialization/__init__.py +5 -0
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +2 -18
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
- telegrinder/tools/formatting/deep_links/parsing.py +90 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html_formatter.py +18 -45
- telegrinder/tools/fullname.py +83 -0
- telegrinder/tools/global_context/__init__.py +4 -3
- telegrinder/tools/global_context/abc.py +17 -14
- telegrinder/tools/global_context/builtin_context.py +39 -0
- telegrinder/tools/global_context/global_context.py +138 -39
- telegrinder/tools/input_file_directory.py +0 -0
- telegrinder/tools/keyboard/__init__.py +39 -0
- telegrinder/tools/keyboard/abc.py +159 -0
- telegrinder/tools/keyboard/base.py +77 -0
- telegrinder/tools/keyboard/buttons/__init__.py +14 -0
- telegrinder/tools/keyboard/buttons/base.py +18 -0
- telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
- telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
- telegrinder/tools/keyboard/buttons/tools.py +18 -0
- telegrinder/tools/keyboard/data.py +20 -0
- telegrinder/tools/keyboard/keyboard.py +131 -0
- telegrinder/tools/keyboard/static_keyboard.py +83 -0
- telegrinder/tools/lifespan.py +87 -51
- telegrinder/tools/limited_dict.py +4 -1
- telegrinder/tools/loop_wrapper.py +332 -0
- telegrinder/tools/magic/__init__.py +32 -0
- telegrinder/tools/magic/annotations.py +165 -0
- telegrinder/tools/magic/dictionary.py +20 -0
- telegrinder/tools/magic/function.py +246 -0
- telegrinder/tools/magic/shortcut.py +111 -0
- telegrinder/tools/parse_mode.py +9 -3
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_storage/__init__.py +0 -0
- telegrinder/tools/state_storage/abc.py +6 -1
- telegrinder/tools/state_storage/memory.py +1 -1
- telegrinder/tools/strings.py +0 -0
- telegrinder/types/__init__.py +307 -268
- telegrinder/types/enums.py +68 -37
- telegrinder/types/input_file.py +3 -3
- telegrinder/types/methods.py +5699 -5055
- telegrinder/types/methods_utils.py +62 -0
- telegrinder/types/objects.py +1782 -994
- telegrinder/verification_utils.py +3 -1
- telegrinder-0.5.1.dist-info/METADATA +162 -0
- telegrinder-0.5.1.dist-info/RECORD +200 -0
- {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/licenses/LICENSE +2 -2
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
- telegrinder/bot/rules/id.py +0 -24
- telegrinder/bot/rules/message.py +0 -15
- telegrinder/client/sonic.py +0 -212
- telegrinder/msgspec_utils.py +0 -478
- telegrinder/tools/adapter/__init__.py +0 -19
- telegrinder/tools/adapter/abc.py +0 -49
- telegrinder/tools/adapter/dataclass.py +0 -56
- telegrinder/tools/adapter/errors.py +0 -5
- telegrinder/tools/adapter/event.py +0 -61
- telegrinder/tools/adapter/node.py +0 -46
- telegrinder/tools/adapter/raw_event.py +0 -27
- telegrinder/tools/adapter/raw_update.py +0 -30
- telegrinder/tools/callback_data_serilization/__init__.py +0 -5
- telegrinder/tools/error_handler/__init__.py +0 -10
- telegrinder/tools/error_handler/abc.py +0 -30
- telegrinder/tools/error_handler/error.py +0 -9
- telegrinder/tools/error_handler/error_handler.py +0 -179
- telegrinder/tools/formatting/spec_html_formats.py +0 -75
- telegrinder/tools/functional.py +0 -8
- telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
- telegrinder/tools/i18n/__init__.py +0 -12
- telegrinder/tools/i18n/abc.py +0 -32
- telegrinder/tools/i18n/middleware/__init__.py +0 -3
- telegrinder/tools/i18n/middleware/abc.py +0 -22
- telegrinder/tools/i18n/simple.py +0 -43
- telegrinder/tools/keyboard.py +0 -132
- telegrinder/tools/loop_wrapper/__init__.py +0 -4
- telegrinder/tools/loop_wrapper/abc.py +0 -20
- telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
- telegrinder/tools/magic.py +0 -344
- telegrinder-0.4.2.dist-info/METADATA +0 -151
- telegrinder-0.4.2.dist-info/RECORD +0 -182
- {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/WHEEL +0 -0
telegrinder/client/abc.py
CHANGED
|
@@ -5,9 +5,10 @@ from abc import ABC, abstractmethod
|
|
|
5
5
|
from telegrinder.client.form_data import MultipartFormProto, encode_form_data
|
|
6
6
|
|
|
7
7
|
type Data = dict[str, typing.Any] | MultipartFormProto
|
|
8
|
+
type Files = dict[str, tuple[str, typing.Any]]
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
class ABCClient
|
|
11
|
+
class ABCClient(ABC):
|
|
11
12
|
CONNECTION_TIMEOUT_ERRORS: tuple[type[BaseException], ...] = ()
|
|
12
13
|
CLIENT_CONNECTION_ERRORS: tuple[type[BaseException], ...] = ()
|
|
13
14
|
|
|
@@ -15,6 +16,11 @@ class ABCClient[MultipartForm: MultipartFormProto](ABC):
|
|
|
15
16
|
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
|
|
16
17
|
pass
|
|
17
18
|
|
|
19
|
+
@property
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def timeout(self) -> float:
|
|
22
|
+
pass
|
|
23
|
+
|
|
18
24
|
@abstractmethod
|
|
19
25
|
async def request_text(
|
|
20
26
|
self,
|
|
@@ -61,7 +67,7 @@ class ABCClient[MultipartForm: MultipartFormProto](ABC):
|
|
|
61
67
|
|
|
62
68
|
@classmethod
|
|
63
69
|
@abstractmethod
|
|
64
|
-
def multipart_form_factory(cls) ->
|
|
70
|
+
def multipart_form_factory(cls) -> MultipartFormProto:
|
|
65
71
|
pass
|
|
66
72
|
|
|
67
73
|
@classmethod
|
|
@@ -69,8 +75,8 @@ class ABCClient[MultipartForm: MultipartFormProto](ABC):
|
|
|
69
75
|
cls,
|
|
70
76
|
*,
|
|
71
77
|
data: dict[str, typing.Any],
|
|
72
|
-
files:
|
|
73
|
-
) ->
|
|
78
|
+
files: Files | None = None,
|
|
79
|
+
) -> MultipartFormProto:
|
|
74
80
|
multipart_form = cls.multipart_form_factory()
|
|
75
81
|
files = files or {}
|
|
76
82
|
|
|
@@ -92,9 +98,8 @@ class ABCClient[MultipartForm: MultipartFormProto](ABC):
|
|
|
92
98
|
exc_type: type[BaseException],
|
|
93
99
|
exc_val: typing.Any,
|
|
94
100
|
exc_tb: typing.Any,
|
|
95
|
-
) ->
|
|
101
|
+
) -> None:
|
|
96
102
|
await self.close()
|
|
97
|
-
return not bool(exc_val)
|
|
98
103
|
|
|
99
104
|
|
|
100
105
|
__all__ = ("ABCClient",)
|
telegrinder/client/aiohttp.py
CHANGED
|
@@ -1,70 +1,176 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
1
4
|
import ssl
|
|
2
5
|
import typing
|
|
3
6
|
|
|
4
7
|
import aiohttp
|
|
5
8
|
import certifi
|
|
6
|
-
from aiohttp import ClientSession, TCPConnector
|
|
9
|
+
from aiohttp import BasicAuth, ClientSession, TCPConnector
|
|
10
|
+
from aiohttp.hdrs import USER_AGENT
|
|
11
|
+
from aiohttp.http import SERVER_SOFTWARE
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
from telegrinder.__meta__ import __version__
|
|
9
14
|
from telegrinder.client.abc import ABCClient
|
|
15
|
+
from telegrinder.msgspec_utils import json
|
|
10
16
|
|
|
11
17
|
if typing.TYPE_CHECKING:
|
|
12
18
|
from aiohttp import ClientResponse
|
|
13
19
|
|
|
14
20
|
type Data = dict[str, typing.Any] | aiohttp.formdata.FormData
|
|
15
21
|
type Response = ClientResponse
|
|
22
|
+
type Timeout = int | float | aiohttp.ClientTimeout
|
|
23
|
+
type Proxy = str | tuple[str, BasicAuth]
|
|
24
|
+
type ProxyChain = typing.Iterable[Proxy]
|
|
25
|
+
type ProxyType = Proxy | ProxyChain
|
|
26
|
+
|
|
27
|
+
DEFAULT_TIMEOUT: typing.Final[float] = 30.0
|
|
28
|
+
DEFAULT_LIMIT_SIMULTANEOUS_CONNECTIONS: typing.Final[int] = 100
|
|
29
|
+
DEFAULT_TTL_DNS_CACHE: typing.Final[int] = 3600
|
|
30
|
+
DEFAULT_HEADERS: typing.Final[dict[str, str]] = {USER_AGENT: f"{SERVER_SOFTWARE} telegrinder/{__version__}"}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _get_client_timeout(timeout: Timeout, /) -> aiohttp.ClientTimeout:
|
|
34
|
+
return timeout if isinstance(timeout, aiohttp.ClientTimeout) else aiohttp.ClientTimeout(total=float(timeout))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _prepare_proxy_connector(
|
|
38
|
+
proxy: ProxyType,
|
|
39
|
+
/,
|
|
40
|
+
) -> tuple[type[TCPConnector], dict[str, typing.Any] | list[typing.Any]]:
|
|
41
|
+
try:
|
|
42
|
+
from aiohttp_socks import ChainProxyConnector, ProxyConnector, ProxyInfo # type: ignore
|
|
43
|
+
from aiohttp_socks.utils import parse_proxy_url # type: ignore
|
|
44
|
+
except ImportError:
|
|
45
|
+
raise ImportError(
|
|
46
|
+
"Module `aiohttp-socks` is not installed. You can install as follows: pip install aiohttp-socks "
|
|
47
|
+
'or pip install "telegrinder[socks]"',
|
|
48
|
+
) from None
|
|
49
|
+
|
|
50
|
+
match proxy:
|
|
51
|
+
case str() | (str(), BasicAuth()):
|
|
52
|
+
proxy_chain = (proxy,)
|
|
53
|
+
case _:
|
|
54
|
+
proxy_chain = proxy
|
|
55
|
+
|
|
56
|
+
proxy_infos = list() # type: ignore
|
|
57
|
+
|
|
58
|
+
for _proxy in proxy_chain:
|
|
59
|
+
url, basic = (_proxy, None) if isinstance(_proxy, str) else _proxy
|
|
60
|
+
proxy_type, host, port, username, password = parse_proxy_url(url) # type: ignore
|
|
61
|
+
|
|
62
|
+
if basic is not None:
|
|
63
|
+
username, password = basic.login, basic.password # type: ignore
|
|
64
|
+
|
|
65
|
+
proxy_infos.append(
|
|
66
|
+
ProxyInfo( # type: ignore
|
|
67
|
+
proxy_type,
|
|
68
|
+
host,
|
|
69
|
+
port,
|
|
70
|
+
username,
|
|
71
|
+
password,
|
|
72
|
+
rdns=True,
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
(
|
|
78
|
+
ProxyConnector, # type: ignore
|
|
79
|
+
proxy_infos[0]._asdict(), # type: ignore
|
|
80
|
+
)
|
|
81
|
+
if len(proxy_infos) == 1
|
|
82
|
+
else (ChainProxyConnector, proxy_infos)
|
|
83
|
+
) # type: ignore
|
|
16
84
|
|
|
17
85
|
|
|
18
|
-
class AiohttpClient(ABCClient
|
|
86
|
+
class AiohttpClient(ABCClient):
|
|
19
87
|
"""HTTP client based on `aiohttp` module."""
|
|
20
88
|
|
|
21
|
-
CONNECTION_TIMEOUT_ERRORS = (
|
|
22
|
-
aiohttp.client.ServerConnectionError,
|
|
23
|
-
TimeoutError,
|
|
24
|
-
)
|
|
89
|
+
CONNECTION_TIMEOUT_ERRORS = (aiohttp.client.ServerConnectionError,)
|
|
25
90
|
CLIENT_CONNECTION_ERRORS = (
|
|
26
91
|
aiohttp.client.ClientConnectionError,
|
|
27
92
|
aiohttp.client.ClientConnectorError,
|
|
28
93
|
aiohttp.ClientOSError,
|
|
29
94
|
)
|
|
30
95
|
|
|
96
|
+
__slots__ = (
|
|
97
|
+
"session",
|
|
98
|
+
"limit",
|
|
99
|
+
"ttl_dns_cache",
|
|
100
|
+
"session_params",
|
|
101
|
+
"_proxy",
|
|
102
|
+
"_tcp_connector_kwargs",
|
|
103
|
+
"_tcp_connector_class",
|
|
104
|
+
)
|
|
105
|
+
|
|
31
106
|
def __init__(
|
|
32
107
|
self,
|
|
33
108
|
session: ClientSession | None = None,
|
|
34
|
-
|
|
109
|
+
proxy: ProxyType | None = None,
|
|
110
|
+
timeout: Timeout = DEFAULT_TIMEOUT,
|
|
111
|
+
limit: int = DEFAULT_LIMIT_SIMULTANEOUS_CONNECTIONS,
|
|
112
|
+
ttl_dns_cache: int = DEFAULT_TTL_DNS_CACHE,
|
|
35
113
|
**session_params: typing.Any,
|
|
36
114
|
) -> None:
|
|
37
115
|
self.session = session
|
|
116
|
+
self.limit = limit
|
|
117
|
+
self.ttl_dns_cache = ttl_dns_cache
|
|
38
118
|
self.session_params = session_params
|
|
39
|
-
self.
|
|
119
|
+
self._timeout = _get_client_timeout(timeout)
|
|
120
|
+
self._proxy = proxy
|
|
121
|
+
self._tcp_connector_kwargs = dict[str, typing.Any](
|
|
122
|
+
ssl=ssl.create_default_context(cafile=certifi.where()),
|
|
123
|
+
limit=limit,
|
|
124
|
+
ttl_dns_cache=ttl_dns_cache,
|
|
125
|
+
)
|
|
126
|
+
self._tcp_connector_class: type[TCPConnector] = TCPConnector
|
|
127
|
+
|
|
128
|
+
self.session_params.setdefault("headers", DEFAULT_HEADERS)
|
|
129
|
+
self._setup_proxy()
|
|
40
130
|
|
|
41
131
|
def __repr__(self) -> str:
|
|
42
132
|
return "<{}: session={!r}, timeout={}, closed={}>".format(
|
|
43
|
-
self.
|
|
133
|
+
type(self).__name__,
|
|
44
134
|
self.session,
|
|
45
|
-
self.
|
|
135
|
+
self._timeout,
|
|
46
136
|
True if self.session is None else self.session.closed,
|
|
47
137
|
)
|
|
48
138
|
|
|
139
|
+
def __del__(self) -> None:
|
|
140
|
+
if self.session and not self.session.closed:
|
|
141
|
+
if self.session._connector is not None and self.session._connector_owner:
|
|
142
|
+
self.session._connector._close()
|
|
143
|
+
self.session._connector = None
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def multipart_form_factory(cls) -> aiohttp.formdata.FormData:
|
|
147
|
+
return aiohttp.formdata.FormData(quote_fields=False)
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def timeout(self) -> float:
|
|
151
|
+
return float(self._timeout.total or 0.0)
|
|
152
|
+
|
|
49
153
|
async def request_raw(
|
|
50
154
|
self,
|
|
51
155
|
url: str,
|
|
52
156
|
method: str = "GET",
|
|
53
157
|
data: Data | None = None,
|
|
158
|
+
*,
|
|
159
|
+
timeout: Timeout | None = None,
|
|
54
160
|
**kwargs: typing.Any,
|
|
55
161
|
) -> Response:
|
|
56
162
|
if not self.session:
|
|
57
163
|
self.session = ClientSession(
|
|
58
|
-
connector=
|
|
164
|
+
connector=self._tcp_connector_class(**self._tcp_connector_kwargs),
|
|
59
165
|
json_serialize=json.dumps,
|
|
60
166
|
**self.session_params,
|
|
61
167
|
)
|
|
62
168
|
|
|
63
169
|
async with self.session.request(
|
|
64
|
-
url=url,
|
|
65
170
|
method=method,
|
|
171
|
+
url=url,
|
|
66
172
|
data=data,
|
|
67
|
-
timeout=self.timeout,
|
|
173
|
+
timeout=self._timeout if timeout is None else _get_client_timeout(timeout),
|
|
68
174
|
**kwargs,
|
|
69
175
|
) as response:
|
|
70
176
|
await response.read()
|
|
@@ -75,9 +181,11 @@ class AiohttpClient(ABCClient[aiohttp.formdata.FormData]):
|
|
|
75
181
|
url: str,
|
|
76
182
|
method: str = "GET",
|
|
77
183
|
data: Data | None = None,
|
|
184
|
+
*,
|
|
185
|
+
timeout: Timeout | None = None,
|
|
78
186
|
**kwargs: typing.Any,
|
|
79
187
|
) -> dict[str, typing.Any]:
|
|
80
|
-
response = await self.request_raw(url, method, data, **kwargs)
|
|
188
|
+
response = await self.request_raw(url, method, data, timeout=timeout, **kwargs)
|
|
81
189
|
return await response.json(
|
|
82
190
|
encoding="UTF-8",
|
|
83
191
|
loads=json.loads,
|
|
@@ -89,9 +197,11 @@ class AiohttpClient(ABCClient[aiohttp.formdata.FormData]):
|
|
|
89
197
|
url: str,
|
|
90
198
|
method: str = "GET",
|
|
91
199
|
data: Data | None = None,
|
|
200
|
+
*,
|
|
201
|
+
timeout: Timeout | None = None,
|
|
92
202
|
**kwargs: typing.Any,
|
|
93
203
|
) -> str:
|
|
94
|
-
response = await self.request_raw(url, method, data, **kwargs)
|
|
204
|
+
response = await self.request_raw(url, method, data, timeout=timeout, **kwargs)
|
|
95
205
|
return await response.text(encoding="UTF-8")
|
|
96
206
|
|
|
97
207
|
async def request_bytes(
|
|
@@ -99,9 +209,11 @@ class AiohttpClient(ABCClient[aiohttp.formdata.FormData]):
|
|
|
99
209
|
url: str,
|
|
100
210
|
method: str = "GET",
|
|
101
211
|
data: Data | None = None,
|
|
212
|
+
*,
|
|
213
|
+
timeout: Timeout | None = None,
|
|
102
214
|
**kwargs: typing.Any,
|
|
103
215
|
) -> bytes:
|
|
104
|
-
response = await self.request_raw(url, method, data, **kwargs)
|
|
216
|
+
response = await self.request_raw(url, method, data, timeout=timeout, **kwargs)
|
|
105
217
|
if response._body is None:
|
|
106
218
|
await response.read()
|
|
107
219
|
return response._body or bytes()
|
|
@@ -111,24 +223,26 @@ class AiohttpClient(ABCClient[aiohttp.formdata.FormData]):
|
|
|
111
223
|
url: str,
|
|
112
224
|
method: str = "GET",
|
|
113
225
|
data: Data | None = None,
|
|
226
|
+
*,
|
|
227
|
+
timeout: Timeout | None = None,
|
|
114
228
|
**kwargs: typing.Any,
|
|
115
229
|
) -> bytes:
|
|
116
|
-
response = await self.request_raw(url, method, data, **kwargs)
|
|
230
|
+
response = await self.request_raw(url, method, data, timeout=timeout, **kwargs)
|
|
117
231
|
return response._body or bytes()
|
|
118
232
|
|
|
119
|
-
async def close(self) -> None:
|
|
233
|
+
async def close(self, *, gracefully: bool = True) -> None:
|
|
120
234
|
if self.session and not self.session.closed:
|
|
121
235
|
await self.session.close()
|
|
122
236
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
237
|
+
if gracefully:
|
|
238
|
+
# Wait 250 ms for graceful shutdown SSL connections
|
|
239
|
+
await asyncio.sleep(0.250)
|
|
126
240
|
|
|
127
|
-
def
|
|
128
|
-
if self.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
self.
|
|
241
|
+
def _setup_proxy(self) -> None:
|
|
242
|
+
if self._proxy is not None:
|
|
243
|
+
tcp_connector_class, data = _prepare_proxy_connector(self._proxy)
|
|
244
|
+
self._tcp_connector_class = tcp_connector_class
|
|
245
|
+
self._tcp_connector_kwargs.update(data if isinstance(data, dict) else dict(proxy_infos=data))
|
|
132
246
|
|
|
133
247
|
|
|
134
248
|
__all__ = ("AiohttpClient",)
|
telegrinder/client/form_data.py
CHANGED
|
@@ -10,7 +10,7 @@ def encode_form_data(
|
|
|
10
10
|
) -> dict[str, str]:
|
|
11
11
|
context = dict(files=files)
|
|
12
12
|
return {
|
|
13
|
-
k: encoder.encode(v, context=context).
|
|
13
|
+
k: encoder.encode(v, context=context).strip('"') # Remove quoted string
|
|
14
14
|
if not isinstance(v, str)
|
|
15
15
|
else v
|
|
16
16
|
for k, v in data.items()
|
telegrinder/model.py
CHANGED
|
@@ -1,53 +1,31 @@
|
|
|
1
1
|
import keyword
|
|
2
|
-
import
|
|
2
|
+
import types
|
|
3
|
+
from reprlib import recursive_repr
|
|
3
4
|
|
|
4
5
|
import msgspec
|
|
5
|
-
|
|
6
|
+
import typing_extensions as typing
|
|
7
|
+
from fntypes.co import Nothing
|
|
6
8
|
|
|
7
|
-
from telegrinder.msgspec_utils import decoder, encoder,
|
|
8
|
-
|
|
9
|
-
if typing.TYPE_CHECKING:
|
|
10
|
-
from telegrinder.api.error import APIError
|
|
9
|
+
from telegrinder.msgspec_utils import Option, decoder, encoder, struct_asdict
|
|
11
10
|
|
|
11
|
+
UNSET: typing.Final[typing.Any] = typing.cast("typing.Any", msgspec.UNSET)
|
|
12
|
+
"""See [DOCS](https://jcristharif.com/msgspec/api.html#unset) about `msgspec.UNSET`."""
|
|
12
13
|
MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
|
|
13
14
|
"dict": True,
|
|
14
15
|
"rename": {kw + "_": kw for kw in keyword.kwlist},
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
"""Docs: https://jcristharif.com/msgspec/api.html#unset
|
|
18
|
-
|
|
19
|
-
During decoding, if a field isn't explicitly set in the model,
|
|
20
|
-
the default value of `UNSET` will be set instead. This lets downstream
|
|
21
|
-
consumers determine whether a field was left unset, or explicitly set a value."""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def full_result[T](
|
|
25
|
-
result: Result[msgspec.Raw, "APIError"],
|
|
26
|
-
full_t: type[T],
|
|
27
|
-
) -> Result[T, "APIError"]:
|
|
28
|
-
return result.map(lambda v: decoder.decode(v, type=full_t))
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def is_none(value: typing.Any, /) -> typing.TypeGuard[None | Nothing]:
|
|
32
|
-
return value is None or isinstance(value, Nothing)
|
|
17
|
+
INSPECTED_MODEL_FIELDS_KEY: typing.Final[str] = "__inspected_struct_fields__"
|
|
33
18
|
|
|
34
19
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
for k, v in (
|
|
38
|
-
*params.pop("other", {}).items(),
|
|
39
|
-
*params.items(),
|
|
40
|
-
):
|
|
41
|
-
if isinstance(v, Proxy):
|
|
42
|
-
v = v.get()
|
|
43
|
-
if k == "self" or is_none(v):
|
|
44
|
-
continue
|
|
45
|
-
validated_params[k] = v.unwrap() if isinstance(v, Some) else v
|
|
46
|
-
return validated_params
|
|
20
|
+
def is_none(obj: typing.Any, /) -> typing.TypeIs[Nothing | None]:
|
|
21
|
+
return isinstance(obj, Nothing | types.NoneType)
|
|
47
22
|
|
|
48
23
|
|
|
49
24
|
if typing.TYPE_CHECKING:
|
|
50
25
|
|
|
26
|
+
@typing.overload
|
|
27
|
+
def field() -> typing.Any: ...
|
|
28
|
+
|
|
51
29
|
@typing.overload
|
|
52
30
|
def field(*, name: str | None = ...) -> typing.Any: ...
|
|
53
31
|
|
|
@@ -100,6 +78,9 @@ else:
|
|
|
100
78
|
type From[T] = T
|
|
101
79
|
|
|
102
80
|
def field(**kwargs):
|
|
81
|
+
if (default := kwargs.get("default")) is Ellipsis:
|
|
82
|
+
kwargs["default"] = UNSET
|
|
83
|
+
|
|
103
84
|
kwargs.pop("converter", None)
|
|
104
85
|
return _field(**kwargs)
|
|
105
86
|
|
|
@@ -108,14 +89,50 @@ else:
|
|
|
108
89
|
class Model(msgspec.Struct, **MODEL_CONFIG):
|
|
109
90
|
if not typing.TYPE_CHECKING:
|
|
110
91
|
|
|
111
|
-
def __post_init__(self):
|
|
112
|
-
for field in self.__struct_fields__:
|
|
113
|
-
if is_none(getattr(self, field)):
|
|
114
|
-
setattr(self, field, UNSET)
|
|
115
|
-
|
|
116
92
|
def __getattribute__(self, name, /):
|
|
117
|
-
|
|
118
|
-
|
|
93
|
+
class_ = type(self)
|
|
94
|
+
val = object.__getattribute__(self, name)
|
|
95
|
+
|
|
96
|
+
if name not in class_.__struct_fields__:
|
|
97
|
+
return val
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
(field_info := class_.get_fields().get(name)) is not None
|
|
101
|
+
and isinstance(field_info.type, msgspec.inspect.CustomType)
|
|
102
|
+
and issubclass(field_info.type.cls, Option)
|
|
103
|
+
):
|
|
104
|
+
return Nothing() if val is UNSET else val
|
|
105
|
+
|
|
106
|
+
if val is UNSET:
|
|
107
|
+
raise AttributeError(f"{class_.__name__!r} object has no attribute {name!r}")
|
|
108
|
+
|
|
109
|
+
return val
|
|
110
|
+
|
|
111
|
+
def __post_init__(self) -> None:
|
|
112
|
+
for field, value in struct_asdict(self, exclude_unset=False).items():
|
|
113
|
+
if is_none(value):
|
|
114
|
+
setattr(self, field, UNSET)
|
|
115
|
+
|
|
116
|
+
@recursive_repr()
|
|
117
|
+
def __repr__(self) -> str:
|
|
118
|
+
return "{}({})".format(
|
|
119
|
+
type(self).__name__,
|
|
120
|
+
", ".join(
|
|
121
|
+
f"{f}={val!r}"
|
|
122
|
+
for f, val in struct_asdict(self, exclude_unset=False, unset_as_nothing=True).items()
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def get_fields(cls) -> types.MappingProxyType[str, msgspec.inspect.Field]:
|
|
128
|
+
if (model_fields := getattr(cls, INSPECTED_MODEL_FIELDS_KEY, None)) is not None:
|
|
129
|
+
return model_fields
|
|
130
|
+
|
|
131
|
+
model_fields = types.MappingProxyType[str, msgspec.inspect.Field](
|
|
132
|
+
{f.name: f for f in msgspec.inspect.type_info(cls).fields}, # type: ignore
|
|
133
|
+
)
|
|
134
|
+
setattr(cls, INSPECTED_MODEL_FIELDS_KEY, model_fields)
|
|
135
|
+
return model_fields
|
|
119
136
|
|
|
120
137
|
@classmethod
|
|
121
138
|
def from_data[**P, T](cls: typing.Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
|
|
@@ -137,7 +154,7 @@ class Model(msgspec.Struct, **MODEL_CONFIG):
|
|
|
137
154
|
) -> dict[str, typing.Any]:
|
|
138
155
|
if dct_name not in self.__dict__:
|
|
139
156
|
self.__dict__[dct_name] = (
|
|
140
|
-
|
|
157
|
+
struct_asdict(self)
|
|
141
158
|
if not full
|
|
142
159
|
else encoder.to_builtins(self.to_dict(exclude_fields=exclude_fields), order="deterministic")
|
|
143
160
|
)
|
|
@@ -155,9 +172,6 @@ class Model(msgspec.Struct, **MODEL_CONFIG):
|
|
|
155
172
|
*,
|
|
156
173
|
exclude_fields: set[str] | None = None,
|
|
157
174
|
) -> dict[str, typing.Any]:
|
|
158
|
-
""":param exclude_fields: Model field names to exclude from the dictionary representation of this model.
|
|
159
|
-
:return: A dictionary representation of this model.
|
|
160
|
-
"""
|
|
161
175
|
return self._to_dict("model_as_dict", exclude_fields or set(), full=False)
|
|
162
176
|
|
|
163
177
|
def to_full_dict(
|
|
@@ -165,49 +179,7 @@ class Model(msgspec.Struct, **MODEL_CONFIG):
|
|
|
165
179
|
*,
|
|
166
180
|
exclude_fields: set[str] | None = None,
|
|
167
181
|
) -> dict[str, typing.Any]:
|
|
168
|
-
""":param exclude_fields: Model field names to exclude from the dictionary representation of this model.
|
|
169
|
-
:return: A dictionary representation of this model including all models, structs, custom types.
|
|
170
|
-
"""
|
|
171
182
|
return self._to_dict("model_as_full_dict", exclude_fields or set(), full=True)
|
|
172
183
|
|
|
173
184
|
|
|
174
|
-
|
|
175
|
-
def __init__(self, cfg: "_ProxiedDict[T]", key: str) -> None:
|
|
176
|
-
self.key = key
|
|
177
|
-
self.cfg = cfg
|
|
178
|
-
|
|
179
|
-
def get(self) -> typing.Any | None:
|
|
180
|
-
return self.cfg._defaults.get(self.key)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
class _ProxiedDict[T]:
|
|
184
|
-
def __init__(self, tp: type[T]) -> None:
|
|
185
|
-
self.type = tp
|
|
186
|
-
self._defaults = {}
|
|
187
|
-
|
|
188
|
-
def __setattribute__(self, name: str, value: typing.Any, /) -> None:
|
|
189
|
-
self._defaults[name] = value
|
|
190
|
-
|
|
191
|
-
def __getitem__(self, key: str, /) -> None:
|
|
192
|
-
return Proxy(self, key) # type: ignore
|
|
193
|
-
|
|
194
|
-
def __setitem__(self, key: str, value: typing.Any, /) -> None:
|
|
195
|
-
self._defaults[key] = value
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if typing.TYPE_CHECKING:
|
|
199
|
-
|
|
200
|
-
def ProxiedDict[T](typed_dct: type[T]) -> T | _ProxiedDict[T]: # noqa: N802
|
|
201
|
-
...
|
|
202
|
-
else:
|
|
203
|
-
ProxiedDict = _ProxiedDict
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
__all__ = (
|
|
207
|
-
"MODEL_CONFIG",
|
|
208
|
-
"Model",
|
|
209
|
-
"ProxiedDict",
|
|
210
|
-
"Proxy",
|
|
211
|
-
"full_result",
|
|
212
|
-
"get_params",
|
|
213
|
-
)
|
|
185
|
+
__all__ = ("MODEL_CONFIG", "UNSET", "Model", "field", "is_none")
|