Repository: aiogram/aiogram
Pull request #1683 (2025-04-28 07:56:36)

Refactor FSM Storage methods input types to calm down MyPy.
#1682 

# Description

Refactor FSM Storage methods input types to calm down MyPy.

Refactored:
- `FSMContext.set_data`
- `FSMContext.update_data`
- `BaseStorage.set_data`
- `BaseStorage.update_data`
- `BaseStorage`'s child methods
- `SceneWizard.set_data`
- `SceneWizard.update_data`

## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] This change requires a documentation update

# How Has This Been Tested?

1 test edited to cover new code

# Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [ ] I have made corresponding changes to the documentation
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes


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

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

Changes file should be named like `<issue or PR number>.<category>.rst`,
example `1234.bugfix.rst` where `1234` is the PR or issue number and `bugfix` is the category.

The content of the file should be a brief description of the changes in
the PR in the format of a description of what has been done.

Possible categories are: `feature`, `bugfix`, `doc`, `removal` and `misc`.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/aiogram/aiogram/pull/1683?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`482629a`)](https://app.codecov.io/gh/aiogram/aiogram/commit/482629ac188d255b7ccf70df2e91a3f3ab5ee7c4?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) to head [(`19250ee`)](https://app.codecov.io/gh/aiogram/aiogram/commit/19250ee43a2507cb87e4ea8b3cb9a82daaee3462?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram).

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/aiogram/aiogram/pull/1683/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/1683?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     #1683   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          568       568           
  Lines        13341     13351   +10     
=========================================
+ Hits         13341     13351   +10     
```

| [Flag](https://app.codecov.io/gh/aiogram/aiogram/pull/1683/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/1683/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 with missing lines](https://app.codecov.io/gh/aiogram/aiogram/pull/1683?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) | Coverage Δ | |
|---|---|---|
| [aiogram/exceptions.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1683?src=pr&el=tree&filepath=aiogram%2Fexceptions.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9leGNlcHRpb25zLnB5) | `100.00% <100.00%> (ø)` | |
| [aiogram/fsm/context.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1683?src=pr&el=tree&filepath=aiogram%2Ffsm%2Fcontext.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9mc20vY29udGV4dC5weQ==) | `100.00% <100.00%> (ø)` | |
| [aiogram/fsm/scene.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1683?src=pr&el=tree&filepath=aiogram%2Ffsm%2Fscene.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9mc20vc2NlbmUucHk=) | `100.00% <100.00%> (ø)` | |
| [aiogram/fsm/storage/base.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1683?src=pr&el=tree&filepath=aiogram%2Ffsm%2Fstorage%2Fbase.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9mc20vc3RvcmFnZS9iYXNlLnB5) | `100.00% <100.00%> (ø)` | |
| [aiogram/fsm/storage/memory.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1683?src=pr&el=tree&filepath=aiogram%2Ffsm%2Fstorage%2Fmemory.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9mc20vc3RvcmFnZS9tZW1vcnkucHk=) | `100.00% <100.00%> (ø)` | |
| [aiogram/fsm/storage/mongo.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1683?src=pr&el=tree&filepath=aiogram%2Ffsm%2Fstorage%2Fmongo.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9mc20vc3RvcmFnZS9tb25nby5weQ==) | `100.00% <100.00%> (ø)` | |
| [aiogram/fsm/storage/redis.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1683?src=pr&el=tree&filepath=aiogram%2Ffsm%2Fstorage%2Fredis.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9mc20vc3RvcmFnZS9yZWRpcy5weQ==) | `100.00% <100.00%> (ø)` | |

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/CHANGES/1681.misc.rst b/CHANGES/1681.misc.rst
new file mode 100644
index 00000000..b74f064b
--- /dev/null
+++ b/CHANGES/1681.misc.rst
@@ -0,0 +1,1 @@
+Changed Babel's pinned version from minor to major.
diff --git a/CHANGES/1683.feature.rst b/CHANGES/1683.feature.rst
new file mode 100644
index 00000000..97a53e2e
--- /dev/null
+++ b/CHANGES/1683.feature.rst
@@ -0,0 +1,11 @@
+Refactor methods input types to calm down MyPy. #1682
+
+`Dict[str, Any]` is replaced with `Mapping[str, Any]` in the following methods:
+
+- `FSMContext.set_data`
+- `FSMContext.update_data`
+- `BaseStorage.set_data`
+- `BaseStorage.update_data`
+- `BaseStorage's child methods`
+- `SceneWizard.set_data`
+- `SceneWizard.update_data`
diff --git a/CHANGES/1685.feature.rst b/CHANGES/1685.feature.rst
new file mode 100644
index 00000000..e53cf5a4
--- /dev/null
+++ b/CHANGES/1685.feature.rst
@@ -0,0 +1,1 @@
+Add support for `State` type in scenes methods like `goto`, `enter`, `get`
diff --git a/aiogram/exceptions.py b/aiogram/exceptions.py
index d195aa7b..a1b73d56 100644
--- a/aiogram/exceptions.py
+++ b/aiogram/exceptions.py
@@ -197,3 +197,9 @@ class ClientDecodeError(AiogramError):
             f"{original_type.__module__}.{original_type.__name__}: {self.original}\n"
             f"Content: {self.data}"
         )
