Repository: aiogram/aiogram (python)

Issue:
Add as_caption_kwargs() method to aiogram.utils.formatting.Text
### aiogram version

3.x

### Problem

Methods like `Bot.send_document() ` use `caption` and `caption_enteties` parameters instead of `text` and `entities`. That results in repeating `Text(...).as_kwargs(text_key="caption", entities_key="caption_entities")` calls.

### Possible solution

I suggest introducing a shortcut method `as_caption_kwargs()` or something like that 

### Alternatives

_No response_

### Code example

```python3

```

### Additional information

_No response_

Pull request:
Add different shortcut methods for Text.as_kwargs()
New methods for `aiogram.utils.formatting.Text`:

- `as_caption_kwargs()`
- `as_poll_question_kwargs()`
- `as_poll_explanation_kwargs()`
- `as_gift_text_kwargs()`

Fixes #1656 

## Type of change

Please delete options that are not relevant.

- [x] New feature (non-breaking change which adds functionality)
- [x] This change requires a documentation update

Patch:
diff --git a/CHANGES/1657.feature.rst b/CHANGES/1657.feature.rst
new file mode 100644
index 00000000..cbb7c51b
--- /dev/null
+++ b/CHANGES/1657.feature.rst
@@ -0,0 +1,1 @@
+Add different shortcut methods for ``aiogram.utils.formatting.Text.as_kwargs()``
diff --git a/CHANGES/1659.misc.rst b/CHANGES/1659.misc.rst
new file mode 100644
index 00000000..88fdcd13
--- /dev/null
+++ b/CHANGES/1659.misc.rst
@@ -0,0 +1,2 @@
+Bump pydantic upper bound from <2.11 to <2.12.
+Upgrading `pydantic` to version 2.11 significantly reduces resource consumption, more details on the `pydantic blog post <https://pydantic.dev/articles/pydantic-v2-11-release>`_
diff --git a/aiogram/utils/formatting.py b/aiogram/utils/formatting.py
index 929afc07..88fa82b0 100644
--- a/aiogram/utils/formatting.py
+++ b/aiogram/utils/formatting.py
@@ -110,7 +110,7 @@ class Text(Iterable[NodeType]):
         parse_mode_key: str = "parse_mode",
     ) -> Dict[str, Any]:
         """
-        Render elements tree as keyword arguments for usage in the API call, for example:
+        Render element tree as keyword arguments for usage in an API call, for example:
 
         .. code-block:: python
 
@@ -132,6 +132,90 @@ class Text(Iterable[NodeType]):
             result[parse_mode_key] = None
         return result
 
+    def as_caption_kwargs(self, *, replace_parse_mode: bool = True) -> Dict[str, Any]:
+        """
+        Shortcut for :meth:`as_kwargs` for usage with API calls that take
+        ``caption`` as a parameter.
+
+        .. code-block:: python
+
+            entities = Text(...)
+            await message.answer_photo(**entities.as_caption_kwargs(), photo=phot)
+
+        :param replace_parse_mode: Will be passed to :meth:`as_kwargs`.
+        :return:
+        """
+        return self.as_kwargs(
+            text_key="caption",
+            entities_key="caption_entities",
+            replace_parse_mode=replace_parse_mode,
+        )
+
+    def as_poll_question_kwargs(self, *, replace_parse_mode: bool = True) -> Dict[str, Any]:
+        """
+        Shortcut for :meth:`as_kwargs` for usage with
+        method :class:`aiogram.methods.send_poll.SendPoll`.
+
+        .. code-block:: python
+
+            entities = Text(...)
+            await message.answer_poll(**entities.as_poll_question_kwargs(), options=options)
+
+        :param replace_parse_mode: Will be passed to :meth:`as_kwargs`.
+        :return:
+        """
+        return self.as_kwargs(
+            text_key="question",
+            entities_key="question_entities",
+            parse_mode_key="question_parse_mode",
+            replace_parse_mode=replace_parse_mode,
+        )
+
+    def as_poll_explanation_kwargs(self, *, replace_parse_mode: bool = True) -> Dict[str, Any]:
+        """
+        Shortcut for :meth:`as_kwargs` for usage with
+        method :class:`aiogram.methods.send_poll.SendPoll`.
+
+        .. code-block:: python
+
+            question_entities = Text(...)
+            explanation_entities = Text(...)
+            await message.answer_poll(
+                **question_entities.as_poll_question_kwargs(),
+                options=options,
+                **explanation_entities.as_poll_explanation_kwargs(),
+            )
+
+        :param replace_parse_mode: Will be passed to :meth:`as_kwargs`.
+        :return:
+        """
+        return self.as_kwargs(
+            text_key="explanation",
+            entities_key="explanation_entities",
+            parse_mode_key="explanation_parse_mode",
+            replace_parse_mode=replace_parse_mode,
+        )
+
+    def as_gift_text_kwargs(self, *, replace_parse_mode: bool = True) -> Dict[str, Any]:
+        """
+        Shortcut for :meth:`as_kwargs` for usage with
+        method :class:`aiogram.methods.send_gift.SendGift`.
+
+        .. code-block:: python
+
+            entities = Text(...)
+            await bot.send_gift(gift_id=gift_id, user_id=user_id, **entities.as_gift_text_kwargs())
+
+        :param replace_parse_mode: Will be passed to :meth:`as_kwargs`.
+        :return:
+        """
+        return self.as_kwargs(
+            text_key="text",
+            entities_key="text_entities",
+            parse_mode_key="text_parse_mode",
+            replace_parse_mode=replace_parse_mode,
+        )
+
     def as_html(self) -> str:
         """
         Render elements tree as HTML markup
