Repository: Tishka17/aiogram_dialog (language: python)

Issue:
Reformat and  linters



Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
new file mode 100644
index 00000000..ea09b9d6
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,39 @@
+[flake8]
+count=True
+statistics=True
+show-source=True
+
+max-line-length=79
+import-order-style=smarkets
+application-import-names=aiogram_dialog
+exclude=
+    .venv,
+    docs,
+    benchmarks
+docstring-convention=pep257
+ignore=
+    # A002 argument "object" is shadowing a python builtin
+    A002,
+    # A003 class attribute "id" is shadowing a python builtin
+    A003,
+    # D100 Missing docstring in public module
+    D100,
+    # D101 Missing docstring in public class
+    D101,
+    # D102 Missing docstring in public method
+    D102,
+    # D103 Missing docstring in public function
+    D103,
+    # D104 Missing docstring in public package
+    D104,
+    # D105 Missing docstring in magic method
+    D105,
+    # D107 Missing docstring in __init__
+    D107,
+    # W504 line break after binary operator
+    W504,
+max-cognitive-complexity=10
+max-complexity=10
+per-file-ignores=
+    tests/**:D1,E800,A003
+    **/__init__.py:F401
diff --git a/.github/workflows/setup.yml b/.github/workflows/setup.yml
new file mode 100644
index 00000000..6aaafea9
--- /dev/null
+++ b/.github/workflows/setup.yml
@@ -0,0 +1,46 @@
+# This workflow will install Python dependencies, run tests and lint with a single version of Python
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+
+name: CI
+
+on:
+  push:
+    branches: [develop]
+  pull_request:
+    branches: [develop]
+
+jobs:
+  cpython:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os:
+          - ubuntu-latest
+        python-version:
+          - "3.8"
+          - "3.9"
+          - "3.10"
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install -r requirements_dev.txt
+
+      - name: Run tests
+        run: |
+          python -m pytest
+
+      - name: Run flake8
+        run: |
+          python -m flake8 --exit-zero
+
+      - name: Run vulture
+        run: |
+          python -m vulture dataclass_factory --min-confidence 70
diff --git a/aiogram_dialog/__init__.py b/aiogram_dialog/__init__.py
index 3240717c..4ddc65b0 100644
--- a/aiogram_dialog/__init__.py
+++ b/aiogram_dialog/__init__.py
@@ -1,8 +1,8 @@
 from . import exceptions
-from .context.events import ChatEvent, StartMode, ShowMode, Data
+from .context.events import ChatEvent, Data, ShowMode, StartMode
 from .context.stack import DEFAULT_STACK_ID
 from .dialog import Dialog
-from .manager.protocols import DialogManager, BaseDialogManager
+from .manager.protocols import BaseDialogManager, DialogManager
 from .manager.registry import DialogRegistry
 from .window import Window
 
diff --git a/aiogram_dialog/context/context.py b/aiogram_dialog/context/context.py
index cfd7d5c5..3cf4b30b 100644
--- a/aiogram_dialog/context/context.py
+++ b/aiogram_dialog/context/context.py
@@ -2,7 +2,7 @@
 from dataclasses import dataclass, field
 from typing import Dict
 
-from aiogram.dispatcher.filters.state import State
+from aiogram.fsm.state import State
 
 from .events import Data
 
@@ -28,8 +28,5 @@ def stack_id(self):
 
     @property
     def data(self):
-        warnings.warn(
-            "use `context.start_data` instead",
-            DeprecationWarning
-        )
+        warnings.warn("use `context.start_data` instead", DeprecationWarning)
         return self.start_data
diff --git a/aiogram_dialog/context/events.py b/aiogram_dialog/context/events.py
index bcb8145c..67eff3b2 100644
--- a/aiogram_dialog/context/events.py
+++ b/aiogram_dialog/context/events.py
@@ -1,12 +1,18 @@
-from dataclasses import dataclass
-from enum import Enum, auto
-from typing import Dict, Any, Optional, Literal
-from typing import Union, List
-
-from aiogram import Bot
-from aiogram.dispatcher.filters.state import State
-from aiogram.types import Message, User, CallbackQuery, Chat, ChatMemberUpdated
-
+from enum import auto, Enum
+from typing import Any, Dict, List, Literal, Optional, Union
+
+from aiogram.fsm.state import State
+from aiogram.types import (
+    CallbackQuery,
+    Chat,
+    ChatMemberUpdated,
+    Message,
+    TelegramObject,
+    Update,
+    User,
+)
+
+DIALOG_EVENT_NAME = "aiogd_update"
 Data = Union[Dict, List, int, str, float, None]
 
 
@@ -29,9 +35,14 @@ class Action(Enum):
     SWITCH = "SWITCH"
 
 
-@dataclass
-class DialogUpdateEvent:
-    bot: Bot
+class DialogUpdateEvent(TelegramObject):
+    class Config:
+        """Pydantic config for custom event."""
+
+        arbitrary_types_allowed = True
+        use_enum_values = False
+        copy_on_model_validation = False
+
     from_user: User
     chat: Chat
     action: Action
@@ -40,14 +51,12 @@ class DialogUpdateEvent:
     stack_id: Optional[str]
 
 
-@dataclass
 class DialogStartEvent(DialogUpdateEvent):
     new_state: State
     mode: StartMode
     show_mode: ShowMode
 
 
-@dataclass
 class DialogSwitchEvent(DialogUpdateEvent):
     new_state: State
 
@@ -55,6 +64,21 @@ class DialogSwitchEvent(DialogUpdateEvent):
 ChatEvent = Union[CallbackQuery, Message, DialogUpdateEvent, ChatMemberUpdated]
 
 
+class DialogUpdate(Update):
+    aiogd_update: DialogUpdateEvent
+
+    def __init__(self, aiogd_update: DialogUpdateEvent):
+        super().__init__(update_id=0, aiogd_update=aiogd_update)
+
+    @property
+    def event_type(self) -> str:
+        return DIALOG_EVENT_NAME
+
+    @property
+    def event(self) -> DialogUpdateEvent:
+        return self.aiogd_update
+
+
 class FakeUser(User):
     fake: Literal[True] = True
 
diff --git a/aiogram_dialog/context/intent_filter.py b/aiogram_dialog/context/intent_filter.py
index 64754cc7..a65fb873 100644
--- a/aiogram_dialog/context/intent_filter.py
+++ b/aiogram_dialog/context/intent_filter.py
@@ -1,20 +1,18 @@
 from logging import getLogger
-from typing import Optional, Type, Dict, Any
+from typing import Any, Awaitable, Callable, Dict, Optional, Type, Union
 
-from aiogram.dispatcher.filters import BoundFilter
-from aiogram.dispatcher.filters.state import StatesGroup
-from aiogram.dispatcher.handler import ctx_data
-from aiogram.dispatcher.middlewares import BaseMiddleware
-from aiogram.dispatcher.storage import BaseStorage
-from aiogram.types import Message, CallbackQuery, ChatMemberUpdated, Update
-from aiogram.types.base import TelegramObject
+from aiogram.dispatcher.middlewares.base import BaseMiddleware
+from aiogram.filters.base import BaseFilter
+from aiogram.fsm.state import StatesGroup
+from aiogram.fsm.storage.base import BaseStorage
+from aiogram.types import CallbackQuery, Message, TelegramObject, Update
 
 from .context import Context
-from .events import DialogUpdateEvent, ChatEvent
-from .stack import DEFAULT_STACK_ID
-from .storage import StorageProxy
+from .events import ChatEvent, DialogUpdate, DialogUpdateEvent
+from .stack import Stack
+from .storage import DEFAULT_STACK_ID, StorageProxy
 from ..exceptions import InvalidStackIdError, OutdatedIntent
-from ..utils import remove_indent_id, get_chat
+from ..utils import remove_indent_id
 
 STORAGE_KEY = "aiogd_storage_proxy"
 STACK_KEY = "aiogd_stack"
@@ -24,71 +22,78 @@
 logger = getLogger(__name__)
 
 
-class IntentFilter(BoundFilter):
-    key = 'aiogd_intent_state_group'
+class IntentFilter(BaseFilter):
+    aiogd_intent_state_group: Optional[Type[StatesGroup]]
 
-    def __init__(
-            self,
-            aiogd_intent_state_group: Optional[Type[StatesGroup]] = None
-    ):
-        self.intent_state_group = aiogd_intent_state_group
-
-    async def check(self, obj: TelegramObject):
-        if self.intent_state_group is None:
+    async def __call__(self, obj: TelegramObject, **kwargs) -> bool:
+        if self.aiogd_intent_state_group is None:
             return True
-        data = ctx_data.get()
-        context: Context = data.get(CONTEXT_KEY)
+
+        context: Context = kwargs[CONTEXT_KEY]
         if not context:
             return False
-        return context and context.state.group == self.intent_state_group
+        return context.state.group == self.aiogd_intent_state_group
 
 
-class IntentMiddleware(BaseMiddleware):
+class IntentMiddlewareFactory:
     def __init__(
             self, storage: BaseStorage,
-            state_groups: Dict[str, Type[StatesGroup]]
-    ) -> None:
+            state_groups: Dict[str, Type[StatesGroup]],
+    ):
         super().__init__()
         self.storage = storage
         self.state_groups = state_groups
 
-    async def _load_context(
-            self, event: ChatEvent,
-            intent_id: Optional[str], stack_id: Optional[str],
-            data: dict,
-    ) -> None:
-        chat = get_chat(event)
+    def storage_proxy(self, data: dict):
         proxy = StorageProxy(
+            bot=data["bot"],
             storage=self.storage,
-            user_id=event.from_user.id,
-            chat_id=chat.id,
+            user_id=data["event_from_user"].id,
+            chat_id=data["event_chat"].id,
             state_groups=self.state_groups,
         )
+        return proxy
+
+    def _check_outdated(self, intent_id: str, stack: Stack):
+        """Check if intent id is outdated for stack."""
+        if stack.empty():
+            raise OutdatedIntent(
+                stack.id,
+                f"Outdated intent id ({intent_id}) "
+                f"for stack ({stack.id})",
+            )
+        elif intent_id != stack.last_intent_id():
+            raise OutdatedIntent(
+                stack.id,
+                f"Outdated intent id ({intent_id}) "
+                f"for stack ({stack.id})",
+            )
+
+    async def _load_context(
+            self,
+            event: ChatEvent,
+            intent_id: Optional[str],
+            stack_id: Optional[str],
+            data: dict,
+    ) -> None:
+        proxy = self.storage_proxy(data)
         logger.debug(
             "Loading context for intent: `%s`, "
             "stack: `%s`, user: `%s`, chat: `%s`",
-            intent_id, stack_id, event.from_user.id, chat.id,
+            intent_id,
+            stack_id,
+            event.from_user.id,
+            proxy.chat_id,
         )
         if intent_id is not None:
             context = await proxy.load_context(intent_id)
             stack = await proxy.load_stack(context.stack_id)
+            self._check_outdated(intent_id, stack)
         elif stack_id is not None:
             stack = await proxy.load_stack(stack_id)
             if stack.empty():
-                if intent_id is not None:
-                    raise OutdatedIntent(
-                        stack.id,
-                        f"Outdated intent id ({intent_id}) "
-                        f"for stack ({stack.id})",
-                    )
                 context = None
             else:
