Repository: aiogram/aiogram
Pull request #1253 (2023-08-08 17:04:58)

Added a section on Dependency Injection technology in documentation
# Description

I often noticed that developers ignored the existence of the concept of dependency injection, using global variables or singleton instead. Previously, this topic was not covered in the documentation, which I corrected.

## Type of change

- [x] Documentation (typos, code examples or any documentation update)

# Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [x] 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]: # :heavy_check_mark: Changelog found.

Thank you for adding a description of the changes

Patch:
diff --git a/CHANGES/1253.doc.rst b/CHANGES/1253.doc.rst
new file mode 100644
index 00000000..d8100687
--- /dev/null
+++ b/CHANGES/1253.doc.rst
@@ -0,0 +1,1 @@
+Added a section on Dependency Injection technology
diff --git a/CHANGES/1256.doc.rst b/CHANGES/1256.doc.rst
new file mode 100644
index 00000000..2b8af247
--- /dev/null
+++ b/CHANGES/1256.doc.rst
@@ -0,0 +1,1 @@
+Refactored examples code to use aiogram enumerations and enhanced chat messages with markdown beautifications for a more user-friendly display.
diff --git a/CHANGES/1259.bugfix.rst b/CHANGES/1259.bugfix.rst
new file mode 100644
index 00000000..e070549d
--- /dev/null
+++ b/CHANGES/1259.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed nested hashtag, cashtag and email message entities not being parsed correctly when these entities are inside another entity.
diff --git a/aiogram/utils/text_decorations.py b/aiogram/utils/text_decorations.py
index 97430a32..eaff6f2c 100644
--- a/aiogram/utils/text_decorations.py
+++ b/aiogram/utils/text_decorations.py
@@ -43,8 +43,11 @@ class TextDecoration(ABC):
             MessageEntityType.URL,
             MessageEntityType.MENTION,
             MessageEntityType.PHONE_NUMBER,
+            MessageEntityType.HASHTAG,
+            MessageEntityType.CASHTAG,
+            MessageEntityType.EMAIL,
         }:
-            # This entities should not be changed
+            # These entities should not be changed
             return text
         if entity.type in {
             MessageEntityType.BOLD,
@@ -71,6 +74,8 @@ class TextDecoration(ABC):
         if entity.type == MessageEntityType.CUSTOM_EMOJI:
             return self.custom_emoji(value=text, custom_emoji_id=cast(str, entity.custom_emoji_id))
 
+        # This case is not possible because of `if` above, but if any new entity is added to
+        # API it will be here too
         return self.quote(text)
 
     def unparse(self, text: str, entities: Optional[List[MessageEntity]] = None) -> str:
diff --git a/docs/dispatcher/dependency_injection.rst b/docs/dispatcher/dependency_injection.rst
new file mode 100644
index 00000000..15b7c908
--- /dev/null
+++ b/docs/dispatcher/dependency_injection.rst
@@ -0,0 +1,56 @@
+####################
+Dependency injection
+####################
+
+Dependency injection is a programming technique that makes a class independent of its dependencies. It achieves that by decoupling the usage of an object from its creation. This helps you to follow `SOLID's <https://en.wikipedia.org/wiki/SOLID>`_ dependency inversion and single responsibility principles.
+
+
+How it works in aiogram
+=======================
+For each update :class:`Dispatcher` passes handling context data. Filters and middleware can also make changes to the context.
+
+To access contextual data you should specify corresponding keyword parameter in handler or filter. For example, to get :class:`FSMContext` we do it like that:
+
+.. code-block:: python
+    @router.message(ProfileCompletion.add_photo, F.photo)
+    async def add_photo(
+        message: types.Message, bot: Bot, state: FSMContext
+    ) -> Any:
+        ... # do something with photo
+
+
+Injecting own dependencies
+==========================
+
+Aiogram provides several ways to complement / modify contextual data.
+
+The first and easiest way is to simply specify the named arguments in :class:`Dispatcher` initialization, polling start methods or :class:`SimpleRequestHandler` initialization if you use webhooks.
+
+.. code-block:: python
+    async def main() -> None:
+        dp = Dispatcher(..., foo=42)
+        return await dp.start_polling(
+            bot, allowed_updates=dp.resolve_used_update_types(), bar="Bazz"
+        )
+Analogy for webhook:
+
+.. code-block:: python
+    async def main() -> None:
+        dp = Dispatcher(..., foo=42)
+        handler = SimpleRequestHandler(dispatcher=dp, bot=bot, bar="Bazz")
+        ... # starting webhook
+:class:`Dispatcher`'s workflow data also can be supplemented by setting values as in a dictionary:
+
+.. code-block:: python
+    dp = Dispatcher(...)
+    dp["eggs"] = Spam()
+The middlewares updates the context quite often.
+You can read more about them on this page:
+
+- `Middlewares <middlewares.html>`__
+
+The last way is to return a dictionary from the filter:
+
+.. literalinclude:: ../../../examples/context_addition_from_filter.py
+
+...or using MagicFilter with :code:`as_()` method. (`Read more <filters/magic_filters.html#get-filter-result-as-handler-argument>`__)
diff --git a/examples/context_addition_from_filter.py b/examples/context_addition_from_filter.py
new file mode 100644
index 00000000..e4eca517
--- /dev/null
+++ b/examples/context_addition_from_filter.py
@@ -0,0 +1,33 @@
+from typing import Any, Dict, Optional, Union
+
+from aiogram import Router
+from aiogram.filters import Filter
+from aiogram.types import Message, User
+
+
+router = Router(name=__name__)
+
+
+class HelloFilter(Filter):
+    def __init__(self, name: Optional[str] = None) -> None:
+        self.name = name
+
+    async def __call__(
+        self,
+        message: Message,
+        event_from_user: User
+        # Filters also can accept keyword parameters like in handlers
+    ) -> Union[bool, Dict[str, Any]]:
+        if message.text.casefold() == "hello":
+            # Returning a dictionary that will update the context data
+            return {"name": event_from_user.mention_html(name=self.name)}
+        return False
+
+
+@router.message(HelloFilter())
+async def my_handler(
+    message: Message, name: str  # Now we can accept "name" as named parameter
+) -> Any:
+    return message.answer(
+        "Hello, {name}!".format(name=name)
+    )
diff --git a/examples/specify_updates.py b/examples/specify_updates.py
index b3c26786..ead296ea 100644
--- a/examples/specify_updates.py
+++ b/examples/specify_updates.py
@@ -72,8 +72,8 @@ deep_dark_router = Router()
 async def my_chat_member_change(chat_member: ChatMemberUpdated, bot: Bot) -> None:
     await bot.send_message(
         chat_member.chat.id,
-        "This Bot`s status was changed from "
-        + f"{hbold(chat_member.old_chat_member.status)} to {hbold(chat_member.new_chat_member.status)}",
+        f"This Bot`s status was changed from {hbold(chat_member.old_chat_member.status)} "
+        f"to {hbold(chat_member.new_chat_member.status)}",
     )
 
 


Test patch:
diff --git a/tests/test_utils/test_text_decorations.py b/tests/test_utils/test_text_decorations.py
index 056bd1cb..25f222f0 100644
--- a/tests/test_utils/test_text_decorations.py
+++ b/tests/test_utils/test_text_decorations.py
@@ -113,6 +113,14 @@ class TestTextDecoration:
     ):
         assert decorator.apply_entity(entity, "test") == result
 
+    def test_unknown_apply_entity(self):
+        assert (
+            html_decoration.apply_entity(
+                MessageEntity(type="unknown", offset=0, length=5), "<test>"
+            )
+            == "&lt;test&gt;"
+        )
+
     @pytest.mark.parametrize(
         "decorator,before,after",
         [
@@ -243,6 +251,33 @@ class TestTextDecoration:
                 [MessageEntity(type="bold", offset=0, length=8, url=None, user=None)],
                 "<b>👋🏾 Hi!</b>",
             ],
+            [
+                html_decoration,
+                "#test",
+                [
+                    MessageEntity(type="hashtag", offset=0, length=5),
+                    MessageEntity(type="bold", offset=0, length=5),
+                ],
+                "<b>#test</b>",
+            ],
+            [
+                html_decoration,
+                "$TEST",
+                [
+                    MessageEntity(type="cashtag", offset=0, length=5),
+                    MessageEntity(type="bold", offset=0, length=5),
+                ],
+                "<b>$TEST</b>",
+            ],
+            [
+                html_decoration,
+                "test@example.com",
+                [
+                    MessageEntity(type="email", offset=0, length=16),
+                    MessageEntity(type="bold", offset=0, length=16),
+                ],
+                "<b>test@example.com</b>",
+            ],
         ],
     )
     def test_unparse(