diff --git a/CHANGES/1233.bugfix.rst b/CHANGES/1233.bugfix.rst new file mode 100644 index 00000000..17aa8fbc --- /dev/null +++ b/CHANGES/1233.bugfix.rst @@ -0,0 +1,4 @@ +Added model validation to remove UNSET before field validation. +This change was necessary to correctly handle parse_mode where 'UNSET' is used as a sentinel value. +Without the removal of 'UNSET', it would create issues when passed to model initialization from Bot.method_name. +'UNSET' was also added to typing. diff --git a/aiogram/types/base.py b/aiogram/types/base.py index 641b16df..9c24c703 100644 --- a/aiogram/types/base.py +++ b/aiogram/types/base.py @@ -1,7 +1,7 @@ -from typing import Any +from typing import Any, Dict from unittest.mock import sentinel -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, model_validator from aiogram.client.context_controller import BotContextController @@ -17,6 +17,19 @@ class TelegramObject(BotContextController, BaseModel): defer_build=True, ) + @model_validator(mode="before") + @classmethod + def remove_unset(cls, values: Dict[str, Any]) -> Dict[str, Any]: + """ + Remove UNSET before fields validation. + + We use UNSET as a sentinel value for `parse_mode` and replace it to real value later. + It isn't a problem when it's just default value for a model field, + but UNSET might be passed to a model initialization from `Bot.method_name`, + so we must take care of it and remove it before fields validation. + """ + return {k: v for k, v in values.items() if not isinstance(v, UNSET_TYPE)} + class MutableTelegramObject(TelegramObject): model_config = ConfigDict( @@ -24,7 +37,7 @@ class MutableTelegramObject(TelegramObject): ) -# special sentinel object which used in situation when None might be a useful value +# special sentinel object which used in a situation when None might be a useful value UNSET: Any = sentinel.UNSET UNSET_PARSE_MODE: Any = sentinel.UNSET_PARSE_MODE UNSET_DISABLE_WEB_PAGE_PREVIEW: Any = sentinel.UNSET_DISABLE_WEB_PAGE_PREVIEW