-                if intent_id is not None and intent_id != stack.last_intent_id():
-                    raise OutdatedIntent(
-                        stack.id,
-                        f"Outdated intent id ({intent_id}) "
-                        f"for stack ({stack.id})",
-                    )
                 context = await proxy.load_context(stack.last_intent_id())
         else:
             raise InvalidStackIdError(
@@ -98,10 +103,12 @@ async def _load_context(
         data[STACK_KEY] = stack
         data[CONTEXT_KEY] = context
 
-    def _intent_id_from_reply(self, event: Message) -> Optional[str]:
+    def _intent_id_from_reply(
+            self, event: Message, data: dict,
+    ) -> Optional[str]:
         if not (
                 event.reply_to_message and
-                event.reply_to_message.from_user.id == event.bot.id and
+                event.reply_to_message.from_user.id == data["bot"].id and
                 event.reply_to_message.reply_markup and
                 event.reply_to_message.reply_markup.inline_keyboard
         ):
@@ -113,98 +120,120 @@ def _intent_id_from_reply(self, event: Message) -> Optional[str]:
                     return intent_id
         return None
 
-    async def on_pre_process_message(
-            self, event: Message, data: dict,
-    ) -> None:
-        if intent_id := self._intent_id_from_reply(event):
-            await self._load_context(
-                event, intent_id, DEFAULT_STACK_ID, data,
-            )
+    async def process_message(
+            self,
+            handler: Callable,
+            event: Message,
+            data: dict,
+    ):
+        if intent_id := self._intent_id_from_reply(event, data):
+            await self._load_context(event, intent_id, DEFAULT_STACK_ID, data)
         else:
-            await self._load_context(
-                event, None, DEFAULT_STACK_ID, data,
-            )
+            await self._load_context(event, None, DEFAULT_STACK_ID, data)
+        return await handler(event, data)
 
-    async def on_post_process_message(
-            self, message: Message, result, data: dict,
+    async def process_my_chat_member(
+            self,
+            handler: Callable,
+            event: Message,
+            data: dict,
     ) -> None:
-        proxy: StorageProxy = data.pop(STORAGE_KEY)
-        await proxy.save_context(data.pop(CONTEXT_KEY))
-        await proxy.save_stack(data.pop(STACK_KEY))
+        await self._load_context(event, None, DEFAULT_STACK_ID, data)
+        return await handler(event, data)
 
-    async def on_pre_process_aiogd_update(
-            self, event: DialogUpdateEvent, data: dict
-    ) -> None:
-        await self._load_context(
-            event, event.intent_id, event.stack_id, data,
-        )
+    async def process_aiogd_update(
+            self,
+            handler: Callable,
+            event: DialogUpdateEvent,
+            data: dict,
+    ):
+        await self._load_context(event, event.intent_id, event.stack_id, data)
+        return await handler(event, data)
+
+    async def process_callback_query(
+            self,
+            handler: Callable,
+            event: CallbackQuery,
+            data: dict,
+    ):
+        proxy = self.storage_proxy(data)
+        data[STORAGE_KEY] = proxy
 
-    async def on_pre_process_callback_query(self, event: CallbackQuery,
-                                            data: dict):
         original_data = event.data
         intent_id, callback_data = remove_indent_id(event.data)
-        await self._load_context(
-            event, intent_id, DEFAULT_STACK_ID, data,
-        )
-        logger.debug("Original callback data: %s", original_data)
-        event.data = callback_data
+        await self._load_context(event, intent_id, DEFAULT_STACK_ID, data)
         data[CALLBACK_DATA_KEY] = original_data
+        return await handler(event, data)
 
-    async def on_pre_process_my_chat_member(
-            self,
-            event: ChatMemberUpdated,
-            data: dict
-    ) -> None:
-        await self._load_context(
-            event, None, DEFAULT_STACK_ID, data,
-        )
 
-    on_post_process_callback_query = on_post_process_message
-    on_post_process_aiogd_update = on_post_process_message
-    on_post_process_my_chat_member = on_post_process_message
+SUPPORTED_ERROR_EVENTS = {
+    "message",
+    "callback_query",
+    "my_chat_member",
+    "aiogd_update",
+}
 
-    async def on_pre_process_error(
-            self, update: Update, error: Exception, data: dict,
-    ) -> None:
-        if isinstance(error, InvalidStackIdError):
-            return
 
-        event = (
-                update.message or
-                update.my_chat_member or
-                update.callback_query
-        )
-        if not event:
-            return
+async def context_saver_middleware(handler, event, data):
+    result = await handler(event, data)
+    proxy: StorageProxy = data.pop(STORAGE_KEY, None)
+    if proxy:
+        await proxy.save_context(data.pop(CONTEXT_KEY))
+        await proxy.save_stack(data.pop(STACK_KEY))
+    return result
 
-        chat = get_chat(event)
 
-        proxy = StorageProxy(
-            storage=self.storage,
-            user_id=event.from_user.id,
-            chat_id=chat.id,
-            state_groups=self.state_groups,
-        )
-        data[STORAGE_KEY] = proxy
+class IntentErrorMiddleware(BaseMiddleware):
+    def __init__(
+            self, storage: BaseStorage,
+            state_groups: Dict[str, Type[StatesGroup]],
+    ):
+        super().__init__()
+        self.storage = storage
+        self.state_groups = state_groups
 
-        if isinstance(error, OutdatedIntent):
-            stack = await proxy.load_stack(stack_id=error.stack_id)
-        else:
-            stack = await proxy.load_stack()
-        if stack.empty():
-            context = None
-        else:
-            context = await proxy.load_context(stack.last_intent_id())
-        data[STACK_KEY] = stack
-        data[CONTEXT_KEY] = context
+    async def __call__(
+            self,
+            handler: Callable[
+                [Union[Update, DialogUpdate], Dict[str, Any]], Awaitable[Any],
+            ],
+            event: Update,
+            data: Dict[str, Any],
+    ) -> Any:
+
+        try:
+            error = data["exception"]
+            if isinstance(error, InvalidStackIdError):
+                return
+            if event.event_type not in SUPPORTED_ERROR_EVENTS:
+                return
+
+            chat = data["event_chat"]
+
+            proxy = StorageProxy(
+                bot=data["bot"],
+                storage=self.storage,
+                user_id=data["event_from_user"].id,
+                chat_id=chat.id,
+                state_groups=self.state_groups,
+            )
+            data[STORAGE_KEY] = proxy
 
-    async def on_post_process_error(
-            self, event: Any, error: Exception, result: list, data: dict,
-    ) -> None:
-        proxy: StorageProxy = data.pop(STORAGE_KEY, None)
-        if not proxy:
-            return
-        context = data.pop(CONTEXT_KEY)
-        if context is not None:
-            await proxy.save_context(context)
-        await proxy.save_stack(data.pop(STACK_KEY))
+            if isinstance(error, OutdatedIntent):
+                stack = await proxy.load_stack(stack_id=error.stack_id)
+            else:
+                stack = await proxy.load_stack()
+            if stack.empty():
+                context = None
+            else:
+                context = await proxy.load_context(stack.last_intent_id())
+            data[STACK_KEY] = stack
+            data[CONTEXT_KEY] = context
+            return await handler(event, data)
+        finally:
+            proxy: StorageProxy = data.pop(STORAGE_KEY, None)
+            if proxy:
+                context = data.pop(CONTEXT_KEY)
+                if context is not None:
+                    await proxy.save_context(context)
+                await proxy.save_stack(data.pop(STACK_KEY))
diff --git a/aiogram_dialog/context/media_storage.py b/aiogram_dialog/context/media_storage.py
index 7d9ef905..ed1f2690 100644
--- a/aiogram_dialog/context/media_storage.py
+++ b/aiogram_dialog/context/media_storage.py
@@ -3,7 +3,7 @@
 from aiogram.types import ContentType
 from cachetools import LRUCache
 
-from ..manager.protocols import MediaIdStorageProtocol, MediaId
+from ..manager.protocols import MediaId, MediaIdStorageProtocol
 
 
 class MediaIdStorage(MediaIdStorageProtocol):
diff --git a/aiogram_dialog/context/stack.py b/aiogram_dialog/context/stack.py
index 9381debd..f3b0ee2f 100644
--- a/aiogram_dialog/context/stack.py
+++ b/aiogram_dialog/context/stack.py
@@ -4,10 +4,10 @@
 from dataclasses import dataclass, field
 from typing import List, Optional
 
-from aiogram.dispatcher.filters.state import State
+from aiogram.fsm.state import State
 
-from .events import Data
 from .context import Context
+from .events import Data
 from ..exceptions import DialogStackOverflow
 
 DEFAULT_STACK_ID = ""
@@ -41,7 +41,9 @@ class Stack:
     last_message_id: Optional[int] = field(compare=False, default=None)
     last_media_id: Optional[str] = field(compare=False, default=None)
     last_media_unique_id: Optional[str] = field(compare=False, default=None)
-    last_income_media_group_id: Optional[str] = field(compare=False, default=None)
+    last_income_media_group_id: Optional[str] = field(
+        compare=False, default=None,
+    )
 
     @property
     def id(self):
@@ -50,7 +52,8 @@ def id(self):
     def push(self, state: State, data: Data) -> Context:
         if len(self.intents) >= STACK_LIMIT:
             raise DialogStackOverflow(
-                f"Cannot open more dialogs in current stack. Max count is {STACK_LIMIT}"
+                f"Cannot open more dialogs in current stack. "
+                f"Max count is {STACK_LIMIT}",
             )
         context = Context(
             _intent_id=new_id(),
diff --git a/aiogram_dialog/context/storage.py b/aiogram_dialog/context/storage.py
index 6f243355..21164495 100644
--- a/aiogram_dialog/context/storage.py
+++ b/aiogram_dialog/context/storage.py
@@ -1,37 +1,46 @@
 from copy import copy
-from typing import Dict, Type, Optional
+from typing import Dict, Optional, Type
 
-from aiogram.dispatcher.filters.state import State, StatesGroup
-from aiogram.dispatcher.storage import BaseStorage
+from aiogram import Bot
+from aiogram.fsm.state import State, StatesGroup
+from aiogram.fsm.storage.base import BaseStorage, StorageKey
 
 from .context import Context
-from .stack import Stack, DEFAULT_STACK_ID
-from ..exceptions import UnknownState, UnknownIntent
+from .stack import DEFAULT_STACK_ID, Stack
+from ..exceptions import UnknownIntent, UnknownState
 
 
 class StorageProxy:
-    def __init__(self, storage: BaseStorage,
-                 user_id: int, chat_id: int,
-                 state_groups: Dict[str, Type[StatesGroup]]):
+    def __init__(
+            self,
+            storage: BaseStorage,
+            user_id: int,
+            chat_id: int,
+            bot: Bot,
+            state_groups: Dict[str, Type[StatesGroup]],
+    ):
         self.storage = storage
         self.state_groups = state_groups
         self.user_id = user_id
         self.chat_id = chat_id
+        self.bot = bot
 
     async def load_context(self, intent_id: str) -> Context:
         data = await self.storage.get_data(
-            chat=self.chat_id,
-            user=self._context_key(intent_id)
+            bot=self.bot,
+            key=self._context_key(intent_id),
         )
         if not data:
-            raise UnknownIntent(f"Context not found for intent id: {intent_id}")
+            raise UnknownIntent(
+                f"Context not found for intent id: {intent_id}",
+            )
         data["state"] = self._state(data["state"])
         return Context(**data)
 
     async def load_stack(self, stack_id: str = DEFAULT_STACK_ID) -> Stack:
         data = await self.storage.get_data(
-            chat=self.chat_id,
-            user=self._stack_key(stack_id)
+            bot=self.bot,
+            key=self._stack_key(stack_id),
         )
         if not data:
             return Stack(_id=stack_id)
@@ -43,42 +52,61 @@ async def save_context(self, context: Optional[Context]) -> None:
         data = copy(vars(context))
         data["state"] = data["state"].state
         await self.storage.set_data(
-            chat=self.chat_id,
-            user=self._context_key(context.id),
+            bot=self.bot,
+            key=self._context_key(context.id),
             data=data,
         )
 
     async def remove_context(self, intent_id: str):
-        await self.storage.reset_data(chat=self.chat_id, user=self._context_key(intent_id))
+        await self.storage.set_data(
+            bot=self.bot,
+            key=self._context_key(intent_id),
+            data={},
+        )
 
     async def remove_stack(self, stack_id: str):
-        await self.storage.reset_data(chat=self.chat_id, user=self._stack_key(stack_id))
+        await self.storage.set_data(
+            bot=self.bot,
+            key=self._stack_key(stack_id),
+            data={},
+        )
 
     async def save_stack(self, stack: Optional[Stack]) -> None:
         if not stack:
             return
         if stack.empty() and not stack.last_message_id:
-            await self.storage.reset_data(
-                chat=self.chat_id,
-                user=self._stack_key(stack.id),
+            await self.storage.set_data(
+                bot=self.bot,
+                key=self._stack_key(stack.id),
+                data={},
             )
         else:
             data = copy(vars(stack))
             await self.storage.set_data(
-                chat=self.chat_id,
-                user=self._stack_key(stack.id),
+                bot=self.bot,
+                key=self._stack_key(stack.id),
                 data=data,
             )
 
-    def _context_key(self, intent_id: str) -> str:
-        return f"{self.user_id}:aiogd:context:{intent_id}"
+    def _context_key(self, intent_id: str) -> StorageKey:
+        return StorageKey(
+            bot_id=self.bot.id,
+            chat_id=self.chat_id,
+            user_id=self.user_id,
+            destiny=f"aiogd:context:{intent_id}",
+        )
 
-    def _stack_key(self, stack_id: str) -> str:
-        return f"{self.user_id}:aiogd:stack:{stack_id}"
+    def _stack_key(self, stack_id: str) -> StorageKey:
+        return StorageKey(
+            bot_id=self.bot.id,
+            chat_id=self.chat_id,
+            user_id=self.user_id,
+            destiny=f"aiogd:stack:{stack_id}",
+        )
 
     def _state(self, state: str) -> State:
         group, *_ = state.partition(":")
-        for real_state in self.state_groups[group].all_states:
+        for real_state in self.state_groups[group].__all_states__:
             if real_state.state == state:
                 return real_state
         raise UnknownState(f"Unknown state {state}")
diff --git a/aiogram_dialog/deprecation_utils.py b/aiogram_dialog/deprecation_utils.py
index c4ce5f9a..7b4155c2 100644
--- a/aiogram_dialog/deprecation_utils.py
+++ b/aiogram_dialog/deprecation_utils.py
@@ -1,5 +1,5 @@
 import warnings
-from typing import Optional, Any
+from typing import Any, Optional
 
 
 def manager_deprecated(manager: Optional[Any]):
diff --git a/aiogram_dialog/dialog.py b/aiogram_dialog/dialog.py
index 734d1b8e..02380b96 100644
--- a/aiogram_dialog/dialog.py
+++ b/aiogram_dialog/dialog.py
@@ -1,24 +1,35 @@
 from logging import getLogger
-from typing import Dict, Callable, Awaitable, List, Union, Any, Optional, Type, TypeVar
-from typing import Protocol
+from typing import (
+    Any,
+    Awaitable,
+    Callable,
+    Dict,
+    List,
+    Optional,
+    Protocol,
+    Type,
+    TypeVar,
+    Union,
+)
 
-from aiogram import Dispatcher
-from aiogram.dispatcher.filters.state import State, StatesGroup
-from aiogram.types import InlineKeyboardMarkup, Message, CallbackQuery, ContentTypes
-from aiogram.utils.exceptions import InvalidQueryID
+from aiogram import Router
+from aiogram.exceptions import TelegramAPIError
+from aiogram.fsm.state import State, StatesGroup
+from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
 
 from .context.events import Data
 from .exceptions import UnregisteredWindowError
 from .manager.protocols import (
-    DialogRegistryProto, ManagedDialogProto, DialogManager, NewMessage,
+    DialogManager,
+    DialogRegistryProto,
     LaunchMode,
+    ManagedDialogProto,
+    NewMessage,
 )
-from .utils import add_indent_id, get_media_id
+from .utils import add_indent_id, get_media_id, remove_indent_id
 from .widgets.action import Actionable
 from .widgets.data import PreviewAwareGetter
-from .widgets.utils import (
-    ensure_data_getter, GetterVariant,
-)
+from .widgets.utils import ensure_data_getter, GetterVariant
 
 logger = getLogger(__name__)
 DIALOG_CONTEXT = "DIALOG_CONTEXT"
@@ -28,24 +39,48 @@
 OnResultEvent = Callable[[Data, Any, DialogManager], Awaitable]
 W = TypeVar("W", bound=Actionable)
 
+_INVALUD_QUERY_ID_MSG = (
+    "query is too old and response timeout expired or query id is invalid"
+)
+
 
 class DialogWindowProto(Protocol):
     async def render_text(self, data: Dict, manager: DialogManager) -> str:
         raise NotImplementedError
 
-    async def render_kbd(self, data: Dict, manager: DialogManager) -> InlineKeyboardMarkup:
+    async def render_kbd(
+            self, data: Dict, manager: DialogManager,
+    ) -> InlineKeyboardMarkup:
         raise NotImplementedError
 
-    async def load_data(self, dialog: "Dialog", manager: DialogManager) -> Dict:
+    async def load_data(
+            self,
+            dialog: "Dialog",
+            manager: DialogManager,
+    ) -> Dict:
         raise NotImplementedError
 
-    async def process_message(self, m: Message, dialog: "Dialog", manager: DialogManager):
+    async def process_message(
+            self,
+            m: Message,
+            dialog: "Dialog",
+            manager: DialogManager,
+    ):
         raise NotImplementedError
 
-    async def process_callback(self, c: CallbackQuery, dialog: "Dialog", manager: DialogManager):
+    async def process_callback(
+            self,
+            c: CallbackQuery,
+            dialog: "Dialog",
+            manager: DialogManager,
+    ):
         raise NotImplementedError
 
-    async def render(self, dialog: "Dialog", manager: DialogManager) -> NewMessage:
+    async def render(
+            self,
+            dialog: "Dialog",
+            manager: DialogManager,
+    ) -> NewMessage:
         raise NotImplementedError
 
     def get_state(self) -> State:
@@ -70,12 +105,16 @@ def __init__(
         self.states: List[State] = []
         for w in windows:
             if w.get_state().group != self._states_group:
-                raise ValueError("All windows must be attached to same StatesGroup")
+                raise ValueError(
+                    "All windows must be attached to same StatesGroup",
+                )
             state = w.get_state()
             if state in self.states:
                 raise ValueError(f"Multiple windows with state {state}")
             self.states.append(state)
-        self.windows: Dict[State, DialogWindowProto] = dict(zip(self.states, windows))
+        self.windows: Dict[State, DialogWindowProto] = dict(
+            zip(self.states, windows),
+        )
         self.on_start = on_start
         self.on_close = on_close
         self.on_process_result = on_process_result
@@ -88,24 +127,32 @@ def __init__(
     async def next(self, manager: DialogManager):
         if not manager.current_context():
             raise ValueError("No intent")
-        new_state = self.states[self.states.index(manager.current_context().state) + 1]
+        current_index = self.states.index(manager.current_context().state)
+        new_state = self.states[current_index + 1]
         await self.switch_to(new_state, manager)
 
     async def back(self, manager: DialogManager):
         if not manager.current_context():
             raise ValueError("No intent")
-        new_state = self.states[self.states.index(manager.current_context().state) - 1]
+        current_index = self.states.index(manager.current_context().state)
+        new_state = self.states[current_index - 1]
         await self.switch_to(new_state, manager)
 
-    async def process_start(self, manager: DialogManager, start_data: Any,
-                            state: Optional[State] = None) -> None:
+    async def process_start(
+            self,
+            manager: DialogManager,
+            start_data: Any,
+            state: Optional[State] = None,
+    ) -> None:
         if state is None:
             state = self.states[0]
         logger.debug("Dialog start: %s (%s)", state, self)
         await self.switch_to(state, manager)
         await self._process_callback(self.on_start, start_data, manager)
 
-    async def _process_callback(self, callback: Optional[OnDialogEvent], *args, **kwargs):
+    async def _process_callback(
+            self, callback: Optional[OnDialogEvent], *args, **kwargs,
+    ):
         if callback:
             await callback(*args, **kwargs)
 
@@ -113,17 +160,19 @@ async def switch_to(self, state: State, manager: DialogManager):
         if state.group != self.states_group():
             raise ValueError(
                 f"Cannot switch from `{self.states_group_name()}` "
-                f"to another states group {state.group}"
+                f"to another states group {state.group}",
             )
         await manager.switch_to(state)
 
-    async def _current_window(self, manager: DialogManager) -> DialogWindowProto:
+    async def _current_window(
+            self, manager: DialogManager,
+    ) -> DialogWindowProto:
         try:
             return self.windows[manager.current_context().state]
-        except ValueError as e:
+        except KeyError as e:
             raise UnregisteredWindowError(
                 f"No window found for `{manager.current_context().state}` "
-                f"Current state group is `{self.states_group_name()}`"
+                f"Current state group is `{self.states_group_name()}`",
             ) from e
 
     async def load_data(self, manager: DialogManager) -> Dict:
@@ -162,35 +211,51 @@ async def show(self, manager: DialogManager) -> None:
                 media_id=get_media_id(message),
             )
 
-    async def _message_handler(self, m: Message, dialog_manager: DialogManager):
+    async def _message_handler(
+            self, m: Message, dialog_manager: DialogManager,
+    ):
         intent = dialog_manager.current_context()
         window = await self._current_window(dialog_manager)
         await window.process_message(m, self, dialog_manager)
         if dialog_manager.current_context() == intent:  # no new dialog started
             await self.show(dialog_manager)
 
-    async def _callback_handler(self, c: CallbackQuery, dialog_manager: DialogManager):
+    async def _callback_handler(
+            self,
+            c: CallbackQuery,
+            dialog_manager: DialogManager,
+    ):
         intent = dialog_manager.current_context()
+        intent_id, callback_data = remove_indent_id(c.data)
+        cleaned_callback = c.copy(update={"data": callback_data})
         window = await self._current_window(dialog_manager)
-        await window.process_callback(c, self, dialog_manager)
+        await window.process_callback(cleaned_callback, self, dialog_manager)
         if dialog_manager.current_context() == intent:  # no new dialog started
             await self.show(dialog_manager)
         if not dialog_manager.is_preview():
             try:
                 await c.answer()
-            except InvalidQueryID as e:
-                logger.warning("Cannot answer callback: %s", e)
+            except TelegramAPIError as e:
+                if _INVALUD_QUERY_ID_MSG in e.message:
+                    logger.warning("Cannot answer callback: %s", e)
+                else:
+                    raise
 
-    async def _update_handler(self, event: ChatEvent, dialog_manager: DialogManager):
+    async def _update_handler(
+            self,
+            event: ChatEvent,
+            dialog_manager: DialogManager,
+    ):
         await self.show(dialog_manager)
 
-    def register(self, registry: DialogRegistryProto, dp: Dispatcher, *args, **filters) -> None:
-        if "state" not in filters:
-            filters["state"] = "*"
-        dp.register_callback_query_handler(self._callback_handler, *args, **filters)
-        if "content_types" not in filters:
-            filters["content_types"] = ContentTypes.ANY
-        dp.register_message_handler(self._message_handler, *args, **filters)
+    def register(
+            self, registry: DialogRegistryProto, router: Router, *args,
+            **filters,
+    ) -> None:
+        router.callback_query.register(
+            self._callback_handler, *args, **filters,
+        )
+        router.message.register(self._message_handler, *args, **filters)
 
     def states_group(self) -> Type[StatesGroup]:
         return self._states_group
@@ -198,8 +263,15 @@ def states_group(self) -> Type[StatesGroup]:
     def states_group_name(self) -> str:
         return self._states_group.__full_group_name__
 
-    async def process_result(self, start_data: Data, result: Any, manager: DialogManager):
-        await self._process_callback(self.on_process_result, start_data, result, manager)
+    async def process_result(
+            self,
+            start_data: Data,
+            result: Any,
+            manager: DialogManager,
+    ):
+        await self._process_callback(
+            self.on_process_result, start_data, result, manager,
+        )
 
     async def process_close(self, result: Any, manager: DialogManager):
         await self._process_callback(self.on_close, result, manager)
diff --git a/aiogram_dialog/manager/bg_manager.py b/aiogram_dialog/manager/bg_manager.py
index d1f39920..4a9c340e 100644
--- a/aiogram_dialog/manager/bg_manager.py
+++ b/aiogram_dialog/manager/bg_manager.py
@@ -2,13 +2,21 @@
 from typing import Any, Dict, Optional
 
 from aiogram import Bot
-from aiogram.dispatcher.filters.state import State
+from aiogram.fsm.state import State
 from aiogram.types import Chat, User
 
-from .protocols import DialogRegistryProto, BaseDialogManager
+from .protocols import BaseDialogManager, DialogRegistryProto
 from ..context.events import (
-    Data, Action, DialogStartEvent, DialogSwitchEvent, DialogUpdateEvent,
-    StartMode, FakeChat, FakeUser, ShowMode,
+    Action,
+    Data,
+    DialogStartEvent,
+    DialogSwitchEvent,
+    DialogUpdate,
+    DialogUpdateEvent,
+    FakeChat,
+    FakeUser,
+    ShowMode,
+    StartMode,
 )
 from ..context.stack import DEFAULT_STACK_ID
 from ..utils import is_chat_loaded, is_user_loaded
@@ -49,14 +57,14 @@ def bg(
         if chat_id in (None, self.chat.id):
             chat = self.chat
         else:
-            chat = FakeChat(id=chat_id)
+            chat = FakeChat(id=chat_id, type="")
 
         if user_id in (None, self.user.id):
             user = self.user
         else:
-            user = FakeUser(id=user_id)
+            user = FakeUser(id=user_id, is_bot=False, first_name="")
 
-        same_chat = (user.id == self.user.id and chat.id == self.chat.id)
+        same_chat = user.id == self.user.id and chat.id == self.chat.id
         if stack_id is None:
             if same_chat:
                 stack_id = self.stack_id
@@ -79,13 +87,17 @@ def bg(
 
     def _base_event_params(self):
         return {
-            "bot": self.bot,
             "from_user": self.user,
             "chat": self.chat,
             "intent_id": self.intent_id,
             "stack_id": self.stack_id,
         }
 
+    async def _notify(self, event: DialogUpdateEvent):
+        await self.registry.notify(
+            bot=self.bot, update=DialogUpdate(aiogd_update=event),
+        )
+
     async def _load(self):
         if self.load:
             if not is_chat_loaded(self.chat):
@@ -93,23 +105,20 @@ async def _load(self):
                 self.chat = await self.bot.get_chat(self.chat.id)
             if not is_user_loaded(self.user):
                 logger.debug(
-                    "load user %s from chat %s",
-                    self.chat.id,
-                    self.user.id
+                    "load user %s from chat %s", self.chat.id, self.user.id,
                 )
                 chat_member = await self.bot.get_chat_member(
-                    self.chat.id,
-                    self.user.id
+                    self.chat.id, self.user.id,
                 )
                 self.user = chat_member.user
 
     async def done(self, result: Any = None) -> None:
         await self._load()
-        await self.registry.notify(DialogUpdateEvent(
-            action=Action.DONE,
-            data=result,
-            **self._base_event_params()
-        ))
+        await self._notify(
+            DialogUpdateEvent(
+                action=Action.DONE, data=result, **self._base_event_params(),
+            ),
+        )
 
     async def start(
             self,
@@ -119,28 +128,32 @@ async def start(
             show_mode: ShowMode = ShowMode.AUTO,
     ) -> None:
         await self._load()
-        await self.registry.notify(DialogStartEvent(
-            action=Action.START,
-            data=data,
-            new_state=state,
-            mode=mode,
-            show_mode=show_mode,
-            **self._base_event_params()
-        ))
+        await self._notify(
+            DialogStartEvent(
+                action=Action.START,
+                data=data,
+                new_state=state,
+                mode=mode,
+                show_mode=show_mode,
+                **self._base_event_params(),
+            ),
+        )
 
     async def switch_to(self, state: State) -> None:
         await self._load()
-        await self.registry.notify(DialogSwitchEvent(
-            action=Action.SWITCH,
-            data={},
-            new_state=state,
-            **self._base_event_params()
-        ))
+        await self._notify(
+            DialogSwitchEvent(
+                action=Action.SWITCH,
+                data={},
+                new_state=state,
+                **self._base_event_params(),
+            ),
+        )
 
     async def update(self, data: Dict) -> None:
         await self._load()
-        await self.registry.notify(DialogUpdateEvent(
-            action=Action.UPDATE,
-            data=data,
-            **self._base_event_params()
-        ))
+        await self._notify(
+            DialogUpdateEvent(
+                action=Action.UPDATE, data=data, **self._base_event_params(),
+            ),
+        )
diff --git a/aiogram_dialog/manager/dialog.py b/aiogram_dialog/manager/dialog.py
index 492bc60d..55d61046 100644
--- a/aiogram_dialog/manager/dialog.py
+++ b/aiogram_dialog/manager/dialog.py
@@ -1,10 +1,12 @@
-from typing import Optional, Any
+from typing import Any, Optional
 
-from aiogram.dispatcher.filters.state import State
+from aiogram.filters.state import State
 
 from aiogram_dialog.deprecation_utils import manager_deprecated
 from aiogram_dialog.manager.protocols import (
-    ManagedDialogProto, DialogManager, ManagedDialogAdapterProto,
+    DialogManager,
+    ManagedDialogAdapterProto,
+    ManagedDialogProto,
 )
 
 
@@ -13,28 +15,30 @@ def __init__(self, dialog: ManagedDialogProto, manager: DialogManager):
         self.dialog = dialog
         self.manager = manager
 
-    async def show(self, preview: bool = False,
-                   manager: Optional[DialogManager] = None):
+    async def show(
+            self, preview: bool = False,
+            manager: Optional[DialogManager] = None,
+    ):
         manager_deprecated(manager)
         return await self.dialog.show(self.manager)
 
-    async def next(self,
-                   manager: Optional[DialogManager] = None):
+    async def next(self, manager: Optional[DialogManager] = None):
         manager_deprecated(manager)
         return await self.dialog.next(self.manager)
 
-    async def back(self,
-                   manager: Optional[DialogManager] = None):
+    async def back(self, manager: Optional[DialogManager] = None):
         manager_deprecated(manager)
         return await self.dialog.back(self.manager)
 
-    async def switch_to(self, state: State,
-                        manager: Optional[DialogManager] = None):
+    async def switch_to(
+            self, state: State, manager: Optional[DialogManager] = None,
+    ):
         manager_deprecated(manager)
         return await self.dialog.switch_to(state, self.manager)
 
-    def find(self, widget_id,
-             manager: Optional[DialogManager] = None) -> Optional[Any]:
+    def find(
+            self, widget_id, manager: Optional[DialogManager] = None,
+    ) -> Optional[Any]:
         manager_deprecated(manager)
         widget = self.dialog.find(widget_id)
         if widget is None:
diff --git a/aiogram_dialog/manager/manager.py b/aiogram_dialog/manager/manager.py
index 0d5ec7ec..60bff934 100644
--- a/aiogram_dialog/manager/manager.py
+++ b/aiogram_dialog/manager/manager.py
@@ -1,32 +1,42 @@
+from datetime import datetime
 from logging import getLogger
-from typing import Any, Optional, Dict
+from typing import Any, Dict, Optional
 
-from aiogram.dispatcher.filters.state import State
-from aiogram.types import Message, CallbackQuery, Document
+from aiogram.fsm.state import State
+from aiogram.types import CallbackQuery, Chat, Document, Message, User
 
 from .bg_manager import BgManager
 from .dialog import ManagedDialogAdapter
 from .protocols import (
-    DialogManager, BaseDialogManager, LaunchMode,
-    ManagedDialogAdapterProto, ManagedDialogProto, DialogRegistryProto,
+    BaseDialogManager,
+    DialogManager,
+    DialogRegistryProto,
+    LaunchMode,
+    ManagedDialogAdapterProto,
+    ManagedDialogProto,
     NewMessage,
 )
 from ..context.context import Context
 from ..context.events import (
-    ChatEvent, StartMode, ShowMode, Data, FakeChat, FakeUser,
+    ChatEvent,
+    Data,
+    FakeChat,
+    FakeUser,
+    ShowMode,
+    StartMode,
 )
-from ..context.intent_filter import CONTEXT_KEY, STORAGE_KEY, STACK_KEY
-from ..context.stack import Stack, DEFAULT_STACK_ID
+from ..context.intent_filter import CONTEXT_KEY, STACK_KEY, STORAGE_KEY
+from ..context.stack import DEFAULT_STACK_ID, Stack
 from ..context.storage import StorageProxy
 from ..exceptions import IncorrectBackgroundError
-from ..utils import get_chat
 
 logger = getLogger(__name__)
 
 
 class ManagerImpl(DialogManager):
-    def __init__(self, event: ChatEvent, registry: DialogRegistryProto,
-                 data: Dict):
+    def __init__(
+            self, event: ChatEvent, registry: DialogRegistryProto, data: Dict,
+    ):
         self.disabled = False
         self._registry = registry
         self.event = event
@@ -42,7 +52,7 @@ def check_disabled(self):
             raise IncorrectBackgroundError(
                 "Detected background access to dialog manager. "
                 "Please use background manager available via `manager.bg()` "
-                "method to access methods from background tasks"
+                "method to access methods from background tasks",
             )
 
     async def load_data(self) -> Dict:
@@ -77,18 +87,25 @@ def storage(self) -> StorageProxy:
         return self.data[STORAGE_KEY]
 
     async def _remove_kbd(self) -> None:
-        chat = get_chat(self.event)
-        message = Message(chat=chat,
-                          message_id=self.current_stack().last_message_id)
+        chat = self.data["event_chat"]
+        bot = self.data["bot"]
+        message = Message(
+            chat=chat,
+            message_id=self.current_stack().last_message_id,
+            date=datetime.now(),
+        )
         await self._registry.message_manager.remove_kbd(
-            self.event.bot, message,
+            bot,
+            message,
         )
         self.current_stack().last_message_id = None
 
     async def _process_last_dialog_result(
-            self, start_data: Data, result: Any,
+            self,
+            start_data: Data,
+            result: Any,
     ) -> None:
-        """Process closing last dialog in stack"""
+        """Process closing last dialog in stack."""
         await self._remove_kbd()
 
     async def done(self, result: Any = None) -> None:
@@ -98,7 +115,8 @@ async def done(self, result: Any = None) -> None:
         context = self.current_context()
         if not context:
             await self._process_last_dialog_result(
-                old_context.start_data, result,
+                old_context.start_data,
+                result,
             )
             return
         dialog = self._dialog()
@@ -117,6 +135,7 @@ async def mark_closed(self) -> None:
         else:
             intent_id = stack.last_intent_id()
             self.data[CONTEXT_KEY] = await storage.load_context(intent_id)
+            await storage.save_stack(stack)
 
     async def start(
             self,
@@ -157,7 +176,8 @@ async def _start_normal(self, state: State, data: Data = None) -> None:
             old_dialog = self._dialog()
             if old_dialog.launch_mode is LaunchMode.EXCLUSIVE:
                 raise ValueError(
-                    "Cannot start dialog on top of one with launch_mode==SINGLE"
+                    "Cannot start dialog on top "
+                    "of one with launch_mode==SINGLE",
                 )
 
         new_dialog = self.registry.find_dialog(state)
@@ -179,16 +199,20 @@ async def switch_to(self, state: State) -> None:
         self.check_disabled()
         context = self.current_context()
         if context.state.group != state.group:
-            raise ValueError(f"Cannot switch to another state group. "
-                             f"Current state: {context.state}, asked for {state}")
+            raise ValueError(
+                f"Cannot switch to another state group. "
+                f"Current state: {context.state}, asked for {state}",
+            )
         context.state = state
 
     async def show(self, new_message: NewMessage) -> Message:
         stack = self.current_stack()
+        bot = self.data["bot"]
+        chat = self.data["event_chat"]
         if (
-                isinstance(self.event, CallbackQuery)
-                and self.event.message
-                and stack.last_message_id == self.event.message.message_id
+                isinstance(self.event, CallbackQuery) and
+                self.event.message and
+                stack.last_message_id == self.event.message.message_id
         ):
             old_message = self.event.message
         else:
@@ -205,17 +229,22 @@ async def show(self, new_message: NewMessage) -> Message:
                 else:
                     document = None
                     # we set some non empty-text which is not equal to anything
-                    text = object()
-                old_message = Message(message_id=stack.last_message_id,
-                                      document=document,
-                                      text=text,
-                                      chat=get_chat(self.event))
+                    text = "𝔞𝔦𝔬𝔤𝔯𝔞𝔪 𝔡𝔦𝔞𝔩𝔬𝔤 𝔲𝔫𝔦𝔮𝔲𝔢 𝔱𝔢𝔵𝔱"
+                old_message = Message(
+                    message_id=stack.last_message_id,
+                    document=document,
+                    text=text,
+                    chat=chat,
+                    date=datetime.now(),
+                )
             else:
                 old_message = None
         if new_message.show_mode is ShowMode.AUTO:
             new_message.show_mode = self._calc_show_mode()
         res = await self._registry.message_manager.show_message(
-            self.event.bot, new_message, old_message,
+            bot,
+            new_message,
+            old_message,
         )
         if isinstance(self.event, Message):
             stack.last_income_media_group_id = self.event.media_group_id
@@ -230,7 +259,8 @@ def _calc_show_mode(self) -> ShowMode:
         if isinstance(self.event, Message):
             if self.event.media_group_id is None:
                 return ShowMode.SEND
-            elif self.event.media_group_id == self.current_stack().last_income_media_group_id:
+            elif self.event.media_group_id == \
+                    self.current_stack().last_income_media_group_id:
                 return ShowMode.EDIT
             else:
                 return ShowMode.SEND
@@ -240,6 +270,25 @@ async def update(self, data: Dict) -> None:
         self.current_context().dialog_data.update(data)
         await self._dialog().show(self)
 
+    def is_same_chat(self, user: User, chat: Chat):
+        current_chat = self.data["event_chat"]
+        current_user = self.event.from_user
+        return user.id == current_user.id and chat.id == current_chat.id
+
+    def _get_fake_user(self, user_id: Optional[int] = None) -> User:
+        """Get User if we have info about him or FakeUser instead."""
+        current_user = self.event.from_user
+        if user_id in (None, current_user.id):
+            return current_user
+        return FakeUser(id=user_id, is_bot=False, first_name="")
+
+    def _get_fake_chat(self, chat_id: Optional[int] = None) -> Chat:
+        """Get Chat if we have info about him or FakeChat instead."""
+        current_chat = self.data["event_chat"]
+        if chat_id in (None, current_chat.id):
+            return current_chat
+        return FakeChat(id=chat_id, type="")
+
     def bg(
             self,
             user_id: Optional[int] = None,
@@ -247,31 +296,21 @@ def bg(
             stack_id: Optional[str] = None,
             load: bool = False,
     ) -> "BaseDialogManager":
-        current_chat = get_chat(self.event)
-        current_user = self.event.from_user
-        if chat_id in (None, current_chat.id):
-            chat = current_chat
-        else:
-            chat = FakeChat(id=chat_id)
-
-        if user_id in (None, current_user.id):
-            user = current_user
-        else:
-            user = FakeUser(id=user_id)
-
-        same_chat = (user.id == current_user.id and chat.id == current_chat.id)
+        user = self._get_fake_user(user_id)
+        chat = self._get_fake_chat(chat_id)
         intent_id = None
         if stack_id is None:
-            if same_chat:
+            if self.is_same_chat(user, chat):
                 stack_id = self.current_stack().id
                 if self.current_context():
                     intent_id = self.current_context().id
             else:
                 stack_id = DEFAULT_STACK_ID
+
         return BgManager(
             user=user,
             chat=chat,
-            bot=self.event.bot,
+            bot=self.data["bot"],
             registry=self.registry,
             intent_id=intent_id,
             stack_id=stack_id,
diff --git a/aiogram_dialog/manager/manager_middleware.py b/aiogram_dialog/manager/manager_middleware.py
index 09d7e2d8..7beb53ee 100644
--- a/aiogram_dialog/manager/manager_middleware.py
+++ b/aiogram_dialog/manager/manager_middleware.py
@@ -1,10 +1,10 @@
-from typing import Any
+from typing import Any, Awaitable, Callable, Dict, Union
 
-from aiogram.dispatcher.middlewares import BaseMiddleware
+from aiogram.dispatcher.middlewares.base import BaseMiddleware
+from aiogram.types import Update
 
-from .protocols import (
-    DialogManager, DialogRegistryProto, DialogManagerFactory,
-)
+from .protocols import DialogManager, DialogManagerFactory, DialogRegistryProto
+from ..context.events import ChatEvent, DialogUpdateEvent
 
 MANAGER_KEY = "dialog_manager"
 
@@ -19,34 +19,24 @@ def __init__(
         self.registry = registry
         self.dialog_manager_factory = dialog_manager_factory
 
-    async def on_pre_process_message(self, event: Any, data: dict):
+    async def __call__(
+            self,
+            handler: Callable[
+                [Union[Update, DialogUpdateEvent], Dict[str, Any]],
+                Awaitable[Any],
+            ],
+            event: ChatEvent,
+            data: Dict[str, Any],
+    ) -> Any:
         data[MANAGER_KEY] = self.dialog_manager_factory(
             event=event,
             registry=self.registry,
             data=data,
         )
 
-    on_pre_process_callback_query = on_pre_process_message
-    on_pre_process_aiogd_update = on_pre_process_message
-    on_pre_process_my_chat_member = on_pre_process_message
-
-    async def on_post_process_message(self, _, result, data: dict):
-        manager: DialogManager = data.pop(MANAGER_KEY)
-        await manager.close_manager()
-
-    on_post_process_callback_query = on_post_process_message
-    on_post_process_aiogd_update = on_post_process_message
-    on_post_process_my_chat_member = on_post_process_message
-
-    async def on_pre_process_error(self, update: Any, error: Exception,
-                                   data: dict) -> None:
-        event = (
-                update.message or
-                update.my_chat_member or
-                update.callback_query
-        )
-        await self.on_pre_process_message(event, data)
-
-    async def on_post_process_error(self, event: Any, error: Exception,
-                                    result: list, data: dict) -> None:
-        await self.on_post_process_message(event, result, data)
+        try:
+            return await handler(event, data)
+        finally:
+            manager: DialogManager = data.pop(MANAGER_KEY, None)
+            if manager:
+                await manager.close_manager()
diff --git a/aiogram_dialog/manager/protocols.py b/aiogram_dialog/manager/protocols.py
index 55a83400..8459ce1f 100644
--- a/aiogram_dialog/manager/protocols.py
+++ b/aiogram_dialog/manager/protocols.py
@@ -1,22 +1,20 @@
 from dataclasses import dataclass
 from enum import Enum
-from typing import Optional, Any, Protocol, Union, Type, Dict
+from typing import Any, Dict, Optional, Protocol, Type, Union
 
-from aiogram import Dispatcher, Bot
-from aiogram.dispatcher.filters.state import State, StatesGroup
-from aiogram.types import (
-    ContentType, Message, Chat, InlineKeyboardMarkup, ParseMode,
-)
+from aiogram import Bot, Router
+from aiogram.fsm.state import State, StatesGroup
+from aiogram.types import Chat, ContentType, InlineKeyboardMarkup, Message
 
 from ..context.context import Context
-from ..context.events import (
-    DialogUpdateEvent, StartMode, ChatEvent, Data, ShowMode,
-)
+from ..context.events import ChatEvent, Data, DialogUpdate, ShowMode, StartMode
 from ..context.stack import Stack
 
 
 class LaunchMode(Enum):
     """
+    Modes of launching new dialog.
+
     `ROOT` dialogs will be always a root dialog in stack.
         Starting such dialogs will automatically reset stack.
         Example: main menu
@@ -32,6 +30,7 @@ class LaunchMode(Enum):
 
     `STANDARD` dialogs have no limitations themselves
     """
+
     STANDARD = "standard"
     ROOT = "root"
     EXCLUSIVE = "exclusive"
@@ -58,8 +57,10 @@ def find(self, widget_id) -> Optional[Any]:
 class ManagedDialogProto(Protocol):
     launch_mode: LaunchMode
 
-    def register(self, registry: "DialogRegistryProto", dp: Dispatcher, *args,
-                 **kwargs) -> None:
+    def register(
+            self, registry: "DialogRegistryProto", router: Router, *args,
+            **kwargs,
+    ) -> None:
         pass
 
     def states_group_name(self) -> str:
@@ -71,15 +72,20 @@ def states_group(self) -> Type[StatesGroup]:
     async def process_close(self, result: Any, manager: "DialogManager"):
         pass
 
-    async def process_start(self, manager: "DialogManager", start_data: Any,
-                            state: Optional[State] = None) -> None:
+    async def process_start(
+            self,
+            manager: "DialogManager",
+            start_data: Any,
+            state: Optional[State] = None,
+    ) -> None:
         pass
 
     async def show(self, manager: "DialogManager"):
         pass
 
-    async def process_result(self, start_data: Data, result: Any,
-                             manager: "DialogManager"):
+    async def process_result(
+            self, start_data: Data, result: Any, manager: "DialogManager",
+    ):
         pass
 
     async def next(self, manager: "DialogManager"):
@@ -150,7 +156,7 @@ class NewMessage:
     chat: Chat
     text: Optional[str] = None
     reply_markup: Optional[InlineKeyboardMarkup] = None
-    parse_mode: Optional[ParseMode] = None
+    parse_mode: Optional[str] = None
     show_mode: ShowMode = ShowMode.AUTO
     disable_web_page_preview: Optional[bool] = None
     media: Optional[MediaAttachment] = None
@@ -160,8 +166,10 @@ class MessageManagerProtocol(Protocol):
     async def remove_kbd(self, bot: Bot, old_message: Optional[Message]):
         raise NotImplementedError
 
-    async def show_message(self, bot: Bot, new_message: NewMessage,
-                           old_message: Optional[Message]):
+    async def show_message(
+            self, bot: Bot, new_message: NewMessage,
+            old_message: Optional[Message],
+    ):
         raise NotImplementedError
 
 
@@ -169,7 +177,7 @@ class DialogRegistryProto(Protocol):
     def find_dialog(self, state: Union[State, str]) -> ManagedDialogProto:
         pass
 
-    async def notify(self, event: DialogUpdateEvent) -> None:
+    async def notify(self, bot: Bot, update: DialogUpdate) -> None:
         pass
 
     @property
@@ -251,7 +259,10 @@ async def load_data(self) -> Dict:
 
 class DialogManagerFactory(Protocol):
     def __call__(
-            self, event: ChatEvent, registry: DialogRegistryProto, data: Dict,
+            self,
+            event: ChatEvent,
+            registry: DialogRegistryProto,
+            data: Dict,
     ) -> DialogManager:
         raise NotImplementedError
 
diff --git a/aiogram_dialog/manager/registry.py b/aiogram_dialog/manager/registry.py
index 225b9835..8accb022 100644
--- a/aiogram_dialog/manager/registry.py
+++ b/aiogram_dialog/manager/registry.py
@@ -1,26 +1,40 @@
 import asyncio
 from contextvars import copy_context
-from typing import Sequence, Type, Dict, Optional
+from typing import Dict, Optional, Sequence, Type
 
-from aiogram import Dispatcher, Bot
-from aiogram.dispatcher.filters.state import State, StatesGroup
-from aiogram.dispatcher.handler import Handler
-from aiogram.types import User, Chat, Message
+from aiogram import Bot, Dispatcher, Router
+from aiogram.dispatcher.event.telegram import TelegramEventObserver
+from aiogram.filters import Command
+from aiogram.fsm.state import any_state, State, StatesGroup
+from aiogram.types import Chat, Message, User
 
 from .manager import ManagerImpl
 from .manager_middleware import ManagerMiddleware
 from .protocols import (
-    ManagedDialogProto, DialogRegistryProto, DialogManager,
-    MediaIdStorageProtocol, MessageManagerProtocol, DialogManagerFactory,
+    DialogManager,
+    DialogManagerFactory,
+    DialogRegistryProto,
+    ManagedDialogProto,
+    MediaIdStorageProtocol,
+    MessageManagerProtocol,
 )
 from .update_handler import handle_update
-from ..context.events import DialogUpdateEvent, StartMode
-from ..context.intent_filter import IntentFilter, IntentMiddleware
+from ..context.events import DIALOG_EVENT_NAME, DialogUpdate, StartMode
+from ..context.intent_filter import (
+    context_saver_middleware,
+    IntentErrorMiddleware,
+    IntentFilter,
+    IntentMiddlewareFactory,
+)
 from ..context.media_storage import MediaIdStorage
 from ..exceptions import UnregisteredDialogError
 from ..message_manager import MessageManager
 
 
+class DialogEventObserver(TelegramEventObserver):
+    pass
+
+
 class DialogRegistry(DialogRegistryProto):
     def __init__(
             self,
@@ -29,25 +43,32 @@ def __init__(
             media_id_storage: Optional[MediaIdStorageProtocol] = None,
             message_manager: Optional[MessageManagerProtocol] = None,
             dialog_manager_factory: DialogManagerFactory = ManagerImpl,
+            default_router: Optional[Router] = None,
     ):
         self.dp = dp
-        self.dialogs = {
-            d.states_group(): d for d in dialogs
-        }
+        self.update_handler = self.dp.observers[
+            DIALOG_EVENT_NAME
+        ] = DialogEventObserver(router=self.dp, event_name=DIALOG_EVENT_NAME)
+        self.default_router = (
+            default_router
+            if default_router
+            else dp.include_router(Router(name="aiogram_dialog_router"))
+        )
+
+        self.dialogs = {d.states_group(): d for d in dialogs}
         self.state_groups: Dict[str, Type[StatesGroup]] = {
             d.states_group_name(): d.states_group() for d in dialogs
         }
-        self.update_handler = Handler(dp, middleware_key="aiogd_update")
-        self.register_update_handler(handle_update, state="*")
-        self.dp.filters_factory.bind(IntentFilter)
-        self.dialog_manager_factory = dialog_manager_factory
-        self._register_middleware()
+        self.register_update_handler(handle_update, any_state)
+
         if media_id_storage is None:
             media_id_storage = MediaIdStorage()
         self._media_id_storage = media_id_storage
         if message_manager is None:
             message_manager = MessageManager()
         self._message_manager = message_manager
+        self.dialog_manager_factory = dialog_manager_factory
+        self._register_middleware()
 
     @property
     def media_id_storage(self) -> MediaIdStorageProtocol:
@@ -57,7 +78,13 @@ def media_id_storage(self) -> MediaIdStorageProtocol:
     def message_manager(self) -> MessageManagerProtocol:
         return self._message_manager
 
-    def register(self, dialog: ManagedDialogProto, *args, **kwargs):
+    def register(
+            self,
+            dialog: ManagedDialogProto,
+            *args,
+            router: Router = None,
+            **kwargs,
+    ):
         group = dialog.states_group()
         if group in self.dialogs:
             raise ValueError(f"StatesGroup `{group}` is already used")
@@ -65,23 +92,54 @@ def register(self, dialog: ManagedDialogProto, *args, **kwargs):
         self.state_groups[dialog.states_group_name()] = group
         dialog.register(
             self,
-            self.dp,
+            router if router else self.default_router,
+            IntentFilter(aiogd_intent_state_group=group),
             *args,
-            aiogd_intent_state_group=group,
-            **kwargs
+            **kwargs,
         )
 
     def register_start_handler(self, state: State):
-        @self.dp.message_handler(commands=["start"], state="*")
         async def start_dialog(m: Message, dialog_manager: DialogManager):
             await dialog_manager.start(state, mode=StartMode.RESET_STACK)
 
+        self.dp.message.register(
+            start_dialog, Command(commands="start"), any_state,
+        )
+
     def _register_middleware(self):
-        self.dp.setup_middleware(
-            ManagerMiddleware(self, self.dialog_manager_factory)
+        manager_middleware = ManagerMiddleware(
+            self,
+            self.dialog_manager_factory,
+        )
+        intent_middleware = IntentMiddlewareFactory(
+            storage=self.dp.fsm.storage, state_groups=self.state_groups,
+        )
+        self.dp.message.middleware(manager_middleware)
+        self.dp.callback_query.middleware(manager_middleware)
+        self.update_handler.middleware(manager_middleware)
+        self.dp.my_chat_member.middleware(manager_middleware)
+        self.dp.errors.middleware(manager_middleware)
+
+        self.dp.message.outer_middleware(intent_middleware.process_message)
+        self.dp.callback_query.outer_middleware(
+            intent_middleware.process_callback_query,
+        )
+        self.update_handler.outer_middleware(
+            intent_middleware.process_aiogd_update,
         )
-        self.dp.setup_middleware(
-            IntentMiddleware(storage=self.dp.storage, state_groups=self.state_groups)
+        self.dp.my_chat_member.outer_middleware(
+            intent_middleware.process_my_chat_member,
+        )
+
+        self.dp.message.middleware(context_saver_middleware)
+        self.dp.callback_query.middleware(context_saver_middleware)
+        self.update_handler.middleware(context_saver_middleware)
+        self.dp.my_chat_member.middleware(context_saver_middleware)
+
+        self.dp.errors.outer_middleware(
+            IntentErrorMiddleware(
+                storage=self.dp.fsm.storage, state_groups=self.state_groups,
+            ),
         )
 
     def find_dialog(self, state: State) -> ManagedDialogProto:
@@ -90,27 +148,31 @@ def find_dialog(self, state: State) -> ManagedDialogProto:
         except KeyError as e:
             raise UnregisteredDialogError(
                 f"No dialog found for `{state.group}`"
-                f" (looking by state `{state}`)"
+                f" (looking by state `{state}`)",
             ) from e
 
-    def register_update_handler(self, callback, *custom_filters, run_task=None, **kwargs) -> None:
-        filters_set = self.dp.filters_factory.resolve(
-            self.update_handler, *custom_filters, **kwargs
-        )
-        self.update_handler.register(
-            self.dp._wrap_async_task(callback, run_task), filters_set
-        )
+    def register_update_handler(
+            self, callback, *custom_filters, **kwargs,
+    ) -> None:
+        self.update_handler.register(callback, *custom_filters, **kwargs)
 
-    async def notify(self, event: DialogUpdateEvent) -> None:
-        callback = lambda: asyncio.create_task(self._process_update(event))
+    async def notify(self, bot: Bot, update: DialogUpdate) -> None:
+        def callback():
+            asyncio.create_task(
+                self._process_update(bot, update),
+            )
 
-        asyncio.get_running_loop().call_soon(
-            callback,
-            context=copy_context()
-        )
+        asyncio.get_running_loop().call_soon(callback, context=copy_context())
 
-    async def _process_update(self, event: DialogUpdateEvent):
-        Bot.set_current(event.bot)
+    async def _process_update(self, bot: Bot, update: DialogUpdate):
+        event = update.event
+        Bot.set_current(bot)
         User.set_current(event.from_user)
         Chat.set_current(event.chat)
-        await self.update_handler.notify(event)
+        await self.dp.propagate_event(
+            update_type="update",
+            event=update,
+            bot=bot,
+            event_from_user=event.from_user,
+            event_chat=event.chat,
+        )
diff --git a/aiogram_dialog/manager/update_handler.py b/aiogram_dialog/manager/update_handler.py
index e8aa4f22..feced888 100644
--- a/aiogram_dialog/manager/update_handler.py
+++ b/aiogram_dialog/manager/update_handler.py
@@ -1,7 +1,12 @@
 from logging import getLogger
 
 from .manager import ManagerImpl
-from ..context.events import DialogUpdateEvent, DialogStartEvent, Action, DialogSwitchEvent
+from ..context.events import (
+    Action,
+    DialogStartEvent,
+    DialogSwitchEvent,
+    DialogUpdateEvent,
+)
 
 logger = getLogger(__name__)
 
@@ -9,8 +14,10 @@
 async def handle_update(event: DialogUpdateEvent, dialog_manager: ManagerImpl):
     if isinstance(event, DialogStartEvent):
         await dialog_manager.start(
-            state=event.new_state, data=event.data,
-            mode=event.mode, show_mode=event.show_mode,
+            state=event.new_state,
+            data=event.data,
+            mode=event.mode,
+            show_mode=event.show_mode,
         )
     elif isinstance(event, DialogSwitchEvent):
         await dialog_manager.switch_to(state=event.new_state)
diff --git a/aiogram_dialog/message_manager.py b/aiogram_dialog/message_manager.py
index 5040988b..dc99ddbc 100644
--- a/aiogram_dialog/message_manager.py
+++ b/aiogram_dialog/message_manager.py
@@ -1,16 +1,22 @@
 from logging import getLogger
-from typing import IO, Optional, Union
+from typing import Optional, Union
 
 from aiogram import Bot
-from aiogram.types import ContentType, InputMedia, Message
-from aiogram.utils.exceptions import (
-    MessageNotModified, MessageCantBeEdited, MessageToEditNotFound,
-    MessageToDeleteNotFound, MessageCantBeDeleted,
+from aiogram.exceptions import TelegramBadRequest
+from aiogram.types import (
+    ContentType,
+    FSInputFile,
+    InputFile,
+    InputMedia,
+    Message,
+    URLInputFile,
 )
 
 from .context.events import ShowMode
 from .manager.protocols import (
-    MediaAttachment, NewMessage, MessageManagerProtocol,
+    MediaAttachment,
+    MessageManagerProtocol,
+    NewMessage,
 )
 from .utils import get_media_id
 
@@ -30,70 +36,121 @@
 
 
 class MessageManager(MessageManagerProtocol):
-
-    async def get_media_source(self, media: MediaAttachment) -> Union[IO, str]:
+    async def get_media_source(
+            self, media: MediaAttachment,
+    ) -> Union[InputFile, str]:
         if media.file_id:
             return media.file_id.file_id
         if media.url:
-            return media.url
+            return URLInputFile(media.url)
         else:
-            return open(media.path, "rb")
-
-    async def show_message(self, bot: Bot, new_message: NewMessage,
-                           old_message: Optional[Message]):
+            return FSInputFile(media.path)
+
+    def had_media(self, old_message: Message) -> bool:
+        return old_message.content_type != ContentType.TEXT
+
+    def need_media(self, new_message: NewMessage) -> bool:
+        return bool(new_message.media)
+
+    def _message_changed(
+            self, new_message: NewMessage, old_message: Message,
+    ) -> bool:
+        if new_message.text != old_message.text:
+            return True
+        if new_message.reply_markup != old_message.reply_markup:
+            return True
+
+        if self.had_media(old_message) != self.need_media(new_message):
+            return True
+        if not self.need_media(new_message):
+            return False
+        if new_message.media.file_id != get_media_id(old_message):
+            return True
+
+        return False
+
+    def _can_edit(self, new_message: NewMessage, old_message: Message) -> bool:
+        # we cannot edit message if media appeared or removed
+        return self.had_media(old_message) == self.need_media(new_message)
+
+    async def show_message(
+            self, bot: Bot, new_message: NewMessage,
+            old_message: Optional[Message],
+    ):
         if not old_message or new_message.show_mode is ShowMode.SEND:
             logger.debug(
                 "Send new message, because: mode=%s, has old_message=%s",
-                new_message.show_mode, bool(old_message))
+                new_message.show_mode,
+                bool(old_message),
+            )
             await self.remove_kbd(bot, old_message)
             return await self.send_message(bot, new_message)
 
-        had_media = old_message.content_type != ContentType.TEXT
-        need_media = bool(new_message.media)
-
-        if (
-                new_message.text == old_message.text and
-                new_message.reply_markup == old_message.reply_markup and
-                had_media == need_media and
-                (
-                        not need_media or
-                        new_message.media.file_id == get_media_id(old_message)
-                )
-        ):
+        if not self._message_changed(new_message, old_message):
             # nothing changed: text, keyboard or media
             return old_message
 
-        if had_media != need_media:
-            # we cannot edit message if media appeared or removed
-            try:
-                await bot.delete_message(chat_id=old_message.chat.id,
-                                         message_id=old_message.message_id)
-            except (MessageToDeleteNotFound, MessageCantBeDeleted):
-                await self.remove_kbd(bot, old_message)
+        if not self._can_edit(new_message, old_message):
+            await self.remove_message_safe(bot, old_message)
             return await self.send_message(bot, new_message)
 
-        try:
-            return await self.edit_message(bot, new_message, old_message)
-        except MessageNotModified:
-            return old_message
-        except (MessageCantBeEdited, MessageToEditNotFound):
-            return await self.send_message(bot, new_message)
+        return await self.edit_message_safe(bot, new_message, old_message)
 
+    # Clear
     async def remove_kbd(self, bot: Bot, old_message: Optional[Message]):
-        if old_message:
-            logger.debug("remove_kbd in %s", old_message.chat)
-            try:
-                await bot.edit_message_reply_markup(
-                    message_id=old_message.message_id,
-                    chat_id=old_message.chat.id,
-                )
-            except (MessageNotModified, MessageCantBeEdited,
-                    MessageToEditNotFound):
+        if not old_message:
+            return
+        logger.debug("remove_kbd in %s", old_message.chat)
+        try:
+            await bot.edit_message_reply_markup(
+                message_id=old_message.message_id,
+                chat_id=old_message.chat.id,
+            )
+        except TelegramBadRequest as err:
+            if "message is not modified" in err.message:
                 pass  # nothing to remove
+            elif "message can't be edited" in err.message:
+                pass
+            elif "message to edit not found" in err.message:
+                pass
+            else:
+                raise err
+
+    async def remove_message_safe(self, bot: Bot, old_message: Message):
+        try:
+            await bot.delete_message(
+                chat_id=old_message.chat.id,
+                message_id=old_message.message_id,
+            )
+        except TelegramBadRequest as err:
+            if (
+                    "message to delete not found" in err.message or
+                    "message can't be deleted" in err.message
+            ):
+                await self.remove_kbd(bot, old_message)
+            else:
+                raise
 
     # Edit
-    async def edit_message(self, bot: Bot, new_message: NewMessage,
-                           old_message: Message):
+    async def edit_message_safe(
+            self, bot: Bot, new_message: NewMessage, old_message: Message,
+    ):
+        try:
+            return await self.edit_message(bot, new_message, old_message)
+        except TelegramBadRequest as err:
+            if "message is not modified" in err.message:
+                return old_message
+            if (
+                    "message can't be edited" in err.message or
+                    "message to edit not found" in err.message
+            ):
+                return await self.send_message(bot, new_message)
+            else:
+                raise
+
+    async def edit_message(
+            self, bot: Bot, new_message: NewMessage, old_message: Message,
+    ):
         if new_message.media:
             if new_message.media.file_id == get_media_id(old_message):
                 return await self.edit_caption(bot, new_message, old_message)
@@ -101,8 +158,9 @@ async def edit_message(self, bot: Bot, new_message: NewMessage,
         else:
             return await self.edit_text(bot, new_message, old_message)
 
-    async def edit_caption(self, bot: Bot, new_message: NewMessage,
-                           old_message: Message):
+    async def edit_caption(
+            self, bot: Bot, new_message: NewMessage, old_message: Message,
+    ):
         logger.debug("edit_caption to %s", new_message.chat)
         return await bot.edit_message_caption(
             message_id=old_message.message_id,
@@ -112,8 +170,9 @@ async def edit_caption(self, bot: Bot, new_message: NewMessage,
             parse_mode=new_message.parse_mode,
         )
 
-    async def edit_text(self, bot: Bot, new_message: NewMessage,
-                        old_message: Message):
+    async def edit_text(
+            self, bot: Bot, new_message: NewMessage, old_message: Message,
+    ):
         logger.debug("edit_text to %s", new_message.chat)
         return await bot.edit_message_text(
             message_id=old_message.message_id,
@@ -124,10 +183,14 @@ async def edit_text(self, bot: Bot, new_message: NewMessage,
             disable_web_page_preview=new_message.disable_web_page_preview,
         )
 
-    async def edit_media(self, bot: Bot, new_message: NewMessage,
-                         old_message: Message):
-        logger.debug("edit_media to %s, media_id: %s",
-                     new_message.chat, new_message.media.file_id)
+    async def edit_media(
+            self, bot: Bot, new_message: NewMessage, old_message: Message,
+    ):
+        logger.debug(
+            "edit_media to %s, media_id: %s",
+            new_message.chat,
+            new_message.media.file_id,
+        )
         media = InputMedia(
             caption=new_message.text,
             reply_markup=new_message.reply_markup,
@@ -162,8 +225,11 @@ async def send_text(self, bot: Bot, new_message: NewMessage):
         )
 
     async def send_media(self, bot: Bot, new_message: NewMessage):
-        logger.debug("send_media to %s, media_id: %s",
-                     new_message.chat, new_message.media.file_id)
+        logger.debug(
+            "send_media to %s, media_id: %s",
+            new_message.chat,
+            new_message.media.file_id,
+        )
         method = getattr(bot, SEND_METHODS[new_message.media.type], None)
         if not method:
             raise ValueError(
diff --git a/aiogram_dialog/tools/__init__.py b/aiogram_dialog/tools/__init__.py
index d53d1d0f..8220f3fc 100644
--- a/aiogram_dialog/tools/__init__.py
+++ b/aiogram_dialog/tools/__init__.py
@@ -1,5 +1,5 @@
-from .transitions import render_transitions
 from .preview import render_preview
+from .transitions import render_transitions
 
 __all__ = [
     "render_transitions",
diff --git a/aiogram_dialog/tools/preview.py b/aiogram_dialog/tools/preview.py
index 9f29e0c7..63783dc0 100644
--- a/aiogram_dialog/tools/preview.py
+++ b/aiogram_dialog/tools/preview.py
@@ -1,25 +1,29 @@
 import html
 import logging
 from dataclasses import dataclass
-from typing import List, Optional, Any, Dict
+from datetime import datetime
+from typing import Any, Dict, List, Optional
 
-from aiogram import Bot
-from aiogram.dispatcher.filters.state import State, StatesGroup
-from aiogram.types import User, Chat, Message, ContentType, CallbackQuery
+from aiogram.fsm.state import State, StatesGroup
+from aiogram.types import CallbackQuery, Chat, ContentType, Message, User
 from jinja2 import Environment, PackageLoader, select_autoescape
 
-from aiogram_dialog import DialogRegistry, DialogManager, Dialog
+from aiogram_dialog import Dialog, DialogManager, DialogRegistry
 from aiogram_dialog.context.context import Context
 from aiogram_dialog.context.events import (
-    DialogUpdateEvent, Action, StartMode, Data,
+    Action,
+    Data,
+    DialogUpdateEvent,
+    StartMode,
 )
 from aiogram_dialog.context.stack import Stack
 from aiogram_dialog.manager.dialog import ManagedDialogAdapter
 from aiogram_dialog.manager.protocols import (
-    NewMessage, DialogRegistryProto, MediaAttachment,
+    DialogRegistryProto,
     ManagedDialogAdapterProto,
+    MediaAttachment,
+    NewMessage,
 )
-from aiogram_dialog.utils import remove_indent_id
 
 
 @dataclass
@@ -47,9 +51,8 @@ class RenderDialog:
 class FakeManager(DialogManager):
     def __init__(self, registry: DialogRegistry):
         self.event = DialogUpdateEvent(
-            bot=registry.dp.bot,
-            from_user=User(),
-            chat=Chat(),
+            from_user=User(id=1, is_bot=False, first_name="Fake"),
+            chat=Chat(id=1, type="private"),
             action=Action.UPDATE,
             data={},
             intent_id=None,
@@ -59,7 +62,9 @@ def __init__(self, registry: DialogRegistry):
         self._context: Optional[Context] = None
         self._dialog = None
         self.data = {
-            "dialog_manager": self
+            "dialog_manager": self,
+            "event_chat": Chat(id=1, type="private"),
+            "event_from_user": User(id=1, is_bot=False, first_name="Fake"),
         }
 
     async def load_data(self) -> Dict:
@@ -110,7 +115,9 @@ def current_context(self) -> Optional[Context]:
 
     async def show(self, new_message: NewMessage) -> Message:
         self.new_message = new_message
-        return Message()
+        return Message(
+            message_id=1, date=datetime.now(), chat=Chat(id=1, type="private"),
+        )
 
     @property
     def registry(self) -> DialogRegistryProto:
@@ -131,19 +138,25 @@ def create_photo(media: Optional[MediaAttachment]) -> Optional[str]:
 
 
 async def create_button(
-        title: str, callback: str, manager: FakeManager,
-        state: State, dialog: Dialog,
+        title: str,
+        callback: str,
+        manager: FakeManager,
+        state: State,
+        dialog: Dialog,
         simulate_events: bool,
 ) -> RenderButton:
     if not simulate_events:
         return RenderButton(title=title, state=state.state)
+    callback_query = CallbackQuery(
+        id=1,
+        from_user=User(id=1, is_bot=False, first_name=""),
+        chat_instance="",
+        data=callback,
+    )
+    manager.set_state(state)
     try:
-        manager.set_state(state)
-        _, callback = remove_indent_id(callback)
-        await dialog._callback_handler(
-            CallbackQuery(data=callback), dialog_manager=manager,
-        )
-    except Exception:
+        await dialog._callback_handler(callback_query, dialog_manager=manager)
+    except Exception:  # noqa: B902
         logging.debug("Click %s", callback)
     state = manager.current_context().state
     return RenderButton(title=title, state=state.state)
@@ -151,23 +164,35 @@ async def create_button(
 
 async def render_input(
         manager: FakeManager,
-        state: State, dialog: Dialog,
-        content_type: str, simulate_events: bool,
+        state: State,
+        dialog: Dialog,
+        content_type: str,
+        simulate_events: bool,
 ) -> Optional[RenderButton]:
     if not simulate_events:
         return None
-    message = Message(message_id=1, **{content_type: "<stub>"})
+    if content_type == ContentType.PHOTO:
+        data = {content_type: []}
+    else:
+        data = {content_type: "<stub>"}
+    message = Message(
+        message_id=1,
+        date=datetime.now(),
+        chat=Chat(id=1, type="private"),
+        **data,
+    )
+    manager.set_state(state)
     try:
-        manager.set_state(state)
         await dialog._message_handler(message, dialog_manager=manager)
-    except Exception:
+    except Exception:  # noqa: B902
         logging.debug("Input %s", content_type)
 
     if state == manager.current_context().state:
         logging.debug("State not changed")
         return None
-    logging.debug("State changed %s >> %s",
-                  state, manager.current_context().state)
+    logging.debug(
+        "State changed %s >> %s", state, manager.current_context().state,
+    )
     return RenderButton(
         title=content_type,
         state=manager.current_context().state.state,
@@ -175,8 +200,11 @@ async def render_input(
 
 
 async def create_window(
-        state: State, message: NewMessage, manager: FakeManager,
-        dialog: Dialog, simulate_events: bool,
+        state: State,
+        message: NewMessage,
+        manager: FakeManager,
+        dialog: Dialog,
+        simulate_events: bool,
 ) -> RenderWindow:
     if message.parse_mode is None or message.parse_mode == "None":
         text = html.escape(message.text)
@@ -186,11 +214,16 @@ async def create_window(
     for row in message.reply_markup.inline_keyboard:
         keyboard_row = []
         for button in row:
-            keyboard_row.append(await create_button(
-                title=button.text, callback=button.callback_data,
-                manager=manager, dialog=dialog, state=state,
-                simulate_events=simulate_events,
-            ))
+            keyboard_row.append(
+                await create_button(
+                    title=button.text,
+                    callback=button.callback_data,
+                    manager=manager,
+                    dialog=dialog,
+                    state=state,
+                    simulate_events=simulate_events,
+                ),
+            )
         keyboard.append(keyboard_row)
 
     return RenderWindow(
@@ -199,49 +232,64 @@ async def create_window(
         photo=create_photo(media=message.media),
         keyboard=keyboard,
         text_input=await render_input(
-            manager=manager, state=state, dialog=dialog,
-            content_type=ContentType.TEXT, simulate_events=simulate_events
+            manager=manager,
+            state=state,
+            dialog=dialog,
+            content_type=ContentType.TEXT,
+            simulate_events=simulate_events,
         ),
         attachment_input=await render_input(
-            manager=manager, state=state, dialog=dialog,
-            content_type=ContentType.PHOTO, simulate_events=simulate_events
+            manager=manager,
+            state=state,
+            dialog=dialog,
+            content_type=ContentType.PHOTO,
+            simulate_events=simulate_events,
         ),
     )
 
 
 async def render_dialog(
-        manager: FakeManager, group: StatesGroup,
-        dialog: Dialog, simulate_events: bool,
+        manager: FakeManager,
+        group: StatesGroup,
+        dialog: Dialog,
+        simulate_events: bool,
 ) -> RenderDialog:
     manager.set_dialog(dialog)
     windows = []
-    for state in group.states:
+    for state in group.__states__:
         manager.set_state(state)
         await dialog.show(manager)
-        windows.append(await create_window(
-            manager=manager, state=state, dialog=dialog,
-            message=manager.new_message, simulate_events=simulate_events,
-        ))
+        windows.append(
+            await create_window(
+                manager=manager,
+                state=state,
+                dialog=dialog,
+                message=manager.new_message,
+                simulate_events=simulate_events,
+            ),
+        )
 
     return RenderDialog(state_group=str(group), windows=windows)
 
 
 async def render_preview(
-        registry: DialogRegistry, file: str,
+        registry: DialogRegistry,
+        file: str,
         simulate_events: bool = False,
 ):
     fake_manager = FakeManager(registry)
-    Bot.set_current(registry.dp.bot)
     dialogs = [
         await render_dialog(
-            manager=fake_manager, group=group, dialog=dialog,
+            manager=fake_manager,
+            group=group,
+            dialog=dialog,
             simulate_events=simulate_events,
         )
         for group, dialog in registry.dialogs.items()
     ]
     env = Environment(
         loader=PackageLoader("aiogram_dialog.tools"),
-        autoescape=select_autoescape()
+        autoescape=select_autoescape(),
     )
     template = env.get_template("message.html")
     res = template.render(dialogs=dialogs)
diff --git a/aiogram_dialog/tools/templates/message.html b/aiogram_dialog/tools/templates/message.html
index 81cb021c..c1411a11 100644
--- a/aiogram_dialog/tools/templates/message.html
+++ b/aiogram_dialog/tools/templates/message.html
@@ -196,11 +196,11 @@
                     </div>
                 {% endif %}
                 {% if window.attachment_input %}
-                <div class="keyboard-row">
-                    <a href="#{{ window.attachment_input.state }}" class="keyboard-button keyboard-attachment">
-                        📎 MessageInput
-                    </a>
-                </div>
+                    <div class="keyboard-row">
+                        <a href="#{{ window.attachment_input.state }}" class="keyboard-button keyboard-attachment">
+                            📎 MessageInput
+                        </a>
+                    </div>
                 {% endif %}
             </div>
         </div>
diff --git a/aiogram_dialog/tools/transitions.py b/aiogram_dialog/tools/transitions.py
index c95c7a9c..b3fd6589 100644
--- a/aiogram_dialog/tools/transitions.py
+++ b/aiogram_dialog/tools/transitions.py
@@ -1,12 +1,20 @@
 import os.path
-from typing import List, Sequence, Tuple, Union
+from typing import Iterable, List, Sequence, Tuple, Union
 
-from aiogram.dispatcher.filters.state import State
-from diagrams import Diagram, Cluster, Edge
+from aiogram.fsm.state import State
+from diagrams import Cluster, Diagram, Edge
 from diagrams.custom import Custom
 
 from aiogram_dialog import Dialog, DialogRegistry
-from aiogram_dialog.widgets.kbd import Group, Back, Cancel, Start, SwitchTo, Next
+from aiogram_dialog.dialog import DialogWindowProto
+from aiogram_dialog.widgets.kbd import (
+    Back,
+    Cancel,
+    Group,
+    Next,
+    Start,
+    SwitchTo,
+)
 
 ICON_PATH = os.path.join(os.path.dirname(__file__), "calculator.png")
 
@@ -25,10 +33,20 @@ def widget_edges(nodes, dialog, starts, current_state, kbd):
     elif isinstance(kbd, Cancel):
         for from_, to_ in starts:
             if to_.group == current_state.group:
-                nodes[current_state] >> Edge(color="grey", style="dashed") >> nodes[from_]
+                (
+                    nodes[current_state] >>
+                    Edge(color="grey", style="dashed") >>
+                    nodes[from_]
+                )
 
 
-def walk_keyboard(nodes, dialog, starts: List[Tuple[State, State]], current_state: State, keyboards: Sequence):
+def walk_keyboard(
+        nodes,
+        dialog,
+        starts: List[Tuple[State, State]],
+        current_state: State,
+        keyboards: Sequence,
+):
     for kbd in keyboards:
         if isinstance(kbd, Group):
             walk_keyboard(nodes, dialog, starts, current_state, kbd.buttons)
@@ -36,7 +54,9 @@ def walk_keyboard(nodes, dialog, starts: List[Tuple[State, State]], current_stat
             widget_edges(nodes, dialog, starts, current_state, kbd)
 
 
-def find_starts(current_state, keyboards: Sequence):
+def find_starts(
+        current_state, keyboards: Sequence,
+) -> Iterable[Tuple[State, State]]:
     for kbd in keyboards:
         if isinstance(kbd, Group):
             yield from find_starts(current_state, kbd.buttons)
@@ -44,28 +64,57 @@ def find_starts(current_state, keyboards: Sequence):
             yield current_state, kbd.state
 
 
-def render_transitions(dialogs: Union[List[Dialog], DialogRegistry],
-                       title: str = "Aiogram Dialog",
-                       filename: str = "aiogram_dialog.png",
-                       format: str = "png"):
+def render_window(
+        nodes: dict, dialog: Dialog, starts: List[Tuple[State, State]],
+        window: DialogWindowProto,
+):
+    walk_keyboard(
+        nodes,
+        dialog,
+        starts,
+        window.get_state(),
+        [window.keyboard],
+    )
+    preview_add_transitions = getattr(
+        window, "preview_add_transitions", None,
+    )
+    if preview_add_transitions:
+        walk_keyboard(
+            nodes,
+            dialog,
+            starts,
+            window.get_state(),
+            preview_add_transitions,
+        )
+
+
+def render_transitions(
+        dialogs: Union[List[Dialog], DialogRegistry],
+        title: str = "Aiogram Dialog",
+        filename: str = "aiogram_dialog.png",
+        format: str = "png",
+):
     if isinstance(dialogs, DialogRegistry):
         dialogs = list(dialogs.dialogs.values())
-        
+
     with Diagram(title, filename=filename, outformat=format, show=False):
         nodes = {}
         for dialog in dialogs:
             with Cluster(dialog.states_group_name()):
                 for window in dialog.windows.values():
-                    nodes[window.get_state()] = Custom(icon_path=ICON_PATH, label=window.get_state().state)
+                    nodes[window.get_state()] = Custom(
+                        icon_path=ICON_PATH, label=window.get_state().state,
+                    )
 
         starts = []
         for dialog in dialogs:
             for window in dialog.windows.values():
-                starts.extend(find_starts(window.get_state(), [window.keyboard]))
+                starts.extend(
+                    find_starts(window.get_state(), [window.keyboard]),
+                )
 
         for dialog in dialogs:
             for window in dialog.windows.values():
-                walk_keyboard(nodes, dialog, starts, window.get_state(), [window.keyboard])
-                preview_add_transitions = getattr(window, "preview_add_transitions", None)
-                if preview_add_transitions:
-                    walk_keyboard(nodes, dialog, starts, window.get_state(), preview_add_transitions)
+                render_window(
+                    nodes=nodes, dialog=dialog, window=window, starts=starts,
+                )
diff --git a/aiogram_dialog/utils.py b/aiogram_dialog/utils.py
index c856c221..702e831d 100644
--- a/aiogram_dialog/utils.py
+++ b/aiogram_dialog/utils.py
@@ -1,14 +1,10 @@
 from logging import getLogger
 from typing import Optional, Tuple
 
-from aiogram.types import (
-    Message, CallbackQuery, Chat, ChatMemberUpdated, User,
-)
+from aiogram.types import CallbackQuery, Chat, ChatMemberUpdated, Message, User
 
-from .context.events import (
-    DialogUpdateEvent, ChatEvent
-)
-from .manager.protocols import NewMessage, MediaId
+from .context.events import ChatEvent, DialogUpdateEvent
+from .manager.protocols import MediaId, NewMessage
 
 logger = getLogger(__name__)
 
@@ -20,22 +16,24 @@ def get_chat(event: ChatEvent) -> Chat:
         return event.chat
     elif isinstance(event, CallbackQuery):
         if not event.message:
-            return Chat(id=event.from_user.id)
+            return Chat(id=event.from_user.id, type="")
         return event.message.chat
 
 
 def is_chat_loaded(chat: Chat) -> bool:
     """
-    Checks if chat is correctly loaded from telegram.
-    Otherwise, it is created with no data inside as a FakeChat
+    Check if chat is correctly loaded from telegram.
+
+    For internal events it can be created with no data inside as a FakeChat
     """
     return getattr(chat, "fake", False)
 
 
 def is_user_loaded(user: User) -> bool:
     """
-    Checks if chat is correctly loaded from telegram.
-    Otherwise, it is created with no data inside as a FakeUser
+    Check if user is correctly loaded from telegram.
+
+    For internal events it can be created with no data inside as a FakeUser
     """
     return getattr(user, "fake", False)
 
@@ -56,8 +54,9 @@ def get_media_id(message: Message) -> Optional[MediaId]:
     )
 
 
-def intent_callback_data(intent_id: str,
-                         callback_data: Optional[str]) -> Optional[str]:
+def intent_callback_data(
+        intent_id: str, callback_data: Optional[str],
+) -> Optional[str]:
     if callback_data is None:
         return None
     return intent_id + CB_SEP + callback_data
@@ -69,7 +68,7 @@ def add_indent_id(message: NewMessage, intent_id: str):
     for row in message.reply_markup.inline_keyboard:
         for button in row:
             button.callback_data = intent_callback_data(
-                intent_id, button.callback_data
+                intent_id, button.callback_data,
             )
 
 
diff --git a/aiogram_dialog/widgets/action.py b/aiogram_dialog/widgets/action.py
index 044e3bf9..c6ec3ab9 100644
--- a/aiogram_dialog/widgets/action.py
+++ b/aiogram_dialog/widgets/action.py
@@ -1,9 +1,9 @@
 import re
-from typing import Optional, Any
+from typing import Any, Optional
 
 from .managed import ManagedWidget
-from ..manager.protocols import DialogManager
 from ..exceptions import InvalidWidgetIdError
+from ..manager.protocols import DialogManager
 
 ID_PATTERN = re.compile("^[a-zA-Z0-9_.]+$")
 
@@ -15,25 +15,26 @@ def __init__(self, id: Optional[str] = None):
         self.widget_id = id
 
     def find(self, widget_id):
+        """Find nested widget or current one by id."""
         if self.widget_id is not None and self.widget_id == widget_id:
             return self
         return None
 
     def get_widget_data(
-            self, manager: DialogManager, default: Any,
+            self,
+            manager: DialogManager,
+            default: Any,
     ) -> Any:
-        """
-        Returns data for current widget id, setting default if needed
-        """
+        """Get data for current widget id, setting default if needed."""
         return manager.current_context().widget_data.setdefault(
-            self.widget_id, default,
+            self.widget_id,
+            default,
         )
 
     def set_widget_data(
-            self, manager: DialogManager, value: Any,
+            self,
+            manager: DialogManager,
+            value: Any,
     ) -> Any:
-        """
-        Set data for current widget id
-        """
+        """Set data for current widget id."""
         manager.current_context().widget_data[self.widget_id] = value
-
diff --git a/aiogram_dialog/widgets/data/__init__.py b/aiogram_dialog/widgets/data/__init__.py
index 4f8c3f40..d0c6047b 100644
--- a/aiogram_dialog/widgets/data/__init__.py
+++ b/aiogram_dialog/widgets/data/__init__.py
@@ -1,5 +1,8 @@
 from .data_context import (
-    DataGetter, CompositeGetter, StaticGetter, PreviewAwareGetter,
+    CompositeGetter,
+    DataGetter,
+    PreviewAwareGetter,
+    StaticGetter,
 )
 
 __all__ = [
diff --git a/aiogram_dialog/widgets/data/data_context.py b/aiogram_dialog/widgets/data/data_context.py
index f0ce237a..93f8f716 100644
--- a/aiogram_dialog/widgets/data/data_context.py
+++ b/aiogram_dialog/widgets/data/data_context.py
@@ -1,4 +1,4 @@
-from typing import Dict, Awaitable, Callable, List
+from typing import Awaitable, Callable, Dict, List
 
 from aiogram_dialog.manager.protocols import DialogManager
 
@@ -31,6 +31,10 @@ def __init__(self, normal_getter: DataGetter, preview_getter: DataGetter):
 
     async def __call__(self, dialog_manager: DialogManager, **kwargs):
         if dialog_manager.is_preview():
-            return await self.preview_getter(dialog_manager=dialog_manager, **kwargs)
+            return await self.preview_getter(
+                dialog_manager=dialog_manager, **kwargs,
+            )
         else:
-            return await self.normal_getter(dialog_manager=dialog_manager, **kwargs)
+            return await self.normal_getter(
+                dialog_manager=dialog_manager, **kwargs,
+            )
diff --git a/aiogram_dialog/widgets/input/__init__.py b/aiogram_dialog/widgets/input/__init__.py
index cfcd18f9..c9e933e5 100644
--- a/aiogram_dialog/widgets/input/__init__.py
+++ b/aiogram_dialog/widgets/input/__init__.py
@@ -1,7 +1,9 @@
-from .base import MessageHandlerFunc, MessageInput, BaseInput
+from .base import BaseInput, MessageHandlerFunc, MessageInput
 from .text import TextInput
 
 __all__ = [
-    "MessageHandlerFunc", "MessageInput", "BaseInput",
+    "MessageHandlerFunc",
+    "MessageInput",
+    "BaseInput",
     "TextInput",
 ]
diff --git a/aiogram_dialog/widgets/input/base.py b/aiogram_dialog/widgets/input/base.py
index 07cc6852..d5ff08ca 100644
--- a/aiogram_dialog/widgets/input/base.py
+++ b/aiogram_dialog/widgets/input/base.py
@@ -1,14 +1,17 @@
-from typing import Callable, Awaitable, List, Union
+from typing import Awaitable, Callable, Sequence, Union
 
-from aiogram.dispatcher.filters import ContentTypeFilter
-from aiogram.types import Message
+from aiogram.filters.content_types import ContentTypesFilter
+from aiogram.types import ContentType, Message
 
 from aiogram_dialog.manager.protocols import (
-    DialogManager, ManagedDialogAdapterProto, ManagedDialogProto
+    DialogManager,
+    ManagedDialogAdapterProto,
+    ManagedDialogProto,
 )
 from aiogram_dialog.widgets.action import Actionable
 from aiogram_dialog.widgets.widget_event import (
-    WidgetEventProcessor, ensure_event_processor,
+    ensure_event_processor,
+    WidgetEventProcessor,
 )
 
 MessageHandlerFunc = Callable[
@@ -18,19 +21,30 @@
 
 
 class BaseInput(Actionable):
-    async def process_message(self, m: Message, dialog: ManagedDialogProto, manager: DialogManager) -> bool:
+    async def process_message(
+            self, m: Message, dialog: ManagedDialogProto,
+            manager: DialogManager,
+    ) -> bool:
         raise NotImplementedError
 
 
 class MessageInput(BaseInput):
-    def __init__(self, func: Union[MessageHandlerFunc, WidgetEventProcessor, None],
-                 content_types: List[str] = ContentTypeFilter.default):
+    def __init__(
+            self,
+            func: Union[MessageHandlerFunc, WidgetEventProcessor, None],
+            content_types: Union[Sequence[str], str] = ContentType.ANY,
+    ):
         super().__init__()
         self.func = ensure_event_processor(func)
-        self.filter = ContentTypeFilter(content_types)
-
-    async def process_message(self, message: Message, dialog: ManagedDialogProto, manager: DialogManager) -> bool:
-        if not await self.filter.check(message):
+        self.filter = ContentTypesFilter(content_types=content_types)
+
+    async def process_message(
+            self,
+            message: Message,
+            dialog: ManagedDialogProto,
+            manager: DialogManager,
+    ) -> bool:
+        if not await self.filter(message):
             return False
         await self.func.process_event(message, manager.dialog(), manager)
         return True
diff --git a/aiogram_dialog/widgets/input/text.py b/aiogram_dialog/widgets/input/text.py
index b8445b57..62914c72 100644
--- a/aiogram_dialog/widgets/input/text.py
+++ b/aiogram_dialog/widgets/input/text.py
@@ -1,10 +1,11 @@
-from typing import Callable, TypeVar, Generic, Awaitable, Union, Optional
+from typing import Awaitable, Callable, Generic, Optional, TypeVar, Union
 
-from aiogram.types import Message, ContentType
+from aiogram.types import ContentType, Message
 
 from aiogram_dialog.manager.protocols import DialogManager, ManagedDialogProto
 from aiogram_dialog.widgets.widget_event import (
-    WidgetEventProcessor, ensure_event_processor,
+    ensure_event_processor,
+    WidgetEventProcessor,
 )
 from .base import BaseInput
 from ..managed import ManagedWidgetAdapter
@@ -18,7 +19,9 @@
 
 class TextInput(BaseInput, Generic[T]):
     def __init__(
-            self, id: str, type_factory: TypeFactory[T] = str,
+            self,
+            id: str,
+            type_factory: TypeFactory[T] = str,
             on_success: Union[OnSuccess[T], WidgetEventProcessor, None] = None,
             on_error: Union[OnError, WidgetEventProcessor, None] = None,
     ):
@@ -28,8 +31,10 @@ def __init__(
         self.on_error = ensure_event_processor(on_error)
 
     async def process_message(
-            self, message: Message,
-            dialog: ManagedDialogProto, manager: DialogManager,
+            self,
+            message: Message,
+            dialog: ManagedDialogProto,
+            manager: DialogManager,
     ):
         if message.content_type != ContentType.TEXT:
             return False
@@ -50,7 +55,6 @@ def managed(self, manager: DialogManager):
 
 
 class ManagedTextInputAdapter(ManagedWidgetAdapter[TextInput[T]], Generic[T]):
-
     def get_value(self, manager: Optional[DialogManager] = None) -> T:
         manager_deprecated(manager)
         return self.widget.get_value(self.manager)
diff --git a/aiogram_dialog/widgets/kbd/__init__.py b/aiogram_dialog/widgets/kbd/__init__.py
index 8b814eab..28ddd30d 100644
--- a/aiogram_dialog/widgets/kbd/__init__.py
+++ b/aiogram_dialog/widgets/kbd/__init__.py
@@ -1,26 +1,46 @@
 from .base import Keyboard
-from .button import Button, Url
+from .button import Button, Url, WebApp
 from .calendar_kbd import Calendar, ManagedCalendarAdapter
 from .checkbox import Checkbox, ManagedCheckboxAdapter
 from .counter import Counter, ManagedCounterAdapter
-from .group import Group, Row, Column
-from .scrolling_group import ScrollingGroup, ManagedScrollingGroupAdapter
+from .group import Column, Group, Row
+from .list_group import ListGroup, ManagedListGroupAdapter
+from .scrolling_group import ManagedScrollingGroupAdapter, ScrollingGroup
 from .select import (
-    Select, Radio, Multiselect, ManagedMultiSelectAdapter, ManagedRadioAdapter,
+    ManagedMultiSelectAdapter,
+    ManagedRadioAdapter,
+    Multiselect,
+    Radio,
+    Select,
 )
-from .list_group import ListGroup, ManagedListGroupAdapter
 from .state import Back, Cancel, Next, Start, SwitchTo
 
 __all__ = [
     "Keyboard",
-    "Button", "Url",
-    "Calendar", "ManagedCalendarAdapter",
-    "Counter", "ManagedCounterAdapter",
-    "Back", "Cancel", "Next", "Start", "SwitchTo",
-    "Group", "Row", "Column",
-    "ScrollingGroup", "ManagedScrollingGroupAdapter",
-    "Checkbox", "ManagedCheckboxAdapter",
-    "Select", "Radio", "Multiselect",
-    "ManagedMultiSelectAdapter", "ManagedRadioAdapter",
-    "ListGroup", "ManagedListGroupAdapter"
+    "Button",
+    "Url",
+    "WebApp",
+    "Calendar",
+    "ManagedCalendarAdapter",
+    "Counter",
+    "ManagedCounterAdapter",
+    "Back",
+    "Cancel",
+    "Next",
+    "Start",
+    "SwitchTo",
+    "Group",
+    "Row",
+    "Column",
+    "ScrollingGroup",
+    "ManagedScrollingGroupAdapter",
+    "Checkbox",
+    "ManagedCheckboxAdapter",
+    "Select",
+    "Radio",
+    "Multiselect",
+    "ManagedMultiSelectAdapter",
+    "ManagedRadioAdapter",
+    "ListGroup",
+    "ManagedListGroupAdapter",
 ]
diff --git a/aiogram_dialog/widgets/kbd/base.py b/aiogram_dialog/widgets/kbd/base.py
index e792d905..d6fecbe3 100644
--- a/aiogram_dialog/widgets/kbd/base.py
+++ b/aiogram_dialog/widgets/kbd/base.py
@@ -1,10 +1,10 @@
 from typing import List, Optional, Union
 
-from aiogram.types import InlineKeyboardButton, CallbackQuery
+from aiogram.types import CallbackQuery, InlineKeyboardButton
 
 from aiogram_dialog.manager.manager import DialogManager, ManagedDialogProto
 from aiogram_dialog.widgets.action import Actionable
-from aiogram_dialog.widgets.when import WhenCondition, Whenable
+from aiogram_dialog.widgets.when import Whenable, WhenCondition
 
 
 class Keyboard(Actionable, Whenable):
@@ -13,7 +13,9 @@ def __init__(self, id: Optional[str] = None, when: WhenCondition = None):
         Whenable.__init__(self, when)
 
     async def render_keyboard(
-            self, data, manager: DialogManager,
+            self,
+            data,
+            manager: DialogManager,
     ) -> List[List[InlineKeyboardButton]]:
         """
         Render keyboard if needed.
@@ -26,7 +28,9 @@ async def render_keyboard(
         return await self._render_keyboard(data, manager)
 
     async def _render_keyboard(
-            self, data, manager: DialogManager,
+            self,
+            data,
+            manager: DialogManager,
     ) -> List[List[InlineKeyboardButton]]:
         raise NotImplementedError
 
@@ -36,44 +40,58 @@ def callback_prefix(self):
         return f"{self.widget_id}:"
 
     def _own_callback_data(self):
-        """Callback data for only button in widget"""
+        """Create callback data for only button in widget."""
         return self.widget_id
 
     def _item_callback_data(self, data: Union[str, int]):
-        """Callback data for widgets button if multiple"""
+        """Create callback data for widgets button if multiple."""
         return f"{self.callback_prefix()}{data}"
 
     async def process_callback(
-            self, c: CallbackQuery,
-            dialog: ManagedDialogProto, manager: DialogManager,
+            self,
+            c: CallbackQuery,
+            dialog: ManagedDialogProto,
+            manager: DialogManager,
     ) -> bool:
         if c.data == self.widget_id:
             return await self._process_own_callback(
-                c, dialog, manager,
+                c,
+                dialog,
+                manager,
             )
         prefix = self.callback_prefix()
         if prefix and c.data.startswith(prefix):
             return await self._process_item_callback(
-                c, c.data[len(prefix):], dialog, manager,
+                c,
+                c.data[len(prefix):],
+                dialog,
+                manager,
             )
         return await self._process_other_callback(c, dialog, manager)
 
     async def _process_own_callback(
-            self, c: CallbackQuery, dialog: ManagedDialogProto,
+            self,
+            c: CallbackQuery,
+            dialog: ManagedDialogProto,
             manager: DialogManager,
     ) -> bool:
-        """Process callback related to _own_callback_data"""
+        """Process callback related to _own_callback_data."""
         return False
 
     async def _process_item_callback(
-            self, c: CallbackQuery, data: str, dialog: ManagedDialogProto,
+            self,
+            c: CallbackQuery,
+            data: str,
+            dialog: ManagedDialogProto,
             manager: DialogManager,
     ) -> bool:
-        """Process callback related to _item_callback_data"""
+        """Process callback related to _item_callback_data."""
         return False
 
     async def _process_other_callback(
-            self, c: CallbackQuery, dialog: ManagedDialogProto,
+            self,
+            c: CallbackQuery,
+            dialog: ManagedDialogProto,
             manager: DialogManager,
     ) -> bool:
         """
diff --git a/aiogram_dialog/widgets/kbd/button.py b/aiogram_dialog/widgets/kbd/button.py
index fc6d3edf..7c661d6d 100644
--- a/aiogram_dialog/widgets/kbd/button.py
+++ b/aiogram_dialog/widgets/kbd/button.py
@@ -1,10 +1,13 @@
-from typing import List, Callable, Optional, Union, Dict, Awaitable
+from typing import Awaitable, Callable, Dict, List, Optional, Union
 
-from aiogram.types import InlineKeyboardButton, CallbackQuery, WebAppInfo
+from aiogram.types import CallbackQuery, InlineKeyboardButton, WebAppInfo
 
-from aiogram_dialog.manager.manager import DialogManager,ManagedDialogProto
+from aiogram_dialog.manager.manager import DialogManager, ManagedDialogProto
 from aiogram_dialog.widgets.text import Text
-from aiogram_dialog.widgets.widget_event import WidgetEventProcessor, ensure_event_processor
+from aiogram_dialog.widgets.widget_event import (
+    ensure_event_processor,
+    WidgetEventProcessor,
+)
 from .base import Keyboard
 from ..when import WhenCondition
 
@@ -12,47 +15,71 @@
 
 
 class Button(Keyboard):
-    def __init__(self, text: Text, id: str,
-                 on_click: Union[OnClick, WidgetEventProcessor, None] = None,
-                 when: WhenCondition = None):
+    def __init__(
+            self,
+            text: Text,
+            id: str,
+            on_click: Union[OnClick, WidgetEventProcessor, None] = None,
+            when: WhenCondition = None,
+    ):
         super().__init__(id, when)
         self.text = text
         self.on_click = ensure_event_processor(on_click)
 
     async def _process_own_callback(
-            self, c: CallbackQuery, dialog: ManagedDialogProto,
+            self,
+            c: CallbackQuery,
+            dialog: ManagedDialogProto,
             manager: DialogManager,
     ) -> bool:
         await self.on_click.process_event(c, self, manager)
         return True
 
-    async def _render_keyboard(self, data: Dict, manager: DialogManager) -> List[List[InlineKeyboardButton]]:
-        return [[
-            InlineKeyboardButton(
-                text=await self.text.render_text(data, manager),
-                callback_data=self._own_callback_data()
-            )
-        ]]
+    async def _render_keyboard(
+            self,
+            data: Dict,
+            manager: DialogManager,
+    ) -> List[List[InlineKeyboardButton]]:
+        return [
+            [
+                InlineKeyboardButton(
+                    text=await self.text.render_text(data, manager),
+                    callback_data=self._own_callback_data(),
+                ),
+            ],
+        ]
 
 
 class Url(Keyboard):
-    def __init__(self, text: Text, url: Text, id: Optional[str] = None, when: Union[str, Callable, None] = None):
+    def __init__(
+            self,
+            text: Text,
+            url: Text,
+            id: Optional[str] = None,
+            when: Union[str, Callable, None] = None,
+    ):
         super().__init__(id, when)
         self.text = text
         self.url = url
 
-    async def _render_keyboard(self, data: Dict, manager: DialogManager) -> List[List[InlineKeyboardButton]]:
-        return [[
-            InlineKeyboardButton(
-                text=await self.text.render_text(data, manager),
-                url=await self.url.render_text(data, manager)
-            )
-        ]]
+    async def _render_keyboard(
+            self,
+            data: Dict,
+            manager: DialogManager,
+    ) -> List[List[InlineKeyboardButton]]:
+        return [
+            [
+                InlineKeyboardButton(
+                    text=await self.text.render_text(data, manager),
+                    url=await self.url.render_text(data, manager),
+                ),
+            ],
+        ]
 
 
 class WebApp(Url):
     async def _render_keyboard(
-        self, data: Dict, manager: DialogManager
+            self, data: Dict, manager: DialogManager,
     ) -> List[List[InlineKeyboardButton]]:
         text = await self.text.render_text(data, manager)
 
diff --git a/aiogram_dialog/widgets/kbd/calendar_kbd.py b/aiogram_dialog/widgets/kbd/calendar_kbd.py
index c776de07..e14526df 100644
--- a/aiogram_dialog/widgets/kbd/calendar_kbd.py
+++ b/aiogram_dialog/widgets/kbd/calendar_kbd.py
@@ -1,20 +1,23 @@
-from abc import ABC
 from calendar import monthcalendar
 from datetime import date
 from time import mktime
-from typing import List, Callable, Union, Awaitable, TypedDict, Optional
+from typing import Awaitable, Callable, List, Optional, TypedDict, Union
 
-from aiogram.types import InlineKeyboardButton, CallbackQuery
+from aiogram.types import CallbackQuery, InlineKeyboardButton
 
 from aiogram_dialog.context.events import ChatEvent
 from aiogram_dialog.manager.protocols import DialogManager, ManagedDialogProto
-from aiogram_dialog.widgets.widget_event import WidgetEventProcessor, \
-    ensure_event_processor
+from aiogram_dialog.widgets.widget_event import (
+    ensure_event_processor,
+    WidgetEventProcessor,
+)
 from .base import Keyboard
 from ..managed import ManagedWidgetAdapter
 from ...deprecation_utils import manager_deprecated
 
-OnDateSelected = Callable[[ChatEvent, "ManagedCalendarAdapter", DialogManager, date], Awaitable]
+OnDateSelected = Callable[
+    [ChatEvent, "ManagedCalendarAdapter", DialogManager, date], Awaitable,
+]
 
 # Constants for managing widget rendering scope
 SCOPE_DAYS = "SCOPE_DAYS"
@@ -37,17 +40,19 @@ class CalendarData(TypedDict):
     current_offset: str
 
 
-class Calendar(Keyboard, ABC):
-    def __init__(self,
-                 id: str,
-                 on_click: Union[OnDateSelected, WidgetEventProcessor, None] = None,
-                 when: Union[str, Callable] = None):
+class Calendar(Keyboard):
+    def __init__(
+            self,
+            id: str,
+            on_click: Union[OnDateSelected, WidgetEventProcessor, None] = None,
+            when: Union[str, Callable] = None,
+    ):
         super().__init__(id, when)
         self.on_click = ensure_event_processor(on_click)
 
-    async def _render_keyboard(self,
-                              data,
-                              manager: DialogManager) -> List[List[InlineKeyboardButton]]:
+    async def _render_keyboard(
+            self, data, manager: DialogManager,
+    ) -> List[List[InlineKeyboardButton]]:
         offset = self.get_offset(manager)
         current_scope = self.get_scope(manager)
 
@@ -59,7 +64,10 @@ async def _render_keyboard(self,
             return self.years_kbd(offset)
 
     async def _process_item_callback(
-            self, c: CallbackQuery, data: str, dialog: ManagedDialogProto,
+            self,
+            c: CallbackQuery,
+            data: str,
+            dialog: ManagedDialogProto,
             manager: DialogManager,
     ) -> bool:
         current_offset = self.get_offset(manager)
@@ -77,7 +85,9 @@ async def _process_item_callback(
                 new_offset = date(current_offset.year - 1, 12, 1)
                 self.set_offset(new_offset, manager)
             else:
-                new_offset = date(current_offset.year, (current_offset.month - 1), 1)
+                new_offset = date(
+                    current_offset.year, (current_offset.month - 1), 1,
+                )
                 self.set_offset(new_offset, manager)
 
         elif data in [SCOPE_MONTHS, SCOPE_YEARS]:
@@ -98,7 +108,9 @@ async def _process_item_callback(
         else:
             raw_date = int(data)
             await self.on_click.process_event(
-                c, self.managed(manager), manager,
+                c,
+                self.managed(manager),
+                manager,
                 date.fromtimestamp(raw_date),
             )
         return True
@@ -108,10 +120,14 @@ def years_kbd(self, offset) -> List[List[InlineKeyboardButton]]:
         for n in range(offset.year - 7, offset.year + 7, 3):
             year_row = []
             for year in range(n, n + 3):
-                year_row.append(InlineKeyboardButton(
-                    text=str(year),
-                    callback_data=self._item_callback_data(f"{PREFIX_YEAR}{year}")
-                ))
+                year_row.append(
+                    InlineKeyboardButton(
+                        text=str(year),
+                        callback_data=self._item_callback_data(
+                            f"{PREFIX_YEAR}{year}",
+                        ),
+                    ),
+                )
             years.append(year_row)
         return years
 
@@ -122,9 +138,13 @@ def months_kbd(self, offset) -> List[List[InlineKeyboardButton]]:
             season = []
             for month in n:
                 month_text = date(offset.year, month, 1).strftime("%B")
-                season.append(InlineKeyboardButton(
-                    text=month_text,
-                    callback_data=self._item_callback_data(f"{PREFIX_MONTH}{month}"))
+                season.append(
+                    InlineKeyboardButton(
+                        text=month_text,
+                        callback_data=self._item_callback_data(
+                            f"{PREFIX_MONTH}{month}",
+                        ),
+                    ),
                 )
             months.append(season)
         return [
@@ -134,7 +154,7 @@ def months_kbd(self, offset) -> List[List[InlineKeyboardButton]]:
                     callback_data=self._item_callback_data(SCOPE_YEARS),
                 ),
             ],
-            *months
+            *months,
         ]
 
     def days_kbd(self, offset) -> List[List[InlineKeyboardButton]]:
@@ -148,16 +168,24 @@ def days_kbd(self, offset) -> List[List[InlineKeyboardButton]]:
             week_row = []
             for day in week:
                 if day == 0:
-                    week_row.append(InlineKeyboardButton(
-                        text=" ",
-                        callback_data=" ",
-                    ))
+                    week_row.append(
+                        InlineKeyboardButton(
+                            text=" ",
+                            callback_data=" ",
+                        ),
+                    )
                 else:
-                    raw_date = int(mktime(date(offset.year, offset.month, day).timetuple()))
-                    week_row.append(InlineKeyboardButton(
-                        text=str(day),
-                        callback_data=self._item_callback_data(raw_date),
-                    ))
+                    raw_date = int(
+                        mktime(
+                            date(offset.year, offset.month, day).timetuple(),
+                        ),
+                    )
+                    week_row.append(
+                        InlineKeyboardButton(
+                            text=str(day),
+                            callback_data=self._item_callback_data(raw_date),
+                        ),
+                    )
             days.append(week_row)
         return [
             [
@@ -217,12 +245,14 @@ def get_offset(self, manager: Optional[DialogManager] = None) -> date:
         manager_deprecated(manager)
         return self.widget.get_offset(self.manager)
 
-    def set_offset(self, new_offset: date,
-                   manager: Optional[DialogManager] = None) -> None:
+    def set_offset(
+            self, new_offset: date, manager: Optional[DialogManager] = None,
+    ) -> None:
         manager_deprecated(manager)
         return self.widget.set_offset(new_offset, self.manager)
 
-    def set_scope(self, new_scope: str,
-                  manager: Optional[DialogManager] = None) -> None:
+    def set_scope(
+            self, new_scope: str, manager: Optional[DialogManager] = None,
+    ) -> None:
         manager_deprecated(manager)
         return self.widget.set_scope(new_scope, self.manager)
diff --git a/aiogram_dialog/widgets/kbd/checkbox.py b/aiogram_dialog/widgets/kbd/checkbox.py
index 2e097e25..47af3b4b 100644
--- a/aiogram_dialog/widgets/kbd/checkbox.py
+++ b/aiogram_dialog/widgets/kbd/checkbox.py
@@ -1,50 +1,64 @@
 from abc import ABC, abstractmethod
-from typing import Callable, Optional, Union, Dict, Awaitable, List
+from typing import Awaitable, Callable, Dict, List, Optional, Union
 
 from aiogram.types import CallbackQuery, InlineKeyboardButton
 
 from aiogram_dialog.context.events import ChatEvent
 from aiogram_dialog.manager.protocols import DialogManager, ManagedDialogProto
-from aiogram_dialog.widgets.text import Text, Case
+from aiogram_dialog.widgets.text import Case, Text
 from aiogram_dialog.widgets.widget_event import (
-    WidgetEventProcessor, ensure_event_processor,
+    ensure_event_processor,
+    WidgetEventProcessor,
 )
 from .base import Keyboard
 from ..managed import ManagedWidgetAdapter
 from ...deprecation_utils import manager_deprecated
 
 OnStateChanged = Callable[
-    [ChatEvent, "ManagedCheckboxAdapter", DialogManager], Awaitable]
+    [ChatEvent, "ManagedCheckboxAdapter", DialogManager], Awaitable,
+]
+OnStateChangedVariant = Union[
+    OnStateChanged, WidgetEventProcessor, None,
+]
 
 
 class BaseCheckbox(Keyboard, ABC):
-    def __init__(self, checked_text: Text, unchecked_text: Text,
-                 id: str,
-                 on_click: Union[
-                     OnStateChanged, WidgetEventProcessor, None] = None,
-                 on_state_changed: Union[
-                     OnStateChanged, WidgetEventProcessor, None] = None,
-                 when: Union[str, Callable] = None):
+    def __init__(
+            self,
+            checked_text: Text,
+            unchecked_text: Text,
+            id: str,
+            on_click: OnStateChangedVariant = None,
+            on_state_changed: OnStateChangedVariant = None,
+            when: Union[str, Callable] = None,
+    ):
         super().__init__(id, when)
-        self.text = Case({True: checked_text, False: unchecked_text},
-                         selector=self._is_text_checked)
+        self.text = Case(
+            {True: checked_text, False: unchecked_text},
+            selector=self._is_text_checked,
+        )
         self.on_click = ensure_event_processor(on_click)
         self.on_state_changed = ensure_event_processor(on_state_changed)
 
-    async def _render_keyboard(self, data: Dict,
-                               manager: DialogManager) -> List[
-        List[InlineKeyboardButton]]:
+    async def _render_keyboard(
+            self, data: Dict, manager: DialogManager,
+    ) -> List[List[InlineKeyboardButton]]:
         checked = int(self.is_checked(manager))
         # store current checked status in callback data
-        return [[
-            InlineKeyboardButton(
-                text=await self.text.render_text(data, manager),
-                callback_data=self._item_callback_data(checked)
-            )
-        ]]
+        return [
+            [
+                InlineKeyboardButton(
+                    text=await self.text.render_text(data, manager),
+                    callback_data=self._item_callback_data(checked),
+                ),
+            ],
+        ]
 
     async def _process_item_callback(
-            self, c: CallbackQuery, data: str, dialog: ManagedDialogProto,
+            self,
+            c: CallbackQuery,
+            data: str,
+            dialog: ManagedDialogProto,
             manager: DialogManager,
     ) -> bool:
         # remove prefix and cast "0" as False, "1" as True
@@ -53,8 +67,9 @@ async def _process_item_callback(
         await self.set_checked(c, not checked, manager)
         return True
 
-    def _is_text_checked(self, data: Dict, case: Case,
-                         manager: DialogManager) -> bool:
+    def _is_text_checked(
+            self, data: Dict, case: Case, manager: DialogManager,
+    ) -> bool:
         return self.is_checked(manager)
 
     @abstractmethod
@@ -62,29 +77,37 @@ def is_checked(self, manager: DialogManager) -> bool:
         raise NotImplementedError
 
     @abstractmethod
-    async def set_checked(self, event: ChatEvent, checked: bool,
-                          manager: DialogManager):
+    async def set_checked(
+            self, event: ChatEvent, checked: bool, manager: DialogManager,
+    ):
         raise NotImplementedError
 
 
 class Checkbox(BaseCheckbox):
-    def __init__(self, checked_text: Text, unchecked_text: Text, id: str,
-                 on_click: Union[OnStateChanged, WidgetEventProcessor, None] = None,
-                 on_state_changed: Optional[OnStateChanged] = None,
-                 default: bool = False,
-                 when: Union[str, Callable] = None):
-        super().__init__(checked_text, unchecked_text, id, on_click, on_state_changed,
-                         when)
+    def __init__(
+            self,
+            checked_text: Text,
+            unchecked_text: Text,
+            id: str,
+            on_click: Union[OnStateChanged, WidgetEventProcessor, None] = None,
+            on_state_changed: Optional[OnStateChanged] = None,
+            default: bool = False,
+            when: Union[str, Callable] = None,
+    ):
+        super().__init__(
+            checked_text, unchecked_text, id, on_click, on_state_changed, when,
+        )
         self.default = default
 
     def is_checked(self, manager: DialogManager) -> bool:
         return self.get_widget_data(manager, self.default)
 
-    async def set_checked(self, event: ChatEvent, checked: bool,
-                          manager: DialogManager) -> None:
+    async def set_checked(
+            self, event: ChatEvent, checked: bool, manager: DialogManager,
+    ) -> None:
         self.set_widget_data(manager, checked)
         await self.on_state_changed.process_event(
-            event, self.managed(manager), manager
+            event, self.managed(manager), manager,
         )
 
     def managed(self, manager: DialogManager):
@@ -96,9 +119,11 @@ def is_checked(self, manager: Optional[DialogManager] = None) -> bool:
         manager_deprecated(manager)
         return self.widget.is_checked(self.manager)
 
-    async def set_checked(self, event: ChatEvent, checked: bool,
-                          manager: Optional[DialogManager] = None) -> None:
+    async def set_checked(
+            self,
+            event: ChatEvent,
+            checked: bool,
+            manager: Optional[DialogManager] = None,
+    ) -> None:
         manager_deprecated(manager)
-        return await self.widget.set_checked(
-            event, checked, self.manager
-        )
+        return await self.widget.set_checked(event, checked, self.manager)
diff --git a/aiogram_dialog/widgets/kbd/counter.py b/aiogram_dialog/widgets/kbd/counter.py
index 1ed0abe5..c8ce3e06 100644
--- a/aiogram_dialog/widgets/kbd/counter.py
+++ b/aiogram_dialog/widgets/kbd/counter.py
@@ -1,6 +1,6 @@
-from typing import List, Dict, Optional, Callable, Awaitable, Union
+from typing import Awaitable, Callable, Dict, List, Optional, Union
 
-from aiogram.types import InlineKeyboardButton, CallbackQuery
+from aiogram.types import CallbackQuery, InlineKeyboardButton
 
 from aiogram_dialog.context.events import ChatEvent
 from aiogram_dialog.manager.protocols import DialogManager, ManagedDialogProto
@@ -8,8 +8,10 @@
 from aiogram_dialog.widgets.managed import ManagedWidgetAdapter
 from aiogram_dialog.widgets.text import Const, Format, Text
 from aiogram_dialog.widgets.when import WhenCondition
-from aiogram_dialog.widgets.widget_event import WidgetEventProcessor, \
-    ensure_event_processor
+from aiogram_dialog.widgets.widget_event import (
+    ensure_event_processor,
+    WidgetEventProcessor,
+)
 
 OnClick = Callable[
     [ChatEvent, "ManagedCounterAdapter", DialogManager],
@@ -20,19 +22,26 @@
     Awaitable,
 ]
 
+PLUS_TEXT = Const("+")
+MINUS_TEXT = Const("-")
+DEFAULT_COUNTER_TEXT = Format("{value:g}")
+
 
 class Counter(Keyboard):
     def __init__(
-            self, id: str,
-            plus: Optional[Text] = Const("+"),
-            minus: Optional[Text] = Const("-"),
-            text: Optional[Text] = Format("{value:g}"),
-            min_value: float = 0, max_value: float = 999999,
-            increment: float = 1, default: float = 0,
+            self,
+            id: str,
+            plus: Optional[Text] = PLUS_TEXT,
+            minus: Optional[Text] = MINUS_TEXT,
+            text: Optional[Text] = DEFAULT_COUNTER_TEXT,
+            min_value: float = 0,
+            max_value: float = 999999,
+            increment: float = 1,
+            default: float = 0,
             cycle: bool = False,
             on_click: Union[OnClick, WidgetEventProcessor, None] = None,
             on_value_changed: Union[
-                OnValueChanged, WidgetEventProcessor, None
+                OnValueChanged, WidgetEventProcessor, None,
             ] = None,
             when: WhenCondition = None,
     ):
@@ -50,42 +59,58 @@ def __init__(
 
     def get_value(self, manager: DialogManager) -> float:
         return manager.current_context().widget_data.get(
-            self.widget_id, self.default
+            self.widget_id, self.default,
         )
 
     async def set_value(self, manager: DialogManager, value: float) -> None:
         if self.min <= value <= self.max:
             manager.current_context().widget_data[self.widget_id] = value
             await self.on_value_changed.process_event(
-                manager.event, self.managed(manager), manager,
+                manager.event,
+                self.managed(manager),
+                manager,
             )
 
     async def _render_keyboard(
-            self, data: Dict, manager: DialogManager,
+            self,
+            data: Dict,
+            manager: DialogManager,
     ) -> List[List[InlineKeyboardButton]]:
         row = []
         if self.minus:
             minus = await self.minus.render_text(data, manager)
-            row.append(InlineKeyboardButton(
-                text=minus, callback_data=self._item_callback_data("-"),
-            ))
+            row.append(
+                InlineKeyboardButton(
+                    text=minus,
+                    callback_data=self._item_callback_data("-"),
+                ),
+            )
         if self.text:
             text = await self.text.render_text(
                 {"value": self.get_value(manager), "data": data},
                 manager,
             )
-            row.append(InlineKeyboardButton(
-                text=text, callback_data=self._item_callback_data(""),
-            ))
+            row.append(
+                InlineKeyboardButton(
+                    text=text,
+                    callback_data=self._item_callback_data(""),
+                ),
+            )
         if self.plus:
             plus = await self.plus.render_text(data, manager)
-            row.append(InlineKeyboardButton(
-                text=plus, callback_data=self._item_callback_data("+"),
-            ))
+            row.append(
+                InlineKeyboardButton(
+                    text=plus,
+                    callback_data=self._item_callback_data("+"),
+                ),
+            )
         return [row]
 
     async def _process_item_callback(
-            self, c: CallbackQuery, data: str, dialog: ManagedDialogProto,
+            self,
+            c: CallbackQuery,
+            data: str,
+            dialog: ManagedDialogProto,
             manager: DialogManager,
     ) -> bool:
         await self.on_click.process_event(c, self.managed(manager), manager)
@@ -111,7 +136,6 @@ def managed(self, manager: DialogManager):
 
 
 class ManagedCounterAdapter(ManagedWidgetAdapter[Counter]):
-
     def get_value(self) -> float:
         return self.widget.get_value(self.manager)
 
diff --git a/aiogram_dialog/widgets/kbd/group.py b/aiogram_dialog/widgets/kbd/group.py
index 13fcc118..9f889310 100644
--- a/aiogram_dialog/widgets/kbd/group.py
+++ b/aiogram_dialog/widgets/kbd/group.py
@@ -1,7 +1,7 @@
 from itertools import chain
-from typing import List, Dict, Optional, Iterable
+from typing import Dict, Iterable, List, Optional
 
-from aiogram.types import InlineKeyboardButton, CallbackQuery
+from aiogram.types import CallbackQuery, InlineKeyboardButton
 
 from aiogram_dialog.manager.manager import DialogManager, ManagedDialogProto
 from .base import Keyboard
@@ -9,8 +9,13 @@
 
 
 class Group(Keyboard):
-    def __init__(self, *buttons: Keyboard, id: Optional[str] = None, width: int = None,
-                 when: WhenCondition = None):
+    def __init__(
+            self,
+            *buttons: Keyboard,
+            id: Optional[str] = None,
+            width: int = None,
+            when: WhenCondition = None,
+    ):
         super().__init__(id, when)
         self.buttons = buttons
         self.width = width
@@ -25,7 +30,11 @@ def find(self, widget_id):
                 return widget
         return None
 
-    async def _render_keyboard(self, data: Dict, manager: DialogManager) -> List[List[InlineKeyboardButton]]:
+    async def _render_keyboard(
+            self,
+            data: Dict,
+            manager: DialogManager,
+    ) -> List[List[InlineKeyboardButton]]:
         kbd: List[List[InlineKeyboardButton]] = []
         for b in self.buttons:
             b_kbd = await b.render_keyboard(data, manager)
@@ -40,7 +49,8 @@ async def _render_keyboard(self, data: Dict, manager: DialogManager) -> List[Lis
         return kbd
 
     def _wrap_kbd(
-            self, kbd: Iterable[InlineKeyboardButton],
+            self,
+            kbd: Iterable[InlineKeyboardButton],
     ) -> List[List[InlineKeyboardButton]]:
         res: List[List[InlineKeyboardButton]] = []
         row: List[InlineKeyboardButton] = []
@@ -54,7 +64,9 @@ def _wrap_kbd(
         return res
 
     async def _process_other_callback(
-            self, c: CallbackQuery, dialog: ManagedDialogProto,
+            self,
+            c: CallbackQuery,
+            dialog: ManagedDialogProto,
             manager: DialogManager,
     ) -> bool:
         for b in self.buttons:
@@ -64,10 +76,22 @@ async def _process_other_callback(
 
 
 class Row(Group):
-    def __init__(self, *buttons: Keyboard, id: Optional[str] = None, when: WhenCondition = None):
-        super().__init__(*buttons, id=id, width=9999, when=when)  # telegram doe not allow even 100 columns
+    def __init__(
+            self,
+            *buttons: Keyboard,
+            id: Optional[str] = None,
+            when: WhenCondition = None,
+    ):
+        super().__init__(
+            *buttons, id=id, width=9999, when=when,
+        )  # telegram doe not allow even 100 columns
 
 
 class Column(Group):
-    def __init__(self, *buttons: Keyboard, id: Optional[str] = None, when: WhenCondition = None):
+    def __init__(
+            self,
+            *buttons: Keyboard,
+            id: Optional[str] = None,
+            when: WhenCondition = None,
+    ):
         super().__init__(*buttons, id=id, when=when, width=1)
diff --git a/aiogram_dialog/widgets/kbd/list_group.py b/aiogram_dialog/widgets/kbd/list_group.py
index dbca1b40..c465c654 100644
--- a/aiogram_dialog/widgets/kbd/list_group.py
+++ b/aiogram_dialog/widgets/kbd/list_group.py
@@ -1,12 +1,15 @@
 import dataclasses
 from operator import itemgetter
-from typing import List, Dict, Optional, Union, Sequence, Callable, Any
+from typing import Any, Callable, Dict, List, Optional, Sequence, Union
 
-from aiogram.types import InlineKeyboardButton, CallbackQuery, Message
+from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
 
 from aiogram_dialog.manager.protocols import (
-    DialogManager, Context, ManagedDialogAdapterProto, NewMessage,
+    Context,
+    DialogManager,
+    ManagedDialogAdapterProto,
     ManagedDialogProto,
+    NewMessage,
 )
 from .base import Keyboard
 from ..managed import ManagedWidgetAdapter
@@ -66,7 +69,8 @@ def identity(data) -> Sequence:
 
 class ListGroup(Keyboard):
     def __init__(
-            self, *buttons: Keyboard,
+            self,
+            *buttons: Keyboard,
             id: Optional[str] = None,
             item_id_getter: ItemIdGetter,
             items: Union[str, Sequence],
@@ -81,7 +85,7 @@ def __init__(
             self.items_getter = get_identity(items)
 
     async def _render_keyboard(
-            self, data: Dict, manager: DialogManager
+            self, data: Dict, manager: DialogManager,
     ) -> List[List[InlineKeyboardButton]]:
         kbd: List[List[InlineKeyboardButton]] = []
         for pos, item in enumerate(self.items_getter(data)):
@@ -89,7 +93,11 @@ async def _render_keyboard(
         return kbd
 
     async def _render_item(
-            self, pos: int, item: Any, data: Dict, manager: DialogManager,
+            self,
+            pos: int,
+            item: Any,
+            data: Dict,
+            manager: DialogManager,
     ) -> List[List[InlineKeyboardButton]]:
         kbd: List[List[InlineKeyboardButton]] = []
         data = {"data": data, "item": item, "pos": pos + 1, "pos0": pos}
@@ -101,13 +109,13 @@ async def _render_item(
                 for btn in row:
                     if btn.callback_data:
                         btn.callback_data = self._item_callback_data(
-                            f"{item_id}:{btn.callback_data}"
+                            f"{item_id}:{btn.callback_data}",
                         )
             kbd.extend(b_kbd)
         return kbd
 
     def find_for_item(
-            self, manager: DialogManager, widget_id: str, item_id: str
+            self, manager: DialogManager, widget_id: str, item_id: str,
     ) -> Optional[Keyboard]:
         for btn in self.buttons:
             widget = btn.find(widget_id)
@@ -116,7 +124,10 @@ def find_for_item(
         return None
 
     async def _process_item_callback(
-            self, c: CallbackQuery, data: str, dialog: ManagedDialogProto,
+            self,
+            c: CallbackQuery,
+            data: str,
+            dialog: ManagedDialogProto,
             manager: DialogManager,
     ) -> bool:
         item_id, callback_data = data.split(":", maxsplit=1)
@@ -133,12 +144,14 @@ def managed(self, manager: DialogManager):
 
 
 class ManagedListGroupAdapter(ManagedWidgetAdapter[ListGroup]):
-    def find_for_item(
-            self, widget_id: str, item_id: str
-    ) -> Optional[Any]:
+    def find_for_item(self, widget_id: str, item_id: str) -> Optional[Any]:
         widget = self.widget.find_for_item(self.manager, widget_id, item_id)
         if widget:
-            return widget.managed(SubManager(
-                self.manager, self.widget.widget_id, item_id,
-            ))
+            return widget.managed(
+                SubManager(
+                    self.manager,
+                    self.widget.widget_id,
+                    item_id,
+                ),
+            )
         return None
diff --git a/aiogram_dialog/widgets/kbd/scrolling_group.py b/aiogram_dialog/widgets/kbd/scrolling_group.py
index 960e4caa..7802856a 100644
--- a/aiogram_dialog/widgets/kbd/scrolling_group.py
+++ b/aiogram_dialog/widgets/kbd/scrolling_group.py
@@ -1,11 +1,14 @@
-from typing import List, Dict, Optional, Callable, Awaitable, Union
+from typing import Awaitable, Callable, Dict, List, Optional, Union
 
-from aiogram.types import InlineKeyboardButton, CallbackQuery
+from aiogram.types import CallbackQuery, InlineKeyboardButton
 
+from aiogram_dialog.context.events import ChatEvent
 from aiogram_dialog.deprecation_utils import manager_deprecated
 from aiogram_dialog.manager.protocols import DialogManager, ManagedDialogProto
-from aiogram_dialog.context.events import ChatEvent
-from aiogram_dialog.widgets.widget_event import WidgetEventProcessor, ensure_event_processor
+from aiogram_dialog.widgets.widget_event import (
+    ensure_event_processor,
+    WidgetEventProcessor,
+)
 from .base import Keyboard
 from .group import Group
 from ..managed import ManagedWidgetAdapter
@@ -18,15 +21,25 @@
 
 
 class ScrollingGroup(Group):
-    def __init__(self, *buttons: Keyboard, id: str, width: Optional[int] = None,
-                 height: int = 0, when: WhenCondition = None,
-                 on_page_changed: Union[OnStateChanged, WidgetEventProcessor, None] = None):
+    def __init__(
+            self,
+            *buttons: Keyboard,
+            id: str,
+            width: Optional[int] = None,
+            height: int = 0,
+            when: WhenCondition = None,
+            on_page_changed: Union[
+                OnStateChanged, WidgetEventProcessor, None,
+            ] = None,
+    ):
         super().__init__(*buttons, id=id, width=width, when=when)
         self.height = height
         self.on_page_changed = ensure_event_processor(on_page_changed)
 
     async def _render_keyboard(
-            self, data: Dict, manager: DialogManager,
+            self,
+            data: Dict,
+            manager: DialogManager,
     ) -> List[List[InlineKeyboardButton]]:
         kbd = await super()._render_keyboard(data, manager)
         pages = len(kbd) // self.height + bool(len(kbd) % self.height)
@@ -36,30 +49,37 @@ async def _render_keyboard(
         current_page = min(last_page, self.get_page(manager))
         next_page = min(last_page, current_page + 1)
         prev_page = max(0, current_page - 1)
-        pager = [[
-            InlineKeyboardButton(
-                text="1", callback_data=self._item_callback_data("0")
-            ),
-            InlineKeyboardButton(
-                text="<", callback_data=self._item_callback_data(prev_page),
-            ),
-            InlineKeyboardButton(
-                text=str(current_page + 1),
-                callback_data=self._item_callback_data(current_page),
-            ),
-            InlineKeyboardButton(
-                text=">", callback_data=self._item_callback_data(next_page),
-            ),
-            InlineKeyboardButton(
-                text=str(last_page + 1),
-                callback_data=self._item_callback_data(last_page),
-            ),
-        ]]
+        pager = [
+            [
+                InlineKeyboardButton(
+                    text="1", callback_data=self._item_callback_data("0"),
+                ),
+                InlineKeyboardButton(
+                    text="<",
+                    callback_data=self._item_callback_data(prev_page),
+                ),
+                InlineKeyboardButton(
+                    text=str(current_page + 1),
+                    callback_data=self._item_callback_data(current_page),
+                ),
+                InlineKeyboardButton(
+                    text=">",
+                    callback_data=self._item_callback_data(next_page),
+                ),
+                InlineKeyboardButton(
+                    text=str(last_page + 1),
+                    callback_data=self._item_callback_data(last_page),
+                ),
+            ],
+        ]
         page_offset = current_page * self.height
-        return kbd[page_offset:page_offset + self.height] + pager
+        return kbd[page_offset: page_offset + self.height] + pager
 
     async def _process_item_callback(
-            self, c: CallbackQuery, data: str, dialog: ManagedDialogProto,
+            self,
+            c: CallbackQuery,
+            data: str,
+            dialog: ManagedDialogProto,
             manager: DialogManager,
     ) -> bool:
         await self.set_page(c, int(data), manager)
@@ -68,11 +88,14 @@ async def _process_item_callback(
     def get_page(self, manager: DialogManager) -> int:
         return manager.current_context().widget_data.get(self.widget_id, 0)
 
-    async def set_page(self, event: ChatEvent, page: int,
-                       manager: DialogManager) -> None:
+    async def set_page(
+            self, event: ChatEvent, page: int, manager: DialogManager,
+    ) -> None:
         manager.current_context().widget_data[self.widget_id] = page
         await self.on_page_changed.process_event(
-            event, self.managed(manager), manager,
+            event,
+            self.managed(manager),
+            manager,
         )
 
     def managed(self, manager: DialogManager):
@@ -84,9 +107,11 @@ def get_page(self, manager: Optional[DialogManager] = None) -> int:
         manager_deprecated(manager)
         return self.widget.get_page(self.manager)
 
-    async def set_page(self, event: ChatEvent, page: int,
-                       manager: Optional[DialogManager] = None) -> None:
+    async def set_page(
+            self,
+            event: ChatEvent,
+            page: int,
+            manager: Optional[DialogManager] = None,
+    ) -> None:
         manager_deprecated(manager)
-        return await self.widget.set_page(
-            event, page, self.manager
-        )
+        return await self.widget.set_page(event, page, self.manager)
diff --git a/aiogram_dialog/widgets/kbd/select.py b/aiogram_dialog/widgets/kbd/select.py
index fb7d9b0d..67560054 100644
--- a/aiogram_dialog/widgets/kbd/select.py
+++ b/aiogram_dialog/widgets/kbd/select.py
@@ -1,14 +1,24 @@
 from abc import ABC, abstractmethod
 from operator import itemgetter
-from typing import Callable, Optional, Union, Dict, Any, List, Awaitable, Sequence
+from typing import (
+    Any,
+    Awaitable,
+    Callable,
+    Dict,
+    List,
+    Optional,
+    Sequence,
+    Union,
+)
 
 from aiogram.types import CallbackQuery, InlineKeyboardButton
 
 from aiogram_dialog.context.events import ChatEvent
 from aiogram_dialog.manager.manager import DialogManager, ManagedDialogProto
-from aiogram_dialog.widgets.text import Text, Case
+from aiogram_dialog.widgets.text import Case, Text
 from aiogram_dialog.widgets.widget_event import (
-    WidgetEventProcessor, ensure_event_processor,
+    ensure_event_processor,
+    WidgetEventProcessor,
 )
 from .base import Keyboard
 from ..managed import ManagedWidgetAdapter
@@ -34,12 +44,15 @@ def identity(data) -> Sequence:
 
 
 class Select(Keyboard):
-    def __init__(self, text: Text,
-                 id: str,
-                 item_id_getter: ItemIdGetter,
-                 items: Union[str, Sequence],
-                 on_click: Union[OnItemClick, WidgetEventProcessor, None] = None,
-                 when: Union[str, Callable] = None):
+    def __init__(
+            self,
+            text: Text,
+            id: str,
+            item_id_getter: ItemIdGetter,
+            items: Union[str, Sequence],
+            on_click: Union[OnItemClick, WidgetEventProcessor, None] = None,
+            when: Union[str, Callable] = None,
+    ):
         super().__init__(id, when)
         self.text = text
         self.on_click = ensure_event_processor(on_click)
@@ -50,66 +63,100 @@ def __init__(self, text: Text,
             self.items_getter = get_identity(items)
 
     async def _render_keyboard(
-            self, data: Dict, manager: DialogManager,
+            self,
+            data: Dict,
+            manager: DialogManager,
     ) -> List[List[InlineKeyboardButton]]:
-        return [[
-            await self._render_button(pos, item, data, manager)
-            for pos, item in enumerate(self.items_getter(data))
-        ]]
-
-    async def _render_button(self, pos: int, item: Any, data: Dict,
-                             manager: DialogManager) -> InlineKeyboardButton:
+        return [
+            [
+                await self._render_button(pos, item, data, manager)
+                for pos, item in enumerate(self.items_getter(data))
+            ],
+        ]
+
+    async def _render_button(
+            self, pos: int, item: Any, data: Dict, manager: DialogManager,
+    ) -> InlineKeyboardButton:
         data = {"data": data, "item": item, "pos": pos + 1, "pos0": pos}
         item_id = self.item_id_getter(item)
         return InlineKeyboardButton(
             text=await self.text.render_text(data, manager),
-            callback_data=self._item_callback_data(item_id)
+            callback_data=self._item_callback_data(item_id),
         )
 
     async def _process_item_callback(
-            self, c: CallbackQuery, data: str, dialog: ManagedDialogProto,
+            self,
+            c: CallbackQuery,
+            data: str,
+            dialog: ManagedDialogProto,
             manager: DialogManager,
     ) -> bool:
         await self.on_click.process_event(
-            c, self.managed(manager), manager, data,
+            c,
+            self.managed(manager),
+            manager,
+            data,
         )
         return True
 
 
 class StatefulSelect(Select, ABC):
-    def __init__(self, checked_text: Text, unchecked_text: Text,
-                 id: str, item_id_getter: ItemIdGetter,
-                 items: Union[str, Sequence],
-                 on_click: Union[OnItemClick, WidgetEventProcessor, None] = None,
-                 on_state_changed: Union[OnItemStateChanged, WidgetEventProcessor, None] = None,
-                 when: Union[str, Callable] = None):
-        text = Case({True: checked_text, False: unchecked_text}, selector=self._is_text_checked)
-        super().__init__(text, id, item_id_getter, items, self._process_click, when)
+    def __init__(
+            self,
+            checked_text: Text,
+            unchecked_text: Text,
+            id: str,
+            item_id_getter: ItemIdGetter,
+            items: Union[str, Sequence],
+            on_click: Union[OnItemClick, WidgetEventProcessor, None] = None,
+            on_state_changed: Union[
+                OnItemStateChanged, WidgetEventProcessor, None,
+            ] = None,
+            when: Union[str, Callable] = None,
+    ):
+        text = Case(
+            {True: checked_text, False: unchecked_text},
+            selector=self._is_text_checked,
+        )
+        super().__init__(
+            text, id, item_id_getter, items, self._process_click, when,
+        )
         self.on_item_click = ensure_event_processor(on_click)
         self.on_state_changed = ensure_event_processor(on_state_changed)
 
-    async def _process_on_state_changed(self, event: ChatEvent, item_id: str,
-                                        manager: DialogManager):
+    async def _process_on_state_changed(
+            self, event: ChatEvent, item_id: str, manager: DialogManager,
+    ):
         if self.on_state_changed:
             await self.on_state_changed.process_event(
-                event, self.managed(manager), manager, item_id
+                event, self.managed(manager), manager, item_id,
             )
 
     @abstractmethod
-    def _is_text_checked(self, data: Dict, case: Case, manager: DialogManager) -> bool:
+    def _is_text_checked(
+            self, data: Dict, case: Case, manager: DialogManager,
+    ) -> bool:
         raise NotImplementedError
 
-    async def _process_click(self, c: CallbackQuery,
-                             select: ManagedWidgetAdapter[Select],
-                             manager: DialogManager, item_id: str):
+    async def _process_click(
+            self,
+            c: CallbackQuery,
+            select: ManagedWidgetAdapter[Select],
+            manager: DialogManager,
+            item_id: str,
+    ):
         if self.on_item_click:
             await self.on_item_click.process_event(c, select, manager, item_id)
         await self._on_click(c, select, manager, item_id)
 
     @abstractmethod
-    async def _on_click(self, c: CallbackQuery,
-                        select: ManagedWidgetAdapter[Select],
-                        manager: DialogManager, item_id: str):
+    async def _on_click(
+            self,
+            c: CallbackQuery,
+            select: ManagedWidgetAdapter[Select],
+            manager: DialogManager,
+            item_id: str,
+    ):
         raise NotImplementedError
 
 
@@ -117,26 +164,38 @@ class Radio(StatefulSelect):
     def get_checked(self, manager: DialogManager) -> Optional[str]:
         return self.get_widget_data(manager, None)
 
-    async def set_checked(self, event: ChatEvent, item_id: Optional[str], manager: DialogManager):
+    async def set_checked(
+            self, event: ChatEvent, item_id: Optional[str],
+            manager: DialogManager,
+    ):
         checked = self.get_checked(manager)
         self.set_widget_data(manager, item_id)
         if checked != item_id:
             await self._process_on_state_changed(event, item_id, manager)
 
-    def is_checked(self, item_id: Union[str, int], manager: DialogManager) -> bool:
+    def is_checked(
+            self, item_id: Union[str, int], manager: DialogManager,
+    ) -> bool:
         return str(item_id) == self.get_checked(manager)
 
     def _preview_checked_id(self, manager: DialogManager, item_id: str) -> str:
         return self.get_widget_data(manager, item_id)
 
-    def _is_text_checked(self, data: Dict, case: Case, manager: DialogManager) -> bool:
+    def _is_text_checked(
+            self, data: Dict, case: Case, manager: DialogManager,
+    ) -> bool:
         item_id = str(self.item_id_getter(data["item"]))
         if manager.is_preview():
-            return item_id==self._preview_checked_id(manager, item_id)
+            return item_id == self._preview_checked_id(manager, item_id)
         return self.is_checked(item_id, manager)
 
-    async def _on_click(self, c: CallbackQuery, select: Select,
-                        manager: DialogManager, item_id: str):
+    async def _on_click(
+            self,
+            c: CallbackQuery,
+            select: Select,
+            manager: DialogManager,
+            item_id: str,
+    ):
         await self.set_checked(c, item_id, manager)
 
     def managed(self, manager: DialogManager):
@@ -144,41 +203,72 @@ def managed(self, manager: DialogManager):
 
 
 class ManagedRadioAdapter(ManagedWidgetAdapter[Radio]):
-    def get_checked(self,
-                    manager: Optional[DialogManager] = None) -> Optional[str]:
+    def get_checked(
+            self, manager: Optional[DialogManager] = None,
+    ) -> Optional[str]:
         manager_deprecated(manager)
         return self.widget.get_checked(self.manager)
 
-    async def set_checked(self, event: ChatEvent, item_id: Optional[str],
-                          manager: Optional[DialogManager] = None):
+    async def set_checked(
+            self,
+            event: ChatEvent,
+            item_id: Optional[str],
+            manager: Optional[DialogManager] = None,
+    ):
         manager_deprecated(manager)
         return await self.widget.set_checked(event, item_id, self.manager)
 
-    def is_checked(self, item_id: Union[str, int],
-                   manager: Optional[DialogManager] = None) -> bool:
+    def is_checked(
+            self, item_id: Union[str, int],
+            manager: Optional[DialogManager] = None,
+    ) -> bool:
         manager_deprecated(manager)
         return self.widget.is_checked(item_id, self.manager)
 
 
 class Multiselect(StatefulSelect):
-    def __init__(self, checked_text: Text, unchecked_text: Text, id: str,
-                 item_id_getter: ItemIdGetter, items: Union[str, Sequence],
-                 min_selected: int = 0, max_selected: int = 0,
-                 on_click: Union[OnItemClick, WidgetEventProcessor, None] = None,
-                 on_state_changed: Union[OnItemStateChanged, WidgetEventProcessor, None] = None,
-                 when: Union[str, Callable] = None):
-        super().__init__(checked_text, unchecked_text, id, item_id_getter, items, on_click,
-                         on_state_changed, when)
+    def __init__(
+            self,
+            checked_text: Text,
+            unchecked_text: Text,
+            id: str,
+            item_id_getter: ItemIdGetter,
+            items: Union[str, Sequence],
+            min_selected: int = 0,
+            max_selected: int = 0,
+            on_click: Union[OnItemClick, WidgetEventProcessor, None] = None,
+            on_state_changed: Union[
+                OnItemStateChanged, WidgetEventProcessor, None,
+            ] = None,
+            when: Union[str, Callable] = None,
+    ):
+        super().__init__(
+            checked_text,
+            unchecked_text,
+            id,
+            item_id_getter,
+            items,
+            on_click,
+            on_state_changed,
+            when,
+        )
         self.min_selected = min_selected
         self.max_selected = max_selected
 
-    def _is_text_checked(self, data: Dict, case: Case, manager: DialogManager) -> bool:
+    def _is_text_checked(
+            self, data: Dict, case: Case, manager: DialogManager,
+    ) -> bool:
         item_id = str(self.item_id_getter(data["item"]))
         if manager.is_preview():
-            return ord(item_id[-1])%2 == 1  # just stupid way to make it differ
+            return (
+                # just stupid way to make it differ in preview
+                ord(item_id[-1]) % 2 == 1
+            )
         return self.is_checked(item_id, manager)
 
-    def is_checked(self, item_id: Union[str, int], manager: DialogManager) -> bool:
+    def is_checked(
+            self, item_id: Union[str, int], manager: DialogManager,
+    ) -> bool:
         data: List = self.get_checked(manager)
         return str(item_id) in data
 
@@ -188,8 +278,13 @@ def get_checked(self, manager: DialogManager) -> List[str]:
     async def reset_checked(self, event: ChatEvent, manager: DialogManager):
         self.set_widget_data(manager, [])
 
-    async def set_checked(self, event: ChatEvent,
-                          item_id: str, checked: bool, manager: DialogManager) -> None:
+    async def set_checked(
+            self,
+            event: ChatEvent,
+            item_id: str,
+            checked: bool,
+            manager: DialogManager,
+    ) -> None:
         data: List = self.get_checked(manager)
         changed = False
         if item_id in data:
@@ -206,33 +301,49 @@ async def set_checked(self, event: ChatEvent,
             self.set_widget_data(manager, data)
             await self._process_on_state_changed(event, item_id, manager)
 
-    async def _on_click(self, c: CallbackQuery, select: Select,
-                        manager: DialogManager, item_id: str):
-        await self.set_checked(c, item_id, not self.is_checked(item_id, manager), manager)
+    async def _on_click(
+            self,
+            c: CallbackQuery,
+            select: Select,
+            manager: DialogManager,
+            item_id: str,
+    ):
+        await self.set_checked(
+            c, item_id, not self.is_checked(item_id, manager), manager,
+        )
 
     def managed(self, manager: DialogManager):
         return ManagedMultiSelectAdapter(self, manager)
 
 
 class ManagedMultiSelectAdapter(ManagedWidgetAdapter[Multiselect]):
-    def is_checked(self, item_id: Union[str, int],
-                   manager: Optional[DialogManager] = None) -> bool:
+    def is_checked(
+            self, item_id: Union[str, int],
+            manager: Optional[DialogManager] = None,
+    ) -> bool:
         manager_deprecated(manager)
         return self.widget.is_checked(item_id, self.manager)
 
-    def get_checked(self, manager: Optional[DialogManager] = None) -> List[str]:
+    def get_checked(
+            self, manager: Optional[DialogManager] = None,
+    ) -> List[str]:
         manager_deprecated(manager)
         return self.widget.get_checked(self.manager)
 
-    async def reset_checked(self, event: ChatEvent,
-                            manager: Optional[DialogManager] = None):
+    async def reset_checked(
+            self, event: ChatEvent, manager: Optional[DialogManager] = None,
+    ):
         manager_deprecated(manager)
         return await self.widget.reset_checked(event, self.manager)
 
-    async def set_checked(self, event: ChatEvent,
-                          item_id: str, checked: bool,
-                          manager: Optional[DialogManager] = None) -> None:
+    async def set_checked(
+            self,
+            event: ChatEvent,
+            item_id: str,
+            checked: bool,
+            manager: Optional[DialogManager] = None,
+    ) -> None:
         manager_deprecated(manager)
-        return await self.widget.set_checked(event, item_id, checked,
-                                             self.manager)
-
+        return await self.widget.set_checked(
+            event, item_id, checked, self.manager,
+        )
diff --git a/aiogram_dialog/widgets/kbd/state.py b/aiogram_dialog/widgets/kbd/state.py
index bf51d24a..f1dd1e49 100644
--- a/aiogram_dialog/widgets/kbd/state.py
+++ b/aiogram_dialog/widgets/kbd/state.py
@@ -1,95 +1,134 @@
-from typing import Callable, Optional, Any
+from typing import Any, Callable, Optional
 
-from aiogram.dispatcher.filters.state import State
+from aiogram.fsm.state import State
 from aiogram.types import CallbackQuery
 
-from aiogram_dialog.context.events import ChatEvent, StartMode, Data
+from aiogram_dialog.context.events import ChatEvent, Data, StartMode
 from aiogram_dialog.manager.manager import DialogManager
-from aiogram_dialog.widgets.text import Text, Const
+from aiogram_dialog.widgets.text import Const, Text
 from aiogram_dialog.widgets.widget_event import WidgetEventProcessor
 from .button import Button, OnClick
 from ..when import WhenCondition
 
+BACK_TEXT = Const("Back")
+NEXT_TEXT = Const("Next")
+CANCEL_TEXT = Const("Cancel")
+
 
 class EventProcessorButton(Button, WidgetEventProcessor):
-    async def process_event(self, event: ChatEvent, source: Any, manager: DialogManager,
-                            *args, **kwargs):
+    async def process_event(
+            self,
+            event: ChatEvent,
+            source: Any,
+            manager: DialogManager,
+            *args,
+            **kwargs,
+    ):
         await self._on_click(event, self, manager)
 
-    async def _on_click(self, c: CallbackQuery, button: Button, manager: DialogManager):
+    async def _on_click(
+            self, c: CallbackQuery, button: Button, manager: DialogManager,
+    ):
         raise NotImplementedError
 
 
 class SwitchTo(EventProcessorButton):
-
-    def __init__(self, text: Text, id: str,
-                 state: State,
-                 on_click: Optional[OnClick] = None,
-                 when: WhenCondition = None):
+    def __init__(
+            self,
+            text: Text,
+            id: str,
+            state: State,
+            on_click: Optional[OnClick] = None,
+            when: WhenCondition = None,
+    ):
         super().__init__(text, id, self._on_click, when)
         self.text = text
         self.user_on_click = on_click
         self.state = state
 
-    async def _on_click(self, c: CallbackQuery, button: Button, manager: DialogManager):
+    async def _on_click(
+            self, c: CallbackQuery, button: Button, manager: DialogManager,
+    ):
         if self.user_on_click:
             await self.user_on_click(c, self, manager)
         await manager.dialog().switch_to(self.state)
 
 
 class Next(EventProcessorButton):
-    def __init__(self, text: Text = Const("Next"), callback_data: str = "__next__",
-                 on_click: Optional[Callable] = None,
-                 when: WhenCondition = None):
+    def __init__(
+            self,
+            text: Text = NEXT_TEXT,
+            callback_data: str = "__next__",
+            on_click: Optional[Callable] = None,
+            when: WhenCondition = None,
+    ):
         super().__init__(text, callback_data, self._on_click, when)
         self.text = text
         self.callback_data = callback_data
         self.user_on_click = on_click
 
-    async def _on_click(self, c: CallbackQuery, button: Button, manager: DialogManager):
+    async def _on_click(
+            self, c: CallbackQuery, button: Button, manager: DialogManager,
+    ):
         if self.user_on_click:
             await self.user_on_click(c, self, manager)
         await manager.dialog().next()
 
 
 class Back(EventProcessorButton):
-    def __init__(self, text: Text = Const("Back"), id: str = "__back__",
-                 on_click: Optional[Callable] = None,
-                 when: WhenCondition = None):
+    def __init__(
+            self,
+            text: Text = BACK_TEXT,
+            id: str = "__back__",
+            on_click: Optional[Callable] = None,
+            when: WhenCondition = None,
+    ):
         super().__init__(text, id, self._on_click, when)
         self.text = text
         self.callback_data = id
         self.user_on_click = on_click
 
-    async def _on_click(self, c: CallbackQuery, button: Button, manager: DialogManager):
+    async def _on_click(
+            self, c: CallbackQuery, button: Button, manager: DialogManager,
+    ):
         if self.user_on_click:
             await self.user_on_click(c, self, manager)
         await manager.dialog().back()
 
 
 class Cancel(EventProcessorButton):
-    def __init__(self, text: Text = Const("Cancel"), id: str = "__cancel__",
-                 result: Any = None,
-                 on_click: Optional[Callable] = None,
-                 when: WhenCondition = None):
+    def __init__(
+            self,
+            text: Text = CANCEL_TEXT,
+            id: str = "__cancel__",
+            result: Any = None,
+            on_click: Optional[Callable] = None,
+            when: WhenCondition = None,
+    ):
         super().__init__(text, id, self._on_click, when)
         self.text = text
         self.result = result
         self.user_on_click = on_click
 
-    async def _on_click(self, c: CallbackQuery, button: Button, manager: DialogManager):
+    async def _on_click(
+            self, c: CallbackQuery, button: Button, manager: DialogManager,
+    ):
         if self.user_on_click:
             await self.user_on_click(c, self, manager)
         await manager.done(self.result)
 
 
 class Start(EventProcessorButton):
-    def __init__(self, text: Text, id: str,
-                 state: State,
-                 data: Data = None,
-                 on_click: Optional[OnClick] = None,
-                 mode: StartMode = StartMode.NORMAL,
-                 when: WhenCondition = None):
+    def __init__(
+            self,
+            text: Text,
+            id: str,
+            state: State,
+            data: Data = None,
+            on_click: Optional[OnClick] = None,
+            mode: StartMode = StartMode.NORMAL,
+            when: WhenCondition = None,
+    ):
         super().__init__(text, id, self._on_click, when)
         self.text = text
         self.start_data = data
@@ -97,7 +136,9 @@ def __init__(self, text: Text, id: str,
         self.state = state
         self.mode = mode
 
-    async def _on_click(self, c: CallbackQuery, button: Button, manager: DialogManager):
+    async def _on_click(
+            self, c: CallbackQuery, button: Button, manager: DialogManager,
+    ):
         if self.user_on_click:
             await self.user_on_click(c, self, manager)
         await manager.start(self.state, self.start_data, self.mode)
diff --git a/aiogram_dialog/widgets/managed.py b/aiogram_dialog/widgets/managed.py
index f5724eb0..a63cd884 100644
--- a/aiogram_dialog/widgets/managed.py
+++ b/aiogram_dialog/widgets/managed.py
@@ -1,8 +1,6 @@
-from typing import TypeVar, Generic
+from typing import Generic, TypeVar
 
-from ..manager.protocols import (
-    ManagedDialogProto, DialogManager, ManagedWidgetProto,
-)
+from ..manager.protocols import DialogManager, ManagedWidgetProto
 
 
 class ManagedWidget(ManagedWidgetProto):
diff --git a/aiogram_dialog/widgets/media/__init__.py b/aiogram_dialog/widgets/media/__init__.py
index 9a74f4f5..748a0864 100644
--- a/aiogram_dialog/widgets/media/__init__.py
+++ b/aiogram_dialog/widgets/media/__init__.py
@@ -4,4 +4,4 @@
 __all__ = [
     "Media",
     "StaticMedia",
-]
\ No newline at end of file
+]
diff --git a/aiogram_dialog/widgets/media/base.py b/aiogram_dialog/widgets/media/base.py
index c611dadc..5db437ee 100644
--- a/aiogram_dialog/widgets/media/base.py
+++ b/aiogram_dialog/widgets/media/base.py
@@ -1,4 +1,4 @@
-from typing import Optional, Any
+from typing import Any, Optional
 
 from aiogram_dialog.manager.manager import DialogManager
 from aiogram_dialog.manager.protocols import MediaAttachment
@@ -7,17 +7,13 @@
 
 class Media(Whenable):
     async def render_media(
-            self,
-            data: Any,
-            manager: DialogManager
+            self, data: Any, manager: DialogManager,
     ) -> Optional[MediaAttachment]:
         if not self.is_(data, manager):
             return None
         return await self._render_media(data, manager)
 
     async def _render_media(
-            self,
-            data: Any,
-            manager: DialogManager
+            self, data: Any, manager: DialogManager,
     ) -> Optional[MediaAttachment]:
         return None
diff --git a/aiogram_dialog/widgets/media/static.py b/aiogram_dialog/widgets/media/static.py
index a9645858..bb7534bb 100644
--- a/aiogram_dialog/widgets/media/static.py
+++ b/aiogram_dialog/widgets/media/static.py
@@ -1,10 +1,10 @@
-from typing import Optional, Any, Dict, Union
+from typing import Any, Dict, Optional, Union
 
 from aiogram.types import ContentType
 
 from aiogram_dialog.manager.manager import DialogManager
 from aiogram_dialog.manager.protocols import MediaAttachment
-from aiogram_dialog.widgets.text import Text, Const
+from aiogram_dialog.widgets.text import Const, Text
 from .base import Media
 from ..when import WhenCondition
 
@@ -32,9 +32,7 @@ def __init__(
         self.media_params = media_params or {}
 
     async def _render_media(
-            self,
-            data: Any,
-            manager: DialogManager
+            self, data: Any, manager: DialogManager,
     ) -> Optional[MediaAttachment]:
         if self.url:
             url = await self.url.render_text(data, manager)
diff --git a/aiogram_dialog/widgets/text/__init__.py b/aiogram_dialog/widgets/text/__init__.py
index 97cd5549..662aee9d 100644
--- a/aiogram_dialog/widgets/text/__init__.py
+++ b/aiogram_dialog/widgets/text/__init__.py
@@ -6,9 +6,11 @@
 from .progress import Progress
 
 __all__ = [
-    "Const", "Text",
+    "Const",
+    "Text",
     "Format",
-    "Multi", "Case",
+    "Multi",
+    "Case",
     "Jinja",
     "List",
     "Progress",
diff --git a/aiogram_dialog/widgets/text/base.py b/aiogram_dialog/widgets/text/base.py
index 2934acb8..ee3d1deb 100644
--- a/aiogram_dialog/widgets/text/base.py
+++ b/aiogram_dialog/widgets/text/base.py
@@ -1,7 +1,7 @@
-from typing import Union, Dict
+from typing import Dict, Union
 
 from aiogram_dialog.manager.manager import DialogManager
-from aiogram_dialog.widgets.when import Whenable, WhenCondition, true
+from aiogram_dialog.widgets.when import true, Whenable, WhenCondition
 
 
 class Text(Whenable):
@@ -43,10 +43,7 @@ def __init__(self, *texts: Text, sep="\n", when: WhenCondition = None):
         self.sep = sep
 
     async def _render_text(self, data, manager: DialogManager) -> str:
-        texts = [
-            await t.render_text(data, manager)
-            for t in self.texts
-        ]
+        texts = [await t.render_text(data, manager) for t in self.texts]
         return self.sep.join(filter(None, texts))
 
     def __iadd__(self, other: Union["Text", str]):
diff --git a/aiogram_dialog/widgets/text/jinja.py b/aiogram_dialog/widgets/text/jinja.py
index 14cb9126..4d6fcbcd 100644
--- a/aiogram_dialog/widgets/text/jinja.py
+++ b/aiogram_dialog/widgets/text/jinja.py
@@ -1,7 +1,16 @@
-from typing import Dict, Any, Iterable, Optional, Callable, Union, Tuple, Mapping
+from typing import (
+    Any,
+    Callable,
+    Dict,
+    Iterable,
+    Mapping,
+    Optional,
+    Tuple,
+    Union,
+)
 
 from aiogram import Bot
-from jinja2 import Environment, BaseLoader
+from jinja2 import BaseLoader, Environment
 
 from .base import Text
 from ..when import WhenCondition
@@ -19,7 +28,8 @@ def __init__(self, text: str, when: WhenCondition = None):
         self.template_text = text
 
     async def _render_text(self, data: Dict, manager: DialogManager) -> str:
-        env: Environment = manager.event.bot.get(BOT_ENV_FIELD, default_env)
+        bot: Bot = manager.data["bot"]
+        env: Environment = getattr(bot, BOT_ENV_FIELD, default_env)
         template = env.get_template(self.template_text)
 
         if env.is_async:
@@ -34,13 +44,12 @@ def get_source(self, environment, template):
 
 
 def _create_env(
-        *args: Any,
-        filters: Optional[Filters] = None,
-        **kwargs: Any) -> Environment:
+        *args: Any, filters: Optional[Filters] = None, **kwargs: Any,
+) -> Environment:
     kwargs.setdefault("autoescape", True)
     kwargs.setdefault("lstrip_blocks", True)
     kwargs.setdefault("trim_blocks", True)
-    if not "loader" in kwargs:
+    if "loader" not in kwargs:
         kwargs["loader"] = StubLoader()
     env = Environment(*args, **kwargs)
     if filters is not None:
@@ -54,8 +63,9 @@ def setup_jinja(
         filters: Optional[Filters] = None,
         **kwargs: Any,
 ) -> Environment:
-    bot[BOT_ENV_FIELD] = _create_env(*args, filters=filters, **kwargs)
-    return bot[BOT_ENV_FIELD]
+    env = _create_env(*args, filters=filters, **kwargs)
+    setattr(bot, BOT_ENV_FIELD, env)
+    return env
 
 
 default_env = _create_env()
diff --git a/aiogram_dialog/widgets/text/list.py b/aiogram_dialog/widgets/text/list.py
index 37a0fa67..4a85d949 100644
--- a/aiogram_dialog/widgets/text/list.py
+++ b/aiogram_dialog/widgets/text/list.py
@@ -1,24 +1,28 @@
 from operator import itemgetter
-from typing import Dict, Union, Sequence, Callable
+from typing import Callable, Dict, Sequence, Union
 
+from aiogram_dialog.manager.manager import DialogManager
 from .base import Text
 from ..when import WhenCondition
 
-from aiogram_dialog.manager.manager import DialogManager
-
-
 ItemsGetter = Callable[[Dict], Sequence]
 
 
 def get_identity(items: Sequence) -> ItemsGetter:
     def identity(data) -> Sequence:
         return items
+
     return identity
 
 
 class List(Text):
-    def __init__(self, field: Text, items: Union[str, Callable, Sequence],
-                 sep: str = "\n", when: WhenCondition = None):
+    def __init__(
+            self,
+            field: Text,
+            items: Union[str, Callable, Sequence],
+            sep: str = "\n",
+            when: WhenCondition = None,
+    ):
         super().__init__(when)
         self.field = field
         self.sep = sep
@@ -31,7 +35,10 @@ def __init__(self, field: Text, items: Union[str, Callable, Sequence],
 
     async def _render_text(self, data: Dict, manager: DialogManager) -> str:
         texts = [
-            await self.field.render_text({"data": data, "item": item, "pos": pos + 1, "pos0": pos}, manager)
+            await self.field.render_text(
+                {"data": data, "item": item, "pos": pos + 1, "pos0": pos},
+                manager,
+            )
             for pos, item in enumerate(self.items_getter(data))
         ]
         return self.sep.join(filter(None, texts))
diff --git a/aiogram_dialog/widgets/text/multi.py b/aiogram_dialog/widgets/text/multi.py
index f917b2e7..705a75f9 100644
--- a/aiogram_dialog/widgets/text/multi.py
+++ b/aiogram_dialog/widgets/text/multi.py
@@ -1,7 +1,8 @@
-import warnings
-from typing import Callable, Union, Dict, Any, Hashable
+from typing import Any, Callable, Dict, Hashable, Union
+from warnings import warn
 
-from .base import Text, Multi as _Multi
+from .base import Multi as _Multi
+from .base import Text
 from ..when import WhenCondition
 from ...manager.manager import DialogManager
 
@@ -11,7 +12,7 @@
 class Multi(_Multi):
     def __init__(self, *texts: Text, sep="\n", when: WhenCondition = None):
         super().__init__(*texts, sep=sep, when=when)
-        warnings.warn(
+        warn(
             "This is compatibility class"
             " and will be removed in aiogram_dialog==2.0, "
             " fix using `from aiogram_dialog.widgets.text import Multi`",
@@ -21,14 +22,21 @@ def __init__(self, *texts: Text, sep="\n", when: WhenCondition = None):
 
 
 def new_case_field(fieldname: str) -> Selector:
-    def case_field(data: Dict, widget: "Case", manager: DialogManager) -> Hashable:
+    def case_field(
+            data: Dict, widget: "Case", manager: DialogManager,
+    ) -> Hashable:
         return data.get(fieldname)
 
     return case_field
 
 
 class Case(Text):
-    def __init__(self, texts: Dict[Any, Text], selector: Union[str, Selector], when: WhenCondition = None):
+    def __init__(
+            self,
+            texts: Dict[Any, Text],
+            selector: Union[str, Selector],
+            when: WhenCondition = None,
+    ):
         super().__init__(when)
         self.texts = texts
         if isinstance(selector, str):
diff --git a/aiogram_dialog/widgets/text/progress.py b/aiogram_dialog/widgets/text/progress.py
index 30082ff2..a62813b5 100644
--- a/aiogram_dialog/widgets/text/progress.py
+++ b/aiogram_dialog/widgets/text/progress.py
@@ -6,7 +6,14 @@
 
 
 class Progress(Text):
-    def __init__(self, field: str, width: int = 10, filled="🟥", empty="⬜", when: WhenCondition = None):
+    def __init__(
+            self,
+            field: str,
+            width: int = 10,
+            filled="🟥",
+            empty="⬜",
+            when: WhenCondition = None,
+    ):
         super().__init__(when)
         self.field = field
         self.width = width
diff --git a/aiogram_dialog/widgets/utils.py b/aiogram_dialog/widgets/utils.py
index 496f45a6..6dc68765 100644
--- a/aiogram_dialog/widgets/utils.py
+++ b/aiogram_dialog/widgets/utils.py
@@ -1,19 +1,21 @@
-from typing import Union, Sequence, Tuple, Callable, Dict, List
+from typing import Callable, Dict, List, Sequence, Tuple, Union
 
-from .data.data_context import DataGetter, StaticGetter, CompositeGetter
-from .input import MessageHandlerFunc, BaseInput, MessageInput
-from .kbd import Keyboard, Group
+from .data.data_context import CompositeGetter, DataGetter, StaticGetter
+from .input import BaseInput, MessageHandlerFunc, MessageInput
+from .kbd import Group, Keyboard
 from .media import Media
-from .text import Multi, Format, Text
+from .text import Format, Multi, Text
 from .widget_event import WidgetEventProcessor
-from ..exceptions import InvalidWidgetType, InvalidWidget
+from ..exceptions import InvalidWidget, InvalidWidgetType
 
 WidgetSrc = Union[str, Text, Keyboard, MessageHandlerFunc, Media, BaseInput]
 
 SingleGetterBase = Union[DataGetter, Dict]
 GetterVariant = Union[
-    None, SingleGetterBase,
-    List[SingleGetterBase], Tuple[SingleGetterBase, ...],
+    None,
+    SingleGetterBase,
+    List[SingleGetterBase],
+    Tuple[SingleGetterBase, ...],
 ]
 
 
@@ -37,9 +39,11 @@ def ensure_keyboard(widget: Union[Keyboard, Sequence[Keyboard]]) -> Keyboard:
 
 def ensure_input(
         widget: Union[
-            MessageHandlerFunc, WidgetEventProcessor, BaseInput,
-            Sequence[BaseInput]
-        ]
+            MessageHandlerFunc,
+            WidgetEventProcessor,
+            BaseInput,
+            Sequence[BaseInput],
+        ],
 ) -> BaseInput:
     if isinstance(widget, BaseInput):
         return widget
@@ -65,7 +69,7 @@ def ensure_media(widget: Union[Media, Sequence[Media]]) -> Media:
 
 
 def ensure_widgets(
-        widgets: Sequence[WidgetSrc]
+        widgets: Sequence[WidgetSrc],
 ) -> Tuple[Text, Keyboard, BaseInput, Media]:
     texts = []
     keyboards = []
@@ -84,7 +88,8 @@ def ensure_widgets(
         else:
             raise InvalidWidgetType(
                 f"Cannot add widget of type {type(w)}. "
-                f"Only str, Text, Keyboard, BaseInput and Callable are supported"
+                f"Only str, Text, Keyboard, BaseInput "
+                f"and Callable are supported",
             )
     return (
         ensure_text(texts),
@@ -106,5 +111,5 @@ def ensure_data_getter(getter: GetterVariant) -> DataGetter:
     else:
         raise InvalidWidgetType(
             f"Cannot add data getter of type {type(getter)}. "
-            f"Only Dict, Callable or List of Callables are supported"
+            f"Only Dict, Callable or List of Callables are supported",
         )
diff --git a/aiogram_dialog/widgets/when.py b/aiogram_dialog/widgets/when.py
index f15bb28c..20470852 100644
--- a/aiogram_dialog/widgets/when.py
+++ b/aiogram_dialog/widgets/when.py
@@ -1,25 +1,27 @@
-from typing import Union, Callable, Dict
+from typing import Callable, Dict, Union
 
 from magic_filter import MagicFilter
 
 from .managed import ManagedWidget
 from ..manager.protocols import DialogManager
 
-
 Predicate = Callable[[Dict, "Whenable", DialogManager], bool]
 WhenCondition = Union[str, MagicFilter, Predicate, None]
 
 
 def new_when_field(fieldname: str) -> Predicate:
-    def when_field(data: Dict, widget: "Whenable",
-                   manager: DialogManager) -> bool:
+    def when_field(
+            data: Dict, widget: "Whenable", manager: DialogManager,
+    ) -> bool:
         return bool(data.get(fieldname))
 
     return when_field
 
 
 def new_when_magic(f: MagicFilter) -> Predicate:
-    def when_magic(data: Dict, widget: "Whenable", manager: DialogManager) -> bool:
+    def when_magic(
+            data: Dict, widget: "Whenable", manager: DialogManager,
+    ) -> bool:
         return f.resolve(data)
 
     return when_magic
@@ -30,7 +32,6 @@ def true(data: Dict, widget: "Whenable", manager: DialogManager):
 
 
 class Whenable(ManagedWidget):
-
     def __init__(self, when: WhenCondition = None):
         self.condition: Predicate
         if when is None:
diff --git a/aiogram_dialog/widgets/widget_event.py b/aiogram_dialog/widgets/widget_event.py
index 07fd6554..f7af9f8a 100644
--- a/aiogram_dialog/widgets/widget_event.py
+++ b/aiogram_dialog/widgets/widget_event.py
@@ -1,11 +1,18 @@
-from typing import Union, Callable, Any
+from typing import Any, Callable, Union
 
 from aiogram_dialog.context.events import ChatEvent
 from aiogram_dialog.manager.protocols import DialogManager
 
 
 class WidgetEventProcessor:
-    async def process_event(self, event: ChatEvent, source: Any, manager: DialogManager, *args, **kwargs):
+    async def process_event(
+            self,
+            event: ChatEvent,
+            source: Any,
+            manager: DialogManager,
+            *args,
+            **kwargs,
+    ):
         raise NotImplementedError
 
 
@@ -13,12 +20,21 @@ class SimpleEventProcessor(WidgetEventProcessor):
     def __init__(self, callback: Callable):
         self.callback = callback
 
-    async def process_event(self, event: ChatEvent, source: Any, manager: DialogManager, *args, **kwargs):
+    async def process_event(
+            self,
+            event: ChatEvent,
+            source: Any,
+            manager: DialogManager,
+            *args,
+            **kwargs,
+    ):
         if self.callback:
             await self.callback(event, source, manager, *args, **kwargs)
 
 
-def ensure_event_processor(processor: Union[Callable, WidgetEventProcessor, None]) -> WidgetEventProcessor:
+def ensure_event_processor(
+        processor: Union[Callable, WidgetEventProcessor, None],
+) -> WidgetEventProcessor:
     if isinstance(processor, WidgetEventProcessor):
         return processor
     else:
diff --git a/aiogram_dialog/window.py b/aiogram_dialog/window.py
index ecf541bf..e4cf2b7b 100644
--- a/aiogram_dialog/window.py
+++ b/aiogram_dialog/window.py
@@ -1,36 +1,41 @@
 from logging import getLogger
-from typing import Dict, Optional, List
+from typing import Dict, List, Optional
 
-from aiogram.dispatcher.filters.state import State
-from aiogram.types import (
-    InlineKeyboardMarkup, Message, CallbackQuery, ParseMode,
-)
+from aiogram.fsm.state import State
+from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message, UNSET
 
 from .dialog import Dialog, DialogWindowProto
 from .manager.protocols import DialogManager, MediaAttachment
-from .utils import get_chat, NewMessage
+from .utils import NewMessage
 from .widgets.action import Actionable
 from .widgets.data import PreviewAwareGetter
 from .widgets.kbd import Keyboard
 from .widgets.utils import (
-    ensure_widgets, ensure_data_getter, GetterVariant, WidgetSrc,
+    ensure_data_getter,
+    ensure_widgets,
+    GetterVariant,
+    WidgetSrc,
 )
 
 logger = getLogger(__name__)
 
 
 class Window(DialogWindowProto):
-    def __init__(self,
-                 *widgets: WidgetSrc,
-                 state: State,
-                 getter: GetterVariant = None,
-                 parse_mode: Optional[ParseMode] = None,
-                 disable_web_page_preview: Optional[bool] = None,
-                 preview_add_transitions: Optional[List[Keyboard]] = None,
-                 preview_data: GetterVariant = None,
-                 ):
+    def __init__(
+            self,
+            *widgets: WidgetSrc,
+            state: State,
+            getter: GetterVariant = None,
+            parse_mode: Optional[str] = UNSET,
+            disable_web_page_preview: Optional[bool] = None,
+            preview_add_transitions: Optional[List[Keyboard]] = None,
+            preview_data: GetterVariant = None,
+    ):
         (
-            self.text, self.keyboard, self.on_message, self.media,
+            self.text,
+            self.keyboard,
+            self.on_message,
+            self.media,
         ) = ensure_widgets(widgets)
         self.getter = PreviewAwareGetter(
             ensure_data_getter(getter),
@@ -45,38 +50,44 @@ async def render_text(self, data: Dict, manager: DialogManager) -> str:
         return await self.text.render_text(data, manager)
 
     async def render_media(
-            self, data: Dict,
-            manager: DialogManager
+            self, data: Dict, manager: DialogManager,
     ) -> Optional[MediaAttachment]:
         if self.media:
             return await self.media.render_media(data, manager)
 
-    async def render_kbd(self, data: Dict,
-                         manager: DialogManager) -> InlineKeyboardMarkup:
+    async def render_kbd(
+            self, data: Dict, manager: DialogManager,
+    ) -> InlineKeyboardMarkup:
         keyboard = await self.keyboard.render_keyboard(data, manager)
         return InlineKeyboardMarkup(inline_keyboard=keyboard)
 
-    async def load_data(self, dialog: "Dialog",
-                        manager: DialogManager) -> Dict:
+    async def load_data(
+            self, dialog: "Dialog", manager: DialogManager,
+    ) -> Dict:
         data = await dialog.load_data(manager)
         data.update(await self.getter(**manager.data))
         return data
 
-    async def process_message(self, message: Message, dialog: Dialog,
-                              manager: DialogManager):
+    async def process_message(
+            self, message: Message, dialog: Dialog, manager: DialogManager,
+    ):
         if self.on_message:
             await self.on_message.process_message(message, dialog, manager)
 
-    async def process_callback(self, c: CallbackQuery, dialog: Dialog,
-                               manager: DialogManager):
+    async def process_callback(
+            self, c: CallbackQuery, dialog: Dialog, manager: DialogManager,
+    ):
         if self.keyboard:
             await self.keyboard.process_callback(c, dialog, manager)
 
-    async def render(self, dialog: Dialog, manager: DialogManager) -> NewMessage:
+    async def render(
+            self, dialog: Dialog, manager: DialogManager,
+    ) -> NewMessage:
         logger.debug("Show window: %s", self)
+        chat = manager.data["event_chat"]
         current_data = await self.load_data(dialog, manager)
         return NewMessage(
-            chat=get_chat(manager.event),
+            chat=chat,
             text=await self.render_text(current_data, manager),
             reply_markup=await self.render_kbd(current_data, manager),
             parse_mode=self.parse_mode,
diff --git a/docs/conf.py b/docs/conf.py
index 809767ea..8ef272f6 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -50,4 +50,4 @@
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
\ No newline at end of file
+html_static_path = ['_static']
diff --git a/docs/examples/quickstart/bot.py b/docs/examples/quickstart/bot.py
index 02ef1c88..073f2bf6 100644
--- a/docs/examples/quickstart/bot.py
+++ b/docs/examples/quickstart/bot.py
@@ -1,6 +1,6 @@
-from aiogram import Bot, Dispatcher, executor
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
+from aiogram import Bot, Dispatcher
+from aiogram.fsm.storage.memory import MemoryStorage
 
 storage = MemoryStorage()
 bot = Bot(token='BOT TOKEN HERE')
-dp = Dispatcher(bot, storage=storage)
+dp = Dispatcher(storage=storage)
diff --git a/docs/examples/quickstart/sg.py b/docs/examples/quickstart/sg.py
index f246ade3..658ef6fb 100644
--- a/docs/examples/quickstart/sg.py
+++ b/docs/examples/quickstart/sg.py
@@ -1,4 +1,4 @@
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.filters.state import StatesGroup, State
 
 class MySG(StatesGroup):
     main = State()
\ No newline at end of file
diff --git a/docs/examples/quickstart/start.py b/docs/examples/quickstart/start.py
index ec778a68..c2f803dd 100644
--- a/docs/examples/quickstart/start.py
+++ b/docs/examples/quickstart/start.py
@@ -1,7 +1,7 @@
 from aiogram.types import Message
 from aiogram_dialog import DialogManager, StartMode
 
-@dp.message_handler(commands=["start"])
+@dp.message(commands=["start"])
 async def start(m: Message, dialog_manager: DialogManager):
     # Important: always set `mode=StartMode.RESET_STACK` you don't want to stack dialogs
     await dialog_manager.start(MySG.main, mode=StartMode.RESET_STACK)
diff --git a/docs/examples/quickstart/start_bot.py b/docs/examples/quickstart/start_bot.py
index 96eaa1a2..688e60cc 100644
--- a/docs/examples/quickstart/start_bot.py
+++ b/docs/examples/quickstart/start_bot.py
@@ -1,4 +1,2 @@
-from aiogram import executor
-
 if __name__ == '__main__':
-    executor.start_polling(dp, skip_updates=True)
+    dp.run_polling(bot, skip_updates=True)
diff --git a/docs/examples/quickstart/summary.py b/docs/examples/quickstart/summary.py
index a92ef383..ca8ba3a2 100644
--- a/docs/examples/quickstart/summary.py
+++ b/docs/examples/quickstart/summary.py
@@ -1,6 +1,6 @@
-from aiogram import Bot, Dispatcher, executor
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram import Bot, Dispatcher
+from aiogram.fsm.storage.memory import MemoryStorage
+from aiogram.filters.state import StatesGroup, State
 from aiogram.types import Message
 
 from aiogram_dialog import Window, Dialog, DialogRegistry, DialogManager, StartMode
@@ -9,7 +9,7 @@
 
 storage = MemoryStorage()
 bot = Bot(token='BOT TOKEN HERE')
-dp = Dispatcher(bot, storage=storage)
+dp = Dispatcher(storage=storage)
 registry = DialogRegistry(dp)
 
 
@@ -26,10 +26,10 @@ class MySG(StatesGroup):
 registry.register(dialog)
 
 
-@dp.message_handler(commands=["start"])
+@dp.message(commands=["start"])
 async def start(m: Message, dialog_manager: DialogManager):
     await dialog_manager.start(MySG.main, mode=StartMode.RESET_STACK)
 
 
 if __name__ == '__main__':
-    executor.start_polling(dp, skip_updates=True)
+    dp.run_polling(bot, skip_updates=True)
diff --git a/docs/examples/quickstart/window.py b/docs/examples/quickstart/window.py
index 4d00d18d..0be43bc0 100644
--- a/docs/examples/quickstart/window.py
+++ b/docs/examples/quickstart/window.py
@@ -1,4 +1,4 @@
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.filters.state import StatesGroup, State
 
 from aiogram_dialog import Window
 from aiogram_dialog.widgets.kbd import Button
diff --git a/docs/examples/tools/preview_summary.py b/docs/examples/tools/preview_summary.py
index ecae550f..8c342144 100644
--- a/docs/examples/tools/preview_summary.py
+++ b/docs/examples/tools/preview_summary.py
@@ -1,8 +1,8 @@
 import asyncio
 
 from aiogram import Bot, Dispatcher
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.fsm.storage.memory import MemoryStorage
+from aiogram.filters.state import StatesGroup, State
 
 from aiogram_dialog import Dialog, Window, DialogRegistry
 from aiogram_dialog.tools import render_preview
diff --git a/docs/examples/tools/render.py b/docs/examples/tools/render.py
index 60275add..d7a03f09 100644
--- a/docs/examples/tools/render.py
+++ b/docs/examples/tools/render.py
@@ -1,4 +1,4 @@
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.filters.state import StatesGroup, State
 from aiogram.types import Message
 
 from aiogram_dialog import Dialog, Window, DialogManager
diff --git a/docs/examples/tools/render_hints.py b/docs/examples/tools/render_hints.py
index 98e38202..29a58602 100644
--- a/docs/examples/tools/render_hints.py
+++ b/docs/examples/tools/render_hints.py
@@ -1,4 +1,4 @@
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.filters.state import StatesGroup, State
 from aiogram.types import Message
 
 from aiogram_dialog import Dialog, Window, DialogManager
diff --git a/docs/examples/transitions/buttons.py b/docs/examples/transitions/buttons.py
index a857fb46..469f806b 100644
--- a/docs/examples/transitions/buttons.py
+++ b/docs/examples/transitions/buttons.py
@@ -1,4 +1,4 @@
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.filters.state import StatesGroup, State
 
 from aiogram_dialog import Dialog, Window
 from aiogram_dialog.widgets.kbd import Row, SwitchTo, Next, Back
diff --git a/docs/examples/transitions/switch.py b/docs/examples/transitions/switch.py
index 33f0beae..7c26b85e 100644
--- a/docs/examples/transitions/switch.py
+++ b/docs/examples/transitions/switch.py
@@ -1,4 +1,4 @@
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.filters.state import StatesGroup, State
 from aiogram.types import CallbackQuery
 
 from aiogram_dialog import Dialog, DialogManager, Window
diff --git a/docs/examples/widgets/getter.py b/docs/examples/widgets/getter.py
index 56fa1077..42af9930 100644
--- a/docs/examples/widgets/getter.py
+++ b/docs/examples/widgets/getter.py
@@ -1,4 +1,4 @@
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.filters.state import StatesGroup, State
 
 from aiogram_dialog import Window, Dialog
 from aiogram_dialog.widgets.kbd import Button
diff --git a/docs/examples/widgets/jinja.py b/docs/examples/widgets/jinja.py
index 61f3d344..63183452 100644
--- a/docs/examples/widgets/jinja.py
+++ b/docs/examples/widgets/jinja.py
@@ -1,4 +1,4 @@
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.filters.state import StatesGroup, State
 from aiogram.types import ParseMode
 
 from aiogram_dialog import Window
diff --git a/docs/examples/widgets/whenable.py b/docs/examples/widgets/whenable.py
index ce74e8e0..ab6b2599 100644
--- a/docs/examples/widgets/whenable.py
+++ b/docs/examples/widgets/whenable.py
@@ -1,6 +1,6 @@
 from typing import Dict
 
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.filters.state import StatesGroup, State
 
 from aiogram_dialog import Window, DialogManager
 from aiogram_dialog.widgets.kbd import Button, Row, Group
diff --git a/example/example.py b/example/example.py
index b4b76905..95455c3b 100644
--- a/example/example.py
+++ b/example/example.py
@@ -3,22 +3,22 @@
 from datetime import datetime
 from operator import itemgetter
 
-from aiogram import Bot, Dispatcher
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram import Bot, Dispatcher, F
+from aiogram.fsm.storage.memory import MemoryStorage, SimpleEventIsolation
+from aiogram.fsm.state import StatesGroup, State
 from aiogram.types import Message, CallbackQuery
 
 from aiogram_dialog import (
     Dialog, DialogManager, DialogRegistry, Window, StartMode, BaseDialogManager
 )
-from aiogram_dialog.tools.preview import render
+from aiogram_dialog.manager.protocols import ManagedDialogAdapterProto
 from aiogram_dialog.widgets.input import MessageInput
 from aiogram_dialog.widgets.kbd import (
     Button, Group, Next, Back, Cancel, Checkbox, Row, Radio, Multiselect, Select
 )
 from aiogram_dialog.widgets.text import Const, Format, Progress
 
-API_TOKEN = ""
+API_TOKEN = "PLACE YOUR TOKEN HERE"
 
 
 # ------ Groups
@@ -60,7 +60,7 @@ async def background(c: CallbackQuery, manager: BaseDialogManager):
         })
 
 
-async def input_fun(m: Message, dialog: Dialog, manager: DialogManager):
+async def input_fun(m: Message, dialog: ManagedDialogAdapterProto, manager: DialogManager):
     print("input_fun")
     await manager.start(Sub.text, m.text)
 
@@ -162,13 +162,13 @@ async def main():
     logging.getLogger("aiogram_dialog").setLevel(logging.DEBUG)
     storage = MemoryStorage()
     bot = Bot(token=API_TOKEN)
-    dp = Dispatcher(bot, storage=storage)
+    dp = Dispatcher(storage=storage, events_isolation=SimpleEventIsolation())
     registry = DialogRegistry(dp)
-    dp.register_message_handler(start, text="/start", state="*")
+    dp.message.register(start, F.text == "/start")
     registry.register(dialog1)
     registry.register(dialog2)
 
-    await dp.start_polling()
+    await dp.start_polling(bot)
 
 
 if __name__ == '__main__':
diff --git a/example/launch_modes.py b/example/launch_modes.py
index 113ecd32..06cbc9a4 100644
--- a/example/launch_modes.py
+++ b/example/launch_modes.py
@@ -2,8 +2,8 @@
 import logging
 
 from aiogram import Bot, Dispatcher
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.fsm.storage.memory import MemoryStorage
+from aiogram.filters.state import StatesGroup, State
 
 from aiogram_dialog import Dialog, Window, DialogRegistry
 from aiogram_dialog.manager.protocols import LaunchMode, DialogManager
@@ -76,14 +76,14 @@ async def main():
     logging.basicConfig(level=logging.INFO)
     storage = MemoryStorage()
     bot = Bot(token=API_TOKEN)
-    dp = Dispatcher(bot, storage=storage)
+    dp = Dispatcher(storage=storage)
     registry = DialogRegistry(dp)
     registry.register_start_handler(MainSG.default)
     registry.register(banner)
     registry.register(product)
     registry.register(main_menu)
 
-    await dp.start_polling()
+    await dp.start_polling(bot)
 
 
 if __name__ == '__main__':
diff --git a/example/list_group.py b/example/list_group.py
index 9f4e11e6..2905926a 100644
--- a/example/list_group.py
+++ b/example/list_group.py
@@ -3,8 +3,8 @@
 from typing import Dict
 
 from aiogram import Bot, Dispatcher
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.filters.state import StatesGroup, State
+from aiogram.fsm.storage.memory import MemoryStorage
 
 from aiogram_dialog import (
     Dialog, DialogManager, DialogRegistry, Window,
@@ -67,12 +67,12 @@ async def main():
     logging.basicConfig(level=logging.INFO)
     storage = MemoryStorage()
     bot = Bot(token=API_TOKEN)
-    dp = Dispatcher(bot, storage=storage)
+    dp = Dispatcher(storage=storage)
     registry = DialogRegistry(dp)
     registry.register_start_handler(DialogSG.greeting)
     registry.register(dialog)
 
-    await dp.start_polling()
+    await dp.start_polling(bot)
 
 
 if __name__ == '__main__':
diff --git a/example/loading.py b/example/loading.py
index cf8e4ce0..7413caf3 100644
--- a/example/loading.py
+++ b/example/loading.py
@@ -1,9 +1,9 @@
 import asyncio
 import logging
 
-from aiogram import Bot, Dispatcher
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram import Bot, Dispatcher, F
+from aiogram.fsm.storage.memory import MemoryStorage, SimpleEventIsolation
+from aiogram.fsm.state import StatesGroup, State
 from aiogram.types import Message, CallbackQuery
 
 from aiogram_dialog import Dialog, DialogManager, Window, DialogRegistry, BaseDialogManager, \
@@ -78,13 +78,13 @@ async def main():
     logging.getLogger("aiogram_dialog").setLevel(logging.DEBUG)
     storage = MemoryStorage()
     bot = Bot(token=API_TOKEN)
-    dp = Dispatcher(bot, storage=storage)
+    dp = Dispatcher(storage=storage, events_isolation=SimpleEventIsolation())
     registry = DialogRegistry(dp)
     registry.register(bg_dialog)
     registry.register(main_menu)
-    dp.register_message_handler(start, text="/start", state="*")
+    dp.message.register(start, F.text == "/start")
 
-    await dp.start_polling()
+    await dp.start_polling(bot)
 
 
 if __name__ == '__main__':
diff --git a/example/multistack.py b/example/multistack.py
index 77ff4f3f..2c7a5f1b 100644
--- a/example/multistack.py
+++ b/example/multistack.py
@@ -4,10 +4,13 @@
 import operator
 
 from aiogram import Bot, Dispatcher
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.filters import Command
+from aiogram.fsm.storage.memory import MemoryStorage
+from aiogram.fsm.state import StatesGroup, State
 from aiogram.types import Message, CallbackQuery
 
+from aiogram_dialog.manager.protocols import ManagedDialogAdapterProto
+
 from aiogram_dialog import (
     Dialog, DialogManager, DialogRegistry, Window, StartMode,
 )
@@ -39,7 +42,7 @@ async def get_data(dialog_manager: DialogManager, **kwargs):
     }
 
 
-async def name_handler(m: Message, dialog: Dialog, manager: DialogManager):
+async def name_handler(m: Message, dialog: ManagedDialogAdapterProto, manager: DialogManager):
     manager.current_context().dialog_data["last_text"] = m.text
     await m.answer(f"Nice to meet you, {m.text}")
 
@@ -78,18 +81,22 @@ async def on_click(c: CallbackQuery, button: Button, manager: DialogManager):
 )
 
 
+async def start(m: Message, dialog_manager: DialogManager):
+    await dialog_manager.start(DialogSG.greeting, mode=StartMode.NEW_STACK)
+
+
 async def main():
     # real main
     logging.basicConfig(level=logging.INFO)
     storage = MemoryStorage()
     bot = Bot(token=API_TOKEN)
-    dp = Dispatcher(bot, storage=storage)
+    dp = Dispatcher(storage=storage)
     registry = DialogRegistry(dp)
     # register handler which resets stack and start dialogs on /start command
-    registry.register_start_handler(DialogSG.greeting)
+    dp.message.register(start, Command(commands=('start', )))
     registry.register(dialog)
 
-    await dp.start_polling()
+    await dp.start_polling(bot)
 
 
 if __name__ == '__main__':
diff --git a/example/simple.py b/example/simple.py
index edd8f014..d97902f3 100644
--- a/example/simple.py
+++ b/example/simple.py
@@ -3,12 +3,14 @@
 import os.path
 from typing import Any
 
-from aiogram import Bot, Dispatcher
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram import Bot, Dispatcher, F
+from aiogram.dispatcher.event.bases import UNHANDLED
+from aiogram.fsm.state import StatesGroup, State
+from aiogram.fsm.storage.memory import MemoryStorage
 from aiogram.types import Message, CallbackQuery, ContentType
 
 from aiogram_dialog import Dialog, DialogManager, DialogRegistry, Window, ChatEvent, StartMode
+from aiogram_dialog.exceptions import UnknownIntent
 from aiogram_dialog.manager.protocols import ManagedDialogAdapterProto
 from aiogram_dialog.widgets.input import MessageInput
 from aiogram_dialog.widgets.kbd import Button, Select, Row, SwitchTo, Back
@@ -67,7 +69,7 @@ async def on_age_changed(c: ChatEvent, select: Any, manager: DialogManager,
             path=os.path.join(src_dir, "python_logo.png"),
             type=ContentType.PHOTO,
         ),
-        MessageInput(name_handler),
+        MessageInput(name_handler, content_types=[ContentType.TEXT]),
         state=DialogSG.greeting,
     ),
     Window(
@@ -105,17 +107,27 @@ async def start(m: Message, dialog_manager: DialogManager):
     await dialog_manager.start(DialogSG.greeting, mode=StartMode.RESET_STACK)
 
 
+async def error_handler(update, exception, dialog_manager: DialogManager):
+    """Example of handling UnknownIntent Error and starting new dialog"""
+    if isinstance(exception, UnknownIntent):
+        await dialog_manager.start(DialogSG.greeting, mode=StartMode.RESET_STACK)
+    else:
+        return UNHANDLED
+
+
 async def main():
     # real main
     logging.basicConfig(level=logging.INFO)
     storage = MemoryStorage()
     bot = Bot(token=API_TOKEN)
-    dp = Dispatcher(bot, storage=storage)
-    dp.register_message_handler(start, text="/start", state="*")
+    dp = Dispatcher(storage=storage)
+    dp.message.register(start, F.text == "/start")
+    dp.errors.register(error_handler)
+
     registry = DialogRegistry(dp)
     registry.register(dialog)
 
-    await dp.start_polling()
+    await dp.start_polling(bot)
 
 
 if __name__ == '__main__':
diff --git a/example/subdialog.py b/example/subdialog.py
index 87445608..0a887646 100644
--- a/example/subdialog.py
+++ b/example/subdialog.py
@@ -3,11 +3,12 @@
 from typing import Any
 
 from aiogram import Bot, Dispatcher
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
-from aiogram.dispatcher.filters.state import StatesGroup, State
+from aiogram.fsm.storage.memory import MemoryStorage
+from aiogram.fsm.state import StatesGroup, State
 from aiogram.types import Message, CallbackQuery
 
 from aiogram_dialog import Dialog, DialogManager, Window, DialogRegistry, Data
+from aiogram_dialog.manager.protocols import ManagedDialogAdapterProto
 from aiogram_dialog.tools import render_transitions, render_preview
 from aiogram_dialog.widgets.input import MessageInput
 from aiogram_dialog.widgets.kbd import Button, Group, Back, Cancel, Row, Start, Next
@@ -23,9 +24,9 @@ class NameSG(StatesGroup):
     confirm = State()
 
 
-async def name_handler(m: Message, dialog: Dialog, manager: DialogManager):
+async def name_handler(m: Message, dialog: ManagedDialogAdapterProto, manager: DialogManager):
     manager.current_context().dialog_data["name"] = m.text
-    await dialog.next(manager)
+    await dialog.next()
 
 
 async def get_name_data(dialog_manager: DialogManager, **kwargs):
@@ -52,6 +53,7 @@ async def on_finish(c: CallbackQuery, button: Button, manager: DialogManager):
         state=NameSG.confirm,
         getter=get_name_data,
         preview_add_transitions=[Cancel()],  # hint for graph rendering
+        preview_data={"name": "John Doe"}  # for preview rendering
     )
 )
 
@@ -88,6 +90,7 @@ async def on_reset_name(c: CallbackQuery, button: Button, manager: DialogManager
         ),
         state=MainSG.main,
         getter=get_main_data,
+        preview_data={"name": "John Doe"}  # for preview rendering
     ),
     on_process_result=process_result,
 )
@@ -98,14 +101,15 @@ async def main():
     logging.basicConfig(level=logging.INFO)
     storage = MemoryStorage()
     bot = Bot(token=API_TOKEN)
-    dp = Dispatcher(bot, storage=storage)
+    dp = Dispatcher(storage=storage)
     registry = DialogRegistry(dp)
     registry.register_start_handler(MainSG.main)  # resets stack and start dialogs on /start command
     registry.register(name_dialog)
     registry.register(main_menu)
     render_transitions(registry)  # render graph with current transtions
+    await render_preview(registry, "preview.html")  # render windows preview
 
-    await dp.start_polling()
+    await dp.start_polling(bot)
 
 
 if __name__ == '__main__':
diff --git a/requirements_dev.txt b/requirements_dev.txt
new file mode 100644
index 00000000..e878717e
--- /dev/null
+++ b/requirements_dev.txt
@@ -0,0 +1,16 @@
+vulture
+flake8==3.*
+flake8-blind-except
+flake8-bugbear
+flake8-builtins
+flake8-cognitive-complexity
+flake8-commas
+flake8-comprehensions
+flake8-docstrings
+flake8-eradicate
+flake8-import-order
+flake8-mutable
+flake8-polyfill
+flake8-print
+
+pytest
\ No newline at end of file
diff --git a/setup.py b/setup.py
index b105aaa5..02bb36c7 100644
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,4 @@
-  #!/usr/bin/env python3
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 from os import path
 
@@ -13,7 +13,7 @@
     description='Mini-framework for dialogs on top of aiogram',
     long_description=long_description,
     long_description_content_type='text/markdown',
-    version='1.8.0',
+    version='2.0.0b9',
     url='https://github.com/tishka17/aiogram_dialog',
     author='A. Tikhonov',
     author_email='17@itishka.org',
@@ -26,7 +26,7 @@
     ],
     packages=find_packages(include=['aiogram_dialog', 'aiogram_dialog.*']),
     install_requires=[
-        'aiogram>=2.20,<3',
+        'aiogram>=3.0.0b4,<4.0.0',
         'jinja2',
         'cachetools==4.*',
         'magic_filter',


Test patch:
diff --git a/tests/test_create.py b/tests/test_create.py
new file mode 100644
index 00000000..bd69a83b
--- /dev/null
+++ b/tests/test_create.py
@@ -0,0 +1,23 @@
+from aiogram import Dispatcher
+from aiogram.fsm.state import StatesGroup, State
+
+from aiogram_dialog import Dialog, Window, DialogRegistry
+from aiogram_dialog.widgets.text import Format
+
+
+class MainSG(StatesGroup):
+    start = State()
+
+
+dialog = Dialog(
+    Window(
+        Format("stub"),
+        state=MainSG.start,
+    )
+)
+
+
+def test_register():
+    dp = Dispatcher()
+    registry = DialogRegistry(dp)
+    registry.register(dialog)