From ea6a02bf97616e5eaec8bb45eba42492d28f3418 Mon Sep 17 00:00:00 2001 From: mpa Date: Sun, 3 May 2020 00:53:25 +0400 Subject: [PATCH 1/9] refactor(sessions): remove BaseSession's initializer, add timeout ommitable field to base method model --- aiogram/api/client/session/aiohttp.py | 46 +++++++++------- aiogram/api/client/session/base.py | 52 ++++++++++++------- aiogram/api/methods/base.py | 11 ++++ .../test_session/test_aiohttp_session.py | 16 ++++++ .../test_session/test_base_session.py | 4 +- 5 files changed, 90 insertions(+), 39 deletions(-) diff --git a/aiogram/api/client/session/aiohttp.py b/aiogram/api/client/session/aiohttp.py index c3c1bba5..be5861ce 100644 --- a/aiogram/api/client/session/aiohttp.py +++ b/aiogram/api/client/session/aiohttp.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import ( Any, AsyncGenerator, - Callable, Dict, Iterable, List, @@ -15,11 +14,11 @@ from typing import ( cast, ) -from aiohttp import BasicAuth, ClientSession, ClientTimeout, FormData, TCPConnector +from aiohttp import BasicAuth, ClientSession, FormData, TCPConnector from aiogram.api.methods import Request, TelegramMethod -from .base import PRODUCTION, BaseSession, TelegramAPIServer +from .base import BaseSession T = TypeVar("T") _ProxyBasic = Union[str, Tuple[str, BasicAuth]] @@ -72,34 +71,42 @@ def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"] class AiohttpSession(BaseSession): - def __init__( - self, - api: TelegramAPIServer = PRODUCTION, - json_loads: Optional[Callable[..., Any]] = None, - json_dumps: Optional[Callable[..., str]] = None, - proxy: Optional[_ProxyType] = None, - ): - super(AiohttpSession, self).__init__( - api=api, json_loads=json_loads, json_dumps=json_dumps, proxy=proxy - ) + def __init__(self, proxy: Optional[_ProxyType] = None): self._session: Optional[ClientSession] = None self._connector_type: Type[TCPConnector] = TCPConnector self._connector_init: Dict[str, Any] = {} + self._should_reset_connector = True # flag determines connector state + self._proxy: Optional[_ProxyType] = None - if self.proxy: + if proxy is not None: try: - self._connector_type, self._connector_init = _prepare_connector( - cast(_ProxyType, self.proxy) - ) + self._setup_proxy_connector(proxy) except ImportError as exc: # pragma: no cover raise UserWarning( "In order to use aiohttp client for proxy requests, install " "https://pypi.org/project/aiohttp-socks/" ) from exc + def _setup_proxy_connector(self, proxy: _ProxyType) -> None: + self._connector_type, self._connector_init = _prepare_connector(proxy) + self._proxy = proxy + + @property + def proxy(self) -> Optional[_ProxyType]: + return self._proxy + + @proxy.setter + def proxy(self, proxy: _ProxyType) -> None: + self._setup_proxy_connector(proxy) + self._should_reset_connector = True + async def create_session(self) -> ClientSession: + if self._should_reset_connector: + await self.close() + if self._session is None or self._session.closed: self._session = ClientSession(connector=self._connector_type(**self._connector_init)) + self._should_reset_connector = False return self._session @@ -125,7 +132,7 @@ 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) as resp: + async with session.post(url, data=form, timeout=call.request_timeout) as resp: raw_result = await resp.json(loads=self.json_loads) response = call.build_response(raw_result) @@ -136,9 +143,8 @@ class AiohttpSession(BaseSession): self, url: str, timeout: int, chunk_size: int ) -> AsyncGenerator[bytes, None]: session = await self.create_session() - client_timeout = ClientTimeout(total=timeout) - async with session.get(url, timeout=client_timeout) as resp: + async with session.get(url, timeout=timeout) as resp: async for chunk in resp.content.iter_chunked(chunk_size): yield chunk diff --git a/aiogram/api/client/session/base.py b/aiogram/api/client/session/base.py index 83e7f3ff..450b958d 100644 --- a/aiogram/api/client/session/base.py +++ b/aiogram/api/client/session/base.py @@ -16,26 +16,42 @@ PT = TypeVar("PT") class BaseSession(abc.ABC): - def __init__( - self, - api: Optional[TelegramAPIServer] = None, - json_loads: Optional[Callable[..., Any]] = None, - json_dumps: Optional[Callable[..., str]] = None, - proxy: Optional[PT] = None, - ) -> None: - if api is None: - api = PRODUCTION - if json_loads is None: - json_loads = json.loads - if json_dumps is None: - json_dumps = json.dumps + _api: TelegramAPIServer + _json_loads: Callable[..., Any] + _json_dumps: Callable[..., str] - self.api = api - self.json_loads = json_loads - self.json_dumps = json_dumps - self.proxy = proxy + @property + def api(self) -> TelegramAPIServer: # pragma: no cover + if not hasattr(self, "_api"): + return PRODUCTION + return self._api - def raise_for_status(self, response: Response[T]) -> None: + @api.setter + def api(self, value: TelegramAPIServer) -> None: # pragma: no cover + self._api = value + + @property + def json_loads(self) -> Callable[..., Any]: # pragma: no cover + if not hasattr(self, "_json_loads"): + return json.loads + return self._json_loads + + @json_loads.setter + def json_loads(self, value: Callable[..., Any]) -> None: # pragma: no cover + self._json_loads = value # type: ignore + + @property + def json_dumps(self) -> Callable[..., str]: # pragma: no cover + if not hasattr(self, "_json_dumps"): + return json.dumps + return self._json_dumps + + @json_dumps.setter + def json_dumps(self, value: Callable[..., str]) -> None: # pragma: no cover + self._json_dumps = value # type: ignore + + @classmethod + def raise_for_status(cls, response: Response[T]) -> None: if response.ok: return raise TelegramAPIError(response.description) diff --git a/aiogram/api/methods/base.py b/aiogram/api/methods/base.py index 72eafa05..fc57a2ff 100644 --- a/aiogram/api/methods/base.py +++ b/aiogram/api/methods/base.py @@ -13,6 +13,7 @@ if TYPE_CHECKING: # pragma: no cover from ..client.bot import Bot T = TypeVar("T") +DEFAULT_REQUEST_TIMEOUT_SECONDS = 60.0 class Request(BaseModel): @@ -55,6 +56,16 @@ class TelegramMethod(abc.ABC, BaseModel, Generic[T]): def build_request(self) -> Request: # pragma: no cover pass + request_timeout: float = DEFAULT_REQUEST_TIMEOUT_SECONDS + + def dict(self, **kwargs: Any) -> Any: + # override dict of pydantic.BaseModel to overcome exporting request_timeout field + exclude = kwargs.pop("exclude", set()) + if isinstance(exclude, set): + exclude.add("request_timeout") + + return super().dict(exclude=exclude, **kwargs) + def build_response(self, data: Dict[str, Any]) -> Response[T]: # noinspection PyTypeChecker return Response[self.__returning__](**data) # type: ignore diff --git a/tests/test_api/test_client/test_session/test_aiohttp_session.py b/tests/test_api/test_client/test_session/test_aiohttp_session.py index 2587a686..e7716f9a 100644 --- a/tests/test_api/test_client/test_session/test_aiohttp_session.py +++ b/tests/test_api/test_client/test_session/test_aiohttp_session.py @@ -83,6 +83,22 @@ class TestAiohttpSession: aiohttp_session = await session.create_session() assert isinstance(aiohttp_session.connector, aiohttp_socks.ChainProxyConnector) + @pytest.mark.asyncio + async def test_reset_connector(self): + session = AiohttpSession() + assert session._should_reset_connector + await session.create_session() + assert session._should_reset_connector is False + await session.close() + assert session._should_reset_connector is False + + assert session.proxy is None + session.proxy = "socks5://auth:auth@proxy.url/" + assert session._should_reset_connector + await session.create_session() + assert session._should_reset_connector is False + await session.close() + @pytest.mark.asyncio async def test_close_session(self): session = AiohttpSession() 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 4c86f9da..9d775123 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 @@ -40,8 +40,10 @@ class TestBaseSession: base="http://example.com/{token}/{method}", file="http://example.com/{token}/file/{path{", ) - session = CustomSession(api=api) + session = CustomSession() + session.api = api assert session.api == api + assert "example.com" in session.api.base def test_prepare_value(self): session = CustomSession() From 2c1de9cdbc991d0988b0b0d84669676af8d39e9b Mon Sep 17 00:00:00 2001 From: mpa Date: Sun, 3 May 2020 01:17:40 +0400 Subject: [PATCH 2/9] dummy commit | run actions From 2adc19724d1e2605dc6ba797da6c5e399031c199 Mon Sep 17 00:00:00 2001 From: mpa Date: Wed, 6 May 2020 00:04:50 +0400 Subject: [PATCH 3/9] refactor(props): simplify some descriptors with tests for them, remove silly "nocovers" --- aiogram/api/client/session/base.py | 31 ++++++++----------- .../test_session/test_base_session.py | 21 +++++++++++++ 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/aiogram/api/client/session/base.py b/aiogram/api/client/session/base.py index 450b958d..b99e9e4b 100644 --- a/aiogram/api/client/session/base.py +++ b/aiogram/api/client/session/base.py @@ -12,42 +12,37 @@ from ...methods import Response, TelegramMethod from ..telegram import PRODUCTION, TelegramAPIServer T = TypeVar("T") -PT = TypeVar("PT") +_JSON_LOADS = Callable[..., Any] +_JSON_DUMPS = Callable[..., str] class BaseSession(abc.ABC): _api: TelegramAPIServer - _json_loads: Callable[..., Any] - _json_dumps: Callable[..., str] + _json_loads: _JSON_LOADS + _json_dumps: _JSON_DUMPS @property - def api(self) -> TelegramAPIServer: # pragma: no cover - if not hasattr(self, "_api"): - return PRODUCTION - return self._api + def api(self) -> TelegramAPIServer: + return getattr(self, "_api", PRODUCTION) # type: ignore @api.setter - def api(self, value: TelegramAPIServer) -> None: # pragma: no cover + def api(self, value: TelegramAPIServer) -> None: self._api = value @property - def json_loads(self) -> Callable[..., Any]: # pragma: no cover - if not hasattr(self, "_json_loads"): - return json.loads - return self._json_loads + def json_loads(self) -> _JSON_LOADS: + return getattr(self, "_json_loads", json.loads) # type: ignore @json_loads.setter - def json_loads(self, value: Callable[..., Any]) -> None: # pragma: no cover + def json_loads(self, value: _JSON_LOADS) -> None: self._json_loads = value # type: ignore @property - def json_dumps(self) -> Callable[..., str]: # pragma: no cover - if not hasattr(self, "_json_dumps"): - return json.dumps - return self._json_dumps + def json_dumps(self) -> _JSON_DUMPS: + return getattr(self, "_json_dumps", json.dumps) # type: ignore @json_dumps.setter - def json_dumps(self, value: Callable[..., str]) -> None: # pragma: no cover + def json_dumps(self, value: _JSON_DUMPS) -> None: self._json_dumps = value # type: ignore @classmethod 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 9d775123..43f1d7d5 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 @@ -1,4 +1,5 @@ import datetime +import json from typing import AsyncContextManager, AsyncGenerator import pytest @@ -35,6 +36,26 @@ class TestBaseSession: session = CustomSession() assert session.api == PRODUCTION + def test_default_props(self): + session = CustomSession() + assert session.api == PRODUCTION + assert session.json_loads == json.loads + assert session.json_dumps == json.dumps + + def custom_loads(*_): + return json.loads + + def custom_dumps(*_): + return json.dumps + + session.json_dumps = custom_dumps + assert session.json_dumps == custom_dumps == session._json_dumps + session.json_loads = custom_loads + 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")) + def test_init_custom_api(self): api = TelegramAPIServer( base="http://example.com/{token}/{method}", From df4ba87dfcce421663d578f68efd7bd2782e8b9c Mon Sep 17 00:00:00 2001 From: mpa Date: Wed, 6 May 2020 02:42:54 +0400 Subject: [PATCH 4/9] 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 From 0aa8069dc478c32a4adeabfebc170104cf211fae Mon Sep 17 00:00:00 2001 From: mpa Date: Wed, 6 May 2020 03:05:43 +0400 Subject: [PATCH 5/9] fix(sessions): make timeout deleter more clever in particular cases --- aiogram/api/client/session/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aiogram/api/client/session/base.py b/aiogram/api/client/session/base.py index 97137e7c..8213e4c3 100644 --- a/aiogram/api/client/session/base.py +++ b/aiogram/api/client/session/base.py @@ -59,7 +59,8 @@ class BaseSession(abc.ABC): @timeout.deleter def timeout(self) -> None: - del self._timeout + if hasattr(self, "_timeout"): + del self._timeout @classmethod def raise_for_status(cls, response: Response[T]) -> None: From 4124770b0e55c7db2439deca9ddbf872266a8f90 Mon Sep 17 00:00:00 2001 From: mpa Date: Sun, 10 May 2020 03:02:31 +0400 Subject: [PATCH 6/9] refactor(handler): rename observers Rename observers but with backward compatibility, relevant documentation --- aiogram/__init__.py | 2 +- aiogram/dispatcher/dispatcher.py | 2 +- aiogram/dispatcher/router.py | 205 +++++++++++++++--- .../class_based_handlers/callback_query.md | 4 +- .../chosen_inline_result.md | 4 +- docs/dispatcher/class_based_handlers/error.md | 4 +- .../class_based_handlers/inline_query.md | 2 +- .../class_based_handlers/message.md | 10 +- docs/dispatcher/class_based_handlers/poll.md | 4 +- .../pre_checkout_query.md | 4 +- .../class_based_handlers/shipping_query.md | 4 +- docs/dispatcher/dispatcher.md | 2 +- docs/dispatcher/filters/command.md | 2 +- docs/dispatcher/filters/index.md | 4 +- docs/dispatcher/router.md | 26 +-- docs/index.md | 4 +- .../test_types/test_reply_keyboard_remove.py | 1 + tests/test_dispatcher/test_deprecated.py | 49 +++++ tests/test_dispatcher/test_dispatcher.py | 16 +- .../test_event/test_observer.py | 26 +-- tests/test_dispatcher/test_router.py | 32 +-- 21 files changed, 297 insertions(+), 110 deletions(-) create mode 100644 tests/test_dispatcher/test_deprecated.py diff --git a/aiogram/__init__.py b/aiogram/__init__.py index 2b93781a..0b2a8cc7 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -28,5 +28,5 @@ __all__ = ( "handler", ) -__version__ = '3.0.0a4' +__version__ = "3.0.0a4" __api_version__ = "4.8" diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 06cce1b1..8960769d 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -57,7 +57,7 @@ class Dispatcher(Router): Bot.set_current(bot) try: - async for result in self.update_handler.trigger(update, bot=bot, **kwargs): + async for result in self.update.trigger(update, bot=bot, **kwargs): handled = True yield result finally: diff --git a/aiogram/dispatcher/router.py b/aiogram/dispatcher/router.py index dab48c25..371c490d 100644 --- a/aiogram/dispatcher/router.py +++ b/aiogram/dispatcher/router.py @@ -24,31 +24,25 @@ class Router: self.sub_routers: List[Router] = [] # Observers - self.update_handler = TelegramEventObserver(router=self, event_name="update") - self.message_handler = TelegramEventObserver(router=self, event_name="message") - self.edited_message_handler = TelegramEventObserver( - router=self, event_name="edited_message" - ) - self.channel_post_handler = TelegramEventObserver(router=self, event_name="channel_post") - self.edited_channel_post_handler = TelegramEventObserver( + self.update = TelegramEventObserver(router=self, event_name="update") + self.message = TelegramEventObserver(router=self, event_name="message") + self.edited_message = TelegramEventObserver(router=self, event_name="edited_message") + self.channel_post = TelegramEventObserver(router=self, event_name="channel_post") + self.edited_channel_post = TelegramEventObserver( router=self, event_name="edited_channel_post" ) - self.inline_query_handler = TelegramEventObserver(router=self, event_name="inline_query") - self.chosen_inline_result_handler = TelegramEventObserver( + self.inline_query = TelegramEventObserver(router=self, event_name="inline_query") + self.chosen_inline_result = TelegramEventObserver( router=self, event_name="chosen_inline_result" ) - self.callback_query_handler = TelegramEventObserver( - router=self, event_name="callback_query" - ) - self.shipping_query_handler = TelegramEventObserver( - router=self, event_name="shipping_query" - ) - self.pre_checkout_query_handler = TelegramEventObserver( + self.callback_query = TelegramEventObserver(router=self, event_name="callback_query") + self.shipping_query = TelegramEventObserver(router=self, event_name="shipping_query") + self.pre_checkout_query = TelegramEventObserver( router=self, event_name="pre_checkout_query" ) - self.poll_handler = TelegramEventObserver(router=self, event_name="poll") - self.poll_answer_handler = TelegramEventObserver(router=self, event_name="poll_answer") - self.errors_handler = TelegramEventObserver(router=self, event_name="error") + self.poll = TelegramEventObserver(router=self, event_name="poll") + self.poll_answer = TelegramEventObserver(router=self, event_name="poll_answer") + self.errors = TelegramEventObserver(router=self, event_name="error") self.middleware = MiddlewareManager(router=self) @@ -56,23 +50,23 @@ class Router: self.shutdown = EventObserver() self.observers: Dict[str, TelegramEventObserver] = { - "update": self.update_handler, - "message": self.message_handler, - "edited_message": self.edited_message_handler, - "channel_post": self.channel_post_handler, - "edited_channel_post": self.edited_channel_post_handler, - "inline_query": self.inline_query_handler, - "chosen_inline_result": self.chosen_inline_result_handler, - "callback_query": self.callback_query_handler, - "shipping_query": self.shipping_query_handler, - "pre_checkout_query": self.pre_checkout_query_handler, - "poll": self.poll_handler, - "poll_answer": self.poll_answer_handler, - "error": self.errors_handler, + "update": self.update, + "message": self.message, + "edited_message": self.edited_message, + "channel_post": self.channel_post, + "edited_channel_post": self.edited_channel_post, + "inline_query": self.inline_query, + "chosen_inline_result": self.chosen_inline_result, + "callback_query": self.callback_query, + "shipping_query": self.shipping_query, + "pre_checkout_query": self.pre_checkout_query, + "poll": self.poll, + "poll_answer": self.poll_answer, + "error": self.errors, } # Root handler - self.update_handler.register(self._listen_update) + self.update.register(self._listen_update) # Builtin filters if use_builtin_filters: @@ -299,7 +293,7 @@ class Router: raise except Exception as e: - async for result in self.errors_handler.trigger(e, **kwargs): + async for result in self.errors.trigger(e, **kwargs): return result raise @@ -336,3 +330,146 @@ class Router: pass for router in self.sub_routers: await router.emit_shutdown(*args, **kwargs) + + @property + def update_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.update_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.update(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.update + + @property + def message_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.message_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.message(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.message + + @property + def edited_message_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.edited_message_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.edited_message(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.edited_message + + @property + def channel_post_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.channel_post_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.channel_post(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.channel_post + + @property + def edited_channel_post_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.edited_channel_post_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.edited_channel_post(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.edited_channel_post + + @property + def inline_query_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.inline_query_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.inline_query(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.inline_query + + @property + def chosen_inline_result_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.chosen_inline_result_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.chosen_inline_result(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.chosen_inline_result + + @property + def callback_query_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.callback_query_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.callback_query(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.callback_query + + @property + def shipping_query_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.shipping_query_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.shipping_query(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.shipping_query + + @property + def pre_checkout_query_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.pre_checkout_query_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.pre_checkout_query(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.pre_checkout_query + + @property + def poll_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.poll_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.poll(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.poll + + @property + def poll_answer_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.poll_answer_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.poll_answer(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.poll_answer + + @property + def errors_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.errors_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.errors(...)`", + DeprecationWarning, + stacklevel=2, + ) + + return self.errors diff --git a/docs/dispatcher/class_based_handlers/callback_query.md b/docs/dispatcher/class_based_handlers/callback_query.md index a9b5549f..67722f6c 100644 --- a/docs/dispatcher/class_based_handlers/callback_query.md +++ b/docs/dispatcher/class_based_handlers/callback_query.md @@ -8,7 +8,7 @@ from aiogram.handlers import CallbackQueryHandler ... -@router.callback_query_handler() +@router.callback_query() class MyHandler(CallbackQueryHandler): async def handle(self) -> Any: ... @@ -26,4 +26,4 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some - [BaseHandler](basics.md#basehandler) - [CallbackQuery](../../api/types/callback_query.md) -- [Router.callback_query_handler](../router.md#callback-query) +- [Router.callback_query](../router.md#callback-query) diff --git a/docs/dispatcher/class_based_handlers/chosen_inline_result.md b/docs/dispatcher/class_based_handlers/chosen_inline_result.md index 44dd353d..78784e9c 100644 --- a/docs/dispatcher/class_based_handlers/chosen_inline_result.md +++ b/docs/dispatcher/class_based_handlers/chosen_inline_result.md @@ -8,7 +8,7 @@ from aiogram.handlers import ChosenInlineResultHandler ... -@router.chosen_inline_result_handler() +@router.chosen_inline_result() class MyHandler(ChosenInlineResultHandler): async def handle(self) -> Any: ... @@ -25,4 +25,4 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some - [BaseHandler](basics.md#basehandler) - [ChosenInlineResult](../../api/types/chosen_inline_result.md) -- [Router.chosen_inline_result_handler](../router.md#chosen-inline-query) +- [Router.chosen_inline_result](../router.md#chosen-inline-query) diff --git a/docs/dispatcher/class_based_handlers/error.md b/docs/dispatcher/class_based_handlers/error.md index 842689a5..6188f98e 100644 --- a/docs/dispatcher/class_based_handlers/error.md +++ b/docs/dispatcher/class_based_handlers/error.md @@ -8,7 +8,7 @@ from aiogram.handlers import ErrorHandler ... -@router.errors_handler() +@router.errors() class MyHandler(ErrorHandler): async def handle(self) -> Any: log.exception( @@ -28,5 +28,5 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some ## Related pages - [BaseHandler](basics.md#basehandler) -- [Router.errors_handler](../router.md#errors) +- [Router.errors](../router.md#errors) - [Filters](../filters/exception.md) diff --git a/docs/dispatcher/class_based_handlers/inline_query.md b/docs/dispatcher/class_based_handlers/inline_query.md index c348be43..cc705a6f 100644 --- a/docs/dispatcher/class_based_handlers/inline_query.md +++ b/docs/dispatcher/class_based_handlers/inline_query.md @@ -7,7 +7,7 @@ from aiogram.handlers import InlineQueryHandler ... -@router.inline_query_handler() +@router.inline_query() class MyHandler(InlineQueryHandler): async def handle(self) -> Any: ... diff --git a/docs/dispatcher/class_based_handlers/message.md b/docs/dispatcher/class_based_handlers/message.md index 093e07eb..aee8c07f 100644 --- a/docs/dispatcher/class_based_handlers/message.md +++ b/docs/dispatcher/class_based_handlers/message.md @@ -8,7 +8,7 @@ from aiogram.handlers import MessageHandler ... -@router.message_handler() +@router.message() class MyHandler(MessageHandler): async def handle(self) -> Any: return SendMessage(chat_id=self.chat.id, text="PASS") @@ -26,7 +26,7 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some - [BaseHandler](basics.md#basehandler) - [Message](../../api/types/message.md) -- [Router.message_handler](../router.md#message) -- [Router.edited_message_handler](../router.md#edited-message) -- [Router.channel_post_handler](../router.md#channel-post) -- [Router.edited_channel_post_handler](../router.md#edited-channel-post) +- [Router.message](../router.md#message) +- [Router.edited_message](../router.md#edited-message) +- [Router.channel_post](../router.md#channel-post) +- [Router.edited_channel_post](../router.md#edited-channel-post) diff --git a/docs/dispatcher/class_based_handlers/poll.md b/docs/dispatcher/class_based_handlers/poll.md index 88dc02aa..46a647cf 100644 --- a/docs/dispatcher/class_based_handlers/poll.md +++ b/docs/dispatcher/class_based_handlers/poll.md @@ -8,7 +8,7 @@ from aiogram.handlers import PollHandler ... -@router.poll_handler() +@router.poll() class MyHandler(PollHandler): async def handle(self) -> Any: ... @@ -25,4 +25,4 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some - [BaseHandler](basics.md#basehandler) - [Poll](../../api/types/poll.md) -- [Router.poll_handler](../router.md#poll) +- [Router.poll](../router.md#poll) diff --git a/docs/dispatcher/class_based_handlers/pre_checkout_query.md b/docs/dispatcher/class_based_handlers/pre_checkout_query.md index 90cf6c2f..ee88c6f3 100644 --- a/docs/dispatcher/class_based_handlers/pre_checkout_query.md +++ b/docs/dispatcher/class_based_handlers/pre_checkout_query.md @@ -8,7 +8,7 @@ from aiogram.handlers import PreCheckoutQueryHandler ... -@router.pre_checkout_query_handler() +@router.pre_checkout_query() class MyHandler(PreCheckoutQueryHandler): async def handle(self) -> Any: ... @@ -24,4 +24,4 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some - [BaseHandler](basics.md#basehandler) - [PreCheckoutQuery](../../api/types/pre_checkout_query.md) -- [Router.pre_checkout_query_handler](../router.md#pre-checkout-query) +- [Router.pre_checkout_query](../router.md#pre-checkout-query) diff --git a/docs/dispatcher/class_based_handlers/shipping_query.md b/docs/dispatcher/class_based_handlers/shipping_query.md index d6d70555..d26a9c2b 100644 --- a/docs/dispatcher/class_based_handlers/shipping_query.md +++ b/docs/dispatcher/class_based_handlers/shipping_query.md @@ -8,7 +8,7 @@ from aiogram.handlers import ShippingQueryHandler ... -@router.shipping_query_handler() +@router.shipping_query() class MyHandler(ShippingQueryHandler): async def handle(self) -> Any: ... @@ -24,4 +24,4 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some - [BaseHandler](basics.md#basehandler) - [ShippingQuery](../../api/types/shipping_query.md) -- [Router.shipping_query_handler](../router.md#shipping-query) +- [Router.shipping_query](../router.md#shipping-query) diff --git a/docs/dispatcher/dispatcher.md b/docs/dispatcher/dispatcher.md index d450df92..462d0748 100644 --- a/docs/dispatcher/dispatcher.md +++ b/docs/dispatcher/dispatcher.md @@ -21,7 +21,7 @@ Example: ```python3 dp = Dispatcher() -@dp.message_handler() +@dp.message() async def message_handler(message: types.Message) -> None: await SendMessage(chat_id=message.from_user.id, text=message.text) ``` diff --git a/docs/dispatcher/filters/command.md b/docs/dispatcher/filters/command.md index 5d8ab771..cd5013c6 100644 --- a/docs/dispatcher/filters/command.md +++ b/docs/dispatcher/filters/command.md @@ -19,7 +19,7 @@ Works only with [Message](../../api/types/message.md) events which have the `tex 1. Handle command by regexp pattern: `#!python3 Command(commands=[re.compile(r"item_(\d+)")])` 1. Match command by multiple variants: `#!python3 Command(commands=["item", re.compile(r"item_(\d+)")])` 1. Handle commands in public chats intended for other bots: `#!python3 Command(commands=["command"], commands)` -1. As keyword argument in registerer: `#!python3 @router.message_handler(commands=["help"])` +1. As keyword argument in registerer: `#!python3 @router.message(commands=["help"])` !!! warning Command cannot include spaces or any whitespace diff --git a/docs/dispatcher/filters/index.md b/docs/dispatcher/filters/index.md index 79dd72bc..76df56a2 100644 --- a/docs/dispatcher/filters/index.md +++ b/docs/dispatcher/filters/index.md @@ -53,9 +53,9 @@ class MyText(BaseFilter): return message.text == self.my_text -router.message_handler.bind_filter(MyText) +router.message.bind_filter(MyText) -@router.message_handler(my_text="hello") +@router.message(my_text="hello") async def my_handler(message: Message): ... ``` diff --git a/docs/dispatcher/router.md b/docs/dispatcher/router.md index 84d18ed9..39c20fdd 100644 --- a/docs/dispatcher/router.md +++ b/docs/dispatcher/router.md @@ -34,91 +34,91 @@ Here is list of available observers and examples how to register handlers (In ex ### Update ```python3 -@router.update_handler() +@router.update() async def message_handler(update: types.Update) -> Any: pass ``` Should be used for handling [updates](../api/types/update.md). By default Router is already have an update handler which route all event types to another observers. ### Message ```python3 -@router.message_handler() +@router.message() async def message_handler(message: types.Message) -> Any: pass ``` Is useful for handling [message](../api/types/message.md) ### Edited message ```python3 -@router.edited_message_handler() +@router.edited_message() async def edited_message_handler(edited_message: types.Message) -> Any: pass ``` Is useful for handling [edited messages](../api/types/message.md) ### Channel post ```python3 -@router.channel_post_handler() +@router.channel_post() async def channel_post_handler(channel_post: types.Message) -> Any: pass ``` Is useful for handling [channel posts](../api/types/message.md) ### Edited channel post ```python3 -@router.edited_channel_post_handler() +@router.edited_channel_post() async def edited_channel_post_handler(edited_channel_post: types.Message) -> Any: pass ``` Is useful for handling [edited channel posts](../api/types/message.md) ### Inline query ```python3 -@router.inline_query_handler() +@router.inline_query() async def inline_query_handler(inline_query: types.Message) -> Any: pass ``` Is useful for handling [inline query](../api/types/inline_query.md) ### Chosen inline query ```python3 -@router.chosen_inline_result_handler() +@router.chosen_inline_result() async def chosen_inline_result_handler(chosen_inline_result: types.ChosenInlineResult) -> Any: pass ``` Is useful for handling [chosen inline query](../api/types/chosen_inline_result.md) ### Callback query ```python3 -@router.callback_query_handler() +@router.callback_query() async def callback_query_handler(callback_query: types.CallbackQuery) -> Any: pass ``` Is useful for handling [callback query's](../api/types/callback_query.md) ### Shipping query ```python3 -@router.shipping_query_handler() +@router.shipping_query() async def shipping_query_handler(shipping_query: types.ShippingQuery) -> Any: pass ``` Is useful for handling [shipping query](../api/types/shipping_query.md) ### Pre checkout query ```python3 -@router.pre_checkout_query_handler() +@router.pre_checkout_query() async def pre_checkout_query_handler(pre_checkout_query: types.PreCheckoutQuery) -> Any: pass ``` Is useful for handling [pre-checkout query](../api/types/pre_checkout_query.md) ### Poll ```python3 -@router.poll_handler() +@router.poll() async def poll_handler(poll: types.Poll) -> Any: pass ``` Is useful for handling [polls](../api/types/poll.md) ### Poll answer ```python3 -@router.poll_answer_handler() +@router.poll_answer() async def poll_answer_handler(poll_answer: types.PollAnswer) -> Any: pass ``` Is useful for handling [polls answers](../api/types/poll_answer.md) ### Errors ```python3 -@router.errors_handler() +@router.errors() async def error_handler(exception: Exception) -> Any: pass ``` Is useful for handling errors from other handlers diff --git a/docs/index.md b/docs/index.md index 06c9a3b0..9529d0ac 100644 --- a/docs/index.md +++ b/docs/index.md @@ -44,7 +44,7 @@ TOKEN = "42:TOKEN" dp = Dispatcher() -@dp.message_handler(commands=["start"]) +@dp.message(commands=["start"]) class MyHandler(MessageHandler): """ This handler receive messages with `/start` command @@ -54,7 +54,7 @@ class MyHandler(MessageHandler): await self.event.answer(f"Hello, {self.from_user.full_name}!") -@dp.message_handler(content_types=[types.ContentType.ANY]) +@dp.message(content_types=[types.ContentType.ANY]) async def echo_handler(message: types.Message, bot: Bot): """ Handler will forward received message back to the sender diff --git a/tests/test_api/test_types/test_reply_keyboard_remove.py b/tests/test_api/test_types/test_reply_keyboard_remove.py index bfc669bd..03eb5e49 100644 --- a/tests/test_api/test_types/test_reply_keyboard_remove.py +++ b/tests/test_api/test_types/test_reply_keyboard_remove.py @@ -1,4 +1,5 @@ import pytest + from aiogram.api.types import ReplyKeyboardRemove diff --git a/tests/test_dispatcher/test_deprecated.py b/tests/test_dispatcher/test_deprecated.py new file mode 100644 index 00000000..98eb87cd --- /dev/null +++ b/tests/test_dispatcher/test_deprecated.py @@ -0,0 +1,49 @@ +import pytest + +from aiogram.dispatcher.event.observer import TelegramEventObserver +from aiogram.dispatcher.router import Router + +OBSERVERS = { + "callback_query", + "channel_post", + "chosen_inline_result", + "edited_channel_post", + "edited_message", + "errors", + "inline_query", + "message", + "poll", + "poll_answer", + "pre_checkout_query", + "shipping_query", + "update", +} + + +def test_deprecated_handlers_name(): + from aiogram import __version__ + + minor_partial = int(__version__.split(".")[1]) + + if minor_partial >= 2: + do_assert = pytest.raises(AttributeError) + else: + do_assert = pytest.warns(DeprecationWarning) + + router = Router() + + async def _(__): + ... + + with do_assert: + for decor in OBSERVERS: + getattr(router, decor + "_handler") + + assert all( + isinstance(getattr(router, handler + "_handler"), TelegramEventObserver) + for handler in OBSERVERS + ) + + assert all( + isinstance(getattr(router, handler), TelegramEventObserver) for handler in OBSERVERS + ) diff --git a/tests/test_dispatcher/test_dispatcher.py b/tests/test_dispatcher/test_dispatcher.py index ae4a7947..46a53db7 100644 --- a/tests/test_dispatcher/test_dispatcher.py +++ b/tests/test_dispatcher/test_dispatcher.py @@ -55,7 +55,7 @@ class TestDispatcher: dp = Dispatcher() bot = Bot("42:TEST") - @dp.message_handler() + @dp.message() async def my_handler(message: Message, **kwargs): assert "bot" in kwargs assert isinstance(kwargs["bot"], Bot) @@ -86,7 +86,7 @@ class TestDispatcher: dp = Dispatcher() bot = Bot("42:TEST") - @dp.message_handler() + @dp.message() async def my_handler(message: Message): assert message.text == "test" return message.text @@ -142,7 +142,7 @@ class TestDispatcher: async def test_process_update_handled(self, bot: MockedBot): dispatcher = Dispatcher() - @dispatcher.update_handler() + @dispatcher.update() async def update_handler(update: Update): pass @@ -152,7 +152,7 @@ class TestDispatcher: async def test_process_update_call_request(self, bot: MockedBot): dispatcher = Dispatcher() - @dispatcher.update_handler() + @dispatcher.update() async def update_handler(update: Update): return GetMe() @@ -167,7 +167,7 @@ class TestDispatcher: async def test_process_update_exception(self, bot: MockedBot, caplog): dispatcher = Dispatcher() - @dispatcher.update_handler() + @dispatcher.update() async def update_handler(update: Update): raise Exception("Kaboom!") @@ -229,7 +229,7 @@ class TestDispatcher: @pytest.mark.asyncio async def test_feed_webhook_update_fast_process(self, bot: MockedBot): dispatcher = Dispatcher() - dispatcher.message_handler.register(simple_message_handler) + dispatcher.message.register(simple_message_handler) response = await dispatcher.feed_webhook_update(bot, RAW_UPDATE, _timeout=2) assert isinstance(response, dict) @@ -251,7 +251,7 @@ class TestDispatcher: warnings.simplefilter("always") dispatcher = Dispatcher() - dispatcher.message_handler.register(simple_message_handler) + dispatcher.message.register(simple_message_handler) with patch( "aiogram.dispatcher.dispatcher.Dispatcher._silent_call_request", @@ -267,7 +267,7 @@ class TestDispatcher: warnings.simplefilter("always") dispatcher = Dispatcher() - dispatcher.message_handler.register(invalid_message_handler) + dispatcher.message.register(invalid_message_handler) response = await dispatcher.feed_webhook_update(bot, RAW_UPDATE, _timeout=1) assert response is None diff --git a/tests/test_dispatcher/test_event/test_observer.py b/tests/test_dispatcher/test_event/test_observer.py index a4029197..c1364676 100644 --- a/tests/test_dispatcher/test_event/test_observer.py +++ b/tests/test_dispatcher/test_event/test_observer.py @@ -109,14 +109,14 @@ class TestTelegramEventObserver: router1.include_router(router2) router2.include_router(router3) - router1.message_handler.bind_filter(MyFilter1) - router1.message_handler.bind_filter(MyFilter2) - router2.message_handler.bind_filter(MyFilter2) - router3.message_handler.bind_filter(MyFilter3) + router1.message.bind_filter(MyFilter1) + router1.message.bind_filter(MyFilter2) + router2.message.bind_filter(MyFilter2) + router3.message.bind_filter(MyFilter3) - filters_chain1 = list(router1.message_handler._resolve_filters_chain()) - filters_chain2 = list(router2.message_handler._resolve_filters_chain()) - filters_chain3 = list(router3.message_handler._resolve_filters_chain()) + filters_chain1 = list(router1.message._resolve_filters_chain()) + filters_chain2 = list(router2.message._resolve_filters_chain()) + filters_chain3 = list(router3.message._resolve_filters_chain()) assert MyFilter1 in filters_chain1 assert MyFilter1 in filters_chain2 @@ -128,7 +128,7 @@ class TestTelegramEventObserver: def test_resolve_filters(self): router = Router(use_builtin_filters=False) - observer = router.message_handler + observer = router.message observer.bind_filter(MyFilter1) resolved = observer.resolve_filters({"test": "PASS"}) @@ -149,7 +149,7 @@ class TestTelegramEventObserver: def test_register(self): router = Router(use_builtin_filters=False) - observer = router.message_handler + observer = router.message observer.bind_filter(MyFilter1) assert observer.register(my_handler) == my_handler @@ -174,7 +174,7 @@ class TestTelegramEventObserver: def test_register_decorator(self): router = Router(use_builtin_filters=False) - observer = router.message_handler + observer = router.message @observer() async def my_handler(event: Any): @@ -186,7 +186,7 @@ class TestTelegramEventObserver: @pytest.mark.asyncio async def test_trigger(self): router = Router(use_builtin_filters=False) - observer = router.message_handler + observer = router.message observer.bind_filter(MyFilter1) observer.register(my_handler, test="ok") @@ -211,7 +211,7 @@ class TestTelegramEventObserver: ) def test_register_filters_via_decorator(self, count, handler, filters): router = Router(use_builtin_filters=False) - observer = router.message_handler + observer = router.message for index in range(count): wrapped_handler = functools.partial(handler, index=index) @@ -227,7 +227,7 @@ class TestTelegramEventObserver: @pytest.mark.asyncio async def test_trigger_right_context_in_handlers(self): router = Router(use_builtin_filters=False) - observer = router.message_handler + observer = router.message observer.register( pipe_handler, lambda event: {"a": 1}, lambda event: False ) # {"a": 1} should not be in result diff --git a/tests/test_dispatcher/test_router.py b/tests/test_dispatcher/test_router.py index 2d26a445..303efbb3 100644 --- a/tests/test_dispatcher/test_router.py +++ b/tests/test_dispatcher/test_router.py @@ -79,18 +79,18 @@ class TestRouter: def test_observers_config(self): router = Router() - assert router.update_handler.handlers - assert router.update_handler.handlers[0].callback == router._listen_update - assert router.observers["message"] == router.message_handler - assert router.observers["edited_message"] == router.edited_message_handler - assert router.observers["channel_post"] == router.channel_post_handler - assert router.observers["edited_channel_post"] == router.edited_channel_post_handler - assert router.observers["inline_query"] == router.inline_query_handler - assert router.observers["chosen_inline_result"] == router.chosen_inline_result_handler - assert router.observers["callback_query"] == router.callback_query_handler - assert router.observers["shipping_query"] == router.shipping_query_handler - assert router.observers["pre_checkout_query"] == router.pre_checkout_query_handler - assert router.observers["poll"] == router.poll_handler + assert router.update.handlers + assert router.update.handlers[0].callback == router._listen_update + assert router.observers["message"] == router.message + assert router.observers["edited_message"] == router.edited_message + assert router.observers["channel_post"] == router.channel_post + assert router.observers["edited_channel_post"] == router.edited_channel_post + assert router.observers["inline_query"] == router.inline_query + assert router.observers["chosen_inline_result"] == router.chosen_inline_result + assert router.observers["callback_query"] == router.callback_query + assert router.observers["shipping_query"] == router.shipping_query + assert router.observers["pre_checkout_query"] == router.pre_checkout_query + assert router.observers["poll"] == router.poll @pytest.mark.asyncio @pytest.mark.parametrize( @@ -341,7 +341,7 @@ class TestRouter: router3 = Router() router1.include_router(router2) router1.include_router(router3) - observer = router3.message_handler + observer = router3.message @observer() async def my_handler(event: Message, **kwargs: Any): @@ -429,7 +429,7 @@ class TestRouter: router = Router() root_router.include_router(router) - @router.message_handler() + @router.message() async def message_handler(message: Message): raise Exception("KABOOM") @@ -452,7 +452,7 @@ class TestRouter: chat=update.message.chat, ) - @root_router.errors_handler() + @root_router.errors() async def root_error_handler(exception: Exception): return exception @@ -466,7 +466,7 @@ class TestRouter: assert isinstance(response, Exception) assert str(response) == "KABOOM" - @router.errors_handler() + @router.errors() async def error_handler(exception: Exception): return "KABOOM" From 6cbf9cdde6419280e12387693cb46bff67e407c5 Mon Sep 17 00:00:00 2001 From: mpa Date: Sun, 10 May 2020 13:21:05 +0400 Subject: [PATCH 7/9] tests(deprecated-observer): clean code remove empty possible func. add new condition for version major --- tests/test_dispatcher/test_deprecated.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/test_dispatcher/test_deprecated.py b/tests/test_dispatcher/test_deprecated.py index 98eb87cd..63c2b4de 100644 --- a/tests/test_dispatcher/test_deprecated.py +++ b/tests/test_dispatcher/test_deprecated.py @@ -23,18 +23,15 @@ OBSERVERS = { def test_deprecated_handlers_name(): from aiogram import __version__ - minor_partial = int(__version__.split(".")[1]) + major, minor = map(int, __version__.split(".")[:-1]) - if minor_partial >= 2: + if minor >= 2 and major >= 3: # version >=3.2.* do_assert = pytest.raises(AttributeError) - else: + else: # for versions <=3.2.* (we don't care if major is lesser than `3`) do_assert = pytest.warns(DeprecationWarning) router = Router() - async def _(__): - ... - with do_assert: for decor in OBSERVERS: getattr(router, decor + "_handler") From c083fcd99a10e13457e25f3f23f04ef283a78021 Mon Sep 17 00:00:00 2001 From: mpa Date: Tue, 12 May 2020 16:07:16 +0400 Subject: [PATCH 8/9] tests(deprecated): add new check_deprecated context manager for version check, use mark.parametrize from pytest for observer deprecation tests --- poetry.lock | 7 +++++- pyproject.toml | 1 + tests/deprecated.py | 26 ++++++++++++++++++++++ tests/test_dispatcher/test_deprecated.py | 28 ++++++------------------ 4 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 tests/deprecated.py diff --git a/poetry.lock b/poetry.lock index ca646b19..d5f77452 100644 --- a/poetry.lock +++ b/poetry.lock @@ -946,7 +946,7 @@ fast = ["uvloop"] proxy = ["aiohttp-socks"] [metadata] -content-hash = "cd83723d922c4c0a645ef6f9f78447f83f2c1a933be928f8174ef897970acb2a" +content-hash = "57137b60a539ba01e8df533db976e2f3eadec37e717cbefbe775dc021a8c2714" python-versions = "^3.7" [metadata.files] @@ -1183,6 +1183,11 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] mccabe = [ diff --git a/pyproject.toml b/pyproject.toml index 5e1812bb..c7808c11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,7 @@ lxml = "^4.4" ipython = "^7.10" markdown-include = "^0.5.1" aiohttp-socks = "^0.3.4" +packaging = "^20.3" [tool.poetry.extras] fast = ["uvloop"] diff --git a/tests/deprecated.py b/tests/deprecated.py new file mode 100644 index 00000000..53d65cf7 --- /dev/null +++ b/tests/deprecated.py @@ -0,0 +1,26 @@ +from contextlib import contextmanager +from typing import Type + +import pytest +from packaging import version + +import aiogram + + +@contextmanager +def check_deprecated( + max_version: str, exception: Type[Exception], warning: Type[Warning] = DeprecationWarning, +) -> None: + """ + Should be used for modules that are being deprecated or already removed from aiogram + """ + + parsed_max_version = version.parse(max_version) + current_version = version.parse(aiogram.__version__) + + if parsed_max_version <= current_version: + with pytest.raises(exception): + yield + else: + with pytest.warns(warning, match=max_version): + yield diff --git a/tests/test_dispatcher/test_deprecated.py b/tests/test_dispatcher/test_deprecated.py index 63c2b4de..fa451a99 100644 --- a/tests/test_dispatcher/test_deprecated.py +++ b/tests/test_dispatcher/test_deprecated.py @@ -2,6 +2,7 @@ import pytest from aiogram.dispatcher.event.observer import TelegramEventObserver from aiogram.dispatcher.router import Router +from tests.deprecated import check_deprecated OBSERVERS = { "callback_query", @@ -19,28 +20,13 @@ OBSERVERS = { "update", } +DEPRECATED_OBSERVERS = {observer + "_handler" for observer in OBSERVERS} -def test_deprecated_handlers_name(): - from aiogram import __version__ - - major, minor = map(int, __version__.split(".")[:-1]) - - if minor >= 2 and major >= 3: # version >=3.2.* - do_assert = pytest.raises(AttributeError) - else: # for versions <=3.2.* (we don't care if major is lesser than `3`) - do_assert = pytest.warns(DeprecationWarning) +@pytest.mark.parametrize("observer_name", DEPRECATED_OBSERVERS) +def test_deprecated_handlers_name(observer_name: str): router = Router() - with do_assert: - for decor in OBSERVERS: - getattr(router, decor + "_handler") - - assert all( - isinstance(getattr(router, handler + "_handler"), TelegramEventObserver) - for handler in OBSERVERS - ) - - assert all( - isinstance(getattr(router, handler), TelegramEventObserver) for handler in OBSERVERS - ) + with check_deprecated("3.2", exception=AttributeError): + observer = getattr(router, observer_name) + isinstance(observer, TelegramEventObserver) From 9f4b76dabc2ba9068919fffce82d8b9c1145b4ad Mon Sep 17 00:00:00 2001 From: mpa Date: Wed, 13 May 2020 17:39:36 +0400 Subject: [PATCH 9/9] tests(deprecated): quick fix add assert --- tests/test_dispatcher/test_deprecated.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dispatcher/test_deprecated.py b/tests/test_dispatcher/test_deprecated.py index fa451a99..efe6dfda 100644 --- a/tests/test_dispatcher/test_deprecated.py +++ b/tests/test_dispatcher/test_deprecated.py @@ -29,4 +29,4 @@ def test_deprecated_handlers_name(observer_name: str): with check_deprecated("3.2", exception=AttributeError): observer = getattr(router, observer_name) - isinstance(observer, TelegramEventObserver) + assert isinstance(observer, TelegramEventObserver)