diff --git a/.apiversion b/.apiversion index c3cae12b..0359f243 100644 --- a/.apiversion +++ b/.apiversion @@ -1 +1 @@ -9.3 +9.4 diff --git a/.butcher/enums/ButtonStyle.yml b/.butcher/enums/ButtonStyle.yml new file mode 100644 index 00000000..12833dde --- /dev/null +++ b/.butcher/enums/ButtonStyle.yml @@ -0,0 +1,12 @@ +name: ButtonStyle +description: | + This object represents a button style (inline- or reply-keyboard). + + Sources: + * https://core.telegram.org/bots/api#inlinekeyboardbutton + * https://core.telegram.org/bots/api#keyboardbutton + +parse: + entity: InlineKeyboardButton + attribute: style + regexp: "'([a-z]+)'" diff --git a/.butcher/methods/createForumTopic/entity.json b/.butcher/methods/createForumTopic/entity.json index c10317c1..e3f744b3 100644 --- a/.butcher/methods/createForumTopic/entity.json +++ b/.butcher/methods/createForumTopic/entity.json @@ -7,9 +7,9 @@ "object": { "anchor": "createforumtopic", "name": "createForumTopic", - "description": "Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator rights. Returns information about the created topic as a ForumTopic object.", - "html_description": "

Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator rights. Returns information about the created topic as a ForumTopic object.

", - "rst_description": "Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator rights. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object.", + "description": "Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator right. Returns information about the created topic as a ForumTopic object.", + "html_description": "

Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator right. Returns information about the created topic as a ForumTopic object.

", + "rst_description": "Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator right. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object.", "annotations": [ { "type": "Integer or String", diff --git a/.butcher/methods/getUserProfileAudios/entity.json b/.butcher/methods/getUserProfileAudios/entity.json new file mode 100644 index 00000000..89fa4466 --- /dev/null +++ b/.butcher/methods/getUserProfileAudios/entity.json @@ -0,0 +1,41 @@ +{ + "meta": {}, + "group": { + "title": "Available methods", + "anchor": "available-methods" + }, + "object": { + "anchor": "getuserprofileaudios", + "name": "getUserProfileAudios", + "description": "Use this method to get a list of profile audios for a user. Returns a UserProfileAudios object.", + "html_description": "

Use this method to get a list of profile audios for a user. Returns a UserProfileAudios object.

", + "rst_description": "Use this method to get a list of profile audios for a user. Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object.", + "annotations": [ + { + "type": "Integer", + "required": true, + "description": "Unique identifier of the target user", + "html_description": "Unique identifier of the target user", + "rst_description": "Unique identifier of the target user\n", + "name": "user_id" + }, + { + "type": "Integer", + "required": false, + "description": "Sequential number of the first audio to be returned. By default, all audios are returned.", + "html_description": "Sequential number of the first audio to be returned. By default, all audios are returned.", + "rst_description": "Sequential number of the first audio to be returned. By default, all audios are returned.\n", + "name": "offset" + }, + { + "type": "Integer", + "required": false, + "description": "Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.", + "html_description": "Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.", + "rst_description": "Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.\n", + "name": "limit" + } + ], + "category": "methods" + } +} diff --git a/.butcher/methods/removeMyProfilePhoto/entity.json b/.butcher/methods/removeMyProfilePhoto/entity.json new file mode 100644 index 00000000..e24d04bc --- /dev/null +++ b/.butcher/methods/removeMyProfilePhoto/entity.json @@ -0,0 +1,16 @@ +{ + "meta": {}, + "group": { + "title": "Available methods", + "anchor": "available-methods" + }, + "object": { + "anchor": "removemyprofilephoto", + "name": "removeMyProfilePhoto", + "description": "Removes the profile photo of the bot. Requires no parameters. Returns True on success.", + "html_description": "

Removes the profile photo of the bot. Requires no parameters. Returns True on success.

", + "rst_description": "Removes the profile photo of the bot. Requires no parameters. Returns :code:`True` on success.", + "annotations": [], + "category": "methods" + } +} diff --git a/.butcher/methods/setMyProfilePhoto/entity.json b/.butcher/methods/setMyProfilePhoto/entity.json new file mode 100644 index 00000000..53f4ab44 --- /dev/null +++ b/.butcher/methods/setMyProfilePhoto/entity.json @@ -0,0 +1,25 @@ +{ + "meta": {}, + "group": { + "title": "Available methods", + "anchor": "available-methods" + }, + "object": { + "anchor": "setmyprofilephoto", + "name": "setMyProfilePhoto", + "description": "Changes the profile photo of the bot. Returns True on success.", + "html_description": "

Changes the profile photo of the bot. Returns True on success.

", + "rst_description": "Changes the profile photo of the bot. Returns :code:`True` on success.", + "annotations": [ + { + "type": "InputProfilePhoto", + "required": true, + "description": "The new profile photo to set", + "html_description": "The new profile photo to set", + "rst_description": "The new profile photo to set\n", + "name": "photo" + } + ], + "category": "methods" + } +} diff --git a/.butcher/schema/schema.json b/.butcher/schema/schema.json index d492782d..0fca3099 100644 --- a/.butcher/schema/schema.json +++ b/.butcher/schema/schema.json @@ -1,7 +1,7 @@ { "api": { - "version": "9.3", - "release_date": "2025-12-31" + "version": "9.4", + "release_date": "2026-02-09" }, "items": [ { @@ -552,6 +552,14 @@ "rst_description": "*Optional*. :code:`True`, if the bot has forum topic mode enabled in private chats. Returned only in :class:`aiogram.methods.get_me.GetMe`.\n", "name": "has_topics_enabled", "required": false + }, + { + "type": "Boolean", + "description": "True, if the bot allows users to create and delete topics in private chats. Returned only in getMe.", + "html_description": "Optional. True, if the bot allows users to create and delete topics in private chats. Returned only in getMe.", + "rst_description": "*Optional*. :code:`True`, if the bot allows users to create and delete topics in private chats. Returned only in :class:`aiogram.methods.get_me.GetMe`.\n", + "name": "allows_users_to_create_topics", + "required": false } ], "category": "types" @@ -1021,6 +1029,14 @@ "name": "rating", "required": false }, + { + "type": "Audio", + "description": "For private chats, the first audio added to the profile of the user", + "html_description": "Optional. For private chats, the first audio added to the profile of the user", + "rst_description": "*Optional*. For private chats, the first audio added to the profile of the user\n", + "name": "first_profile_audio", + "required": false + }, { "type": "UniqueGiftColors", "description": "The color scheme based on a unique gift that must be used for the chat's name, message replies and link previews", @@ -1479,6 +1495,22 @@ "name": "left_chat_member", "required": false }, + { + "type": "ChatOwnerLeft", + "description": "Service message: chat owner has left", + "html_description": "Optional. Service message: chat owner has left", + "rst_description": "*Optional*. Service message: chat owner has left\n", + "name": "chat_owner_left", + "required": false + }, + { + "type": "ChatOwnerChanged", + "description": "Service message: chat owner has changed", + "html_description": "Optional. Service message: chat owner has changed", + "rst_description": "*Optional*. Service message: chat owner has changed\n", + "name": "chat_owner_changed", + "required": false + }, { "type": "String", "description": "A chat title was changed to this value", @@ -2794,6 +2826,64 @@ ], "category": "types" }, + { + "anchor": "videoquality", + "name": "VideoQuality", + "description": "This object represents a video file of a specific quality.", + "html_description": "

This object represents a video file of a specific quality.

", + "rst_description": "This object represents a video file of a specific quality.", + "annotations": [ + { + "type": "String", + "description": "Identifier for this file, which can be used to download or reuse the file", + "html_description": "Identifier for this file, which can be used to download or reuse the file", + "rst_description": "Identifier for this file, which can be used to download or reuse the file\n", + "name": "file_id", + "required": true + }, + { + "type": "String", + "description": "Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file.", + "html_description": "Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file.", + "rst_description": "Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file.\n", + "name": "file_unique_id", + "required": true + }, + { + "type": "Integer", + "description": "Video width", + "html_description": "Video width", + "rst_description": "Video width\n", + "name": "width", + "required": true + }, + { + "type": "Integer", + "description": "Video height", + "html_description": "Video height", + "rst_description": "Video height\n", + "name": "height", + "required": true + }, + { + "type": "String", + "description": "Codec that was used to encode the video, for example, 'h264', 'h265', or 'av01'", + "html_description": "Codec that was used to encode the video, for example, “h264”, “h265”, or “av01”", + "rst_description": "Codec that was used to encode the video, for example, 'h264', 'h265', or 'av01'\n", + "name": "codec", + "required": true + }, + { + "type": "Integer", + "description": "File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.", + "html_description": "Optional. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.", + "rst_description": "*Optional*. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.\n", + "name": "file_size", + "required": false + } + ], + "category": "types" + }, { "anchor": "video", "name": "Video", @@ -2865,6 +2955,14 @@ "name": "start_timestamp", "required": false }, + { + "type": "Array of VideoQuality", + "description": "List of available qualities of the video", + "html_description": "Optional. List of available qualities of the video", + "rst_description": "*Optional*. List of available qualities of the video\n", + "name": "qualities", + "required": false + }, { "type": "String", "description": "Original filename as defined by the sender", @@ -5184,6 +5282,32 @@ ], "category": "types" }, + { + "anchor": "userprofileaudios", + "name": "UserProfileAudios", + "description": "This object represents the audios displayed on a user's profile.", + "html_description": "

This object represents the audios displayed on a user's profile.

", + "rst_description": "This object represents the audios displayed on a user's profile.", + "annotations": [ + { + "type": "Integer", + "description": "Total number of profile audios for the target user", + "html_description": "Total number of profile audios for the target user", + "rst_description": "Total number of profile audios for the target user\n", + "name": "total_count", + "required": true + }, + { + "type": "Array of Audio", + "description": "Requested profile audios", + "html_description": "Requested profile audios", + "rst_description": "Requested profile audios\n", + "name": "audios", + "required": true + } + ], + "category": "types" + }, { "anchor": "file", "name": "File", @@ -5305,18 +5429,34 @@ { "anchor": "keyboardbutton", "name": "KeyboardButton", - "description": "This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, String can be used instead of this object to specify the button text.\nNote: request_users and request_chat options will only work in Telegram versions released after 3 February, 2023. Older clients will display unsupported message.", - "html_description": "

This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, String can be used instead of this object to specify the button text.

Note: request_users and request_chat options will only work in Telegram versions released after 3 February, 2023. Older clients will display unsupported message.

", - "rst_description": "This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, *String* can be used instead of this object to specify the button text.\n**Note:** *request_users* and *request_chat* options will only work in Telegram versions released after 3 February, 2023. Older clients will display *unsupported message*.", + "description": "This object represents one button of the reply keyboard. At most one of the fields other than text, icon_custom_emoji_id, and style must be used to specify the type of the button. For simple text buttons, String can be used instead of this object to specify the button text.", + "html_description": "

This object represents one button of the reply keyboard. At most one of the fields other than text, icon_custom_emoji_id, and style must be used to specify the type of the button. For simple text buttons, String can be used instead of this object to specify the button text.

", + "rst_description": "This object represents one button of the reply keyboard. At most one of the fields other than *text*, *icon_custom_emoji_id*, and *style* must be used to specify the type of the button. For simple text buttons, *String* can be used instead of this object to specify the button text.", "annotations": [ { "type": "String", - "description": "Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed", - "html_description": "Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed", - "rst_description": "Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed\n", + "description": "Text of the button. If none of the fields other than text, icon_custom_emoji_id, and style are used, it will be sent as a message when the button is pressed", + "html_description": "Text of the button. If none of the fields other than text, icon_custom_emoji_id, and style are used, it will be sent as a message when the button is pressed", + "rst_description": "Text of the button. If none of the fields other than *text*, *icon_custom_emoji_id*, and *style* are used, it will be sent as a message when the button is pressed\n", "name": "text", "required": true }, + { + "type": "String", + "description": "Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.", + "html_description": "Optional. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.", + "rst_description": "*Optional*. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on `Fragment `_ or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.\n", + "name": "icon_custom_emoji_id", + "required": false + }, + { + "type": "String", + "description": "Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.", + "html_description": "Optional. Style of the button. Must be one of “danger” (red), “success” (green) or “primary” (blue). If omitted, then an app-specific style is used.", + "rst_description": "*Optional*. Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.\n", + "name": "style", + "required": false + }, { "type": "KeyboardButtonRequestUsers", "description": "If specified, pressing the button will open a list of suitable users. Identifiers of selected users will be sent to the bot in a 'users_shared' service message. Available in private chats only.", @@ -5597,9 +5737,9 @@ { "anchor": "inlinekeyboardbutton", "name": "InlineKeyboardButton", - "description": "This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button.", - "html_description": "

This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button.

", - "rst_description": "This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button.", + "description": "This object represents one button of an inline keyboard. Exactly one of the fields other than text, icon_custom_emoji_id, and style must be used to specify the type of the button.", + "html_description": "

This object represents one button of an inline keyboard. Exactly one of the fields other than text, icon_custom_emoji_id, and style must be used to specify the type of the button.

", + "rst_description": "This object represents one button of an inline keyboard. Exactly one of the fields other than *text*, *icon_custom_emoji_id*, and *style* must be used to specify the type of the button.", "annotations": [ { "type": "String", @@ -5609,6 +5749,22 @@ "name": "text", "required": true }, + { + "type": "String", + "description": "Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.", + "html_description": "Optional. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.", + "rst_description": "*Optional*. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on `Fragment `_ or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.\n", + "name": "icon_custom_emoji_id", + "required": false + }, + { + "type": "String", + "description": "Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.", + "html_description": "Optional. Style of the button. Must be one of “danger” (red), “success” (green) or “primary” (blue). If omitted, then an app-specific style is used.", + "rst_description": "*Optional*. Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.\n", + "name": "style", + "required": false + }, { "type": "String", "description": "HTTP or tg:// URL to be opened when the button is pressed. Links tg://user?id= can be used to mention a user by their identifier without using a username, if this is allowed by their privacy settings.", @@ -7884,11 +8040,19 @@ }, { "type": "Integer", - "description": "The number of unique gifts that receive this model for every 1000 gifts upgraded", - "html_description": "The number of unique gifts that receive this model for every 1000 gifts upgraded", - "rst_description": "The number of unique gifts that receive this model for every 1000 gifts upgraded\n", + "description": "The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts.", + "html_description": "The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts.", + "rst_description": "The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts.\n", "name": "rarity_per_mille", "required": true + }, + { + "type": "String", + "description": "Rarity of the model if it is a crafted model. Currently, can be 'uncommon', 'rare', 'epic', or 'legendary'.", + "html_description": "Optional. Rarity of the model if it is a crafted model. Currently, can be “uncommon”, “rare”, “epic”, or “legendary”.", + "rst_description": "*Optional*. Rarity of the model if it is a crafted model. Currently, can be 'uncommon', 'rare', 'epic', or 'legendary'.\n", + "name": "rarity", + "required": false } ], "category": "types" @@ -8132,6 +8296,14 @@ "name": "is_premium", "required": false }, + { + "type": "True", + "description": "True, if the gift was used to craft another gift and isn't available anymore", + "html_description": "Optional. True, if the gift was used to craft another gift and isn't available anymore", + "rst_description": "*Optional*. :code:`True`, if the gift was used to craft another gift and isn't available anymore\n", + "name": "is_burned", + "required": false + }, { "type": "True", "description": "True, if the gift is assigned from the TON blockchain and can't be resold or transferred in Telegram", @@ -9193,6 +9365,42 @@ ], "category": "types" }, + { + "anchor": "chatownerleft", + "name": "ChatOwnerLeft", + "description": "Describes a service message about the chat owner leaving the chat.", + "html_description": "

Describes a service message about the chat owner leaving the chat.

", + "rst_description": "Describes a service message about the chat owner leaving the chat.", + "annotations": [ + { + "type": "User", + "description": "The user which will be the new owner of the chat if the previous owner does not return to the chat", + "html_description": "Optional. The user which will be the new owner of the chat if the previous owner does not return to the chat", + "rst_description": "*Optional*. The user which will be the new owner of the chat if the previous owner does not return to the chat\n", + "name": "new_owner", + "required": false + } + ], + "category": "types" + }, + { + "anchor": "chatownerchanged", + "name": "ChatOwnerChanged", + "description": "Describes a service message about an ownership change in the chat.", + "html_description": "

Describes a service message about an ownership change in the chat.

", + "rst_description": "Describes a service message about an ownership change in the chat.", + "annotations": [ + { + "type": "User", + "description": "The new owner of the chat", + "html_description": "The new owner of the chat", + "rst_description": "The new owner of the chat\n", + "name": "new_owner", + "required": true + } + ], + "category": "types" + }, { "anchor": "userchatboosts", "name": "UserChatBoosts", @@ -13040,6 +13248,40 @@ ], "category": "methods" }, + { + "anchor": "getuserprofileaudios", + "name": "getUserProfileAudios", + "description": "Use this method to get a list of profile audios for a user. Returns a UserProfileAudios object.", + "html_description": "

Use this method to get a list of profile audios for a user. Returns a UserProfileAudios object.

", + "rst_description": "Use this method to get a list of profile audios for a user. Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object.", + "annotations": [ + { + "type": "Integer", + "required": true, + "description": "Unique identifier of the target user", + "html_description": "Unique identifier of the target user", + "rst_description": "Unique identifier of the target user\n", + "name": "user_id" + }, + { + "type": "Integer", + "required": false, + "description": "Sequential number of the first audio to be returned. By default, all audios are returned.", + "html_description": "Sequential number of the first audio to be returned. By default, all audios are returned.", + "rst_description": "Sequential number of the first audio to be returned. By default, all audios are returned.\n", + "name": "offset" + }, + { + "type": "Integer", + "required": false, + "description": "Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.", + "html_description": "Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.", + "rst_description": "Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.\n", + "name": "limit" + } + ], + "category": "methods" + }, { "anchor": "setuseremojistatus", "name": "setUserEmojiStatus", @@ -14116,9 +14358,9 @@ { "anchor": "createforumtopic", "name": "createForumTopic", - "description": "Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator rights. Returns information about the created topic as a ForumTopic object.", - "html_description": "

Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator rights. Returns information about the created topic as a ForumTopic object.

", - "rst_description": "Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator rights. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object.", + "description": "Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator right. Returns information about the created topic as a ForumTopic object.", + "html_description": "

Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot must be an administrator in the chat for this to work and must have the can_manage_topics administrator right. Returns information about the created topic as a ForumTopic object.

", + "rst_description": "Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator right. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object.", "annotations": [ { "type": "Integer or String", @@ -14729,6 +14971,33 @@ ], "category": "methods" }, + { + "anchor": "setmyprofilephoto", + "name": "setMyProfilePhoto", + "description": "Changes the profile photo of the bot. Returns True on success.", + "html_description": "

Changes the profile photo of the bot. Returns True on success.

", + "rst_description": "Changes the profile photo of the bot. Returns :code:`True` on success.", + "annotations": [ + { + "type": "InputProfilePhoto", + "required": true, + "description": "The new profile photo to set", + "html_description": "The new profile photo to set", + "rst_description": "The new profile photo to set\n", + "name": "photo" + } + ], + "category": "methods" + }, + { + "anchor": "removemyprofilephoto", + "name": "removeMyProfilePhoto", + "description": "Removes the profile photo of the bot. Requires no parameters. Returns True on success.", + "html_description": "

Removes the profile photo of the bot. Requires no parameters. Returns True on success.

", + "rst_description": "Removes the profile photo of the bot. Requires no parameters. Returns :code:`True` on success.", + "annotations": [], + "category": "methods" + }, { "anchor": "setchatmenubutton", "name": "setChatMenuButton", @@ -22596,9 +22865,9 @@ { "anchor": "gamehighscore", "name": "GameHighScore", - "description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\nIf you've got any questions, please check out our Bot FAQ", - "html_description": "

This object represents one row of the high scores table for a game.

And that's about all we've got for now.
\nIf you've got any questions, please check out our Bot FAQ »

", - "rst_description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\n\nIf you've got any questions, please check out our `https://core.telegram.org/bots/faq `_ **Bot FAQ »**", + "description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\nIf you've got any questions, please check out our Bot FAQ\n-", + "html_description": "

This object represents one row of the high scores table for a game.

And that's about all we've got for now.
\nIf you've got any questions, please check out our Bot FAQ »
\n-

", + "rst_description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\n\nIf you've got any questions, please check out our `https://core.telegram.org/bots/faq `_ **Bot FAQ »**\n\n-", "annotations": [ { "type": "Integer", diff --git a/.butcher/types/ChatFullInfo/entity.json b/.butcher/types/ChatFullInfo/entity.json index 937ed037..3627d65d 100644 --- a/.butcher/types/ChatFullInfo/entity.json +++ b/.butcher/types/ChatFullInfo/entity.json @@ -395,6 +395,14 @@ "name": "rating", "required": false }, + { + "type": "Audio", + "description": "For private chats, the first audio added to the profile of the user", + "html_description": "Optional. For private chats, the first audio added to the profile of the user", + "rst_description": "*Optional*. For private chats, the first audio added to the profile of the user\n", + "name": "first_profile_audio", + "required": false + }, { "type": "UniqueGiftColors", "description": "The color scheme based on a unique gift that must be used for the chat's name, message replies and link previews", diff --git a/.butcher/types/ChatOwnerChanged/entity.json b/.butcher/types/ChatOwnerChanged/entity.json new file mode 100644 index 00000000..083be3fa --- /dev/null +++ b/.butcher/types/ChatOwnerChanged/entity.json @@ -0,0 +1,25 @@ +{ + "meta": {}, + "group": { + "title": "Available types", + "anchor": "available-types" + }, + "object": { + "anchor": "chatownerchanged", + "name": "ChatOwnerChanged", + "description": "Describes a service message about an ownership change in the chat.", + "html_description": "

Describes a service message about an ownership change in the chat.

", + "rst_description": "Describes a service message about an ownership change in the chat.", + "annotations": [ + { + "type": "User", + "description": "The new owner of the chat", + "html_description": "The new owner of the chat", + "rst_description": "The new owner of the chat\n", + "name": "new_owner", + "required": true + } + ], + "category": "types" + } +} diff --git a/.butcher/types/ChatOwnerLeft/entity.json b/.butcher/types/ChatOwnerLeft/entity.json new file mode 100644 index 00000000..bbeac660 --- /dev/null +++ b/.butcher/types/ChatOwnerLeft/entity.json @@ -0,0 +1,25 @@ +{ + "meta": {}, + "group": { + "title": "Available types", + "anchor": "available-types" + }, + "object": { + "anchor": "chatownerleft", + "name": "ChatOwnerLeft", + "description": "Describes a service message about the chat owner leaving the chat.", + "html_description": "

Describes a service message about the chat owner leaving the chat.

", + "rst_description": "Describes a service message about the chat owner leaving the chat.", + "annotations": [ + { + "type": "User", + "description": "The user which will be the new owner of the chat if the previous owner does not return to the chat", + "html_description": "Optional. The user which will be the new owner of the chat if the previous owner does not return to the chat", + "rst_description": "*Optional*. The user which will be the new owner of the chat if the previous owner does not return to the chat\n", + "name": "new_owner", + "required": false + } + ], + "category": "types" + } +} diff --git a/.butcher/types/GameHighScore/entity.json b/.butcher/types/GameHighScore/entity.json index ce3f52d2..21a8a5e7 100644 --- a/.butcher/types/GameHighScore/entity.json +++ b/.butcher/types/GameHighScore/entity.json @@ -7,9 +7,9 @@ "object": { "anchor": "gamehighscore", "name": "GameHighScore", - "description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\nIf you've got any questions, please check out our Bot FAQ", - "html_description": "

This object represents one row of the high scores table for a game.

And that's about all we've got for now.
\nIf you've got any questions, please check out our Bot FAQ »

", - "rst_description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\n\nIf you've got any questions, please check out our `https://core.telegram.org/bots/faq `_ **Bot FAQ »**", + "description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\nIf you've got any questions, please check out our Bot FAQ\n-", + "html_description": "

This object represents one row of the high scores table for a game.

And that's about all we've got for now.
\nIf you've got any questions, please check out our Bot FAQ »
\n-

", + "rst_description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\n\nIf you've got any questions, please check out our `https://core.telegram.org/bots/faq `_ **Bot FAQ »**\n\n-", "annotations": [ { "type": "Integer", diff --git a/.butcher/types/InlineKeyboardButton/entity.json b/.butcher/types/InlineKeyboardButton/entity.json index 6279c8e6..15e52808 100644 --- a/.butcher/types/InlineKeyboardButton/entity.json +++ b/.butcher/types/InlineKeyboardButton/entity.json @@ -7,9 +7,9 @@ "object": { "anchor": "inlinekeyboardbutton", "name": "InlineKeyboardButton", - "description": "This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button.", - "html_description": "

This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button.

", - "rst_description": "This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button.", + "description": "This object represents one button of an inline keyboard. Exactly one of the fields other than text, icon_custom_emoji_id, and style must be used to specify the type of the button.", + "html_description": "

This object represents one button of an inline keyboard. Exactly one of the fields other than text, icon_custom_emoji_id, and style must be used to specify the type of the button.

", + "rst_description": "This object represents one button of an inline keyboard. Exactly one of the fields other than *text*, *icon_custom_emoji_id*, and *style* must be used to specify the type of the button.", "annotations": [ { "type": "String", @@ -19,6 +19,22 @@ "name": "text", "required": true }, + { + "type": "String", + "description": "Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.", + "html_description": "Optional. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.", + "rst_description": "*Optional*. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on `Fragment `_ or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.\n", + "name": "icon_custom_emoji_id", + "required": false + }, + { + "type": "String", + "description": "Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.", + "html_description": "Optional. Style of the button. Must be one of “danger” (red), “success” (green) or “primary” (blue). If omitted, then an app-specific style is used.", + "rst_description": "*Optional*. Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.\n", + "name": "style", + "required": false + }, { "type": "String", "description": "HTTP or tg:// URL to be opened when the button is pressed. Links tg://user?id= can be used to mention a user by their identifier without using a username, if this is allowed by their privacy settings.", diff --git a/.butcher/types/KeyboardButton/entity.json b/.butcher/types/KeyboardButton/entity.json index d0eff8d0..16771a3b 100644 --- a/.butcher/types/KeyboardButton/entity.json +++ b/.butcher/types/KeyboardButton/entity.json @@ -7,18 +7,34 @@ "object": { "anchor": "keyboardbutton", "name": "KeyboardButton", - "description": "This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, String can be used instead of this object to specify the button text.\nNote: request_users and request_chat options will only work in Telegram versions released after 3 February, 2023. Older clients will display unsupported message.", - "html_description": "

This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, String can be used instead of this object to specify the button text.

Note: request_users and request_chat options will only work in Telegram versions released after 3 February, 2023. Older clients will display unsupported message.

", - "rst_description": "This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, *String* can be used instead of this object to specify the button text.\n**Note:** *request_users* and *request_chat* options will only work in Telegram versions released after 3 February, 2023. Older clients will display *unsupported message*.", + "description": "This object represents one button of the reply keyboard. At most one of the fields other than text, icon_custom_emoji_id, and style must be used to specify the type of the button. For simple text buttons, String can be used instead of this object to specify the button text.", + "html_description": "

This object represents one button of the reply keyboard. At most one of the fields other than text, icon_custom_emoji_id, and style must be used to specify the type of the button. For simple text buttons, String can be used instead of this object to specify the button text.

", + "rst_description": "This object represents one button of the reply keyboard. At most one of the fields other than *text*, *icon_custom_emoji_id*, and *style* must be used to specify the type of the button. For simple text buttons, *String* can be used instead of this object to specify the button text.", "annotations": [ { "type": "String", - "description": "Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed", - "html_description": "Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed", - "rst_description": "Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed\n", + "description": "Text of the button. If none of the fields other than text, icon_custom_emoji_id, and style are used, it will be sent as a message when the button is pressed", + "html_description": "Text of the button. If none of the fields other than text, icon_custom_emoji_id, and style are used, it will be sent as a message when the button is pressed", + "rst_description": "Text of the button. If none of the fields other than *text*, *icon_custom_emoji_id*, and *style* are used, it will be sent as a message when the button is pressed\n", "name": "text", "required": true }, + { + "type": "String", + "description": "Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.", + "html_description": "Optional. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.", + "rst_description": "*Optional*. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on `Fragment `_ or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.\n", + "name": "icon_custom_emoji_id", + "required": false + }, + { + "type": "String", + "description": "Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.", + "html_description": "Optional. Style of the button. Must be one of “danger” (red), “success” (green) or “primary” (blue). If omitted, then an app-specific style is used.", + "rst_description": "*Optional*. Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.\n", + "name": "style", + "required": false + }, { "type": "KeyboardButtonRequestUsers", "description": "If specified, pressing the button will open a list of suitable users. Identifiers of selected users will be sent to the bot in a 'users_shared' service message. Available in private chats only.", diff --git a/.butcher/types/Message/entity.json b/.butcher/types/Message/entity.json index d4dd8bc0..594442fc 100644 --- a/.butcher/types/Message/entity.json +++ b/.butcher/types/Message/entity.json @@ -443,6 +443,22 @@ "name": "left_chat_member", "required": false }, + { + "type": "ChatOwnerLeft", + "description": "Service message: chat owner has left", + "html_description": "Optional. Service message: chat owner has left", + "rst_description": "*Optional*. Service message: chat owner has left\n", + "name": "chat_owner_left", + "required": false + }, + { + "type": "ChatOwnerChanged", + "description": "Service message: chat owner has changed", + "html_description": "Optional. Service message: chat owner has changed", + "rst_description": "*Optional*. Service message: chat owner has changed\n", + "name": "chat_owner_changed", + "required": false + }, { "type": "String", "description": "A chat title was changed to this value", diff --git a/.butcher/types/UniqueGift/entity.json b/.butcher/types/UniqueGift/entity.json index 9d5f4a50..4b4427ae 100644 --- a/.butcher/types/UniqueGift/entity.json +++ b/.butcher/types/UniqueGift/entity.json @@ -75,6 +75,14 @@ "name": "is_premium", "required": false }, + { + "type": "True", + "description": "True, if the gift was used to craft another gift and isn't available anymore", + "html_description": "Optional. True, if the gift was used to craft another gift and isn't available anymore", + "rst_description": "*Optional*. :code:`True`, if the gift was used to craft another gift and isn't available anymore\n", + "name": "is_burned", + "required": false + }, { "type": "True", "description": "True, if the gift is assigned from the TON blockchain and can't be resold or transferred in Telegram", diff --git a/.butcher/types/UniqueGiftModel/entity.json b/.butcher/types/UniqueGiftModel/entity.json index 4ff33770..6573026a 100644 --- a/.butcher/types/UniqueGiftModel/entity.json +++ b/.butcher/types/UniqueGiftModel/entity.json @@ -29,11 +29,19 @@ }, { "type": "Integer", - "description": "The number of unique gifts that receive this model for every 1000 gifts upgraded", - "html_description": "The number of unique gifts that receive this model for every 1000 gifts upgraded", - "rst_description": "The number of unique gifts that receive this model for every 1000 gifts upgraded\n", + "description": "The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts.", + "html_description": "The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts.", + "rst_description": "The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts.\n", "name": "rarity_per_mille", "required": true + }, + { + "type": "String", + "description": "Rarity of the model if it is a crafted model. Currently, can be 'uncommon', 'rare', 'epic', or 'legendary'.", + "html_description": "Optional. Rarity of the model if it is a crafted model. Currently, can be “uncommon”, “rare”, “epic”, or “legendary”.", + "rst_description": "*Optional*. Rarity of the model if it is a crafted model. Currently, can be 'uncommon', 'rare', 'epic', or 'legendary'.\n", + "name": "rarity", + "required": false } ], "category": "types" diff --git a/.butcher/types/User/aliases.yml b/.butcher/types/User/aliases.yml index 85d2188d..51e3e370 100644 --- a/.butcher/types/User/aliases.yml +++ b/.butcher/types/User/aliases.yml @@ -2,3 +2,7 @@ get_profile_photos: method: getUserProfilePhotos fill: user_id: self.id +get_profile_audios: + method: getUserProfileAudios + fill: + user_id: self.id diff --git a/.butcher/types/User/entity.json b/.butcher/types/User/entity.json index b444dbb3..cead909a 100644 --- a/.butcher/types/User/entity.json +++ b/.butcher/types/User/entity.json @@ -122,6 +122,14 @@ "rst_description": "*Optional*. :code:`True`, if the bot has forum topic mode enabled in private chats. Returned only in :class:`aiogram.methods.get_me.GetMe`.\n", "name": "has_topics_enabled", "required": false + }, + { + "type": "Boolean", + "description": "True, if the bot allows users to create and delete topics in private chats. Returned only in getMe.", + "html_description": "Optional. True, if the bot allows users to create and delete topics in private chats. Returned only in getMe.", + "rst_description": "*Optional*. :code:`True`, if the bot allows users to create and delete topics in private chats. Returned only in :class:`aiogram.methods.get_me.GetMe`.\n", + "name": "allows_users_to_create_topics", + "required": false } ], "category": "types" diff --git a/.butcher/types/UserProfileAudios/entity.json b/.butcher/types/UserProfileAudios/entity.json new file mode 100644 index 00000000..77e373ce --- /dev/null +++ b/.butcher/types/UserProfileAudios/entity.json @@ -0,0 +1,33 @@ +{ + "meta": {}, + "group": { + "title": "Available types", + "anchor": "available-types" + }, + "object": { + "anchor": "userprofileaudios", + "name": "UserProfileAudios", + "description": "This object represents the audios displayed on a user's profile.", + "html_description": "