diff --git a/pyproject.toml b/pyproject.toml
index be73b725..1314ae68 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,7 +43,7 @@ classifiers = [
 dependencies = [
     "magic-filter>=1.0.12,<1.1",
     "aiohttp>=3.9.0,<3.12",
-    "pydantic>=2.4.1,<2.11",
+    "pydantic>=2.4.1,<2.12",
     "aiofiles>=23.2.1,<24.2",
     "certifi>=2023.7.22",
     "typing-extensions>=4.7.0,<=5.0",


Test patch:
diff --git a/tests/test_utils/test_formatting.py b/tests/test_utils/test_formatting.py
index dbe07d17..ffaef31e 100644
--- a/tests/test_utils/test_formatting.py
+++ b/tests/test_utils/test_formatting.py
@@ -172,6 +172,34 @@ class TestNode:
         assert "parse_mode" not in result
         assert "custom_parse_mode" in result
 
+    def test_as_caption_kwargs(self):
+        node = Text("Hello, ", Bold("World"), "!")
+        result = node.as_caption_kwargs()
+        assert "caption" in result
+        assert "caption_entities" in result
+        assert "parse_mode" in result
+
+    def test_as_poll_question_kwargs(self):
+        node = Text("Hello, ", Bold("World"), "!")
+        result = node.as_poll_question_kwargs()
+        assert "question" in result
+        assert "question_entities" in result
+        assert "question_parse_mode" in result
+
+    def test_as_poll_explanation_kwargs(self):
+        node = Text("Hello, ", Bold("World"), "!")
+        result = node.as_poll_explanation_kwargs()
+        assert "explanation" in result
+        assert "explanation_entities" in result
+        assert "explanation_parse_mode" in result
+
+    def test_as_as_gift_text_kwargs_kwargs(self):
+        node = Text("Hello, ", Bold("World"), "!")
+        result = node.as_gift_text_kwargs()
+        assert "text" in result
+        assert "text_entities" in result
+        assert "text_parse_mode" in result
+
     def test_as_html(self):
         node = Text("Hello, ", Bold("World"), "!")
         assert node.as_html() == "Hello, <b>World</b>!"