diff --git a/.apiversion b/.apiversion index b6afe802..899dd4f5 100644 --- a/.apiversion +++ b/.apiversion @@ -1 +1 @@ -4.8 \ No newline at end of file +4.9 \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5e252c62..6061286a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -49,6 +49,5 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} file: coverage.xml flags: unittests - yml: codecov.yml name: py-${{ matrix.python-version }}-${{ matrix.os }} fail_ci_if_error: true diff --git a/aiogram/__init__.py b/aiogram/__init__.py index 0b2a8cc7..435b4eaf 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -28,5 +28,5 @@ __all__ = ( "handler", ) -__version__ = "3.0.0a4" -__api_version__ = "4.8" +__version__ = "3.0.0a5" +__api_version__ = "4.9" diff --git a/aiogram/api/client/bot.py b/aiogram/api/client/bot.py index 96061c24..ce12de07 100644 --- a/aiogram/api/client/bot.py +++ b/aiogram/api/client/bot.py @@ -95,6 +95,7 @@ from ..methods import ( UploadStickerFile, ) from ..types import ( + UNSET, BotCommand, Chat, ChatMember, @@ -146,6 +147,10 @@ class Bot(ContextInstanceMixin["Bot"]): self.parse_mode = parse_mode self.__token = token + @property + def token(self) -> str: + return self.__token + @property def id(self) -> int: """ @@ -268,14 +273,16 @@ class Bot(ContextInstanceMixin["Bot"]): file_path, destination=destination, timeout=timeout, chunk_size=chunk_size, seek=seek ) - async def __call__(self, method: TelegramMethod[T]) -> T: + async def __call__( + self, method: TelegramMethod[T], request_timeout: Optional[int] = None + ) -> T: """ Call API method :param method: :return: """ - return await self.session.make_request(self.__token, method) + return await self.session.make_request(self, method, timeout=request_timeout) def __hash__(self) -> int: """ @@ -307,6 +314,7 @@ class Bot(ContextInstanceMixin["Bot"]): limit: Optional[int] = None, timeout: Optional[int] = None, allowed_updates: Optional[List[str]] = None, + request_timeout: Optional[int] = None, ) -> List[Update]: """ Use this method to receive incoming updates using long polling (wiki). An Array of Update @@ -336,12 +344,13 @@ class Bot(ContextInstanceMixin["Bot"]): Update for a complete list of available update types. Specify an empty list to receive all updates regardless of type (default). If not specified, the previous setting will be used. + :param request_timeout: Request timeout :return: An Array of Update objects is returned. """ call = GetUpdates( offset=offset, limit=limit, timeout=timeout, allowed_updates=allowed_updates, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def set_webhook( self, @@ -349,6 +358,7 @@ class Bot(ContextInstanceMixin["Bot"]): certificate: Optional[InputFile] = None, max_connections: Optional[int] = None, allowed_updates: Optional[List[str]] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to specify a url and receive incoming updates via an outgoing webhook. @@ -357,7 +367,7 @@ class Bot(ContextInstanceMixin["Bot"]): will give up after a reasonable amount of attempts. Returns True on success. If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a secret path in the URL, e.g. https://www.example.com/. Since nobody else - knows your bot‘s token, you can be pretty sure it’s us. + knows your bot's token, you can be pretty sure it's us. Notes 1. You will not be able to receive updates using getUpdates for as long as an outgoing webhook is set up. @@ -375,14 +385,15 @@ class Bot(ContextInstanceMixin["Bot"]): can be checked. See our self-signed guide for details. :param max_connections: Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40. Use lower - values to limit the load on your bot‘s server, and higher values - to increase your bot’s throughput. + values to limit the load on your bot's server, and higher values + to increase your bot's throughput. :param allowed_updates: A JSON-serialized list of the update types you want your bot to receive. For example, specify ['message', 'edited_channel_post', 'callback_query'] to only receive updates of these types. See Update for a complete list of available update types. Specify an empty list to receive all updates regardless of type (default). If not specified, the previous setting will be used. + :param request_timeout: Request timeout :return: Returns True on success. """ call = SetWebhook( @@ -391,21 +402,22 @@ class Bot(ContextInstanceMixin["Bot"]): max_connections=max_connections, allowed_updates=allowed_updates, ) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def delete_webhook(self,) -> bool: + async def delete_webhook(self, request_timeout: Optional[int] = None,) -> bool: """ Use this method to remove webhook integration if you decide to switch back to getUpdates. Returns True on success. Requires no parameters. Source: https://core.telegram.org/bots/api#deletewebhook + :param request_timeout: Request timeout :return: Returns True on success. """ call = DeleteWebhook() - return await self(call) + return await self(call, request_timeout=request_timeout) - async def get_webhook_info(self,) -> WebhookInfo: + async def get_webhook_info(self, request_timeout: Optional[int] = None,) -> WebhookInfo: """ Use this method to get current webhook status. Requires no parameters. On success, returns a WebhookInfo object. If the bot is using getUpdates, will return an object with the url @@ -413,40 +425,43 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#getwebhookinfo + :param request_timeout: Request timeout :return: On success, returns a WebhookInfo object. If the bot is using getUpdates, will return an object with the url field empty. """ call = GetWebhookInfo() - return await self(call) + return await self(call, request_timeout=request_timeout) # ============================================================================================= # Group: Available methods # Source: https://core.telegram.org/bots/api#available-methods # ============================================================================================= - async def get_me(self,) -> User: + async def get_me(self, request_timeout: Optional[int] = None,) -> User: """ A simple method for testing your bot's auth token. Requires no parameters. Returns basic information about the bot in form of a User object. Source: https://core.telegram.org/bots/api#getme + :param request_timeout: Request timeout :return: Returns basic information about the bot in form of a User object. """ call = GetMe() - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_message( self, chat_id: Union[int, str], text: str, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_web_page_preview: Optional[bool] = None, disable_notification: Optional[bool] = None, reply_to_message_id: Optional[int] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send text messages. On success, the sent Message is returned. @@ -465,6 +480,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendMessage( @@ -476,7 +492,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def forward_message( self, @@ -484,6 +500,7 @@ class Bot(ContextInstanceMixin["Bot"]): from_chat_id: Union[int, str], message_id: int, disable_notification: Optional[bool] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to forward messages of any kind. On success, the sent Message is returned. @@ -497,6 +514,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param message_id: Message identifier in the chat specified in from_chat_id :param disable_notification: Sends the message silently. Users will receive a notification with no sound. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = ForwardMessage( @@ -505,19 +523,20 @@ class Bot(ContextInstanceMixin["Bot"]): message_id=message_id, disable_notification=disable_notification, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_photo( self, chat_id: Union[int, str], photo: Union[InputFile, str], caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_notification: Optional[bool] = None, reply_to_message_id: Optional[int] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send photos. On success, the sent Message is returned. @@ -540,6 +559,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendPhoto( @@ -551,14 +571,14 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_audio( self, chat_id: Union[int, str], audio: Union[InputFile, str], caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, duration: Optional[int] = None, performer: Optional[str] = None, title: Optional[str] = None, @@ -568,6 +588,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send audio files, if you want Telegram clients to display them in the @@ -592,9 +613,9 @@ class Bot(ContextInstanceMixin["Bot"]): :param title: Track name :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and - less than 200 kB in size. A thumbnail‘s width and height should not exceed + less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can’t be reused and can be only uploaded as a new file, so you + Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :param disable_notification: Sends the message silently. Users will receive a notification @@ -603,6 +624,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendAudio( @@ -618,7 +640,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_document( self, @@ -626,12 +648,13 @@ class Bot(ContextInstanceMixin["Bot"]): document: Union[InputFile, str], thumb: Optional[Union[InputFile, str]] = None, caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_notification: Optional[bool] = None, reply_to_message_id: Optional[int] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send general files. On success, the sent Message is returned. Bots can @@ -648,9 +671,9 @@ class Bot(ContextInstanceMixin["Bot"]): multipart/form-data. :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and - less than 200 kB in size. A thumbnail‘s width and height should not exceed + less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can’t be reused and can be only uploaded as a new file, so you + Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :param caption: Document caption (may also be used when resending documents by file_id), @@ -663,6 +686,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendDocument( @@ -675,7 +699,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_video( self, @@ -686,13 +710,14 @@ class Bot(ContextInstanceMixin["Bot"]): height: Optional[int] = None, thumb: Optional[Union[InputFile, str]] = None, caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, supports_streaming: Optional[bool] = None, disable_notification: Optional[bool] = None, reply_to_message_id: Optional[int] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send video files, Telegram clients support mp4 videos (other formats @@ -712,9 +737,9 @@ class Bot(ContextInstanceMixin["Bot"]): :param height: Video height :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and - less than 200 kB in size. A thumbnail‘s width and height should not exceed + less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can’t be reused and can be only uploaded as a new file, so you + Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :param caption: Video caption (may also be used when resending videos by file_id), 0-1024 @@ -728,6 +753,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendVideo( @@ -744,7 +770,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_animation( self, @@ -755,12 +781,13 @@ class Bot(ContextInstanceMixin["Bot"]): height: Optional[int] = None, thumb: Optional[Union[InputFile, str]] = None, caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_notification: Optional[bool] = None, reply_to_message_id: Optional[int] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). On @@ -780,9 +807,9 @@ class Bot(ContextInstanceMixin["Bot"]): :param height: Animation height :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and - less than 200 kB in size. A thumbnail‘s width and height should not exceed + less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can’t be reused and can be only uploaded as a new file, so you + Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :param caption: Animation caption (may also be used when resending animation by file_id), @@ -795,6 +822,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendAnimation( @@ -810,20 +838,21 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_voice( self, chat_id: Union[int, str], voice: Union[InputFile, str], caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, duration: Optional[int] = None, disable_notification: Optional[bool] = None, reply_to_message_id: Optional[int] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send audio files, if you want Telegram clients to display the file as a @@ -850,6 +879,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendVoice( @@ -862,7 +892,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_video_note( self, @@ -876,6 +906,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long. @@ -893,9 +924,9 @@ class Bot(ContextInstanceMixin["Bot"]): :param length: Video width and height, i.e. diameter of the video message :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and - less than 200 kB in size. A thumbnail‘s width and height should not exceed + less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can’t be reused and can be only uploaded as a new file, so you + Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :param disable_notification: Sends the message silently. Users will receive a notification @@ -904,6 +935,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendVideoNote( @@ -916,7 +948,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_media_group( self, @@ -924,6 +956,7 @@ class Bot(ContextInstanceMixin["Bot"]): media: List[Union[InputMediaPhoto, InputMediaVideo]], disable_notification: Optional[bool] = None, reply_to_message_id: Optional[int] = None, + request_timeout: Optional[int] = None, ) -> List[Message]: """ Use this method to send a group of photos or videos as an album. On success, an array of @@ -938,6 +971,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param disable_notification: Sends the messages silently. Users will receive a notification with no sound. :param reply_to_message_id: If the messages are a reply, ID of the original message + :param request_timeout: Request timeout :return: On success, an array of the sent Messages is returned. """ call = SendMediaGroup( @@ -946,7 +980,7 @@ class Bot(ContextInstanceMixin["Bot"]): disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_location( self, @@ -959,6 +993,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send point on the map. On success, the sent Message is returned. @@ -977,6 +1012,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendLocation( @@ -988,7 +1024,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def edit_message_live_location( self, @@ -998,6 +1034,7 @@ class Bot(ContextInstanceMixin["Bot"]): message_id: Optional[int] = None, inline_message_id: Optional[str] = None, reply_markup: Optional[InlineKeyboardMarkup] = None, + request_timeout: Optional[int] = None, ) -> Union[Message, bool]: """ Use this method to edit live location messages. A location can be edited until its @@ -1017,6 +1054,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message :param reply_markup: A JSON-serialized object for a new inline keyboard. + :param request_timeout: Request timeout :return: On success, if the edited message was sent by the bot, the edited Message is returned, otherwise True is returned. """ @@ -1028,7 +1066,7 @@ class Bot(ContextInstanceMixin["Bot"]): inline_message_id=inline_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def stop_message_live_location( self, @@ -1036,6 +1074,7 @@ class Bot(ContextInstanceMixin["Bot"]): message_id: Optional[int] = None, inline_message_id: Optional[str] = None, reply_markup: Optional[InlineKeyboardMarkup] = None, + request_timeout: Optional[int] = None, ) -> Union[Message, bool]: """ Use this method to stop updating a live location message before live_period expires. On @@ -1052,6 +1091,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message :param reply_markup: A JSON-serialized object for a new inline keyboard. + :param request_timeout: Request timeout :return: On success, if the message was sent by the bot, the sent Message is returned, otherwise True is returned. """ @@ -1061,7 +1101,7 @@ class Bot(ContextInstanceMixin["Bot"]): inline_message_id=inline_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_venue( self, @@ -1077,6 +1117,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send information about a venue. On success, the sent Message is @@ -1100,6 +1141,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendVenue( @@ -1114,7 +1156,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_contact( self, @@ -1128,6 +1170,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send phone contacts. On success, the sent Message is returned. @@ -1146,6 +1189,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendContact( @@ -1158,7 +1202,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_poll( self, @@ -1170,7 +1214,7 @@ class Bot(ContextInstanceMixin["Bot"]): allows_multiple_answers: Optional[bool] = None, correct_option_id: Optional[int] = None, explanation: Optional[str] = None, - explanation_parse_mode: Optional[str] = None, + explanation_parse_mode: Optional[str] = UNSET, open_period: Optional[int] = None, close_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None, is_closed: Optional[bool] = None, @@ -1179,6 +1223,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send a native poll. On success, the sent Message is returned. @@ -1214,6 +1259,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendPoll( @@ -1233,7 +1279,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def send_dice( self, @@ -1244,24 +1290,26 @@ class Bot(ContextInstanceMixin["Bot"]): reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ - Use this method to send a dice, which will have a random value from 1 to 6. On success, - the sent Message is returned. (Yes, we're aware of the 'proper' singular of die. But it's - awkward, and we decided to help it change. One dice at a time!) + Use this method to send an animated emoji that will display a random value. On success, + the sent Message is returned. Source: https://core.telegram.org/bots/api#senddice :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) :param emoji: Emoji on which the dice throw animation is based. Currently, must be one of - '' or ''. Defauts to '' + '', '', or ''. Dice can have values 1-6 for '' and '', and values 1-5 for + ''. Defaults to '' :param disable_notification: Sends the message silently. Users will receive a notification with no sound. :param reply_to_message_id: If the message is a reply, ID of the original message :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendDice( @@ -1271,9 +1319,11 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def send_chat_action(self, chat_id: Union[int, str], action: str,) -> bool: + async def send_chat_action( + self, chat_id: Union[int, str], action: str, request_timeout: Optional[int] = None, + ) -> bool: """ Use this method when you need to tell the user that something is happening on the bot's side. The status is set for 5 seconds or less (when a message arrives from your bot, @@ -1294,13 +1344,18 @@ class Bot(ContextInstanceMixin["Bot"]): record_video or upload_video for videos, record_audio or upload_audio for audio files, upload_document for general files, find_location for location data, record_video_note or upload_video_note for video notes. + :param request_timeout: Request timeout :return: Returns True on success. """ call = SendChatAction(chat_id=chat_id, action=action,) - return await self(call) + return await self(call, request_timeout=request_timeout) async def get_user_profile_photos( - self, user_id: int, offset: Optional[int] = None, limit: Optional[int] = None, + self, + user_id: int, + offset: Optional[int] = None, + limit: Optional[int] = None, + request_timeout: Optional[int] = None, ) -> UserProfilePhotos: """ Use this method to get a list of profile pictures for a user. Returns a UserProfilePhotos @@ -1313,12 +1368,13 @@ class Bot(ContextInstanceMixin["Bot"]): are returned. :param limit: Limits the number of photos to be retrieved. Values between 1-100 are accepted. Defaults to 100. + :param request_timeout: Request timeout :return: Returns a UserProfilePhotos object. """ call = GetUserProfilePhotos(user_id=user_id, offset=offset, limit=limit,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def get_file(self, file_id: str,) -> File: + async def get_file(self, file_id: str, request_timeout: Optional[int] = None,) -> File: """ Use this method to get basic info about a file and prepare it for downloading. For the moment, bots can download files of up to 20MB in size. On success, a File object is @@ -1332,16 +1388,18 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#getfile :param file_id: File identifier to get info about + :param request_timeout: Request timeout :return: On success, a File object is returned. """ call = GetFile(file_id=file_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) async def kick_chat_member( self, chat_id: Union[int, str], user_id: int, until_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to kick a user from a group, a supergroup or a channel. In the case of @@ -1357,13 +1415,16 @@ class Bot(ContextInstanceMixin["Bot"]): :param until_date: Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever + :param request_timeout: Request timeout :return: In the case of supergroups and channels, the user will not be able to return to the group on their own using invite links, etc. Returns True on success. """ call = KickChatMember(chat_id=chat_id, user_id=user_id, until_date=until_date,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def unban_chat_member(self, chat_id: Union[int, str], user_id: int,) -> bool: + async def unban_chat_member( + self, chat_id: Union[int, str], user_id: int, request_timeout: Optional[int] = None, + ) -> bool: """ Use this method to unban a previously kicked user in a supergroup or channel. The user will not return to the group or channel automatically, but will be able to join via link, @@ -1374,11 +1435,12 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target group or username of the target supergroup or channel (in the format @username) :param user_id: Unique identifier of the target user + :param request_timeout: Request timeout :return: The user will not return to the group or channel automatically, but will be able to join via link, etc. Returns True on success. """ call = UnbanChatMember(chat_id=chat_id, user_id=user_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) async def restrict_chat_member( self, @@ -1386,6 +1448,7 @@ class Bot(ContextInstanceMixin["Bot"]): user_id: int, permissions: ChatPermissions, until_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to restrict a user in a supergroup. The bot must be an administrator in @@ -1401,12 +1464,13 @@ class Bot(ContextInstanceMixin["Bot"]): :param until_date: Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever + :param request_timeout: Request timeout :return: Returns True on success. """ call = RestrictChatMember( chat_id=chat_id, user_id=user_id, permissions=permissions, until_date=until_date, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def promote_chat_member( self, @@ -1420,6 +1484,7 @@ class Bot(ContextInstanceMixin["Bot"]): can_restrict_members: Optional[bool] = None, can_pin_messages: Optional[bool] = None, can_promote_members: Optional[bool] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to promote or demote a user in a supergroup or a channel. The bot must be @@ -1448,6 +1513,7 @@ class Bot(ContextInstanceMixin["Bot"]): with a subset of their own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by him) + :param request_timeout: Request timeout :return: Returns True on success. """ call = PromoteChatMember( @@ -1462,10 +1528,14 @@ class Bot(ContextInstanceMixin["Bot"]): can_pin_messages=can_pin_messages, can_promote_members=can_promote_members, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def set_chat_administrator_custom_title( - self, chat_id: Union[int, str], user_id: int, custom_title: str, + self, + chat_id: Union[int, str], + user_id: int, + custom_title: str, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to set a custom title for an administrator in a supergroup promoted by the @@ -1478,15 +1548,19 @@ class Bot(ContextInstanceMixin["Bot"]): :param user_id: Unique identifier of the target user :param custom_title: New custom title for the administrator; 0-16 characters, emoji are not allowed + :param request_timeout: Request timeout :return: Returns True on success. """ call = SetChatAdministratorCustomTitle( chat_id=chat_id, user_id=user_id, custom_title=custom_title, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def set_chat_permissions( - self, chat_id: Union[int, str], permissions: ChatPermissions, + self, + chat_id: Union[int, str], + permissions: ChatPermissions, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to set default chat permissions for all members. The bot must be an @@ -1498,12 +1572,15 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) :param permissions: New default chat permissions + :param request_timeout: Request timeout :return: Returns True on success. """ call = SetChatPermissions(chat_id=chat_id, permissions=permissions,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def export_chat_invite_link(self, chat_id: Union[int, str],) -> str: + async def export_chat_invite_link( + self, chat_id: Union[int, str], request_timeout: Optional[int] = None, + ) -> str: """ Use this method to generate a new invite link for a chat; any previously generated link is revoked. The bot must be an administrator in the chat for this to work and must have the @@ -1518,12 +1595,15 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :param request_timeout: Request timeout :return: Returns the new invite link as String on success. """ call = ExportChatInviteLink(chat_id=chat_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def set_chat_photo(self, chat_id: Union[int, str], photo: InputFile,) -> bool: + async def set_chat_photo( + self, chat_id: Union[int, str], photo: InputFile, request_timeout: Optional[int] = None, + ) -> bool: """ Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have @@ -1534,12 +1614,15 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) :param photo: New chat photo, uploaded using multipart/form-data + :param request_timeout: Request timeout :return: Returns True on success. """ call = SetChatPhoto(chat_id=chat_id, photo=photo,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def delete_chat_photo(self, chat_id: Union[int, str],) -> bool: + async def delete_chat_photo( + self, chat_id: Union[int, str], request_timeout: Optional[int] = None, + ) -> bool: """ Use this method to delete a chat photo. Photos can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have the appropriate admin @@ -1549,12 +1632,15 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :param request_timeout: Request timeout :return: Returns True on success. """ call = DeleteChatPhoto(chat_id=chat_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def set_chat_title(self, chat_id: Union[int, str], title: str,) -> bool: + async def set_chat_title( + self, chat_id: Union[int, str], title: str, request_timeout: Optional[int] = None, + ) -> bool: """ Use this method to change the title of a chat. Titles can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have the @@ -1565,13 +1651,17 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) :param title: New chat title, 1-255 characters + :param request_timeout: Request timeout :return: Returns True on success. """ call = SetChatTitle(chat_id=chat_id, title=title,) - return await self(call) + return await self(call, request_timeout=request_timeout) async def set_chat_description( - self, chat_id: Union[int, str], description: Optional[str] = None, + self, + chat_id: Union[int, str], + description: Optional[str] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to change the description of a group, a supergroup or a channel. The bot @@ -1583,21 +1673,23 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) :param description: New chat description, 0-255 characters + :param request_timeout: Request timeout :return: Returns True on success. """ call = SetChatDescription(chat_id=chat_id, description=description,) - return await self(call) + return await self(call, request_timeout=request_timeout) async def pin_chat_message( self, chat_id: Union[int, str], message_id: int, disable_notification: Optional[bool] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to pin a message in a group, a supergroup, or a channel. The bot must be - an administrator in the chat for this to work and must have the ‘can_pin_messages’ admin - right in the supergroup or ‘can_edit_messages’ admin right in the channel. Returns True on + an administrator in the chat for this to work and must have the 'can_pin_messages' admin + right in the supergroup or 'can_edit_messages' admin right in the channel. Returns True on success. Source: https://core.telegram.org/bots/api#pinchatmessage @@ -1608,30 +1700,36 @@ class Bot(ContextInstanceMixin["Bot"]): :param disable_notification: Pass True, if it is not necessary to send a notification to all chat members about the new pinned message. Notifications are always disabled in channels. + :param request_timeout: Request timeout :return: Returns True on success. """ call = PinChatMessage( chat_id=chat_id, message_id=message_id, disable_notification=disable_notification, ) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def unpin_chat_message(self, chat_id: Union[int, str],) -> bool: + async def unpin_chat_message( + self, chat_id: Union[int, str], request_timeout: Optional[int] = None, + ) -> bool: """ Use this method to unpin a message in a group, a supergroup, or a channel. The bot must be - an administrator in the chat for this to work and must have the ‘can_pin_messages’ admin - right in the supergroup or ‘can_edit_messages’ admin right in the channel. Returns True on + an administrator in the chat for this to work and must have the 'can_pin_messages' admin + right in the supergroup or 'can_edit_messages' admin right in the channel. Returns True on success. Source: https://core.telegram.org/bots/api#unpinchatmessage :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) + :param request_timeout: Request timeout :return: Returns True on success. """ call = UnpinChatMessage(chat_id=chat_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def leave_chat(self, chat_id: Union[int, str],) -> bool: + async def leave_chat( + self, chat_id: Union[int, str], request_timeout: Optional[int] = None, + ) -> bool: """ Use this method for your bot to leave a group, supergroup or channel. Returns True on success. @@ -1640,12 +1738,15 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target supergroup or channel (in the format @channelusername) + :param request_timeout: Request timeout :return: Returns True on success. """ call = LeaveChat(chat_id=chat_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def get_chat(self, chat_id: Union[int, str],) -> Chat: + async def get_chat( + self, chat_id: Union[int, str], request_timeout: Optional[int] = None, + ) -> Chat: """ Use this method to get up to date information about the chat (current name of the user for one-on-one conversations, current username of a user, group or channel, etc.). Returns a @@ -1655,12 +1756,15 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target supergroup or channel (in the format @channelusername) + :param request_timeout: Request timeout :return: Returns a Chat object on success. """ call = GetChat(chat_id=chat_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def get_chat_administrators(self, chat_id: Union[int, str],) -> List[ChatMember]: + async def get_chat_administrators( + self, chat_id: Union[int, str], request_timeout: Optional[int] = None, + ) -> List[ChatMember]: """ Use this method to get a list of administrators in a chat. On success, returns an Array of ChatMember objects that contains information about all chat administrators except other @@ -1671,15 +1775,18 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target supergroup or channel (in the format @channelusername) + :param request_timeout: Request timeout :return: On success, returns an Array of ChatMember objects that contains information about all chat administrators except other bots. If the chat is a group or a supergroup and no administrators were appointed, only the creator will be returned. """ call = GetChatAdministrators(chat_id=chat_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def get_chat_members_count(self, chat_id: Union[int, str],) -> int: + async def get_chat_members_count( + self, chat_id: Union[int, str], request_timeout: Optional[int] = None, + ) -> int: """ Use this method to get the number of members in a chat. Returns Int on success. @@ -1687,12 +1794,15 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target supergroup or channel (in the format @channelusername) + :param request_timeout: Request timeout :return: Returns Int on success. """ call = GetChatMembersCount(chat_id=chat_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def get_chat_member(self, chat_id: Union[int, str], user_id: int,) -> ChatMember: + async def get_chat_member( + self, chat_id: Union[int, str], user_id: int, request_timeout: Optional[int] = None, + ) -> ChatMember: """ Use this method to get information about a member of a chat. Returns a ChatMember object on success. @@ -1702,12 +1812,18 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target supergroup or channel (in the format @channelusername) :param user_id: Unique identifier of the target user + :param request_timeout: Request timeout :return: Returns a ChatMember object on success. """ call = GetChatMember(chat_id=chat_id, user_id=user_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def set_chat_sticker_set(self, chat_id: Union[int, str], sticker_set_name: str,) -> bool: + async def set_chat_sticker_set( + self, + chat_id: Union[int, str], + sticker_set_name: str, + request_timeout: Optional[int] = None, + ) -> bool: """ Use this method to set a new group sticker set for a supergroup. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Use @@ -1719,13 +1835,16 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) :param sticker_set_name: Name of the sticker set to be set as the group sticker set + :param request_timeout: Request timeout :return: Use the field can_set_sticker_set optionally returned in getChat requests to check if the bot can use this method. Returns True on success. """ call = SetChatStickerSet(chat_id=chat_id, sticker_set_name=sticker_set_name,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def delete_chat_sticker_set(self, chat_id: Union[int, str],) -> bool: + async def delete_chat_sticker_set( + self, chat_id: Union[int, str], request_timeout: Optional[int] = None, + ) -> bool: """ Use this method to delete a group sticker set from a supergroup. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Use @@ -1736,11 +1855,12 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) + :param request_timeout: Request timeout :return: Use the field can_set_sticker_set optionally returned in getChat requests to check if the bot can use this method. Returns True on success. """ call = DeleteChatStickerSet(chat_id=chat_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) async def answer_callback_query( self, @@ -1749,6 +1869,7 @@ class Bot(ContextInstanceMixin["Bot"]): show_alert: Optional[bool] = None, url: Optional[str] = None, cache_time: Optional[int] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to send answers to callback queries sent from inline keyboards. The answer @@ -1772,6 +1893,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param cache_time: The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. + :param request_timeout: Request timeout :return: On success, True is returned. """ call = AnswerCallbackQuery( @@ -1781,9 +1903,11 @@ class Bot(ContextInstanceMixin["Bot"]): url=url, cache_time=cache_time, ) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def set_my_commands(self, commands: List[BotCommand],) -> bool: + async def set_my_commands( + self, commands: List[BotCommand], request_timeout: Optional[int] = None, + ) -> bool: """ Use this method to change the list of the bot's commands. Returns True on success. @@ -1791,22 +1915,24 @@ class Bot(ContextInstanceMixin["Bot"]): :param commands: A JSON-serialized list of bot commands to be set as the list of the bot's commands. At most 100 commands can be specified. + :param request_timeout: Request timeout :return: Returns True on success. """ call = SetMyCommands(commands=commands,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def get_my_commands(self,) -> List[BotCommand]: + async def get_my_commands(self, request_timeout: Optional[int] = None,) -> List[BotCommand]: """ Use this method to get the current list of the bot's commands. Requires no parameters. Returns Array of BotCommand on success. Source: https://core.telegram.org/bots/api#getmycommands + :param request_timeout: Request timeout :return: Returns Array of BotCommand on success. """ call = GetMyCommands() - return await self(call) + return await self(call, request_timeout=request_timeout) # ============================================================================================= # Group: Updating messages @@ -1819,9 +1945,10 @@ class Bot(ContextInstanceMixin["Bot"]): chat_id: Optional[Union[int, str]] = None, message_id: Optional[int] = None, inline_message_id: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_web_page_preview: Optional[bool] = None, reply_markup: Optional[InlineKeyboardMarkup] = None, + request_timeout: Optional[int] = None, ) -> Union[Message, bool]: """ Use this method to edit text and game messages. On success, if edited message is sent by @@ -1841,6 +1968,7 @@ class Bot(ContextInstanceMixin["Bot"]): for more details. :param disable_web_page_preview: Disables link previews for links in this message :param reply_markup: A JSON-serialized object for an inline keyboard. + :param request_timeout: Request timeout :return: On success, if edited message is sent by the bot, the edited Message is returned, otherwise True is returned. """ @@ -1853,7 +1981,7 @@ class Bot(ContextInstanceMixin["Bot"]): disable_web_page_preview=disable_web_page_preview, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def edit_message_caption( self, @@ -1861,8 +1989,9 @@ class Bot(ContextInstanceMixin["Bot"]): message_id: Optional[int] = None, inline_message_id: Optional[str] = None, caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, reply_markup: Optional[InlineKeyboardMarkup] = None, + request_timeout: Optional[int] = None, ) -> Union[Message, bool]: """ Use this method to edit captions of messages. On success, if edited message is sent by the @@ -1881,6 +2010,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param parse_mode: Mode for parsing entities in the message caption. See formatting options for more details. :param reply_markup: A JSON-serialized object for an inline keyboard. + :param request_timeout: Request timeout :return: On success, if edited message is sent by the bot, the edited Message is returned, otherwise True is returned. """ @@ -1892,7 +2022,7 @@ class Bot(ContextInstanceMixin["Bot"]): parse_mode=parse_mode, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def edit_message_media( self, @@ -1901,6 +2031,7 @@ class Bot(ContextInstanceMixin["Bot"]): message_id: Optional[int] = None, inline_message_id: Optional[str] = None, reply_markup: Optional[InlineKeyboardMarkup] = None, + request_timeout: Optional[int] = None, ) -> Union[Message, bool]: """ Use this method to edit animation, audio, document, photo, or video messages. If a message @@ -1921,6 +2052,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message :param reply_markup: A JSON-serialized object for a new inline keyboard. + :param request_timeout: Request timeout :return: On success, if the edited message was sent by the bot, the edited Message is returned, otherwise True is returned. """ @@ -1931,7 +2063,7 @@ class Bot(ContextInstanceMixin["Bot"]): inline_message_id=inline_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def edit_message_reply_markup( self, @@ -1939,6 +2071,7 @@ class Bot(ContextInstanceMixin["Bot"]): message_id: Optional[int] = None, inline_message_id: Optional[str] = None, reply_markup: Optional[InlineKeyboardMarkup] = None, + request_timeout: Optional[int] = None, ) -> Union[Message, bool]: """ Use this method to edit only the reply markup of messages. On success, if edited message @@ -1954,6 +2087,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message :param reply_markup: A JSON-serialized object for an inline keyboard. + :param request_timeout: Request timeout :return: On success, if edited message is sent by the bot, the edited Message is returned, otherwise True is returned. """ @@ -1963,13 +2097,14 @@ class Bot(ContextInstanceMixin["Bot"]): inline_message_id=inline_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def stop_poll( self, chat_id: Union[int, str], message_id: int, reply_markup: Optional[InlineKeyboardMarkup] = None, + request_timeout: Optional[int] = None, ) -> Poll: """ Use this method to stop a poll which was sent by the bot. On success, the stopped Poll @@ -1981,12 +2116,15 @@ class Bot(ContextInstanceMixin["Bot"]): (in the format @channelusername) :param message_id: Identifier of the original message with the poll :param reply_markup: A JSON-serialized object for a new message inline keyboard. + :param request_timeout: Request timeout :return: On success, the stopped Poll with the final results is returned. """ call = StopPoll(chat_id=chat_id, message_id=message_id, reply_markup=reply_markup,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def delete_message(self, chat_id: Union[int, str], message_id: int,) -> bool: + async def delete_message( + self, chat_id: Union[int, str], message_id: int, request_timeout: Optional[int] = None, + ) -> bool: """ Use this method to delete a message, including service messages, with the following limitations: @@ -2006,10 +2144,11 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) :param message_id: Identifier of the message to delete + :param request_timeout: Request timeout :return: Returns True on success. """ call = DeleteMessage(chat_id=chat_id, message_id=message_id,) - return await self(call) + return await self(call, request_timeout=request_timeout) # ============================================================================================= # Group: Stickers @@ -2025,6 +2164,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] ] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send static .WEBP or animated .TGS stickers. On success, the sent @@ -2044,6 +2184,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendSticker( @@ -2053,21 +2194,26 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def get_sticker_set(self, name: str,) -> StickerSet: + async def get_sticker_set( + self, name: str, request_timeout: Optional[int] = None, + ) -> StickerSet: """ Use this method to get a sticker set. On success, a StickerSet object is returned. Source: https://core.telegram.org/bots/api#getstickerset :param name: Name of the sticker set + :param request_timeout: Request timeout :return: On success, a StickerSet object is returned. """ call = GetStickerSet(name=name,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def upload_sticker_file(self, user_id: int, png_sticker: InputFile,) -> File: + async def upload_sticker_file( + self, user_id: int, png_sticker: InputFile, request_timeout: Optional[int] = None, + ) -> File: """ Use this method to upload a .PNG file with a sticker for later use in createNewStickerSet and addStickerToSet methods (can be used multiple times). Returns the uploaded File on @@ -2079,10 +2225,11 @@ class Bot(ContextInstanceMixin["Bot"]): :param png_sticker: PNG image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. + :param request_timeout: Request timeout :return: Returns the uploaded File on success. """ call = UploadStickerFile(user_id=user_id, png_sticker=png_sticker,) - return await self(call) + return await self(call, request_timeout=request_timeout) async def create_new_sticker_set( self, @@ -2094,6 +2241,7 @@ class Bot(ContextInstanceMixin["Bot"]): tgs_sticker: Optional[InputFile] = None, contains_masks: Optional[bool] = None, mask_position: Optional[MaskPosition] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to create a new sticker set owned by a user. The bot will be able to edit @@ -2121,6 +2269,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param contains_masks: Pass True, if a set of mask stickers should be created :param mask_position: A JSON-serialized object for position where the mask should be placed on faces + :param request_timeout: Request timeout :return: Returns True on success. """ call = CreateNewStickerSet( @@ -2133,7 +2282,7 @@ class Bot(ContextInstanceMixin["Bot"]): contains_masks=contains_masks, mask_position=mask_position, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def add_sticker_to_set( self, @@ -2143,6 +2292,7 @@ class Bot(ContextInstanceMixin["Bot"]): png_sticker: Optional[Union[InputFile, str]] = None, tgs_sticker: Optional[InputFile] = None, mask_position: Optional[MaskPosition] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to add a new sticker to a set created by the bot. You must use exactly one @@ -2166,6 +2316,7 @@ class Bot(ContextInstanceMixin["Bot"]): for technical requirements :param mask_position: A JSON-serialized object for position where the mask should be placed on faces + :param request_timeout: Request timeout :return: Returns True on success. """ call = AddStickerToSet( @@ -2176,9 +2327,11 @@ class Bot(ContextInstanceMixin["Bot"]): tgs_sticker=tgs_sticker, mask_position=mask_position, ) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def set_sticker_position_in_set(self, sticker: str, position: int,) -> bool: + async def set_sticker_position_in_set( + self, sticker: str, position: int, request_timeout: Optional[int] = None, + ) -> bool: """ Use this method to move a sticker in a set created by the bot to a specific position. Returns True on success. @@ -2187,12 +2340,15 @@ class Bot(ContextInstanceMixin["Bot"]): :param sticker: File identifier of the sticker :param position: New sticker position in the set, zero-based + :param request_timeout: Request timeout :return: Returns True on success. """ call = SetStickerPositionInSet(sticker=sticker, position=position,) - return await self(call) + return await self(call, request_timeout=request_timeout) - async def delete_sticker_from_set(self, sticker: str,) -> bool: + async def delete_sticker_from_set( + self, sticker: str, request_timeout: Optional[int] = None, + ) -> bool: """ Use this method to delete a sticker from a set created by the bot. Returns True on success. @@ -2200,13 +2356,18 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#deletestickerfromset :param sticker: File identifier of the sticker + :param request_timeout: Request timeout :return: Returns True on success. """ call = DeleteStickerFromSet(sticker=sticker,) - return await self(call) + return await self(call, request_timeout=request_timeout) async def set_sticker_set_thumb( - self, name: str, user_id: int, thumb: Optional[Union[InputFile, str]] = None, + self, + name: str, + user_id: int, + thumb: Optional[Union[InputFile, str]] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for @@ -2225,10 +2386,11 @@ class Bot(ContextInstanceMixin["Bot"]): String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data.. Animated sticker set thumbnail can't be uploaded via HTTP URL. + :param request_timeout: Request timeout :return: Returns True on success. """ call = SetStickerSetThumb(name=name, user_id=user_id, thumb=thumb,) - return await self(call) + return await self(call, request_timeout=request_timeout) # ============================================================================================= # Group: Inline mode @@ -2244,6 +2406,7 @@ class Bot(ContextInstanceMixin["Bot"]): next_offset: Optional[str] = None, switch_pm_text: Optional[str] = None, switch_pm_parameter: Optional[str] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Use this method to send answers to an inline query. On success, True is returned. @@ -2260,14 +2423,15 @@ class Bot(ContextInstanceMixin["Bot"]): user who sends the same query :param next_offset: Pass the offset that a client should send in the next query with the same text to receive more results. Pass an empty string if there are - no more results or if you don‘t support pagination. Offset length - can’t exceed 64 bytes. + no more results or if you don't support pagination. Offset length + can't exceed 64 bytes. :param switch_pm_text: If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter switch_pm_parameter :param switch_pm_parameter: Deep-linking parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed. + :param request_timeout: Request timeout :return: On success, True is returned. """ call = AnswerInlineQuery( @@ -2279,7 +2443,7 @@ class Bot(ContextInstanceMixin["Bot"]): switch_pm_text=switch_pm_text, switch_pm_parameter=switch_pm_parameter, ) - return await self(call) + return await self(call, request_timeout=request_timeout) # ============================================================================================= # Group: Payments @@ -2311,6 +2475,7 @@ class Bot(ContextInstanceMixin["Bot"]): disable_notification: Optional[bool] = None, reply_to_message_id: Optional[int] = None, reply_markup: Optional[InlineKeyboardMarkup] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send invoices. On success, the sent Message is returned. @@ -2355,6 +2520,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param reply_markup: A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendInvoice( @@ -2382,7 +2548,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def answer_shipping_query( self, @@ -2390,6 +2556,7 @@ class Bot(ContextInstanceMixin["Bot"]): ok: bool, shipping_options: Optional[List[ShippingOption]] = None, error_message: Optional[str] = None, + request_timeout: Optional[int] = None, ) -> bool: """ If you sent an invoice requesting a shipping address and the parameter is_flexible was @@ -2408,6 +2575,7 @@ class Bot(ContextInstanceMixin["Bot"]): explains why it is impossible to complete the order (e.g. "Sorry, delivery to your desired address is unavailable'). Telegram will display this message to the user. + :param request_timeout: Request timeout :return: On success, True is returned. """ call = AnswerShippingQuery( @@ -2416,10 +2584,14 @@ class Bot(ContextInstanceMixin["Bot"]): shipping_options=shipping_options, error_message=error_message, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def answer_pre_checkout_query( - self, pre_checkout_query_id: str, ok: bool, error_message: Optional[str] = None, + self, + pre_checkout_query_id: str, + ok: bool, + error_message: Optional[str] = None, + request_timeout: Optional[int] = None, ) -> bool: """ Once the user has confirmed their payment and shipping details, the Bot API sends the @@ -2438,12 +2610,13 @@ class Bot(ContextInstanceMixin["Bot"]): while you were busy filling out your payment details. Please choose a different color or garment!"). Telegram will display this message to the user. + :param request_timeout: Request timeout :return: On success, True is returned. """ call = AnswerPreCheckoutQuery( pre_checkout_query_id=pre_checkout_query_id, ok=ok, error_message=error_message, ) - return await self(call) + return await self(call, request_timeout=request_timeout) # ============================================================================================= # Group: Telegram Passport @@ -2451,7 +2624,10 @@ class Bot(ContextInstanceMixin["Bot"]): # ============================================================================================= async def set_passport_data_errors( - self, user_id: int, errors: List[PassportElementError], + self, + user_id: int, + errors: List[PassportElementError], + request_timeout: Optional[int] = None, ) -> bool: """ Informs a user that some of the Telegram Passport elements they provided contains errors. @@ -2467,12 +2643,13 @@ class Bot(ContextInstanceMixin["Bot"]): :param user_id: User identifier :param errors: A JSON-serialized array describing the errors + :param request_timeout: Request timeout :return: The user will not be able to re-submit their Passport to you until the errors are fixed (the contents of the field for which you returned the error must change). Returns True on success. """ call = SetPassportDataErrors(user_id=user_id, errors=errors,) - return await self(call) + return await self(call, request_timeout=request_timeout) # ============================================================================================= # Group: Games @@ -2486,6 +2663,7 @@ class Bot(ContextInstanceMixin["Bot"]): disable_notification: Optional[bool] = None, reply_to_message_id: Optional[int] = None, reply_markup: Optional[InlineKeyboardMarkup] = None, + request_timeout: Optional[int] = None, ) -> Message: """ Use this method to send a game. On success, the sent Message is returned. @@ -2498,9 +2676,10 @@ class Bot(ContextInstanceMixin["Bot"]): :param disable_notification: Sends the message silently. Users will receive a notification with no sound. :param reply_to_message_id: If the message is a reply, ID of the original message - :param reply_markup: A JSON-serialized object for an inline keyboard. If empty, one ‘Play - game_title’ button will be shown. If not empty, the first button must + :param reply_markup: A JSON-serialized object for an inline keyboard. If empty, one 'Play + game_title' button will be shown. If not empty, the first button must launch the game. + :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ call = SendGame( @@ -2510,7 +2689,7 @@ class Bot(ContextInstanceMixin["Bot"]): reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def set_game_score( self, @@ -2521,6 +2700,7 @@ class Bot(ContextInstanceMixin["Bot"]): chat_id: Optional[int] = None, message_id: Optional[int] = None, inline_message_id: Optional[str] = None, + request_timeout: Optional[int] = None, ) -> Union[Message, bool]: """ Use this method to set the score of the specified user in a game. On success, if the @@ -2542,6 +2722,7 @@ class Bot(ContextInstanceMixin["Bot"]): message :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message + :param request_timeout: Request timeout :return: On success, if the message was sent by the bot, returns the edited Message, otherwise returns True. Returns an error, if the new score is not greater than the user's current score in the chat and force is False. @@ -2555,7 +2736,7 @@ class Bot(ContextInstanceMixin["Bot"]): message_id=message_id, inline_message_id=inline_message_id, ) - return await self(call) + return await self(call, request_timeout=request_timeout) async def get_game_high_scores( self, @@ -2563,6 +2744,7 @@ class Bot(ContextInstanceMixin["Bot"]): chat_id: Optional[int] = None, message_id: Optional[int] = None, inline_message_id: Optional[str] = None, + request_timeout: Optional[int] = None, ) -> List[GameHighScore]: """ Use this method to get data for high score tables. Will return the score of the specified @@ -2581,6 +2763,7 @@ class Bot(ContextInstanceMixin["Bot"]): message :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message + :param request_timeout: Request timeout :return: Will return the score of the specified user and several of their neighbors in a game. On success, returns an Array of GameHighScore objects. This method will currently return scores for the target user, plus two of their closest neighbors @@ -2593,4 +2776,4 @@ class Bot(ContextInstanceMixin["Bot"]): message_id=message_id, inline_message_id=inline_message_id, ) - return await self(call) + return await self(call, request_timeout=request_timeout) diff --git a/aiogram/api/client/session/aiohttp.py b/aiogram/api/client/session/aiohttp.py index 774e7186..1fda8bbf 100644 --- a/aiogram/api/client/session/aiohttp.py +++ b/aiogram/api/client/session/aiohttp.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import ( + TYPE_CHECKING, Any, AsyncGenerator, Dict, @@ -20,6 +21,9 @@ from aiogram.api.methods import Request, TelegramMethod from .base import BaseSession +if TYPE_CHECKING: # pragma: no cover + from ..bot import Bot + T = TypeVar("T") _ProxyBasic = Union[str, Tuple[str, BasicAuth]] _ProxyChain = Iterable[_ProxyBasic] @@ -125,15 +129,17 @@ class AiohttpSession(BaseSession): form.add_field(key, value, filename=value.filename or key) return form - async def make_request(self, token: str, call: TelegramMethod[T]) -> T: + async def make_request( + self, bot: Bot, call: TelegramMethod[T], timeout: Optional[int] = None + ) -> T: session = await self.create_session() - request = call.build_request() - url = self.api.api_url(token=token, method=request.method) + request = call.build_request(bot) + url = self.api.api_url(token=bot.token, method=request.method) form = self.build_form_data(request) async with session.post( - url, data=form, timeout=call.request_timeout or self.timeout + url, data=form, timeout=self.timeout if timeout is None else timeout ) as resp: raw_result = await resp.json(loads=self.json_loads) diff --git a/aiogram/api/client/session/base.py b/aiogram/api/client/session/base.py index 8213e4c3..d1249733 100644 --- a/aiogram/api/client/session/base.py +++ b/aiogram/api/client/session/base.py @@ -4,63 +4,39 @@ import abc import datetime import json from types import TracebackType -from typing import Any, AsyncGenerator, Callable, ClassVar, Optional, Type, TypeVar, Union +from typing import ( + TYPE_CHECKING, + Any, + AsyncGenerator, + Callable, + ClassVar, + Optional, + Type, + TypeVar, + Union, +) from aiogram.utils.exceptions import TelegramAPIError +from ....utils.helper import Default from ...methods import Response, TelegramMethod +from ...types import UNSET from ..telegram import PRODUCTION, TelegramAPIServer +if TYPE_CHECKING: # pragma: no cover + from ..bot import Bot + T = TypeVar("T") _JsonLoads = Callable[..., Any] _JsonDumps = Callable[..., str] class BaseSession(abc.ABC): - # global session timeout default_timeout: ClassVar[float] = 60.0 - - _api: TelegramAPIServer - _json_loads: _JsonLoads - _json_dumps: _JsonDumps - _timeout: float - - @property - def api(self) -> TelegramAPIServer: - return getattr(self, "_api", PRODUCTION) # type: ignore - - @api.setter - def api(self, value: TelegramAPIServer) -> None: - self._api = value - - @property - def json_loads(self) -> _JsonLoads: - return getattr(self, "_json_loads", json.loads) # type: ignore - - @json_loads.setter - def json_loads(self, value: _JsonLoads) -> None: - self._json_loads = value # type: ignore - - @property - def json_dumps(self) -> _JsonDumps: - return getattr(self, "_json_dumps", json.dumps) # type: ignore - - @json_dumps.setter - 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: - if hasattr(self, "_timeout"): - del self._timeout + api: Default[TelegramAPIServer] = Default(PRODUCTION) + json_loads: Default[_JsonLoads] = Default(json.loads) + json_dumps: Default[_JsonDumps] = Default(json.dumps) + timeout: Default[float] = Default(fget=lambda self: float(self.__class__.default_timeout)) @classmethod def raise_for_status(cls, response: Response[T]) -> None: @@ -73,7 +49,9 @@ class BaseSession(abc.ABC): pass @abc.abstractmethod - async def make_request(self, token: str, method: TelegramMethod[T]) -> T: # pragma: no cover + async def make_request( + self, bot: Bot, method: TelegramMethod[T], timeout: Optional[int] = UNSET + ) -> T: # pragma: no cover pass @abc.abstractmethod diff --git a/aiogram/api/methods/add_sticker_to_set.py b/aiogram/api/methods/add_sticker_to_set.py index f9c801e3..735371e4 100644 --- a/aiogram/api/methods/add_sticker_to_set.py +++ b/aiogram/api/methods/add_sticker_to_set.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import InputFile, MaskPosition from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class AddStickerToSet(TelegramMethod[bool]): """ @@ -34,7 +39,7 @@ class AddStickerToSet(TelegramMethod[bool]): mask_position: Optional[MaskPosition] = None """A JSON-serialized object for position where the mask should be placed on faces""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/answer_callback_query.py b/aiogram/api/methods/answer_callback_query.py index f0d8405f..1f407c70 100644 --- a/aiogram/api/methods/answer_callback_query.py +++ b/aiogram/api/methods/answer_callback_query.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class AnswerCallbackQuery(TelegramMethod[bool]): """ @@ -33,7 +38,7 @@ class AnswerCallbackQuery(TelegramMethod[bool]): """The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="answerCallbackQuery", data=data) diff --git a/aiogram/api/methods/answer_inline_query.py b/aiogram/api/methods/answer_inline_query.py index a5b8bd7b..c5c2709d 100644 --- a/aiogram/api/methods/answer_inline_query.py +++ b/aiogram/api/methods/answer_inline_query.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List, Optional from ..types import InlineQueryResult from .base import Request, TelegramMethod, prepare_parse_mode +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class AnswerInlineQuery(TelegramMethod[bool]): """ @@ -26,8 +31,8 @@ class AnswerInlineQuery(TelegramMethod[bool]): query. By default, results may be returned to any user who sends the same query""" next_offset: Optional[str] = None """Pass the offset that a client should send in the next query with the same text to receive - more results. Pass an empty string if there are no more results or if you don‘t support - pagination. Offset length can’t exceed 64 bytes.""" + more results. Pass an empty string if there are no more results or if you don't support + pagination. Offset length can't exceed 64 bytes.""" switch_pm_text: Optional[str] = None """If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter @@ -36,8 +41,8 @@ class AnswerInlineQuery(TelegramMethod[bool]): """Deep-linking parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() - prepare_parse_mode(data["results"]) + prepare_parse_mode(bot, data["results"]) return Request(method="answerInlineQuery", data=data) diff --git a/aiogram/api/methods/answer_pre_checkout_query.py b/aiogram/api/methods/answer_pre_checkout_query.py index fae21d5c..8ffae74e 100644 --- a/aiogram/api/methods/answer_pre_checkout_query.py +++ b/aiogram/api/methods/answer_pre_checkout_query.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class AnswerPreCheckoutQuery(TelegramMethod[bool]): """ @@ -26,7 +31,7 @@ class AnswerPreCheckoutQuery(TelegramMethod[bool]): amazing black T-shirts while you were busy filling out your payment details. Please choose a different color or garment!"). Telegram will display this message to the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="answerPreCheckoutQuery", data=data) diff --git a/aiogram/api/methods/answer_shipping_query.py b/aiogram/api/methods/answer_shipping_query.py index 986d8fe6..da79adb5 100644 --- a/aiogram/api/methods/answer_shipping_query.py +++ b/aiogram/api/methods/answer_shipping_query.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List, Optional from ..types import ShippingOption from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class AnswerShippingQuery(TelegramMethod[bool]): """ @@ -27,7 +32,7 @@ class AnswerShippingQuery(TelegramMethod[bool]): impossible to complete the order (e.g. "Sorry, delivery to your desired address is unavailable'). Telegram will display this message to the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="answerShippingQuery", data=data) diff --git a/aiogram/api/methods/base.py b/aiogram/api/methods/base.py index 8d15edba..dfdfda34 100644 --- a/aiogram/api/methods/base.py +++ b/aiogram/api/methods/base.py @@ -4,10 +4,10 @@ import abc import secrets from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, Optional, TypeVar, Union -from pydantic import BaseConfig, BaseModel, Extra +from pydantic import BaseConfig, BaseModel, Extra, root_validator from pydantic.generics import GenericModel -from ..types import InputFile, ResponseParameters +from ..types import UNSET, InputFile, ResponseParameters if TYPE_CHECKING: # pragma: no cover from ..client.bot import Bot @@ -46,22 +46,33 @@ class TelegramMethod(abc.ABC, BaseModel, Generic[T]): arbitrary_types_allowed = True orm_mode = True + @root_validator(pre=True) + def remove_unset(cls, values: Dict[str, Any]) -> Dict[str, Any]: + """ + Remove UNSET from `parse_mode` before fields validation. + + We use UNSET as a sentinel value for `parse_mode` and replace it to real value later. + It isn't a problem when it's just default value for a model field, but UNSET might be passing to + a model initialization from `Bot.method_name`, so we must take care of it and + remove it before fields validation. + """ + for parse_mode_attribute in {"parse_mode", "explanation_parse_mode"}: + if parse_mode_attribute in values and values[parse_mode_attribute] is UNSET: + values.pop(parse_mode_attribute) + return values + @property @abc.abstractmethod def __returning__(self) -> type: # pragma: no cover pass @abc.abstractmethod - def build_request(self) -> Request: # pragma: no cover + def build_request(self, bot: Bot) -> Request: # pragma: no cover pass - request_timeout: Optional[float] = None - 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) @@ -115,19 +126,20 @@ def prepare_media_file(data: Dict[str, Any], files: Dict[str, InputFile]) -> Non data["media"]["media"] = f"attach://{tag}" -def prepare_parse_mode(root: Any, parse_mode_property: str = "parse_mode") -> None: +def prepare_parse_mode(bot: Bot, root: Any, parse_mode_property: str = "parse_mode") -> None: + """ + Find and set parse_mode with highest priority. + + Developer can manually set parse_mode for each message (or message-like) object, + but if parse_mode was unset we should use value from Bot object. + + We can't use None for "unset state", because None itself is the parse_mode option. + """ if isinstance(root, list): for item in root: - prepare_parse_mode(item, parse_mode_property=parse_mode_property) - return - - if root.get(parse_mode_property): - return - - from ..client.bot import Bot - - bot = Bot.get_current(no_error=True) - if bot and bot.parse_mode: - root[parse_mode_property] = bot.parse_mode - return - return + prepare_parse_mode(bot=bot, root=item, parse_mode_property=parse_mode_property) + elif root.get(parse_mode_property, UNSET) is UNSET: + if bot.parse_mode: + root[parse_mode_property] = bot.parse_mode + else: + root[parse_mode_property] = None diff --git a/aiogram/api/methods/create_new_sticker_set.py b/aiogram/api/methods/create_new_sticker_set.py index f495986c..e6a222ae 100644 --- a/aiogram/api/methods/create_new_sticker_set.py +++ b/aiogram/api/methods/create_new_sticker_set.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import InputFile, MaskPosition from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class CreateNewStickerSet(TelegramMethod[bool]): """ @@ -40,7 +45,7 @@ class CreateNewStickerSet(TelegramMethod[bool]): mask_position: Optional[MaskPosition] = None """A JSON-serialized object for position where the mask should be placed on faces""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/delete_chat_photo.py b/aiogram/api/methods/delete_chat_photo.py index dc9e075c..8be35986 100644 --- a/aiogram/api/methods/delete_chat_photo.py +++ b/aiogram/api/methods/delete_chat_photo.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class DeleteChatPhoto(TelegramMethod[bool]): """ @@ -18,7 +23,7 @@ class DeleteChatPhoto(TelegramMethod[bool]): """Unique identifier for the target chat or username of the target channel (in the format @channelusername)""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="deleteChatPhoto", data=data) diff --git a/aiogram/api/methods/delete_chat_sticker_set.py b/aiogram/api/methods/delete_chat_sticker_set.py index 45d473c0..14801ec4 100644 --- a/aiogram/api/methods/delete_chat_sticker_set.py +++ b/aiogram/api/methods/delete_chat_sticker_set.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class DeleteChatStickerSet(TelegramMethod[bool]): """ @@ -19,7 +24,7 @@ class DeleteChatStickerSet(TelegramMethod[bool]): """Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="deleteChatStickerSet", data=data) diff --git a/aiogram/api/methods/delete_message.py b/aiogram/api/methods/delete_message.py index a38df0c0..da0edce1 100644 --- a/aiogram/api/methods/delete_message.py +++ b/aiogram/api/methods/delete_message.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class DeleteMessage(TelegramMethod[bool]): """ @@ -28,7 +33,7 @@ class DeleteMessage(TelegramMethod[bool]): message_id: int """Identifier of the message to delete""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="deleteMessage", data=data) diff --git a/aiogram/api/methods/delete_sticker_from_set.py b/aiogram/api/methods/delete_sticker_from_set.py index 2637c599..30ac3d4b 100644 --- a/aiogram/api/methods/delete_sticker_from_set.py +++ b/aiogram/api/methods/delete_sticker_from_set.py @@ -1,7 +1,12 @@ -from typing import Any, Dict +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class DeleteStickerFromSet(TelegramMethod[bool]): """ @@ -15,7 +20,7 @@ class DeleteStickerFromSet(TelegramMethod[bool]): sticker: str """File identifier of the sticker""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="deleteStickerFromSet", data=data) diff --git a/aiogram/api/methods/delete_webhook.py b/aiogram/api/methods/delete_webhook.py index 0783974e..9242a634 100644 --- a/aiogram/api/methods/delete_webhook.py +++ b/aiogram/api/methods/delete_webhook.py @@ -1,7 +1,12 @@ -from typing import Any, Dict +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class DeleteWebhook(TelegramMethod[bool]): """ @@ -13,7 +18,7 @@ class DeleteWebhook(TelegramMethod[bool]): __returning__ = bool - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="deleteWebhook", data=data) diff --git a/aiogram/api/methods/edit_message_caption.py b/aiogram/api/methods/edit_message_caption.py index c0e14469..3359c45f 100644 --- a/aiogram/api/methods/edit_message_caption.py +++ b/aiogram/api/methods/edit_message_caption.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations -from ..types import InlineKeyboardMarkup, Message +from typing import TYPE_CHECKING, Any, Dict, Optional, Union + +from ..types import UNSET, InlineKeyboardMarkup, Message from .base import Request, TelegramMethod, prepare_parse_mode +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class EditMessageCaption(TelegramMethod[Union[Message, bool]]): """ @@ -23,13 +28,13 @@ class EditMessageCaption(TelegramMethod[Union[Message, bool]]): """Required if chat_id and message_id are not specified. Identifier of the inline message""" caption: Optional[str] = None """New caption of the message, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the message caption. See formatting options for more details.""" reply_markup: Optional[InlineKeyboardMarkup] = None """A JSON-serialized object for an inline keyboard.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() - prepare_parse_mode(data) + prepare_parse_mode(bot, data) return Request(method="editMessageCaption", data=data) diff --git a/aiogram/api/methods/edit_message_live_location.py b/aiogram/api/methods/edit_message_live_location.py index ca54c11e..ddd95d74 100644 --- a/aiogram/api/methods/edit_message_live_location.py +++ b/aiogram/api/methods/edit_message_live_location.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import InlineKeyboardMarkup, Message from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class EditMessageLiveLocation(TelegramMethod[Union[Message, bool]]): """ @@ -30,7 +35,7 @@ class EditMessageLiveLocation(TelegramMethod[Union[Message, bool]]): reply_markup: Optional[InlineKeyboardMarkup] = None """A JSON-serialized object for a new inline keyboard.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="editMessageLiveLocation", data=data) diff --git a/aiogram/api/methods/edit_message_media.py b/aiogram/api/methods/edit_message_media.py index 88c43973..db8e344f 100644 --- a/aiogram/api/methods/edit_message_media.py +++ b/aiogram/api/methods/edit_message_media.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import InlineKeyboardMarkup, InputFile, InputMedia, Message from .base import Request, TelegramMethod, prepare_media_file, prepare_parse_mode +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class EditMessageMedia(TelegramMethod[Union[Message, bool]]): """ @@ -30,9 +35,9 @@ class EditMessageMedia(TelegramMethod[Union[Message, bool]]): reply_markup: Optional[InlineKeyboardMarkup] = None """A JSON-serialized object for a new inline keyboard.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() - prepare_parse_mode(data["media"]) + prepare_parse_mode(bot, data["media"]) files: Dict[str, InputFile] = {} prepare_media_file(data=data, files=files) diff --git a/aiogram/api/methods/edit_message_reply_markup.py b/aiogram/api/methods/edit_message_reply_markup.py index 49dec7fc..b9973e40 100644 --- a/aiogram/api/methods/edit_message_reply_markup.py +++ b/aiogram/api/methods/edit_message_reply_markup.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import InlineKeyboardMarkup, Message from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class EditMessageReplyMarkup(TelegramMethod[Union[Message, bool]]): """ @@ -24,7 +29,7 @@ class EditMessageReplyMarkup(TelegramMethod[Union[Message, bool]]): reply_markup: Optional[InlineKeyboardMarkup] = None """A JSON-serialized object for an inline keyboard.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="editMessageReplyMarkup", data=data) diff --git a/aiogram/api/methods/edit_message_text.py b/aiogram/api/methods/edit_message_text.py index da1da00f..d30fc82e 100644 --- a/aiogram/api/methods/edit_message_text.py +++ b/aiogram/api/methods/edit_message_text.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations -from ..types import InlineKeyboardMarkup, Message +from typing import TYPE_CHECKING, Any, Dict, Optional, Union + +from ..types import UNSET, InlineKeyboardMarkup, Message from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class EditMessageText(TelegramMethod[Union[Message, bool]]): """ @@ -23,14 +28,14 @@ class EditMessageText(TelegramMethod[Union[Message, bool]]): """Required if inline_message_id is not specified. Identifier of the message to edit""" inline_message_id: Optional[str] = None """Required if chat_id and message_id are not specified. Identifier of the inline message""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the message text. See formatting options for more details.""" disable_web_page_preview: Optional[bool] = None """Disables link previews for links in this message""" reply_markup: Optional[InlineKeyboardMarkup] = None """A JSON-serialized object for an inline keyboard.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="editMessageText", data=data) diff --git a/aiogram/api/methods/export_chat_invite_link.py b/aiogram/api/methods/export_chat_invite_link.py index 923664e0..b77b01a5 100644 --- a/aiogram/api/methods/export_chat_invite_link.py +++ b/aiogram/api/methods/export_chat_invite_link.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class ExportChatInviteLink(TelegramMethod[str]): """ @@ -23,7 +28,7 @@ class ExportChatInviteLink(TelegramMethod[str]): """Unique identifier for the target chat or username of the target channel (in the format @channelusername)""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="exportChatInviteLink", data=data) diff --git a/aiogram/api/methods/forward_message.py b/aiogram/api/methods/forward_message.py index d8082704..06f19956 100644 --- a/aiogram/api/methods/forward_message.py +++ b/aiogram/api/methods/forward_message.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import Message from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class ForwardMessage(TelegramMethod[Message]): """ @@ -24,7 +29,7 @@ class ForwardMessage(TelegramMethod[Message]): disable_notification: Optional[bool] = None """Sends the message silently. Users will receive a notification with no sound.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="forwardMessage", data=data) diff --git a/aiogram/api/methods/get_chat.py b/aiogram/api/methods/get_chat.py index be1fe55d..6131c00d 100644 --- a/aiogram/api/methods/get_chat.py +++ b/aiogram/api/methods/get_chat.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from ..types import Chat from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetChat(TelegramMethod[Chat]): """ @@ -19,7 +24,7 @@ class GetChat(TelegramMethod[Chat]): """Unique identifier for the target chat or username of the target supergroup or channel (in the format @channelusername)""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getChat", data=data) diff --git a/aiogram/api/methods/get_chat_administrators.py b/aiogram/api/methods/get_chat_administrators.py index 75398f82..584d6f9a 100644 --- a/aiogram/api/methods/get_chat_administrators.py +++ b/aiogram/api/methods/get_chat_administrators.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, List, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List, Union from ..types import ChatMember from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetChatAdministrators(TelegramMethod[List[ChatMember]]): """ @@ -20,7 +25,7 @@ class GetChatAdministrators(TelegramMethod[List[ChatMember]]): """Unique identifier for the target chat or username of the target supergroup or channel (in the format @channelusername)""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getChatAdministrators", data=data) diff --git a/aiogram/api/methods/get_chat_member.py b/aiogram/api/methods/get_chat_member.py index 69a1c6a3..903fd803 100644 --- a/aiogram/api/methods/get_chat_member.py +++ b/aiogram/api/methods/get_chat_member.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from ..types import ChatMember from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetChatMember(TelegramMethod[ChatMember]): """ @@ -20,7 +25,7 @@ class GetChatMember(TelegramMethod[ChatMember]): user_id: int """Unique identifier of the target user""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getChatMember", data=data) diff --git a/aiogram/api/methods/get_chat_members_count.py b/aiogram/api/methods/get_chat_members_count.py index 50b4f484..9b830af0 100644 --- a/aiogram/api/methods/get_chat_members_count.py +++ b/aiogram/api/methods/get_chat_members_count.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetChatMembersCount(TelegramMethod[int]): """ @@ -16,7 +21,7 @@ class GetChatMembersCount(TelegramMethod[int]): """Unique identifier for the target chat or username of the target supergroup or channel (in the format @channelusername)""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getChatMembersCount", data=data) diff --git a/aiogram/api/methods/get_file.py b/aiogram/api/methods/get_file.py index f39f2ebf..9fce6eb0 100644 --- a/aiogram/api/methods/get_file.py +++ b/aiogram/api/methods/get_file.py @@ -1,8 +1,13 @@ -from typing import Any, Dict +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict from ..types import File from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetFile(TelegramMethod[File]): """ @@ -22,7 +27,7 @@ class GetFile(TelegramMethod[File]): file_id: str """File identifier to get info about""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getFile", data=data) diff --git a/aiogram/api/methods/get_game_high_scores.py b/aiogram/api/methods/get_game_high_scores.py index 5f1b1bb3..c2dd0671 100644 --- a/aiogram/api/methods/get_game_high_scores.py +++ b/aiogram/api/methods/get_game_high_scores.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List, Optional from ..types import GameHighScore from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetGameHighScores(TelegramMethod[List[GameHighScore]]): """ @@ -27,7 +32,7 @@ class GetGameHighScores(TelegramMethod[List[GameHighScore]]): inline_message_id: Optional[str] = None """Required if chat_id and message_id are not specified. Identifier of the inline message""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getGameHighScores", data=data) diff --git a/aiogram/api/methods/get_me.py b/aiogram/api/methods/get_me.py index f12f75fc..c9171d47 100644 --- a/aiogram/api/methods/get_me.py +++ b/aiogram/api/methods/get_me.py @@ -1,8 +1,13 @@ -from typing import Any, Dict +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict from ..types import User from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetMe(TelegramMethod[User]): """ @@ -14,7 +19,7 @@ class GetMe(TelegramMethod[User]): __returning__ = User - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getMe", data=data) diff --git a/aiogram/api/methods/get_my_commands.py b/aiogram/api/methods/get_my_commands.py index 1e1d8f1f..c748cb92 100644 --- a/aiogram/api/methods/get_my_commands.py +++ b/aiogram/api/methods/get_my_commands.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, List +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List from ..types import BotCommand from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetMyCommands(TelegramMethod[List[BotCommand]]): """ @@ -14,7 +19,7 @@ class GetMyCommands(TelegramMethod[List[BotCommand]]): __returning__ = List[BotCommand] - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getMyCommands", data=data) diff --git a/aiogram/api/methods/get_sticker_set.py b/aiogram/api/methods/get_sticker_set.py index f929117c..56e54577 100644 --- a/aiogram/api/methods/get_sticker_set.py +++ b/aiogram/api/methods/get_sticker_set.py @@ -1,8 +1,13 @@ -from typing import Any, Dict +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict from ..types import StickerSet from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetStickerSet(TelegramMethod[StickerSet]): """ @@ -16,7 +21,7 @@ class GetStickerSet(TelegramMethod[StickerSet]): name: str """Name of the sticker set""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getStickerSet", data=data) diff --git a/aiogram/api/methods/get_updates.py b/aiogram/api/methods/get_updates.py index 20ca5a8a..ffb74c92 100644 --- a/aiogram/api/methods/get_updates.py +++ b/aiogram/api/methods/get_updates.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List, Optional from ..types import Update from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetUpdates(TelegramMethod[List[Update]]): """ @@ -37,7 +42,7 @@ class GetUpdates(TelegramMethod[List[Update]]): list to receive all updates regardless of type (default). If not specified, the previous setting will be used.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getUpdates", data=data) diff --git a/aiogram/api/methods/get_user_profile_photos.py b/aiogram/api/methods/get_user_profile_photos.py index def605c0..bce33116 100644 --- a/aiogram/api/methods/get_user_profile_photos.py +++ b/aiogram/api/methods/get_user_profile_photos.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional from ..types import UserProfilePhotos from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetUserProfilePhotos(TelegramMethod[UserProfilePhotos]): """ @@ -22,7 +27,7 @@ class GetUserProfilePhotos(TelegramMethod[UserProfilePhotos]): """Limits the number of photos to be retrieved. Values between 1-100 are accepted. Defaults to 100.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getUserProfilePhotos", data=data) diff --git a/aiogram/api/methods/get_webhook_info.py b/aiogram/api/methods/get_webhook_info.py index 8a147162..218569bb 100644 --- a/aiogram/api/methods/get_webhook_info.py +++ b/aiogram/api/methods/get_webhook_info.py @@ -1,8 +1,13 @@ -from typing import Any, Dict +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict from ..types import WebhookInfo from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class GetWebhookInfo(TelegramMethod[WebhookInfo]): """ @@ -15,7 +20,7 @@ class GetWebhookInfo(TelegramMethod[WebhookInfo]): __returning__ = WebhookInfo - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="getWebhookInfo", data=data) diff --git a/aiogram/api/methods/kick_chat_member.py b/aiogram/api/methods/kick_chat_member.py index 64af64a1..45880c4c 100644 --- a/aiogram/api/methods/kick_chat_member.py +++ b/aiogram/api/methods/kick_chat_member.py @@ -1,8 +1,13 @@ +from __future__ import annotations + import datetime -from typing import Any, Dict, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class KickChatMember(TelegramMethod[bool]): """ @@ -25,7 +30,7 @@ class KickChatMember(TelegramMethod[bool]): """Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="kickChatMember", data=data) diff --git a/aiogram/api/methods/leave_chat.py b/aiogram/api/methods/leave_chat.py index 0827fc88..7af04143 100644 --- a/aiogram/api/methods/leave_chat.py +++ b/aiogram/api/methods/leave_chat.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class LeaveChat(TelegramMethod[bool]): """ @@ -16,7 +21,7 @@ class LeaveChat(TelegramMethod[bool]): """Unique identifier for the target chat or username of the target supergroup or channel (in the format @channelusername)""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="leaveChat", data=data) diff --git a/aiogram/api/methods/pin_chat_message.py b/aiogram/api/methods/pin_chat_message.py index 1d7383e7..dbf0d013 100644 --- a/aiogram/api/methods/pin_chat_message.py +++ b/aiogram/api/methods/pin_chat_message.py @@ -1,13 +1,18 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class PinChatMessage(TelegramMethod[bool]): """ Use this method to pin a message in a group, a supergroup, or a channel. The bot must be an - administrator in the chat for this to work and must have the ‘can_pin_messages’ admin right in - the supergroup or ‘can_edit_messages’ admin right in the channel. Returns True on success. + administrator in the chat for this to work and must have the 'can_pin_messages' admin right in + the supergroup or 'can_edit_messages' admin right in the channel. Returns True on success. Source: https://core.telegram.org/bots/api#pinchatmessage """ @@ -23,7 +28,7 @@ class PinChatMessage(TelegramMethod[bool]): """Pass True, if it is not necessary to send a notification to all chat members about the new pinned message. Notifications are always disabled in channels.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="pinChatMessage", data=data) diff --git a/aiogram/api/methods/promote_chat_member.py b/aiogram/api/methods/promote_chat_member.py index db32fd5b..1b4ca625 100644 --- a/aiogram/api/methods/promote_chat_member.py +++ b/aiogram/api/methods/promote_chat_member.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class PromoteChatMember(TelegramMethod[bool]): """ @@ -39,7 +44,7 @@ class PromoteChatMember(TelegramMethod[bool]): privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by him)""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="promoteChatMember", data=data) diff --git a/aiogram/api/methods/restrict_chat_member.py b/aiogram/api/methods/restrict_chat_member.py index 05dac3a3..9626e05a 100644 --- a/aiogram/api/methods/restrict_chat_member.py +++ b/aiogram/api/methods/restrict_chat_member.py @@ -1,9 +1,14 @@ +from __future__ import annotations + import datetime -from typing import Any, Dict, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ChatPermissions from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class RestrictChatMember(TelegramMethod[bool]): """ @@ -28,7 +33,7 @@ class RestrictChatMember(TelegramMethod[bool]): more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="restrictChatMember", data=data) diff --git a/aiogram/api/methods/send_animation.py b/aiogram/api/methods/send_animation.py index 6e5f6444..dcb8e91c 100644 --- a/aiogram/api/methods/send_animation.py +++ b/aiogram/api/methods/send_animation.py @@ -1,6 +1,9 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( + UNSET, ForceReply, InlineKeyboardMarkup, InputFile, @@ -10,6 +13,9 @@ from ..types import ( ) from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendAnimation(TelegramMethod[Message]): """ @@ -38,14 +44,14 @@ class SendAnimation(TelegramMethod[Message]): thumb: Optional[Union[InputFile, str]] = None """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 320. Ignored if the file is not uploaded - using multipart/form-data. Thumbnails can’t be reused and can be only uploaded as a new + A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded + using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under .""" caption: Optional[str] = None """Animation caption (may also be used when resending animation by file_id), 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the animation caption. See formatting options for more details.""" disable_notification: Optional[bool] = None @@ -58,7 +64,7 @@ class SendAnimation(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"animation", "thumb"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/send_audio.py b/aiogram/api/methods/send_audio.py index d56782e5..739fadcb 100644 --- a/aiogram/api/methods/send_audio.py +++ b/aiogram/api/methods/send_audio.py @@ -1,6 +1,9 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( + UNSET, ForceReply, InlineKeyboardMarkup, InputFile, @@ -10,6 +13,9 @@ from ..types import ( ) from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendAudio(TelegramMethod[Message]): """ @@ -33,7 +39,7 @@ class SendAudio(TelegramMethod[Message]): file from the Internet, or upload a new one using multipart/form-data.""" caption: Optional[str] = None """Audio caption, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the audio caption. See formatting options for more details.""" duration: Optional[int] = None """Duration of the audio in seconds""" @@ -44,8 +50,8 @@ class SendAudio(TelegramMethod[Message]): thumb: Optional[Union[InputFile, str]] = None """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 320. Ignored if the file is not uploaded - using multipart/form-data. Thumbnails can’t be reused and can be only uploaded as a new + A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded + using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under .""" disable_notification: Optional[bool] = None @@ -58,7 +64,7 @@ class SendAudio(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"audio", "thumb"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/send_chat_action.py b/aiogram/api/methods/send_chat_action.py index 7f5710a3..bff4283c 100644 --- a/aiogram/api/methods/send_chat_action.py +++ b/aiogram/api/methods/send_chat_action.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendChatAction(TelegramMethod[bool]): """ @@ -29,7 +34,7 @@ class SendChatAction(TelegramMethod[bool]): record_audio or upload_audio for audio files, upload_document for general files, find_location for location data, record_video_note or upload_video_note for video notes.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="sendChatAction", data=data) diff --git a/aiogram/api/methods/send_contact.py b/aiogram/api/methods/send_contact.py index b7569804..90c1fd7d 100644 --- a/aiogram/api/methods/send_contact.py +++ b/aiogram/api/methods/send_contact.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( ForceReply, @@ -9,6 +11,9 @@ from ..types import ( ) from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendContact(TelegramMethod[Message]): """ @@ -40,7 +45,7 @@ class SendContact(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="sendContact", data=data) diff --git a/aiogram/api/methods/send_dice.py b/aiogram/api/methods/send_dice.py index f200850e..1b69fed4 100644 --- a/aiogram/api/methods/send_dice.py +++ b/aiogram/api/methods/send_dice.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( ForceReply, @@ -9,12 +11,14 @@ from ..types import ( ) from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendDice(TelegramMethod[Message]): """ - Use this method to send a dice, which will have a random value from 1 to 6. On success, the - sent Message is returned. (Yes, we're aware of the 'proper' singular of die. But it's awkward, - and we decided to help it change. One dice at a time!) + Use this method to send an animated emoji that will display a random value. On success, the + sent Message is returned. Source: https://core.telegram.org/bots/api#senddice """ @@ -25,8 +29,8 @@ class SendDice(TelegramMethod[Message]): """Unique identifier for the target chat or username of the target channel (in the format @channelusername)""" emoji: Optional[str] = None - """Emoji on which the dice throw animation is based. Currently, must be one of '' or ''. - Defauts to ''""" + """Emoji on which the dice throw animation is based. Currently, must be one of '', '', or ''. + Dice can have values 1-6 for '' and '', and values 1-5 for ''. Defaults to ''""" disable_notification: Optional[bool] = None """Sends the message silently. Users will receive a notification with no sound.""" reply_to_message_id: Optional[int] = None @@ -37,7 +41,7 @@ class SendDice(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="sendDice", data=data) diff --git a/aiogram/api/methods/send_document.py b/aiogram/api/methods/send_document.py index 8415aadf..9ee44b54 100644 --- a/aiogram/api/methods/send_document.py +++ b/aiogram/api/methods/send_document.py @@ -1,6 +1,9 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( + UNSET, ForceReply, InlineKeyboardMarkup, InputFile, @@ -10,6 +13,9 @@ from ..types import ( ) from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendDocument(TelegramMethod[Message]): """ @@ -32,14 +38,14 @@ class SendDocument(TelegramMethod[Message]): thumb: Optional[Union[InputFile, str]] = None """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 320. Ignored if the file is not uploaded - using multipart/form-data. Thumbnails can’t be reused and can be only uploaded as a new + A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded + using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under .""" caption: Optional[str] = None """Document caption (may also be used when resending documents by file_id), 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the document caption. See formatting options for more details.""" disable_notification: Optional[bool] = None """Sends the message silently. Users will receive a notification with no sound.""" @@ -51,7 +57,7 @@ class SendDocument(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"document", "thumb"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/send_game.py b/aiogram/api/methods/send_game.py index 0e32c6eb..15257073 100644 --- a/aiogram/api/methods/send_game.py +++ b/aiogram/api/methods/send_game.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional from ..types import InlineKeyboardMarkup, Message from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendGame(TelegramMethod[Message]): """ @@ -23,10 +28,10 @@ class SendGame(TelegramMethod[Message]): reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" reply_markup: Optional[InlineKeyboardMarkup] = None - """A JSON-serialized object for an inline keyboard. If empty, one ‘Play game_title’ button + """A JSON-serialized object for an inline keyboard. If empty, one 'Play game_title' button will be shown. If not empty, the first button must launch the game.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="sendGame", data=data) diff --git a/aiogram/api/methods/send_invoice.py b/aiogram/api/methods/send_invoice.py index f66b6a80..fa9f0615 100644 --- a/aiogram/api/methods/send_invoice.py +++ b/aiogram/api/methods/send_invoice.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List, Optional from ..types import InlineKeyboardMarkup, LabeledPrice, Message from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendInvoice(TelegramMethod[Message]): """ @@ -66,7 +71,7 @@ class SendInvoice(TelegramMethod[Message]): """A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="sendInvoice", data=data) diff --git a/aiogram/api/methods/send_location.py b/aiogram/api/methods/send_location.py index 541a81fc..76cdda91 100644 --- a/aiogram/api/methods/send_location.py +++ b/aiogram/api/methods/send_location.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( ForceReply, @@ -9,6 +11,9 @@ from ..types import ( ) from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendLocation(TelegramMethod[Message]): """ @@ -39,7 +44,7 @@ class SendLocation(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="sendLocation", data=data) diff --git a/aiogram/api/methods/send_media_group.py b/aiogram/api/methods/send_media_group.py index 735711dc..70c804e1 100644 --- a/aiogram/api/methods/send_media_group.py +++ b/aiogram/api/methods/send_media_group.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, List, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from ..types import InputFile, InputMediaPhoto, InputMediaVideo, Message from .base import Request, TelegramMethod, prepare_input_media, prepare_parse_mode +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendMediaGroup(TelegramMethod[List[Message]]): """ @@ -24,9 +29,9 @@ class SendMediaGroup(TelegramMethod[List[Message]]): reply_to_message_id: Optional[int] = None """If the messages are a reply, ID of the original message""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() - prepare_parse_mode(data["media"]) + prepare_parse_mode(bot, data["media"]) files: Dict[str, InputFile] = {} prepare_input_media(data, files) diff --git a/aiogram/api/methods/send_message.py b/aiogram/api/methods/send_message.py index 080adfdb..9dbeaa16 100644 --- a/aiogram/api/methods/send_message.py +++ b/aiogram/api/methods/send_message.py @@ -1,6 +1,9 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( + UNSET, ForceReply, InlineKeyboardMarkup, Message, @@ -9,6 +12,9 @@ from ..types import ( ) from .base import Request, TelegramMethod, prepare_parse_mode +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendMessage(TelegramMethod[Message]): """ @@ -24,7 +30,7 @@ class SendMessage(TelegramMethod[Message]): @channelusername)""" text: str """Text of the message to be sent, 1-4096 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the message text. See formatting options for more details.""" disable_web_page_preview: Optional[bool] = None """Disables link previews for links in this message""" @@ -38,8 +44,8 @@ class SendMessage(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() - prepare_parse_mode(data) + prepare_parse_mode(bot, data) return Request(method="sendMessage", data=data) diff --git a/aiogram/api/methods/send_photo.py b/aiogram/api/methods/send_photo.py index 2b75b815..b35cc165 100644 --- a/aiogram/api/methods/send_photo.py +++ b/aiogram/api/methods/send_photo.py @@ -1,6 +1,9 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( + UNSET, ForceReply, InlineKeyboardMarkup, InputFile, @@ -10,6 +13,9 @@ from ..types import ( ) from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendPhoto(TelegramMethod[Message]): """ @@ -30,7 +36,7 @@ class SendPhoto(TelegramMethod[Message]): caption: Optional[str] = None """Photo caption (may also be used when resending photos by file_id), 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the photo caption. See formatting options for more details.""" disable_notification: Optional[bool] = None """Sends the message silently. Users will receive a notification with no sound.""" @@ -42,7 +48,7 @@ class SendPhoto(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"photo"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/send_poll.py b/aiogram/api/methods/send_poll.py index 95ed10ed..35fa45be 100644 --- a/aiogram/api/methods/send_poll.py +++ b/aiogram/api/methods/send_poll.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import datetime -from typing import Any, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from ..types import ( + UNSET, ForceReply, InlineKeyboardMarkup, Message, @@ -10,6 +13,9 @@ from ..types import ( ) from .base import Request, TelegramMethod, prepare_parse_mode +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendPoll(TelegramMethod[Message]): """ @@ -39,7 +45,7 @@ class SendPoll(TelegramMethod[Message]): explanation: Optional[str] = None """Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-200 characters with at most 2 line feeds after entities parsing""" - explanation_parse_mode: Optional[str] = None + explanation_parse_mode: Optional[str] = UNSET """Mode for parsing entities in the explanation. See formatting options for more details.""" open_period: Optional[int] = None """Amount of time in seconds the poll will be active after creation, 5-600. Can't be used @@ -59,8 +65,8 @@ class SendPoll(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() - prepare_parse_mode(data, parse_mode_property="explanation_parse_mode") + prepare_parse_mode(bot, data, parse_mode_property="explanation_parse_mode") return Request(method="sendPoll", data=data) diff --git a/aiogram/api/methods/send_sticker.py b/aiogram/api/methods/send_sticker.py index e110b2f8..a2adbffa 100644 --- a/aiogram/api/methods/send_sticker.py +++ b/aiogram/api/methods/send_sticker.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( ForceReply, @@ -10,6 +12,9 @@ from ..types import ( ) from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendSticker(TelegramMethod[Message]): """ @@ -38,7 +43,7 @@ class SendSticker(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"sticker"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/send_venue.py b/aiogram/api/methods/send_venue.py index 0e03dc6a..e81455e4 100644 --- a/aiogram/api/methods/send_venue.py +++ b/aiogram/api/methods/send_venue.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( ForceReply, @@ -9,6 +11,9 @@ from ..types import ( ) from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendVenue(TelegramMethod[Message]): """ @@ -45,7 +50,7 @@ class SendVenue(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="sendVenue", data=data) diff --git a/aiogram/api/methods/send_video.py b/aiogram/api/methods/send_video.py index 1017b286..c9c5acb2 100644 --- a/aiogram/api/methods/send_video.py +++ b/aiogram/api/methods/send_video.py @@ -1,6 +1,9 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( + UNSET, ForceReply, InlineKeyboardMarkup, InputFile, @@ -10,6 +13,9 @@ from ..types import ( ) from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendVideo(TelegramMethod[Message]): """ @@ -38,14 +44,14 @@ class SendVideo(TelegramMethod[Message]): thumb: Optional[Union[InputFile, str]] = None """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 320. Ignored if the file is not uploaded - using multipart/form-data. Thumbnails can’t be reused and can be only uploaded as a new + A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded + using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under .""" caption: Optional[str] = None """Video caption (may also be used when resending videos by file_id), 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the video caption. See formatting options for more details.""" supports_streaming: Optional[bool] = None """Pass True, if the uploaded video is suitable for streaming""" @@ -59,7 +65,7 @@ class SendVideo(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"video", "thumb"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/send_video_note.py b/aiogram/api/methods/send_video_note.py index 05f55696..f3b75574 100644 --- a/aiogram/api/methods/send_video_note.py +++ b/aiogram/api/methods/send_video_note.py @@ -1,4 +1,6 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( ForceReply, @@ -10,6 +12,9 @@ from ..types import ( ) from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendVideoNote(TelegramMethod[Message]): """ @@ -35,8 +40,8 @@ class SendVideoNote(TelegramMethod[Message]): thumb: Optional[Union[InputFile, str]] = None """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 320. Ignored if the file is not uploaded - using multipart/form-data. Thumbnails can’t be reused and can be only uploaded as a new + A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded + using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under .""" disable_notification: Optional[bool] = None @@ -49,7 +54,7 @@ class SendVideoNote(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"video_note", "thumb"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/send_voice.py b/aiogram/api/methods/send_voice.py index 96e2a159..2a464439 100644 --- a/aiogram/api/methods/send_voice.py +++ b/aiogram/api/methods/send_voice.py @@ -1,6 +1,9 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import ( + UNSET, ForceReply, InlineKeyboardMarkup, InputFile, @@ -10,6 +13,9 @@ from ..types import ( ) from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SendVoice(TelegramMethod[Message]): """ @@ -33,7 +39,7 @@ class SendVoice(TelegramMethod[Message]): Internet, or upload a new one using multipart/form-data.""" caption: Optional[str] = None """Voice message caption, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the voice message caption. See formatting options for more details.""" duration: Optional[int] = None @@ -48,7 +54,7 @@ class SendVoice(TelegramMethod[Message]): """Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"voice"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/set_chat_administrator_custom_title.py b/aiogram/api/methods/set_chat_administrator_custom_title.py index a153212a..e4929c51 100644 --- a/aiogram/api/methods/set_chat_administrator_custom_title.py +++ b/aiogram/api/methods/set_chat_administrator_custom_title.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetChatAdministratorCustomTitle(TelegramMethod[bool]): """ @@ -21,7 +26,7 @@ class SetChatAdministratorCustomTitle(TelegramMethod[bool]): custom_title: str """New custom title for the administrator; 0-16 characters, emoji are not allowed""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="setChatAdministratorCustomTitle", data=data) diff --git a/aiogram/api/methods/set_chat_description.py b/aiogram/api/methods/set_chat_description.py index 38c35138..2a7ac937 100644 --- a/aiogram/api/methods/set_chat_description.py +++ b/aiogram/api/methods/set_chat_description.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetChatDescription(TelegramMethod[bool]): """ @@ -20,7 +25,7 @@ class SetChatDescription(TelegramMethod[bool]): description: Optional[str] = None """New chat description, 0-255 characters""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="setChatDescription", data=data) diff --git a/aiogram/api/methods/set_chat_permissions.py b/aiogram/api/methods/set_chat_permissions.py index 5e415340..f4739c5c 100644 --- a/aiogram/api/methods/set_chat_permissions.py +++ b/aiogram/api/methods/set_chat_permissions.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from ..types import ChatPermissions from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetChatPermissions(TelegramMethod[bool]): """ @@ -21,7 +26,7 @@ class SetChatPermissions(TelegramMethod[bool]): permissions: ChatPermissions """New default chat permissions""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="setChatPermissions", data=data) diff --git a/aiogram/api/methods/set_chat_photo.py b/aiogram/api/methods/set_chat_photo.py index 4767edb0..94e8cf55 100644 --- a/aiogram/api/methods/set_chat_photo.py +++ b/aiogram/api/methods/set_chat_photo.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from ..types import InputFile from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetChatPhoto(TelegramMethod[bool]): """ @@ -21,7 +26,7 @@ class SetChatPhoto(TelegramMethod[bool]): photo: InputFile """New chat photo, uploaded using multipart/form-data""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"photo"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/set_chat_sticker_set.py b/aiogram/api/methods/set_chat_sticker_set.py index b7dc0ed0..7f37c7ff 100644 --- a/aiogram/api/methods/set_chat_sticker_set.py +++ b/aiogram/api/methods/set_chat_sticker_set.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetChatStickerSet(TelegramMethod[bool]): """ @@ -21,7 +26,7 @@ class SetChatStickerSet(TelegramMethod[bool]): sticker_set_name: str """Name of the sticker set to be set as the group sticker set""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="setChatStickerSet", data=data) diff --git a/aiogram/api/methods/set_chat_title.py b/aiogram/api/methods/set_chat_title.py index 8337a1a1..4b3bd8b4 100644 --- a/aiogram/api/methods/set_chat_title.py +++ b/aiogram/api/methods/set_chat_title.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetChatTitle(TelegramMethod[bool]): """ @@ -20,7 +25,7 @@ class SetChatTitle(TelegramMethod[bool]): title: str """New chat title, 1-255 characters""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="setChatTitle", data=data) diff --git a/aiogram/api/methods/set_game_score.py b/aiogram/api/methods/set_game_score.py index 5e5dc397..41d3f054 100644 --- a/aiogram/api/methods/set_game_score.py +++ b/aiogram/api/methods/set_game_score.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import Message from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetGameScore(TelegramMethod[Union[Message, bool]]): """ @@ -32,7 +37,7 @@ class SetGameScore(TelegramMethod[Union[Message, bool]]): inline_message_id: Optional[str] = None """Required if chat_id and message_id are not specified. Identifier of the inline message""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="setGameScore", data=data) diff --git a/aiogram/api/methods/set_my_commands.py b/aiogram/api/methods/set_my_commands.py index 97ac9bcb..f7fd7b9a 100644 --- a/aiogram/api/methods/set_my_commands.py +++ b/aiogram/api/methods/set_my_commands.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, List +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List from ..types import BotCommand from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetMyCommands(TelegramMethod[bool]): """ @@ -17,7 +22,7 @@ class SetMyCommands(TelegramMethod[bool]): """A JSON-serialized list of bot commands to be set as the list of the bot's commands. At most 100 commands can be specified.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="setMyCommands", data=data) diff --git a/aiogram/api/methods/set_passport_data_errors.py b/aiogram/api/methods/set_passport_data_errors.py index 44753546..280187a9 100644 --- a/aiogram/api/methods/set_passport_data_errors.py +++ b/aiogram/api/methods/set_passport_data_errors.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, List +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List from ..types import PassportElementError from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetPassportDataErrors(TelegramMethod[bool]): """ @@ -24,7 +29,7 @@ class SetPassportDataErrors(TelegramMethod[bool]): errors: List[PassportElementError] """A JSON-serialized array describing the errors""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="setPassportDataErrors", data=data) diff --git a/aiogram/api/methods/set_sticker_position_in_set.py b/aiogram/api/methods/set_sticker_position_in_set.py index 378a39d3..54c27bfd 100644 --- a/aiogram/api/methods/set_sticker_position_in_set.py +++ b/aiogram/api/methods/set_sticker_position_in_set.py @@ -1,7 +1,12 @@ -from typing import Any, Dict +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetStickerPositionInSet(TelegramMethod[bool]): """ @@ -18,7 +23,7 @@ class SetStickerPositionInSet(TelegramMethod[bool]): position: int """New sticker position in the set, zero-based""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="setStickerPositionInSet", data=data) diff --git a/aiogram/api/methods/set_sticker_set_thumb.py b/aiogram/api/methods/set_sticker_set_thumb.py index 5ccd3bf3..67d3e50e 100644 --- a/aiogram/api/methods/set_sticker_set_thumb.py +++ b/aiogram/api/methods/set_sticker_set_thumb.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import InputFile from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetStickerSetThumb(TelegramMethod[bool]): """ @@ -27,7 +32,7 @@ class SetStickerSetThumb(TelegramMethod[bool]): Internet, or upload a new one using multipart/form-data.. Animated sticker set thumbnail can't be uploaded via HTTP URL.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"thumb"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/set_webhook.py b/aiogram/api/methods/set_webhook.py index 905385ad..49c7617d 100644 --- a/aiogram/api/methods/set_webhook.py +++ b/aiogram/api/methods/set_webhook.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List, Optional from ..types import InputFile from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class SetWebhook(TelegramMethod[bool]): """ @@ -12,7 +17,7 @@ class SetWebhook(TelegramMethod[bool]): after a reasonable amount of attempts. Returns True on success. If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a secret path in the URL, e.g. https://www.example.com/. Since nobody else knows your - bot‘s token, you can be pretty sure it’s us. + bot's token, you can be pretty sure it's us. Notes 1. You will not be able to receive updates using getUpdates for as long as an outgoing webhook is set up. @@ -34,8 +39,8 @@ class SetWebhook(TelegramMethod[bool]): our self-signed guide for details.""" max_connections: Optional[int] = None """Maximum allowed number of simultaneous HTTPS connections to the webhook for update - delivery, 1-100. Defaults to 40. Use lower values to limit the load on your bot‘s server, - and higher values to increase your bot’s throughput.""" + delivery, 1-100. Defaults to 40. Use lower values to limit the load on your bot's server, + and higher values to increase your bot's throughput.""" allowed_updates: Optional[List[str]] = None """A JSON-serialized list of the update types you want your bot to receive. For example, specify ['message', 'edited_channel_post', 'callback_query'] to only receive updates of @@ -43,7 +48,7 @@ class SetWebhook(TelegramMethod[bool]): list to receive all updates regardless of type (default). If not specified, the previous setting will be used.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"certificate"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/methods/stop_message_live_location.py b/aiogram/api/methods/stop_message_live_location.py index f4f17910..a7677163 100644 --- a/aiogram/api/methods/stop_message_live_location.py +++ b/aiogram/api/methods/stop_message_live_location.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import InlineKeyboardMarkup, Message from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class StopMessageLiveLocation(TelegramMethod[Union[Message, bool]]): """ @@ -26,7 +31,7 @@ class StopMessageLiveLocation(TelegramMethod[Union[Message, bool]]): reply_markup: Optional[InlineKeyboardMarkup] = None """A JSON-serialized object for a new inline keyboard.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="stopMessageLiveLocation", data=data) diff --git a/aiogram/api/methods/stop_poll.py b/aiogram/api/methods/stop_poll.py index d6be55b9..81e8ef26 100644 --- a/aiogram/api/methods/stop_poll.py +++ b/aiogram/api/methods/stop_poll.py @@ -1,8 +1,13 @@ -from typing import Any, Dict, Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import InlineKeyboardMarkup, Poll from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class StopPoll(TelegramMethod[Poll]): """ @@ -22,7 +27,7 @@ class StopPoll(TelegramMethod[Poll]): reply_markup: Optional[InlineKeyboardMarkup] = None """A JSON-serialized object for a new message inline keyboard.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="stopPoll", data=data) diff --git a/aiogram/api/methods/unban_chat_member.py b/aiogram/api/methods/unban_chat_member.py index 09e6da6f..56a30bd6 100644 --- a/aiogram/api/methods/unban_chat_member.py +++ b/aiogram/api/methods/unban_chat_member.py @@ -1,7 +1,12 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class UnbanChatMember(TelegramMethod[bool]): """ @@ -20,7 +25,7 @@ class UnbanChatMember(TelegramMethod[bool]): user_id: int """Unique identifier of the target user""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="unbanChatMember", data=data) diff --git a/aiogram/api/methods/unpin_chat_message.py b/aiogram/api/methods/unpin_chat_message.py index 20cee142..419a7edd 100644 --- a/aiogram/api/methods/unpin_chat_message.py +++ b/aiogram/api/methods/unpin_chat_message.py @@ -1,13 +1,18 @@ -from typing import Any, Dict, Union +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union from .base import Request, TelegramMethod +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class UnpinChatMessage(TelegramMethod[bool]): """ Use this method to unpin a message in a group, a supergroup, or a channel. The bot must be an - administrator in the chat for this to work and must have the ‘can_pin_messages’ admin right in - the supergroup or ‘can_edit_messages’ admin right in the channel. Returns True on success. + administrator in the chat for this to work and must have the 'can_pin_messages' admin right in + the supergroup or 'can_edit_messages' admin right in the channel. Returns True on success. Source: https://core.telegram.org/bots/api#unpinchatmessage """ @@ -18,7 +23,7 @@ class UnpinChatMessage(TelegramMethod[bool]): """Unique identifier for the target chat or username of the target channel (in the format @channelusername)""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() return Request(method="unpinChatMessage", data=data) diff --git a/aiogram/api/methods/upload_sticker_file.py b/aiogram/api/methods/upload_sticker_file.py index 7e806754..a5f553e3 100644 --- a/aiogram/api/methods/upload_sticker_file.py +++ b/aiogram/api/methods/upload_sticker_file.py @@ -1,8 +1,13 @@ -from typing import Any, Dict +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict from ..types import File, InputFile from .base import Request, TelegramMethod, prepare_file +if TYPE_CHECKING: # pragma: no cover + from ..client.bot import Bot + class UploadStickerFile(TelegramMethod[File]): """ @@ -20,7 +25,7 @@ class UploadStickerFile(TelegramMethod[File]): """PNG image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px.""" - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"png_sticker"}) files: Dict[str, InputFile] = {} diff --git a/aiogram/api/types/__init__.py b/aiogram/api/types/__init__.py index e5020458..3dbb0439 100644 --- a/aiogram/api/types/__init__.py +++ b/aiogram/api/types/__init__.py @@ -1,6 +1,6 @@ from .animation import Animation from .audio import Audio -from .base import TelegramObject +from .base import UNSET, TelegramObject from .bot_command import BotCommand from .callback_game import CallbackGame from .callback_query import CallbackQuery @@ -103,6 +103,7 @@ from .webhook_info import WebhookInfo __all__ = ( "TelegramObject", "Downloadable", + "UNSET", "BufferedInputFile", "FSInputFile", "URLInputFile", diff --git a/aiogram/api/types/base.py b/aiogram/api/types/base.py index 8c098202..99db1a01 100644 --- a/aiogram/api/types/base.py +++ b/aiogram/api/types/base.py @@ -1,4 +1,6 @@ import datetime +from typing import Any +from unittest.mock import sentinel from pydantic import BaseModel, Extra @@ -19,3 +21,6 @@ class TelegramObject(ContextInstanceMixin["TelegramObject"], BaseModel): class MutableTelegramObject(TelegramObject): class Config: allow_mutation = True + + +UNSET: Any = sentinel.UNSET # special sentinel object which used in sutuation when None might be a useful value diff --git a/aiogram/api/types/chat_member.py b/aiogram/api/types/chat_member.py index 58aa7630..dca3029e 100644 --- a/aiogram/api/types/chat_member.py +++ b/aiogram/api/types/chat_member.py @@ -3,6 +3,7 @@ from __future__ import annotations import datetime from typing import TYPE_CHECKING, Optional, Union +from ...utils import helper from .base import TelegramObject if TYPE_CHECKING: # pragma: no cover @@ -65,3 +66,26 @@ class ChatMember(TelegramObject): inline bots""" can_add_web_page_previews: Optional[bool] = None """Restricted only. True, if the user is allowed to add web page previews to their messages""" + + @property + def is_chat_admin(self) -> bool: + return self.status in {ChatMemberStatus.CREATOR, ChatMemberStatus.ADMINISTRATOR} + + @property + def is_chat_member(self) -> bool: + return self.status not in {ChatMemberStatus.LEFT, ChatMemberStatus.KICKED} + + +class ChatMemberStatus(helper.Helper): + """ + Chat member status + """ + + mode = helper.HelperMode.lowercase + + CREATOR = helper.Item() # creator + ADMINISTRATOR = helper.Item() # administrator + MEMBER = helper.Item() # member + RESTRICTED = helper.Item() # restricted + LEFT = helper.Item() # left + KICKED = helper.Item() # kicked diff --git a/aiogram/api/types/dice.py b/aiogram/api/types/dice.py index a85d54ac..d9b1da10 100644 --- a/aiogram/api/types/dice.py +++ b/aiogram/api/types/dice.py @@ -5,9 +5,7 @@ from .base import TelegramObject class Dice(TelegramObject): """ - This object represents a dice with a random value from 1 to 6 for currently supported base - emoji. (Yes, we're aware of the 'proper' singular of die. But it's awkward, and we decided to - help it change. One dice at a time!) + This object represents an animated emoji that displays a random value. Source: https://core.telegram.org/bots/api#dice """ @@ -15,7 +13,7 @@ class Dice(TelegramObject): emoji: str """Emoji on which the dice throw animation is based""" value: int - """Value of the dice, 1-6 for currently supported base emoji""" + """Value of the dice, 1-6 for '' and '' base emoji, 1-5 for '' base emoji""" class DiceEmoji: diff --git a/aiogram/api/types/force_reply.py b/aiogram/api/types/force_reply.py index 8ef4a1bd..8cbc1653 100644 --- a/aiogram/api/types/force_reply.py +++ b/aiogram/api/types/force_reply.py @@ -8,7 +8,7 @@ from .base import MutableTelegramObject class ForceReply(MutableTelegramObject): """ Upon receiving a message with this object, Telegram clients will display a reply interface to - the user (act as if the user has selected the bot‘s message and tapped ’Reply'). This can be + the user (act as if the user has selected the bot's message and tapped 'Reply'). This can be extremely useful if you want to create user-friendly step-by-step interfaces without having to sacrifice privacy mode. Example: A poll bot for groups runs in privacy mode (only receives commands, replies to its @@ -16,19 +16,19 @@ class ForceReply(MutableTelegramObject): Explain the user how to send a command with parameters (e.g. /newpoll question answer1 answer2). May be appealing for hardcore users but lacks modern day polish. - Guide the user through a step-by-step process. ‘Please send me your question’, ‘Cool, now - let’s add the first answer option‘, ’Great. Keep adding answer options, then send /done when - you‘re ready’. - The last option is definitely more attractive. And if you use ForceReply in your bot‘s - questions, it will receive the user’s answers even if it only receives replies, commands and + Guide the user through a step-by-step process. 'Please send me your question', 'Cool, now + let's add the first answer option', 'Great. Keep adding answer options, then send /done when + you're ready'. + The last option is definitely more attractive. And if you use ForceReply in your bot's + questions, it will receive the user's answers even if it only receives replies, commands and mentions — without any extra work for the user. Source: https://core.telegram.org/bots/api#forcereply """ force_reply: bool - """Shows reply interface to the user, as if they manually selected the bot‘s message and - tapped ’Reply'""" + """Shows reply interface to the user, as if they manually selected the bot's message and + tapped 'Reply'""" selective: Optional[bool] = None """Use this parameter if you want to force reply from specific users only. Targets: 1) users that are @mentioned in the text of the Message object; 2) if the bot's message is a reply diff --git a/aiogram/api/types/game_high_score.py b/aiogram/api/types/game_high_score.py index af8a1f3a..7bc35de6 100644 --- a/aiogram/api/types/game_high_score.py +++ b/aiogram/api/types/game_high_score.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: # pragma: no cover class GameHighScore(TelegramObject): """ This object represents one row of the high scores table for a game. - And that‘s about all we’ve got for now. + And that's about all we've got for now. If you've got any questions, please check out our Bot FAQ Source: https://core.telegram.org/bots/api#gamehighscore diff --git a/aiogram/api/types/inline_keyboard_button.py b/aiogram/api/types/inline_keyboard_button.py index deb1f3c2..33ade442 100644 --- a/aiogram/api/types/inline_keyboard_button.py +++ b/aiogram/api/types/inline_keyboard_button.py @@ -28,11 +28,11 @@ class InlineKeyboardButton(MutableTelegramObject): """Data to be sent in a callback query to the bot when button is pressed, 1-64 bytes""" switch_inline_query: Optional[str] = None """If set, pressing the button will prompt the user to select one of their chats, open that - chat and insert the bot‘s username and the specified inline query in the input field. Can - be empty, in which case just the bot’s username will be inserted.""" + chat and insert the bot's username and the specified inline query in the input field. Can + be empty, in which case just the bot's username will be inserted.""" switch_inline_query_current_chat: Optional[str] = None - """If set, pressing the button will insert the bot‘s username and the specified inline query - in the current chat’s input field. Can be empty, in which case only the bot's username will + """If set, pressing the button will insert the bot's username and the specified inline query + in the current chat's input field. Can be empty, in which case only the bot's username will be inserted.""" callback_game: Optional[CallbackGame] = None """Description of the game that will be launched when the user presses the button.""" diff --git a/aiogram/api/types/inline_query_result_audio.py b/aiogram/api/types/inline_query_result_audio.py index dd4ed624..b236f26d 100644 --- a/aiogram/api/types/inline_query_result_audio.py +++ b/aiogram/api/types/inline_query_result_audio.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -32,7 +33,7 @@ class InlineQueryResultAudio(InlineQueryResult): """Title""" caption: Optional[str] = None """Caption, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the audio caption. See formatting options for more details.""" performer: Optional[str] = None """Performer""" diff --git a/aiogram/api/types/inline_query_result_cached_audio.py b/aiogram/api/types/inline_query_result_cached_audio.py index b1b32880..b26edfed 100644 --- a/aiogram/api/types/inline_query_result_cached_audio.py +++ b/aiogram/api/types/inline_query_result_cached_audio.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -30,7 +31,7 @@ class InlineQueryResultCachedAudio(InlineQueryResult): """A valid file identifier for the audio file""" caption: Optional[str] = None """Caption, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the audio caption. See formatting options for more details.""" reply_markup: Optional[InlineKeyboardMarkup] = None """Inline keyboard attached to the message""" diff --git a/aiogram/api/types/inline_query_result_cached_document.py b/aiogram/api/types/inline_query_result_cached_document.py index 1f377fb7..74fa2ecf 100644 --- a/aiogram/api/types/inline_query_result_cached_document.py +++ b/aiogram/api/types/inline_query_result_cached_document.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -34,7 +35,7 @@ class InlineQueryResultCachedDocument(InlineQueryResult): """Short description of the result""" caption: Optional[str] = None """Caption of the document to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the document caption. See formatting options for more details.""" reply_markup: Optional[InlineKeyboardMarkup] = None """Inline keyboard attached to the message""" diff --git a/aiogram/api/types/inline_query_result_cached_gif.py b/aiogram/api/types/inline_query_result_cached_gif.py index 228da905..cb66af96 100644 --- a/aiogram/api/types/inline_query_result_cached_gif.py +++ b/aiogram/api/types/inline_query_result_cached_gif.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -30,7 +31,7 @@ class InlineQueryResultCachedGif(InlineQueryResult): """Title for the result""" caption: Optional[str] = None """Caption of the GIF file to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the caption. See formatting options for more details.""" reply_markup: Optional[InlineKeyboardMarkup] = None """Inline keyboard attached to the message""" diff --git a/aiogram/api/types/inline_query_result_cached_mpeg4_gif.py b/aiogram/api/types/inline_query_result_cached_mpeg4_gif.py index 73e9ad2b..a43de25f 100644 --- a/aiogram/api/types/inline_query_result_cached_mpeg4_gif.py +++ b/aiogram/api/types/inline_query_result_cached_mpeg4_gif.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -31,7 +32,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): """Title for the result""" caption: Optional[str] = None """Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the caption. See formatting options for more details.""" reply_markup: Optional[InlineKeyboardMarkup] = None """Inline keyboard attached to the message""" diff --git a/aiogram/api/types/inline_query_result_cached_photo.py b/aiogram/api/types/inline_query_result_cached_photo.py index be425a5c..b3a9f8a1 100644 --- a/aiogram/api/types/inline_query_result_cached_photo.py +++ b/aiogram/api/types/inline_query_result_cached_photo.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -32,7 +33,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): """Short description of the result""" caption: Optional[str] = None """Caption of the photo to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the photo caption. See formatting options for more details.""" reply_markup: Optional[InlineKeyboardMarkup] = None """Inline keyboard attached to the message""" diff --git a/aiogram/api/types/inline_query_result_cached_video.py b/aiogram/api/types/inline_query_result_cached_video.py index 497283e4..ef561bb4 100644 --- a/aiogram/api/types/inline_query_result_cached_video.py +++ b/aiogram/api/types/inline_query_result_cached_video.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -32,7 +33,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult): """Short description of the result""" caption: Optional[str] = None """Caption of the video to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the video caption. See formatting options for more details.""" reply_markup: Optional[InlineKeyboardMarkup] = None """Inline keyboard attached to the message""" diff --git a/aiogram/api/types/inline_query_result_cached_voice.py b/aiogram/api/types/inline_query_result_cached_voice.py index c11ecef0..aec92d44 100644 --- a/aiogram/api/types/inline_query_result_cached_voice.py +++ b/aiogram/api/types/inline_query_result_cached_voice.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -32,7 +33,7 @@ class InlineQueryResultCachedVoice(InlineQueryResult): """Voice message title""" caption: Optional[str] = None """Caption, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the voice message caption. See formatting options for more details.""" reply_markup: Optional[InlineKeyboardMarkup] = None diff --git a/aiogram/api/types/inline_query_result_document.py b/aiogram/api/types/inline_query_result_document.py index 207345de..01e389ff 100644 --- a/aiogram/api/types/inline_query_result_document.py +++ b/aiogram/api/types/inline_query_result_document.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -35,7 +36,7 @@ class InlineQueryResultDocument(InlineQueryResult): """Mime type of the content of the file, either 'application/pdf' or 'application/zip'""" caption: Optional[str] = None """Caption of the document to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the document caption. See formatting options for more details.""" description: Optional[str] = None """Short description of the result""" diff --git a/aiogram/api/types/inline_query_result_gif.py b/aiogram/api/types/inline_query_result_gif.py index e428225b..8812c43a 100644 --- a/aiogram/api/types/inline_query_result_gif.py +++ b/aiogram/api/types/inline_query_result_gif.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -27,18 +28,21 @@ class InlineQueryResultGif(InlineQueryResult): gif_url: str """A valid URL for the GIF file. File size must not exceed 1MB""" thumb_url: str - """URL of the static thumbnail for the result (jpeg or gif)""" + """URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result""" gif_width: Optional[int] = None """Width of the GIF""" gif_height: Optional[int] = None """Height of the GIF""" gif_duration: Optional[int] = None """Duration of the GIF""" + thumb_mime_type: Optional[str] = None + """MIME type of the thumbnail, must be one of 'image/jpeg', 'image/gif', or 'video/mp4'. + Defaults to 'image/jpeg'""" title: Optional[str] = None """Title for the result""" caption: Optional[str] = None """Caption of the GIF file to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the caption. See formatting options for more details.""" reply_markup: Optional[InlineKeyboardMarkup] = None """Inline keyboard attached to the message""" diff --git a/aiogram/api/types/inline_query_result_mpeg4_gif.py b/aiogram/api/types/inline_query_result_mpeg4_gif.py index 139dbb30..76f97e0a 100644 --- a/aiogram/api/types/inline_query_result_mpeg4_gif.py +++ b/aiogram/api/types/inline_query_result_mpeg4_gif.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -28,18 +29,21 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult): mpeg4_url: str """A valid URL for the MP4 file. File size must not exceed 1MB""" thumb_url: str - """URL of the static thumbnail (jpeg or gif) for the result""" + """URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result""" mpeg4_width: Optional[int] = None """Video width""" mpeg4_height: Optional[int] = None """Video height""" mpeg4_duration: Optional[int] = None """Video duration""" + thumb_mime_type: Optional[str] = None + """MIME type of the thumbnail, must be one of 'image/jpeg', 'image/gif', or 'video/mp4'. + Defaults to 'image/jpeg'""" title: Optional[str] = None """Title for the result""" caption: Optional[str] = None """Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the caption. See formatting options for more details.""" reply_markup: Optional[InlineKeyboardMarkup] = None """Inline keyboard attached to the message""" diff --git a/aiogram/api/types/inline_query_result_photo.py b/aiogram/api/types/inline_query_result_photo.py index e44b63c8..31cddee2 100644 --- a/aiogram/api/types/inline_query_result_photo.py +++ b/aiogram/api/types/inline_query_result_photo.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -38,7 +39,7 @@ class InlineQueryResultPhoto(InlineQueryResult): """Short description of the result""" caption: Optional[str] = None """Caption of the photo to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the photo caption. See formatting options for more details.""" reply_markup: Optional[InlineKeyboardMarkup] = None """Inline keyboard attached to the message""" diff --git a/aiogram/api/types/inline_query_result_video.py b/aiogram/api/types/inline_query_result_video.py index a025fc4e..3002b8be 100644 --- a/aiogram/api/types/inline_query_result_video.py +++ b/aiogram/api/types/inline_query_result_video.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -36,7 +37,7 @@ class InlineQueryResultVideo(InlineQueryResult): """Title for the result""" caption: Optional[str] = None """Caption of the video to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the video caption. See formatting options for more details.""" video_width: Optional[int] = None """Video width""" diff --git a/aiogram/api/types/inline_query_result_voice.py b/aiogram/api/types/inline_query_result_voice.py index 161eafe8..724d46bc 100644 --- a/aiogram/api/types/inline_query_result_voice.py +++ b/aiogram/api/types/inline_query_result_voice.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional from pydantic import Field +from .base import UNSET from .inline_query_result import InlineQueryResult if TYPE_CHECKING: # pragma: no cover @@ -33,7 +34,7 @@ class InlineQueryResultVoice(InlineQueryResult): """Recording title""" caption: Optional[str] = None """Caption, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the voice message caption. See formatting options for more details.""" voice_duration: Optional[int] = None diff --git a/aiogram/api/types/input_media_animation.py b/aiogram/api/types/input_media_animation.py index 17523135..d30084a2 100644 --- a/aiogram/api/types/input_media_animation.py +++ b/aiogram/api/types/input_media_animation.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional, Union from pydantic import Field +from .base import UNSET from .input_media import InputMedia if TYPE_CHECKING: # pragma: no cover @@ -27,13 +28,13 @@ class InputMediaAnimation(InputMedia): thumb: Optional[Union[InputFile, str]] = None """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 320. Ignored if the file is not uploaded - using multipart/form-data. Thumbnails can’t be reused and can be only uploaded as a new + A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded + using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under .""" caption: Optional[str] = None """Caption of the animation to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the animation caption. See formatting options for more details.""" width: Optional[int] = None diff --git a/aiogram/api/types/input_media_audio.py b/aiogram/api/types/input_media_audio.py index 37162374..098087d1 100644 --- a/aiogram/api/types/input_media_audio.py +++ b/aiogram/api/types/input_media_audio.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional, Union from pydantic import Field +from .base import UNSET from .input_media import InputMedia if TYPE_CHECKING: # pragma: no cover @@ -27,13 +28,13 @@ class InputMediaAudio(InputMedia): thumb: Optional[Union[InputFile, str]] = None """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 320. Ignored if the file is not uploaded - using multipart/form-data. Thumbnails can’t be reused and can be only uploaded as a new + A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded + using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under .""" caption: Optional[str] = None """Caption of the audio to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the audio caption. See formatting options for more details.""" duration: Optional[int] = None """Duration of the audio in seconds""" diff --git a/aiogram/api/types/input_media_document.py b/aiogram/api/types/input_media_document.py index a3237906..e29a5014 100644 --- a/aiogram/api/types/input_media_document.py +++ b/aiogram/api/types/input_media_document.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional, Union from pydantic import Field +from .base import UNSET from .input_media import InputMedia if TYPE_CHECKING: # pragma: no cover @@ -27,11 +28,11 @@ class InputMediaDocument(InputMedia): thumb: Optional[Union[InputFile, str]] = None """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 320. Ignored if the file is not uploaded - using multipart/form-data. Thumbnails can’t be reused and can be only uploaded as a new + A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded + using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under .""" caption: Optional[str] = None """Caption of the document to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the document caption. See formatting options for more details.""" diff --git a/aiogram/api/types/input_media_photo.py b/aiogram/api/types/input_media_photo.py index 529a5bc9..6ef61006 100644 --- a/aiogram/api/types/input_media_photo.py +++ b/aiogram/api/types/input_media_photo.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional, Union from pydantic import Field +from .base import UNSET from .input_media import InputMedia if TYPE_CHECKING: # pragma: no cover @@ -26,5 +27,5 @@ class InputMediaPhoto(InputMedia): name.""" caption: Optional[str] = None """Caption of the photo to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the photo caption. See formatting options for more details.""" diff --git a/aiogram/api/types/input_media_video.py b/aiogram/api/types/input_media_video.py index 6af1b68c..d8dc0adf 100644 --- a/aiogram/api/types/input_media_video.py +++ b/aiogram/api/types/input_media_video.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional, Union from pydantic import Field +from .base import UNSET from .input_media import InputMedia if TYPE_CHECKING: # pragma: no cover @@ -27,13 +28,13 @@ class InputMediaVideo(InputMedia): thumb: Optional[Union[InputFile, str]] = None """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. - A thumbnail‘s width and height should not exceed 320. Ignored if the file is not uploaded - using multipart/form-data. Thumbnails can’t be reused and can be only uploaded as a new + A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded + using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under .""" caption: Optional[str] = None """Caption of the video to be sent, 0-1024 characters after entities parsing""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the video caption. See formatting options for more details.""" width: Optional[int] = None """Video width""" diff --git a/aiogram/api/types/input_text_message_content.py b/aiogram/api/types/input_text_message_content.py index 2a8257cd..389cb48e 100644 --- a/aiogram/api/types/input_text_message_content.py +++ b/aiogram/api/types/input_text_message_content.py @@ -2,6 +2,7 @@ from __future__ import annotations from typing import Optional +from .base import UNSET from .input_message_content import InputMessageContent @@ -14,7 +15,7 @@ class InputTextMessageContent(InputMessageContent): message_text: str """Text of the message to be sent, 1-4096 characters""" - parse_mode: Optional[str] = None + parse_mode: Optional[str] = UNSET """Mode for parsing entities in the message text. See formatting options for more details.""" disable_web_page_preview: Optional[bool] = None """Disables link previews for links in the sent message""" diff --git a/aiogram/api/types/message.py b/aiogram/api/types/message.py index 68b56209..67470b66 100644 --- a/aiogram/api/types/message.py +++ b/aiogram/api/types/message.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, List, Optional, Union from pydantic import Field from ...utils import helper -from .base import TelegramObject +from .base import UNSET, TelegramObject if TYPE_CHECKING: # pragma: no cover from .animation import Animation @@ -90,6 +90,8 @@ class Message(TelegramObject): reply_to_message: Optional[Message] = None """For replies, the original message. Note that the Message object in this field will not contain further reply_to_message fields even if it itself is a reply.""" + via_bot: Optional[User] = None + """Bot through which the message was sent""" edit_date: Optional[int] = None """Date the message was last edited in Unix time""" media_group_id: Optional[str] = None @@ -101,40 +103,41 @@ class Message(TelegramObject): entities: Optional[List[MessageEntity]] = None """For text messages, special entities like usernames, URLs, bot commands, etc. that appear in the text""" - caption_entities: Optional[List[MessageEntity]] = None - """For messages with a caption, special entities like usernames, URLs, bot commands, etc. that - appear in the caption""" + animation: Optional[Animation] = None + """Message is an animation, information about the animation. For backward compatibility, when + this field is set, the document field will also be set""" audio: Optional[Audio] = None """Message is an audio file, information about the file""" document: Optional[Document] = None """Message is a general file, information about the file""" - animation: Optional[Animation] = None - """Message is an animation, information about the animation. For backward compatibility, when - this field is set, the document field will also be set""" - game: Optional[Game] = None - """Message is a game, information about the game.""" photo: Optional[List[PhotoSize]] = None """Message is a photo, available sizes of the photo""" sticker: Optional[Sticker] = None """Message is a sticker, information about the sticker""" video: Optional[Video] = None """Message is a video, information about the video""" - voice: Optional[Voice] = None - """Message is a voice message, information about the file""" video_note: Optional[VideoNote] = None """Message is a video note, information about the video message""" + voice: Optional[Voice] = None + """Message is a voice message, information about the file""" caption: Optional[str] = None """Caption for the animation, audio, document, photo, video or voice, 0-1024 characters""" + caption_entities: Optional[List[MessageEntity]] = None + """For messages with a caption, special entities like usernames, URLs, bot commands, etc. that + appear in the caption""" contact: Optional[Contact] = None """Message is a shared contact, information about the contact""" - location: Optional[Location] = None - """Message is a shared location, information about the location""" - venue: Optional[Venue] = None - """Message is a venue, information about the venue""" - poll: Optional[Poll] = None - """Message is a native poll, information about the poll""" dice: Optional[Dice] = None """Message is a dice with random value from 1 to 6""" + game: Optional[Game] = None + """Message is a game, information about the game.""" + poll: Optional[Poll] = None + """Message is a native poll, information about the poll""" + venue: Optional[Venue] = None + """Message is a venue, information about the venue. For backward compatibility, when this + field is set, the location field will also be set""" + location: Optional[Location] = None + """Message is a shared location, information about the location""" new_chat_members: Optional[List[User]] = None """New members that were added to the group or supergroup and information about them (the bot itself may be one of these members)""" @@ -150,13 +153,13 @@ class Message(TelegramObject): group_chat_created: Optional[bool] = None """Service message: the group has been created""" supergroup_chat_created: Optional[bool] = None - """Service message: the supergroup has been created. This field can‘t be received in a message - coming through updates, because bot can’t be a member of a supergroup when it is created. + """Service message: the supergroup has been created. This field can't be received in a message + coming through updates, because bot can't be a member of a supergroup when it is created. It can only be found in reply_to_message if someone replies to a very first message in a directly created supergroup.""" channel_chat_created: Optional[bool] = None - """Service message: the channel has been created. This field can‘t be received in a message - coming through updates, because bot can’t be a member of a channel when it is created. It + """Service message: the channel has been created. This field can't be received in a message + coming through updates, because bot can't be a member of a channel when it is created. It can only be found in reply_to_message if someone replies to a very first message in a channel.""" migrate_to_chat_id: Optional[int] = None @@ -253,7 +256,7 @@ class Message(TelegramObject): height: Optional[int] = None, thumb: Optional[Union[InputFile, str]] = None, caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_notification: Optional[bool] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] @@ -297,7 +300,7 @@ class Message(TelegramObject): height: Optional[int] = None, thumb: Optional[Union[InputFile, str]] = None, caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_notification: Optional[bool] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] @@ -337,7 +340,7 @@ class Message(TelegramObject): self, audio: Union[InputFile, str], caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, duration: Optional[int] = None, performer: Optional[str] = None, title: Optional[str] = None, @@ -381,7 +384,7 @@ class Message(TelegramObject): self, audio: Union[InputFile, str], caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, duration: Optional[int] = None, performer: Optional[str] = None, title: Optional[str] = None, @@ -496,7 +499,7 @@ class Message(TelegramObject): document: Union[InputFile, str], thumb: Optional[Union[InputFile, str]] = None, caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_notification: Optional[bool] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] @@ -531,7 +534,7 @@ class Message(TelegramObject): document: Union[InputFile, str], thumb: Optional[Union[InputFile, str]] = None, caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_notification: Optional[bool] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] @@ -874,7 +877,7 @@ class Message(TelegramObject): def reply( self, text: str, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_web_page_preview: Optional[bool] = None, disable_notification: Optional[bool] = None, reply_markup: Optional[ @@ -906,7 +909,7 @@ class Message(TelegramObject): def answer( self, text: str, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_web_page_preview: Optional[bool] = None, disable_notification: Optional[bool] = None, reply_markup: Optional[ @@ -939,7 +942,7 @@ class Message(TelegramObject): self, photo: Union[InputFile, str], caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_notification: Optional[bool] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] @@ -971,7 +974,7 @@ class Message(TelegramObject): self, photo: Union[InputFile, str], caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, disable_notification: Optional[bool] = None, reply_markup: Optional[ Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply] @@ -1008,7 +1011,7 @@ class Message(TelegramObject): allows_multiple_answers: Optional[bool] = None, correct_option_id: Optional[int] = None, explanation: Optional[str] = None, - explanation_parse_mode: Optional[str] = None, + explanation_parse_mode: Optional[str] = UNSET, open_period: Optional[int] = None, close_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None, is_closed: Optional[bool] = None, @@ -1064,7 +1067,7 @@ class Message(TelegramObject): allows_multiple_answers: Optional[bool] = None, correct_option_id: Optional[int] = None, explanation: Optional[str] = None, - explanation_parse_mode: Optional[str] = None, + explanation_parse_mode: Optional[str] = UNSET, open_period: Optional[int] = None, close_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None, is_closed: Optional[bool] = None, @@ -1305,7 +1308,7 @@ class Message(TelegramObject): height: Optional[int] = None, thumb: Optional[Union[InputFile, str]] = None, caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, supports_streaming: Optional[bool] = None, disable_notification: Optional[bool] = None, reply_markup: Optional[ @@ -1352,7 +1355,7 @@ class Message(TelegramObject): height: Optional[int] = None, thumb: Optional[Union[InputFile, str]] = None, caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, supports_streaming: Optional[bool] = None, disable_notification: Optional[bool] = None, reply_markup: Optional[ @@ -1465,7 +1468,7 @@ class Message(TelegramObject): self, voice: Union[InputFile, str], caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, duration: Optional[int] = None, disable_notification: Optional[bool] = None, reply_markup: Optional[ @@ -1500,7 +1503,7 @@ class Message(TelegramObject): self, voice: Union[InputFile, str], caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, duration: Optional[int] = None, disable_notification: Optional[bool] = None, reply_markup: Optional[ diff --git a/aiogram/api/types/update.py b/aiogram/api/types/update.py index fa89aa74..84803baa 100644 --- a/aiogram/api/types/update.py +++ b/aiogram/api/types/update.py @@ -24,8 +24,8 @@ class Update(TelegramObject): """ update_id: int - """The update‘s unique identifier. Update identifiers start from a certain positive number and - increase sequentially. This ID becomes especially handy if you’re using Webhooks, since it + """The update's unique identifier. Update identifiers start from a certain positive number and + increase sequentially. This ID becomes especially handy if you're using Webhooks, since it allows you to ignore repeated updates or to restore the correct update sequence, should they get out of order. If there are no new updates for at least a week, then identifier of the next update will be chosen randomly instead of sequentially.""" diff --git a/aiogram/api/types/user.py b/aiogram/api/types/user.py index e3a9eee1..15c87c12 100644 --- a/aiogram/api/types/user.py +++ b/aiogram/api/types/user.py @@ -17,11 +17,11 @@ class User(TelegramObject): is_bot: bool """True, if this user is a bot""" first_name: str - """User‘s or bot’s first name""" + """User's or bot's first name""" last_name: Optional[str] = None - """User‘s or bot’s last name""" + """User's or bot's last name""" username: Optional[str] = None - """User‘s or bot’s username""" + """User's or bot's username""" language_code: Optional[str] = None """IETF language tag of the user's language""" can_join_groups: Optional[bool] = None diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 1c4b08aa..699f746d 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -219,7 +219,7 @@ class Dispatcher(Router): # TODO: handle exceptions response: Any = process_updates.result() if isinstance(response, TelegramMethod): - request = response.build_request() + request = response.build_request(bot=bot) return request.render_webhook_request() else: diff --git a/aiogram/utils/helper.py b/aiogram/utils/helper.py index e582a4f0..57f4e76e 100644 --- a/aiogram/utils/helper.py +++ b/aiogram/utils/helper.py @@ -14,7 +14,10 @@ Example: <<< ['barItem', 'bazItem', 'fooItem', 'lorem'] """ import inspect -from typing import Any, Callable, Iterable, List, Optional, Union, cast +from typing import Any, Callable, Generic, Iterable, List, Optional, TypeVar, Union, cast +from weakref import WeakKeyDictionary + +T = TypeVar("T") PROPS_KEYS_ATTR_NAME = "_props_keys" @@ -233,3 +236,56 @@ class OrderedHelper(Helper, metaclass=OrderedHelperMeta): else: result.append(value) return result + + +class Default(Generic[T]): + """ + Descriptor that holds default value getter + + Example: + >>> class MyClass: + ... att = Default("dflt") + ... + >>> my_instance = MyClass() + >>> my_instance.att = "not dflt" + >>> my_instance.att + 'not dflt' + >>> MyClass.att + 'dflt' + >>> del my_instance.att + >>> my_instance.att + 'dflt' + >>> + + Intended to be used as a class attribute and only internally. + """ + + __slots__ = "fget", "_descriptor_instances" + + def __init__( + self, default: Optional[T] = None, *, fget: Optional[Callable[[Any], T]] = None, + ) -> None: + self.fget = fget or (lambda _: cast(T, default)) + self._descriptor_instances = WeakKeyDictionary() # type: ignore + + def __get__(self, instance: Any, owner: Any) -> T: + if instance is None: + return self.fget(instance) + + return self._descriptor_instances.get(instance, self.fget(instance)) + + def __set__(self, instance: Any, value: T) -> None: + if instance is None or isinstance(instance, type): + raise AttributeError( + "Instance cannot be class or None. Setter must be called from a class." + ) + + self._descriptor_instances[instance] = value + + def __delete__(self, instance: Any) -> None: + if instance is None or isinstance(instance, type): + raise AttributeError( + "Instance cannot be class or None. Deleter must be called from a class." + ) + + self._descriptor_instances.pop(instance, None) diff --git a/docs/_api_version.md b/docs/_api_version.md index ef216a53..86a9588a 100644 --- a/docs/_api_version.md +++ b/docs/_api_version.md @@ -1 +1 @@ -4.8 +4.9 diff --git a/docs/_package_version.md b/docs/_package_version.md index 255dd065..78e56ffb 100644 --- a/docs/_package_version.md +++ b/docs/_package_version.md @@ -1 +1 @@ -3.0.0a4 +3.0.0a5 diff --git a/docs/api/types/chat_member.md b/docs/api/types/chat_member.md index 0d374b01..809f8296 100644 --- a/docs/api/types/chat_member.md +++ b/docs/api/types/chat_member.md @@ -30,12 +30,33 @@ This object contains information about one member of a chat. | `can_add_web_page_previews` | `#!python Optional[bool]` | Optional. Restricted only. True, if the user is allowed to add web page previews to their messages | +## Extensions + +| Name | Type | Description | +| - | - | - | +| `is_chat_admin` | `#!python bool` | True if the user is administrator or creator of the chat | +| `is_chat_member` | `#!python bool` | True if the user is member of the chat | + + +## ChatMemberStatus helper + +This object helps to describe user's status. + +#### Attributes: +- CREATOR +- ADMINISTRATOR +- MEMBER +- RESTRICTED +- LEFT +- KICKED + ## Location - `from aiogram.types import ChatMember` - `from aiogram.api.types import ChatMember` - `from aiogram.api.types.chat_member import ChatMember` +- `from aiogram.api.types.chat_member import ChatMemberStatus` ## Related pages: diff --git a/poetry.lock b/poetry.lock index 9b328339..d81c8b2f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -130,7 +130,7 @@ description = "Specifications for callback functions passed in to an API" name = "backcall" optional = false python-versions = "*" -version = "0.1.0" +version = "0.2.0" [[package]] category = "dev" @@ -191,7 +191,7 @@ description = "Python package for providing Mozilla's CA Bundle." name = "certifi" optional = false python-versions = "*" -version = "2020.4.5.1" +version = "2020.4.5.2" [[package]] category = "dev" @@ -322,7 +322,7 @@ description = "the modular source code checker: pep8 pyflakes and co" name = "flake8" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "3.8.2" +version = "3.8.3" [package.dependencies] mccabe = ">=0.6.0,<0.7.0" @@ -380,7 +380,7 @@ description = "File identification library for Python" name = "identify" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "1.4.17" +version = "1.4.19" [package.extras] license = ["editdistance"] @@ -414,14 +414,14 @@ description = "Read metadata from Python packages" name = "importlib-metadata" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.6.0" +version = "1.6.1" [package.dependencies] zipp = ">=0.5" [package.extras] docs = ["sphinx", "rst.linker"] -testing = ["packaging", "importlib-resources"] +testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] [[package]] category = "dev" @@ -429,7 +429,7 @@ description = "IPython: Productive Interactive Computing" name = "ipython" optional = false python-versions = ">=3.6" -version = "7.14.0" +version = "7.15.0" [package.dependencies] appnope = "*" @@ -445,7 +445,7 @@ setuptools = ">=18.5" traitlets = ">=4.2" [package.extras] -all = ["nose (>=0.10.1)", "Sphinx (>=1.3)", "testpath", "nbformat", "ipywidgets", "qtconsole", "numpy (>=1.14)", "notebook", "ipyparallel", "ipykernel", "pygments", "requests", "nbconvert"] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] doc = ["Sphinx (>=1.3)"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] @@ -576,11 +576,14 @@ description = "Python LiveReload is an awesome tool for web developers" name = "livereload" optional = false python-versions = "*" -version = "2.6.1" +version = "2.6.2" [package.dependencies] six = "*" -tornado = "*" + +[package.dependencies.tornado] +python = ">=2.8" +version = "*" [[package]] category = "dev" @@ -715,7 +718,7 @@ description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" optional = false python-versions = ">=3.5" -version = "8.3.0" +version = "8.4.0" [[package]] category = "dev" @@ -786,7 +789,7 @@ description = "Node.js virtual environment builder" name = "nodeenv" optional = false python-versions = "*" -version = "1.3.5" +version = "1.4.0" [[package]] category = "dev" @@ -879,7 +882,7 @@ description = "Python dependency management and packaging made easy." name = "poetry" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.0.5" +version = "1.0.9" [package.dependencies] cachy = ">=0.3.0,<0.4.0" @@ -914,7 +917,7 @@ description = "A framework for managing and maintaining multi-language pre-commi name = "pre-commit" optional = false python-versions = ">=3.6.1" -version = "2.4.0" +version = "2.5.1" [package.dependencies] cfgv = ">=2.0.0" @@ -1045,7 +1048,7 @@ description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" -version = "5.4.2" +version = "5.4.3" [package.dependencies] atomicwrites = ">=1.0" @@ -1085,11 +1088,11 @@ description = "Pytest plugin for measuring coverage." name = "pytest-cov" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.9.0" +version = "2.10.0" [package.dependencies] coverage = ">=4.4" -pytest = ">=3.6" +pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] @@ -1183,7 +1186,7 @@ description = "Alternative regular expression module, to replace re." name = "regex" optional = false python-versions = "*" -version = "2020.5.14" +version = "2020.6.8" [[package]] category = "dev" @@ -1274,7 +1277,7 @@ marker = "python_version > \"2.7\"" name = "tqdm" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.46.0" +version = "4.46.1" [package.extras] dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"] @@ -1339,7 +1342,7 @@ description = "Virtual Python Environment builder" name = "virtualenv" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "20.0.21" +version = "20.0.23" [package.dependencies] appdirs = ">=1.4.3,<2" @@ -1353,15 +1356,15 @@ version = ">=0.12,<2" [package.extras] docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] -testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout", "packaging (>=20.0)", "xonsh (>=0.9.16)"] +testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "flaky (>=3)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] [[package]] category = "dev" -description = "Measures number of Terminal column cells of wide-character codes" +description = "Measures the displayed width of unicode strings in a terminal" name = "wcwidth" optional = false python-versions = "*" -version = "0.1.9" +version = "0.2.4" [[package]] category = "dev" @@ -1400,7 +1403,7 @@ fast = ["uvloop"] proxy = ["aiohttp-socks"] [metadata] -content-hash = "50478042294d42c7ac6eef8df3d2036b4dc42455ca60cdfb9434cd476e27ddfc" +content-hash = "152bb9b155a00baadd3c8b9fa21f08af719180bddccb8ad6c3dd6548c3e71e3e" python-versions = "^3.7" [metadata.files] @@ -1462,8 +1465,8 @@ babel = [ {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, ] backcall = [ - {file = "backcall-0.1.0.tar.gz", hash = "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4"}, - {file = "backcall-0.1.0.zip", hash = "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"}, + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] black = [ {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, @@ -1478,8 +1481,8 @@ cachy = [ {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, ] certifi = [ - {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, - {file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"}, + {file = "certifi-2020.4.5.2-py2.py3-none-any.whl", hash = "sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc"}, + {file = "certifi-2020.4.5.2.tar.gz", hash = "sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1"}, ] cffi = [ {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, @@ -1601,8 +1604,8 @@ filelock = [ {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, ] flake8 = [ - {file = "flake8-3.8.2-py2.py3-none-any.whl", hash = "sha256:ccaa799ef9893cebe69fdfefed76865aeaefbb94cb8545617b2298786a4de9a5"}, - {file = "flake8-3.8.2.tar.gz", hash = "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634"}, + {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, + {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, ] flake8-html = [ {file = "flake8-html-0.4.1.tar.gz", hash = "sha256:2fb436cbfe1e109275bc8fb7fdd0cb00e67b3b48cfeb397309b6b2c61eeb4cb4"}, @@ -1616,8 +1619,8 @@ html5lib = [ {file = "html5lib-1.0.1.tar.gz", hash = "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"}, ] identify = [ - {file = "identify-1.4.17-py2.py3-none-any.whl", hash = "sha256:ef6fa3d125c27516f8d1aaa2038c3263d741e8723825eb38350cdc0288ab35eb"}, - {file = "identify-1.4.17.tar.gz", hash = "sha256:be66b9673d59336acd18a3a0e0c10d35b8a780309561edf16c46b6b74b83f6af"}, + {file = "identify-1.4.19-py2.py3-none-any.whl", hash = "sha256:781fd3401f5d2b17b22a8b18b493a48d5d948e3330634e82742e23f9c20234ef"}, + {file = "identify-1.4.19.tar.gz", hash = "sha256:249ebc7e2066d6393d27c1b1be3b70433f824a120b1d8274d362f1eb419e3b52"}, ] idna = [ {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, @@ -1626,12 +1629,12 @@ idna = [ importlib-metadata = [ {file = "importlib_metadata-1.1.3-py2.py3-none-any.whl", hash = "sha256:7c7f8ac40673f507f349bef2eed21a0e5f01ddf5b2a7356a6c65eb2099b53764"}, {file = "importlib_metadata-1.1.3.tar.gz", hash = "sha256:7a99fb4084ffe6dae374961ba7a6521b79c1d07c658ab3a28aa264ee1d1b14e3"}, - {file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"}, - {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"}, + {file = "importlib_metadata-1.6.1-py2.py3-none-any.whl", hash = "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"}, + {file = "importlib_metadata-1.6.1.tar.gz", hash = "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545"}, ] ipython = [ - {file = "ipython-7.14.0-py3-none-any.whl", hash = "sha256:5b241b84bbf0eb085d43ae9d46adf38a13b45929ca7774a740990c2c242534bb"}, - {file = "ipython-7.14.0.tar.gz", hash = "sha256:f0126781d0f959da852fb3089e170ed807388e986a8dd4e6ac44855845b0fb1c"}, + {file = "ipython-7.15.0-py3-none-any.whl", hash = "sha256:1b85d65632211bf5d3e6f1406f3393c8c429a47d7b947b9a87812aa5bce6595c"}, + {file = "ipython-7.15.0.tar.gz", hash = "sha256:0ef1433879816a960cd3ae1ae1dc82c64732ca75cec8dab5a4e29783fb571d0e"}, ] ipython-genutils = [ {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, @@ -1666,8 +1669,7 @@ keyring = [ {file = "keyring-20.0.1.tar.gz", hash = "sha256:963bfa7f090269d30bdc5e25589e5fd9dad2cf2a7c6f176a7f2386910e5d0d8d"}, ] livereload = [ - {file = "livereload-2.6.1-py2.py3-none-any.whl", hash = "sha256:78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b"}, - {file = "livereload-2.6.1.tar.gz", hash = "sha256:89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"}, + {file = "livereload-2.6.2.tar.gz", hash = "sha256:d1eddcb5c5eb8d2ca1fa1f750e580da624c0f7fcb734aa5780dc81b7dcbd89be"}, ] lockfile = [ {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, @@ -1741,11 +1743,6 @@ 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 = [ @@ -1764,8 +1761,8 @@ mkdocs-material = [ {file = "mkdocs_material-4.6.3-py2.py3-none-any.whl", hash = "sha256:7f3afa0a09c07d0b89a6a9755fdb00513aee8f0cec3538bb903325c80f66f444"}, ] more-itertools = [ - {file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"}, - {file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"}, + {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, + {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"}, ] msgpack = [ {file = "msgpack-1.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:cec8bf10981ed70998d98431cd814db0ecf3384e6b113366e7f36af71a0fca08"}, @@ -1830,7 +1827,7 @@ nltk = [ {file = "nltk-3.5.zip", hash = "sha256:845365449cd8c5f9731f7cb9f8bd6fd0767553b9d53af9eb1b3abf7700936b35"}, ] nodeenv = [ - {file = "nodeenv-1.3.5-py2.py3-none-any.whl", hash = "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"}, + {file = "nodeenv-1.4.0-py2.py3-none-any.whl", hash = "sha256:4b0b77afa3ba9b54f4b6396e60b0c83f59eaeb2d63dc3cc7a70f7f4af96c82bc"}, ] packaging = [ {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, @@ -1865,12 +1862,12 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] poetry = [ - {file = "poetry-1.0.5-py2.py3-none-any.whl", hash = "sha256:81cdb3c708fe03cdef484ccd6c3becc74811dce2b15cb67444c2495b1240a278"}, - {file = "poetry-1.0.5.tar.gz", hash = "sha256:8e195ea8a4bce4f418a23fd828aa2f9ce06be7655720efd1d95beb0ee641030a"}, + {file = "poetry-1.0.9-py2.py3-none-any.whl", hash = "sha256:27e0c9c16f785156a8d8d0e98a73e239c8d2083306c180718825d11d5664aea0"}, + {file = "poetry-1.0.9.tar.gz", hash = "sha256:0a4c56983546964b47cbbe0e1b49fef5662277bbf0673e3e350e1215560377ab"}, ] pre-commit = [ - {file = "pre_commit-2.4.0-py2.py3-none-any.whl", hash = "sha256:5559e09afcac7808933951ffaf4ff9aac524f31efbc3f24d021540b6c579813c"}, - {file = "pre_commit-2.4.0.tar.gz", hash = "sha256:703e2e34cbe0eedb0d319eff9f7b83e2022bb5a3ab5289a6a8841441076514d0"}, + {file = "pre_commit-2.5.1-py2.py3-none-any.whl", hash = "sha256:c5c8fd4d0e1c363723aaf0a8f9cba0f434c160b48c4028f4bae6d219177945b3"}, + {file = "pre_commit-2.5.1.tar.gz", hash = "sha256:da463cf8f0e257f9af49047ba514f6b90dbd9b4f92f4c8847a3ccd36834874c7"}, ] prompt-toolkit = [ {file = "prompt_toolkit-3.0.5-py3-none-any.whl", hash = "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"}, @@ -1935,16 +1932,16 @@ pyrsistent = [ {file = "pyrsistent-0.14.11.tar.gz", hash = "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2"}, ] pytest = [ - {file = "pytest-5.4.2-py3-none-any.whl", hash = "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3"}, - {file = "pytest-5.4.2.tar.gz", hash = "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"}, + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] pytest-asyncio = [ {file = "pytest-asyncio-0.10.0.tar.gz", hash = "sha256:9fac5100fd716cbecf6ef89233e8590a4ad61d729d1732e0a96b84182df1daaf"}, {file = "pytest_asyncio-0.10.0-py3-none-any.whl", hash = "sha256:d734718e25cfc32d2bf78d346e99d33724deeba774cc4afdf491530c6184b63b"}, ] pytest-cov = [ - {file = "pytest-cov-2.9.0.tar.gz", hash = "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322"}, - {file = "pytest_cov-2.9.0-py2.py3-none-any.whl", hash = "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424"}, + {file = "pytest-cov-2.10.0.tar.gz", hash = "sha256:1a629dc9f48e53512fcbfda6b07de490c374b0c83c55ff7a1720b3fccff0ac87"}, + {file = "pytest_cov-2.10.0-py2.py3-none-any.whl", hash = "sha256:6e6d18092dce6fad667cd7020deed816f858ad3b49d5b5e2b1cc1c97a4dba65c"}, ] pytest-html = [ {file = "pytest-html-2.1.1.tar.gz", hash = "sha256:6a4ac391e105e391208e3eb9bd294a60dd336447fd8e1acddff3a6de7f4e57c5"}, @@ -1984,27 +1981,27 @@ pyyaml = [ {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] regex = [ - {file = "regex-2020.5.14-cp27-cp27m-win32.whl", hash = "sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e"}, - {file = "regex-2020.5.14-cp27-cp27m-win_amd64.whl", hash = "sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd"}, - {file = "regex-2020.5.14-cp36-cp36m-win32.whl", hash = "sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994"}, - {file = "regex-2020.5.14-cp36-cp36m-win_amd64.whl", hash = "sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f"}, - {file = "regex-2020.5.14-cp37-cp37m-win32.whl", hash = "sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929"}, - {file = "regex-2020.5.14-cp37-cp37m-win_amd64.whl", hash = "sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7"}, - {file = "regex-2020.5.14-cp38-cp38-win32.whl", hash = "sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927"}, - {file = "regex-2020.5.14-cp38-cp38-win_amd64.whl", hash = "sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108"}, - {file = "regex-2020.5.14.tar.gz", hash = "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5"}, + {file = "regex-2020.6.8-cp27-cp27m-win32.whl", hash = "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"}, + {file = "regex-2020.6.8-cp27-cp27m-win_amd64.whl", hash = "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:92d8a043a4241a710c1cf7593f5577fbb832cf6c3a00ff3fc1ff2052aff5dd89"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bae83f2a56ab30d5353b47f9b2a33e4aac4de9401fb582b55c42b132a8ac3868"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b2ba0f78b3ef375114856cbdaa30559914d081c416b431f2437f83ce4f8b7f2f"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:95fa7726d073c87141f7bbfb04c284901f8328e2d430eeb71b8ffdd5742a5ded"}, + {file = "regex-2020.6.8-cp36-cp36m-win32.whl", hash = "sha256:e3cdc9423808f7e1bb9c2e0bdb1c9dc37b0607b30d646ff6faf0d4e41ee8fee3"}, + {file = "regex-2020.6.8-cp36-cp36m-win_amd64.whl", hash = "sha256:c78e66a922de1c95a208e4ec02e2e5cf0bb83a36ceececc10a72841e53fbf2bd"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:08997a37b221a3e27d68ffb601e45abfb0093d39ee770e4257bd2f5115e8cb0a"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2f6f211633ee8d3f7706953e9d3edc7ce63a1d6aad0be5dcee1ece127eea13ae"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:55b4c25cbb3b29f8d5e63aeed27b49fa0f8476b0d4e1b3171d85db891938cc3a"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:89cda1a5d3e33ec9e231ece7307afc101b5217523d55ef4dc7fb2abd6de71ba3"}, + {file = "regex-2020.6.8-cp37-cp37m-win32.whl", hash = "sha256:690f858d9a94d903cf5cada62ce069b5d93b313d7d05456dbcd99420856562d9"}, + {file = "regex-2020.6.8-cp37-cp37m-win_amd64.whl", hash = "sha256:1700419d8a18c26ff396b3b06ace315b5f2a6e780dad387e4c48717a12a22c29"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:654cb773b2792e50151f0e22be0f2b6e1c3a04c5328ff1d9d59c0398d37ef610"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:52e1b4bef02f4040b2fd547357a170fc1146e60ab310cdbdd098db86e929b387"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:cf59bbf282b627130f5ba68b7fa3abdb96372b24b66bdf72a4920e8153fc7910"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5aaa5928b039ae440d775acea11d01e42ff26e1561c0ffcd3d805750973c6baf"}, + {file = "regex-2020.6.8-cp38-cp38-win32.whl", hash = "sha256:97712e0d0af05febd8ab63d2ef0ab2d0cd9deddf4476f7aa153f76feef4b2754"}, + {file = "regex-2020.6.8-cp38-cp38-win_amd64.whl", hash = "sha256:6ad8663c17db4c5ef438141f99e291c4d4edfeaacc0ce28b5bba2b0bf273d9b5"}, + {file = "regex-2020.6.8.tar.gz", hash = "sha256:e9b64e609d37438f7d6e68c2546d2cb8062f3adb27e6336bc129b51be20773ac"}, ] requests = [ {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, @@ -2046,8 +2043,8 @@ tornado = [ {file = "tornado-6.0.4.tar.gz", hash = "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc"}, ] tqdm = [ - {file = "tqdm-4.46.0-py2.py3-none-any.whl", hash = "sha256:acdafb20f51637ca3954150d0405ff1a7edde0ff19e38fb99a80a66210d2a28f"}, - {file = "tqdm-4.46.0.tar.gz", hash = "sha256:4733c4a10d0f2a4d098d801464bdaf5240c7dadd2a7fde4ee93b0a0efd9fb25e"}, + {file = "tqdm-4.46.1-py2.py3-none-any.whl", hash = "sha256:07c06493f1403c1380b630ae3dcbe5ae62abcf369a93bbc052502279f189ab8c"}, + {file = "tqdm-4.46.1.tar.gz", hash = "sha256:cd140979c2bebd2311dfb14781d8f19bd5a9debb92dcab9f6ef899c987fcf71f"}, ] traitlets = [ {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, @@ -2097,12 +2094,12 @@ uvloop = [ {file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"}, ] virtualenv = [ - {file = "virtualenv-20.0.21-py2.py3-none-any.whl", hash = "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70"}, - {file = "virtualenv-20.0.21.tar.gz", hash = "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf"}, + {file = "virtualenv-20.0.23-py2.py3-none-any.whl", hash = "sha256:ccfb8e1e05a1174f7bd4c163700277ba730496094fe1a58bea9d4ac140a207c8"}, + {file = "virtualenv-20.0.23.tar.gz", hash = "sha256:5102fbf1ec57e80671ef40ed98a84e980a71194cedf30c87c2b25c3a9e0b0107"}, ] wcwidth = [ - {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"}, - {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"}, + {file = "wcwidth-0.2.4-py2.py3-none-any.whl", hash = "sha256:79375666b9954d4a1a10739315816324c3e73110af9d0e102d906fdb0aec009f"}, + {file = "wcwidth-0.2.4.tar.gz", hash = "sha256:8c6b5b6ee1360b842645f336d9e5d68c55817c26d3050f46b235ef2bc650e48f"}, ] webencodings = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, diff --git a/pyproject.toml b/pyproject.toml index e878d0f3..20470618 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aiogram" -version = "3.0.0-alpha.4" +version = "3.0.0-alpha.5" description = "Modern and fully asynchronous framework for Telegram Bot API" authors = ["Alex Root Junior "] license = "MIT" diff --git a/tests/mocked_bot.py b/tests/mocked_bot.py index 9ee5731c..f0b1ead6 100644 --- a/tests/mocked_bot.py +++ b/tests/mocked_bot.py @@ -5,6 +5,7 @@ from aiogram import Bot from aiogram.api.client.session.base import BaseSession from aiogram.api.methods import TelegramMethod from aiogram.api.methods.base import Request, Response, T +from aiogram.api.types import UNSET class MockedSession(BaseSession): @@ -23,8 +24,10 @@ class MockedSession(BaseSession): async def close(self): pass - async def make_request(self, token: str, method: TelegramMethod[T]) -> T: - self.requests.append(method.build_request()) + async def make_request( + self, bot: Bot, method: TelegramMethod[T], timeout: Optional[int] = UNSET + ) -> T: + self.requests.append(method.build_request(bot)) response: Response[T] = self.responses.pop() self.raise_for_status(response) return response.result # type: ignore diff --git a/tests/test_api/test_client/test_bot.py b/tests/test_api/test_client/test_bot.py index 675c0dd3..b9dff9e7 100644 --- a/tests/test_api/test_client/test_bot.py +++ b/tests/test_api/test_client/test_bot.py @@ -42,12 +42,13 @@ class TestBot: new_callable=CoroutineMock, ) as mocked_make_request: await bot(method) - mocked_make_request.assert_awaited_with("42:TEST", method) + mocked_make_request.assert_awaited_with(bot, method, timeout=None) @pytest.mark.asyncio async def test_close(self): - bot = Bot("42:TEST", session=AiohttpSession()) - await bot.session.create_session() + session = AiohttpSession() + bot = Bot("42:TEST", session=session) + await session.create_session() with patch( "aiogram.api.client.session.aiohttp.AiohttpSession.close", new_callable=CoroutineMock 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 e7716f9a..bb89e888 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 @@ -5,9 +5,11 @@ import aiohttp_socks import pytest from aresponses import ResponsesMockServer +from aiogram import Bot from aiogram.api.client.session.aiohttp import AiohttpSession from aiogram.api.methods import Request, TelegramMethod from aiogram.api.types import InputFile +from tests.mocked_bot import MockedBot try: from asynctest import CoroutineMock, patch @@ -147,7 +149,7 @@ class TestAiohttpSession: assert isinstance(fields[1][2], BareInputFile) @pytest.mark.asyncio - async def test_make_request(self, aresponses: ResponsesMockServer): + async def test_make_request(self, bot: MockedBot, aresponses: ResponsesMockServer): aresponses.add( aresponses.ANY, "/bot42:TEST/method", @@ -164,14 +166,14 @@ class TestAiohttpSession: class TestMethod(TelegramMethod[int]): __returning__ = int - def build_request(self) -> Request: + def build_request(self, bot: Bot) -> Request: return Request(method="method", data={}) call = TestMethod() with patch( "aiogram.api.client.session.base.BaseSession.raise_for_status" ) as patched_raise_for_status: - result = await session.make_request("42:TEST", call) + result = await session.make_request(bot, call) assert isinstance(result, int) assert result == 42 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 35dcfa8e..42dfedf7 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,12 +1,13 @@ import datetime import json -from typing import AsyncContextManager, AsyncGenerator +from typing import AsyncContextManager, AsyncGenerator, Optional import pytest from aiogram.api.client.session.base import BaseSession, T from aiogram.api.client.telegram import PRODUCTION, TelegramAPIServer from aiogram.api.methods import GetMe, Response, TelegramMethod +from aiogram.api.types import UNSET try: from asynctest import CoroutineMock, patch @@ -18,7 +19,7 @@ class CustomSession(BaseSession): async def close(self): pass - async def make_request(self, token: str, method: TelegramMethod[T]) -> None: # type: ignore + async def make_request(self, token: str, method: TelegramMethod[T], timeout: Optional[int] = UNSET) -> None: # type: ignore assert isinstance(token, str) assert isinstance(method, TelegramMethod) @@ -49,14 +50,9 @@ class TestBaseSession: return json.dumps session.json_dumps = custom_dumps - assert session.json_dumps == custom_dumps == session._json_dumps + assert session.json_dumps == custom_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") - ) + assert session.json_loads == custom_loads def test_timeout(self): session = CustomSession() diff --git a/tests/test_api/test_methods/test_answer_inline_query.py b/tests/test_api/test_methods/test_answer_inline_query.py index 84c0a05e..950502c9 100644 --- a/tests/test_api/test_methods/test_answer_inline_query.py +++ b/tests/test_api/test_methods/test_answer_inline_query.py @@ -29,17 +29,14 @@ class TestAnswerInlineQuery: assert request.method == "answerInlineQuery" assert response == prepare_result.result - def test_parse_mode(self): + def test_parse_mode(self, bot: MockedBot): query = AnswerInlineQuery( inline_query_id="query id", results=[InlineQueryResultPhoto(id="result id", photo_url="photo", thumb_url="thumb")], ) - request = query.build_request() + request = query.build_request(bot) assert request.data["results"][0]["parse_mode"] is None - token = Bot.set_current(Bot(token="42:TEST", parse_mode="HTML")) - try: - request = query.build_request() - assert request.data["results"][0]["parse_mode"] == "HTML" - finally: - Bot.reset_current(token) + new_bot = Bot(token="42:TEST", parse_mode="HTML") + request = query.build_request(new_bot) + assert request.data["results"][0]["parse_mode"] == "HTML" diff --git a/tests/test_api/test_methods/test_base.py b/tests/test_api/test_methods/test_base.py index c32a075b..3fd46078 100644 --- a/tests/test_api/test_methods/test_base.py +++ b/tests/test_api/test_methods/test_base.py @@ -4,6 +4,7 @@ import pytest from aiogram import Bot from aiogram.api.methods.base import prepare_parse_mode +from tests.mocked_bot import MockedBot class TestPrepareFile: @@ -35,19 +36,19 @@ class TestPrepareParseMode: ) @pytest.mark.asyncio async def test_default_parse_mode( - self, parse_mode: str, data: Dict[str, str], result: Optional[str] + self, bot: MockedBot, parse_mode: str, data: Dict[str, str], result: Optional[str] ): async with Bot(token="42:TEST", parse_mode=parse_mode).context() as bot: assert bot.parse_mode == parse_mode - prepare_parse_mode(data) + prepare_parse_mode(bot, data) assert data.get("parse_mode") == result @pytest.mark.asyncio async def test_list(self): data = [{}] * 2 data.append({"parse_mode": "HTML"}) - async with Bot(token="42:TEST", parse_mode="Markdown").context(): - prepare_parse_mode(data) + bot = Bot(token="42:TEST", parse_mode="Markdown") + prepare_parse_mode(bot, data) assert isinstance(data, list) assert len(data) == 3 @@ -56,7 +57,7 @@ class TestPrepareParseMode: assert data[1]["parse_mode"] == "Markdown" assert data[2]["parse_mode"] == "HTML" - def test_bot_not_in_context(self): + def test_bot_not_in_context(self, bot: MockedBot): data = {} - prepare_parse_mode(data) - assert "parse_mode" not in data + prepare_parse_mode(bot, data) + assert data["parse_mode"] is None diff --git a/tests/test_api/test_types/test_chat_member.py b/tests/test_api/test_types/test_chat_member.py new file mode 100644 index 00000000..bb88e690 --- /dev/null +++ b/tests/test_api/test_types/test_chat_member.py @@ -0,0 +1,29 @@ +import pytest + +from aiogram.api.types import ChatMember, User + +user = User(id=42, is_bot=False, first_name="User", last_name=None) + + +class TestChatMember: + @pytest.mark.parametrize( + "status,result", [["administrator", True], ["creator", True], ["member", False]] + ) + def test_is_chat_admin(self, status: str, result: bool): + chat_member = ChatMember(user=user, status=status) + assert chat_member.is_chat_admin == result + + @pytest.mark.parametrize( + "status,result", + [ + ["administrator", True], + ["creator", True], + ["member", True], + ["restricted", True], + ["kicked", False], + ["left", False], + ], + ) + def test_is_chat_member(self, status: str, result: bool): + chat_member = ChatMember(user=user, status=status) + assert chat_member.is_chat_member == result diff --git a/tests/test_utils/test_helper.py b/tests/test_utils/test_helper.py index 8125ef60..466db4ef 100644 --- a/tests/test_utils/test_helper.py +++ b/tests/test_utils/test_helper.py @@ -1,6 +1,6 @@ import pytest -from aiogram.utils.helper import Helper, HelperMode, Item, ListItem, OrderedHelper +from aiogram.utils.helper import Default, Helper, HelperMode, Item, ListItem, OrderedHelper class TestHelper: @@ -132,3 +132,50 @@ class TestOrderedHelper: B = ListItem() assert MyOrderedHelper.all() == ["A", "D", "C", "B"] + + +class TestDefaultDescriptor: + def test_descriptor_fs(self): + obj = type("ClassA", (), {})() + default_x_val = "some_x" + x = Default(default_x_val) + + # we can omit owner, usually it's just obj.__class__ + assert x.__get__(instance=obj, owner=None) == default_x_val + assert x.__get__(instance=obj, owner=obj.__class__) == default_x_val + + new_x_val = "new_x" + assert x.__set__(instance=obj, value=new_x_val) is None + + with pytest.raises(AttributeError) as exc: + x.__set__(instance=obj.__class__, value="will never be set") + assert "Instance cannot be class or None" in str(exc.value) + + assert x.__get__(instance=obj, owner=obj.__class__) == new_x_val + + with pytest.raises(AttributeError) as exc: + x.__delete__(instance=obj.__class__) + assert "Instance cannot be class or None" in str(exc.value) + + x.__delete__(instance=obj) + assert x.__get__(instance=obj, owner=obj.__class__) == default_x_val + + def test_init(self): + class A: + x = Default(fget=lambda a_inst: "nothing") + + assert isinstance(A.__dict__["x"], Default) + + a = A() + assert a.x == "nothing" + + x = Default("x") + assert x.__get__(None, None) == "x" + assert x.fget(None) == x.__get__(None, None) + + def test_nullability(self): + class A: + x = Default(default=None, fget=None) + + assert A.x is None + assert A().x is None