This object represents the audios displayed on a user's profile.

", + "rst_description": "This object represents the audios displayed on a user's profile.", + "annotations": [ + { + "type": "Integer", + "description": "Total number of profile audios for the target user", + "html_description": "Total number of profile audios for the target user", + "rst_description": "Total number of profile audios for the target user\n", + "name": "total_count", + "required": true + }, + { + "type": "Array of Audio", + "description": "Requested profile audios", + "html_description": "Requested profile audios", + "rst_description": "Requested profile audios\n", + "name": "audios", + "required": true + } + ], + "category": "types" + } +} diff --git a/.butcher/types/Video/entity.json b/.butcher/types/Video/entity.json index d62323ee..41ec3bb6 100644 --- a/.butcher/types/Video/entity.json +++ b/.butcher/types/Video/entity.json @@ -75,6 +75,14 @@ "name": "start_timestamp", "required": false }, + { + "type": "Array of VideoQuality", + "description": "List of available qualities of the video", + "html_description": "Optional. List of available qualities of the video", + "rst_description": "*Optional*. List of available qualities of the video\n", + "name": "qualities", + "required": false + }, { "type": "String", "description": "Original filename as defined by the sender", diff --git a/.butcher/types/VideoQuality/entity.json b/.butcher/types/VideoQuality/entity.json new file mode 100644 index 00000000..0ba0b78a --- /dev/null +++ b/.butcher/types/VideoQuality/entity.json @@ -0,0 +1,65 @@ +{ + "meta": {}, + "group": { + "title": "Available types", + "anchor": "available-types" + }, + "object": { + "anchor": "videoquality", + "name": "VideoQuality", + "description": "This object represents a video file of a specific quality.", + "html_description": "

This object represents a video file of a specific quality.

