From df4ba87dfcce421663d578f68efd7bd2782e8b9c Mon Sep 17 00:00:00 2001 From: mpa Date: Wed, 6 May 2020 02:42:54 +0400 Subject: [PATCH] feat(timeout): implement (class-bound, instance-bound, request-bound) session timeout for requests. fix docs config, fix aiohttp session docs links. --- aiogram/api/client/session/aiohttp.py | 4 ++- aiogram/api/client/session/base.py | 34 ++++++++++++++----- aiogram/api/methods/base.py | 3 +- docs/api/client/session/aiohttp.md | 4 +-- mkdocs.yml | 1 + .../test_session/test_base_session.py | 25 ++++++++++++-- 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/aiogram/api/client/session/aiohttp.py b/aiogram/api/client/session/aiohttp.py index be5861ce..774e7186 100644 --- a/aiogram/api/client/session/aiohttp.py +++ b/aiogram/api/client/session/aiohttp.py @@ -132,7 +132,9 @@ class AiohttpSession(BaseSession): url = self.api.api_url(token=token, method=request.method) form = self.build_form_data(request) - async with session.post(url, data=form, timeout=call.request_timeout) as resp: + async with session.post( + url, data=form, timeout=call.request_timeout or self.timeout + ) as resp: raw_result = await resp.json(loads=self.json_loads) response = call.build_response(raw_result) diff --git a/aiogram/api/client/session/base.py b/aiogram/api/client/session/base.py index b99e9e4b..97137e7c 100644 --- a/aiogram/api/client/session/base.py +++ b/aiogram/api/client/session/base.py @@ -4,7 +4,7 @@ import abc import datetime import json from types import TracebackType -from typing import Any, AsyncGenerator, Callable, Optional, Type, TypeVar, Union +from typing import Any, AsyncGenerator, Callable, ClassVar, Optional, Type, TypeVar, Union from aiogram.utils.exceptions import TelegramAPIError @@ -12,14 +12,18 @@ from ...methods import Response, TelegramMethod from ..telegram import PRODUCTION, TelegramAPIServer T = TypeVar("T") -_JSON_LOADS = Callable[..., Any] -_JSON_DUMPS = Callable[..., str] +_JsonLoads = Callable[..., Any] +_JsonDumps = Callable[..., str] class BaseSession(abc.ABC): + # global session timeout + default_timeout: ClassVar[float] = 60.0 + _api: TelegramAPIServer - _json_loads: _JSON_LOADS - _json_dumps: _JSON_DUMPS + _json_loads: _JsonLoads + _json_dumps: _JsonDumps + _timeout: float @property def api(self) -> TelegramAPIServer: @@ -30,21 +34,33 @@ class BaseSession(abc.ABC): self._api = value @property - def json_loads(self) -> _JSON_LOADS: + def json_loads(self) -> _JsonLoads: return getattr(self, "_json_loads", json.loads) # type: ignore @json_loads.setter - def json_loads(self, value: _JSON_LOADS) -> None: + def json_loads(self, value: _JsonLoads) -> None: self._json_loads = value # type: ignore @property - def json_dumps(self) -> _JSON_DUMPS: + def json_dumps(self) -> _JsonDumps: return getattr(self, "_json_dumps", json.dumps) # type: ignore @json_dumps.setter - def json_dumps(self, value: _JSON_DUMPS) -> None: + def json_dumps(self, value: _JsonDumps) -> None: self._json_dumps = value # type: ignore + @property + def timeout(self) -> float: + return getattr(self, "_timeout", self.__class__.default_timeout) # type: ignore + + @timeout.setter + def timeout(self, value: float) -> None: + self._timeout = value + + @timeout.deleter + def timeout(self) -> None: + del self._timeout + @classmethod def raise_for_status(cls, response: Response[T]) -> None: if response.ok: diff --git a/aiogram/api/methods/base.py b/aiogram/api/methods/base.py index fc57a2ff..78158192 100644 --- a/aiogram/api/methods/base.py +++ b/aiogram/api/methods/base.py @@ -13,7 +13,6 @@ if TYPE_CHECKING: # pragma: no cover from ..client.bot import Bot T = TypeVar("T") -DEFAULT_REQUEST_TIMEOUT_SECONDS = 60.0 class Request(BaseModel): @@ -56,7 +55,7 @@ class TelegramMethod(abc.ABC, BaseModel, Generic[T]): def build_request(self) -> Request: # pragma: no cover pass - request_timeout: float = DEFAULT_REQUEST_TIMEOUT_SECONDS + request_timeout: Optional[float] = None def dict(self, **kwargs: Any) -> Any: # override dict of pydantic.BaseModel to overcome exporting request_timeout field diff --git a/docs/api/client/session/aiohttp.md b/docs/api/client/session/aiohttp.md index 9eab5ede..223ad468 100644 --- a/docs/api/client/session/aiohttp.md +++ b/docs/api/client/session/aiohttp.md @@ -1,6 +1,6 @@ # Aiohttp session -AiohttpSession represents a wrapper-class around `ClientSession` from [aiohttp]('https://pypi.org/project/aiohttp/') +AiohttpSession represents a wrapper-class around `ClientSession` from [aiohttp](https://pypi.org/project/aiohttp/ "PyPi repository"){target=_blank} Currently `AiohttpSession` is a default session used in `aiogram.Bot` @@ -17,7 +17,7 @@ Bot('token', session=session) ## Proxy requests in AiohttpSession -In order to use AiohttpSession with proxy connector you have to install [aiohttp-socks]('https://pypi.org/project/aiohttp-socks/') +In order to use AiohttpSession with proxy connector you have to install [aiohttp-socks](https://pypi.org/project/aiohttp-socks/ "PyPi repository"){target=_blank} Binding session to bot: ```python diff --git a/mkdocs.yml b/mkdocs.yml index 0d77e640..527d0561 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -35,6 +35,7 @@ markdown_extensions: - pymdownx.inlinehilite - markdown_include.include: base_path: docs + - attr_list nav: - index.md diff --git a/tests/test_api/test_client/test_session/test_base_session.py b/tests/test_api/test_client/test_session/test_base_session.py index 43f1d7d5..35dcfa8e 100644 --- a/tests/test_api/test_client/test_session/test_base_session.py +++ b/tests/test_api/test_client/test_session/test_base_session.py @@ -54,12 +54,33 @@ class TestBaseSession: assert session.json_loads == custom_loads == session._json_loads different_session = CustomSession() - assert all(not hasattr(different_session, attr) for attr in ("_json_loads", "_json_dumps", "_api")) + assert all( + not hasattr(different_session, attr) for attr in ("_json_loads", "_json_dumps", "_api") + ) + + def test_timeout(self): + session = CustomSession() + assert session.timeout == session.default_timeout == CustomSession.default_timeout + + session.default_timeout = float(65.0_0) # mypy will complain + assert session.timeout != session.default_timeout + + CustomSession.default_timeout = float(68.0_0) + assert session.timeout == CustomSession.default_timeout + + session.timeout = float(71.0_0) + assert session.timeout != session.default_timeout + del session.timeout + CustomSession.default_timeout = session.default_timeout + 100 + assert ( + session.timeout != BaseSession.default_timeout + and session.timeout == CustomSession.default_timeout + ) def test_init_custom_api(self): api = TelegramAPIServer( base="http://example.com/{token}/{method}", - file="http://example.com/{token}/file/{path{", + file="http://example.com/{token}/file/{path}", ) session = CustomSession() session.api = api