+
+
+class DataNotDictLikeError(DetailedAiogramError):
+    """
+    Exception raised when data is not dict-like.
+    """
diff --git a/aiogram/fsm/context.py b/aiogram/fsm/context.py
index c9432c11..f353df5d 100644
--- a/aiogram/fsm/context.py
+++ b/aiogram/fsm/context.py
@@ -1,4 +1,4 @@
-from typing import Any, Dict, Optional, overload
+from typing import Any, Dict, Mapping, Optional, overload
 
 from aiogram.fsm.storage.base import BaseStorage, StateType, StorageKey
 
@@ -14,7 +14,7 @@ class FSMContext:
     async def get_state(self) -> Optional[str]:
         return await self.storage.get_state(key=self.key)
 
-    async def set_data(self, data: Dict[str, Any]) -> None:
+    async def set_data(self, data: Mapping[str, Any]) -> None:
         await self.storage.set_data(key=self.key, data=data)
 
     async def get_data(self) -> Dict[str, Any]:
@@ -30,7 +30,7 @@ class FSMContext:
         return await self.storage.get_value(storage_key=self.key, dict_key=key, default=default)
 
     async def update_data(
-        self, data: Optional[Dict[str, Any]] = None, **kwargs: Any
+        self, data: Optional[Mapping[str, Any]] = None, **kwargs: Any
     ) -> Dict[str, Any]:
         if data:
             kwargs.update(data)
diff --git a/aiogram/fsm/scene.py b/aiogram/fsm/scene.py
index 11f7e5a9..94def0d6 100644
--- a/aiogram/fsm/scene.py
+++ b/aiogram/fsm/scene.py
@@ -4,7 +4,18 @@ import inspect
 from collections import defaultdict
 from dataclasses import dataclass, replace
 from enum import Enum, auto
-from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, Union, overload
+from typing import (
+    Any,
+    ClassVar,
+    Dict,
+    List,
+    Mapping,
+    Optional,
+    Tuple,
+    Type,
+    Union,
+    overload,
+)
 
 from typing_extensions import Self
 
@@ -172,7 +183,7 @@ class ActionContainer:
         name: str,
         filters: Tuple[CallbackType, ...],
         action: SceneAction,
-        target: Optional[Union[Type[Scene], str]] = None,
+        target: Optional[Union[Type[Scene], State, str]] = None,
     ) -> None:
         self.name = name
         self.filters = filters
@@ -537,14 +548,14 @@ class SceneWizard:
         assert self.scene_config.state is not None, "Scene state is not specified"
         await self.goto(self.scene_config.state, **kwargs)
 
-    async def goto(self, scene: Union[Type[Scene], str], **kwargs: Any) -> None:
+    async def goto(self, scene: Union[Type[Scene], State, str], **kwargs: Any) -> None:
         """
         The `goto` method transitions to a new scene.
         It first calls the `leave` method to perform any necessary cleanup
         in the current scene, then calls the `enter` event to enter the specified scene.
 
         :param scene: The scene to transition to. Can be either a `Scene` instance
-            or a string representing the scene.
+            `State` instance or a string representing the scene.
         :param kwargs: Additional keyword arguments to pass to the `enter`
             method of the scene manager.
         :return: None
@@ -577,11 +588,11 @@ class SceneWizard:
         await action_config[event_type].call(self.scene, self.event, **{**self.data, **kwargs})
         return True
 
-    async def set_data(self, data: Dict[str, Any]) -> None:
+    async def set_data(self, data: Mapping[str, Any]) -> None:
         """
         Sets custom data in the current state.
 
