From 2620a6547c888c0bb9d622a706764d45d6381c42 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 12 Dec 2021 18:15:36 +0200 Subject: [PATCH] Rework session DI and files path wrapper --- CHANGES/776.misc | 1 + CHANGES/778.misc | 1 + CHANGES/779.misc | 2 + aiogram/client/bot.py | 2 +- aiogram/client/session/aiohttp.py | 4 +- aiogram/client/session/base.py | 34 +++++++++------ aiogram/client/telegram.py | 42 +++++++++++++++++-- tests/test_api/test_client/test_api_server.py | 33 ++++++++++++++- .../test_session/test_base_session.py | 19 --------- 9 files changed, 98 insertions(+), 40 deletions(-) create mode 100644 CHANGES/776.misc create mode 100644 CHANGES/778.misc create mode 100644 CHANGES/779.misc diff --git a/CHANGES/776.misc b/CHANGES/776.misc new file mode 100644 index 00000000..2dd825f5 --- /dev/null +++ b/CHANGES/776.misc @@ -0,0 +1 @@ +Check :code:`destiny` in case of no :code:`with_destiny` enabled in RedisStorage key builder diff --git a/CHANGES/778.misc b/CHANGES/778.misc new file mode 100644 index 00000000..7fe2f8dd --- /dev/null +++ b/CHANGES/778.misc @@ -0,0 +1 @@ +Stop using feature from #336. From now settings of client-session should be placed as initializer arguments instead of changing instance attributes. diff --git a/CHANGES/779.misc b/CHANGES/779.misc new file mode 100644 index 00000000..7ff24c3e --- /dev/null +++ b/CHANGES/779.misc @@ -0,0 +1,2 @@ +Make TelegramAPIServer files wrapper in local mode bi-directional (server-client, client-server) +Now you can convert local path to server path and server path to local path. diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py index 74bc7d4a..d6378215 100644 --- a/aiogram/client/bot.py +++ b/aiogram/client/bot.py @@ -270,7 +270,7 @@ class Bot(ContextInstanceMixin["Bot"]): close_stream = False if self.session.api.is_local: stream = self.__aiofiles_reader( - self.session.api.wrap_local_file(file_path), chunk_size=chunk_size + str(self.session.api.wrap_local_file.to_local(file_path)), chunk_size=chunk_size ) close_stream = True else: diff --git a/aiogram/client/session/aiohttp.py b/aiogram/client/session/aiohttp.py index ab15dbf1..9d7df940 100644 --- a/aiogram/client/session/aiohttp.py +++ b/aiogram/client/session/aiohttp.py @@ -76,8 +76,8 @@ def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"] class AiohttpSession(BaseSession): - def __init__(self, proxy: Optional[_ProxyType] = None): - super().__init__() + def __init__(self, proxy: Optional[_ProxyType] = None, **kwargs: Any) -> None: + super().__init__(**kwargs) self._session: Optional[ClientSession] = None self._connector_type: Type[TCPConnector] = TCPConnector diff --git a/aiogram/client/session/base.py b/aiogram/client/session/base.py index 0519efca..d5a95622 100644 --- a/aiogram/client/session/base.py +++ b/aiogram/client/session/base.py @@ -12,7 +12,7 @@ from typing import ( AsyncGenerator, Awaitable, Callable, - ClassVar, + Final, List, Optional, Type, @@ -33,7 +33,6 @@ from aiogram.exceptions import ( TelegramServerError, TelegramUnauthorizedError, ) -from aiogram.utils.helper import Default from ...methods import Response, TelegramMethod from ...methods.base import TelegramType @@ -58,20 +57,29 @@ RequestMiddlewareType = Union[ ], ] +DEFAULT_TIMEOUT: Final[float] = 60.0 + class BaseSession(abc.ABC): - api: Default[TelegramAPIServer] = Default(PRODUCTION) - """Telegram Bot API URL patterns""" - json_loads: Default[_JsonLoads] = Default(json.loads) - """JSON loader""" - json_dumps: Default[_JsonDumps] = Default(json.dumps) - """JSON dumper""" - default_timeout: ClassVar[float] = 60.0 - """Default timeout""" - timeout: Default[float] = Default(fget=lambda self: float(self.__class__.default_timeout)) - """Session scope request timeout""" + def __init__( + self, + api: TelegramAPIServer = PRODUCTION, + json_loads: _JsonLoads = json.loads, + json_dumps: _JsonDumps = json.dumps, + timeout: float = DEFAULT_TIMEOUT, + ) -> None: + """ + + :param api: Telegram Bot API URL patterns + :param json_loads: JSON loader + :param json_dumps: JSON dumper + :param timeout: Session scope request timeout + """ + self.api = api + self.json_loads = json_loads + self.json_dumps = json_dumps + self.timeout = timeout - def __init__(self) -> None: self.middlewares: List[RequestMiddlewareType[TelegramObject]] = [] def check_response( diff --git a/aiogram/client/telegram.py b/aiogram/client/telegram.py index 2363e24e..502f950f 100644 --- a/aiogram/client/telegram.py +++ b/aiogram/client/telegram.py @@ -1,11 +1,45 @@ +from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import Any, Protocol +from pathlib import Path +from typing import Any, Union -class WrapLocalFileCallbackCallbackProtocol(Protocol): # pragma: no cover - def __call__(self, value: str) -> str: +class FilesPathWrapper(ABC): + @abstractmethod + def to_local(self, path: Union[Path, str]) -> Union[Path, str]: pass + @abstractmethod + def to_server(self, path: Union[Path, str]) -> Union[Path, str]: + pass + + +class BareFilesPathWrapper(FilesPathWrapper): + def to_local(self, path: Union[Path, str]) -> Union[Path, str]: + return path + + def to_server(self, path: Union[Path, str]) -> Union[Path, str]: + return path + + +class SimpleFilesPathWrapper(FilesPathWrapper): + def __init__(self, server_path: Path, local_path: Path) -> None: + self.server_path = server_path + self.local_path = local_path + + @classmethod + def _resolve( + cls, base1: Union[Path, str], base2: Union[Path, str], value: Union[Path, str] + ) -> Path: + relative = Path(value).relative_to(base1) + return base2 / relative + + def to_local(self, path: Union[Path, str]) -> Union[Path, str]: + return self._resolve(base1=self.server_path, base2=self.local_path, value=path) + + def to_server(self, path: Union[Path, str]) -> Union[Path, str]: + return self._resolve(base1=self.local_path, base2=self.server_path, value=path) + @dataclass(frozen=True) class TelegramAPIServer: @@ -19,7 +53,7 @@ class TelegramAPIServer: """Files URL""" is_local: bool = False """Mark this server is in `local mode `_.""" - wrap_local_file: WrapLocalFileCallbackCallbackProtocol = lambda v: v + wrap_local_file: FilesPathWrapper = BareFilesPathWrapper() """Callback to wrap files path in local mode""" def api_url(self, token: str, method: str) -> str: diff --git a/tests/test_api/test_client/test_api_server.py b/tests/test_api/test_client/test_api_server.py index 6faf0c0f..118ea630 100644 --- a/tests/test_api/test_client/test_api_server.py +++ b/tests/test_api/test_client/test_api_server.py @@ -1,4 +1,11 @@ -from aiogram.client.telegram import PRODUCTION, TelegramAPIServer +from pathlib import Path + +from aiogram.client.telegram import ( + PRODUCTION, + BareFilesPathWrapper, + SimpleFilesPathWrapper, + TelegramAPIServer, +) class TestAPIServer: @@ -19,3 +26,27 @@ class TestAPIServer: assert method_url == "http://localhost:8081/bot42:TEST/apiMethod" assert file_url == "http://localhost:8081/file/bot42:TEST/path" assert local_server.is_local + + +class TestBareFilesPathWrapper: + def test_to_local(self): + wrapper = BareFilesPathWrapper() + assert wrapper.to_local("/path/to/file.dat") == "/path/to/file.dat" + + def test_to_server(self): + wrapper = BareFilesPathWrapper() + assert wrapper.to_server("/path/to/file.dat") == "/path/to/file.dat" + + +class TestSimpleFilesPathWrapper: + def test_to_local(self): + wrapper = SimpleFilesPathWrapper(Path("/etc/telegram-bot-api/data"), Path("/opt/app/data")) + assert wrapper.to_local("/etc/telegram-bot-api/data/documents/file.dat") == Path( + "/opt/app/data/documents/file.dat" + ) + + def test_to_server(self): + wrapper = SimpleFilesPathWrapper(Path("/etc/telegram-bot-api/data"), Path("/opt/app/data")) + assert wrapper.to_server("/opt/app/data/documents/file.dat") == Path( + "/etc/telegram-bot-api/data/documents/file.dat" + ) 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 543187df..a6c5f700 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 @@ -74,25 +74,6 @@ class TestBaseSession: session.json_loads = custom_loads assert session.json_loads == custom_loads - 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}",