Repository: aiogram/aiogram
Pull request #1154 (2023-04-02 17:15:59)

fix(docs): fix wrong page link in docs
# Description

Wrong link to input_file docs page.
Also, it was changed in #1133


## 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)
- [ ] 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

- [ ] Test A
- [ ] Test B

**Test Configuration**:
* Operating System:
* Python version:

# 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
- [ ] 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)

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}")
 
diff --git a/docs/api/upload_file.rst b/docs/api/upload_file.rst
index 9399f1dd..715474f7 100644
--- a/docs/api/upload_file.rst
+++ b/docs/api/upload_file.rst
@@ -10,7 +10,7 @@ there are three ways to send files (photos, stickers, audio, media, etc.):
 If the file is already stored somewhere on the Telegram servers or file is available by the URL,
 you don't need to reupload it.
 
-But if you need to upload a new file just use subclasses of `InputFile <types/input_file.md>`__.
+But if you need to upload a new file just use subclasses of `InputFile <types/input_file.html>`__.
 
 Here are the three different available builtin types of input file:
 


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