From 7ea168a84bb43305cdfedafb59bc41dc2ff00ab6 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 8 Jul 2023 03:00:58 +0300 Subject: [PATCH] PoC: Mount objects to the Bot instance, bind shortcuts to configured instance --- aiogram/client/context_controller.py | 22 +++++++ aiogram/client/session/aiohttp.py | 4 +- aiogram/client/session/base.py | 6 +- aiogram/methods/base.py | 22 ++++--- aiogram/types/base.py | 4 +- aiogram/types/callback_query.py | 2 +- aiogram/types/chat.py | 54 ++++++++--------- aiogram/types/chat_join_request.py | 4 +- aiogram/types/inline_query.py | 2 +- aiogram/types/message.py | 90 ++++++++++++++-------------- aiogram/types/sticker.py | 4 +- aiogram/types/user.py | 2 +- 12 files changed, 123 insertions(+), 93 deletions(-) create mode 100644 aiogram/client/context_controller.py diff --git a/aiogram/client/context_controller.py b/aiogram/client/context_controller.py new file mode 100644 index 00000000..351a7caf --- /dev/null +++ b/aiogram/client/context_controller.py @@ -0,0 +1,22 @@ +from typing import TYPE_CHECKING, Any, Optional + +from pydantic import BaseModel, PrivateAttr +from typing_extensions import Self + +if TYPE_CHECKING: + from aiogram.client.bot import Bot + + +class BotContextController(BaseModel): + _bot: Optional["Bot"] = PrivateAttr() + + def model_post_init(self, __context: Any) -> None: + if not __context: + self._bot = None + else: + self._bot = __context.get("bot") + + def as_(self, bot: "Bot") -> Self: + """Bind object to a bot instance.""" + self._bot = bot + return self diff --git a/aiogram/client/session/aiohttp.py b/aiogram/client/session/aiohttp.py index b4c791e8..79e2fa4f 100644 --- a/aiogram/client/session/aiohttp.py +++ b/aiogram/client/session/aiohttp.py @@ -167,7 +167,9 @@ class AiohttpSession(BaseSession): raise TelegramNetworkError(method=method, message="Request timeout error") except ClientError as e: raise TelegramNetworkError(method=method, message=f"{type(e).__name__}: {e}") - response = self.check_response(method=method, status_code=resp.status, content=raw_result) + response = self.check_response( + bot=bot, method=method, status_code=resp.status, content=raw_result + ) return cast(TelegramType, response.result) async def stream_content( diff --git a/aiogram/client/session/base.py b/aiogram/client/session/base.py index afbb2edb..b4c7ce70 100644 --- a/aiogram/client/session/base.py +++ b/aiogram/client/session/base.py @@ -75,7 +75,7 @@ class BaseSession(abc.ABC): self.middleware = RequestMiddlewareManager() def check_response( - self, method: TelegramMethod[TelegramType], status_code: int, content: str + self, bot: Bot, method: TelegramMethod[TelegramType], status_code: int, content: str ) -> Response[TelegramType]: """ Check response status @@ -89,7 +89,9 @@ class BaseSession(abc.ABC): raise ClientDecodeError("Failed to decode object", e, content) try: - response = method.build_response(json_data) + response = Response[method.__returning__].model_validate( + json_data, context={"bot": bot} + ) except ValidationError as e: raise ClientDecodeError("Failed to deserialize object", e, json_data) diff --git a/aiogram/methods/base.py b/aiogram/methods/base.py index 53175784..2fefa209 100644 --- a/aiogram/methods/base.py +++ b/aiogram/methods/base.py @@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, Optional, TypeV from pydantic import BaseModel, ConfigDict from pydantic.functional_validators import model_validator +from aiogram.client.context_controller import BotContextController + from ..types import InputFile, ResponseParameters from ..types.base import UNSET_TYPE @@ -32,7 +34,7 @@ class Response(BaseModel, Generic[TelegramType]): parameters: Optional[ResponseParameters] = None -class TelegramMethod(BaseModel, Generic[TelegramType], ABC): +class TelegramMethod(BotContextController, BaseModel, Generic[TelegramType], ABC): model_config = ConfigDict( extra="allow", populate_by_name=True, @@ -40,6 +42,7 @@ class TelegramMethod(BaseModel, Generic[TelegramType], ABC): ) @model_validator(mode="before") + @classmethod def remove_unset(cls, values: Dict[str, Any]) -> Dict[str, Any]: """ Remove UNSET before fields validation. @@ -53,7 +56,7 @@ class TelegramMethod(BaseModel, Generic[TelegramType], ABC): @property @abstractmethod - def __returning__(self) -> type: # pragma: no cover + def __returning__(self) -> type: pass @property @@ -61,15 +64,16 @@ class TelegramMethod(BaseModel, Generic[TelegramType], ABC): def __api_method__(self) -> str: pass - def build_response(self, data: Dict[str, Any]) -> Response[TelegramType]: - # noinspection PyTypeChecker - return Response[self.__returning__](**data) # type: ignore - async def emit(self, bot: Bot) -> TelegramType: return await bot(self) def __await__(self) -> Generator[Any, None, TelegramType]: - from aiogram.client.bot import Bot - - bot = Bot.get_current(no_error=False) + bot = self._bot + if not bot: + raise RuntimeError( + "This method is not mounted to a any bot instance, please call it explicilty " + "with bot instance `await bot(method)`\n" + "or mount method to a bot instance `method.as_(bot)` " + "and then call it `await method()`" + ) return self.emit(bot).__await__() diff --git a/aiogram/types/base.py b/aiogram/types/base.py index a9d3eb1a..9d24b51e 100644 --- a/aiogram/types/base.py +++ b/aiogram/types/base.py @@ -3,10 +3,10 @@ from unittest.mock import sentinel from pydantic import BaseModel, ConfigDict -from aiogram.utils.mixins import ContextInstanceMixin +from aiogram.client.context_controller import BotContextController -class TelegramObject(ContextInstanceMixin["TelegramObject"], BaseModel): +class TelegramObject(BotContextController, BaseModel): model_config = ConfigDict( use_enum_values=True, extra="allow", diff --git a/aiogram/types/callback_query.py b/aiogram/types/callback_query.py index 5812bb13..59f9ea66 100644 --- a/aiogram/types/callback_query.py +++ b/aiogram/types/callback_query.py @@ -74,4 +74,4 @@ class CallbackQuery(TelegramObject): url=url, cache_time=cache_time, **kwargs, - ) + ).as_(self._bot) diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index 4bb6d688..d5d0dfcc 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -164,7 +164,7 @@ class Chat(TelegramObject): chat_id=self.id, sender_chat_id=sender_chat_id, **kwargs, - ) + ).as_(self._bot) def unban_sender_chat( self, @@ -193,7 +193,7 @@ class Chat(TelegramObject): chat_id=self.id, sender_chat_id=sender_chat_id, **kwargs, - ) + ).as_(self._bot) def get_administrators( self, @@ -219,7 +219,7 @@ class Chat(TelegramObject): return GetChatAdministrators( chat_id=self.id, **kwargs, - ) + ).as_(self._bot) def delete_message( self, @@ -266,7 +266,7 @@ class Chat(TelegramObject): chat_id=self.id, message_id=message_id, **kwargs, - ) + ).as_(self._bot) def revoke_invite_link( self, @@ -295,7 +295,7 @@ class Chat(TelegramObject): chat_id=self.id, invite_link=invite_link, **kwargs, - ) + ).as_(self._bot) def edit_invite_link( self, @@ -336,7 +336,7 @@ class Chat(TelegramObject): member_limit=member_limit, creates_join_request=creates_join_request, **kwargs, - ) + ).as_(self._bot) def create_invite_link( self, @@ -374,7 +374,7 @@ class Chat(TelegramObject): member_limit=member_limit, creates_join_request=creates_join_request, **kwargs, - ) + ).as_(self._bot) def export_invite_link( self, @@ -402,7 +402,7 @@ class Chat(TelegramObject): return ExportChatInviteLink( chat_id=self.id, **kwargs, - ) + ).as_(self._bot) def do( self, @@ -438,7 +438,7 @@ class Chat(TelegramObject): action=action, message_thread_id=message_thread_id, **kwargs, - ) + ).as_(self._bot) def delete_sticker_set( self, @@ -464,7 +464,7 @@ class Chat(TelegramObject): return DeleteChatStickerSet( chat_id=self.id, **kwargs, - ) + ).as_(self._bot) def set_sticker_set( self, @@ -493,7 +493,7 @@ class Chat(TelegramObject): chat_id=self.id, sticker_set_name=sticker_set_name, **kwargs, - ) + ).as_(self._bot) def get_member( self, @@ -522,7 +522,7 @@ class Chat(TelegramObject): chat_id=self.id, user_id=user_id, **kwargs, - ) + ).as_(self._bot) def get_member_count( self, @@ -548,7 +548,7 @@ class Chat(TelegramObject): return GetChatMemberCount( chat_id=self.id, **kwargs, - ) + ).as_(self._bot) def leave( self, @@ -574,7 +574,7 @@ class Chat(TelegramObject): return LeaveChat( chat_id=self.id, **kwargs, - ) + ).as_(self._bot) def unpin_all_messages( self, @@ -600,7 +600,7 @@ class Chat(TelegramObject): return UnpinAllChatMessages( chat_id=self.id, **kwargs, - ) + ).as_(self._bot) def unpin_message( self, @@ -629,7 +629,7 @@ class Chat(TelegramObject): chat_id=self.id, message_id=message_id, **kwargs, - ) + ).as_(self._bot) def pin_message( self, @@ -661,7 +661,7 @@ class Chat(TelegramObject): message_id=message_id, disable_notification=disable_notification, **kwargs, - ) + ).as_(self._bot) def set_administrator_custom_title( self, @@ -693,7 +693,7 @@ class Chat(TelegramObject): user_id=user_id, custom_title=custom_title, **kwargs, - ) + ).as_(self._bot) def set_permissions( self, @@ -725,7 +725,7 @@ class Chat(TelegramObject): permissions=permissions, use_independent_chat_permissions=use_independent_chat_permissions, **kwargs, - ) + ).as_(self._bot) def promote( self, @@ -790,7 +790,7 @@ class Chat(TelegramObject): can_pin_messages=can_pin_messages, can_manage_topics=can_manage_topics, **kwargs, - ) + ).as_(self._bot) def restrict( self, @@ -828,7 +828,7 @@ class Chat(TelegramObject): use_independent_chat_permissions=use_independent_chat_permissions, until_date=until_date, **kwargs, - ) + ).as_(self._bot) def unban( self, @@ -860,7 +860,7 @@ class Chat(TelegramObject): user_id=user_id, only_if_banned=only_if_banned, **kwargs, - ) + ).as_(self._bot) def ban( self, @@ -895,7 +895,7 @@ class Chat(TelegramObject): until_date=until_date, revoke_messages=revoke_messages, **kwargs, - ) + ).as_(self._bot) def set_description( self, @@ -924,7 +924,7 @@ class Chat(TelegramObject): chat_id=self.id, description=description, **kwargs, - ) + ).as_(self._bot) def set_title( self, @@ -953,7 +953,7 @@ class Chat(TelegramObject): chat_id=self.id, title=title, **kwargs, - ) + ).as_(self._bot) def delete_photo( self, @@ -979,7 +979,7 @@ class Chat(TelegramObject): return DeleteChatPhoto( chat_id=self.id, **kwargs, - ) + ).as_(self._bot) def set_photo( self, @@ -1008,4 +1008,4 @@ class Chat(TelegramObject): chat_id=self.id, photo=photo, **kwargs, - ) + ).as_(self._bot) diff --git a/aiogram/types/chat_join_request.py b/aiogram/types/chat_join_request.py index ca4f8bb7..94ef61ba 100644 --- a/aiogram/types/chat_join_request.py +++ b/aiogram/types/chat_join_request.py @@ -62,7 +62,7 @@ class ChatJoinRequest(TelegramObject): chat_id=self.chat.id, user_id=self.from_user.id, **kwargs, - ) + ).as_(self._bot) def decline( self, @@ -90,4 +90,4 @@ class ChatJoinRequest(TelegramObject): chat_id=self.chat.id, user_id=self.from_user.id, **kwargs, - ) + ).as_(self._bot) diff --git a/aiogram/types/inline_query.py b/aiogram/types/inline_query.py index 55eab72f..afb76a8b 100644 --- a/aiogram/types/inline_query.py +++ b/aiogram/types/inline_query.py @@ -81,4 +81,4 @@ class InlineQuery(TelegramObject): switch_pm_parameter=switch_pm_parameter, switch_pm_text=switch_pm_text, **kwargs, - ) + ).as_(self._bot) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index a057ef97..9339fe7f 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -418,7 +418,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_animation( self, @@ -490,7 +490,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_audio( self, @@ -559,7 +559,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_audio( self, @@ -629,7 +629,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_contact( self, @@ -685,7 +685,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_contact( self, @@ -742,7 +742,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_document( self, @@ -804,7 +804,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_document( self, @@ -867,7 +867,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_game( self, @@ -912,7 +912,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_game( self, @@ -958,7 +958,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_invoice( self, @@ -1063,7 +1063,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_invoice( self, @@ -1169,7 +1169,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_location( self, @@ -1231,7 +1231,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_location( self, @@ -1294,7 +1294,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_media_group( self, @@ -1336,7 +1336,7 @@ class Message(TelegramObject): protect_content=protect_content, allow_sending_without_reply=allow_sending_without_reply, **kwargs, - ) + ).as_(self._bot) def answer_media_group( self, @@ -1379,7 +1379,7 @@ class Message(TelegramObject): reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, **kwargs, - ) + ).as_(self._bot) def reply( self, @@ -1435,7 +1435,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer( self, @@ -1492,7 +1492,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_photo( self, @@ -1551,7 +1551,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_photo( self, @@ -1611,7 +1611,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_poll( self, @@ -1691,7 +1691,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_poll( self, @@ -1772,7 +1772,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_dice( self, @@ -1819,7 +1819,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_dice( self, @@ -1867,7 +1867,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_sticker( self, @@ -1917,7 +1917,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_sticker( self, @@ -1968,7 +1968,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_venue( self, @@ -2036,7 +2036,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_venue( self, @@ -2105,7 +2105,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_video( self, @@ -2179,7 +2179,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_video( self, @@ -2254,7 +2254,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_video_note( self, @@ -2310,7 +2310,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_video_note( self, @@ -2367,7 +2367,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def reply_voice( self, @@ -2426,7 +2426,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def answer_voice( self, @@ -2486,7 +2486,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def send_copy( # noqa: C901 self: Message, @@ -2684,7 +2684,7 @@ class Message(TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def edit_text( self, @@ -2730,7 +2730,7 @@ class Message(TelegramObject): disable_web_page_preview=disable_web_page_preview, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def forward( self, @@ -2770,7 +2770,7 @@ class Message(TelegramObject): disable_notification=disable_notification, protect_content=protect_content, **kwargs, - ) + ).as_(self._bot) def edit_media( self, @@ -2807,7 +2807,7 @@ class Message(TelegramObject): inline_message_id=inline_message_id, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def edit_reply_markup( self, @@ -2841,7 +2841,7 @@ class Message(TelegramObject): inline_message_id=inline_message_id, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def delete_reply_markup(self) -> EditMessageReplyMarkup: return self.edit_reply_markup(reply_markup=None) @@ -2893,7 +2893,7 @@ class Message(TelegramObject): proximity_alert_radius=proximity_alert_radius, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def stop_live_location( self, @@ -2927,7 +2927,7 @@ class Message(TelegramObject): inline_message_id=inline_message_id, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def edit_caption( self, @@ -2970,7 +2970,7 @@ class Message(TelegramObject): caption_entities=caption_entities, reply_markup=reply_markup, **kwargs, - ) + ).as_(self._bot) def delete( self, @@ -3016,7 +3016,7 @@ class Message(TelegramObject): chat_id=self.chat.id, message_id=self.message_id, **kwargs, - ) + ).as_(self._bot) def pin( self, @@ -3047,7 +3047,7 @@ class Message(TelegramObject): message_id=self.message_id, disable_notification=disable_notification, **kwargs, - ) + ).as_(self._bot) def unpin( self, @@ -3075,7 +3075,7 @@ class Message(TelegramObject): chat_id=self.chat.id, message_id=self.message_id, **kwargs, - ) + ).as_(self._bot) def get_url(self, force_private: bool = False) -> Optional[str]: """ diff --git a/aiogram/types/sticker.py b/aiogram/types/sticker.py index 1bac276f..cb453b32 100644 --- a/aiogram/types/sticker.py +++ b/aiogram/types/sticker.py @@ -76,7 +76,7 @@ class Sticker(TelegramObject): sticker=self.file_id, position=position, **kwargs, - ) + ).as_(self._bot) def delete_from_set( self, @@ -102,4 +102,4 @@ class Sticker(TelegramObject): return DeleteStickerFromSet( sticker=self.file_id, **kwargs, - ) + ).as_(self._bot) diff --git a/aiogram/types/user.py b/aiogram/types/user.py index 3b71af19..de1941fb 100644 --- a/aiogram/types/user.py +++ b/aiogram/types/user.py @@ -90,4 +90,4 @@ class User(TelegramObject): offset=offset, limit=limit, **kwargs, - ) + ).as_(self._bot)