Repository: aiogram/aiogram
Pull request #1146 (2023-03-18 11:41:54)

Change `InlineQueryResultType.MPEG` to more correct
# Description

Change type of result in `InlineQueryResult` enum for `InlineQueryResultCachedMpeg4Gif]` and `InlineQueryResultMpeg4Gif` to more correct according to documentation

## Type of change

- [ ] Documentation (typos, code examples or any documentation update)
- [ ] Bug fix (non-breaking change which fixes an issue)

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)
JrooTJunior: This part of code should not be changed manually, because we uses codegeneration and this fields will be changed again.


You can try to fix it here: https://github.com/aiogram/butcher
And here: https://github.com/aiogram/aiogram/tree/dev-3.x/.butcher

Or just provide an MRE to check this feature. 

Patch:
diff --git a/.butcher/enums/InlineQueryResultType.yml b/.butcher/enums/InlineQueryResultType.yml
index 74428a3c..2d823438 100644
--- a/.butcher/enums/InlineQueryResultType.yml
+++ b/.butcher/enums/InlineQueryResultType.yml
@@ -5,7 +5,7 @@ description: |
   Source: https://core.telegram.org/bots/api#inlinequeryresult
 multi_parse:
   attribute: type
-  regexp: "must be ([a-z_]+)"
+  regexp: "must be ([a-z_0-9]+)"
   entities:
   - InlineQueryResultCachedAudio
   - InlineQueryResultCachedDocument
diff --git a/CHANGES/1146.bugfix.rst b/CHANGES/1146.bugfix.rst
new file mode 100644
index 00000000..fbe88e4c
--- /dev/null
+++ b/CHANGES/1146.bugfix.rst
@@ -0,0 +1,4 @@
+Change type of result in InlineQueryResult enum for `InlineQueryResultCachedMpeg4Gif`
+and `InlineQueryResultMpeg4Gif` to more correct according to documentation.
+
+Change regexp for entities parsing to more correct (`InlineQueryResultType.yml`).
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/enums/inline_query_result_type.py b/aiogram/enums/inline_query_result_type.py
index 771dc498..ae20045a 100644
--- a/aiogram/enums/inline_query_result_type.py
+++ b/aiogram/enums/inline_query_result_type.py
@@ -11,7 +11,7 @@ class InlineQueryResultType(str, Enum):
     AUDIO = "audio"
     DOCUMENT = "document"
     GIF = "gif"
-    MPEG = "mpeg"
+    MPEG4_GIF = "mpeg4_gif"
     PHOTO = "photo"
     STICKER = "sticker"
     VIDEO = "video"
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/types/inline_query_result_cached_mpeg4_gif.py b/aiogram/types/inline_query_result_cached_mpeg4_gif.py
index ab7a2a9b..539de025 100644
--- a/aiogram/types/inline_query_result_cached_mpeg4_gif.py
+++ b/aiogram/types/inline_query_result_cached_mpeg4_gif.py
@@ -21,7 +21,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
     Source: https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif
     """
 
-    type: str = Field(InlineQueryResultType.MPEG, const=True)
+    type: str = Field(InlineQueryResultType.MPEG4_GIF, const=True)
     """Type of the result, must be *mpeg4_gif*"""
     id: str
     """Unique identifier for this result, 1-64 bytes"""
diff --git a/aiogram/types/inline_query_result_mpeg4_gif.py b/aiogram/types/inline_query_result_mpeg4_gif.py
index 44a61fee..06c73620 100644
--- a/aiogram/types/inline_query_result_mpeg4_gif.py
+++ b/aiogram/types/inline_query_result_mpeg4_gif.py
@@ -21,7 +21,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
     Source: https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif
     """
 
-    type: str = Field(InlineQueryResultType.MPEG, const=True)
+    type: str = Field(InlineQueryResultType.MPEG4_GIF, const=True)
     """Type of the result, must be *mpeg4_gif*"""
     id: str
     """Unique identifier for this result, 1-64 bytes"""
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