Repository: aiogram/aiogram
Pull request #1147 (2023-03-19 12:17:14)

skip if current router does not have observer for custom event
# Description

Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.

Fixes # (issue)

## Type of change

Please delete options that are not relevant.

- [ ] Documentation (typos, code examples or any documentation update)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

# How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

- [x] tests/test_dispatcher/test_router.py::TestRouter::test_custom_evenv_nested_router

**Test Configuration**:
* Operating System: Ubuntu 20.04.1
* Python version: 3.8.16

# Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes


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

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

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

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

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

```diff
@@            Coverage Diff            @@
##           dev-3.x     #1147   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          354       354           
  Lines         8791      8796    +5     
=========================================
+ Hits          8791      8796    +5     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `100.00% <100.00%> (ø)` | |

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

| [Impacted Files](https://codecov.io/gh/aiogram/aiogram/pull/1147?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) | Coverage Δ | |
|---|---|---|
| [aiogram/dispatcher/event/telegram.py](https://codecov.io/gh/aiogram/aiogram/pull/1147?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9kaXNwYXRjaGVyL2V2ZW50L3RlbGVncmFtLnB5) | `100.00% <100.00%> (ø)` | |
| [aiogram/dispatcher/router.py](https://codecov.io/gh/aiogram/aiogram/pull/1147?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9kaXNwYXRjaGVyL3JvdXRlci5weQ==) | `100.00% <100.00%> (ø)` | |


Patch:
diff --git a/CHANGES/1147.feature b/CHANGES/1147.feature
new file mode 100644
index 00000000..fa34e703
--- /dev/null
+++ b/CHANGES/1147.feature
@@ -0,0 +1,1 @@
+If router does not support custom event it does not break and passes it to included routers
\ No newline at end of file
diff --git a/CHANGES/1155.bugfix.rst b/CHANGES/1155.bugfix.rst
new file mode 100644
index 00000000..753ffc96
--- /dev/null
+++ b/CHANGES/1155.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed signature of startup/shutdown events to include the **dispatcher.workflow_data as the handler arguments.
diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py
index 71791f72..8485d83c 100644
--- a/aiogram/dispatcher/dispatcher.py
+++ b/aiogram/dispatcher/dispatcher.py
@@ -80,9 +80,9 @@ class Dispatcher(Router):
         # FSM middleware should always be registered after User context middleware
         # because here is used context from previous step
         self.fsm = FSMContextMiddleware(
-            storage=storage if storage else MemoryStorage(),
+            storage=storage or MemoryStorage(),
             strategy=fsm_strategy,
-            events_isolation=events_isolation if events_isolation else DisabledEventIsolation(),
+            events_isolation=events_isolation or DisabledEventIsolation(),
         )
         if not disable_fsm:
             # Note that when FSM middleware is disabled, the event isolation is also disabled
@@ -251,14 +251,14 @@ class Dispatcher(Router):
         try:
             update_type = update.event_type
             event = update.event
-        except UpdateTypeLookupError:
+        except UpdateTypeLookupError as e:
             warnings.warn(
                 "Detected unknown update type.\n"
                 "Seems like Telegram Bot API was updated and you have "
                 "installed not latest version of aiogram framework",
                 RuntimeWarning,
             )
-            raise SkipHandler()
+            raise SkipHandler() from e
 
         kwargs.update(event_update=update)
 
@@ -373,7 +373,7 @@ class Dispatcher(Router):
         loop = asyncio.get_running_loop()
         waiter = loop.create_future()
 
-        def release_waiter(*args: Any) -> None:
+        def release_waiter(*_: Any) -> None:
             if not waiter.done():
                 waiter.set_result(None)
 
@@ -488,8 +488,13 @@ class Dispatcher(Router):
                         signal.SIGINT, self._signal_stop_polling, signal.SIGINT
                     )
 
-            workflow_data = {"dispatcher": self, "bots": bots, "bot": bots[-1]}
-            workflow_data.update(kwargs)
+            workflow_data = {
+                "dispatcher": self,
+                "bots": bots,
+                "bot": bots[-1],
+                **kwargs,
+                **self.workflow_data,
+            }
             await self.emit_startup(**workflow_data)
             loggers.dispatcher.info("Start polling")
             try:
diff --git a/aiogram/dispatcher/event/telegram.py b/aiogram/dispatcher/event/telegram.py
index bb943004..a468816c 100644
--- a/aiogram/dispatcher/event/telegram.py
+++ b/aiogram/dispatcher/event/telegram.py
@@ -49,8 +49,9 @@ class TelegramEventObserver:
     def _resolve_middlewares(self) -> List[MiddlewareType[TelegramObject]]:
         middlewares: List[MiddlewareType[TelegramObject]] = []
         for router in reversed(tuple(self.router.chain_head)):
-            observer = router.observers[self.event_name]
-            middlewares.extend(observer.middleware)
+            observer = router.observers.get(self.event_name)
+            if observer:
+                middlewares.extend(observer.middleware)
 
         return middlewares
 
diff --git a/aiogram/dispatcher/router.py b/aiogram/dispatcher/router.py
index 86359ce0..85c41ae8 100644
--- a/aiogram/dispatcher/router.py
+++ b/aiogram/dispatcher/router.py
@@ -105,27 +105,31 @@ class Router:
 
     async def propagate_event(self, update_type: str, event: TelegramObject, **kwargs: Any) -> Any:
         kwargs.update(event_router=self)
-        observer = self.observers[update_type]
+        observer = self.observers.get(update_type)
 
         async def _wrapped(telegram_event: TelegramObject, **data: Any) -> Any:
             return await self._propagate_event(
                 observer=observer, update_type=update_type, event=telegram_event, **data
             )
 
-        return await observer.wrap_outer_middleware(_wrapped, event=event, data=kwargs)
+        if observer:
+            return await observer.wrap_outer_middleware(_wrapped, event=event, data=kwargs)
+        return await _wrapped(event, **kwargs)
 
     async def _propagate_event(
         self,
-        observer: TelegramEventObserver,
+        observer: Optional[TelegramEventObserver],
         update_type: str,
         event: TelegramObject,
         **kwargs: Any,
     ) -> Any:
-        response = await observer.trigger(event, **kwargs)
-        if response is REJECTED:
-            return UNHANDLED
-        if response is not UNHANDLED:
-            return response
+        response = UNHANDLED
+        if observer:
+            response = await observer.trigger(event, **kwargs)
+            if response is REJECTED:
+                return UNHANDLED
+            if response is not UNHANDLED:
+                return response
 
         for router in self.sub_routers:
             response = await router.propagate_event(update_type=update_type, event=event, **kwargs)
diff --git a/aiogram/filters/magic_data.py b/aiogram/filters/magic_data.py
index 351c8e4f..5b3c31d7 100644
--- a/aiogram/filters/magic_data.py
+++ b/aiogram/filters/magic_data.py
@@ -18,7 +18,7 @@ class MagicData(Filter):
 
     async def __call__(self, event: TelegramObject, *args: Any, **kwargs: Any) -> Any:
         return self.magic_data.resolve(
-            AttrDict({"event": event, **{k: v for k, v in enumerate(args)}, **kwargs})
+            AttrDict({"event": event, **dict(enumerate(args)), **kwargs})
         )
 
     def __str__(self) -> str:
diff --git a/aiogram/utils/deep_linking.py b/aiogram/utils/deep_linking.py
index ec3f9bff..3d27633f 100644
--- a/aiogram/utils/deep_linking.py
+++ b/aiogram/utils/deep_linking.py
@@ -15,8 +15,9 @@ Basic link example:
 
     .. code-block:: python
 
-        from aiogram.utils.deep_linking import get_start_link
-        link = await get_start_link('foo')
+        from aiogram.utils.deep_linking import create_start_link
+        
+        link = await create_start_link(bot, 'foo')
 
         # result: 'https://t.me/MyBot?start=foo'
 
@@ -24,20 +25,21 @@ Encoded link example:
 
     .. code-block:: python
 
-        from aiogram.utils.deep_linking import get_start_link
+        from aiogram.utils.deep_linking import create_start_link
 
-        link = await get_start_link('foo', encode=True)
+        link = await create_start_link(bot, 'foo', encode=True)
         # result: 'https://t.me/MyBot?start=Zm9v'
 
 Decode it back example:
     .. code-block:: python
 
         from aiogram.utils.deep_linking import decode_payload
+        from aiogram.filters import CommandStart, CommandObject
         from aiogram.types import Message
 
-        @dp.message_handler(commands=["start"])
-        async def handler(message: Message):
-            args = message.get_args()
+        @router.message(CommandStart(deep_link=True))
+        async def handler(message: Message, command: CommandObject):
+            args = command.args
             payload = decode_payload(args)
             await message.answer(f"Your payload: {payload}")
 


Test patch:
diff --git a/tests/test_dispatcher/test_router.py b/tests/test_dispatcher/test_router.py
index 3404822d..1ac78480 100644
--- a/tests/test_dispatcher/test_router.py
+++ b/tests/test_dispatcher/test_router.py
@@ -1,6 +1,7 @@
 import pytest
 
 from aiogram.dispatcher.event.bases import UNHANDLED, SkipHandler, skip
+from aiogram.dispatcher.event.telegram import TelegramEventObserver
 from aiogram.dispatcher.router import Router
 
 
@@ -161,3 +162,20 @@ class TestRouter:
         assert tuple(r2_1.chain_head) == (r2_1, r1)
         assert tuple(r2_2.chain_head) == (r2_2, r1)
         assert tuple(r3.chain_head) == (r3, r2_1, r1)
+
+    async def test_custom_evenv_nested_router(self):
+        r1 = Router()
+        r2 = Router()
+        r3 = Router()
+        r3.observers["custom-event"] = TelegramEventObserver(r3, event_name="custom-event")
+
+        async def handler(evt):
+            return evt
+
+        r1.include_router(r2)
+        r1.include_router(r3)
+        r3.observers["custom-event"].register(handler)
+
+        assert await r1.propagate_event(update_type="custom-event", event=None) is None
+        assert await r2.propagate_event(update_type="custom-event", event=None) is UNHANDLED
+        assert await r3.propagate_event(update_type="custom-event", event=None) is None