diff --git a/CHANGES/669.misc b/CHANGES/669.misc new file mode 100644 index 00000000..2410f4c1 --- /dev/null +++ b/CHANGES/669.misc @@ -0,0 +1 @@ +Moved update type detection from Dispatcher to Update object diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 2f4bb1ba..fffe0262 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -9,7 +9,8 @@ from typing import Any, AsyncGenerator, Dict, List, Optional, Union from .. import loggers from ..client.bot import Bot from ..methods import GetUpdates, TelegramMethod -from ..types import TelegramObject, Update, User +from ..types import Update, User +from ..types.update import UpdateTypeLookupError from ..utils.backoff import Backoff, BackoffConfig from ..utils.exceptions.base import TelegramAPIError from ..utils.exceptions.network import NetworkError @@ -186,47 +187,10 @@ class Dispatcher(Router): :param kwargs: :return: """ - event: TelegramObject - if update.message: - update_type = "message" - event = update.message - elif update.edited_message: - update_type = "edited_message" - event = update.edited_message - elif update.channel_post: - update_type = "channel_post" - event = update.channel_post - elif update.edited_channel_post: - update_type = "edited_channel_post" - event = update.edited_channel_post - elif update.inline_query: - update_type = "inline_query" - event = update.inline_query - elif update.chosen_inline_result: - update_type = "chosen_inline_result" - event = update.chosen_inline_result - elif update.callback_query: - update_type = "callback_query" - event = update.callback_query - elif update.shipping_query: - update_type = "shipping_query" - event = update.shipping_query - elif update.pre_checkout_query: - update_type = "pre_checkout_query" - event = update.pre_checkout_query - elif update.poll: - update_type = "poll" - event = update.poll - elif update.poll_answer: - update_type = "poll_answer" - event = update.poll_answer - elif update.my_chat_member: - update_type = "my_chat_member" - event = update.my_chat_member - elif update.chat_member: - update_type = "chat_member" - event = update.chat_member - else: + try: + update_type = update.event_type + event = update.event + except UpdateTypeLookupError: warnings.warn( "Detected unknown update type.\n" "Seems like Telegram Bot API was updated and you have " diff --git a/aiogram/types/update.py b/aiogram/types/update.py index 3e43a316..7b54195c 100644 --- a/aiogram/types/update.py +++ b/aiogram/types/update.py @@ -1,7 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, cast +from ..utils.mypy_hacks import lru_cache from .base import TelegramObject if TYPE_CHECKING: # pragma: no cover @@ -53,3 +54,52 @@ class Update(TelegramObject): """*Optional*. The bot's chat member status was updated in a chat. For private chats, this update is received only when the bot is blocked or unblocked by the user.""" chat_member: Optional[ChatMemberUpdated] = None """*Optional*. A chat member's status was updated in a chat. The bot must be an administrator in the chat and must explicitly specify 'chat_member' in the list of *allowed_updates* to receive these updates.""" + + def __hash__(self) -> int: + return hash((type(self), self.update_id)) + + @property # type: ignore + @lru_cache() + def event_type(self) -> str: + """ + Detect update type + If update type is unknown, raise UpdateTypeLookupError + + :return: + """ + if self.message: + return "message" + if self.edited_message: + return "edited_message" + if self.channel_post: + return "channel_post" + if self.edited_channel_post: + return "edited_channel_post" + if self.inline_query: + return "inline_query" + if self.chosen_inline_result: + return "chosen_inline_result" + if self.callback_query: + return "callback_query" + if self.shipping_query: + return "shipping_query" + if self.pre_checkout_query: + return "pre_checkout_query" + if self.poll: + return "poll" + if self.poll_answer: + return "poll_answer" + if self.my_chat_member: + return "my_chat_member" + if self.chat_member: + return "chat_member" + + raise UpdateTypeLookupError("Update does not contain any known event type.") + + @property + def event(self) -> TelegramObject: + return cast(TelegramObject, getattr(self, self.event_type)) + + +class UpdateTypeLookupError(LookupError): + """Update does not contain any known event type.""" diff --git a/aiogram/utils/mypy_hacks.py b/aiogram/utils/mypy_hacks.py new file mode 100644 index 00000000..ea47a9dc --- /dev/null +++ b/aiogram/utils/mypy_hacks.py @@ -0,0 +1,16 @@ +import functools +from typing import Callable, TypeVar + +T = TypeVar("T") + + +def lru_cache(maxsize: int = 128, typed: bool = False) -> Callable[[T], T]: + """ + fix: lru_cache annotation doesn't work with a property + this hack is only needed for the property, so type annotations are as they are + """ + + def wrapper(func: T) -> T: + return functools.lru_cache(maxsize, typed)(func) # type: ignore + + return wrapper