Repository: aiogram/aiogram
Pull request #749 (2021-11-14 17:12:59)

fixed a spelling error in the documentation
# 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)
- [ ] 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


codecov[bot]: # [Codecov](https://codecov.io/gh/aiogram/aiogram/pull/749?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) Report
> Merging [#749](https://codecov.io/gh/aiogram/aiogram/pull/749?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) (775dbe9) into [dev-3.x](https://codecov.io/gh/aiogram/aiogram/commit/092b3f06f20c813d09b9ffe08316e53d58b6c8df?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) (092b3f0) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/aiogram/aiogram/pull/749/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/749?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      #749   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          277       277           
  Lines         7302      7302           
=========================================
  Hits          7302      7302           
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `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.



Patch:
diff --git a/CHANGES/751.bugfix b/CHANGES/751.bugfix
new file mode 100644
index 00000000..ddf1e524
--- /dev/null
+++ b/CHANGES/751.bugfix
@@ -0,0 +1,1 @@
+Fixed: Missing ChatMemberHandler import in aiogram/dispatcher/handler
diff --git a/CHANGES/759.feature b/CHANGES/759.feature
new file mode 100644
index 00000000..da4a0d99
--- /dev/null
+++ b/CHANGES/759.feature
@@ -0,0 +1,13 @@
+Added new custom operation for MagicFilter named :code:`as_`
+
+Now you can use it to get magic filter result as handler argument
+
+.. code-block:: python
+
+    from aiogram import F
+
+    ...
+
+    @router.message(F.text.regexp(r"^(\d+)$").as_("digits"))
+    async def any_digits_handler(message: Message, digits: Match[str]):
+        await message.answer(html.quote(str(digits)))
diff --git a/aiogram/__init__.py b/aiogram/__init__.py
index f1c4813e..32e6a5e5 100644
--- a/aiogram/__init__.py
+++ b/aiogram/__init__.py
@@ -1,11 +1,10 @@
-from magic_filter import MagicFilter
-
 from .client import session
 from .client.bot import Bot
 from .dispatcher import filters, handler
 from .dispatcher.dispatcher import Dispatcher
 from .dispatcher.middlewares.base import BaseMiddleware
 from .dispatcher.router import Router
+from .utils.magic_filter import MagicFilter
 from .utils.text_decorations import html_decoration as _html_decoration
 from .utils.text_decorations import markdown_decoration as _markdown_decoration
 
@@ -37,5 +36,5 @@ __all__ = (
     "md",
 )
 
-__version__ = "3.0.0a18"
+__version__ = "3.0.0a19"
 __api_version__ = "5.4"
diff --git a/aiogram/dispatcher/handler/__init__.py b/aiogram/dispatcher/handler/__init__.py
index 7388a80c..54837881 100644
--- a/aiogram/dispatcher/handler/__init__.py
+++ b/aiogram/dispatcher/handler/__init__.py
@@ -1,6 +1,6 @@
 from .base import BaseHandler, BaseHandlerMixin
 from .callback_query import CallbackQueryHandler
-from .chat_member import ChatMemberUpdated
+from .chat_member import ChatMemberHandler
 from .chosen_inline_result import ChosenInlineResultHandler
 from .error import ErrorHandler
 from .inline_query import InlineQueryHandler
@@ -13,7 +13,7 @@ __all__ = (
     "BaseHandler",
     "BaseHandlerMixin",
     "CallbackQueryHandler",
-    "ChatMemberUpdated",
+    "ChatMemberHandler",
     "ChosenInlineResultHandler",
     "ErrorHandler",
     "InlineQueryHandler",
diff --git a/aiogram/utils/magic_filter.py b/aiogram/utils/magic_filter.py
new file mode 100644
index 00000000..a00d0010
--- /dev/null
+++ b/aiogram/utils/magic_filter.py
@@ -0,0 +1,21 @@
+from typing import Any
+
+from magic_filter import MagicFilter as _MagicFilter
+from magic_filter import MagicT as _MagicT
+from magic_filter.operations import BaseOperation
+
+
+class AsFilterResultOperation(BaseOperation):
+    __slots__ = ("name",)
+
+    def __init__(self, name: str) -> None:
+        self.name = name
+
+    def resolve(self, value: Any, initial_value: Any) -> Any:
+        if value:
+            return {self.name: value}
+
+
+class MagicFilter(_MagicFilter):
+    def as_(self: _MagicT, name: str) -> _MagicT:
+        return self._extend(AsFilterResultOperation(name=name))
diff --git a/docs/dispatcher/filters/magic_filters.rst b/docs/dispatcher/filters/magic_filters.rst
index f60dca5d..3117aeb3 100644
--- a/docs/dispatcher/filters/magic_filters.rst
+++ b/docs/dispatcher/filters/magic_filters.rst
@@ -16,7 +16,11 @@ That's mean you can install it and use with any other libraries and in own proje
 Usage
 =====
 
-The **magic_filter** package implements class shortly named :class:`magic_filter.F` that's mean :code:`F` can be imported from :code:`magic_filter`. :class:`F` is alias for :class:`MagicFilter`.
+The **magic_filter** package implements class shortly named :class:`magic_filter.F` that's mean :code:`F` can be imported from :code:`aiogram` or :code:`magic_filter`. :class:`F` is alias for :class:`MagicFilter`.
+
+.. note::
+
+    Note that *aiogram* has an small extension over magic-filter and if you want to use this extension you should import magic from *aiogram* instead of *magic_filter* package
 
 The :class:`MagicFilter` object is callable, supports :ref:`some actions <magic-filter-possible-actions>`
 and memorize the attributes chain and the action which should be checked on demand.
@@ -130,6 +134,21 @@ Can be used only with string attributes.
     F.text.len() == 5  # lambda message: len(message.text) == 5
 
 
+Get filter result as handler argument
+-------------------------------------
+
+This part is not available in *magic-filter* directly but can be used with *aiogram*
+
+.. code-block:: python
+
+    from aiogram import F
+
+    ...
+
+    @router.message(F.text.regexp(r"^(\d+)$").as_("digits"))
+    async def any_digits_handler(message: Message, digits: Match[str]):
+        await message.answer(html.quote(str(digits)))
+
 Usage in *aiogram*
 ==================
 
diff --git a/docs/dispatcher/index.rst b/docs/dispatcher/index.rst
index 1d0f469e..935522b2 100644
--- a/docs/dispatcher/index.rst
+++ b/docs/dispatcher/index.rst
@@ -2,7 +2,7 @@
 Handling events
 ===============
 
-*aiogram* imcludes Dispatcher mechanism.
+*aiogram* includes Dispatcher mechanism.
 Dispatcher is needed for handling incoming updates from Telegram.
 
 With dispatcher you can do:
diff --git a/poetry.lock b/poetry.lock
index 20905344..3ed1df7c 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -513,7 +513,7 @@ tornado = {version = "*", markers = "python_version > \"2.7\""}
 
 [[package]]
 name = "magic-filter"
-version = "1.0.3"
+version = "1.0.4"
 description = "This package provides magic filter based on dynamic attribute getter"
 category = "main"
 optional = false
@@ -917,7 +917,7 @@ pytest = ">=3.5"
 
 [[package]]
 name = "python-socks"
-version = "1.2.4"
+version = "2.0.0"
 description = "Core proxy (SOCKS4, SOCKS5, HTTP tunneling) functionality for Python"
 category = "main"
 optional = true
@@ -927,6 +927,7 @@ python-versions = "*"
 async-timeout = {version = ">=3.0.1", optional = true, markers = "extra == \"asyncio\""}
 
 [package.extras]
+anyio = ["anyio (>=3.3.4)"]
 asyncio = ["async-timeout (>=3.0.1)"]
 curio = ["curio (>=1.4)"]
 trio = ["trio (>=0.16.0)"]
@@ -1323,7 +1324,7 @@ redis = ["aioredis"]
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.8"
-content-hash = "de8ae82fb5f86da3377cc87aab35239ec5baddac4e0151595b5c4f6152d95ac8"
+content-hash = "27d602728b6dab256d184fbd44953030676edf320652ad828c6e1b4beaa80f8b"
 
 [metadata.files]
 aiofiles = [
@@ -1681,8 +1682,8 @@ livereload = [
     {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"},
 ]
 magic-filter = [
-    {file = "magic-filter-1.0.3.tar.gz", hash = "sha256:f39c1a27bdbdf5bec2e9ccfc4269557f9812345559ddd656926220f28921492b"},
-    {file = "magic_filter-1.0.3-py3-none-any.whl", hash = "sha256:e1139dad0b0f6426894d3fd214fb7d5bb1f964e8748141cb902414020e5ceb71"},
+    {file = "magic-filter-1.0.4.tar.gz", hash = "sha256:2b77de98bfef16a990c22b2a68a904b4a99913b656d48cf877367da8ae5f5c84"},
+    {file = "magic_filter-1.0.4-py3-none-any.whl", hash = "sha256:daf869025b9490c4438f1c2d3f4f9bcb8295fa0ffa7fec643bc20486f519f349"},
 ]
 markdown = [
     {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"},
@@ -2002,8 +2003,8 @@ pytest-mypy = [
     {file = "pytest_mypy-0.8.1-py3-none-any.whl", hash = "sha256:6e68e8eb7ceeb7d1c83a1590912f784879f037b51adfb9c17b95c6b2fc57466b"},
 ]
 python-socks = [
-    {file = "python-socks-1.2.4.tar.gz", hash = "sha256:7d0ef2578cead9f762b71317d25a6c118fabaf79535555e75b3e102f5158ddd8"},
-    {file = "python_socks-1.2.4-py3-none-any.whl", hash = "sha256:9f12e8fe78629b87543fad0e4ea0ccf103a4fad6a7872c5d0ecb36d9903fa548"},
+    {file = "python-socks-2.0.0.tar.gz", hash = "sha256:7944dad882846ac73e5f79e180c841e3895ee058e16855b7e8fff24f4cd0b90b"},
+    {file = "python_socks-2.0.0-py3-none-any.whl", hash = "sha256:aac65671cbd3b0eb55b20f8558c8de3894a315536aaab3ec0a7b9d46ff89c1bf"},
 ]
 pytz = [
     {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
diff --git a/pyproject.toml b/pyproject.toml
index cf6e8959..1f7b162c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,7 +38,7 @@ classifiers = [
 
 [tool.poetry.dependencies]
 python = "^3.8"
-magic-filter = "^1.0.3"
+magic-filter = "^1.0.4"
 aiohttp = "^3.8.0"
 pydantic = "^1.8.2"
 aiofiles = "^0.7.0"


Test patch:
diff --git a/tests/test_utils/test_magic_filter.py b/tests/test_utils/test_magic_filter.py
new file mode 100644
index 00000000..4a1d05f3
--- /dev/null
+++ b/tests/test_utils/test_magic_filter.py
@@ -0,0 +1,21 @@
+from dataclasses import dataclass
+from re import Match
+
+from aiogram import F
+from aiogram.utils.magic_filter import MagicFilter
+
+
+@dataclass
+class MyObject:
+    text: str
+
+
+class TestMagicFilter:
+    def test_operation_as(self):
+        magic: MagicFilter = F.text.regexp(r"^(\d+)$").as_("match")
+
+        assert not magic.resolve(MyObject(text="test"))
+
+        result = magic.resolve(MyObject(text="123"))
+        assert isinstance(result, dict)
+        assert isinstance(result["match"], Match)