", + "rst_description": "This object represents a video file of a specific quality.", + "annotations": [ + { + "type": "String", + "description": "Identifier for this file, which can be used to download or reuse the file", + "html_description": "Identifier for this file, which can be used to download or reuse the file", + "rst_description": "Identifier for this file, which can be used to download or reuse the file\n", + "name": "file_id", + "required": true + }, + { + "type": "String", + "description": "Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file.", + "html_description": "Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file.", + "rst_description": "Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file.\n", + "name": "file_unique_id", + "required": true + }, + { + "type": "Integer", + "description": "Video width", + "html_description": "Video width", + "rst_description": "Video width\n", + "name": "width", + "required": true + }, + { + "type": "Integer", + "description": "Video height", + "html_description": "Video height", + "rst_description": "Video height\n", + "name": "height", + "required": true + }, + { + "type": "String", + "description": "Codec that was used to encode the video, for example, 'h264', 'h265', or 'av01'", + "html_description": "Codec that was used to encode the video, for example, “h264”, “h265”, or “av01”", + "rst_description": "Codec that was used to encode the video, for example, 'h264', 'h265', or 'av01'\n", + "name": "codec", + "required": true + }, + { + "type": "Integer", + "description": "File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.", + "html_description": "Optional. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.", + "rst_description": "*Optional*. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.\n", + "name": "file_size", + "required": false + } + ], + "category": "types" + } +} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 122d6b1d..2937cb60 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -72,7 +72,7 @@ jobs: run: | uv run ruff check --output-format=github aiogram examples uv run mypy aiogram - uv run black --check --diff aiogram tests + uv run ruff format --check --diff aiogram tests scripts examples - name: Setup redis if: ${{ env.IS_WINDOWS == 'false' }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 97742762..891f4221 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,13 +13,10 @@ repos: - id: "check-toml" - id: "check-json" - - repo: https://github.com/psf/black - rev: 25.9.0 - hooks: - - id: black - files: &files '^(aiogram|tests|examples)' - - repo: https://github.com/astral-sh/ruff-pre-commit rev: 'v0.14.0' hooks: - id: ruff + files: &files '^(aiogram|tests|examples)' + - id: ruff-format + files: *files diff --git a/CHANGES.rst b/CHANGES.rst index 4369caa7..be4cb56e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -16,6 +16,88 @@ Changelog .. towncrier release notes start +3.25.0 (2026-02-10) +==================== + +Features +-------- + +- Add full_name property to Contact and corresponding tests + `#1758 `_ +- Updated to `Bot API 9.4 (February 9, 2026) `_ + + **New Features:** + + - Bots with Premium subscriptions can now use custom emoji directly in messages to private, group, and supergroup chats + - Bots can create topics in private chats via the :class:`aiogram.methods.create_forum_topic.CreateForumTopic` method + - Bots can prevent users from creating/deleting topics in private chats through BotFather settings + + **New Fields:** + + - Added :code:`allows_users_to_create_topics` field to :class:`aiogram.types.user.User` class - indicates whether the user allows others to create topics in chats with them + - Added :code:`icon_custom_emoji_id` field to :class:`aiogram.types.keyboard_button.KeyboardButton` and :class:`aiogram.types.inline_keyboard_button.InlineKeyboardButton` classes - allows displaying custom emoji icons on buttons + - Added :code:`style` field to :class:`aiogram.types.keyboard_button.KeyboardButton` and :class:`aiogram.types.inline_keyboard_button.InlineKeyboardButton` classes - changes button color/style + - Added :code:`chat_owner_left` field to :class:`aiogram.types.message.Message` class - service message indicating chat owner has left (type: :class:`aiogram.types.chat_owner_left.ChatOwnerLeft`) + - Added :code:`chat_owner_changed` field to :class:`aiogram.types.message.Message` class - service message indicating chat ownership has transferred (type: :class:`aiogram.types.chat_owner_changed.ChatOwnerChanged`) + - Added :code:`qualities` field to :class:`aiogram.types.video.Video` class - list of available video quality options (type: :code:`list[`:class:`aiogram.types.video_quality.VideoQuality`:code:`]`) + - Added :code:`first_profile_audio` field to :class:`aiogram.types.chat_full_info.ChatFullInfo` class - user's first profile audio + - Added :code:`rarity` field to :class:`aiogram.types.unique_gift_model.UniqueGiftModel` class + - Added :code:`is_burned` field to :class:`aiogram.types.unique_gift.UniqueGift` class + + **New Methods:** + + - Added :class:`aiogram.methods.set_my_profile_photo.SetMyProfilePhoto` method - allows bots to set their profile photo + - Added :class:`aiogram.methods.remove_my_profile_photo.RemoveMyProfilePhoto` method - allows bots to remove their profile photo + - Added :class:`aiogram.methods.get_user_profile_audios.GetUserProfileAudios` method - retrieves a user's profile audio list + - Added :meth:`aiogram.types.user.User.get_profile_audios` shortcut - creates a prefilled :class:`aiogram.methods.get_user_profile_audios.GetUserProfileAudios` request with :code:`user_id` + + **New Types:** + + - Added :class:`aiogram.types.chat_owner_left.ChatOwnerLeft` type - describes a service message about the chat owner leaving the chat + - Added :class:`aiogram.types.chat_owner_changed.ChatOwnerChanged` type - describes a service message about an ownership change in the chat + - Added :class:`aiogram.types.video_quality.VideoQuality` type - describes available video quality options + - Added :class:`aiogram.types.user_profile_audios.UserProfileAudios` type - represents the collection of audios displayed on a user's profile + + `#1761 `_ + + +Bugfixes +-------- + +- Fixed scene handling for ``channel_post`` and ``edited_channel_post`` when Scenes are registered but FSM state is unavailable, and added channel-scoped FSM context support for ``CHAT``/``CHAT_TOPIC`` strategies. + `#1743 `_ + + +Misc +---- + +- Migrated from Black and isort to Ruff for code formatting and linting, a modern, blazingly fast formatter and linter written in Rust. + + Enabled additional ruff rule sets. + + **For end users:** + + No changes required. This is purely a development tooling change that doesn't affect the library API or behavior. + + **For contributors:** + + - Use ``make reformat`` or ``uv run ruff format`` to format code (replaces ``black`` and ``isort``) + - Use ``make lint`` to check code quality (now includes formatting, linting, and type checking) + - Pre-commit hooks automatically updated to use ``ruff`` and ``ruff-format`` + - CI/CD pipelines updated to use ruff in GitHub Actions workflows + + **Benefits:** + + - 10-100x faster formatting and linting compared to Black + isort + flake8 + - Single tool for formatting, import sorting, and linting + - More comprehensive code quality checks out of the box + - Auto-fixes for many common issues (33 issues auto-fixed during migration) + - Better integration with modern Python development workflows + + This change improves the developer experience and code quality while maintaining the same code style standards. + `#1750 `_ + + 3.24.0 (2026-01-02) ==================== diff --git a/Makefile b/Makefile index 0c6ad5a3..a92db1f8 100644 --- a/Makefile +++ b/Makefile @@ -37,15 +37,14 @@ install: clean .PHONY: lint lint: - uv run isort --check-only $(code_dir) - uv run black --check --diff $(code_dir) + uv run ruff format --check --diff $(package_dir) uv run ruff check --show-fixes --preview $(package_dir) $(examples_dir) uv run mypy $(package_dir) .PHONY: reformat reformat: - uv run black $(code_dir) - uv run isort $(code_dir) + uv run ruff format $(code_dir) + uv run ruff check --fix $(code_dir) # ================================================================================================= # Tests diff --git a/README.rst b/README.rst index 52322104..c3b660d3 100644 --- a/README.rst +++ b/README.rst @@ -52,7 +52,7 @@ Features - Asynchronous (`asyncio docs `_, :pep:`492`) - Has type hints (:pep:`484`) and can be used with `mypy `_ - Supports `PyPy `_ -- Supports `Telegram Bot API 9.3 `_ and gets fast updates to the latest versions of the Bot API +- Supports `Telegram Bot API 9.4 `_ and gets fast updates to the latest versions of the Bot API - Telegram Bot API integration code was `autogenerated `_ and can be easily re-generated when API gets updated - Updates router (Blueprints) - Has Finite State Machine diff --git a/aiogram/__meta__.py b/aiogram/__meta__.py index 6f449426..0c33f1a9 100644 --- a/aiogram/__meta__.py +++ b/aiogram/__meta__.py @@ -1,2 +1,2 @@ -__version__ = "3.24.0" -__api_version__ = "9.3" +__version__ = "3.25.0" +__api_version__ = "9.4" diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py index fd5ba841..d10429f6 100644 --- a/aiogram/client/bot.py +++ b/aiogram/client/bot.py @@ -2,12 +2,11 @@ from __future__ import annotations import io import pathlib +from collections.abc import AsyncGenerator, AsyncIterator from contextlib import asynccontextmanager from types import TracebackType from typing import ( Any, - AsyncGenerator, - AsyncIterator, BinaryIO, TypeVar, cast, @@ -93,6 +92,7 @@ from ..methods import ( GetUpdates, GetUserChatBoosts, GetUserGifts, + GetUserProfileAudios, GetUserProfilePhotos, GetWebhookInfo, GiftPremiumSubscription, @@ -106,6 +106,7 @@ from ..methods import ( RefundStarPayment, RemoveBusinessAccountProfilePhoto, RemoveChatVerification, + RemoveMyProfilePhoto, RemoveUserVerification, ReopenForumTopic, ReopenGeneralForumTopic, @@ -155,6 +156,7 @@ from ..methods import ( SetMyDefaultAdministratorRights, SetMyDescription, SetMyName, + SetMyProfilePhoto, SetMyShortDescription, SetPassportDataErrors, SetStickerEmojiList, @@ -242,6 +244,7 @@ from ..types import ( Update, User, UserChatBoosts, + UserProfileAudios, UserProfilePhotos, WebhookInfo, ) @@ -283,9 +286,9 @@ class Bot: # Few arguments are completely removed in 3.7.0 version # Temporary solution to raise an error if user passed these arguments # with explanation how to fix it - parse_mode = kwargs.get("parse_mode", None) - link_preview_is_disabled = kwargs.get("disable_web_page_preview", None) - protect_content = kwargs.get("protect_content", None) + parse_mode = kwargs.get("parse_mode") + link_preview_is_disabled = kwargs.get("disable_web_page_preview") + protect_content = kwargs.get("protect_content") if ( parse_mode is not None or link_preview_is_disabled is not None @@ -310,7 +313,7 @@ class Bot: self.__token = token self._me: User | None = None - async def __aenter__(self) -> "Bot": + async def __aenter__(self) -> Bot: return self async def __aexit__( @@ -912,7 +915,7 @@ class Bot: request_timeout: int | None = None, ) -> ForumTopic: """ - Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator rights. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object. + Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator right. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object. Source: https://core.telegram.org/bots/api#createforumtopic @@ -5843,3 +5846,65 @@ class Bot: entities=entities, ) return await self(call, request_timeout=request_timeout) + + async def get_user_profile_audios( + self, + user_id: int, + offset: int | None = None, + limit: int | None = None, + request_timeout: int | None = None, + ) -> UserProfileAudios: + """ + Use this method to get a list of profile audios for a user. Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object. + + Source: https://core.telegram.org/bots/api#getuserprofileaudios + + :param user_id: Unique identifier of the target user + :param offset: Sequential number of the first audio to be returned. By default, all audios are returned. + :param limit: Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100. + :param request_timeout: Request timeout + :return: Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object. + """ + + call = GetUserProfileAudios( + user_id=user_id, + offset=offset, + limit=limit, + ) + return await self(call, request_timeout=request_timeout) + + async def remove_my_profile_photo( + self, + request_timeout: int | None = None, + ) -> bool: + """ + Removes the profile photo of the bot. Requires no parameters. Returns :code:`True` on success. + + Source: https://core.telegram.org/bots/api#removemyprofilephoto + + :param request_timeout: Request timeout + :return: Returns :code:`True` on success. + """ + + call = RemoveMyProfilePhoto() + return await self(call, request_timeout=request_timeout) + + async def set_my_profile_photo( + self, + photo: InputProfilePhotoUnion, + request_timeout: int | None = None, + ) -> bool: + """ + Changes the profile photo of the bot. Returns :code:`True` on success. + + Source: https://core.telegram.org/bots/api#setmyprofilephoto + + :param photo: The new profile photo to set + :param request_timeout: Request timeout + :return: Returns :code:`True` on success. + """ + + call = SetMyProfilePhoto( + photo=photo, + ) + return await self(call, request_timeout=request_timeout) diff --git a/aiogram/client/session/aiohttp.py b/aiogram/client/session/aiohttp.py index 7b032c0e..c1d0b291 100644 --- a/aiogram/client/session/aiohttp.py +++ b/aiogram/client/session/aiohttp.py @@ -58,7 +58,7 @@ def _prepare_connector(chain_or_plain: _ProxyType) -> tuple[type[TCPConnector], # since tuple is Iterable(compatible with _ProxyChain) object, we assume that # user wants chained proxies if tuple is a pair of string(url) and BasicAuth if isinstance(chain_or_plain, str) or ( - isinstance(chain_or_plain, tuple) and len(chain_or_plain) == 2 + isinstance(chain_or_plain, tuple) and len(chain_or_plain) == 2 # noqa: PLR2004 ): chain_or_plain = cast(_ProxyBasic, chain_or_plain) return ProxyConnector, _retrieve_basic(chain_or_plain) @@ -170,10 +170,10 @@ class AiohttpSession(BaseSession): timeout=self.timeout if timeout is None else timeout, ) as resp: raw_result = await resp.text() - except asyncio.TimeoutError: - raise TelegramNetworkError(method=method, message="Request timeout error") + except asyncio.TimeoutError as e: + raise TelegramNetworkError(method=method, message="Request timeout error") from e except ClientError as e: - raise TelegramNetworkError(method=method, message=f"{type(e).__name__}: {e}") + raise TelegramNetworkError(method=method, message=f"{type(e).__name__}: {e}") from e response = self.check_response( bot=bot, method=method, diff --git a/aiogram/client/session/base.py b/aiogram/client/session/base.py index a5eea81e..08c428bd 100644 --- a/aiogram/client/session/base.py +++ b/aiogram/client/session/base.py @@ -90,14 +90,14 @@ class BaseSession(abc.ABC): # in due to decoder can be customized and raise any exception msg = "Failed to decode object" - raise ClientDecodeError(msg, e, content) + raise ClientDecodeError(msg, e, content) from e try: response_type = Response[method.__returning__] # type: ignore response = response_type.model_validate(json_data, context={"bot": bot}) except ValidationError as e: msg = "Failed to deserialize object" - raise ClientDecodeError(msg, e, json_data) + raise ClientDecodeError(msg, e, json_data) from e if HTTPStatus.OK <= status_code <= HTTPStatus.IM_USED and response.ok: return response diff --git a/aiogram/dispatcher/flags.py b/aiogram/dispatcher/flags.py index f7b6a76d..f96d5213 100644 --- a/aiogram/dispatcher/flags.py +++ b/aiogram/dispatcher/flags.py @@ -21,10 +21,10 @@ class FlagDecorator: flag: Flag @classmethod - def _with_flag(cls, flag: Flag) -> "FlagDecorator": + def _with_flag(cls, flag: Flag) -> FlagDecorator: return cls(flag) - def _with_value(self, value: Any) -> "FlagDecorator": + def _with_value(self, value: Any) -> FlagDecorator: new_flag = Flag(self.flag.name, value) return self._with_flag(new_flag) @@ -33,11 +33,11 @@ class FlagDecorator: pass @overload - def __call__(self, value: Any, /) -> "FlagDecorator": + def __call__(self, value: Any, /) -> FlagDecorator: pass @overload - def __call__(self, **kwargs: Any) -> "FlagDecorator": + def __call__(self, **kwargs: Any) -> FlagDecorator: pass def __call__( diff --git a/aiogram/enums/__init__.py b/aiogram/enums/__init__.py index aa9d6b4a..1a6e5326 100644 --- a/aiogram/enums/__init__.py +++ b/aiogram/enums/__init__.py @@ -1,4 +1,5 @@ from .bot_command_scope_type import BotCommandScopeType +from .button_style import ButtonStyle from .chat_action import ChatAction from .chat_boost_source_type import ChatBoostSourceType from .chat_member_status import ChatMemberStatus @@ -36,6 +37,7 @@ from .update_type import UpdateType __all__ = ( "BotCommandScopeType", + "ButtonStyle", "ChatAction", "ChatBoostSourceType", "ChatMemberStatus", diff --git a/aiogram/enums/button_style.py b/aiogram/enums/button_style.py new file mode 100644 index 00000000..8f633e96 --- /dev/null +++ b/aiogram/enums/button_style.py @@ -0,0 +1,15 @@ +from enum import Enum + + +class ButtonStyle(str, Enum): + """ + This object represents a button style (inline- or reply-keyboard). + + Sources: + * https://core.telegram.org/bots/api#inlinekeyboardbutton + * https://core.telegram.org/bots/api#keyboardbutton + """ + + DANGER = "danger" + SUCCESS = "success" + PRIMARY = "primary" diff --git a/aiogram/enums/content_type.py b/aiogram/enums/content_type.py index 0c1c4b29..b2a555d4 100644 --- a/aiogram/enums/content_type.py +++ b/aiogram/enums/content_type.py @@ -28,6 +28,8 @@ class ContentType(str, Enum): LOCATION = "location" NEW_CHAT_MEMBERS = "new_chat_members" LEFT_CHAT_MEMBER = "left_chat_member" + CHAT_OWNER_LEFT = "chat_owner_left" + CHAT_OWNER_CHANGED = "chat_owner_changed" NEW_CHAT_TITLE = "new_chat_title" NEW_CHAT_PHOTO = "new_chat_photo" DELETE_CHAT_PHOTO = "delete_chat_photo" diff --git a/aiogram/filters/chat_member_updated.py b/aiogram/filters/chat_member_updated.py index dc86fe21..c9f18ea3 100644 --- a/aiogram/filters/chat_member_updated.py +++ b/aiogram/filters/chat_member_updated.py @@ -37,7 +37,7 @@ class _MemberStatusMarker: def __or__( self, other: _MemberStatusMarker | _MemberStatusGroupMarker, - ) -> "_MemberStatusGroupMarker": + ) -> _MemberStatusGroupMarker: if isinstance(other, _MemberStatusMarker): return _MemberStatusGroupMarker(self, other) if isinstance(other, _MemberStatusGroupMarker): @@ -53,7 +53,7 @@ class _MemberStatusMarker: def __rshift__( self, other: _MemberStatusMarker | _MemberStatusGroupMarker, - ) -> "_MemberStatusTransition": + ) -> _MemberStatusTransition: old = _MemberStatusGroupMarker(self) if isinstance(other, _MemberStatusMarker): return _MemberStatusTransition(old=old, new=_MemberStatusGroupMarker(other)) @@ -68,7 +68,7 @@ class _MemberStatusMarker: def __lshift__( self, other: _MemberStatusMarker | _MemberStatusGroupMarker, - ) -> "_MemberStatusTransition": + ) -> _MemberStatusTransition: new = _MemberStatusGroupMarker(self) if isinstance(other, _MemberStatusMarker): return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=new) @@ -118,7 +118,7 @@ class _MemberStatusGroupMarker: def __rshift__( self, other: _MemberStatusMarker | _MemberStatusGroupMarker, - ) -> "_MemberStatusTransition": + ) -> _MemberStatusTransition: if isinstance(other, _MemberStatusMarker): return _MemberStatusTransition(old=self, new=_MemberStatusGroupMarker(other)) if isinstance(other, _MemberStatusGroupMarker): @@ -132,7 +132,7 @@ class _MemberStatusGroupMarker: def __lshift__( self, other: _MemberStatusMarker | _MemberStatusGroupMarker, - ) -> "_MemberStatusTransition": + ) -> _MemberStatusTransition: if isinstance(other, _MemberStatusMarker): return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=self) if isinstance(other, _MemberStatusGroupMarker): diff --git a/aiogram/filters/command.py b/aiogram/filters/command.py index cab50969..498cd8c7 100644 --- a/aiogram/filters/command.py +++ b/aiogram/filters/command.py @@ -123,14 +123,15 @@ class Command(Filter): result.update(command.magic_result) return result - def extract_command(self, text: str) -> CommandObject: + @classmethod + def extract_command(cls, text: str) -> CommandObject: # First step: separate command with arguments # "/command@mention arg1 arg2" -> "/command@mention", ["arg1 arg2"] try: full_command, *args = text.split(maxsplit=1) - except ValueError: + except ValueError as e: msg = "not enough values to unpack" - raise CommandException(msg) + raise CommandException(msg) from e # Separate command into valuable parts # "/command@mention" -> "/", ("command", "@", "mention") @@ -292,6 +293,6 @@ class CommandStart(Command): args = decode_payload(args) except UnicodeDecodeError as e: msg = f"Failed to decode Base64: {e}" - raise CommandException(msg) + raise CommandException(msg) from e return replace(command, args=args) return command diff --git a/aiogram/fsm/middleware.py b/aiogram/fsm/middleware.py index 41fd993f..effa3f02 100644 --- a/aiogram/fsm/middleware.py +++ b/aiogram/fsm/middleware.py @@ -70,6 +70,9 @@ class FSMContextMiddleware(BaseMiddleware): ) -> FSMContext | None: if chat_id is None: chat_id = user_id + elif user_id is None and self.strategy in {FSMStrategy.CHAT, FSMStrategy.CHAT_TOPIC}: + # CHAT/CHAT_TOPIC are chat-scoped, so missing user_id can fallback to chat_id. + user_id = chat_id if chat_id is not None and user_id is not None: chat_id, user_id, thread_id = apply_strategy( diff --git a/aiogram/fsm/scene.py b/aiogram/fsm/scene.py index 42b038c5..da0a52d2 100644 --- a/aiogram/fsm/scene.py +++ b/aiogram/fsm/scene.py @@ -120,7 +120,7 @@ class ObserverDecorator: handlers = getattr(target, "__aiogram_handler__", None) if not handlers: handlers = [] - setattr(target, "__aiogram_handler__", handlers) + target.__aiogram_handler__ = handlers # type: ignore[union-attr] handlers.append( HandlerContainer( @@ -137,7 +137,7 @@ class ObserverDecorator: action = getattr(target, "__aiogram_action__", None) if action is None: action = defaultdict(dict) - setattr(target, "__aiogram_action__", action) + target.__aiogram_action__ = action # type: ignore[attr-defined] action[self.action][self.name] = CallableObject(target) def __call__(self, target: CallbackType) -> CallbackType: @@ -248,8 +248,16 @@ class SceneHandlerWrapper: event: TelegramObject, **kwargs: Any, ) -> Any: - state: FSMContext = kwargs["state"] - scenes: ScenesManager = kwargs["scenes"] + try: + state: FSMContext = kwargs["state"] + scenes: ScenesManager = kwargs["scenes"] + except KeyError as error: + missing_key = error.args[0] + msg = ( + f"Scene context key {missing_key!r} is not available. " + "Ensure FSM is enabled and pipeline is intact." + ) + raise SceneException(msg) from None event_update: Update = kwargs["event_update"] scene = self.scene( wizard=SceneWizard( @@ -768,12 +776,15 @@ class SceneRegistry: data: dict[str, Any], ) -> Any: assert isinstance(event, Update), "Event must be an Update instance" + state = data.get("state") + if state is None: + return await handler(event, data) data["scenes"] = ScenesManager( registry=self, update_type=event.event_type, event=event.event, - state=data["state"], + state=state, data=data, ) return await handler(event, data) @@ -784,12 +795,16 @@ class SceneRegistry: event: TelegramObject, data: dict[str, Any], ) -> Any: + state = data.get("state") + if state is None: + return await handler(event, data) + update: Update = data["event_update"] data["scenes"] = ScenesManager( registry=self, update_type=update.event_type, event=event, - state=data["state"], + state=state, data=data, ) return await handler(event, data) @@ -865,7 +880,7 @@ class SceneRegistry: return self._scenes[scene] except KeyError: msg = f"Scene {scene!r} is not registered" - raise SceneException(msg) + raise SceneException(msg) from None @dataclass diff --git a/aiogram/methods/__init__.py b/aiogram/methods/__init__.py index 9c1ebccd..786e53e5 100644 --- a/aiogram/methods/__init__.py +++ b/aiogram/methods/__init__.py @@ -74,6 +74,7 @@ from .get_sticker_set import GetStickerSet from .get_updates import GetUpdates from .get_user_chat_boosts import GetUserChatBoosts from .get_user_gifts import GetUserGifts +from .get_user_profile_audios import GetUserProfileAudios from .get_user_profile_photos import GetUserProfilePhotos from .get_webhook_info import GetWebhookInfo from .gift_premium_subscription import GiftPremiumSubscription @@ -87,6 +88,7 @@ from .read_business_message import ReadBusinessMessage from .refund_star_payment import RefundStarPayment from .remove_business_account_profile_photo import RemoveBusinessAccountProfilePhoto from .remove_chat_verification import RemoveChatVerification +from .remove_my_profile_photo import RemoveMyProfilePhoto from .remove_user_verification import RemoveUserVerification from .reopen_forum_topic import ReopenForumTopic from .reopen_general_forum_topic import ReopenGeneralForumTopic @@ -136,6 +138,7 @@ from .set_my_commands import SetMyCommands from .set_my_default_administrator_rights import SetMyDefaultAdministratorRights from .set_my_description import SetMyDescription from .set_my_name import SetMyName +from .set_my_profile_photo import SetMyProfilePhoto from .set_my_short_description import SetMyShortDescription from .set_passport_data_errors import SetPassportDataErrors from .set_sticker_emoji_list import SetStickerEmojiList @@ -238,6 +241,7 @@ __all__ = ( "GetUpdates", "GetUserChatBoosts", "GetUserGifts", + "GetUserProfileAudios", "GetUserProfilePhotos", "GetWebhookInfo", "GiftPremiumSubscription", @@ -251,6 +255,7 @@ __all__ = ( "RefundStarPayment", "RemoveBusinessAccountProfilePhoto", "RemoveChatVerification", + "RemoveMyProfilePhoto", "RemoveUserVerification", "ReopenForumTopic", "ReopenGeneralForumTopic", @@ -302,6 +307,7 @@ __all__ = ( "SetMyDefaultAdministratorRights", "SetMyDescription", "SetMyName", + "SetMyProfilePhoto", "SetMyShortDescription", "SetPassportDataErrors", "SetStickerEmojiList", diff --git a/aiogram/methods/base.py b/aiogram/methods/base.py index ca97dd7d..fdc1e3f0 100644 --- a/aiogram/methods/base.py +++ b/aiogram/methods/base.py @@ -1,11 +1,11 @@ from __future__ import annotations from abc import ABC, abstractmethod +from collections.abc import Generator from typing import ( TYPE_CHECKING, Any, ClassVar, - Generator, Generic, TypeVar, ) diff --git a/aiogram/methods/create_forum_topic.py b/aiogram/methods/create_forum_topic.py index 0d7940a3..242b67cb 100644 --- a/aiogram/methods/create_forum_topic.py +++ b/aiogram/methods/create_forum_topic.py @@ -8,7 +8,7 @@ from .base import TelegramMethod class CreateForumTopic(TelegramMethod[ForumTopic]): """ - Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator rights. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object. + Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator right. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object. Source: https://core.telegram.org/bots/api#createforumtopic """ diff --git a/aiogram/methods/get_user_profile_audios.py b/aiogram/methods/get_user_profile_audios.py new file mode 100644 index 00000000..ce77de40 --- /dev/null +++ b/aiogram/methods/get_user_profile_audios.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from ..types import UserProfileAudios +from .base import TelegramMethod + + +class GetUserProfileAudios(TelegramMethod[UserProfileAudios]): + """ + Use this method to get a list of profile audios for a user. Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object. + + Source: https://core.telegram.org/bots/api#getuserprofileaudios + """ + + __returning__ = UserProfileAudios + __api_method__ = "getUserProfileAudios" + + user_id: int + """Unique identifier of the target user""" + offset: int | None = None + """Sequential number of the first audio to be returned. By default, all audios are returned.""" + limit: int | None = None + """Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.""" + + if TYPE_CHECKING: + # DO NOT EDIT MANUALLY!!! + # This section was auto-generated via `butcher` + + def __init__( + __pydantic__self__, + *, + user_id: int, + offset: int | None = None, + limit: int | None = None, + **__pydantic_kwargs: Any, + ) -> None: + # DO NOT EDIT MANUALLY!!! + # This method was auto-generated via `butcher` + # Is needed only for type checking and IDE support without any additional plugins + + super().__init__(user_id=user_id, offset=offset, limit=limit, **__pydantic_kwargs) diff --git a/aiogram/methods/remove_my_profile_photo.py b/aiogram/methods/remove_my_profile_photo.py new file mode 100644 index 00000000..c59dca32 --- /dev/null +++ b/aiogram/methods/remove_my_profile_photo.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from .base import TelegramMethod + + +class RemoveMyProfilePhoto(TelegramMethod[bool]): + """ + Removes the profile photo of the bot. Requires no parameters. Returns :code:`True` on success. + + Source: https://core.telegram.org/bots/api#removemyprofilephoto + """ + + __returning__ = bool + __api_method__ = "removeMyProfilePhoto" diff --git a/aiogram/methods/set_my_profile_photo.py b/aiogram/methods/set_my_profile_photo.py new file mode 100644 index 00000000..4f72ec61 --- /dev/null +++ b/aiogram/methods/set_my_profile_photo.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from ..types import InputProfilePhotoUnion +from .base import TelegramMethod + + +class SetMyProfilePhoto(TelegramMethod[bool]): + """ + Changes the profile photo of the bot. Returns :code:`True` on success. + + Source: https://core.telegram.org/bots/api#setmyprofilephoto + """ + + __returning__ = bool + __api_method__ = "setMyProfilePhoto" + + photo: InputProfilePhotoUnion + """The new profile photo to set""" + + if TYPE_CHECKING: + # DO NOT EDIT MANUALLY!!! + # This section was auto-generated via `butcher` + + def __init__( + __pydantic__self__, *, photo: InputProfilePhotoUnion, **__pydantic_kwargs: Any + ) -> None: + # DO NOT EDIT MANUALLY!!! + # This method was auto-generated via `butcher` + # Is needed only for type checking and IDE support without any additional plugins + + super().__init__(photo=photo, **__pydantic_kwargs) diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py index 6fe65642..90e59515 100644 --- a/aiogram/types/__init__.py +++ b/aiogram/types/__init__.py @@ -1,4 +1,4 @@ -from typing import List, Literal, Optional, Union +from typing import Literal, Optional, Union from .accepted_gift_types import AcceptedGiftTypes from .affiliate_info import AffiliateInfo @@ -387,6 +387,8 @@ __all__ = ( "ChatMemberRestricted", "ChatMemberUnion", "ChatMemberUpdated", + "ChatOwnerChanged", + "ChatOwnerLeft", "ChatPermissions", "ChatPhoto", "ChatShared", @@ -627,6 +629,7 @@ __all__ = ( "Update", "User", "UserChatBoosts", + "UserProfileAudios", "UserProfilePhotos", "UserRating", "UserShared", @@ -638,6 +641,7 @@ __all__ = ( "VideoChatScheduled", "VideoChatStarted", "VideoNote", + "VideoQuality", "Voice", "WebAppData", "WebAppInfo", @@ -646,6 +650,10 @@ __all__ = ( ) from ..client.default import Default as _Default +from .chat_owner_changed import ChatOwnerChanged +from .chat_owner_left import ChatOwnerLeft +from .user_profile_audios import UserProfileAudios +from .video_quality import VideoQuality # Load typing forward refs for every TelegramObject for _entity_name in __all__: @@ -654,7 +662,7 @@ for _entity_name in __all__: continue _entity.model_rebuild( _types_namespace={ - "List": List, + "List": list, "Optional": Optional, "Union": Union, "Literal": Literal, diff --git a/aiogram/types/chat_full_info.py b/aiogram/types/chat_full_info.py index 9505aebf..93fb0a00 100644 --- a/aiogram/types/chat_full_info.py +++ b/aiogram/types/chat_full_info.py @@ -9,6 +9,7 @@ from .custom import DateTime if TYPE_CHECKING: from .accepted_gift_types import AcceptedGiftTypes + from .audio import Audio from .birthdate import Birthdate from .business_intro import BusinessIntro from .business_location import BusinessLocation @@ -125,6 +126,8 @@ class ChatFullInfo(Chat): """*Optional*. For supergroups, the location to which the supergroup is connected""" rating: UserRating | None = None """*Optional*. For private chats, the rating of the user if any""" + first_profile_audio: Audio | None = None + """*Optional*. For private chats, the first audio added to the profile of the user""" unique_gift_colors: UniqueGiftColors | None = None """*Optional*. The color scheme based on a unique gift that must be used for the chat's name, message replies and link previews""" paid_message_star_count: int | None = None @@ -190,6 +193,7 @@ class ChatFullInfo(Chat): linked_chat_id: int | None = None, location: ChatLocation | None = None, rating: UserRating | None = None, + first_profile_audio: Audio | None = None, unique_gift_colors: UniqueGiftColors | None = None, paid_message_star_count: int | None = None, can_send_gift: bool | None = None, @@ -248,6 +252,7 @@ class ChatFullInfo(Chat): linked_chat_id=linked_chat_id, location=location, rating=rating, + first_profile_audio=first_profile_audio, unique_gift_colors=unique_gift_colors, paid_message_star_count=paid_message_star_count, can_send_gift=can_send_gift, diff --git a/aiogram/types/chat_owner_changed.py b/aiogram/types/chat_owner_changed.py new file mode 100644 index 00000000..1b9bc00f --- /dev/null +++ b/aiogram/types/chat_owner_changed.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from .base import TelegramObject + +if TYPE_CHECKING: + from .user import User + + +class ChatOwnerChanged(TelegramObject): + """ + Describes a service message about an ownership change in the chat. + + Source: https://core.telegram.org/bots/api#chatownerchanged + """ + + new_owner: User + """The new owner of the chat""" + + if TYPE_CHECKING: + # DO NOT EDIT MANUALLY!!! + # This section was auto-generated via `butcher` + + def __init__(__pydantic__self__, *, new_owner: User, **__pydantic_kwargs: Any) -> None: + # DO NOT EDIT MANUALLY!!! + # This method was auto-generated via `butcher` + # Is needed only for type checking and IDE support without any additional plugins + + super().__init__(new_owner=new_owner, **__pydantic_kwargs) diff --git a/aiogram/types/chat_owner_left.py b/aiogram/types/chat_owner_left.py new file mode 100644 index 00000000..8c9af97d --- /dev/null +++ b/aiogram/types/chat_owner_left.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from .base import TelegramObject + +if TYPE_CHECKING: + from .user import User + + +class ChatOwnerLeft(TelegramObject): + """ + Describes a service message about the chat owner leaving the chat. + + Source: https://core.telegram.org/bots/api#chatownerleft + """ + + new_owner: User | None = None + """*Optional*. The user which will be the new owner of the chat if the previous owner does not return to the chat""" + + if TYPE_CHECKING: + # DO NOT EDIT MANUALLY!!! + # This section was auto-generated via `butcher` + + def __init__( + __pydantic__self__, *, new_owner: User | None = None, **__pydantic_kwargs: Any + ) -> None: + # DO NOT EDIT MANUALLY!!! + # This method was auto-generated via `butcher` + # Is needed only for type checking and IDE support without any additional plugins + + super().__init__(new_owner=new_owner, **__pydantic_kwargs) diff --git a/aiogram/types/contact.py b/aiogram/types/contact.py index acc53765..9e7677a4 100644 --- a/aiogram/types/contact.py +++ b/aiogram/types/contact.py @@ -49,3 +49,9 @@ class Contact(TelegramObject): vcard=vcard, **__pydantic_kwargs, ) + + @property + def full_name(self) -> str: + if self.last_name: + return f"{self.first_name} {self.last_name}" + return self.first_name diff --git a/aiogram/types/custom.py b/aiogram/types/custom.py index 8617fa64..660c0be7 100644 --- a/aiogram/types/custom.py +++ b/aiogram/types/custom.py @@ -1,8 +1,8 @@ import sys from datetime import datetime, timezone +from typing import Annotated from pydantic import PlainSerializer -from typing_extensions import Annotated if sys.platform == "win32": # pragma: no cover diff --git a/aiogram/types/game_high_score.py b/aiogram/types/game_high_score.py index 5364be6e..e8fdcf40 100644 --- a/aiogram/types/game_high_score.py +++ b/aiogram/types/game_high_score.py @@ -15,6 +15,8 @@ class GameHighScore(TelegramObject): If you've got any questions, please check out our `https://core.telegram.org/bots/faq `_ **Bot FAQ »** + - + Source: https://core.telegram.org/bots/api#gamehighscore """ diff --git a/aiogram/types/inaccessible_message.py b/aiogram/types/inaccessible_message.py index 30c48a70..8b8a9f1f 100644 --- a/aiogram/types/inaccessible_message.py +++ b/aiogram/types/inaccessible_message.py @@ -130,9 +130,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendMessage - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendMessage( chat_id=self.chat.id, @@ -208,9 +208,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendMessage - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendMessage( chat_id=self.chat.id, @@ -298,9 +298,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendAnimation - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendAnimation( chat_id=self.chat.id, @@ -391,9 +391,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendAnimation - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendAnimation( chat_id=self.chat.id, @@ -483,9 +483,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendAudio - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendAudio( chat_id=self.chat.id, @@ -571,9 +571,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendAudio - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendAudio( chat_id=self.chat.id, @@ -652,9 +652,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendContact - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendContact( chat_id=self.chat.id, @@ -727,9 +727,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendContact - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendContact( chat_id=self.chat.id, @@ -808,9 +808,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendDocument - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendDocument( chat_id=self.chat.id, @@ -889,9 +889,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendDocument - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendDocument( chat_id=self.chat.id, @@ -958,9 +958,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendGame - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendGame( chat_id=self.chat.id, @@ -1018,9 +1018,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendGame - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendGame( chat_id=self.chat.id, @@ -1122,9 +1122,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendInvoice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendInvoice( chat_id=self.chat.id, @@ -1245,9 +1245,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendInvoice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendInvoice( chat_id=self.chat.id, @@ -1342,9 +1342,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendLocation - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendLocation( chat_id=self.chat.id, @@ -1423,9 +1423,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendLocation - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendLocation( chat_id=self.chat.id, @@ -1492,9 +1492,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendMediaGroup - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendMediaGroup( chat_id=self.chat.id, @@ -1552,9 +1552,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendMediaGroup - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendMediaGroup( chat_id=self.chat.id, @@ -1628,9 +1628,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendPhoto - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPhoto( chat_id=self.chat.id, @@ -1709,9 +1709,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendPhoto - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPhoto( chat_id=self.chat.id, @@ -1804,9 +1804,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendPoll - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPoll( chat_id=self.chat.id, @@ -1903,9 +1903,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendPoll - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPoll( chat_id=self.chat.id, @@ -1982,9 +1982,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendDice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendDice( chat_id=self.chat.id, @@ -2048,9 +2048,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendDice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendDice( chat_id=self.chat.id, @@ -2118,9 +2118,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendSticker - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendSticker( chat_id=self.chat.id, @@ -2187,9 +2187,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendSticker - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendSticker( chat_id=self.chat.id, @@ -2270,9 +2270,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendVenue - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVenue( chat_id=self.chat.id, @@ -2357,9 +2357,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendVenue - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVenue( chat_id=self.chat.id, @@ -2456,9 +2456,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendVideo - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVideo( chat_id=self.chat.id, @@ -2558,9 +2558,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendVideo - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVideo( chat_id=self.chat.id, @@ -2644,9 +2644,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendVideoNote - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVideoNote( chat_id=self.chat.id, @@ -2719,9 +2719,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendVideoNote - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVideoNote( chat_id=self.chat.id, @@ -2798,9 +2798,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendVoice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVoice( chat_id=self.chat.id, @@ -2876,9 +2876,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendVoice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVoice( chat_id=self.chat.id, @@ -2954,9 +2954,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendPaidMedia - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPaidMedia( chat_id=self.chat.id, @@ -3031,9 +3031,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage): from aiogram.methods import SendPaidMedia - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPaidMedia( chat_id=self.chat.id, diff --git a/aiogram/types/inline_keyboard_button.py b/aiogram/types/inline_keyboard_button.py index bc4e934f..f85418cd 100644 --- a/aiogram/types/inline_keyboard_button.py +++ b/aiogram/types/inline_keyboard_button.py @@ -14,13 +14,17 @@ if TYPE_CHECKING: class InlineKeyboardButton(MutableTelegramObject): """ - This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button. + This object represents one button of an inline keyboard. Exactly one of the fields other than *text*, *icon_custom_emoji_id*, and *style* must be used to specify the type of the button. Source: https://core.telegram.org/bots/api#inlinekeyboardbutton """ text: str """Label text on the button""" + icon_custom_emoji_id: str | None = None + """*Optional*. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on `Fragment `_ or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.""" + style: str | None = None + """*Optional*. Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.""" url: str | None = None """*Optional*. HTTP or tg:// URL to be opened when the button is pressed. Links :code:`tg://user?id=` can be used to mention a user by their identifier without using a username, if this is allowed by their privacy settings.""" callback_data: str | None = None @@ -50,6 +54,8 @@ class InlineKeyboardButton(MutableTelegramObject): __pydantic__self__, *, text: str, + icon_custom_emoji_id: str | None = None, + style: str | None = None, url: str | None = None, callback_data: str | None = None, web_app: WebAppInfo | None = None, @@ -68,6 +74,8 @@ class InlineKeyboardButton(MutableTelegramObject): super().__init__( text=text, + icon_custom_emoji_id=icon_custom_emoji_id, + style=style, url=url, callback_data=callback_data, web_app=web_app, diff --git a/aiogram/types/input_file.py b/aiogram/types/input_file.py index 07cc0765..b8df230e 100644 --- a/aiogram/types/input_file.py +++ b/aiogram/types/input_file.py @@ -3,8 +3,9 @@ from __future__ import annotations import io import os from abc import ABC, abstractmethod +from collections.abc import AsyncGenerator from pathlib import Path -from typing import TYPE_CHECKING, Any, AsyncGenerator +from typing import TYPE_CHECKING, Any import aiofiles @@ -33,7 +34,7 @@ class InputFile(ABC): self.chunk_size = chunk_size @abstractmethod - async def read(self, bot: "Bot") -> AsyncGenerator[bytes, None]: # pragma: no cover + async def read(self, bot: Bot) -> AsyncGenerator[bytes, None]: # pragma: no cover yield b"" @@ -72,7 +73,7 @@ class BufferedInputFile(InputFile): data = f.read() return cls(data, filename=filename, chunk_size=chunk_size) - async def read(self, bot: "Bot") -> AsyncGenerator[bytes, None]: + async def read(self, bot: Bot) -> AsyncGenerator[bytes, None]: buffer = io.BytesIO(self.data) while chunk := buffer.read(self.chunk_size): yield chunk @@ -99,7 +100,7 @@ class FSInputFile(InputFile): self.path = path - async def read(self, bot: "Bot") -> AsyncGenerator[bytes, None]: + async def read(self, bot: Bot) -> AsyncGenerator[bytes, None]: async with aiofiles.open(self.path, "rb") as f: while chunk := await f.read(self.chunk_size): yield chunk @@ -135,7 +136,7 @@ class URLInputFile(InputFile): self.timeout = timeout self.bot = bot - async def read(self, bot: "Bot") -> AsyncGenerator[bytes, None]: + async def read(self, bot: Bot) -> AsyncGenerator[bytes, None]: bot = self.bot or bot stream = bot.session.stream_content( url=self.url, diff --git a/aiogram/types/keyboard_button.py b/aiogram/types/keyboard_button.py index 0635476e..f0c4af22 100644 --- a/aiogram/types/keyboard_button.py +++ b/aiogram/types/keyboard_button.py @@ -16,14 +16,17 @@ if TYPE_CHECKING: class KeyboardButton(MutableTelegramObject): """ - This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, *String* can be used instead of this object to specify the button text. - **Note:** *request_users* and *request_chat* options will only work in Telegram versions released after 3 February, 2023. Older clients will display *unsupported message*. + This object represents one button of the reply keyboard. At most one of the fields other than *text*, *icon_custom_emoji_id*, and *style* must be used to specify the type of the button. For simple text buttons, *String* can be used instead of this object to specify the button text. Source: https://core.telegram.org/bots/api#keyboardbutton """ text: str - """Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed""" + """Text of the button. If none of the fields other than *text*, *icon_custom_emoji_id*, and *style* are used, it will be sent as a message when the button is pressed""" + icon_custom_emoji_id: str | None = None + """*Optional*. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on `Fragment `_ or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.""" + style: str | None = None + """*Optional*. Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.""" request_users: KeyboardButtonRequestUsers | None = None """*Optional*. If specified, pressing the button will open a list of suitable users. Identifiers of selected users will be sent to the bot in a 'users_shared' service message. Available in private chats only.""" request_chat: KeyboardButtonRequestChat | None = None @@ -52,6 +55,8 @@ class KeyboardButton(MutableTelegramObject): __pydantic__self__, *, text: str, + icon_custom_emoji_id: str | None = None, + style: str | None = None, request_users: KeyboardButtonRequestUsers | None = None, request_chat: KeyboardButtonRequestChat | None = None, request_contact: bool | None = None, @@ -67,6 +72,8 @@ class KeyboardButton(MutableTelegramObject): super().__init__( text=text, + icon_custom_emoji_id=icon_custom_emoji_id, + style=style, request_users=request_users, request_chat=request_chat, request_contact=request_contact, diff --git a/aiogram/types/message.py b/aiogram/types/message.py index f740e224..b35fe0fb 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -55,6 +55,8 @@ if TYPE_CHECKING: from .chat_background import ChatBackground from .chat_boost_added import ChatBoostAdded from .chat_id_union import ChatIdUnion + from .chat_owner_changed import ChatOwnerChanged + from .chat_owner_left import ChatOwnerLeft from .chat_shared import ChatShared from .checklist import Checklist from .checklist_tasks_added import ChecklistTasksAdded @@ -245,6 +247,10 @@ class Message(MaybeInaccessibleMessage): """*Optional*. New members that were added to the group or supergroup and information about them (the bot itself may be one of these members)""" left_chat_member: User | None = None """*Optional*. A member was removed from the group, information about them (this member may be the bot itself)""" + chat_owner_left: ChatOwnerLeft | None = None + """*Optional*. Service message: chat owner has left""" + chat_owner_changed: ChatOwnerChanged | None = None + """*Optional*. Service message: chat owner has changed""" new_chat_title: str | None = None """*Optional*. A chat title was changed to this value""" new_chat_photo: list[PhotoSize] | None = None @@ -440,6 +446,8 @@ class Message(MaybeInaccessibleMessage): location: Location | None = None, new_chat_members: list[User] | None = None, left_chat_member: User | None = None, + chat_owner_left: ChatOwnerLeft | None = None, + chat_owner_changed: ChatOwnerChanged | None = None, new_chat_title: str | None = None, new_chat_photo: list[PhotoSize] | None = None, delete_chat_photo: bool | None = None, @@ -557,6 +565,8 @@ class Message(MaybeInaccessibleMessage): location=location, new_chat_members=new_chat_members, left_chat_member=left_chat_member, + chat_owner_left=chat_owner_left, + chat_owner_changed=chat_owner_changed, new_chat_title=new_chat_title, new_chat_photo=new_chat_photo, delete_chat_photo=delete_chat_photo, @@ -650,6 +660,10 @@ class Message(MaybeInaccessibleMessage): return ContentType.NEW_CHAT_MEMBERS if self.left_chat_member: return ContentType.LEFT_CHAT_MEMBER + if self.chat_owner_left: + return ContentType.CHAT_OWNER_LEFT + if self.chat_owner_changed: + return ContentType.CHAT_OWNER_CHANGED if self.invoice: return ContentType.INVOICE if self.successful_payment: @@ -851,9 +865,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendAnimation - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendAnimation( chat_id=self.chat.id, @@ -944,9 +958,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendAnimation - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendAnimation( chat_id=self.chat.id, @@ -1032,9 +1046,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendAudio - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendAudio( chat_id=self.chat.id, @@ -1120,9 +1134,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendAudio - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendAudio( chat_id=self.chat.id, @@ -1197,9 +1211,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendContact - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendContact( chat_id=self.chat.id, @@ -1272,9 +1286,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendContact - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendContact( chat_id=self.chat.id, @@ -1349,9 +1363,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendDocument - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendDocument( chat_id=self.chat.id, @@ -1430,9 +1444,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendDocument - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendDocument( chat_id=self.chat.id, @@ -1495,9 +1509,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendGame - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendGame( chat_id=self.chat.id, @@ -1555,9 +1569,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendGame - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendGame( chat_id=self.chat.id, @@ -1657,9 +1671,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendInvoice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendInvoice( chat_id=self.chat.id, @@ -1783,9 +1797,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendInvoice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendInvoice( chat_id=self.chat.id, @@ -1877,9 +1891,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendLocation - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendLocation( chat_id=self.chat.id, @@ -1958,9 +1972,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendLocation - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendLocation( chat_id=self.chat.id, @@ -2023,9 +2037,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendMediaGroup - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendMediaGroup( chat_id=self.chat.id, @@ -2083,9 +2097,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendMediaGroup - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendMediaGroup( chat_id=self.chat.id, @@ -2153,9 +2167,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendMessage - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendMessage( chat_id=self.chat.id, @@ -2231,9 +2245,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendMessage - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendMessage( chat_id=self.chat.id, @@ -2309,9 +2323,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendPhoto - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPhoto( chat_id=self.chat.id, @@ -2390,9 +2404,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendPhoto - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPhoto( chat_id=self.chat.id, @@ -2481,9 +2495,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendPoll - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPoll( chat_id=self.chat.id, @@ -2580,9 +2594,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendPoll - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPoll( chat_id=self.chat.id, @@ -2655,9 +2669,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendDice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendDice( chat_id=self.chat.id, @@ -2721,9 +2735,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendDice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendDice( chat_id=self.chat.id, @@ -2787,9 +2801,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendSticker - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendSticker( chat_id=self.chat.id, @@ -2856,9 +2870,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendSticker - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendSticker( chat_id=self.chat.id, @@ -2935,9 +2949,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendVenue - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVenue( chat_id=self.chat.id, @@ -3022,9 +3036,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendVenue - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVenue( chat_id=self.chat.id, @@ -3117,9 +3131,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendVideo - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVideo( chat_id=self.chat.id, @@ -3219,9 +3233,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendVideo - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVideo( chat_id=self.chat.id, @@ -3301,9 +3315,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendVideoNote - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVideoNote( chat_id=self.chat.id, @@ -3376,9 +3390,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendVideoNote - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVideoNote( chat_id=self.chat.id, @@ -3451,9 +3465,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendVoice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVoice( chat_id=self.chat.id, @@ -3529,9 +3543,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendVoice - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendVoice( chat_id=self.chat.id, @@ -3808,9 +3822,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import CopyMessage - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return CopyMessage( from_chat_id=self.chat.id, @@ -3872,9 +3886,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import EditMessageText - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return EditMessageText( chat_id=self.chat.id, @@ -3928,9 +3942,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import ForwardMessage - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return ForwardMessage( from_chat_id=self.chat.id, @@ -3975,9 +3989,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import EditMessageMedia - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return EditMessageMedia( chat_id=self.chat.id, @@ -4016,9 +4030,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import EditMessageReplyMarkup - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return EditMessageReplyMarkup( chat_id=self.chat.id, @@ -4055,9 +4069,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import EditMessageReplyMarkup - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return EditMessageReplyMarkup( chat_id=self.chat.id, @@ -4107,9 +4121,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import EditMessageLiveLocation - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return EditMessageLiveLocation( chat_id=self.chat.id, @@ -4153,9 +4167,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import StopMessageLiveLocation - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return StopMessageLiveLocation( chat_id=self.chat.id, @@ -4201,9 +4215,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import EditMessageCaption - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return EditMessageCaption( chat_id=self.chat.id, @@ -4261,9 +4275,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import DeleteMessage - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return DeleteMessage( chat_id=self.chat.id, @@ -4297,9 +4311,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import PinChatMessage - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return PinChatMessage( chat_id=self.chat.id, @@ -4332,9 +4346,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import UnpinChatMessage - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return UnpinChatMessage( chat_id=self.chat.id, @@ -4353,7 +4367,7 @@ class Message(MaybeInaccessibleMessage): :param include_thread_id: if set, adds chat thread id to URL and returns like https://t.me/username/thread_id/message_id :return: string with full message URL """ - if self.chat.type in ("private", "group"): + if self.chat.type in {"private", "group"}: return None chat_value = ( @@ -4397,9 +4411,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SetMessageReaction - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SetMessageReaction( chat_id=self.chat.id, @@ -4461,9 +4475,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendPaidMedia - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPaidMedia( chat_id=self.chat.id, @@ -4536,9 +4550,9 @@ class Message(MaybeInaccessibleMessage): from aiogram.methods import SendPaidMedia - assert ( - self.chat is not None - ), "This method can be used only if chat is present in the message." + assert self.chat is not None, ( + "This method can be used only if chat is present in the message." + ) return SendPaidMedia( chat_id=self.chat.id, diff --git a/aiogram/types/unique_gift.py b/aiogram/types/unique_gift.py index 586e79a6..1b23337c 100644 --- a/aiogram/types/unique_gift.py +++ b/aiogram/types/unique_gift.py @@ -35,6 +35,8 @@ class UniqueGift(TelegramObject): """Backdrop of the gift""" is_premium: bool | None = None """*Optional*. :code:`True`, if the original regular gift was exclusively purchaseable by Telegram Premium subscribers""" + is_burned: bool | None = None + """*Optional*. :code:`True`, if the gift was used to craft another gift and isn't available anymore""" is_from_blockchain: bool | None = None """*Optional*. :code:`True`, if the gift is assigned from the TON blockchain and can't be resold or transferred in Telegram""" colors: UniqueGiftColors | None = None @@ -57,6 +59,7 @@ class UniqueGift(TelegramObject): symbol: UniqueGiftSymbol, backdrop: UniqueGiftBackdrop, is_premium: bool | None = None, + is_burned: bool | None = None, is_from_blockchain: bool | None = None, colors: UniqueGiftColors | None = None, publisher_chat: Chat | None = None, @@ -75,6 +78,7 @@ class UniqueGift(TelegramObject): symbol=symbol, backdrop=backdrop, is_premium=is_premium, + is_burned=is_burned, is_from_blockchain=is_from_blockchain, colors=colors, publisher_chat=publisher_chat, diff --git a/aiogram/types/unique_gift_model.py b/aiogram/types/unique_gift_model.py index 7a87f032..17faedfd 100644 --- a/aiogram/types/unique_gift_model.py +++ b/aiogram/types/unique_gift_model.py @@ -20,7 +20,9 @@ class UniqueGiftModel(TelegramObject): sticker: Sticker """The sticker that represents the unique gift""" rarity_per_mille: int - """The number of unique gifts that receive this model for every 1000 gifts upgraded""" + """The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts.""" + rarity: str | None = None + """*Optional*. Rarity of the model if it is a crafted model. Currently, can be 'uncommon', 'rare', 'epic', or 'legendary'.""" if TYPE_CHECKING: # DO NOT EDIT MANUALLY!!! @@ -32,6 +34,7 @@ class UniqueGiftModel(TelegramObject): name: str, sticker: Sticker, rarity_per_mille: int, + rarity: str | None = None, **__pydantic_kwargs: Any, ) -> None: # DO NOT EDIT MANUALLY!!! @@ -39,5 +42,9 @@ class UniqueGiftModel(TelegramObject): # Is needed only for type checking and IDE support without any additional plugins super().__init__( - name=name, sticker=sticker, rarity_per_mille=rarity_per_mille, **__pydantic_kwargs + name=name, + sticker=sticker, + rarity_per_mille=rarity_per_mille, + rarity=rarity, + **__pydantic_kwargs, ) diff --git a/aiogram/types/user.py b/aiogram/types/user.py index 0dfb7ee2..7f03504c 100644 --- a/aiogram/types/user.py +++ b/aiogram/types/user.py @@ -7,7 +7,7 @@ from ..utils.link import create_tg_link from .base import TelegramObject if TYPE_CHECKING: - from ..methods import GetUserProfilePhotos + from ..methods import GetUserProfileAudios, GetUserProfilePhotos class User(TelegramObject): @@ -45,6 +45,8 @@ class User(TelegramObject): """*Optional*. :code:`True`, if the bot has a main Web App. Returned only in :class:`aiogram.methods.get_me.GetMe`.""" has_topics_enabled: bool | None = None """*Optional*. :code:`True`, if the bot has forum topic mode enabled in private chats. Returned only in :class:`aiogram.methods.get_me.GetMe`.""" + allows_users_to_create_topics: bool | None = None + """*Optional*. :code:`True`, if the bot allows users to create and delete topics in private chats. Returned only in :class:`aiogram.methods.get_me.GetMe`.""" if TYPE_CHECKING: # DO NOT EDIT MANUALLY!!! @@ -67,6 +69,7 @@ class User(TelegramObject): can_connect_to_business: bool | None = None, has_main_web_app: bool | None = None, has_topics_enabled: bool | None = None, + allows_users_to_create_topics: bool | None = None, **__pydantic_kwargs: Any, ) -> None: # DO NOT EDIT MANUALLY!!! @@ -88,6 +91,7 @@ class User(TelegramObject): can_connect_to_business=can_connect_to_business, has_main_web_app=has_main_web_app, has_topics_enabled=has_topics_enabled, + allows_users_to_create_topics=allows_users_to_create_topics, **__pydantic_kwargs, ) @@ -142,3 +146,35 @@ class User(TelegramObject): limit=limit, **kwargs, ).as_(self._bot) + + def get_profile_audios( + self, + offset: int | None = None, + limit: int | None = None, + **kwargs: Any, + ) -> GetUserProfileAudios: + """ + Shortcut for method :class:`aiogram.methods.get_user_profile_audios.GetUserProfileAudios` + will automatically fill method attributes: + + - :code:`user_id` + + Use this method to get a list of profile audios for a user. Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object. + + Source: https://core.telegram.org/bots/api#getuserprofileaudios + + :param offset: Sequential number of the first audio to be returned. By default, all audios are returned. + :param limit: Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100. + :return: instance of method :class:`aiogram.methods.get_user_profile_audios.GetUserProfileAudios` + """ + # DO NOT EDIT MANUALLY!!! + # This method was auto-generated via `butcher` + + from aiogram.methods import GetUserProfileAudios + + return GetUserProfileAudios( + user_id=self.id, + offset=offset, + limit=limit, + **kwargs, + ).as_(self._bot) diff --git a/aiogram/types/user_profile_audios.py b/aiogram/types/user_profile_audios.py new file mode 100644 index 00000000..a8f7eb9a --- /dev/null +++ b/aiogram/types/user_profile_audios.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from .base import TelegramObject + +if TYPE_CHECKING: + from .audio import Audio + + +class UserProfileAudios(TelegramObject): + """ + This object represents the audios displayed on a user's profile. + + Source: https://core.telegram.org/bots/api#userprofileaudios + """ + + total_count: int + """Total number of profile audios for the target user""" + audios: list[Audio] + """Requested profile audios""" + + if TYPE_CHECKING: + # DO NOT EDIT MANUALLY!!! + # This section was auto-generated via `butcher` + + def __init__( + __pydantic__self__, *, total_count: int, audios: list[Audio], **__pydantic_kwargs: Any + ) -> None: + # DO NOT EDIT MANUALLY!!! + # This method was auto-generated via `butcher` + # Is needed only for type checking and IDE support without any additional plugins + + super().__init__(total_count=total_count, audios=audios, **__pydantic_kwargs) diff --git a/aiogram/types/video.py b/aiogram/types/video.py index a13c9c96..1674f6c2 100644 --- a/aiogram/types/video.py +++ b/aiogram/types/video.py @@ -7,6 +7,7 @@ from .base import TelegramObject if TYPE_CHECKING: from .photo_size import PhotoSize + from .video_quality import VideoQuality class Video(TelegramObject): @@ -32,6 +33,8 @@ class Video(TelegramObject): """*Optional*. Available sizes of the cover of the video in the message""" start_timestamp: datetime.datetime | None = None """*Optional*. Timestamp in seconds from which the video will play in the message""" + qualities: list[VideoQuality] | None = None + """*Optional*. List of available qualities of the video""" file_name: str | None = None """*Optional*. Original filename as defined by the sender""" mime_type: str | None = None @@ -54,6 +57,7 @@ class Video(TelegramObject): thumbnail: PhotoSize | None = None, cover: list[PhotoSize] | None = None, start_timestamp: datetime.datetime | None = None, + qualities: list[VideoQuality] | None = None, file_name: str | None = None, mime_type: str | None = None, file_size: int | None = None, @@ -72,6 +76,7 @@ class Video(TelegramObject): thumbnail=thumbnail, cover=cover, start_timestamp=start_timestamp, + qualities=qualities, file_name=file_name, mime_type=mime_type, file_size=file_size, diff --git a/aiogram/types/video_quality.py b/aiogram/types/video_quality.py new file mode 100644 index 00000000..9b17e79c --- /dev/null +++ b/aiogram/types/video_quality.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from .base import TelegramObject + + +class VideoQuality(TelegramObject): + """ + This object represents a video file of a specific quality. + + Source: https://core.telegram.org/bots/api#videoquality + """ + + file_id: str + """Identifier for this file, which can be used to download or reuse the file""" + file_unique_id: str + """Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file.""" + width: int + """Video width""" + height: int + """Video height""" + codec: str + """Codec that was used to encode the video, for example, 'h264', 'h265', or 'av01'""" + file_size: int | None = None + """*Optional*. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.""" + + if TYPE_CHECKING: + # DO NOT EDIT MANUALLY!!! + # This section was auto-generated via `butcher` + + def __init__( + __pydantic__self__, + *, + file_id: str, + file_unique_id: str, + width: int, + height: int, + codec: str, + file_size: int | None = None, + **__pydantic_kwargs: Any, + ) -> None: + # DO NOT EDIT MANUALLY!!! + # This method was auto-generated via `butcher` + # Is needed only for type checking and IDE support without any additional plugins + + super().__init__( + file_id=file_id, + file_unique_id=file_unique_id, + width=width, + height=height, + codec=codec, + file_size=file_size, + **__pydantic_kwargs, + ) diff --git a/aiogram/utils/deep_linking.py b/aiogram/utils/deep_linking.py index 4892ca5a..1de7581e 100644 --- a/aiogram/utils/deep_linking.py +++ b/aiogram/utils/deep_linking.py @@ -22,6 +22,7 @@ if TYPE_CHECKING: from aiogram import Bot BAD_PATTERN = re.compile(r"[^a-zA-Z0-9-_]") +DEEPLINK_PAYLOAD_LENGTH = 64 async def create_start_link( @@ -145,8 +146,8 @@ def create_deep_link( ) raise ValueError(msg) - if len(payload) > 64: - msg = "Payload must be up to 64 characters long." + if len(payload) > DEEPLINK_PAYLOAD_LENGTH: + msg = f"Payload must be up to {DEEPLINK_PAYLOAD_LENGTH} characters long." raise ValueError(msg) if not app_name: diff --git a/aiogram/utils/mixins.py b/aiogram/utils/mixins.py index 15d94cd9..70d90cc4 100644 --- a/aiogram/utils/mixins.py +++ b/aiogram/utils/mixins.py @@ -15,7 +15,7 @@ class DataMixin: data: dict[str, Any] | None = getattr(self, "_data", None) if data is None: data = {} - setattr(self, "_data", data) + self._data = data return data def __getitem__(self, key: str) -> Any: diff --git a/docs/api/enums/button_style.rst b/docs/api/enums/button_style.rst new file mode 100644 index 00000000..d9ac08ca --- /dev/null +++ b/docs/api/enums/button_style.rst @@ -0,0 +1,9 @@ +########### +ButtonStyle +########### + + +.. automodule:: aiogram.enums.button_style + :members: + :member-order: bysource + :undoc-members: True diff --git a/docs/api/enums/index.rst b/docs/api/enums/index.rst index be84c782..8082a207 100644 --- a/docs/api/enums/index.rst +++ b/docs/api/enums/index.rst @@ -11,6 +11,7 @@ Here is list of all available enums: :maxdepth: 1 bot_command_scope_type + button_style chat_action chat_boost_source_type chat_member_status diff --git a/docs/api/methods/get_user_profile_audios.rst b/docs/api/methods/get_user_profile_audios.rst new file mode 100644 index 00000000..f9567228 --- /dev/null +++ b/docs/api/methods/get_user_profile_audios.rst @@ -0,0 +1,38 @@ +#################### +getUserProfileAudios +#################### + +Returns: :obj:`UserProfileAudios` + +.. automodule:: aiogram.methods.get_user_profile_audios + :members: + :member-order: bysource + :undoc-members: True + :exclude-members: model_config,model_fields + + +Usage +===== + +As bot method +------------- + +.. code-block:: + + result: UserProfileAudios = await bot.get_user_profile_audios(...) + + +Method as object +---------------- + +Imports: + +- :code:`from aiogram.methods.get_user_profile_audios import GetUserProfileAudios` +- alias: :code:`from aiogram.methods import GetUserProfileAudios` + +With specific bot +~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + result: UserProfileAudios = await bot(GetUserProfileAudios(...)) diff --git a/docs/api/methods/index.rst b/docs/api/methods/index.rst index a93ed67d..bbd6303a 100644 --- a/docs/api/methods/index.rst +++ b/docs/api/methods/index.rst @@ -82,6 +82,7 @@ Available methods get_my_short_description get_user_chat_boosts get_user_gifts + get_user_profile_audios get_user_profile_photos gift_premium_subscription hide_general_forum_topic @@ -93,6 +94,7 @@ Available methods read_business_message remove_business_account_profile_photo remove_chat_verification + remove_my_profile_photo remove_user_verification reopen_forum_topic reopen_general_forum_topic @@ -135,6 +137,7 @@ Available methods set_my_default_administrator_rights set_my_description set_my_name + set_my_profile_photo set_my_short_description set_user_emoji_status transfer_business_account_stars diff --git a/docs/api/methods/remove_my_profile_photo.rst b/docs/api/methods/remove_my_profile_photo.rst new file mode 100644 index 00000000..19d0d251 --- /dev/null +++ b/docs/api/methods/remove_my_profile_photo.rst @@ -0,0 +1,45 @@ +#################### +removeMyProfilePhoto +#################### + +Returns: :obj:`bool` + +.. automodule:: aiogram.methods.remove_my_profile_photo + :members: + :member-order: bysource + :undoc-members: True + :exclude-members: model_config,model_fields + + +Usage +===== + +As bot method +------------- + +.. code-block:: + + result: bool = await bot.remove_my_profile_photo(...) + + +Method as object +---------------- + +Imports: + +- :code:`from aiogram.methods.remove_my_profile_photo import RemoveMyProfilePhoto` +- alias: :code:`from aiogram.methods import RemoveMyProfilePhoto` + +With specific bot +~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + result: bool = await bot(RemoveMyProfilePhoto(...)) + +As reply into Webhook in handler +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + return RemoveMyProfilePhoto(...) diff --git a/docs/api/methods/set_my_profile_photo.rst b/docs/api/methods/set_my_profile_photo.rst new file mode 100644 index 00000000..c2290414 --- /dev/null +++ b/docs/api/methods/set_my_profile_photo.rst @@ -0,0 +1,45 @@ +################# +setMyProfilePhoto +################# + +Returns: :obj:`bool` + +.. automodule:: aiogram.methods.set_my_profile_photo + :members: + :member-order: bysource + :undoc-members: True + :exclude-members: model_config,model_fields + + +Usage +===== + +As bot method +------------- + +.. code-block:: + + result: bool = await bot.set_my_profile_photo(...) + + +Method as object +---------------- + +Imports: + +- :code:`from aiogram.methods.set_my_profile_photo import SetMyProfilePhoto` +- alias: :code:`from aiogram.methods import SetMyProfilePhoto` + +With specific bot +~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + result: bool = await bot(SetMyProfilePhoto(...)) + +As reply into Webhook in handler +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + return SetMyProfilePhoto(...) diff --git a/docs/api/types/chat_owner_changed.rst b/docs/api/types/chat_owner_changed.rst new file mode 100644 index 00000000..b87b0f84 --- /dev/null +++ b/docs/api/types/chat_owner_changed.rst @@ -0,0 +1,10 @@ +################ +ChatOwnerChanged +################ + + +.. automodule:: aiogram.types.chat_owner_changed + :members: + :member-order: bysource + :undoc-members: True + :exclude-members: model_config,model_fields diff --git a/docs/api/types/chat_owner_left.rst b/docs/api/types/chat_owner_left.rst new file mode 100644 index 00000000..1cca68c3 --- /dev/null +++ b/docs/api/types/chat_owner_left.rst @@ -0,0 +1,10 @@ +############# +ChatOwnerLeft +############# + + +.. automodule:: aiogram.types.chat_owner_left + :members: + :member-order: bysource + :undoc-members: True + :exclude-members: model_config,model_fields diff --git a/docs/api/types/index.rst b/docs/api/types/index.rst index e48d11b5..8cfa8a45 100644 --- a/docs/api/types/index.rst +++ b/docs/api/types/index.rst @@ -67,6 +67,8 @@ Available types chat_member_owner chat_member_restricted chat_member_updated + chat_owner_changed + chat_owner_left chat_permissions chat_photo chat_shared @@ -199,6 +201,7 @@ Available types unique_gift_symbol user user_chat_boosts + user_profile_audios user_profile_photos user_rating user_shared @@ -210,6 +213,7 @@ Available types video_chat_scheduled video_chat_started video_note + video_quality voice web_app_data web_app_info diff --git a/docs/api/types/user_profile_audios.rst b/docs/api/types/user_profile_audios.rst new file mode 100644 index 00000000..2c4b12e0 --- /dev/null +++ b/docs/api/types/user_profile_audios.rst @@ -0,0 +1,10 @@ +################# +UserProfileAudios +################# + + +.. automodule:: aiogram.types.user_profile_audios + :members: + :member-order: bysource + :undoc-members: True + :exclude-members: model_config,model_fields diff --git a/docs/api/types/video_quality.rst b/docs/api/types/video_quality.rst new file mode 100644 index 00000000..a77a9cf3 --- /dev/null +++ b/docs/api/types/video_quality.rst @@ -0,0 +1,10 @@ +############ +VideoQuality +############ + + +.. automodule:: aiogram.types.video_quality + :members: + :member-order: bysource + :undoc-members: True + :exclude-members: model_config,model_fields diff --git a/docs/contributing.rst b/docs/contributing.rst index 558601ab..4b9f6c1f 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -148,8 +148,8 @@ When using :code:`uv`, prefix commands with :code:`uv run` to execute them in th .. code-block:: bash # Format code - uv run black aiogram tests examples - uv run isort aiogram tests examples + uv run ruff format aiogram tests scripts examples + uv run ruff check --fix aiogram tests scripts examples # Run tests uv run pytest tests @@ -180,22 +180,22 @@ implementing new features or experimenting. Format the code (code-style) ---------------------------- -Note that this project is Black-formatted, so you should follow that code-style, -too be sure You're correctly doing this let's reformat the code automatically: +Note that this project uses Ruff for formatting and linting, so you should follow that code-style. +To be sure you're correctly doing this, let's reformat the code automatically: Using traditional approach: .. code-block:: bash - black aiogram tests examples - isort aiogram tests examples + ruff format aiogram tests scripts examples + ruff check --fix aiogram tests scripts examples Or with uv: .. code-block:: bash - uv run black aiogram tests examples - uv run isort aiogram tests examples + uv run ruff format aiogram tests scripts examples + uv run ruff check --fix aiogram tests scripts examples Or simply use Makefile: diff --git a/docs/migration_2_to_3.rst b/docs/migration_2_to_3.rst index 1a4fc84a..ca7367a9 100644 --- a/docs/migration_2_to_3.rst +++ b/docs/migration_2_to_3.rst @@ -94,9 +94,61 @@ Bot API However, all errors are classified by HTTP status codes, and for each method, only one type of error can be associated with a given code. Therefore, in most cases, you should check only the error type (by status code) - without inspecting the error message. + without inspecting the error message. More details can be found in the + :ref:`exceptions section » `. +Exceptions +========== + +Mapping (v2 -> v3) +------------------- + +- RetryAfter -> :class:`TelegramRetryAfter` (:mod:`aiogram.exceptions`) + - Important attribute in v3: ``retry_after`` (int). + +- ChatMigrated / MigrateToChat -> :class:`TelegramMigrateToChat` + - Important attribute in v3: ``migrate_to_chat_id`` (int). + +- ClientDecodeError -> :class:`ClientDecodeError` + - Important attributes in v3: ``original`` (Exception) and ``data`` (response body). + +- BadRequest -> :class:`TelegramBadRequest` +- Unauthorized -> :class:`TelegramUnauthorizedError` +- Forbidden -> :class:`TelegramForbiddenError` +- NotFound -> :class:`TelegramNotFound` +- Conflict -> :class:`TelegramConflictError` +- ServerError -> :class:`TelegramServerError` +- NetworkError -> :class:`TelegramNetworkError` +- EntityTooLarge -> :class:`TelegramEntityTooLarge` + + +Exceptions removed in v3 (from v2) +---------------------------------- + +The list below contains common exception names that appeared in aiogram v2 but +are not defined as separate classes in the v3 codebase. For each v2 name, a +recommended v3 replacement (or handling) is provided — keep your migration +logic simple and rely on the v3 exception classes and their attributes. + +- MessageNotModified -> :class:`TelegramBadRequest` +- MessageToEditNotFound -> :class:`TelegramNotFound` +- MessageToDeleteNotFound -> :class:`TelegramNotFound` +- MessageCantBeDeleted -> :class:`TelegramForbiddenError` / :class:`TelegramBadRequest` +- CantParseEntities -> :class:`TelegramBadRequest` +- MessageIsTooLong -> :class:`TelegramEntityTooLarge` +- MessageIdentifierNotFound -> :class:`TelegramNotFound` +- UserDeactivated -> :class:`TelegramForbiddenError` +- CantInitiateConversation -> :class:`TelegramBadRequest` +- StickerSetNameInvalid -> :class:`TelegramBadRequest` +- ChatAdminRequired -> :class:`TelegramForbiddenError` + +Use these replacements when migrating exception handling from v2 to v3. If +you relied on catching very specific v2 exception classes, replace those +handlers with the corresponding v3 class above (or catch a broader v3 class +such as :class:`TelegramBadRequest` / :class:`TelegramAPIError`) and inspect +available attributes (see "Mapping (v2 -> v3)") for any required details. + Middlewares =========== diff --git a/examples/multi_file_bot/bot.py b/examples/multi_file_bot/bot.py index 0f1ba083..709500be 100644 --- a/examples/multi_file_bot/bot.py +++ b/examples/multi_file_bot/bot.py @@ -2,12 +2,11 @@ import asyncio import logging from os import getenv -from handlers.echo import echo_router -from handlers.start import start_router - from aiogram import Bot, Dispatcher from aiogram.client.default import DefaultBotProperties from aiogram.enums import ParseMode +from handlers.echo import echo_router +from handlers.start import start_router # Bot token can be obtained via https://t.me/BotFather TOKEN = getenv("BOT_TOKEN") diff --git a/examples/multibot.py b/examples/multibot.py index e8de6794..2dc2de1a 100644 --- a/examples/multibot.py +++ b/examples/multibot.py @@ -4,7 +4,6 @@ from os import getenv from typing import Any from aiohttp import web -from finite_state_machine import form_router from aiogram import Bot, Dispatcher, F, Router from aiogram.client.session.aiohttp import AiohttpSession @@ -19,6 +18,7 @@ from aiogram.webhook.aiohttp_server import ( TokenBasedRequestHandler, setup_application, ) +from finite_state_machine import form_router main_router = Router() diff --git a/examples/web_app/main.py b/examples/web_app/main.py index fbbbcff1..1e1938c6 100644 --- a/examples/web_app/main.py +++ b/examples/web_app/main.py @@ -4,14 +4,14 @@ from os import getenv from aiohttp.web import run_app from aiohttp.web_app import Application -from handlers import my_router -from routes import check_data_handler, demo_handler, send_message_handler from aiogram import Bot, Dispatcher from aiogram.client.default import DefaultBotProperties from aiogram.enums.parse_mode import ParseMode from aiogram.types import MenuButtonWebApp, WebAppInfo from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application +from handlers import my_router +from routes import check_data_handler, demo_handler, send_message_handler TOKEN = getenv("BOT_TOKEN") diff --git a/pyproject.toml b/pyproject.toml index 122ae13f..ca0c8c4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,8 +94,6 @@ docs = [ [dependency-groups] dev = [ - "black~=25.9", - "isort~=7.0", "ruff~=0.14", "mypy==1.10.1", "toml~=0.10.2", @@ -139,16 +137,38 @@ exclude = [ [tool.ruff.lint] select = [ # "C", # TODO: mccabe - code complecity - "C4", - "E", - "F", - "Q", - "RET", - "T10", - "T20", + "A", # flake8-annotations + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "DTZ", # flake8-datetimez + "E", # pycodestyle errors + "F", # pyflakes + "I", # isort + "PERF", # perflint + "PL", # pylint + "Q", # flake8-quotes + "RET", # flake8-return + "SIM", # flake8-simplify + "T10", # flake8-debugger + "T20", # flake8-print + "UP", # pyupgrade ] ignore = [ - "F401", + "F401", # unused-import (handled by other tools) + "A002", # builtin-argument-shadowing (common in API: type, id, format, etc.) + "A005", # stdlib-module-shadowing (Module `types` shadows a Python standard-library module) + "B008", # function-call-in-default-argument (Pydantic Field() defaults) + "PLC0415", # import-outside-top-level (needed for circular imports) + "PLC1901", # compare-to-empty-string (sometimes more explicit is better) + "PLR0904", # too-many-public-methods (Bot class has many API methods) + "PLR0911", # too-many-return-statements + "PLR0912", # too-many-branches + "PLR0913", # too-many-arguments (Telegram API methods have many params) + "PLR0915", # too-many-statements + "PLR0917", # too-many-positional-arguments (Telegram API design) + "PLR6301", # no-self-use (sometimes methods are intentionally not static) + "PLW2901", # redefined-loop-name (intentional pattern in this codebase) + "PLW3201", # bad-dunder-method-name (custom dunders for framework design) ] [tool.ruff.lint.isort] @@ -164,6 +184,20 @@ known-first-party = [ "aiogram/types/*" = ["E501"] "aiogram/methods/*" = ["E501"] "aiogram/enums/*" = ["E501"] +"tests/**" = [ + "PLR0124", + "PLR2004", + "DTZ005", + "DTZ006", + "A001", + "A004", + "B018", + "B020", + "B904", + "E501", + "F821", + "UP", +] [tool.pytest.ini_options] @@ -174,7 +208,6 @@ testpaths = [ filterwarnings = [ "error", "ignore::pytest.PytestUnraisableExceptionWarning", - # Remove when uvloop fixes the issue # https://github.com/MagicStack/uvloop/issues/703 # https://github.com/MagicStack/uvloop/pull/705 @@ -235,23 +268,10 @@ module = [ ignore_missing_imports = true disallow_untyped_defs = true -[tool.black] -line-length = 99 -target-version = ['py310', 'py311', 'py312', 'py313'] -exclude = ''' -( - \.eggs - | \.git - | \.tox - | build - | dist - | venv - | docs -) -''' - -[tool.isort] -profile = "black" +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +line-ending = "auto" [tool.towncrier] package = "aiogram" diff --git a/scripts/bump_version.py b/scripts/bump_version.py index 92485406..3dc586c7 100644 --- a/scripts/bump_version.py +++ b/scripts/bump_version.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Version bumping script for aiogram (replaces hatch version).""" + import re import sys from pathlib import Path @@ -44,9 +45,9 @@ def bump_version(part: str) -> str: if __name__ == "__main__": - if len(sys.argv) != 2: - print("Usage: python scripts/bump_version.py [major|minor|patch|to:X.Y.Z]") + if len(sys.argv) != 2: # noqa: PLR2004 + print("Usage: python scripts/bump_version.py [major|minor|patch|to:X.Y.Z]") # noqa: T201 sys.exit(1) new_version = bump_version(sys.argv[1]) - print(f"Bumped version to {new_version}") + print(f"Bumped version to {new_version}") # noqa: T201 diff --git a/scripts/bump_versions.py b/scripts/bump_versions.py index 2cd37642..7174f422 100644 --- a/scripts/bump_versions.py +++ b/scripts/bump_versions.py @@ -45,8 +45,7 @@ def get_telegram_api_version() -> str: def replace_line(content: str, pattern: re.Pattern, new_value: str) -> str: - result = pattern.sub(f"\\g<1>{new_value}\\g<2>", content) - return result + return pattern.sub(f"\\g<1>{new_value}\\g<2>", content) def write_package_meta(api_version: str) -> None: @@ -55,7 +54,7 @@ def write_package_meta(api_version: str) -> None: content = replace_line(content, API_VERSION, api_version) - print(f"Write {path}") + print(f"Write {path}") # noqa: T201 path.write_text(content) @@ -64,7 +63,7 @@ def write_readme(api_version: str) -> None: content = path.read_text() content = replace_line(content, API_VERSION_BADGE, api_version) content = replace_line(content, API_VERSION_LINE, api_version) - print(f"Write {path}") + print(f"Write {path}") # noqa: T201 path.write_text(content) @@ -72,14 +71,14 @@ def write_docs_index(api_version: str) -> None: path = Path.cwd() / "docs" / "index.rst" content = path.read_text() content = replace_line(content, API_VERSION_BADGE, api_version) - print(f"Write {path}") + print(f"Write {path}") # noqa: T201 path.write_text(content) def main(): api_version = get_telegram_api_version() - print(f"Telegram Bot API version: {api_version}") + print(f"Telegram Bot API version: {api_version}") # noqa: T201 write_package_meta(api_version=api_version) write_readme(api_version=api_version) write_docs_index(api_version=api_version) diff --git a/tests/conftest.py b/tests/conftest.py index 5034cde5..9632a5c1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -144,8 +144,7 @@ async def memory_storage(): @pytest.fixture() async def redis_isolation(redis_storage): - isolation = redis_storage.create_isolation() - return isolation + return redis_storage.create_isolation() @pytest.fixture() diff --git a/tests/deprecated.py b/tests/deprecated.py index f030e515..138ef190 100644 --- a/tests/deprecated.py +++ b/tests/deprecated.py @@ -1,5 +1,4 @@ from contextlib import contextmanager -from typing import Type import pytest from packaging import version @@ -10,8 +9,8 @@ import aiogram @contextmanager def check_deprecated( max_version: str, - exception: Type[Exception], - warning: Type[Warning] = DeprecationWarning, + exception: type[Exception], + warning: type[Warning] = DeprecationWarning, ) -> None: """ Should be used for modules that are being deprecated or already removed from aiogram diff --git a/tests/mocked_bot.py b/tests/mocked_bot.py index cd137aee..b7007029 100644 --- a/tests/mocked_bot.py +++ b/tests/mocked_bot.py @@ -1,5 +1,6 @@ from collections import deque -from typing import TYPE_CHECKING, Any, AsyncGenerator, Deque, Dict, Optional, Type +from collections.abc import AsyncGenerator +from typing import TYPE_CHECKING, Any from aiogram import Bot from aiogram.client.session.base import BaseSession @@ -10,9 +11,9 @@ from aiogram.types import UNSET_PARSE_MODE, ResponseParameters, User class MockedSession(BaseSession): def __init__(self): - super(MockedSession, self).__init__() - self.responses: Deque[Response[TelegramType]] = deque() - self.requests: Deque[TelegramMethod[TelegramType]] = deque() + super().__init__() + self.responses: deque[Response[TelegramType]] = deque() + self.requests: deque[TelegramMethod[TelegramType]] = deque() self.closed = True def add_result(self, response: Response[TelegramType]) -> Response[TelegramType]: @@ -29,7 +30,7 @@ class MockedSession(BaseSession): self, bot: Bot, method: TelegramMethod[TelegramType], - timeout: Optional[int] = UNSET_PARSE_MODE, + timeout: int | None = UNSET_PARSE_MODE, ) -> TelegramType: self.closed = False self.requests.append(method) @@ -45,7 +46,7 @@ class MockedSession(BaseSession): async def stream_content( self, url: str, - headers: Optional[Dict[str, Any]] = None, + headers: dict[str, Any] | None = None, timeout: int = 30, chunk_size: int = 65536, raise_for_status: bool = True, @@ -58,9 +59,7 @@ class MockedBot(Bot): session: MockedSession def __init__(self, **kwargs): - super(MockedBot, self).__init__( - kwargs.pop("token", "42:TEST"), session=MockedSession(), **kwargs - ) + super().__init__(kwargs.pop("token", "42:TEST"), session=MockedSession(), **kwargs) self._me = User( id=self.id, is_bot=True, @@ -72,13 +71,13 @@ class MockedBot(Bot): def add_result_for( self, - method: Type[TelegramMethod[TelegramType]], + method: type[TelegramMethod[TelegramType]], ok: bool, result: TelegramType = None, - description: Optional[str] = None, + description: str | None = None, error_code: int = 200, - migrate_to_chat_id: Optional[int] = None, - retry_after: Optional[int] = None, + migrate_to_chat_id: int | None = None, + retry_after: int | None = None, ) -> Response[TelegramType]: response = Response[method.__returning__]( # type: ignore ok=ok, 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 bc0239df..a10b6a2e 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 @@ -1,12 +1,8 @@ import asyncio +from collections.abc import AsyncGenerator, AsyncIterable from typing import ( Any, AsyncContextManager, - AsyncGenerator, - AsyncIterable, - Dict, - List, - Union, ) from unittest.mock import AsyncMock, patch @@ -115,10 +111,10 @@ class TestAiohttpSession: str_: str int_: int bool_: bool - unset_: Union[str, Default] = Default("parse_mode") + unset_: str | Default = Default("parse_mode") null_: None - list_: List[str] - dict_: Dict[str, Any] + list_: list[str] + dict_: dict[str, Any] session = AiohttpSession() form = session.build_form_data( 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 6bca619c..46d6bbc7 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,6 +1,7 @@ import datetime import json -from typing import Any, AsyncContextManager, AsyncGenerator, Dict, Optional +from collections.abc import AsyncGenerator +from typing import Any, AsyncContextManager from unittest.mock import AsyncMock, patch import pytest @@ -39,7 +40,7 @@ class CustomSession(BaseSession): self, token: str, method: TelegramMethod[TelegramType], - timeout: Optional[int] = UNSET_PARSE_MODE, + timeout: int | None = UNSET_PARSE_MODE, ) -> None: # type: ignore assert isinstance(token, str) assert isinstance(method, TelegramMethod) @@ -47,7 +48,7 @@ class CustomSession(BaseSession): async def stream_content( self, url: str, - headers: Optional[Dict[str, Any]] = None, + headers: dict[str, Any] | None = None, timeout: int = 30, chunk_size: int = 65536, raise_for_status: bool = True, diff --git a/tests/test_api/test_methods/test_add_sticker_to_set.py b/tests/test_api/test_methods/test_add_sticker_to_set.py index 84b7a9a8..bf388bf4 100644 --- a/tests/test_api/test_methods/test_add_sticker_to_set.py +++ b/tests/test_api/test_methods/test_add_sticker_to_set.py @@ -15,5 +15,5 @@ class TestAddStickerToSet: sticker="file id", format=StickerFormat.STATIC, emoji_list=[":)"] ), ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_answer_callback_query.py b/tests/test_api/test_methods/test_answer_callback_query.py index 11e01b1e..8565237f 100644 --- a/tests/test_api/test_methods/test_answer_callback_query.py +++ b/tests/test_api/test_methods/test_answer_callback_query.py @@ -7,5 +7,5 @@ class TestAnswerCallbackQuery: prepare_result = bot.add_result_for(AnswerCallbackQuery, ok=True, result=True) response: bool = await bot.answer_callback_query(callback_query_id="cq id", text="OK") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result 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 f634ff1a..da9d8c0a 100644 --- a/tests/test_api/test_methods/test_answer_inline_query.py +++ b/tests/test_api/test_methods/test_answer_inline_query.py @@ -17,5 +17,5 @@ class TestAnswerInlineQuery: ) ], ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_answer_pre_checkout_query.py b/tests/test_api/test_methods/test_answer_pre_checkout_query.py index 09887aff..c31b1633 100644 --- a/tests/test_api/test_methods/test_answer_pre_checkout_query.py +++ b/tests/test_api/test_methods/test_answer_pre_checkout_query.py @@ -9,5 +9,5 @@ class TestAnswerPreCheckoutQuery: response: bool = await bot.answer_pre_checkout_query( pre_checkout_query_id="query id", ok=True ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_answer_shipping_query.py b/tests/test_api/test_methods/test_answer_shipping_query.py index 3d8b2a68..5c1d8550 100644 --- a/tests/test_api/test_methods/test_answer_shipping_query.py +++ b/tests/test_api/test_methods/test_answer_shipping_query.py @@ -7,5 +7,5 @@ class TestAnswerShippingQuery: prepare_result = bot.add_result_for(AnswerShippingQuery, ok=True, result=True) response: bool = await bot.answer_shipping_query(shipping_query_id="query id", ok=True) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_answer_web_app_query.py b/tests/test_api/test_methods/test_answer_web_app_query.py index a42640a8..f38d0938 100644 --- a/tests/test_api/test_methods/test_answer_web_app_query.py +++ b/tests/test_api/test_methods/test_answer_web_app_query.py @@ -15,5 +15,5 @@ class TestAnswerWebAppQuery: thumbnail_url="test", ), ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_approve_chat_join_request.py b/tests/test_api/test_methods/test_approve_chat_join_request.py index f4e035b7..cea8c17f 100755 --- a/tests/test_api/test_methods/test_approve_chat_join_request.py +++ b/tests/test_api/test_methods/test_approve_chat_join_request.py @@ -10,5 +10,5 @@ class TestApproveChatJoinRequest: chat_id=-42, user_id=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_approve_suggested_post.py b/tests/test_api/test_methods/test_approve_suggested_post.py index e652c47d..8d84f5fe 100644 --- a/tests/test_api/test_methods/test_approve_suggested_post.py +++ b/tests/test_api/test_methods/test_approve_suggested_post.py @@ -10,5 +10,5 @@ class TestApproveSuggestedPost: chat_id=-42, message_id=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_ban_chat_member.py b/tests/test_api/test_methods/test_ban_chat_member.py index 1e81a01b..6809a596 100644 --- a/tests/test_api/test_methods/test_ban_chat_member.py +++ b/tests/test_api/test_methods/test_ban_chat_member.py @@ -7,5 +7,5 @@ class TestKickChatMember: prepare_result = bot.add_result_for(BanChatMember, ok=True, result=True) response: bool = await bot.ban_chat_member(chat_id=-42, user_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_ban_chat_sender_chat.py b/tests/test_api/test_methods/test_ban_chat_sender_chat.py index 237516f5..feefbc8d 100755 --- a/tests/test_api/test_methods/test_ban_chat_sender_chat.py +++ b/tests/test_api/test_methods/test_ban_chat_sender_chat.py @@ -10,5 +10,5 @@ class TestBanChatSenderChat: chat_id=-42, sender_chat_id=-1337, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_close.py b/tests/test_api/test_methods/test_close.py index 6944ca2a..fe2602e2 100644 --- a/tests/test_api/test_methods/test_close.py +++ b/tests/test_api/test_methods/test_close.py @@ -7,5 +7,5 @@ class TestClose: prepare_result = bot.add_result_for(Close, ok=True, result=True) response: bool = await bot.close() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_close_forum_topic.py b/tests/test_api/test_methods/test_close_forum_topic.py index 44644e31..527ed511 100644 --- a/tests/test_api/test_methods/test_close_forum_topic.py +++ b/tests/test_api/test_methods/test_close_forum_topic.py @@ -10,5 +10,5 @@ class TestCloseForumTopic: chat_id=42, message_thread_id=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_close_general_forum_topic.py b/tests/test_api/test_methods/test_close_general_forum_topic.py index f73e3b47..45133499 100644 --- a/tests/test_api/test_methods/test_close_general_forum_topic.py +++ b/tests/test_api/test_methods/test_close_general_forum_topic.py @@ -7,5 +7,5 @@ class TestCloseGeneralForumTopic: prepare_result = bot.add_result_for(CloseGeneralForumTopic, ok=True, result=True) response: bool = await bot.close_general_forum_topic(chat_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_convert_gift_to_stars.py b/tests/test_api/test_methods/test_convert_gift_to_stars.py index 4d9182b5..37fb4aa1 100644 --- a/tests/test_api/test_methods/test_convert_gift_to_stars.py +++ b/tests/test_api/test_methods/test_convert_gift_to_stars.py @@ -9,5 +9,5 @@ class TestConvertGiftToStars: response: bool = await bot.convert_gift_to_stars( business_connection_id="test_connection_id", owned_gift_id="test_gift_id" ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_copy_message.py b/tests/test_api/test_methods/test_copy_message.py index 0b9a762a..f9425998 100644 --- a/tests/test_api/test_methods/test_copy_message.py +++ b/tests/test_api/test_methods/test_copy_message.py @@ -12,5 +12,5 @@ class TestCopyMessage: from_chat_id=42, message_id=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_create_chat_invite_link.py b/tests/test_api/test_methods/test_create_chat_invite_link.py index 5e8216d3..8247d8fc 100644 --- a/tests/test_api/test_methods/test_create_chat_invite_link.py +++ b/tests/test_api/test_methods/test_create_chat_invite_link.py @@ -20,5 +20,5 @@ class TestCreateChatInviteLink: response: ChatInviteLink = await bot.create_chat_invite_link( chat_id=-42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_create_chat_subscruption_invite_link.py b/tests/test_api/test_methods/test_create_chat_subscruption_invite_link.py index 66b002eb..7d8b4d87 100644 --- a/tests/test_api/test_methods/test_create_chat_subscruption_invite_link.py +++ b/tests/test_api/test_methods/test_create_chat_subscruption_invite_link.py @@ -24,5 +24,5 @@ class TestCreateChatSubscriptionInviteLink: subscription_period=timedelta(days=30), subscription_price=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_create_forum_topic.py b/tests/test_api/test_methods/test_create_forum_topic.py index e39882c2..23e7ea87 100644 --- a/tests/test_api/test_methods/test_create_forum_topic.py +++ b/tests/test_api/test_methods/test_create_forum_topic.py @@ -15,5 +15,5 @@ class TestCreateForumTopic: chat_id=42, name="test", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_create_invoice_link.py b/tests/test_api/test_methods/test_create_invoice_link.py index 97112036..9de395a4 100644 --- a/tests/test_api/test_methods/test_create_invoice_link.py +++ b/tests/test_api/test_methods/test_create_invoice_link.py @@ -17,5 +17,5 @@ class TestCreateInvoiceLink: currency="BTC", prices=[LabeledPrice(label="Test", amount=1)], ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_create_new_sticker_set.py b/tests/test_api/test_methods/test_create_new_sticker_set.py index cdddc933..d5bfbd65 100644 --- a/tests/test_api/test_methods/test_create_new_sticker_set.py +++ b/tests/test_api/test_methods/test_create_new_sticker_set.py @@ -20,5 +20,5 @@ class TestCreateNewStickerSet: ], sticker_format=StickerFormat.STATIC, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_decline_chat_join_request.py b/tests/test_api/test_methods/test_decline_chat_join_request.py index 9da77257..5a5cf4a5 100755 --- a/tests/test_api/test_methods/test_decline_chat_join_request.py +++ b/tests/test_api/test_methods/test_decline_chat_join_request.py @@ -10,5 +10,5 @@ class TestDeclineChatJoinRequest: chat_id=-42, user_id=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_decline_suggested_post.py b/tests/test_api/test_methods/test_decline_suggested_post.py index 6edb190b..8509c943 100644 --- a/tests/test_api/test_methods/test_decline_suggested_post.py +++ b/tests/test_api/test_methods/test_decline_suggested_post.py @@ -10,5 +10,5 @@ class TestDeclineSuggestedPost: chat_id=-42, message_id=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_delete_business_messages.py b/tests/test_api/test_methods/test_delete_business_messages.py index 060dcf94..6f39e599 100644 --- a/tests/test_api/test_methods/test_delete_business_messages.py +++ b/tests/test_api/test_methods/test_delete_business_messages.py @@ -9,5 +9,5 @@ class TestDeleteBusinessMessages: response: bool = await bot.delete_business_messages( business_connection_id="test_connection_id", message_ids=[1, 2, 3] ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_delete_chat_photo.py b/tests/test_api/test_methods/test_delete_chat_photo.py index dd9b0b9c..954bc9fc 100644 --- a/tests/test_api/test_methods/test_delete_chat_photo.py +++ b/tests/test_api/test_methods/test_delete_chat_photo.py @@ -7,5 +7,5 @@ class TestDeleteChatPhoto: prepare_result = bot.add_result_for(DeleteChatPhoto, ok=True, result=True) response: bool = await bot.delete_chat_photo(chat_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_delete_chat_sticker_set.py b/tests/test_api/test_methods/test_delete_chat_sticker_set.py index 5de60460..611fb5e9 100644 --- a/tests/test_api/test_methods/test_delete_chat_sticker_set.py +++ b/tests/test_api/test_methods/test_delete_chat_sticker_set.py @@ -7,5 +7,5 @@ class TestDeleteChatStickerSet: prepare_result = bot.add_result_for(DeleteChatStickerSet, ok=True, result=True) response: bool = await bot.delete_chat_sticker_set(chat_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_delete_forum_topic.py b/tests/test_api/test_methods/test_delete_forum_topic.py index ad14ba61..9af1b271 100644 --- a/tests/test_api/test_methods/test_delete_forum_topic.py +++ b/tests/test_api/test_methods/test_delete_forum_topic.py @@ -10,5 +10,5 @@ class TestDeleteForumTopic: chat_id=42, message_thread_id=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_delete_message.py b/tests/test_api/test_methods/test_delete_message.py index 67c9d767..e8531e34 100644 --- a/tests/test_api/test_methods/test_delete_message.py +++ b/tests/test_api/test_methods/test_delete_message.py @@ -7,5 +7,5 @@ class TestDeleteMessage: prepare_result = bot.add_result_for(DeleteMessage, ok=True, result=True) response: bool = await bot.delete_message(chat_id=42, message_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_delete_my_commands.py b/tests/test_api/test_methods/test_delete_my_commands.py index 9aff0d71..c771c4e2 100644 --- a/tests/test_api/test_methods/test_delete_my_commands.py +++ b/tests/test_api/test_methods/test_delete_my_commands.py @@ -7,5 +7,5 @@ class TestKickChatMember: prepare_result = bot.add_result_for(DeleteMyCommands, ok=True, result=True) response: bool = await bot.delete_my_commands() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_delete_sticker_from_set.py b/tests/test_api/test_methods/test_delete_sticker_from_set.py index 2bc0311f..a8e7b438 100644 --- a/tests/test_api/test_methods/test_delete_sticker_from_set.py +++ b/tests/test_api/test_methods/test_delete_sticker_from_set.py @@ -7,5 +7,5 @@ class TestDeleteStickerFromSet: prepare_result = bot.add_result_for(DeleteStickerFromSet, ok=True, result=True) response: bool = await bot.delete_sticker_from_set(sticker="sticker id") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_delete_sticker_set.py b/tests/test_api/test_methods/test_delete_sticker_set.py index 1b864c75..2f7fd06d 100644 --- a/tests/test_api/test_methods/test_delete_sticker_set.py +++ b/tests/test_api/test_methods/test_delete_sticker_set.py @@ -7,5 +7,5 @@ class TestDeleteStickerSet: prepare_result = bot.add_result_for(DeleteStickerSet, ok=True, result=True) response: bool = await bot.delete_sticker_set(name="test") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_delete_story.py b/tests/test_api/test_methods/test_delete_story.py index c205cc20..4d1295e2 100644 --- a/tests/test_api/test_methods/test_delete_story.py +++ b/tests/test_api/test_methods/test_delete_story.py @@ -9,5 +9,5 @@ class TestDeleteStory: response: bool = await bot.delete_story( business_connection_id="test_connection_id", story_id=42 ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_delete_webhook.py b/tests/test_api/test_methods/test_delete_webhook.py index 85c93d57..713302da 100644 --- a/tests/test_api/test_methods/test_delete_webhook.py +++ b/tests/test_api/test_methods/test_delete_webhook.py @@ -7,5 +7,5 @@ class TestDeleteWebhook: prepare_result = bot.add_result_for(DeleteWebhook, ok=True, result=True) response: bool = await bot.delete_webhook() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_chat_invite_link.py b/tests/test_api/test_methods/test_edit_chat_invite_link.py index 9f1c0822..f79514c5 100644 --- a/tests/test_api/test_methods/test_edit_chat_invite_link.py +++ b/tests/test_api/test_methods/test_edit_chat_invite_link.py @@ -20,5 +20,5 @@ class TestEditChatInviteLink: response: ChatInviteLink = await bot.edit_chat_invite_link( chat_id=-42, invite_link="https://t.me/username", member_limit=1 ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_chat_subscruption_invite_link.py b/tests/test_api/test_methods/test_edit_chat_subscruption_invite_link.py index 7edbb032..c5853734 100644 --- a/tests/test_api/test_methods/test_edit_chat_subscruption_invite_link.py +++ b/tests/test_api/test_methods/test_edit_chat_subscruption_invite_link.py @@ -22,5 +22,5 @@ class TestEditChatSubscriptionInviteLink: invite_link="https://t.me/username/test", name="test", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_forum_topic.py b/tests/test_api/test_methods/test_edit_forum_topic.py index e00cda20..d68945b8 100644 --- a/tests/test_api/test_methods/test_edit_forum_topic.py +++ b/tests/test_api/test_methods/test_edit_forum_topic.py @@ -12,5 +12,5 @@ class TestEditForumTopic: name="test", icon_custom_emoji_id="0", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_general_forum_topic.py b/tests/test_api/test_methods/test_edit_general_forum_topic.py index 6d08f809..ff4e8493 100644 --- a/tests/test_api/test_methods/test_edit_general_forum_topic.py +++ b/tests/test_api/test_methods/test_edit_general_forum_topic.py @@ -7,5 +7,5 @@ class TestCloseGeneralForumTopic: prepare_result = bot.add_result_for(EditGeneralForumTopic, ok=True, result=True) response: bool = await bot.edit_general_forum_topic(chat_id=42, name="Test") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_message_caption.py b/tests/test_api/test_methods/test_edit_message_caption.py index 2aba0502..8713c673 100644 --- a/tests/test_api/test_methods/test_edit_message_caption.py +++ b/tests/test_api/test_methods/test_edit_message_caption.py @@ -1,5 +1,4 @@ import datetime -from typing import Union from aiogram.methods import EditMessageCaption from aiogram.types import Chat, Message @@ -19,6 +18,6 @@ class TestEditMessageCaption: ), ) - response: Union[Message, bool] = await bot.edit_message_caption() - request = bot.get_request() + response: Message | bool = await bot.edit_message_caption() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_message_checklist.py b/tests/test_api/test_methods/test_edit_message_checklist.py index e154cf6f..e3c4dd02 100644 --- a/tests/test_api/test_methods/test_edit_message_checklist.py +++ b/tests/test_api/test_methods/test_edit_message_checklist.py @@ -31,5 +31,5 @@ class TestEditMessageChecklist: message_id=42, checklist=checklist, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_message_live_location.py b/tests/test_api/test_methods/test_edit_message_live_location.py index 2ab8372e..89c6075e 100644 --- a/tests/test_api/test_methods/test_edit_message_live_location.py +++ b/tests/test_api/test_methods/test_edit_message_live_location.py @@ -1,5 +1,3 @@ -from typing import Union - from aiogram.methods import EditMessageLiveLocation from aiogram.types import Message from tests.mocked_bot import MockedBot @@ -9,8 +7,8 @@ class TestEditMessageLiveLocation: async def test_bot_method(self, bot: MockedBot): prepare_result = bot.add_result_for(EditMessageLiveLocation, ok=True, result=True) - response: Union[Message, bool] = await bot.edit_message_live_location( + response: Message | bool = await bot.edit_message_live_location( latitude=3.141592, longitude=3.141592 ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_message_media.py b/tests/test_api/test_methods/test_edit_message_media.py index c4c1cfd5..562588f3 100644 --- a/tests/test_api/test_methods/test_edit_message_media.py +++ b/tests/test_api/test_methods/test_edit_message_media.py @@ -1,5 +1,3 @@ -from typing import Union - from aiogram.methods import EditMessageMedia from aiogram.types import BufferedInputFile, InputMediaPhoto, Message from tests.mocked_bot import MockedBot @@ -9,8 +7,8 @@ class TestEditMessageMedia: async def test_bot_method(self, bot: MockedBot): prepare_result = bot.add_result_for(EditMessageMedia, ok=True, result=True) - response: Union[Message, bool] = await bot.edit_message_media( + response: Message | bool = await bot.edit_message_media( media=InputMediaPhoto(media=BufferedInputFile(b"", "photo.png")) ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_message_reply_markup.py b/tests/test_api/test_methods/test_edit_message_reply_markup.py index 2e2e97e3..2a4438e6 100644 --- a/tests/test_api/test_methods/test_edit_message_reply_markup.py +++ b/tests/test_api/test_methods/test_edit_message_reply_markup.py @@ -1,5 +1,3 @@ -from typing import Union - from aiogram.methods import EditMessageReplyMarkup from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message from tests.mocked_bot import MockedBot @@ -9,7 +7,7 @@ class TestEditMessageReplyMarkup: async def test_bot_method(self, bot: MockedBot): prepare_result = bot.add_result_for(EditMessageReplyMarkup, ok=True, result=True) - response: Union[Message, bool] = await bot.edit_message_reply_markup( + response: Message | bool = await bot.edit_message_reply_markup( chat_id=42, inline_message_id="inline message id", reply_markup=InlineKeyboardMarkup( @@ -18,5 +16,5 @@ class TestEditMessageReplyMarkup: ] ), ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_message_text.py b/tests/test_api/test_methods/test_edit_message_text.py index f4f33f4c..6c22624b 100644 --- a/tests/test_api/test_methods/test_edit_message_text.py +++ b/tests/test_api/test_methods/test_edit_message_text.py @@ -1,5 +1,3 @@ -from typing import Union - from aiogram.methods import EditMessageText from aiogram.types import Message from tests.mocked_bot import MockedBot @@ -9,8 +7,8 @@ class TestEditMessageText: async def test_bot_method(self, bot: MockedBot): prepare_result = bot.add_result_for(EditMessageText, ok=True, result=True) - response: Union[Message, bool] = await bot.edit_message_text( + response: Message | bool = await bot.edit_message_text( chat_id=42, inline_message_id="inline message id", text="text" ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_story.py b/tests/test_api/test_methods/test_edit_story.py index 30555974..2203b5e5 100644 --- a/tests/test_api/test_methods/test_edit_story.py +++ b/tests/test_api/test_methods/test_edit_story.py @@ -1,5 +1,3 @@ -import datetime - from aiogram.methods import EditStory from aiogram.types import Chat, InputStoryContentPhoto, Story from tests.mocked_bot import MockedBot @@ -22,5 +20,5 @@ class TestEditStory: content=InputStoryContentPhoto(type="photo", photo="test_photo"), caption="Test caption", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_edit_user_star_subscription.py b/tests/test_api/test_methods/test_edit_user_star_subscription.py index c534e781..695df9af 100644 --- a/tests/test_api/test_methods/test_edit_user_star_subscription.py +++ b/tests/test_api/test_methods/test_edit_user_star_subscription.py @@ -11,5 +11,5 @@ class TestEditUserStarSubscription: telegram_payment_charge_id="telegram_payment_charge_id", is_canceled=False, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_export_chat_invite_link.py b/tests/test_api/test_methods/test_export_chat_invite_link.py index 1d0de7ec..56714362 100644 --- a/tests/test_api/test_methods/test_export_chat_invite_link.py +++ b/tests/test_api/test_methods/test_export_chat_invite_link.py @@ -9,5 +9,5 @@ class TestExportChatInviteLink: ) response: str = await bot.export_chat_invite_link(chat_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_available_gifts.py b/tests/test_api/test_methods/test_get_available_gifts.py index 3576eee5..a3db3936 100644 --- a/tests/test_api/test_methods/test_get_available_gifts.py +++ b/tests/test_api/test_methods/test_get_available_gifts.py @@ -28,5 +28,5 @@ class TestGetAvailableGifts: ) response: Gifts = await bot.get_available_gifts() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_business_account_gifts.py b/tests/test_api/test_methods/test_get_business_account_gifts.py index 697e3b6c..712911cb 100644 --- a/tests/test_api/test_methods/test_get_business_account_gifts.py +++ b/tests/test_api/test_methods/test_get_business_account_gifts.py @@ -37,5 +37,5 @@ class TestGetBusinessAccountGifts: business_connection_id="test_connection_id", limit=10, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_business_account_star_balance.py b/tests/test_api/test_methods/test_get_business_account_star_balance.py index 5540fa44..67bdb19e 100644 --- a/tests/test_api/test_methods/test_get_business_account_star_balance.py +++ b/tests/test_api/test_methods/test_get_business_account_star_balance.py @@ -17,5 +17,5 @@ class TestGetBusinessAccountStarBalance: response: StarAmount = await bot.get_business_account_star_balance( business_connection_id="test_connection_id", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_business_connection.py b/tests/test_api/test_methods/test_get_business_connection.py index 22776cb4..99bf1ba0 100644 --- a/tests/test_api/test_methods/test_get_business_connection.py +++ b/tests/test_api/test_methods/test_get_business_connection.py @@ -20,5 +20,5 @@ class TestGetBusinessConnection: response: BusinessConnection = await bot.get_business_connection( business_connection_id="test" ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_chat.py b/tests/test_api/test_methods/test_get_chat.py index bc0eb8cd..f02aa2e0 100644 --- a/tests/test_api/test_methods/test_get_chat.py +++ b/tests/test_api/test_methods/test_get_chat.py @@ -25,5 +25,5 @@ class TestGetChat: ) response: ChatFullInfo = await bot.get_chat(chat_id=-42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_chat_administrators.py b/tests/test_api/test_methods/test_get_chat_administrators.py index f5992a87..6026fa8c 100644 --- a/tests/test_api/test_methods/test_get_chat_administrators.py +++ b/tests/test_api/test_methods/test_get_chat_administrators.py @@ -1,5 +1,3 @@ -from typing import List - from aiogram.methods import GetChatAdministrators from aiogram.types import ChatMember, ChatMemberOwner, User from tests.mocked_bot import MockedBot @@ -16,6 +14,6 @@ class TestGetChatAdministrators: ) ], ) - response: List[ChatMember] = await bot.get_chat_administrators(chat_id=-42) - request = bot.get_request() + response: list[ChatMember] = await bot.get_chat_administrators(chat_id=-42) + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_chat_gifts.py b/tests/test_api/test_methods/test_get_chat_gifts.py index 26c83b47..90111ac9 100644 --- a/tests/test_api/test_methods/test_get_chat_gifts.py +++ b/tests/test_api/test_methods/test_get_chat_gifts.py @@ -37,5 +37,5 @@ class TestGetChatGifts: chat_id=42, limit=10, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_chat_member.py b/tests/test_api/test_methods/test_get_chat_member.py index 5d0490a5..091f3391 100644 --- a/tests/test_api/test_methods/test_get_chat_member.py +++ b/tests/test_api/test_methods/test_get_chat_member.py @@ -13,5 +13,5 @@ class TestGetChatMember: ), ) response = await bot.get_chat_member(chat_id=-42, user_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_chat_member_count.py b/tests/test_api/test_methods/test_get_chat_member_count.py index d2af7e2f..bec7ec16 100644 --- a/tests/test_api/test_methods/test_get_chat_member_count.py +++ b/tests/test_api/test_methods/test_get_chat_member_count.py @@ -7,5 +7,5 @@ class TestGetChatMembersCount: prepare_result = bot.add_result_for(GetChatMemberCount, ok=True, result=42) response: int = await bot.get_chat_member_count(chat_id=-42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_chat_menu_button.py b/tests/test_api/test_methods/test_get_chat_menu_button.py index 361ebc14..dbfb0285 100644 --- a/tests/test_api/test_methods/test_get_chat_menu_button.py +++ b/tests/test_api/test_methods/test_get_chat_menu_button.py @@ -8,5 +8,5 @@ class TestGetChatMenuButton: prepare_result = bot.add_result_for(GetChatMenuButton, ok=True, result=MenuButtonDefault()) response: MenuButton = await bot.get_chat_menu_button() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_custom_emoji_stickers.py b/tests/test_api/test_methods/test_get_custom_emoji_stickers.py index 20775c75..c29a0852 100644 --- a/tests/test_api/test_methods/test_get_custom_emoji_stickers.py +++ b/tests/test_api/test_methods/test_get_custom_emoji_stickers.py @@ -1,5 +1,3 @@ -from typing import List - from aiogram.methods import GetCustomEmojiStickers from aiogram.types import Sticker from tests.mocked_bot import MockedBot @@ -24,8 +22,8 @@ class TestGetCustomEmojiStickers: ], ) - response: List[Sticker] = await bot.get_custom_emoji_stickers( + response: list[Sticker] = await bot.get_custom_emoji_stickers( custom_emoji_ids=["1", "2"], ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_file.py b/tests/test_api/test_methods/test_get_file.py index 808681b4..71a85e2c 100644 --- a/tests/test_api/test_methods/test_get_file.py +++ b/tests/test_api/test_methods/test_get_file.py @@ -10,5 +10,5 @@ class TestGetFile: ) response: File = await bot.get_file(file_id="file id") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_forum_topic_icon_stickers.py b/tests/test_api/test_methods/test_get_forum_topic_icon_stickers.py index df0ed2b2..4e798113 100644 --- a/tests/test_api/test_methods/test_get_forum_topic_icon_stickers.py +++ b/tests/test_api/test_methods/test_get_forum_topic_icon_stickers.py @@ -1,5 +1,3 @@ -from typing import List - from aiogram.methods import GetForumTopicIconStickers from aiogram.types import Sticker from tests.mocked_bot import MockedBot @@ -9,6 +7,6 @@ class TestGetForumTopicIconStickers: async def test_bot_method(self, bot: MockedBot): prepare_result = bot.add_result_for(GetForumTopicIconStickers, ok=True, result=[]) - response: List[Sticker] = await bot.get_forum_topic_icon_stickers() - request = bot.get_request() + response: list[Sticker] = await bot.get_forum_topic_icon_stickers() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_game_high_scores.py b/tests/test_api/test_methods/test_get_game_high_scores.py index 189c05a8..075a2e66 100644 --- a/tests/test_api/test_methods/test_get_game_high_scores.py +++ b/tests/test_api/test_methods/test_get_game_high_scores.py @@ -1,5 +1,3 @@ -from typing import List - from aiogram.methods import GetGameHighScores from aiogram.types import GameHighScore, User from tests.mocked_bot import MockedBot @@ -17,6 +15,6 @@ class TestGetGameHighScores: ], ) - response: List[GameHighScore] = await bot.get_game_high_scores(user_id=42) - request = bot.get_request() + response: list[GameHighScore] = await bot.get_game_high_scores(user_id=42) + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_me.py b/tests/test_api/test_methods/test_get_me.py index eac5557d..ab86adce 100644 --- a/tests/test_api/test_methods/test_get_me.py +++ b/tests/test_api/test_methods/test_get_me.py @@ -9,7 +9,7 @@ class TestGetMe: GetMe, ok=True, result=User(id=42, is_bot=False, first_name="User") ) response: User = await bot.get_me() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result async def test_me_property(self, bot: MockedBot): diff --git a/tests/test_api/test_methods/test_get_my_commands.py b/tests/test_api/test_methods/test_get_my_commands.py index 45a69a20..7b6633f7 100644 --- a/tests/test_api/test_methods/test_get_my_commands.py +++ b/tests/test_api/test_methods/test_get_my_commands.py @@ -1,5 +1,3 @@ -from typing import List - from aiogram.methods import GetMyCommands from aiogram.types import BotCommand from tests.mocked_bot import MockedBot @@ -9,6 +7,6 @@ class TestGetMyCommands: async def test_bot_method(self, bot: MockedBot): prepare_result = bot.add_result_for(GetMyCommands, ok=True, result=None) - response: List[BotCommand] = await bot.get_my_commands() - request = bot.get_request() + response: list[BotCommand] = await bot.get_my_commands() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_my_default_administrator_rights.py b/tests/test_api/test_methods/test_get_my_default_administrator_rights.py index da76a4b2..75136c05 100644 --- a/tests/test_api/test_methods/test_get_my_default_administrator_rights.py +++ b/tests/test_api/test_methods/test_get_my_default_administrator_rights.py @@ -24,5 +24,5 @@ class TestGetMyDefaultAdministratorRights: ) response: ChatAdministratorRights = await bot.get_my_default_administrator_rights() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_my_description.py b/tests/test_api/test_methods/test_get_my_description.py index 061d0837..9c08631a 100644 --- a/tests/test_api/test_methods/test_get_my_description.py +++ b/tests/test_api/test_methods/test_get_my_description.py @@ -10,5 +10,5 @@ class TestGetMyDescription: ) response: BotDescription = await bot.get_my_description() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_my_short_description.py b/tests/test_api/test_methods/test_get_my_short_description.py index f0d4a3ac..7f155a45 100644 --- a/tests/test_api/test_methods/test_get_my_short_description.py +++ b/tests/test_api/test_methods/test_get_my_short_description.py @@ -10,5 +10,5 @@ class TestGetMyShortDescription: ) response: BotShortDescription = await bot.get_my_short_description() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_my_star_balance.py b/tests/test_api/test_methods/test_get_my_star_balance.py index 7c9edd21..22434e38 100644 --- a/tests/test_api/test_methods/test_get_my_star_balance.py +++ b/tests/test_api/test_methods/test_get_my_star_balance.py @@ -14,5 +14,5 @@ class TestGetMyStarBalance: ) response: StarAmount = await bot.get_my_star_balance() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_star_transactions.py b/tests/test_api/test_methods/test_get_star_transactions.py index d7d37374..9429f12f 100644 --- a/tests/test_api/test_methods/test_get_star_transactions.py +++ b/tests/test_api/test_methods/test_get_star_transactions.py @@ -3,7 +3,6 @@ from datetime import datetime from aiogram.enums import TransactionPartnerUserTransactionTypeEnum from aiogram.methods import GetStarTransactions from aiogram.types import ( - File, StarTransaction, StarTransactions, TransactionPartnerUser, @@ -45,5 +44,5 @@ class TestGetStarTransactions: ) response: StarTransactions = await bot.get_star_transactions(limit=10, offset=0) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_sticker_set.py b/tests/test_api/test_methods/test_get_sticker_set.py index 8b643b25..4881e6c5 100644 --- a/tests/test_api/test_methods/test_get_sticker_set.py +++ b/tests/test_api/test_methods/test_get_sticker_set.py @@ -29,5 +29,5 @@ class TestGetStickerSet: ) response: StickerSet = await bot.get_sticker_set(name="test") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_updates.py b/tests/test_api/test_methods/test_get_updates.py index f9249e03..7033c616 100644 --- a/tests/test_api/test_methods/test_get_updates.py +++ b/tests/test_api/test_methods/test_get_updates.py @@ -1,5 +1,3 @@ -from typing import List - from aiogram.methods import GetUpdates from aiogram.types import Update from tests.mocked_bot import MockedBot @@ -9,6 +7,6 @@ class TestGetUpdates: async def test_bot_method(self, bot: MockedBot): prepare_result = bot.add_result_for(GetUpdates, ok=True, result=[Update(update_id=42)]) - response: List[Update] = await bot.get_updates() - request = bot.get_request() + response: list[Update] = await bot.get_updates() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_url.py b/tests/test_api/test_methods/test_get_url.py index 0c5cddc5..87e25487 100644 --- a/tests/test_api/test_methods/test_get_url.py +++ b/tests/test_api/test_methods/test_get_url.py @@ -1,5 +1,4 @@ import datetime -from typing import Optional import pytest @@ -31,9 +30,9 @@ class TestGetMessageUrl: bot: MockedBot, chat_type: str, chat_id: int, - chat_username: Optional[str], + chat_username: str | None, force_private: bool, - expected_result: Optional[str], + expected_result: str | None, ): fake_chat = Chat(id=chat_id, username=chat_username, type=chat_type) fake_message_id = 10 @@ -80,11 +79,11 @@ class TestGetMessageUrl: def test_get_url_if_topic_message( self, bot: MockedBot, - chat_username: Optional[str], + chat_username: str | None, force_private: bool, include_thread_id: bool, - fake_thread_id_topic: Optional[int], - expected_result: Optional[str], + fake_thread_id_topic: int | None, + expected_result: str | None, ): fake_message_id = 10 fake_chat_id = -1001234567890 diff --git a/tests/test_api/test_methods/test_get_user_gifts.py b/tests/test_api/test_methods/test_get_user_gifts.py index 38430c0e..b9b6c076 100644 --- a/tests/test_api/test_methods/test_get_user_gifts.py +++ b/tests/test_api/test_methods/test_get_user_gifts.py @@ -37,5 +37,5 @@ class TestGetUserGifts: user_id=42, limit=10, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_user_profile_audios.py b/tests/test_api/test_methods/test_get_user_profile_audios.py new file mode 100644 index 00000000..b4f08905 --- /dev/null +++ b/tests/test_api/test_methods/test_get_user_profile_audios.py @@ -0,0 +1,19 @@ +from aiogram.methods import GetUserProfileAudios +from aiogram.types import Audio, UserProfileAudios +from tests.mocked_bot import MockedBot + + +class TestGetUserProfileAudios: + async def test_bot_method(self, bot: MockedBot): + prepare_result = bot.add_result_for( + GetUserProfileAudios, + ok=True, + result=UserProfileAudios( + total_count=1, + audios=[Audio(file_id="file_id", file_unique_id="file_unique_id", duration=120)], + ), + ) + + response: UserProfileAudios = await bot.get_user_profile_audios(user_id=42) + bot.get_request() + assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_user_profile_photos.py b/tests/test_api/test_methods/test_get_user_profile_photos.py index 1b9fad5d..df61db63 100644 --- a/tests/test_api/test_methods/test_get_user_profile_photos.py +++ b/tests/test_api/test_methods/test_get_user_profile_photos.py @@ -17,5 +17,5 @@ class TestGetUserProfilePhotos: ) response: UserProfilePhotos = await bot.get_user_profile_photos(user_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_webhook_info.py b/tests/test_api/test_methods/test_get_webhook_info.py index 20ef27e0..29f392ef 100644 --- a/tests/test_api/test_methods/test_get_webhook_info.py +++ b/tests/test_api/test_methods/test_get_webhook_info.py @@ -14,5 +14,5 @@ class TestGetWebhookInfo: ) response: WebhookInfo = await bot.get_webhook_info() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_gift_premium_subscription.py b/tests/test_api/test_methods/test_gift_premium_subscription.py index c3bcc948..bd60ec13 100644 --- a/tests/test_api/test_methods/test_gift_premium_subscription.py +++ b/tests/test_api/test_methods/test_gift_premium_subscription.py @@ -12,5 +12,5 @@ class TestGiftPremiumSubscription: star_count=1000, text="Enjoy your premium subscription!", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_hide_general_forum_topic.py b/tests/test_api/test_methods/test_hide_general_forum_topic.py index 8c6163aa..259fe03b 100644 --- a/tests/test_api/test_methods/test_hide_general_forum_topic.py +++ b/tests/test_api/test_methods/test_hide_general_forum_topic.py @@ -7,5 +7,5 @@ class TestHideGeneralForumTopic: prepare_result = bot.add_result_for(HideGeneralForumTopic, ok=True, result=True) response: bool = await bot.hide_general_forum_topic(chat_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_leave_chat.py b/tests/test_api/test_methods/test_leave_chat.py index 56c87bac..fc4b33d2 100644 --- a/tests/test_api/test_methods/test_leave_chat.py +++ b/tests/test_api/test_methods/test_leave_chat.py @@ -7,5 +7,5 @@ class TestLeaveChat: prepare_result = bot.add_result_for(LeaveChat, ok=True, result=True) response: bool = await bot.leave_chat(chat_id=-42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_log_out.py b/tests/test_api/test_methods/test_log_out.py index 2c23dede..0f1462e1 100644 --- a/tests/test_api/test_methods/test_log_out.py +++ b/tests/test_api/test_methods/test_log_out.py @@ -7,5 +7,5 @@ class TestLogOut: prepare_result = bot.add_result_for(LogOut, ok=True, result=True) response: bool = await bot.log_out() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_pin_chat_message.py b/tests/test_api/test_methods/test_pin_chat_message.py index 7ce4e613..f9f44fff 100644 --- a/tests/test_api/test_methods/test_pin_chat_message.py +++ b/tests/test_api/test_methods/test_pin_chat_message.py @@ -7,5 +7,5 @@ class TestPinChatMessage: prepare_result = bot.add_result_for(PinChatMessage, ok=True, result=True) response: bool = await bot.pin_chat_message(chat_id=-42, message_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_post_story.py b/tests/test_api/test_methods/test_post_story.py index 0dd74cc4..3a37e373 100644 --- a/tests/test_api/test_methods/test_post_story.py +++ b/tests/test_api/test_methods/test_post_story.py @@ -1,5 +1,3 @@ -import datetime - from aiogram.methods import PostStory from aiogram.types import Chat, InputStoryContentPhoto, Story from tests.mocked_bot import MockedBot @@ -22,5 +20,5 @@ class TestPostStory: active_period=6 * 3600, # 6 hours caption="Test story caption", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_promote_chat_member.py b/tests/test_api/test_methods/test_promote_chat_member.py index 93a3a1d0..ee3b7f4e 100644 --- a/tests/test_api/test_methods/test_promote_chat_member.py +++ b/tests/test_api/test_methods/test_promote_chat_member.py @@ -7,5 +7,5 @@ class TestPromoteChatMember: prepare_result = bot.add_result_for(PromoteChatMember, ok=True, result=True) response: bool = await bot.promote_chat_member(chat_id=-42, user_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_read_business_message.py b/tests/test_api/test_methods/test_read_business_message.py index 73db813f..0f3b2db7 100644 --- a/tests/test_api/test_methods/test_read_business_message.py +++ b/tests/test_api/test_methods/test_read_business_message.py @@ -9,5 +9,5 @@ class TestReadBusinessMessage: response: bool = await bot.read_business_message( business_connection_id="test_connection_id", chat_id=123456789, message_id=42 ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_refund_star_payment.py b/tests/test_api/test_methods/test_refund_star_payment.py index a39e55ca..989d1cc4 100644 --- a/tests/test_api/test_methods/test_refund_star_payment.py +++ b/tests/test_api/test_methods/test_refund_star_payment.py @@ -10,5 +10,5 @@ class TestRefundStarPayment: user_id=42, telegram_payment_charge_id="12345", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_remove_business_account_profile_photo.py b/tests/test_api/test_methods/test_remove_business_account_profile_photo.py index d951f9e5..8ba1b691 100644 --- a/tests/test_api/test_methods/test_remove_business_account_profile_photo.py +++ b/tests/test_api/test_methods/test_remove_business_account_profile_photo.py @@ -11,5 +11,5 @@ class TestRemoveBusinessAccountProfilePhoto: response: bool = await bot.remove_business_account_profile_photo( business_connection_id="test_connection_id", is_public=True ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_remove_chat_verification.py b/tests/test_api/test_methods/test_remove_chat_verification.py index 29186300..242daadb 100644 --- a/tests/test_api/test_methods/test_remove_chat_verification.py +++ b/tests/test_api/test_methods/test_remove_chat_verification.py @@ -1,5 +1,4 @@ -from aiogram.methods import RemoveChatVerification, VerifyChat -from aiogram.types import Poll +from aiogram.methods import RemoveChatVerification from tests.mocked_bot import MockedBot @@ -8,5 +7,5 @@ class TestRemoveChatVerification: prepare_result = bot.add_result_for(RemoveChatVerification, ok=True, result=True) response: bool = await bot.remove_chat_verification(chat_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_remove_my_profile_photo.py b/tests/test_api/test_methods/test_remove_my_profile_photo.py new file mode 100644 index 00000000..9be7b4b1 --- /dev/null +++ b/tests/test_api/test_methods/test_remove_my_profile_photo.py @@ -0,0 +1,11 @@ +from aiogram.methods import RemoveMyProfilePhoto +from tests.mocked_bot import MockedBot + + +class TestRemoveMyProfilePhoto: + async def test_bot_method(self, bot: MockedBot): + prepare_result = bot.add_result_for(RemoveMyProfilePhoto, ok=True, result=True) + + response: bool = await bot.remove_my_profile_photo() + bot.get_request() + assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_remove_user_verification.py b/tests/test_api/test_methods/test_remove_user_verification.py index 4d405c82..d5e32484 100644 --- a/tests/test_api/test_methods/test_remove_user_verification.py +++ b/tests/test_api/test_methods/test_remove_user_verification.py @@ -1,5 +1,4 @@ -from aiogram.methods import RemoveUserVerification, VerifyChat -from aiogram.types import Poll +from aiogram.methods import RemoveUserVerification from tests.mocked_bot import MockedBot @@ -8,5 +7,5 @@ class TestRemoveUserVerification: prepare_result = bot.add_result_for(RemoveUserVerification, ok=True, result=True) response: bool = await bot.remove_user_verification(user_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_reopen_forum_topic.py b/tests/test_api/test_methods/test_reopen_forum_topic.py index 35cd6f4c..b71c5484 100644 --- a/tests/test_api/test_methods/test_reopen_forum_topic.py +++ b/tests/test_api/test_methods/test_reopen_forum_topic.py @@ -10,5 +10,5 @@ class TestReopenForumTopic: chat_id=42, message_thread_id=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_reopen_general_forum_topic.py b/tests/test_api/test_methods/test_reopen_general_forum_topic.py index e7c08b89..8e35855f 100644 --- a/tests/test_api/test_methods/test_reopen_general_forum_topic.py +++ b/tests/test_api/test_methods/test_reopen_general_forum_topic.py @@ -7,5 +7,5 @@ class TestReopenGeneralForumTopic: prepare_result = bot.add_result_for(ReopenGeneralForumTopic, ok=True, result=True) response: bool = await bot.reopen_general_forum_topic(chat_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_replace_sticker_in_set.py b/tests/test_api/test_methods/test_replace_sticker_in_set.py index e556919d..50e066bc 100644 --- a/tests/test_api/test_methods/test_replace_sticker_in_set.py +++ b/tests/test_api/test_methods/test_replace_sticker_in_set.py @@ -17,5 +17,5 @@ class TestReplaceStickerInSet: emoji_list=["test"], ), ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_repost_story.py b/tests/test_api/test_methods/test_repost_story.py index f00b9f11..0fab2c2f 100644 --- a/tests/test_api/test_methods/test_repost_story.py +++ b/tests/test_api/test_methods/test_repost_story.py @@ -20,5 +20,5 @@ class TestRepostStory: from_story_id=456, active_period=6 * 3600, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_restrict_chat_member.py b/tests/test_api/test_methods/test_restrict_chat_member.py index e845b400..96d7a676 100644 --- a/tests/test_api/test_methods/test_restrict_chat_member.py +++ b/tests/test_api/test_methods/test_restrict_chat_member.py @@ -10,5 +10,5 @@ class TestRestrictChatMember: response: bool = await bot.restrict_chat_member( chat_id=-42, user_id=42, permissions=ChatPermissions() ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_revoke_chat_invite_link.py b/tests/test_api/test_methods/test_revoke_chat_invite_link.py index cbaab593..cbc51e2c 100644 --- a/tests/test_api/test_methods/test_revoke_chat_invite_link.py +++ b/tests/test_api/test_methods/test_revoke_chat_invite_link.py @@ -21,5 +21,5 @@ class TestRevokeChatInviteLink: chat_id=-42, invite_link="https://t.me/username", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_save_prepared_inline_message.py b/tests/test_api/test_methods/test_save_prepared_inline_message.py index 2a1d1f4d..6cf5b9f8 100644 --- a/tests/test_api/test_methods/test_save_prepared_inline_message.py +++ b/tests/test_api/test_methods/test_save_prepared_inline_message.py @@ -30,5 +30,5 @@ class TestSavePreparedInlineMessage: ), ), ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_animation.py b/tests/test_api/test_methods/test_send_animation.py index 569d663d..9f08eb8c 100644 --- a/tests/test_api/test_methods/test_send_animation.py +++ b/tests/test_api/test_methods/test_send_animation.py @@ -21,5 +21,5 @@ class TestSendAnimation: ) response: Message = await bot.send_animation(chat_id=42, animation="file id") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_audio.py b/tests/test_api/test_methods/test_send_audio.py index 63cdaf10..8ba52624 100644 --- a/tests/test_api/test_methods/test_send_audio.py +++ b/tests/test_api/test_methods/test_send_audio.py @@ -19,5 +19,5 @@ class TestSendAudio: ) response: Message = await bot.send_audio(chat_id=42, audio="file id") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_chat_action.py b/tests/test_api/test_methods/test_send_chat_action.py index 22f2805d..95835f7b 100644 --- a/tests/test_api/test_methods/test_send_chat_action.py +++ b/tests/test_api/test_methods/test_send_chat_action.py @@ -8,5 +8,5 @@ class TestSendChatAction: prepare_result = bot.add_result_for(SendChatAction, ok=True, result=True) response: bool = await bot.send_chat_action(chat_id=42, action=ChatAction.TYPING) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_checklist.py b/tests/test_api/test_methods/test_send_checklist.py index 40c279f3..b4677672 100644 --- a/tests/test_api/test_methods/test_send_checklist.py +++ b/tests/test_api/test_methods/test_send_checklist.py @@ -30,5 +30,5 @@ class TestSendChecklist: chat_id=42, checklist=checklist, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_contact.py b/tests/test_api/test_methods/test_send_contact.py index e3f14496..8cd1f1fc 100644 --- a/tests/test_api/test_methods/test_send_contact.py +++ b/tests/test_api/test_methods/test_send_contact.py @@ -21,5 +21,5 @@ class TestSendContact: response: Message = await bot.send_contact( chat_id=42, phone_number="911", first_name="911" ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_dice.py b/tests/test_api/test_methods/test_send_dice.py index 66daf7d8..00dcbdb9 100644 --- a/tests/test_api/test_methods/test_send_dice.py +++ b/tests/test_api/test_methods/test_send_dice.py @@ -8,5 +8,5 @@ class TestSendDice: prepare_result = bot.add_result_for(SendDice, ok=True, result=None) response: Message = await bot.send_dice(chat_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_document.py b/tests/test_api/test_methods/test_send_document.py index 9a96a6ac..17dd9cf1 100644 --- a/tests/test_api/test_methods/test_send_document.py +++ b/tests/test_api/test_methods/test_send_document.py @@ -19,5 +19,5 @@ class TestSendDocument: ) response: Message = await bot.send_document(chat_id=42, document="file id") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_game.py b/tests/test_api/test_methods/test_send_game.py index 99621444..dfe8d10f 100644 --- a/tests/test_api/test_methods/test_send_game.py +++ b/tests/test_api/test_methods/test_send_game.py @@ -25,5 +25,5 @@ class TestSendGame: ) response: Message = await bot.send_game(chat_id=42, game_short_name="game") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_gift.py b/tests/test_api/test_methods/test_send_gift.py index 5654d49d..6acd477d 100644 --- a/tests/test_api/test_methods/test_send_gift.py +++ b/tests/test_api/test_methods/test_send_gift.py @@ -7,5 +7,5 @@ class TestSendGift: prepare_result = bot.add_result_for(SendGift, ok=True, result=True) response: bool = await bot.send_gift(user_id=42, gift_id="gift_id") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_invoice.py b/tests/test_api/test_methods/test_send_invoice.py index b65ffd89..a079fe14 100644 --- a/tests/test_api/test_methods/test_send_invoice.py +++ b/tests/test_api/test_methods/test_send_invoice.py @@ -34,5 +34,5 @@ class TestSendInvoice: currency="BTC", prices=[LabeledPrice(amount=1, label="test")], ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_location.py b/tests/test_api/test_methods/test_send_location.py index be17dbec..c6db81a5 100644 --- a/tests/test_api/test_methods/test_send_location.py +++ b/tests/test_api/test_methods/test_send_location.py @@ -19,5 +19,5 @@ class TestSendLocation: ) response: Message = await bot.send_location(chat_id=42, latitude=3.14, longitude=3.14) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_media_group.py b/tests/test_api/test_methods/test_send_media_group.py index 1ac4cf42..63d8f7ab 100644 --- a/tests/test_api/test_methods/test_send_media_group.py +++ b/tests/test_api/test_methods/test_send_media_group.py @@ -1,5 +1,4 @@ import datetime -from typing import List from aiogram.methods import SendMediaGroup from aiogram.types import ( @@ -45,12 +44,12 @@ class TestSendMediaGroup: ], ) - response: List[Message] = await bot.send_media_group( + response: list[Message] = await bot.send_media_group( chat_id=42, media=[ InputMediaPhoto(media="file id"), InputMediaVideo(media=BufferedInputFile(b"", "video.mp4")), ], ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_message.py b/tests/test_api/test_methods/test_send_message.py index f16267c2..3a612148 100644 --- a/tests/test_api/test_methods/test_send_message.py +++ b/tests/test_api/test_methods/test_send_message.py @@ -19,7 +19,7 @@ class TestSendMessage: ) response: Message = await bot.send_message(chat_id=42, text="test") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result async def test_force_reply(self): diff --git a/tests/test_api/test_methods/test_send_message_draft.py b/tests/test_api/test_methods/test_send_message_draft.py index cdb775fd..5edf790b 100644 --- a/tests/test_api/test_methods/test_send_message_draft.py +++ b/tests/test_api/test_methods/test_send_message_draft.py @@ -15,5 +15,5 @@ class TestSendMessageDraft: draft_id=1, text="test draft", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_paid_media.py b/tests/test_api/test_methods/test_send_paid_media.py index 37ff2717..0039d88a 100755 --- a/tests/test_api/test_methods/test_send_paid_media.py +++ b/tests/test_api/test_methods/test_send_paid_media.py @@ -39,5 +39,5 @@ class TestSendPaidMedia: response: Message = await bot.send_paid_media( chat_id=-42, star_count=1, media=[InputPaidMediaPhoto(media="file_id")] ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_photo.py b/tests/test_api/test_methods/test_send_photo.py index f1ccc638..07eb2080 100644 --- a/tests/test_api/test_methods/test_send_photo.py +++ b/tests/test_api/test_methods/test_send_photo.py @@ -21,5 +21,5 @@ class TestSendPhoto: ) response: Message = await bot.send_photo(chat_id=42, photo="file id") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_poll.py b/tests/test_api/test_methods/test_send_poll.py index d070643d..27288b51 100644 --- a/tests/test_api/test_methods/test_send_poll.py +++ b/tests/test_api/test_methods/test_send_poll.py @@ -34,5 +34,5 @@ class TestSendPoll: response: Message = await bot.send_poll( chat_id=42, question="Q?", options=["A", "B"], correct_option_id=0, type="quiz" ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_sticker.py b/tests/test_api/test_methods/test_send_sticker.py index 9e553e4c..add81076 100644 --- a/tests/test_api/test_methods/test_send_sticker.py +++ b/tests/test_api/test_methods/test_send_sticker.py @@ -27,5 +27,5 @@ class TestSendSticker: ) response: Message = await bot.send_sticker(chat_id=42, sticker="file id") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_venue.py b/tests/test_api/test_methods/test_send_venue.py index 3b910851..8eb733af 100644 --- a/tests/test_api/test_methods/test_send_venue.py +++ b/tests/test_api/test_methods/test_send_venue.py @@ -31,5 +31,5 @@ class TestSendVenue: address="Under the stairs, 4 Privet Drive, " "Little Whinging, Surrey, England, Great Britain", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_video.py b/tests/test_api/test_methods/test_send_video.py index 31bec569..3e412084 100644 --- a/tests/test_api/test_methods/test_send_video.py +++ b/tests/test_api/test_methods/test_send_video.py @@ -21,5 +21,5 @@ class TestSendVideo: ) response: Message = await bot.send_video(chat_id=42, video="file id") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_video_note.py b/tests/test_api/test_methods/test_send_video_note.py index f78fdce7..6bc52ddf 100644 --- a/tests/test_api/test_methods/test_send_video_note.py +++ b/tests/test_api/test_methods/test_send_video_note.py @@ -25,5 +25,5 @@ class TestSendVideoNote: video_note="file id", thumbnail=BufferedInputFile(b"", "file.png"), ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_send_voice.py b/tests/test_api/test_methods/test_send_voice.py index 11ff9d85..40e36df0 100644 --- a/tests/test_api/test_methods/test_send_voice.py +++ b/tests/test_api/test_methods/test_send_voice.py @@ -19,5 +19,5 @@ class TestSendVoice: ) response: Message = await bot.send_voice(chat_id=42, voice="file id") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_business_account_bio.py b/tests/test_api/test_methods/test_set_business_account_bio.py index 5041122b..e2cde8bc 100644 --- a/tests/test_api/test_methods/test_set_business_account_bio.py +++ b/tests/test_api/test_methods/test_set_business_account_bio.py @@ -10,5 +10,5 @@ class TestSetBusinessAccountBio: business_connection_id="test_connection_id", bio="This is a test bio for the business account", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_business_account_gift_settings.py b/tests/test_api/test_methods/test_set_business_account_gift_settings.py index 9aaa7201..bd98a675 100644 --- a/tests/test_api/test_methods/test_set_business_account_gift_settings.py +++ b/tests/test_api/test_methods/test_set_business_account_gift_settings.py @@ -18,5 +18,5 @@ class TestSetBusinessAccountGiftSettings: premium_subscription=True, ), ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_business_account_name.py b/tests/test_api/test_methods/test_set_business_account_name.py index 5098ad34..77a437b1 100644 --- a/tests/test_api/test_methods/test_set_business_account_name.py +++ b/tests/test_api/test_methods/test_set_business_account_name.py @@ -11,5 +11,5 @@ class TestSetBusinessAccountName: first_name="Test Business", last_name="Account Name", ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_business_account_profile_photo.py b/tests/test_api/test_methods/test_set_business_account_profile_photo.py index 99808985..b7dbec3c 100644 --- a/tests/test_api/test_methods/test_set_business_account_profile_photo.py +++ b/tests/test_api/test_methods/test_set_business_account_profile_photo.py @@ -12,5 +12,5 @@ class TestSetBusinessAccountProfilePhoto: photo=InputProfilePhotoStatic(photo="test_photo_file_id"), is_public=True, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_business_account_username.py b/tests/test_api/test_methods/test_set_business_account_username.py index db912ca3..a7b050f5 100644 --- a/tests/test_api/test_methods/test_set_business_account_username.py +++ b/tests/test_api/test_methods/test_set_business_account_username.py @@ -9,5 +9,5 @@ class TestSetBusinessAccountUsername: response: bool = await bot.set_business_account_username( business_connection_id="test_connection_id", username="test_business_username" ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_chat_administrator_custom_title.py b/tests/test_api/test_methods/test_set_chat_administrator_custom_title.py index 7ac13d36..9aabcbb5 100644 --- a/tests/test_api/test_methods/test_set_chat_administrator_custom_title.py +++ b/tests/test_api/test_methods/test_set_chat_administrator_custom_title.py @@ -9,5 +9,5 @@ class TestSetChatTitle: response: bool = await bot.set_chat_administrator_custom_title( chat_id=-42, user_id=42, custom_title="test chat" ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_chat_description.py b/tests/test_api/test_methods/test_set_chat_description.py index 7f4d8404..95385606 100644 --- a/tests/test_api/test_methods/test_set_chat_description.py +++ b/tests/test_api/test_methods/test_set_chat_description.py @@ -7,5 +7,5 @@ class TestSetChatDescription: prepare_result = bot.add_result_for(SetChatDescription, ok=True, result=True) response: bool = await bot.set_chat_description(chat_id=-42, description="awesome chat") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_chat_menu_button.py b/tests/test_api/test_methods/test_set_chat_menu_button.py index 86d482cf..5ab8c51f 100644 --- a/tests/test_api/test_methods/test_set_chat_menu_button.py +++ b/tests/test_api/test_methods/test_set_chat_menu_button.py @@ -7,5 +7,5 @@ class TestSetChatMenuButton: prepare_result = bot.add_result_for(SetChatMenuButton, ok=True, result=True) response: bool = await bot.set_chat_menu_button() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_chat_permissions.py b/tests/test_api/test_methods/test_set_chat_permissions.py index 11cb8743..1cb41c91 100644 --- a/tests/test_api/test_methods/test_set_chat_permissions.py +++ b/tests/test_api/test_methods/test_set_chat_permissions.py @@ -10,5 +10,5 @@ class TestSetChatPermissions: response: bool = await bot.set_chat_permissions( chat_id=-42, permissions=ChatPermissions(can_send_messages=False) ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_chat_photo.py b/tests/test_api/test_methods/test_set_chat_photo.py index d69aacea..8a4057a7 100644 --- a/tests/test_api/test_methods/test_set_chat_photo.py +++ b/tests/test_api/test_methods/test_set_chat_photo.py @@ -10,5 +10,5 @@ class TestSetChatPhoto: response: bool = await bot.set_chat_photo( chat_id=-42, photo=BufferedInputFile(b"", filename="file.png") ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_chat_sticker_set.py b/tests/test_api/test_methods/test_set_chat_sticker_set.py index 78e692bc..44f776ac 100644 --- a/tests/test_api/test_methods/test_set_chat_sticker_set.py +++ b/tests/test_api/test_methods/test_set_chat_sticker_set.py @@ -7,5 +7,5 @@ class TestSetChatStickerSet: prepare_result = bot.add_result_for(SetChatStickerSet, ok=True, result=True) response: bool = await bot.set_chat_sticker_set(chat_id=-42, sticker_set_name="test") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_chat_title.py b/tests/test_api/test_methods/test_set_chat_title.py index c9a9c087..056b0f2b 100644 --- a/tests/test_api/test_methods/test_set_chat_title.py +++ b/tests/test_api/test_methods/test_set_chat_title.py @@ -7,5 +7,5 @@ class TestSetChatTitle: prepare_result = bot.add_result_for(SetChatTitle, ok=True, result=True) response: bool = await bot.set_chat_title(chat_id=-42, title="test chat") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_custom_emoji_sticker_set_thumbnail.py b/tests/test_api/test_methods/test_set_custom_emoji_sticker_set_thumbnail.py index b78e8c84..4b92229f 100644 --- a/tests/test_api/test_methods/test_set_custom_emoji_sticker_set_thumbnail.py +++ b/tests/test_api/test_methods/test_set_custom_emoji_sticker_set_thumbnail.py @@ -11,5 +11,5 @@ class TestSetCustomEmojiStickerSetThumbnail: response: bool = await bot.set_custom_emoji_sticker_set_thumbnail( name="test", custom_emoji_id="custom id" ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_game_score.py b/tests/test_api/test_methods/test_set_game_score.py index c2f1a2fa..ea1db0c1 100644 --- a/tests/test_api/test_methods/test_set_game_score.py +++ b/tests/test_api/test_methods/test_set_game_score.py @@ -1,5 +1,3 @@ -from typing import Union - from aiogram.methods import SetGameScore from aiogram.types import Message from tests.mocked_bot import MockedBot @@ -9,8 +7,8 @@ class TestSetGameScore: async def test_bot_method(self, bot: MockedBot): prepare_result = bot.add_result_for(SetGameScore, ok=True, result=True) - response: Union[Message, bool] = await bot.set_game_score( + response: Message | bool = await bot.set_game_score( user_id=42, score=100500, inline_message_id="inline message" ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_my_commands.py b/tests/test_api/test_methods/test_set_my_commands.py index 6a42aeed..fa5fe3c3 100644 --- a/tests/test_api/test_methods/test_set_my_commands.py +++ b/tests/test_api/test_methods/test_set_my_commands.py @@ -9,5 +9,5 @@ class TestSetMyCommands: response: bool = await bot.set_my_commands( commands=[], ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_my_default_administrator_rights.py b/tests/test_api/test_methods/test_set_my_default_administrator_rights.py index 157bdd38..3f42071e 100644 --- a/tests/test_api/test_methods/test_set_my_default_administrator_rights.py +++ b/tests/test_api/test_methods/test_set_my_default_administrator_rights.py @@ -7,5 +7,5 @@ class TestSetMyDefaultAdministratorRights: prepare_result = bot.add_result_for(SetMyDefaultAdministratorRights, ok=True, result=True) response: bool = await bot.set_my_default_administrator_rights() - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_my_description.py b/tests/test_api/test_methods/test_set_my_description.py index 2c0dc6f3..a2134cbc 100644 --- a/tests/test_api/test_methods/test_set_my_description.py +++ b/tests/test_api/test_methods/test_set_my_description.py @@ -7,5 +7,5 @@ class TestSetMyDescription: prepare_result = bot.add_result_for(SetMyDescription, ok=True, result=True) response: bool = await bot.set_my_description(description="Test") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_my_profile_photo.py b/tests/test_api/test_methods/test_set_my_profile_photo.py new file mode 100644 index 00000000..2bf596f5 --- /dev/null +++ b/tests/test_api/test_methods/test_set_my_profile_photo.py @@ -0,0 +1,14 @@ +from aiogram.methods import SetMyProfilePhoto +from aiogram.types import InputProfilePhotoStatic +from tests.mocked_bot import MockedBot + + +class TestSetMyProfilePhoto: + async def test_bot_method(self, bot: MockedBot): + prepare_result = bot.add_result_for(SetMyProfilePhoto, ok=True, result=True) + + response: bool = await bot.set_my_profile_photo( + photo=InputProfilePhotoStatic(photo="file_id") + ) + bot.get_request() + assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_my_short_description.py b/tests/test_api/test_methods/test_set_my_short_description.py index 6bebc09b..729b2670 100644 --- a/tests/test_api/test_methods/test_set_my_short_description.py +++ b/tests/test_api/test_methods/test_set_my_short_description.py @@ -7,5 +7,5 @@ class TestSetMyShortDescription: prepare_result = bot.add_result_for(SetMyShortDescription, ok=True, result=True) response: bool = await bot.set_my_short_description(short_description="Test") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_passport_data_errors.py b/tests/test_api/test_methods/test_set_passport_data_errors.py index c3afe7de..e21b48ec 100644 --- a/tests/test_api/test_methods/test_set_passport_data_errors.py +++ b/tests/test_api/test_methods/test_set_passport_data_errors.py @@ -17,5 +17,5 @@ class TestSetPassportDataErrors: ) ], ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_sticker_emoji_list.py b/tests/test_api/test_methods/test_set_sticker_emoji_list.py index 82acb7db..97266d8e 100644 --- a/tests/test_api/test_methods/test_set_sticker_emoji_list.py +++ b/tests/test_api/test_methods/test_set_sticker_emoji_list.py @@ -7,5 +7,5 @@ class TestSetStickerEmojiList: prepare_result = bot.add_result_for(SetStickerEmojiList, ok=True, result=True) response: bool = await bot.set_sticker_emoji_list(sticker="sticker id", emoji_list=["X"]) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_sticker_keywords.py b/tests/test_api/test_methods/test_set_sticker_keywords.py index 4c279aab..21f70b2c 100644 --- a/tests/test_api/test_methods/test_set_sticker_keywords.py +++ b/tests/test_api/test_methods/test_set_sticker_keywords.py @@ -7,5 +7,5 @@ class TestSetStickerKeywords: prepare_result = bot.add_result_for(SetStickerKeywords, ok=True, result=True) response: bool = await bot.set_sticker_keywords(sticker="sticker id", keywords=["X"]) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_sticker_mask_position.py b/tests/test_api/test_methods/test_set_sticker_mask_position.py index ac9f9e0a..3f842ceb 100644 --- a/tests/test_api/test_methods/test_set_sticker_mask_position.py +++ b/tests/test_api/test_methods/test_set_sticker_mask_position.py @@ -7,5 +7,5 @@ class TestSetStickerEmojiList: prepare_result = bot.add_result_for(SetStickerMaskPosition, ok=True, result=True) response: bool = await bot.set_sticker_mask_position(sticker="sticker id") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_sticker_position_in_set.py b/tests/test_api/test_methods/test_set_sticker_position_in_set.py index 2640de82..2646d24d 100644 --- a/tests/test_api/test_methods/test_set_sticker_position_in_set.py +++ b/tests/test_api/test_methods/test_set_sticker_position_in_set.py @@ -7,5 +7,5 @@ class TestSetStickerPositionInSet: prepare_result = bot.add_result_for(SetStickerPositionInSet, ok=True, result=True) response: bool = await bot.set_sticker_position_in_set(sticker="sticker", position=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_sticker_set_thumbnail.py b/tests/test_api/test_methods/test_set_sticker_set_thumbnail.py index 5d4b745b..45dc9700 100644 --- a/tests/test_api/test_methods/test_set_sticker_set_thumbnail.py +++ b/tests/test_api/test_methods/test_set_sticker_set_thumbnail.py @@ -10,5 +10,5 @@ class TestSetStickerSetThumbnail: response: bool = await bot.set_sticker_set_thumbnail( name="test", format=StickerFormat.STATIC, user_id=42 ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_sticker_set_title.py b/tests/test_api/test_methods/test_set_sticker_set_title.py index a4eb9d09..6c0b7d21 100644 --- a/tests/test_api/test_methods/test_set_sticker_set_title.py +++ b/tests/test_api/test_methods/test_set_sticker_set_title.py @@ -7,5 +7,5 @@ class TestSetStickerSetTitle: prepare_result = bot.add_result_for(SetStickerSetTitle, ok=True, result=True) response: bool = await bot.set_sticker_set_title(name="test", title="Test") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_user_emoji_status.py b/tests/test_api/test_methods/test_set_user_emoji_status.py index 4e661eaf..1a03d5f7 100644 --- a/tests/test_api/test_methods/test_set_user_emoji_status.py +++ b/tests/test_api/test_methods/test_set_user_emoji_status.py @@ -13,5 +13,5 @@ class TestSetUserEmojiStatus: emoji_status_custom_emoji_id="emoji_status_custom_emoji_id", emoji_status_expiration_date=datetime.now() + timedelta(days=1), ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_webhook.py b/tests/test_api/test_methods/test_set_webhook.py index 37f75a9c..dd9f6902 100644 --- a/tests/test_api/test_methods/test_set_webhook.py +++ b/tests/test_api/test_methods/test_set_webhook.py @@ -7,5 +7,5 @@ class TestSetWebhook: prepare_result = bot.add_result_for(SetWebhook, ok=True, result=True) response: bool = await bot.set_webhook(url="https://example.com") - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_stop_message_live_location.py b/tests/test_api/test_methods/test_stop_message_live_location.py index b0b7f397..46792924 100644 --- a/tests/test_api/test_methods/test_stop_message_live_location.py +++ b/tests/test_api/test_methods/test_stop_message_live_location.py @@ -1,5 +1,3 @@ -from typing import Union - from aiogram.methods import StopMessageLiveLocation from aiogram.types import Message from tests.mocked_bot import MockedBot @@ -9,8 +7,8 @@ class TestStopMessageLiveLocation: async def test_bot_method(self, bot: MockedBot): prepare_result = bot.add_result_for(StopMessageLiveLocation, ok=True, result=True) - response: Union[Message, bool] = await bot.stop_message_live_location( + response: Message | bool = await bot.stop_message_live_location( inline_message_id="inline message id" ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_stop_poll.py b/tests/test_api/test_methods/test_stop_poll.py index 5a89e1ed..64d0406c 100644 --- a/tests/test_api/test_methods/test_stop_poll.py +++ b/tests/test_api/test_methods/test_stop_poll.py @@ -22,5 +22,5 @@ class TestStopPoll: ) response: Poll = await bot.stop_poll(chat_id=42, message_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_transfer_business_account_stars.py b/tests/test_api/test_methods/test_transfer_business_account_stars.py index c45b87c6..d8253006 100644 --- a/tests/test_api/test_methods/test_transfer_business_account_stars.py +++ b/tests/test_api/test_methods/test_transfer_business_account_stars.py @@ -9,5 +9,5 @@ class TestTransferBusinessAccountStars: response: bool = await bot.transfer_business_account_stars( business_connection_id="test_connection_id", star_count=100 ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_transfer_gift.py b/tests/test_api/test_methods/test_transfer_gift.py index 8d2221d1..5de5db02 100644 --- a/tests/test_api/test_methods/test_transfer_gift.py +++ b/tests/test_api/test_methods/test_transfer_gift.py @@ -12,5 +12,5 @@ class TestTransferGift: new_owner_chat_id=123456789, star_count=50, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_unban_chat_member.py b/tests/test_api/test_methods/test_unban_chat_member.py index 23f922ad..3a2acfee 100644 --- a/tests/test_api/test_methods/test_unban_chat_member.py +++ b/tests/test_api/test_methods/test_unban_chat_member.py @@ -7,5 +7,5 @@ class TestUnbanChatMember: prepare_result = bot.add_result_for(UnbanChatMember, ok=True, result=True) response: bool = await bot.unban_chat_member(chat_id=-42, user_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_unban_chat_sender_chat.py b/tests/test_api/test_methods/test_unban_chat_sender_chat.py index e23c1b13..0ca89e0a 100755 --- a/tests/test_api/test_methods/test_unban_chat_sender_chat.py +++ b/tests/test_api/test_methods/test_unban_chat_sender_chat.py @@ -10,5 +10,5 @@ class TestUnbanChatSenderChat: chat_id=-42, sender_chat_id=-1337, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_unhide_general_forum_topic.py b/tests/test_api/test_methods/test_unhide_general_forum_topic.py index da830cd6..9de88ad6 100644 --- a/tests/test_api/test_methods/test_unhide_general_forum_topic.py +++ b/tests/test_api/test_methods/test_unhide_general_forum_topic.py @@ -7,5 +7,5 @@ class TestUnhideGeneralForumTopic: prepare_result = bot.add_result_for(UnhideGeneralForumTopic, ok=True, result=True) response: bool = await bot.unhide_general_forum_topic(chat_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_unpin_all_chat_messages.py b/tests/test_api/test_methods/test_unpin_all_chat_messages.py index 64ce1d4c..ba92871d 100644 --- a/tests/test_api/test_methods/test_unpin_all_chat_messages.py +++ b/tests/test_api/test_methods/test_unpin_all_chat_messages.py @@ -9,5 +9,5 @@ class TestUnpinAllChatMessages: response: bool = await bot.unpin_all_chat_messages( chat_id=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_unpin_all_forum_topic_messages.py b/tests/test_api/test_methods/test_unpin_all_forum_topic_messages.py index 045fd926..74171470 100644 --- a/tests/test_api/test_methods/test_unpin_all_forum_topic_messages.py +++ b/tests/test_api/test_methods/test_unpin_all_forum_topic_messages.py @@ -10,5 +10,5 @@ class TestUnpinAllForumTopicMessages: chat_id=42, message_thread_id=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_unpin_all_general_forum_topic_messages.py b/tests/test_api/test_methods/test_unpin_all_general_forum_topic_messages.py index 09a3658e..21325a8f 100644 --- a/tests/test_api/test_methods/test_unpin_all_general_forum_topic_messages.py +++ b/tests/test_api/test_methods/test_unpin_all_general_forum_topic_messages.py @@ -11,5 +11,5 @@ class TestUnpinAllForumTopicMessages: response: bool = await bot.unpin_all_general_forum_topic_messages( chat_id=42, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_unpin_chat_message.py b/tests/test_api/test_methods/test_unpin_chat_message.py index fdf1622d..8506a021 100644 --- a/tests/test_api/test_methods/test_unpin_chat_message.py +++ b/tests/test_api/test_methods/test_unpin_chat_message.py @@ -7,5 +7,5 @@ class TestUnpinChatMessage: prepare_result = bot.add_result_for(UnpinChatMessage, ok=True, result=True) response: bool = await bot.unpin_chat_message(chat_id=-42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_upgrade_gift.py b/tests/test_api/test_methods/test_upgrade_gift.py index f88f8dff..4461b677 100644 --- a/tests/test_api/test_methods/test_upgrade_gift.py +++ b/tests/test_api/test_methods/test_upgrade_gift.py @@ -12,5 +12,5 @@ class TestUpgradeGift: keep_original_details=True, star_count=100, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_upload_sticker_file.py b/tests/test_api/test_methods/test_upload_sticker_file.py index 0823772c..31819054 100644 --- a/tests/test_api/test_methods/test_upload_sticker_file.py +++ b/tests/test_api/test_methods/test_upload_sticker_file.py @@ -15,5 +15,5 @@ class TestUploadStickerFile: sticker=BufferedInputFile(b"", "file.png"), sticker_format=StickerFormat.STATIC, ) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_verify_chat.py b/tests/test_api/test_methods/test_verify_chat.py index 1473b976..17401621 100644 --- a/tests/test_api/test_methods/test_verify_chat.py +++ b/tests/test_api/test_methods/test_verify_chat.py @@ -1,5 +1,4 @@ from aiogram.methods import VerifyChat -from aiogram.types import Poll from tests.mocked_bot import MockedBot @@ -8,5 +7,5 @@ class TestVerifyChat: prepare_result = bot.add_result_for(VerifyChat, ok=True, result=True) response: bool = await bot.verify_chat(chat_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_verify_user.py b/tests/test_api/test_methods/test_verify_user.py index 9bb502ca..b69471a3 100644 --- a/tests/test_api/test_methods/test_verify_user.py +++ b/tests/test_api/test_methods/test_verify_user.py @@ -1,5 +1,4 @@ -from aiogram.methods import VerifyChat, VerifyUser -from aiogram.types import Poll +from aiogram.methods import VerifyUser from tests.mocked_bot import MockedBot @@ -8,5 +7,5 @@ class TestVerifyUser: prepare_result = bot.add_result_for(VerifyUser, ok=True, result=True) response: bool = await bot.verify_user(user_id=42) - request = bot.get_request() + bot.get_request() assert response == prepare_result.result diff --git a/tests/test_api/test_types/test_callback_query.py b/tests/test_api/test_types/test_callback_query.py index f3716fe1..ed45d2b1 100644 --- a/tests/test_api/test_types/test_callback_query.py +++ b/tests/test_api/test_types/test_callback_query.py @@ -8,7 +8,7 @@ class TestCallbackQuery: id="id", from_user=User(id=42, is_bot=False, first_name="name"), chat_instance="chat" ) - kwargs = dict(text="foo", show_alert=True, url="https://foo.bar/", cache_time=123) + kwargs = {"text": "foo", "show_alert": True, "url": "https://foo.bar/", "cache_time": 123} api_method = callback_query.answer(**kwargs) diff --git a/tests/test_api/test_types/test_chat.py b/tests/test_api/test_types/test_chat.py index 14cc3dc2..360b2ee1 100644 --- a/tests/test_api/test_types/test_chat.py +++ b/tests/test_api/test_types/test_chat.py @@ -1,5 +1,3 @@ -from typing import Optional - from pytest import mark, param from aiogram.enums import ChatAction @@ -199,9 +197,9 @@ class TestChat: ) def test_full_name( self, - first: Optional[str], - last: Optional[str], - title: Optional[str], + first: str | None, + last: str | None, + title: str | None, chat_type: str, result: str, ): diff --git a/tests/test_api/test_types/test_chat_join_request.py b/tests/test_api/test_types/test_chat_join_request.py index ec442cb1..3bb1fc3f 100644 --- a/tests/test_api/test_types/test_chat_join_request.py +++ b/tests/test_api/test_types/test_chat_join_request.py @@ -1,5 +1,5 @@ import datetime -from typing import Any, Dict, Type, Union +from typing import Any import pytest @@ -59,45 +59,49 @@ class TestChatJoinRequest: @pytest.mark.parametrize( "alias_for_method,kwargs,method_class", [ - ["answer_animation", dict(animation="animation"), SendAnimation], - ["answer_audio", dict(audio="audio"), SendAudio], - ["answer_contact", dict(phone_number="+000000000000", first_name="Test"), SendContact], - ["answer_document", dict(document="document"), SendDocument], - ["answer_game", dict(game_short_name="game"), SendGame], + ["answer_animation", {"animation": "animation"}, SendAnimation], + ["answer_audio", {"audio": "audio"}, SendAudio], + [ + "answer_contact", + {"phone_number": "+000000000000", "first_name": "Test"}, + SendContact, + ], + ["answer_document", {"document": "document"}, SendDocument], + ["answer_game", {"game_short_name": "game"}, SendGame], [ "answer_invoice", - dict( - title="title", - description="description", - payload="payload", - provider_token="provider_token", - start_parameter="start_parameter", - currency="currency", - prices=[], - ), + { + "title": "title", + "description": "description", + "payload": "payload", + "provider_token": "provider_token", + "start_parameter": "start_parameter", + "currency": "currency", + "prices": [], + }, SendInvoice, ], - ["answer_location", dict(latitude=0.42, longitude=0.42), SendLocation], - ["answer_media_group", dict(media=[]), SendMediaGroup], - ["answer", dict(text="test"), SendMessage], - ["answer_photo", dict(photo="photo"), SendPhoto], - ["answer_poll", dict(question="Q?", options=[]), SendPoll], - ["answer_dice", dict(), SendDice], - ["answer_sticker", dict(sticker="sticker"), SendSticker], - ["answer_sticker", dict(sticker="sticker"), SendSticker], + ["answer_location", {"latitude": 0.42, "longitude": 0.42}, SendLocation], + ["answer_media_group", {"media": []}, SendMediaGroup], + ["answer", {"text": "test"}, SendMessage], + ["answer_photo", {"photo": "photo"}, SendPhoto], + ["answer_poll", {"question": "Q?", "options": []}, SendPoll], + ["answer_dice", {}, SendDice], + ["answer_sticker", {"sticker": "sticker"}, SendSticker], + ["answer_sticker", {"sticker": "sticker"}, SendSticker], [ "answer_venue", - dict( - latitude=0.42, - longitude=0.42, - title="title", - address="address", - ), + { + "latitude": 0.42, + "longitude": 0.42, + "title": "title", + "address": "address", + }, SendVenue, ], - ["answer_video", dict(video="video"), SendVideo], - ["answer_video_note", dict(video_note="video_note"), SendVideoNote], - ["answer_voice", dict(voice="voice"), SendVoice], + ["answer_video", {"video": "video"}, SendVideo], + ["answer_video_note", {"video_note": "video_note"}, SendVideoNote], + ["answer_voice", {"voice": "voice"}, SendVoice], ], ) @pytest.mark.parametrize("suffix", ["", "_pm"]) @@ -105,27 +109,25 @@ class TestChatJoinRequest: self, alias_for_method: str, suffix: str, - kwargs: Dict[str, Any], - method_class: Type[ - Union[ - SendAnimation, - SendAudio, - SendContact, - SendDocument, - SendGame, - SendInvoice, - SendLocation, - SendMediaGroup, - SendMessage, - SendPhoto, - SendPoll, - SendSticker, - SendSticker, - SendVenue, - SendVideo, - SendVideoNote, - SendVoice, - ] + kwargs: dict[str, Any], + method_class: type[ + SendAnimation + | SendAudio + | SendContact + | SendDocument + | SendGame + | SendInvoice + | SendLocation + | SendMediaGroup + | SendMessage + | SendPhoto + | SendPoll + | SendSticker + | SendSticker + | SendVenue + | SendVideo + | SendVideoNote + | SendVoice ], ): event = ChatJoinRequest( diff --git a/tests/test_api/test_types/test_chat_member_updated.py b/tests/test_api/test_types/test_chat_member_updated.py index 2d9c9a94..a48e7a0d 100644 --- a/tests/test_api/test_types/test_chat_member_updated.py +++ b/tests/test_api/test_types/test_chat_member_updated.py @@ -1,5 +1,5 @@ import datetime -from typing import Any, Dict, Type, Union +from typing import Any import pytest @@ -35,71 +35,73 @@ class TestChatMemberUpdated: @pytest.mark.parametrize( "alias_for_method,kwargs,method_class", [ - ["answer_animation", dict(animation="animation"), SendAnimation], - ["answer_audio", dict(audio="audio"), SendAudio], - ["answer_contact", dict(phone_number="+000000000000", first_name="Test"), SendContact], - ["answer_document", dict(document="document"), SendDocument], - ["answer_game", dict(game_short_name="game"), SendGame], + ["answer_animation", {"animation": "animation"}, SendAnimation], + ["answer_audio", {"audio": "audio"}, SendAudio], + [ + "answer_contact", + {"phone_number": "+000000000000", "first_name": "Test"}, + SendContact, + ], + ["answer_document", {"document": "document"}, SendDocument], + ["answer_game", {"game_short_name": "game"}, SendGame], [ "answer_invoice", - dict( - title="title", - description="description", - payload="payload", - provider_token="provider_token", - start_parameter="start_parameter", - currency="currency", - prices=[], - ), + { + "title": "title", + "description": "description", + "payload": "payload", + "provider_token": "provider_token", + "start_parameter": "start_parameter", + "currency": "currency", + "prices": [], + }, SendInvoice, ], - ["answer_location", dict(latitude=0.42, longitude=0.42), SendLocation], - ["answer_media_group", dict(media=[]), SendMediaGroup], - ["answer", dict(text="test"), SendMessage], - ["answer_photo", dict(photo="photo"), SendPhoto], - ["answer_poll", dict(question="Q?", options=[]), SendPoll], - ["answer_dice", dict(), SendDice], - ["answer_sticker", dict(sticker="sticker"), SendSticker], - ["answer_sticker", dict(sticker="sticker"), SendSticker], + ["answer_location", {"latitude": 0.42, "longitude": 0.42}, SendLocation], + ["answer_media_group", {"media": []}, SendMediaGroup], + ["answer", {"text": "test"}, SendMessage], + ["answer_photo", {"photo": "photo"}, SendPhoto], + ["answer_poll", {"question": "Q?", "options": []}, SendPoll], + ["answer_dice", {}, SendDice], + ["answer_sticker", {"sticker": "sticker"}, SendSticker], + ["answer_sticker", {"sticker": "sticker"}, SendSticker], [ "answer_venue", - dict( - latitude=0.42, - longitude=0.42, - title="title", - address="address", - ), + { + "latitude": 0.42, + "longitude": 0.42, + "title": "title", + "address": "address", + }, SendVenue, ], - ["answer_video", dict(video="video"), SendVideo], - ["answer_video_note", dict(video_note="video_note"), SendVideoNote], - ["answer_voice", dict(voice="voice"), SendVoice], + ["answer_video", {"video": "video"}, SendVideo], + ["answer_video_note", {"video_note": "video_note"}, SendVideoNote], + ["answer_voice", {"voice": "voice"}, SendVoice], ], ) def test_answer_aliases( self, alias_for_method: str, - kwargs: Dict[str, Any], - method_class: Type[ - Union[ - SendAnimation, - SendAudio, - SendContact, - SendDocument, - SendGame, - SendInvoice, - SendLocation, - SendMediaGroup, - SendMessage, - SendPhoto, - SendPoll, - SendSticker, - SendSticker, - SendVenue, - SendVideo, - SendVideoNote, - SendVoice, - ] + kwargs: dict[str, Any], + method_class: type[ + SendAnimation + | SendAudio + | SendContact + | SendDocument + | SendGame + | SendInvoice + | SendLocation + | SendMediaGroup + | SendMessage + | SendPhoto + | SendPoll + | SendSticker + | SendSticker + | SendVenue + | SendVideo + | SendVideoNote + | SendVoice ], ): user = User(id=42, is_bot=False, first_name="Test") diff --git a/tests/test_api/test_types/test_contact.py b/tests/test_api/test_types/test_contact.py new file mode 100644 index 00000000..bc555809 --- /dev/null +++ b/tests/test_api/test_types/test_contact.py @@ -0,0 +1,20 @@ +import pytest + +from aiogram.types import Contact + + +class TestContact: + @pytest.mark.parametrize( + "first,last,result", + [ + ["User", None, "User"], + ["", None, ""], + [" ", None, " "], + ["User", "Name", "User Name"], + ["User", " ", "User "], + [" ", " ", " "], + ], + ) + def test_full_name(self, first: str, last: str, result: bool): + contact = Contact(phone_number="911", first_name=first, last_name=last) + assert contact.full_name == result diff --git a/tests/test_api/test_types/test_inaccessible_message.py b/tests/test_api/test_types/test_inaccessible_message.py index 5a7ee111..4719957d 100644 --- a/tests/test_api/test_types/test_inaccessible_message.py +++ b/tests/test_api/test_types/test_inaccessible_message.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Type, Union +from typing import Any import pytest @@ -49,46 +49,46 @@ class TestMessage: @pytest.mark.parametrize( "alias_for_method,kwargs,method_class", [ - ["animation", dict(animation="animation"), SendAnimation], - ["audio", dict(audio="audio"), SendAudio], - ["contact", dict(phone_number="+000000000000", first_name="Test"), SendContact], - ["document", dict(document="document"), SendDocument], - ["game", dict(game_short_name="game"), SendGame], + ["animation", {"animation": "animation"}, SendAnimation], + ["audio", {"audio": "audio"}, SendAudio], + ["contact", {"phone_number": "+000000000000", "first_name": "Test"}, SendContact], + ["document", {"document": "document"}, SendDocument], + ["game", {"game_short_name": "game"}, SendGame], [ "invoice", - dict( - title="title", - description="description", - payload="payload", - provider_token="provider_token", - start_parameter="start_parameter", - currency="currency", - prices=[], - ), + { + "title": "title", + "description": "description", + "payload": "payload", + "provider_token": "provider_token", + "start_parameter": "start_parameter", + "currency": "currency", + "prices": [], + }, SendInvoice, ], - ["location", dict(latitude=0.42, longitude=0.42), SendLocation], - ["media_group", dict(media=[]), SendMediaGroup], - ["", dict(text="test"), SendMessage], - ["photo", dict(photo="photo"), SendPhoto], - ["poll", dict(question="Q?", options=[]), SendPoll], - ["dice", dict(), SendDice], - ["sticker", dict(sticker="sticker"), SendSticker], - ["sticker", dict(sticker="sticker"), SendSticker], + ["location", {"latitude": 0.42, "longitude": 0.42}, SendLocation], + ["media_group", {"media": []}, SendMediaGroup], + ["", {"text": "test"}, SendMessage], + ["photo", {"photo": "photo"}, SendPhoto], + ["poll", {"question": "Q?", "options": []}, SendPoll], + ["dice", {}, SendDice], + ["sticker", {"sticker": "sticker"}, SendSticker], + ["sticker", {"sticker": "sticker"}, SendSticker], [ "venue", - dict( - latitude=0.42, - longitude=0.42, - title="title", - address="address", - ), + { + "latitude": 0.42, + "longitude": 0.42, + "title": "title", + "address": "address", + }, SendVenue, ], - ["video", dict(video="video"), SendVideo], - ["video_note", dict(video_note="video_note"), SendVideoNote], - ["voice", dict(voice="voice"), SendVoice], - ["paid_media", dict(media=[], star_count=42), SendPaidMedia], + ["video", {"video": "video"}, SendVideo], + ["video_note", {"video_note": "video_note"}, SendVideoNote], + ["voice", {"voice": "voice"}, SendVoice], + ["paid_media", {"media": [], "star_count": 42}, SendPaidMedia], ], ) @pytest.mark.parametrize("alias_type", ["reply", "answer"]) @@ -96,28 +96,26 @@ class TestMessage: self, alias_for_method: str, alias_type: str, - kwargs: Dict[str, Any], - method_class: Type[ - Union[ - SendAnimation, - SendAudio, - SendContact, - SendDocument, - SendGame, - SendInvoice, - SendLocation, - SendMediaGroup, - SendMessage, - SendPhoto, - SendPoll, - SendSticker, - SendSticker, - SendVenue, - SendVideo, - SendVideoNote, - SendVoice, - SendPaidMedia, - ] + kwargs: dict[str, Any], + method_class: type[ + SendAnimation + | SendAudio + | SendContact + | SendDocument + | SendGame + | SendInvoice + | SendLocation + | SendMediaGroup + | SendMessage + | SendPhoto + | SendPoll + | SendSticker + | SendSticker + | SendVenue + | SendVideo + | SendVideoNote + | SendVoice + | SendPaidMedia ], ): message = InaccessibleMessage( diff --git a/tests/test_api/test_types/test_inline_query.py b/tests/test_api/test_types/test_inline_query.py index c822649d..088f0cc5 100644 --- a/tests/test_api/test_types/test_inline_query.py +++ b/tests/test_api/test_types/test_inline_query.py @@ -11,13 +11,13 @@ class TestInlineQuery: offset="", ) - kwargs = dict( - results=[], - cache_time=123, - next_offset="123", - switch_pm_text="foo", - switch_pm_parameter="foo", - ) + kwargs = { + "results": [], + "cache_time": 123, + "next_offset": "123", + "switch_pm_text": "foo", + "switch_pm_parameter": "foo", + } api_method = inline_query.answer(**kwargs) diff --git a/tests/test_api/test_types/test_message.py b/tests/test_api/test_types/test_message.py index f211f300..51d1790d 100644 --- a/tests/test_api/test_types/test_message.py +++ b/tests/test_api/test_types/test_message.py @@ -1,5 +1,5 @@ import datetime -from typing import Any, Dict, Optional, Type, Union +from typing import Any import pytest @@ -46,6 +46,8 @@ from aiogram.types import ( Chat, ChatBackground, ChatBoostAdded, + ChatOwnerChanged, + ChatOwnerLeft, ChatShared, Checklist, ChecklistTask, @@ -253,6 +255,31 @@ TEST_MESSAGE_LEFT_CHAT_MEMBER = Message( chat=Chat(id=42, type="private"), from_user=User(id=42, is_bot=False, first_name="Test"), ) +TEST_MESSAGE_CHAT_OWNER_LEFT = Message( + message_id=42, + date=datetime.datetime.now(), + chat_owner_left=ChatOwnerLeft( + new_owner=User(id=43, is_bot=False, first_name="NewOwner"), + ), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), +) +TEST_MESSAGE_CHAT_OWNER_LEFT_NO_SUCCESSOR = Message( + message_id=42, + date=datetime.datetime.now(), + chat_owner_left=ChatOwnerLeft(), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), +) +TEST_MESSAGE_CHAT_OWNER_CHANGED = Message( + message_id=42, + date=datetime.datetime.now(), + chat_owner_changed=ChatOwnerChanged( + new_owner=User(id=43, is_bot=False, first_name="NewOwner"), + ), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), +) TEST_MESSAGE_INVOICE = Message( message_id=42, date=datetime.datetime.now(), @@ -849,6 +876,8 @@ MESSAGES_AND_CONTENT_TYPES = [ [TEST_MESSAGE_LOCATION, ContentType.LOCATION], [TEST_MESSAGE_NEW_CHAT_MEMBERS, ContentType.NEW_CHAT_MEMBERS], [TEST_MESSAGE_LEFT_CHAT_MEMBER, ContentType.LEFT_CHAT_MEMBER], + [TEST_MESSAGE_CHAT_OWNER_LEFT, ContentType.CHAT_OWNER_LEFT], + [TEST_MESSAGE_CHAT_OWNER_CHANGED, ContentType.CHAT_OWNER_CHANGED], [TEST_MESSAGE_INVOICE, ContentType.INVOICE], [TEST_MESSAGE_SUCCESSFUL_PAYMENT, ContentType.SUCCESSFUL_PAYMENT], [TEST_MESSAGE_CONNECTED_WEBSITE, ContentType.CONNECTED_WEBSITE], @@ -930,6 +959,8 @@ MESSAGES_AND_COPY_METHODS = [ [TEST_MESSAGE_STORY, ForwardMessage], [TEST_MESSAGE_NEW_CHAT_MEMBERS, None], [TEST_MESSAGE_LEFT_CHAT_MEMBER, None], + [TEST_MESSAGE_CHAT_OWNER_LEFT, None], + [TEST_MESSAGE_CHAT_OWNER_CHANGED, None], [TEST_MESSAGE_INVOICE, None], [TEST_MESSAGE_SUCCESSFUL_PAYMENT, None], [TEST_MESSAGE_CONNECTED_WEBSITE, None], @@ -989,7 +1020,7 @@ MESSAGES_AND_COPY_METHODS = [ class TestAllMessageTypesTested: @pytest.fixture(scope="function") def known_content_types(self): - content_types = {t for t in ContentType} + content_types = set(ContentType) content_types.remove(ContentType.ANY) return content_types @@ -1023,6 +1054,11 @@ class TestMessage: def test_content_type(self, message: Message, content_type: str): assert message.content_type == content_type + def test_chat_owner_left_no_successor(self): + assert ( + TEST_MESSAGE_CHAT_OWNER_LEFT_NO_SUCCESSOR.content_type == ContentType.CHAT_OWNER_LEFT + ) + def test_as_reply_parameters(self): message = Message( message_id=42, chat=Chat(id=42, type="private"), date=datetime.datetime.now() @@ -1034,46 +1070,46 @@ class TestMessage: @pytest.mark.parametrize( "alias_for_method,kwargs,method_class", [ - ["animation", dict(animation="animation"), SendAnimation], - ["audio", dict(audio="audio"), SendAudio], - ["contact", dict(phone_number="+000000000000", first_name="Test"), SendContact], - ["document", dict(document="document"), SendDocument], - ["game", dict(game_short_name="game"), SendGame], + ["animation", {"animation": "animation"}, SendAnimation], + ["audio", {"audio": "audio"}, SendAudio], + ["contact", {"phone_number": "+000000000000", "first_name": "Test"}, SendContact], + ["document", {"document": "document"}, SendDocument], + ["game", {"game_short_name": "game"}, SendGame], [ "invoice", - dict( - title="title", - description="description", - payload="payload", - provider_token="provider_token", - start_parameter="start_parameter", - currency="currency", - prices=[], - ), + { + "title": "title", + "description": "description", + "payload": "payload", + "provider_token": "provider_token", + "start_parameter": "start_parameter", + "currency": "currency", + "prices": [], + }, SendInvoice, ], - ["location", dict(latitude=0.42, longitude=0.42), SendLocation], - ["media_group", dict(media=[]), SendMediaGroup], - ["", dict(text="test"), SendMessage], - ["photo", dict(photo="photo"), SendPhoto], - ["poll", dict(question="Q?", options=[]), SendPoll], - ["dice", dict(), SendDice], - ["sticker", dict(sticker="sticker"), SendSticker], - ["sticker", dict(sticker="sticker"), SendSticker], + ["location", {"latitude": 0.42, "longitude": 0.42}, SendLocation], + ["media_group", {"media": []}, SendMediaGroup], + ["", {"text": "test"}, SendMessage], + ["photo", {"photo": "photo"}, SendPhoto], + ["poll", {"question": "Q?", "options": []}, SendPoll], + ["dice", {}, SendDice], + ["sticker", {"sticker": "sticker"}, SendSticker], + ["sticker", {"sticker": "sticker"}, SendSticker], [ "venue", - dict( - latitude=0.42, - longitude=0.42, - title="title", - address="address", - ), + { + "latitude": 0.42, + "longitude": 0.42, + "title": "title", + "address": "address", + }, SendVenue, ], - ["video", dict(video="video"), SendVideo], - ["video_note", dict(video_note="video_note"), SendVideoNote], - ["voice", dict(voice="voice"), SendVoice], - ["paid_media", dict(media=[], star_count=42), SendPaidMedia], + ["video", {"video": "video"}, SendVideo], + ["video_note", {"video_note": "video_note"}, SendVideoNote], + ["voice", {"voice": "voice"}, SendVoice], + ["paid_media", {"media": [], "star_count": 42}, SendPaidMedia], ], ) @pytest.mark.parametrize("alias_type", ["reply", "answer"]) @@ -1081,28 +1117,26 @@ class TestMessage: self, alias_for_method: str, alias_type: str, - kwargs: Dict[str, Any], - method_class: Type[ - Union[ - SendAnimation, - SendAudio, - SendContact, - SendDocument, - SendGame, - SendInvoice, - SendLocation, - SendMediaGroup, - SendMessage, - SendPhoto, - SendPoll, - SendSticker, - SendSticker, - SendVenue, - SendVideo, - SendVideoNote, - SendVoice, - SendPaidMedia, - ] + kwargs: dict[str, Any], + method_class: type[ + SendAnimation + | SendAudio + | SendContact + | SendDocument + | SendGame + | SendInvoice + | SendLocation + | SendMediaGroup + | SendMessage + | SendPhoto + | SendPoll + | SendSticker + | SendSticker + | SendVenue + | SendVideo + | SendVideoNote + | SendVoice + | SendPaidMedia ], ): message = Message( @@ -1145,7 +1179,7 @@ class TestMessage: def test_send_copy( self, message: Message, - expected_method: Optional[Type[TelegramMethod]], + expected_method: type[TelegramMethod] | None, ): if expected_method is None: with pytest.raises(TypeError, match="This type of message can't be copied."): @@ -1181,8 +1215,8 @@ class TestMessage: def test_send_copy_custom_parse_mode( self, message: Message, - expected_method: Optional[Type[TelegramMethod]], - custom_parse_mode: Optional[str], + expected_method: type[TelegramMethod] | None, + custom_parse_mode: str | None, ): method = message.send_copy( chat_id=42, diff --git a/tests/test_api/test_types/test_pre_checkout_query.py b/tests/test_api/test_types/test_pre_checkout_query.py index ef6c9865..a003c274 100644 --- a/tests/test_api/test_types/test_pre_checkout_query.py +++ b/tests/test_api/test_types/test_pre_checkout_query.py @@ -12,7 +12,7 @@ class TestPreCheckoutQuery: invoice_payload="payload", ) - kwargs = dict(ok=True, error_message="foo") + kwargs = {"ok": True, "error_message": "foo"} api_method = pre_checkout_query.answer(**kwargs) diff --git a/tests/test_api/test_types/test_reply_keyboard_remove.py b/tests/test_api/test_types/test_reply_keyboard_remove.py index 1d252cc5..1e14fd2c 100644 --- a/tests/test_api/test_types/test_reply_keyboard_remove.py +++ b/tests/test_api/test_types/test_reply_keyboard_remove.py @@ -1,5 +1,3 @@ -from typing import Dict - import pytest from aiogram.types import ReplyKeyboardRemove @@ -11,13 +9,13 @@ class TestReplyKeyboardRemove: """ def test_remove_keyboard_default_is_true(self): - assert ( - ReplyKeyboardRemove.model_fields["remove_keyboard"].default is True - ), "Remove keyboard has incorrect default value!" + assert ReplyKeyboardRemove.model_fields["remove_keyboard"].default is True, ( + "Remove keyboard has incorrect default value!" + ) @pytest.mark.parametrize( "kwargs,expected", [[{}, True], [{"remove_keyboard": True}, True]], ) - def test_remove_keyboard_values(self, kwargs: Dict[str, bool], expected: bool): + def test_remove_keyboard_values(self, kwargs: dict[str, bool], expected: bool): assert ReplyKeyboardRemove(**kwargs).remove_keyboard is expected diff --git a/tests/test_api/test_types/test_shipping_query.py b/tests/test_api/test_types/test_shipping_query.py index 76668b15..aa3537c5 100644 --- a/tests/test_api/test_types/test_shipping_query.py +++ b/tests/test_api/test_types/test_shipping_query.py @@ -28,7 +28,7 @@ class TestInlineQuery: ShippingOption(id="id", title="foo", prices=[LabeledPrice(label="foo", amount=123)]) ] - kwargs = dict(ok=True, shipping_options=shipping_options, error_message="foo") + kwargs = {"ok": True, "shipping_options": shipping_options, "error_message": "foo"} api_method = shipping_query.answer(**kwargs) diff --git a/tests/test_api/test_types/test_user.py b/tests/test_api/test_types/test_user.py index 9f28175b..610fcd9c 100644 --- a/tests/test_api/test_types/test_user.py +++ b/tests/test_api/test_types/test_user.py @@ -56,3 +56,9 @@ class TestUser: method = user.get_profile_photos(description="test") assert method.user_id == user.id + + def test_get_profile_audios(self): + user = User(id=42, is_bot=False, first_name="Test", last_name="User") + + method = user.get_profile_audios(description="test") + assert method.user_id == user.id diff --git a/tests/test_api/test_types/test_video_quality.py b/tests/test_api/test_types/test_video_quality.py new file mode 100644 index 00000000..20144911 --- /dev/null +++ b/tests/test_api/test_types/test_video_quality.py @@ -0,0 +1,61 @@ +import pytest + +from aiogram.types import Video, VideoQuality + + +@pytest.fixture() +def video_quality(): + return VideoQuality( + file_id="abc123", + file_unique_id="unique123", + width=1920, + height=1080, + codec="h264", + ) + + +class TestVideoQuality: + def test_instantiation(self, video_quality: VideoQuality): + assert video_quality.file_id == "abc123" + assert video_quality.file_unique_id == "unique123" + assert video_quality.width == 1920 + assert video_quality.height == 1080 + assert video_quality.codec == "h264" + assert video_quality.file_size is None + + def test_instantiation_with_file_size(self): + file_size = 1048576 + vq = VideoQuality( + file_id="abc123", + file_unique_id="unique123", + width=1920, + height=1080, + codec="h265", + file_size=file_size, + ) + assert vq.file_size == file_size + + def test_video_with_qualities(self, video_quality: VideoQuality): + file_size = 524288 + video = Video( + file_id="video123", + file_unique_id="unique_video123", + width=1920, + height=1080, + duration=120, + qualities=[ + video_quality, + VideoQuality( + file_id="q2", + file_unique_id="uq2", + width=1280, + height=720, + codec="h264", + file_size=file_size, + ), + ], + ) + assert video.qualities is not None + assert len(video.qualities) == 2 + assert video.qualities[0].width == 1920 + assert video.qualities[1].file_size == file_size diff --git a/tests/test_dispatcher/test_dispatcher.py b/tests/test_dispatcher/test_dispatcher.py index 0b50a2ba..ca7d4092 100644 --- a/tests/test_dispatcher/test_dispatcher.py +++ b/tests/test_dispatcher/test_dispatcher.py @@ -6,7 +6,7 @@ import warnings from asyncio import Event from collections import Counter from contextlib import suppress -from typing import Any, Optional +from typing import Any from unittest.mock import AsyncMock, patch import pytest @@ -199,7 +199,7 @@ class TestDispatcher: async def test_process_update_empty(self, bot: MockedBot): dispatcher = Dispatcher() - with pytest.warns(RuntimeWarning, match="Detected unknown update type") as record: + with pytest.warns(RuntimeWarning, match="Detected unknown update type"): result = await dispatcher._process_update(bot=bot, update=Update(update_id=42)) assert not result @@ -819,7 +819,7 @@ class TestDispatcher: with ( patch( "aiogram.dispatcher.dispatcher.Dispatcher._process_update", new_callable=AsyncMock - ) as mocked_process_update, + ), patch( "aiogram.dispatcher.dispatcher.Dispatcher._listen_updates" ) as patched_listen_updates, @@ -913,7 +913,7 @@ class TestDispatcher: patch( "aiogram.dispatcher.dispatcher.Dispatcher._process_update", side_effect=mock_process_update, - ) as mocked_process_update, + ), patch( "aiogram.dispatcher.dispatcher.Dispatcher._listen_updates" ) as patched_listen_updates, diff --git a/tests/test_dispatcher/test_event/test_handler.py b/tests/test_dispatcher/test_event/test_handler.py index 7105a058..5b5bc4c9 100644 --- a/tests/test_dispatcher/test_event/test_handler.py +++ b/tests/test_dispatcher/test_event/test_handler.py @@ -1,5 +1,6 @@ import functools -from typing import Any, Callable, Dict, Set, Union +from collections.abc import Callable +from typing import Any import pytest from magic_filter import F as A @@ -29,7 +30,7 @@ async def callback4(foo: int, *, bar: int, baz: int): class TestFilter(Filter): - async def __call__(self, foo: int, bar: int, baz: int) -> Union[bool, Dict[str, Any]]: + async def __call__(self, foo: int, bar: int, baz: int) -> bool | dict[str, Any]: return locals() @@ -61,7 +62,7 @@ class TestCallableObject: pytest.param(SyncCallable(), {"foo", "bar", "baz"}), ], ) - def test_init_args_spec(self, callback: Callable, args: Set[str]): + def test_init_args_spec(self, callback: Callable, args: set[str]): obj = CallableObject(callback) assert set(obj.params) == args @@ -125,7 +126,7 @@ class TestCallableObject: ], ) def test_prepare_kwargs( - self, callback: Callable, kwargs: Dict[str, Any], result: Dict[str, Any] + self, callback: Callable, kwargs: dict[str, Any], result: dict[str, Any] ): obj = CallableObject(callback) assert obj._prepare_kwargs(kwargs) == result @@ -147,7 +148,6 @@ class TestFilterObject: def test_post_init(self): case = F.test filter_obj = FilterObject(callback=case) - print(filter_obj.callback) assert filter_obj.callback == case.resolve diff --git a/tests/test_dispatcher/test_event/test_telegram.py b/tests/test_dispatcher/test_event/test_telegram.py index 713aabb8..36c13ee4 100644 --- a/tests/test_dispatcher/test_event/test_telegram.py +++ b/tests/test_dispatcher/test_event/test_telegram.py @@ -1,6 +1,6 @@ import datetime import functools -from typing import Any, Dict, NoReturn, Optional, Union +from typing import Any, NoReturn import pytest from pydantic import BaseModel @@ -31,7 +31,7 @@ async def pipe_handler(*args, **kwargs): class MyFilter1(Filter, BaseModel): test: str - async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]: + async def __call__(self, *args: Any, **kwargs: Any) -> bool | dict[str, Any]: return True @@ -44,16 +44,16 @@ class MyFilter3(MyFilter1): class OptionalFilter(Filter, BaseModel): - optional: Optional[str] + optional: str | None - async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]: + async def __call__(self, *args: Any, **kwargs: Any) -> bool | dict[str, Any]: return True class DefaultFilter(Filter, BaseModel): default: str = "Default" - async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]: + async def __call__(self, *args: Any, **kwargs: Any) -> bool | dict[str, Any]: return True diff --git a/tests/test_filters/test_base.py b/tests/test_filters/test_base.py index 36d5bbee..131cf1b0 100644 --- a/tests/test_filters/test_base.py +++ b/tests/test_filters/test_base.py @@ -1,4 +1,4 @@ -from typing import Awaitable +from collections.abc import Awaitable from unittest.mock import AsyncMock, patch from aiogram.filters import Filter diff --git a/tests/test_filters/test_callback_data.py b/tests/test_filters/test_callback_data.py index 1bc50cae..79ca5929 100644 --- a/tests/test_filters/test_callback_data.py +++ b/tests/test_filters/test_callback_data.py @@ -107,20 +107,20 @@ class TestCallbackData: def test_pack_optional(self): class MyCallback1(CallbackData, prefix="test1"): foo: str - bar: Optional[int] = None + bar: int | None = None assert MyCallback1(foo="spam").pack() == "test1:spam:" assert MyCallback1(foo="spam", bar=42).pack() == "test1:spam:42" class MyCallback2(CallbackData, prefix="test2"): - foo: Optional[str] = None + foo: str | None = None bar: int assert MyCallback2(bar=42).pack() == "test2::42" assert MyCallback2(foo="spam", bar=42).pack() == "test2:spam:42" class MyCallback3(CallbackData, prefix="test3"): - foo: Optional[str] = "experiment" + foo: str | None = "experiment" bar: int assert MyCallback3(bar=42).pack() == "test3:experiment:42" @@ -141,28 +141,28 @@ class TestCallbackData: class MyCallback1(CallbackData, prefix="test1"): foo: str - bar: Optional[int] = None + bar: int | None = None assert MyCallback1.unpack("test1:spam:") == MyCallback1(foo="spam") assert MyCallback1.unpack("test1:spam:42") == MyCallback1(foo="spam", bar=42) class MyCallback2(CallbackData, prefix="test2"): - foo: Optional[str] = None + foo: str | None = None bar: int assert MyCallback2.unpack("test2::42") == MyCallback2(bar=42) assert MyCallback2.unpack("test2:spam:42") == MyCallback2(foo="spam", bar=42) class MyCallback3(CallbackData, prefix="test3"): - foo: Optional[str] = "experiment" + foo: str | None = "experiment" bar: int assert MyCallback3.unpack("test3:experiment:42") == MyCallback3(bar=42) assert MyCallback3.unpack("test3:spam:42") == MyCallback3(foo="spam", bar=42) class MyCallback4(CallbackData, prefix="test4"): - foo: Optional[str] = "" - bar: Optional[str] = None + foo: str | None = "" + bar: str | None = None assert MyCallback4.unpack("test4::") == MyCallback4(foo="", bar=None) assert MyCallback4.unpack("test4::") == MyCallback4() diff --git a/tests/test_filters/test_chat_member_updated.py b/tests/test_filters/test_chat_member_updated.py index 2a608c8f..c88b705e 100644 --- a/tests/test_filters/test_chat_member_updated.py +++ b/tests/test_filters/test_chat_member_updated.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import Optional import pytest @@ -27,7 +26,7 @@ from aiogram.types import ( class ChatMemberCustom(ChatMember): status: str - is_member: Optional[bool] = None + is_member: bool | None = None class TestMemberStatusMarker: diff --git a/tests/test_filters/test_state.py b/tests/test_filters/test_state.py index 16124ef9..de7ad72d 100644 --- a/tests/test_filters/test_state.py +++ b/tests/test_filters/test_state.py @@ -56,7 +56,7 @@ class TestStateFilter: assert SG.state == copy(SG.state) assert SG.state == "SG:state" - assert "SG:state" == SG.state + assert SG.state == "SG:state" assert State() == State() assert SG.state != 1 diff --git a/tests/test_fsm/storage/test_key_builder.py b/tests/test_fsm/storage/test_key_builder.py index f62ac505..43df2a0e 100644 --- a/tests/test_fsm/storage/test_key_builder.py +++ b/tests/test_fsm/storage/test_key_builder.py @@ -1,4 +1,4 @@ -from typing import Literal, Optional +from typing import Literal import pytest @@ -67,7 +67,7 @@ class TestDefaultKeyBuilder: async def test_generate_key( self, key_builder: DefaultKeyBuilder, - field: Optional[Literal["data", "state", "lock"]], + field: Literal["data", "state", "lock"] | None, result: str, ): key = StorageKey( diff --git a/tests/test_fsm/test_middleware.py b/tests/test_fsm/test_middleware.py new file mode 100644 index 00000000..82ef93b2 --- /dev/null +++ b/tests/test_fsm/test_middleware.py @@ -0,0 +1,90 @@ +from aiogram.fsm.middleware import FSMContextMiddleware +from aiogram.fsm.storage.memory import DisabledEventIsolation, MemoryStorage +from aiogram.fsm.strategy import FSMStrategy +from tests.mocked_bot import MockedBot + +CHANNEL_ID = -1001234567890 +THREAD_ID = 42 + + +def create_middleware(strategy: FSMStrategy) -> FSMContextMiddleware: + return FSMContextMiddleware( + storage=MemoryStorage(), + events_isolation=DisabledEventIsolation(), + strategy=strategy, + ) + + +class TestFSMContextMiddleware: + def test_resolve_context_for_channel_in_chat_strategy(self): + bot = MockedBot() + middleware = create_middleware(FSMStrategy.CHAT) + + context = middleware.resolve_context( + bot=bot, + chat_id=CHANNEL_ID, + user_id=None, + ) + + assert context is not None + assert context.key.chat_id == CHANNEL_ID + assert context.key.user_id == CHANNEL_ID + + def test_resolve_context_with_missing_user_in_chat_topic_strategy_uses_chat_id_for_user_id( + self, + ): + """ + When user_id is absent (e.g., channel-like updates), chat-scoped strategies + should still build a stable FSM key by mirroring chat_id into user_id. + """ + bot = MockedBot() + middleware = create_middleware(FSMStrategy.CHAT_TOPIC) + + context = middleware.resolve_context( + bot=bot, + chat_id=CHANNEL_ID, + user_id=None, + thread_id=THREAD_ID, + ) + + assert context is not None + assert context.key.chat_id == CHANNEL_ID + assert context.key.user_id == CHANNEL_ID + assert context.key.thread_id == THREAD_ID + + def test_resolve_context_for_channel_in_user_in_chat_strategy(self): + bot = MockedBot() + middleware = create_middleware(FSMStrategy.USER_IN_CHAT) + + context = middleware.resolve_context( + bot=bot, + chat_id=CHANNEL_ID, + user_id=None, + ) + + assert context is None + + def test_resolve_context_for_channel_in_global_user_strategy(self): + bot = MockedBot() + middleware = create_middleware(FSMStrategy.GLOBAL_USER) + + context = middleware.resolve_context( + bot=bot, + chat_id=CHANNEL_ID, + user_id=None, + ) + + assert context is None + + def test_resolve_context_for_channel_in_user_in_topic_strategy(self): + bot = MockedBot() + middleware = create_middleware(FSMStrategy.USER_IN_TOPIC) + + context = middleware.resolve_context( + bot=bot, + chat_id=CHANNEL_ID, + user_id=None, + thread_id=THREAD_ID, + ) + + assert context is None diff --git a/tests/test_fsm/test_scene.py b/tests/test_fsm/test_scene.py index 58c2a23f..3a9944b0 100644 --- a/tests/test_fsm/test_scene.py +++ b/tests/test_fsm/test_scene.py @@ -309,6 +309,34 @@ class TestSceneHandlerWrapper: # Check whether result is correct assert result == 42 + async def test_scene_handler_wrapper_call_without_scene_context(self): + class MyScene(Scene): + pass + + async def handler_mock(*args, **kwargs): + return 42 + + event_update_mock = Update( + update_id=42, + message=Message( + message_id=42, + text="test", + date=datetime.now(), + chat=Chat( + type="private", + id=42, + ), + ), + ) + + scene_handler_wrapper = SceneHandlerWrapper(MyScene, handler_mock) + + with pytest.raises( + SceneException, + match="Scene context key 'state' is not available. Ensure FSM is enabled and pipeline is intact.", + ): + await scene_handler_wrapper(event_update_mock, event_update=event_update_mock) + def test_scene_handler_wrapper_str(self): class MyScene(Scene): pass @@ -1558,6 +1586,27 @@ class TestSceneRegistry: handler.assert_called_once_with(event, data) assert result == handler.return_value + async def test_scene_registry_update_middleware_without_state(self): + router = Router() + registry = SceneRegistry(router) + handler = AsyncMock(spec=NextMiddlewareType) + event = Update( + update_id=42, + message=Message( + message_id=42, + text="test", + date=datetime.now(), + chat=Chat(id=42, type="private"), + ), + ) + data = {} + + result = await registry._update_middleware(handler, event, data) + + assert "scenes" not in data + handler.assert_called_once_with(event, data) + assert result == handler.return_value + async def test_scene_registry_update_middleware_not_update(self, bot: MockedBot): router = Router() registry = SceneRegistry(router) @@ -1604,6 +1653,24 @@ class TestSceneRegistry: handler.assert_called_once_with(event, data) assert result == handler.return_value + async def test_scene_registry_middleware_without_state(self): + router = Router() + registry = SceneRegistry(router) + handler = AsyncMock(spec=NextMiddlewareType) + event = Message( + message_id=42, + text="test", + date=datetime.now(), + chat=Chat(id=42, type="private"), + ) + data = {} + + result = await registry._middleware(handler, event, data) + + assert "scenes" not in data + handler.assert_called_once_with(event, data) + assert result == handler.return_value + class TestSceneInheritance: def test_inherit_handlers(self): diff --git a/tests/test_issues/test_1672_middleware_data_in_scene.py b/tests/test_issues/test_1672_middleware_data_in_scene.py index 13885dcb..0935c3f9 100644 --- a/tests/test_issues/test_1672_middleware_data_in_scene.py +++ b/tests/test_issues/test_1672_middleware_data_in_scene.py @@ -1,5 +1,6 @@ +from collections.abc import Awaitable, Callable from datetime import datetime -from typing import Any, Awaitable, Callable, Dict +from typing import Any from unittest.mock import AsyncMock import pytest @@ -25,9 +26,9 @@ class EchoScene(Scene, state="test"): class TestMiddleware(BaseMiddleware): async def __call__( self, - handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], + handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], event: TelegramObject, - data: Dict[str, Any], + data: dict[str, Any], ) -> Any: data["test_context"] = "Custom context here" return await handler(event, data) diff --git a/tests/test_issues/test_1743_channel_post_with_scenes.py b/tests/test_issues/test_1743_channel_post_with_scenes.py new file mode 100644 index 00000000..cdaf58a7 --- /dev/null +++ b/tests/test_issues/test_1743_channel_post_with_scenes.py @@ -0,0 +1,118 @@ +from datetime import datetime + +import pytest + +from aiogram import Dispatcher, F, Router +from aiogram.fsm.context import FSMContext +from aiogram.fsm.scene import Scene, SceneRegistry, on +from aiogram.fsm.strategy import FSMStrategy +from aiogram.types import Chat, Message, Update +from tests.mocked_bot import MockedBot + +CHANNEL_ID = -1001234567890 + + +class BrowseScene(Scene, state="browse"): + pass + + +class ChannelStateScene(Scene, state="channel_state"): + @on.channel_post() + async def save_message_id( + self, + message: Message, + state: FSMContext, + ) -> int: + await state.update_data(last_message_id=message.message_id) + return message.message_id + + +@pytest.mark.parametrize("update_type", ["channel_post", "edited_channel_post"]) +async def test_channel_events_with_scenes_do_not_require_fsm_state( + bot: MockedBot, + update_type: str, +): + dispatcher = Dispatcher() + channel_router = Router() + + if update_type == "channel_post": + channel_router.channel_post.filter(F.chat.id == CHANNEL_ID) + + @channel_router.channel_post() + async def on_channel_post(message: Message): + return message.message_id + else: + channel_router.edited_channel_post.filter(F.chat.id == CHANNEL_ID) + + @channel_router.edited_channel_post() + async def on_edited_channel_post(message: Message): + return message.message_id + + dispatcher.include_router(channel_router) + SceneRegistry(dispatcher).add(BrowseScene) + + message = Message( + message_id=1, + date=datetime.now(), + chat=Chat(id=CHANNEL_ID, type="channel"), + text="test", + ) + + kwargs = {"update_id": 1, update_type: message} + update = Update(**kwargs) + + result = await dispatcher.feed_update(bot, update) + assert result == 1 + + +async def test_channel_scene_has_fsm_state_with_chat_strategy(bot: MockedBot): + dispatcher = Dispatcher(fsm_strategy=FSMStrategy.CHAT) + router = Router() + + @router.channel_post((F.chat.id == CHANNEL_ID) & (F.text == "enter")) + async def enter_channel_state(message: Message, state: FSMContext): + await state.set_state(ChannelStateScene.__scene_config__.state) + return message.message_id + + dispatcher.include_router(router) + SceneRegistry(dispatcher).add(ChannelStateScene) + + initial_update = Update( + update_id=1, + channel_post=Message( + message_id=10, + date=datetime.now(), + chat=Chat(id=CHANNEL_ID, type="channel"), + text="enter", + ), + ) + await dispatcher.feed_update(bot, initial_update) + + active_state = await dispatcher.fsm.storage.get_state( + key=dispatcher.fsm.get_context( + bot=bot, + chat_id=CHANNEL_ID, + user_id=CHANNEL_ID, + ).key + ) + assert active_state == ChannelStateScene.__scene_config__.state + + scene_update = Update( + update_id=2, + channel_post=Message( + message_id=11, + date=datetime.now(), + chat=Chat(id=CHANNEL_ID, type="channel"), + text="scene", + ), + ) + result = await dispatcher.feed_update(bot, scene_update) + assert result == 11 + state_data = await dispatcher.fsm.storage.get_data( + key=dispatcher.fsm.get_context( + bot=bot, + chat_id=CHANNEL_ID, + user_id=CHANNEL_ID, + ).key + ) + assert state_data["last_message_id"] == 11 diff --git a/tests/test_utils/test_backoff.py b/tests/test_utils/test_backoff.py index 57e7269f..13a0f256 100644 --- a/tests/test_utils/test_backoff.py +++ b/tests/test_utils/test_backoff.py @@ -9,10 +9,15 @@ class TestBackoffConfig: @pytest.mark.parametrize( "kwargs", [ - dict(min_delay=1.0, max_delay=1.0, factor=2.0, jitter=0.1), # equals min and max - dict(min_delay=1.0, max_delay=1.0, factor=1.0, jitter=0.1), # factor == 1 - dict(min_delay=1.0, max_delay=2.0, factor=0.5, jitter=0.1), # factor < 1 - dict(min_delay=2.0, max_delay=1.0, factor=2.0, jitter=0.1), # min > max + { + "min_delay": 1.0, + "max_delay": 1.0, + "factor": 2.0, + "jitter": 0.1, + }, # equals min and max + {"min_delay": 1.0, "max_delay": 1.0, "factor": 1.0, "jitter": 0.1}, # factor == 1 + {"min_delay": 1.0, "max_delay": 2.0, "factor": 0.5, "jitter": 0.1}, # factor < 1 + {"min_delay": 2.0, "max_delay": 1.0, "factor": 2.0, "jitter": 0.1}, # min > max ], ) def test_incorrect_post_init(self, kwargs): @@ -21,7 +26,7 @@ class TestBackoffConfig: @pytest.mark.parametrize( "kwargs", - [dict(min_delay=1.0, max_delay=2.0, factor=1.2, jitter=0.1)], + [{"min_delay": 1.0, "max_delay": 2.0, "factor": 1.2, "jitter": 0.1}], ) def test_correct_post_init(self, kwargs): assert BackoffConfig(**kwargs) diff --git a/tests/test_utils/test_chat_member.py b/tests/test_utils/test_chat_member.py index 2f0f9eef..8a42600c 100644 --- a/tests/test_utils/test_chat_member.py +++ b/tests/test_utils/test_chat_member.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import Type import pytest @@ -90,6 +89,6 @@ CHAT_MEMBER_RESTRICTED = ChatMemberRestricted( (CHAT_MEMBER_RESTRICTED, ChatMemberRestricted), ], ) -def test_chat_member_resolution(data: dict, resolved_type: Type[ChatMember]) -> None: +def test_chat_member_resolution(data: dict, resolved_type: type[ChatMember]) -> None: chat_member = ChatMemberAdapter.validate_python(data) assert isinstance(chat_member, resolved_type) diff --git a/tests/test_utils/test_dataclass.py b/tests/test_utils/test_dataclass.py index d714362d..9beeadb4 100644 --- a/tests/test_utils/test_dataclass.py +++ b/tests/test_utils/test_dataclass.py @@ -31,7 +31,6 @@ class TestDataclassKwargs: ) def test_dataclass_kwargs(self, py_version, expected): with patch("sys.version_info", py_version): - assert ( dataclass_kwargs( init=True, diff --git a/tests/test_utils/test_i18n.py b/tests/test_utils/test_i18n.py index 016b0e23..d6dc2444 100644 --- a/tests/test_utils/test_i18n.py +++ b/tests/test_utils/test_i18n.py @@ -1,5 +1,4 @@ -from pathlib import Path -from typing import Any, Dict +from typing import Any import pytest @@ -81,7 +80,7 @@ class TestI18nCore: ["it", {"singular": "test", "plural": "test2", "n": 2}, "test2"], ], ) - def test_gettext(self, i18n: I18n, locale: str, case: Dict[str, Any], result: str): + def test_gettext(self, i18n: I18n, locale: str, case: dict[str, Any], result: str): if locale is not None: i18n.current_locale = locale with i18n.context(): diff --git a/tests/test_utils/test_keyboard.py b/tests/test_utils/test_keyboard.py index 1bc7063a..e80fed8e 100644 --- a/tests/test_utils/test_keyboard.py +++ b/tests/test_utils/test_keyboard.py @@ -207,21 +207,21 @@ class TestKeyboardBuilder: markup = builder.export() assert len(markup) == len(shape) - for row, expected_size in zip(markup, shape): + for row, expected_size in zip(markup, shape, strict=False): assert len(row) == expected_size @pytest.mark.parametrize( "builder_type,kwargs,expected", [ - [ReplyKeyboardBuilder, dict(text="test"), KeyboardButton(text="test")], + [ReplyKeyboardBuilder, {"text": "test"}, KeyboardButton(text="test")], [ InlineKeyboardBuilder, - dict(text="test", callback_data="callback"), + {"text": "test", "callback_data": "callback"}, InlineKeyboardButton(text="test", callback_data="callback"), ], [ InlineKeyboardBuilder, - dict(text="test", callback_data=MyCallback(value="test")), + {"text": "test", "callback_data": MyCallback(value="test")}, InlineKeyboardButton(text="test", callback_data="test:test"), ], ], diff --git a/tests/test_utils/test_link.py b/tests/test_utils/test_link.py index f0276703..c2ac5514 100644 --- a/tests/test_utils/test_link.py +++ b/tests/test_utils/test_link.py @@ -1,5 +1,5 @@ from itertools import product -from typing import Any, Dict +from typing import Any from urllib.parse import parse_qs import pytest @@ -18,7 +18,7 @@ class TestLink: "base,params,result", [["user", {"id": 42}, "tg://user?id=42"]], ) - def test_create_tg_link(self, base: str, params: Dict[str, Any], result: str): + def test_create_tg_link(self, base: str, params: dict[str, Any], result: str): assert create_tg_link(base, **params) == result @pytest.mark.parametrize( @@ -28,7 +28,7 @@ class TestLink: ["username", {"start": "test"}, "https://t.me/username?start=test"], ], ) - def test_create_telegram_link(self, base: str, params: Dict[str, Any], result: str): + def test_create_telegram_link(self, base: str, params: dict[str, Any], result: str): assert create_telegram_link(base, **params) == result def test_fragment(self): @@ -70,8 +70,8 @@ class TestCreateChannelBotLink: } variants = product([True, False], repeat=len(params)) - for index, variants in enumerate(variants): - kwargs = {k: v for k, v in zip(params, variants) if v} + for _index, variants in enumerate(variants): + kwargs = {k: v for k, v in zip(params, variants, strict=False) if v} if not kwargs: # Variant without additional arguments is already covered continue diff --git a/tests/test_utils/test_markdown.py b/tests/test_utils/test_markdown.py index 786cc321..7759ab4c 100644 --- a/tests/test_utils/test_markdown.py +++ b/tests/test_utils/test_markdown.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Optional, Tuple +from collections.abc import Callable +from typing import Any import pytest @@ -75,6 +76,6 @@ class TestMarkdown: ], ) def test_formatter( - self, func: Callable[[Any], Any], args: Tuple[str], sep: Optional[str], result: str + self, func: Callable[[Any], Any], args: tuple[str], sep: str | None, result: str ): assert func(*args, **({"sep": sep} if sep is not None else {})) == result # type: ignore diff --git a/tests/test_utils/test_text_decorations.py b/tests/test_utils/test_text_decorations.py index 50623447..b4ccb5e8 100644 --- a/tests/test_utils/test_text_decorations.py +++ b/tests/test_utils/test_text_decorations.py @@ -1,5 +1,3 @@ -from typing import List, Optional - import pytest from aiogram.types import MessageEntity, User @@ -304,7 +302,7 @@ class TestTextDecoration: self, decorator: TextDecoration, text: str, - entities: Optional[List[MessageEntity]], + entities: list[MessageEntity] | None, result: str, ): assert decorator.unparse(text, entities) == result diff --git a/tests/test_webhook/test_aiohttp_server.py b/tests/test_webhook/test_aiohttp_server.py index b6ee3b8b..881e8fc7 100644 --- a/tests/test_webhook/test_aiohttp_server.py +++ b/tests/test_webhook/test_aiohttp_server.py @@ -2,7 +2,7 @@ import asyncio import time from asyncio import Event from dataclasses import dataclass -from typing import Any, Dict +from typing import Any from unittest.mock import AsyncMock, patch import pytest @@ -251,7 +251,7 @@ class TestTokenBasedRequestHandler: @dataclass class FakeRequest: - match_info: Dict[str, Any] + match_info: dict[str, Any] bot1 = await handler.resolve_bot(request=FakeRequest(match_info={"bot_token": "42:TEST"})) assert bot1.id == 42 diff --git a/uv.lock b/uv.lock index 809545e0..913926a4 100644 --- a/uv.lock +++ b/uv.lock @@ -84,8 +84,6 @@ signature = [ [package.dev-dependencies] dev = [ - { name = "black" }, - { name = "isort" }, { name = "motor-types" }, { name = "mypy" }, { name = "packaging" }, @@ -139,8 +137,6 @@ provides-extras = ["cli", "docs", "fast", "i18n", "mongo", "proxy", "redis", "si [package.metadata.requires-dev] dev = [ - { name = "black", specifier = "~=25.9" }, - { name = "isort", specifier = "~=7.0" }, { name = "motor-types", specifier = "==1.0.0b4" }, { name = "mypy", specifier = "==1.10.1" }, { name = "packaging", specifier = "~=25.0" }, @@ -425,50 +421,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] -[[package]] -name = "black" -version = "25.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "pytokens" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/d9/07b458a3f1c525ac392b5edc6b191ff140b596f9d77092429417a54e249d/black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7", size = 659264, upload-time = "2025-12-08T01:40:52.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/d5/8d3145999d380e5d09bb00b0f7024bf0a8ccb5c07b5648e9295f02ec1d98/black-25.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f85ba1ad15d446756b4ab5f3044731bf68b777f8f9ac9cdabd2425b97cd9c4e8", size = 1895720, upload-time = "2025-12-08T01:46:58.197Z" }, - { url = "https://files.pythonhosted.org/packages/06/97/7acc85c4add41098f4f076b21e3e4e383ad6ed0a3da26b2c89627241fc11/black-25.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546eecfe9a3a6b46f9d69d8a642585a6eaf348bcbbc4d87a19635570e02d9f4a", size = 1727193, upload-time = "2025-12-08T01:52:26.674Z" }, - { url = "https://files.pythonhosted.org/packages/24/f0/fdf0eb8ba907ddeb62255227d29d349e8256ef03558fbcadfbc26ecfe3b2/black-25.12.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17dcc893da8d73d8f74a596f64b7c98ef5239c2cd2b053c0f25912c4494bf9ea", size = 1774506, upload-time = "2025-12-08T01:46:25.721Z" }, - { url = "https://files.pythonhosted.org/packages/e4/f5/9203a78efe00d13336786b133c6180a9303d46908a9aa72d1104ca214222/black-25.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:09524b0e6af8ba7a3ffabdfc7a9922fb9adef60fed008c7cd2fc01f3048e6e6f", size = 1416085, upload-time = "2025-12-08T01:46:06.073Z" }, - { url = "https://files.pythonhosted.org/packages/ba/cc/7a6090e6b081c3316282c05c546e76affdce7bf7a3b7d2c3a2a69438bd01/black-25.12.0-cp310-cp310-win_arm64.whl", hash = "sha256:b162653ed89eb942758efeb29d5e333ca5bb90e5130216f8369857db5955a7da", size = 1226038, upload-time = "2025-12-08T01:45:29.388Z" }, - { url = "https://files.pythonhosted.org/packages/60/ad/7ac0d0e1e0612788dbc48e62aef8a8e8feffac7eb3d787db4e43b8462fa8/black-25.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0cfa263e85caea2cff57d8f917f9f51adae8e20b610e2b23de35b5b11ce691a", size = 1877003, upload-time = "2025-12-08T01:43:29.967Z" }, - { url = "https://files.pythonhosted.org/packages/e8/dd/a237e9f565f3617a88b49284b59cbca2a4f56ebe68676c1aad0ce36a54a7/black-25.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a2f578ae20c19c50a382286ba78bfbeafdf788579b053d8e4980afb079ab9be", size = 1712639, upload-time = "2025-12-08T01:52:46.756Z" }, - { url = "https://files.pythonhosted.org/packages/12/80/e187079df1ea4c12a0c63282ddd8b81d5107db6d642f7d7b75a6bcd6fc21/black-25.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e1b65634b0e471d07ff86ec338819e2ef860689859ef4501ab7ac290431f9b", size = 1758143, upload-time = "2025-12-08T01:45:29.137Z" }, - { url = "https://files.pythonhosted.org/packages/93/b5/3096ccee4f29dc2c3aac57274326c4d2d929a77e629f695f544e159bfae4/black-25.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a3fa71e3b8dd9f7c6ac4d818345237dfb4175ed3bf37cd5a581dbc4c034f1ec5", size = 1420698, upload-time = "2025-12-08T01:45:53.379Z" }, - { url = "https://files.pythonhosted.org/packages/7e/39/f81c0ffbc25ffbe61c7d0385bf277e62ffc3e52f5ee668d7369d9854fadf/black-25.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:51e267458f7e650afed8445dc7edb3187143003d52a1b710c7321aef22aa9655", size = 1229317, upload-time = "2025-12-08T01:46:35.606Z" }, - { url = "https://files.pythonhosted.org/packages/d1/bd/26083f805115db17fda9877b3c7321d08c647df39d0df4c4ca8f8450593e/black-25.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f96b7c98c1ddaeb07dc0f56c652e25bdedaac76d5b68a059d998b57c55594a", size = 1924178, upload-time = "2025-12-08T01:49:51.048Z" }, - { url = "https://files.pythonhosted.org/packages/89/6b/ea00d6651561e2bdd9231c4177f4f2ae19cc13a0b0574f47602a7519b6ca/black-25.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05dd459a19e218078a1f98178c13f861fe6a9a5f88fc969ca4d9b49eb1809783", size = 1742643, upload-time = "2025-12-08T01:49:59.09Z" }, - { url = "https://files.pythonhosted.org/packages/6d/f3/360fa4182e36e9875fabcf3a9717db9d27a8d11870f21cff97725c54f35b/black-25.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1f68c5eff61f226934be6b5b80296cf6939e5d2f0c2f7d543ea08b204bfaf59", size = 1800158, upload-time = "2025-12-08T01:44:27.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/08/2c64830cb6616278067e040acca21d4f79727b23077633953081c9445d61/black-25.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:274f940c147ddab4442d316b27f9e332ca586d39c85ecf59ebdea82cc9ee8892", size = 1426197, upload-time = "2025-12-08T01:45:51.198Z" }, - { url = "https://files.pythonhosted.org/packages/d4/60/a93f55fd9b9816b7432cf6842f0e3000fdd5b7869492a04b9011a133ee37/black-25.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:169506ba91ef21e2e0591563deda7f00030cb466e747c4b09cb0a9dae5db2f43", size = 1237266, upload-time = "2025-12-08T01:45:10.556Z" }, - { url = "https://files.pythonhosted.org/packages/c8/52/c551e36bc95495d2aa1a37d50566267aa47608c81a53f91daa809e03293f/black-25.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a05ddeb656534c3e27a05a29196c962877c83fa5503db89e68857d1161ad08a5", size = 1923809, upload-time = "2025-12-08T01:46:55.126Z" }, - { url = "https://files.pythonhosted.org/packages/a0/f7/aac9b014140ee56d247e707af8db0aae2e9efc28d4a8aba92d0abd7ae9d1/black-25.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ec77439ef3e34896995503865a85732c94396edcc739f302c5673a2315e1e7f", size = 1742384, upload-time = "2025-12-08T01:49:37.022Z" }, - { url = "https://files.pythonhosted.org/packages/74/98/38aaa018b2ab06a863974c12b14a6266badc192b20603a81b738c47e902e/black-25.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e509c858adf63aa61d908061b52e580c40eae0dfa72415fa47ac01b12e29baf", size = 1798761, upload-time = "2025-12-08T01:46:05.386Z" }, - { url = "https://files.pythonhosted.org/packages/16/3a/a8ac542125f61574a3f015b521ca83b47321ed19bb63fe6d7560f348bfe1/black-25.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:252678f07f5bac4ff0d0e9b261fbb029fa530cfa206d0a636a34ab445ef8ca9d", size = 1429180, upload-time = "2025-12-08T01:45:34.903Z" }, - { url = "https://files.pythonhosted.org/packages/e6/2d/bdc466a3db9145e946762d52cd55b1385509d9f9004fec1c97bdc8debbfb/black-25.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc5b1c09fe3c931ddd20ee548511c64ebf964ada7e6f0763d443947fd1c603ce", size = 1239350, upload-time = "2025-12-08T01:46:09.458Z" }, - { url = "https://files.pythonhosted.org/packages/35/46/1d8f2542210c502e2ae1060b2e09e47af6a5e5963cb78e22ec1a11170b28/black-25.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a0953b134f9335c2434864a643c842c44fba562155c738a2a37a4d61f00cad5", size = 1917015, upload-time = "2025-12-08T01:53:27.987Z" }, - { url = "https://files.pythonhosted.org/packages/41/37/68accadf977672beb8e2c64e080f568c74159c1aaa6414b4cd2aef2d7906/black-25.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2355bbb6c3b76062870942d8cc450d4f8ac71f9c93c40122762c8784df49543f", size = 1741830, upload-time = "2025-12-08T01:54:36.861Z" }, - { url = "https://files.pythonhosted.org/packages/ac/76/03608a9d8f0faad47a3af3a3c8c53af3367f6c0dd2d23a84710456c7ac56/black-25.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9678bd991cc793e81d19aeeae57966ee02909877cb65838ccffef24c3ebac08f", size = 1791450, upload-time = "2025-12-08T01:44:52.581Z" }, - { url = "https://files.pythonhosted.org/packages/06/99/b2a4bd7dfaea7964974f947e1c76d6886d65fe5d24f687df2d85406b2609/black-25.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:97596189949a8aad13ad12fcbb4ae89330039b96ad6742e6f6b45e75ad5cfd83", size = 1452042, upload-time = "2025-12-08T01:46:13.188Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7c/d9825de75ae5dd7795d007681b752275ea85a1c5d83269b4b9c754c2aaab/black-25.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:778285d9ea197f34704e3791ea9404cd6d07595745907dd2ce3da7a13627b29b", size = 1267446, upload-time = "2025-12-08T01:46:14.497Z" }, - { url = "https://files.pythonhosted.org/packages/68/11/21331aed19145a952ad28fca2756a1433ee9308079bd03bd898e903a2e53/black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828", size = 206191, upload-time = "2025-12-08T01:40:50.963Z" }, -] - [[package]] name = "certifi" version = "2025.11.12" @@ -1175,15 +1127,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] -[[package]] -name = "isort" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049, upload-time = "2025-10-11T13:30:59.107Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" }, -] - [[package]] name = "jinja2" version = "3.1.6" @@ -1530,15 +1473,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, -] - [[package]] name = "platformdirs" version = "4.5.1" @@ -2172,15 +2106,6 @@ asyncio = [ { name = "async-timeout", marker = "python_full_version < '3.11'" }, ] -[[package]] -name = "pytokens" -version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" }, -] - [[package]] name = "pytz" version = "2025.2"