-        :param data: A dictionary containing the custom data to be set in the current state.
+        :param data: A mapping containing the custom data to be set in the current state.
         :return: None
         """
         await self.state.set_data(data=data)
@@ -621,12 +632,12 @@ class SceneWizard:
         return await self.state.get_value(key, default)
 
     async def update_data(
-        self, data: Optional[Dict[str, Any]] = None, **kwargs: Any
+        self, data: Optional[Mapping[str, Any]] = None, **kwargs: Any
     ) -> Dict[str, Any]:
         """
         This method updates the data stored in the current state
 
-        :param data: Optional dictionary of data to update.
+        :param data: Optional mapping of data to update.
         :param kwargs: Additional key-value pairs of data to update.
         :return: Dictionary of updated data
         """
@@ -665,7 +676,7 @@ class ScenesManager:
 
         self.history = HistoryManager(self.state)
 
-    async def _get_scene(self, scene_type: Optional[Union[Type[Scene], str]]) -> Scene:
+    async def _get_scene(self, scene_type: Optional[Union[Type[Scene], State, str]]) -> Scene:
         scene_type = self.registry.get(scene_type)
         return scene_type(
             wizard=SceneWizard(
@@ -687,14 +698,14 @@ class ScenesManager:
 
     async def enter(
         self,
-        scene_type: Optional[Union[Type[Scene], str]],
+        scene_type: Optional[Union[Type[Scene], State, str]],
         _check_active: bool = True,
         **kwargs: Any,
     ) -> None:
         """
         Enters the specified scene.
 
-        :param scene_type: Optional Type[Scene] or str representing the scene type to enter.
+        :param scene_type: Optional Type[Scene], State or str representing the scene type to enter.
         :param _check_active: Optional bool indicating whether to check if
             there is an active scene to exit before entering the new scene. Defaults to True.
         :param kwargs: Additional keyword arguments to pass to the scene's wizard.enter() method.
@@ -833,18 +844,19 @@ class SceneRegistry:
         """
         self.add(*scenes, router=self.router)
 
-    def get(self, scene: Optional[Union[Type[Scene], str]]) -> Type[Scene]:
+    def get(self, scene: Optional[Union[Type[Scene], State, str]]) -> Type[Scene]:
         """
         This method returns the registered Scene object for the specified scene.
-        The scene parameter can be either a Scene object or a string representing
+        The scene parameter can be either a Scene object, State object or a string representing
         the name of the scene. If a Scene object is provided, the state attribute
         of the SceneConfig object associated with the Scene object will be used as the scene name.
-        If None or an invalid type is provided, a SceneException will be raised.
+        If a State object is provided, the state attribute of the State object will be used as the
+        scene name. If None or an invalid type is provided, a SceneException will be raised.
 
         If the specified scene is not registered in the SceneRegistry object,
         a SceneException will be raised.
 
-        :param scene: A Scene object or a string representing the name of the scene.
+        :param scene: A Scene object, State object or a string representing the name of the scene.
         :return: The registered Scene object corresponding to the given scene parameter.
 
         """
@@ -853,7 +865,7 @@ class SceneRegistry:
         if isinstance(scene, State):
             scene = scene.state
         if scene is not None and not isinstance(scene, str):
-            raise SceneException("Scene must be a subclass of Scene or a string")
+            raise SceneException("Scene must be a subclass of Scene, State or a string")
 
         try:
             return self._scenes[scene]
@@ -864,7 +876,7 @@ class SceneRegistry:
 @dataclass
 class After:
     action: SceneAction
-    scene: Optional[Union[Type[Scene], str]] = None
+    scene: Optional[Union[Type[Scene], State, str]] = None
 
     @classmethod
     def exit(cls) -> After:
@@ -875,7 +887,7 @@ class After:
         return cls(action=SceneAction.back)
 
     @classmethod
-    def goto(cls, scene: Optional[Union[Type[Scene], str]]) -> After:
+    def goto(cls, scene: Optional[Union[Type[Scene], State, str]]) -> After:
         return cls(action=SceneAction.enter, scene=scene)
 
 
diff --git a/aiogram/fsm/storage/base.py b/aiogram/fsm/storage/base.py
index 96cb2cbe..7a1059d6 100644
--- a/aiogram/fsm/storage/base.py
+++ b/aiogram/fsm/storage/base.py
@@ -1,7 +1,16 @@
 from abc import ABC, abstractmethod
 from contextlib import asynccontextmanager
 from dataclasses import dataclass
-from typing import Any, AsyncGenerator, Dict, Literal, Optional, Union, overload
+from typing import (
+    Any,
+    AsyncGenerator,
+    Dict,
+    Literal,
+    Mapping,
+    Optional,
+    Union,
+    overload,
+)
 
 from aiogram.fsm.state import State
 
@@ -125,7 +134,7 @@ class BaseStorage(ABC):
         pass
 
     @abstractmethod
-    async def set_data(self, key: StorageKey, data: Dict[str, Any]) -> None:
+    async def set_data(self, key: StorageKey, data: Mapping[str, Any]) -> None:
         """
         Write data (replace)
 
