Added more aliases, refactor CallbackData factory, added base exceptions classification mechanism

This commit is contained in:
Alex Root Junior 2021-05-25 00:56:44 +03:00
parent 9451a085d1
commit f022b4441c
18 changed files with 364 additions and 664 deletions

View file

@ -302,7 +302,7 @@ class Bot(ContextInstanceMixin["Bot"]):
:param method:
:return:
"""
return await self.session.make_request(self, method, timeout=request_timeout)
return await self.session(self, method, timeout=request_timeout)
def __hash__(self) -> int:
"""

View file

@ -10,7 +10,6 @@ from typing import (
Optional,
Tuple,
Type,
TypeVar,
Union,
cast,
)
@ -19,12 +18,12 @@ from aiohttp import BasicAuth, ClientSession, FormData, TCPConnector
from aiogram.methods import Request, TelegramMethod
from ...methods.base import TelegramType
from .base import UNSET, BaseSession
if TYPE_CHECKING: # pragma: no cover
from ..bot import Bot
T = TypeVar("T")
_ProxyBasic = Union[str, Tuple[str, BasicAuth]]
_ProxyChain = Iterable[_ProxyBasic]
_ProxyType = Union[_ProxyChain, _ProxyBasic]
@ -76,6 +75,8 @@ def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"]
class AiohttpSession(BaseSession):
def __init__(self, proxy: Optional[_ProxyType] = None):
super().__init__()
self._session: Optional[ClientSession] = None
self._connector_type: Type[TCPConnector] = TCPConnector
self._connector_init: Dict[str, Any] = {}
@ -86,7 +87,7 @@ class AiohttpSession(BaseSession):
try:
self._setup_proxy_connector(proxy)
except ImportError as exc: # pragma: no cover
raise UserWarning(
raise RuntimeError(
"In order to use aiohttp client for proxy requests, install "
"https://pypi.org/project/aiohttp-socks/"
) from exc
@ -130,8 +131,8 @@ class AiohttpSession(BaseSession):
return form
async def make_request(
self, bot: Bot, call: TelegramMethod[T], timeout: Optional[int] = None
) -> T:
self, bot: Bot, call: TelegramMethod[TelegramType], timeout: Optional[int] = None
) -> TelegramType:
session = await self.create_session()
request = call.build_request(bot)
@ -141,11 +142,10 @@ class AiohttpSession(BaseSession):
async with session.post(
url, data=form, timeout=self.timeout if timeout is None else timeout
) as resp:
raw_result = await resp.json(loads=self.json_loads)
raw_result = await resp.text()
response = call.build_response(raw_result)
self.raise_for_status(response)
return cast(T, response.result)
response = self.check_response(method=call, status_code=resp.status, content=raw_result)
return cast(TelegramType, response.result)
async def stream_content(
self, url: str, timeout: int, chunk_size: int

View file

@ -3,32 +3,44 @@ from __future__ import annotations
import abc
import datetime
import json
from functools import partial
from types import TracebackType
from typing import (
TYPE_CHECKING,
Any,
AsyncGenerator,
Awaitable,
Callable,
ClassVar,
List,
Optional,
Type,
TypeVar,
Union,
cast,
)
from aiogram.utils.exceptions import TelegramAPIError
from aiogram.utils.exceptions.base import TelegramAPIError
from aiogram.utils.helper import Default
from ...methods import Response, TelegramMethod
from ...types import UNSET
from ...methods.base import TelegramType
from ...types import UNSET, TelegramObject
from ...utils.exceptions.special import MigrateToChat, RetryAfter
from ..errors_middleware import RequestErrorMiddleware
from ..telegram import PRODUCTION, TelegramAPIServer
if TYPE_CHECKING: # pragma: no cover
from ..bot import Bot
T = TypeVar("T")
_JsonLoads = Callable[..., Any]
_JsonDumps = Callable[..., str]
NextRequestMiddlewareType = Callable[
["Bot", TelegramMethod[TelegramObject]], Awaitable[Response[TelegramObject]]
]
RequestMiddlewareType = Callable[
["Bot", TelegramMethod[TelegramType], NextRequestMiddlewareType],
Awaitable[Response[TelegramType]],
]
class BaseSession(abc.ABC):
@ -43,16 +55,40 @@ class BaseSession(abc.ABC):
timeout: Default[float] = Default(fget=lambda self: float(self.__class__.default_timeout))
"""Session scope request timeout"""
@classmethod
def raise_for_status(cls, response: Response[T]) -> None:
errors_middleware: ClassVar[RequestErrorMiddleware] = RequestErrorMiddleware()
def __init__(self) -> None:
self.middlewares: List[RequestMiddlewareType[TelegramObject]] = [
self.errors_middleware,
]
def check_response(
self, method: TelegramMethod[TelegramType], status_code: int, content: str
) -> Response[TelegramType]:
"""
Check response status
:param response: Response instance
"""
json_data = self.json_loads(content)
response = method.build_response(json_data)
if response.ok:
return
raise TelegramAPIError(response.description)
return response
description = cast(str, response.description)
if parameters := response.parameters:
if parameters.retry_after:
raise RetryAfter(
method=method, message=description, retry_after=parameters.retry_after
)
if parameters.migrate_to_chat_id:
raise MigrateToChat(
method=method,
message=description,
migrate_to_chat_id=parameters.migrate_to_chat_id,
)
raise TelegramAPIError(
method=method,
message=description,
)
@abc.abstractmethod
async def close(self) -> None: # pragma: no cover
@ -63,8 +99,8 @@ class BaseSession(abc.ABC):
@abc.abstractmethod
async def make_request(
self, bot: Bot, method: TelegramMethod[T], timeout: Optional[int] = UNSET
) -> T: # pragma: no cover
self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET
) -> TelegramType: # pragma: no cover
"""
Make request to Telegram Bot API
@ -111,6 +147,20 @@ class BaseSession(abc.ABC):
return {k: self.clean_json(v) for k, v in value.items() if v is not None}
return value
def middleware(
self, middleware: RequestMiddlewareType[TelegramObject]
) -> RequestMiddlewareType[TelegramObject]:
self.middlewares.append(middleware)
return middleware
async def __call__(
self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET
) -> TelegramType:
middleware = partial(self.make_request, timeout=timeout)
for m in reversed(self.middlewares):
middleware = partial(m, make_request=middleware) # type: ignore
return await middleware(bot, method)
async def __aenter__(self) -> BaseSession:
return self