diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py index a4a56127..1c6de27c 100644 --- a/aiogram/client/bot.py +++ b/aiogram/client/bot.py @@ -20,7 +20,9 @@ from typing import ( import aiofiles from aiogram.utils.token import extract_bot_id, validate_token - +from .default import Default, DefaultBotProperties +from .session.aiohttp import AiohttpSession +from .session.base import BaseSession from ..methods import ( AddStickerToSet, AnswerCallbackQuery, @@ -165,7 +167,6 @@ from ..types import ( ChatMemberOwner, ChatMemberRestricted, ChatPermissions, - DateTime, Downloadable, File, ForceReply, @@ -234,9 +235,6 @@ from ..types import ( UserProfilePhotos, WebhookInfo, ) -from .default import Default, DefaultBotProperties -from .session.aiohttp import AiohttpSession -from .session.base import BaseSession T = TypeVar("T") @@ -2895,7 +2893,7 @@ class Bot: explanation_parse_mode: Optional[Union[str, Default]] = Default("parse_mode"), explanation_entities: Optional[List[MessageEntity]] = None, open_period: Optional[int] = None, - close_date: Optional[DateTime] = None, + close_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None, is_closed: Optional[bool] = None, disable_notification: Optional[bool] = None, protect_content: Optional[Union[bool, Default]] = Default("protect_content"), diff --git a/aiogram/types/base.py b/aiogram/types/base.py index 4fbe7113..e251cfa0 100644 --- a/aiogram/types/base.py +++ b/aiogram/types/base.py @@ -45,7 +45,7 @@ class TelegramObject(BotContextController, BaseModel): Replacing `Default` placeholders with actual values from bot defaults. Ensures JSON serialization backward compatibility by handling non-standard objects. """ - if isinstance(self, BotContextController) and isinstance(self, BaseModel): + if isinstance(self, TelegramObject): properties = self.bot.default if self.bot else DefaultBotProperties() default_fields = { key: properties[value.name] for key, value in self if isinstance(value, Default) diff --git a/aiogram/types/input_media_animation.py b/aiogram/types/input_media_animation.py index ac05dbbe..9dcf0a9e 100644 --- a/aiogram/types/input_media_animation.py +++ b/aiogram/types/input_media_animation.py @@ -20,7 +20,7 @@ class InputMediaAnimation(InputMedia): type: Literal[InputMediaType.ANIMATION] = InputMediaType.ANIMATION """Type of the result, must be *animation*""" - media: Union[InputFile, str] + media: Union[str, InputFile] """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More information on Sending Files » `""" thumbnail: Optional[InputFile] = None """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files » `""" @@ -47,7 +47,7 @@ class InputMediaAnimation(InputMedia): __pydantic__self__, *, type: Literal[InputMediaType.ANIMATION] = InputMediaType.ANIMATION, - media: Union[InputFile, str], + media: Union[str, InputFile], thumbnail: Optional[InputFile] = None, caption: Optional[str] = None, parse_mode: Optional[Union[str, Default]] = Default("parse_mode"), diff --git a/aiogram/types/input_media_audio.py b/aiogram/types/input_media_audio.py index bb551c90..c3722011 100644 --- a/aiogram/types/input_media_audio.py +++ b/aiogram/types/input_media_audio.py @@ -20,7 +20,7 @@ class InputMediaAudio(InputMedia): type: Literal[InputMediaType.AUDIO] = InputMediaType.AUDIO """Type of the result, must be *audio*""" - media: Union[InputFile, str] + media: Union[str, InputFile] """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More information on Sending Files » `""" thumbnail: Optional[InputFile] = None """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files » `""" @@ -45,7 +45,7 @@ class InputMediaAudio(InputMedia): __pydantic__self__, *, type: Literal[InputMediaType.AUDIO] = InputMediaType.AUDIO, - media: Union[InputFile, str], + media: Union[str, InputFile], thumbnail: Optional[InputFile] = None, caption: Optional[str] = None, parse_mode: Optional[Union[str, Default]] = Default("parse_mode"), diff --git a/aiogram/types/input_media_document.py b/aiogram/types/input_media_document.py index 100f66b8..7c24b929 100644 --- a/aiogram/types/input_media_document.py +++ b/aiogram/types/input_media_document.py @@ -20,7 +20,7 @@ class InputMediaDocument(InputMedia): type: Literal[InputMediaType.DOCUMENT] = InputMediaType.DOCUMENT """Type of the result, must be *document*""" - media: Union[InputFile, str] + media: Union[str, InputFile] """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More information on Sending Files » `""" thumbnail: Optional[InputFile] = None """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files » `""" @@ -41,7 +41,7 @@ class InputMediaDocument(InputMedia): __pydantic__self__, *, type: Literal[InputMediaType.DOCUMENT] = InputMediaType.DOCUMENT, - media: Union[InputFile, str], + media: Union[str, InputFile], thumbnail: Optional[InputFile] = None, caption: Optional[str] = None, parse_mode: Optional[Union[str, Default]] = Default("parse_mode"), diff --git a/aiogram/types/input_media_photo.py b/aiogram/types/input_media_photo.py index db00a72f..ecb3c91d 100644 --- a/aiogram/types/input_media_photo.py +++ b/aiogram/types/input_media_photo.py @@ -20,7 +20,7 @@ class InputMediaPhoto(InputMedia): type: Literal[InputMediaType.PHOTO] = InputMediaType.PHOTO """Type of the result, must be *photo*""" - media: Union[InputFile, str] + media: Union[str, InputFile] """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More information on Sending Files » `""" caption: Optional[str] = None """*Optional*. Caption of the photo to be sent, 0-1024 characters after entities parsing""" @@ -39,7 +39,7 @@ class InputMediaPhoto(InputMedia): __pydantic__self__, *, type: Literal[InputMediaType.PHOTO] = InputMediaType.PHOTO, - media: Union[InputFile, str], + media: Union[str, InputFile], caption: Optional[str] = None, parse_mode: Optional[Union[str, Default]] = Default("parse_mode"), caption_entities: Optional[List[MessageEntity]] = None, diff --git a/aiogram/types/input_media_video.py b/aiogram/types/input_media_video.py index 5ffbe640..b44f6a76 100644 --- a/aiogram/types/input_media_video.py +++ b/aiogram/types/input_media_video.py @@ -20,7 +20,7 @@ class InputMediaVideo(InputMedia): type: Literal[InputMediaType.VIDEO] = InputMediaType.VIDEO """Type of the result, must be *video*""" - media: Union[InputFile, str] + media: Union[str, InputFile] """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More information on Sending Files » `""" thumbnail: Optional[InputFile] = None """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files » `""" @@ -49,7 +49,7 @@ class InputMediaVideo(InputMedia): __pydantic__self__, *, type: Literal[InputMediaType.VIDEO] = InputMediaType.VIDEO, - media: Union[InputFile, str], + media: Union[str, InputFile], thumbnail: Optional[InputFile] = None, caption: Optional[str] = None, parse_mode: Optional[Union[str, Default]] = Default("parse_mode"), diff --git a/aiogram/utils/media_group.py b/aiogram/utils/media_group.py index ebe04667..e4277d2f 100644 --- a/aiogram/utils/media_group.py +++ b/aiogram/utils/media_group.py @@ -63,7 +63,7 @@ class MediaGroupBuilder: self, *, type: Literal[InputMediaType.AUDIO], - media: Union[InputFile, str], + media: Union[str, InputFile], caption: Optional[str] = None, parse_mode: Optional[str] = UNSET_PARSE_MODE, caption_entities: Optional[List[MessageEntity]] = None, @@ -79,7 +79,7 @@ class MediaGroupBuilder: self, *, type: Literal[InputMediaType.PHOTO], - media: Union[InputFile, str], + media: Union[str, InputFile], caption: Optional[str] = None, parse_mode: Optional[str] = UNSET_PARSE_MODE, caption_entities: Optional[List[MessageEntity]] = None, @@ -93,7 +93,7 @@ class MediaGroupBuilder: self, *, type: Literal[InputMediaType.VIDEO], - media: Union[InputFile, str], + media: Union[str, InputFile], thumbnail: Optional[Union[InputFile, str]] = None, caption: Optional[str] = None, parse_mode: Optional[str] = UNSET_PARSE_MODE, @@ -112,7 +112,7 @@ class MediaGroupBuilder: self, *, type: Literal[InputMediaType.DOCUMENT], - media: Union[InputFile, str], + media: Union[str, InputFile], thumbnail: Optional[Union[InputFile, str]] = None, caption: Optional[str] = None, parse_mode: Optional[str] = UNSET_PARSE_MODE, @@ -144,7 +144,7 @@ class MediaGroupBuilder: def add_audio( self, - media: Union[InputFile, str], + media: Union[str, InputFile], thumbnail: Optional[InputFile] = None, caption: Optional[str] = None, parse_mode: Optional[str] = UNSET_PARSE_MODE, @@ -194,7 +194,7 @@ class MediaGroupBuilder: def add_photo( self, - media: Union[InputFile, str], + media: Union[str, InputFile], caption: Optional[str] = None, parse_mode: Optional[str] = UNSET_PARSE_MODE, caption_entities: Optional[List[MessageEntity]] = None, @@ -233,7 +233,7 @@ class MediaGroupBuilder: def add_video( self, - media: Union[InputFile, str], + media: Union[str, InputFile], thumbnail: Optional[InputFile] = None, caption: Optional[str] = None, parse_mode: Optional[str] = UNSET_PARSE_MODE, @@ -295,7 +295,7 @@ class MediaGroupBuilder: def add_document( self, - media: Union[InputFile, str], + media: Union[str, InputFile], thumbnail: Optional[InputFile] = None, caption: Optional[str] = None, parse_mode: Optional[str] = UNSET_PARSE_MODE, diff --git a/aiogram/webhook/aiohttp_server.py b/aiogram/webhook/aiohttp_server.py index b1f34ddc..7a60c06d 100644 --- a/aiogram/webhook/aiohttp_server.py +++ b/aiogram/webhook/aiohttp_server.py @@ -8,6 +8,7 @@ from aiohttp import MultipartWriter, web from aiohttp.abc import Application from aiohttp.typedefs import Handler from aiohttp.web_middlewares import middleware +from pydantic_core import from_json, to_json from aiogram import Bot, Dispatcher, loggers from aiogram.client.form import extract_files, form_serialize @@ -141,11 +142,11 @@ class BaseRequestHandler(ABC): async def _handle_request_background(self, bot: Bot, request: web.Request) -> web.Response: feed_update_task = asyncio.create_task( - self._background_feed_update(bot=bot, update=await request.json()) + self._background_feed_update(bot=bot, update=await request.json(loads=from_json)) ) self._background_feed_update_tasks.add(feed_update_task) feed_update_task.add_done_callback(self._background_feed_update_tasks.discard) - return web.json_response({}) + return web.json_response({}) # TODO def _build_response_writer( self, bot: Bot, result: Optional[TelegramMethod[TelegramType]] 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 0ab9b77d..b90b500e 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 @@ -122,7 +122,7 @@ class TestBaseSession: ), "1494994302", ], - [LinkPreviewOptions(is_disabled=True, url=None), '{"is_disabled":true}'], + [LinkPreviewOptions(is_disabled=True), '{"is_disabled":true}'], [Default("parse_mode"), "HTML"], [Default("protect_content"), "true"], [Default("link_preview_is_disabled"), "true"], diff --git a/tests/test_api/test_methods/test_base.py b/tests/test_api/test_methods/test_base.py index 66cfd101..07dedc77 100644 --- a/tests/test_api/test_methods/test_base.py +++ b/tests/test_api/test_methods/test_base.py @@ -20,15 +20,15 @@ class TestTelegramMethodRemoveUnset: ) @pytest.mark.parametrize("obj", [TelegramMethod, TelegramObject]) def test_remove_unset(self, values, names, obj): - validated = obj.remove_unset(values) + validated = obj.remove_unset.wrapped(values) assert set(validated.keys()) == names @pytest.mark.parametrize("obj", [TelegramMethod, TelegramObject]) def test_remove_unset_non_dict(self, obj): - assert obj.remove_unset("") == "" + assert obj.remove_unset.wrapped("") == "" -class TestTelegramMethodJsonSerialize: +class TestTelegramMethodModelDumpJson: @pytest.mark.parametrize( "obj", [ @@ -39,7 +39,7 @@ class TestTelegramMethodJsonSerialize: LinkPreviewOptions(), ], ) - def test_json_serialize(self, obj): + def test_model_dump_json(self, obj): def has_defaults(dump: Dict[str, Any]) -> bool: return any(isinstance(value, Default) for value in dump.values()) diff --git a/tests/test_api/test_types/test_input_file.py b/tests/test_api/test_types/test_input_file.py index fa7a267b..c5cfcb04 100644 --- a/tests/test_api/test_types/test_input_file.py +++ b/tests/test_api/test_types/test_input_file.py @@ -99,3 +99,5 @@ class TestInputFile: assert chunk_size == 1 size += chunk_size assert size == 10 + + # TODO: used_valid_bot