@@ -173,7 +182,7 @@ class BaseStorage(ABC):
         data = await self.get_data(storage_key)
         return data.get(dict_key, default)
 
-    async def update_data(self, key: StorageKey, data: Dict[str, Any]) -> Dict[str, Any]:
+    async def update_data(self, key: StorageKey, data: Mapping[str, Any]) -> Dict[str, Any]:
         """
         Update date in the storage for key (like dict.update)
 
diff --git a/aiogram/fsm/storage/memory.py b/aiogram/fsm/storage/memory.py
index f26d15c0..b5eebe4a 100644
--- a/aiogram/fsm/storage/memory.py
+++ b/aiogram/fsm/storage/memory.py
@@ -3,8 +3,18 @@ from collections import defaultdict
 from contextlib import asynccontextmanager
 from copy import copy
 from dataclasses import dataclass, field
-from typing import Any, AsyncGenerator, DefaultDict, Dict, Hashable, Optional, overload
+from typing import (
+    Any,
+    AsyncGenerator,
+    DefaultDict,
+    Dict,
+    Hashable,
+    Mapping,
+    Optional,
+    overload,
+)
 
+from aiogram.exceptions import DataNotDictLikeError
 from aiogram.fsm.state import State
 from aiogram.fsm.storage.base import (
     BaseEventIsolation,
@@ -44,7 +54,11 @@ class MemoryStorage(BaseStorage):
     async def get_state(self, key: StorageKey) -> Optional[str]:
         return self.storage[key].state
 
-    async def set_data(self, key: StorageKey, data: Dict[str, Any]) -> None:
+    async def set_data(self, key: StorageKey, data: Mapping[str, Any]) -> None:
+        if not isinstance(data, dict):
+            raise DataNotDictLikeError(
+                f"Data must be a dict or dict-like object, got {type(data).__name__}"
+            )
         self.storage[key].data = data.copy()
 
     async def get_data(self, key: StorageKey) -> Dict[str, Any]:
diff --git a/aiogram/fsm/storage/mongo.py b/aiogram/fsm/storage/mongo.py
index b4b1eeaa..b44aeeb9 100644
--- a/aiogram/fsm/storage/mongo.py
+++ b/aiogram/fsm/storage/mongo.py
@@ -1,7 +1,8 @@
-from typing import Any, Dict, Optional, cast
+from typing import Any, Dict, Mapping, Optional, cast
 
 from motor.motor_asyncio import AsyncIOMotorClient
 
+from aiogram.exceptions import DataNotDictLikeError
 from aiogram.fsm.state import State
 from aiogram.fsm.storage.base import (
     BaseStorage,
@@ -90,7 +91,12 @@ class MongoStorage(BaseStorage):
             return None
         return document.get("state")
 
-    async def set_data(self, key: StorageKey, data: Dict[str, Any]) -> None:
+    async def set_data(self, key: StorageKey, data: Mapping[str, Any]) -> None:
+        if not isinstance(data, dict):
+            raise DataNotDictLikeError(
+                f"Data must be a dict or dict-like object, got {type(data).__name__}"
+            )
+
         document_id = self._key_builder.build(key)
         if not data:
             updated = await self._collection.find_one_and_update(
@@ -115,7 +121,7 @@ class MongoStorage(BaseStorage):
             return {}
         return cast(Dict[str, Any], document["data"])
 
-    async def update_data(self, key: StorageKey, data: Dict[str, Any]) -> Dict[str, Any]:
+    async def update_data(self, key: StorageKey, data: Mapping[str, Any]) -> Dict[str, Any]:
         document_id = self._key_builder.build(key)
         update_with = {f"data.{key}": value for key, value in data.items()}
         update_result = await self._collection.find_one_and_update(
diff --git a/aiogram/fsm/storage/redis.py b/aiogram/fsm/storage/redis.py
index edcf9d63..4136972d 100644
--- a/aiogram/fsm/storage/redis.py
+++ b/aiogram/fsm/storage/redis.py
@@ -1,12 +1,13 @@
 import json
 from contextlib import asynccontextmanager
-from typing import Any, AsyncGenerator, Callable, Dict, Optional, cast
+from typing import Any, AsyncGenerator, Callable, Dict, Mapping, Optional, cast
 
 from redis.asyncio.client import Redis
 from redis.asyncio.connection import ConnectionPool
 from redis.asyncio.lock import Lock
 from redis.typing import ExpiryT
 
+from aiogram.exceptions import DataNotDictLikeError
 from aiogram.fsm.state import State
 from aiogram.fsm.storage.base import (
     BaseEventIsolation,
@@ -103,8 +104,13 @@ class RedisStorage(BaseStorage):
     async def set_data(
         self,
         key: StorageKey,
-        data: Dict[str, Any],
+        data: Mapping[str, Any],
     ) -> None:
+        if not isinstance(data, dict):
+            raise DataNotDictLikeError(
+                f"Data must be a dict or dict-like object, got {type(data).__name__}"
+            )
+
         redis_key = self.key_builder.build(key, "data")
         if not data:
             await self.redis.delete(redis_key)
diff --git a/aiogram/webhook/aiohttp_server.py b/aiogram/webhook/aiohttp_server.py
index a453c9d8..04b61413 100644
--- a/aiogram/webhook/aiohttp_server.py
+++ b/aiogram/webhook/aiohttp_server.py
@@ -5,7 +5,7 @@ from asyncio import Transport
 from typing import Any, Awaitable, Callable, Dict, Optional, Set, Tuple, cast
 
 from aiohttp import JsonPayload, MultipartWriter, Payload, web
-from aiohttp.abc import Application
+from aiohttp.web_app import Application
 from aiohttp.typedefs import Handler
 from aiohttp.web_middlewares import middleware
 
diff --git a/pyproject.toml b/pyproject.toml
index 1314ae68..32745580 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -69,7 +69,7 @@ proxy = [
     "aiohttp-socks~=0.8.3",
 ]
 i18n = [
-    "Babel~=2.13.0",
+    "Babel>=2.13.0,<3",
 ]
 cli = [
     "aiogram-cli>=1.1.0,<2.0.0",


Test patch:
diff --git a/tests/test_fsm/storage/test_storages.py b/tests/test_fsm/storage/test_storages.py
index 64d4d734..44424ed2 100644
--- a/tests/test_fsm/storage/test_storages.py
+++ b/tests/test_fsm/storage/test_storages.py
@@ -1,5 +1,8 @@
+from typing import TypedDict
+
 import pytest
 
+from aiogram.exceptions import DataNotDictLikeError
 from aiogram.fsm.storage.base import BaseStorage, StorageKey
 
 
@@ -44,6 +47,21 @@ class TestStorages:
             == "baz"
         )
 
+        class CustomTypedDict(TypedDict, total=False):
+            foo: str
+            bar: str
+
+        await storage.set_data(key=storage_key, data=CustomTypedDict(foo="bar", bar="baz"))
+        assert await storage.get_data(key=storage_key) == {"foo": "bar", "bar": "baz"}
+        assert await storage.get_value(storage_key=storage_key, dict_key="foo") == "bar"
+        assert (
+            await storage.get_value(storage_key=storage_key, dict_key="foo", default="baz")
+            == "bar"
+        )
+
+        with pytest.raises(DataNotDictLikeError):
+            await storage.set_data(key=storage_key, data=())
+
     async def test_update_data(self, storage: BaseStorage, storage_key: StorageKey):
         assert await storage.get_data(key=storage_key) == {}
         assert await storage.update_data(key=storage_key, data={"foo": "bar"}) == {"foo": "bar"}
diff --git a/tests/test_fsm/test_scene.py b/tests/test_fsm/test_scene.py
index b39cf1ac..58c2a23f 100644
--- a/tests/test_fsm/test_scene.py
+++ b/tests/test_fsm/test_scene.py
@@ -1490,7 +1490,9 @@ class TestSceneRegistry:
         registry = SceneRegistry(router, register_on_add)
         registry.add(MyScene)
 
-        with pytest.raises(SceneException, match="Scene must be a subclass of Scene or a string"):
+        with pytest.raises(
+            SceneException, match="Scene must be a subclass of Scene, State or a string"
+        ):
             registry.get(MyScene)
 
     def test_scene_registry_get_scene_not_registered(self):