Repository: aiogram/aiogram
Pull request #1290 (2023-08-28 22:49:58)

Fixed error overlapping when validation error is caused by remove_unset root validator in base types and methods.
# Description

Fixed error overlapping when validation error is caused by `remove_unset` root validator in base types and methods.


## Example:

```python
InlineKeyboardButton(
    text="Play with Friends",
    switch_inline_query_chosen_chat="test",
)
```
### Caused:
```
  .../aiogram/types/base.py", line 31, in remove_unset
    return {k: v for k, v in values.items() if not isinstance(v, UNSET_TYPE)}
AttributeError: 'str' object has no attribute 'items'                                                                                                                      
```

### Expected:
```
pydantic_core._pydantic_core.ValidationError: 1 validation error for InlineKeyboardButton
switch_inline_query_chosen_chat
  Input should be a valid dictionary or instance of SwitchInlineQueryChosenChat [type=model_type, input_value='test', input_type=str]
    For further information visit https://errors.pydantic.dev/2.1/v/model_type
```

github-actions[bot]: # :x: Changelog is required!

You need to add a brief description of the changes to the `CHANGES` directory.

For example, you can run `towncrier create <issue>.<type>` to create a file in the change directory and then write a description on that file.

Read more at [Towncrier docs](https://towncrier.readthedocs.io/en/latest/quickstart.html#creating-news-fragments)
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/aiogram/aiogram/pull/1290?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) Report
> Merging [#1290](https://app.codecov.io/gh/aiogram/aiogram/pull/1290?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) (639c058) into [dev-3.x](https://app.codecov.io/gh/aiogram/aiogram/commit/e1be9dd6681bb79cfbf96e125c9d2acb429dbdc0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) (e1be9dd) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://app.codecov.io/gh/aiogram/aiogram/pull/1290/graphs/tree.svg?width=650&height=150&src=pr&token=cFqEr3p9MX&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram)](https://app.codecov.io/gh/aiogram/aiogram/pull/1290?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram)

```diff
@@            Coverage Diff            @@
##           dev-3.x     #1290   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          367       367           
  Lines         9350      9354    +4     
=========================================
+ Hits          9350      9354    +4     
```

| [Flag](https://app.codecov.io/gh/aiogram/aiogram/pull/1290/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/aiogram/aiogram/pull/1290/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) | `100.00% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files Changed](https://app.codecov.io/gh/aiogram/aiogram/pull/1290?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) | Coverage Δ | |
|---|---|---|
| [aiogram/methods/base.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1290?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9tZXRob2RzL2Jhc2UucHk=) | `100.00% <100.00%> (ø)` | |
| [aiogram/types/base.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1290?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS90eXBlcy9iYXNlLnB5) | `100.00% <100.00%> (ø)` | |


Patch:
diff --git a/CHANGES/1290.bugfix.rst b/CHANGES/1290.bugfix.rst
new file mode 100644
index 00000000..04ba92dc
--- /dev/null
+++ b/CHANGES/1290.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed error overlapping when validation error is caused by remove_unset root validator in base types and methods.
diff --git a/aiogram/methods/base.py b/aiogram/methods/base.py
index 221ad9c0..b2d35bec 100644
--- a/aiogram/methods/base.py
+++ b/aiogram/methods/base.py
@@ -61,6 +61,8 @@ class TelegramMethod(BotContextController, BaseModel, Generic[TelegramType], ABC
         but UNSET might be passing to a model initialization from `Bot.method_name`,
         so we must take care of it and remove it before fields validation.
         """
+        if not isinstance(values, dict):
+            return values
         return {k: v for k, v in values.items() if not isinstance(v, UNSET_TYPE)}
 
     if TYPE_CHECKING:
diff --git a/aiogram/types/base.py b/aiogram/types/base.py
index 9c24c703..dae52156 100644
--- a/aiogram/types/base.py
+++ b/aiogram/types/base.py
@@ -28,6 +28,8 @@ class TelegramObject(BotContextController, BaseModel):
         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.
         """
+        if not isinstance(values, dict):
+            return values
         return {k: v for k, v in values.items() if not isinstance(v, UNSET_TYPE)}
 
 


Test patch:
diff --git a/tests/test_api/test_methods/test_base.py b/tests/test_api/test_methods/test_base.py
index 9626c9b7..498428ce 100644
--- a/tests/test_api/test_methods/test_base.py
+++ b/tests/test_api/test_methods/test_base.py
@@ -3,7 +3,7 @@ from unittest.mock import sentinel
 import pytest
 
 from aiogram.methods import GetMe, TelegramMethod
-from aiogram.types import User
+from aiogram.types import User, TelegramObject
 from tests.mocked_bot import MockedBot
 
 
@@ -16,10 +16,15 @@ class TestTelegramMethodRemoveUnset:
             [{"foo": "bar", "baz": sentinel.DEFAULT}, {"foo"}],
         ],
     )
-    def test_remove_unset(self, values, names):
-        validated = TelegramMethod.remove_unset(values)
+    @pytest.mark.parametrize("obj", [TelegramMethod, TelegramObject])
+    def test_remove_unset(self, values, names, obj):
+        validated = obj.remove_unset(values)
         assert set(validated.keys()) == names
 
+    @pytest.mark.parametrize("obj", [TelegramMethod, TelegramObject])
+    def test_remove_unset_non_dict(self, obj):
+        assert obj.remove_unset("") == ""
+
 
 class TestTelegramMethodCall:
     async def test_async_emit_unsuccessful(self, bot: MockedBot):