Repository: aiogram/aiogram
Pull request #633 (2021-07-16 10:02:46)

Fix not all args are passed to handler function invocation.
# Description

Fix not all args are passed to handler function invocation. Handlers wrapped with `functools.partial` or having keyword-only arguments won't work without this:

```python
...

async def sender(event: Message, command: Union[CommandObject, str], categories: list[str]):
    print(locals())

dp.message.register(sender, commands=['foo', 'bar'])
dp.message.register(functools.partial(sender, command='foo'))

...
```

```python
ERROR:aiogram.dispatcher:Cause exception while process update id=xxx by bot id=xxx
TypeError: sender() missing 1 required positional argument: 'categories'
Traceback (most recent call last):
  File "<project-dir>/.venv/lib/python3.9/site-packages/aiogram/dispatcher/dispatcher.py", line 279, in _process_update
    response = await self.feed_update(bot, update, **kwargs)
  File "<project-dir>/.venv/lib/python3.9/site-packages/aiogram/dispatcher/dispatcher.py", line 101, in feed_update
    response = await self.update.trigger(update, bot=bot, **kwargs)
  File "<project-dir>/.venv/lib/python3.9/site-packages/aiogram/dispatcher/event/telegram.py", line 135, in trigger
    return await wrapped_outer(event, kwargs)
  File "<project-dir>/.venv/lib/python3.9/site-packages/aiogram/dispatcher/event/telegram.py", line 146, in _trigger
    return await wrapped_inner(event, kwargs)
  File "<project-dir>/.venv/lib/python3.9/site-packages/aiogram/dispatcher/event/handler.py", line 40, in call
    return await wrapped()
  File "<project-dir>/.venv/lib/python3.9/site-packages/aiogram/dispatcher/dispatcher.py", line 241, in _listen_update
    response = await observer.trigger(event, update=update, **kwargs)
  File "<project-dir>/.venv/lib/python3.9/site-packages/aiogram/dispatcher/event/telegram.py", line 135, in trigger
    return await wrapped_outer(event, kwargs)
  File "<project-dir>/.venv/lib/python3.9/site-packages/aiogram/dispatcher/event/telegram.py", line 146, in _trigger
    return await wrapped_inner(event, kwargs)
  File "<project-dir>/.venv/lib/python3.9/site-packages/aiogram/dispatcher/event/handler.py", line 40, in call
    return await wrapped()
TypeError: sender() missing 1 required positional argument: 'categories'
```

# How has this been tested?

1. ```bash
   poetry install
   make test
   ```
2. ran code above, got no errors

**Test Configuration**:
* Operating System: Arch Linux 5.12.9-arch1-1
* Python version: 3.9.5
* Aiogram version: 3.0.0a10 (from test.pypi.org)

# Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes


codecov[bot]: # [Codecov](https://codecov.io/gh/aiogram/aiogram/pull/633?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) Report
> Merging [#633](https://codecov.io/gh/aiogram/aiogram/pull/633?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) (df882a1) into [dev-3.x](https://codecov.io/gh/aiogram/aiogram/commit/125fc22ff941e76101e90f834f55738cf2fb0d4b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) (125fc22) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/aiogram/aiogram/pull/633/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/633?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     #633   +/-   ##
========================================
  Coverage    98.60%   98.60%           
========================================
  Files          251      251           
  Lines         5529     5529           
========================================
  Hits          5452     5452           
  Misses          77       77           
```

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

| [Impacted Files](https://codecov.io/gh/aiogram/aiogram/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) | Coverage Δ | |
|---|---|---|
| [aiogram/dispatcher/event/handler.py](https://codecov.io/gh/aiogram/aiogram/pull/633/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9kaXNwYXRjaGVyL2V2ZW50L2hhbmRsZXIucHk=) | `100.00% <100.00%> (ø)` | |


Patch:
diff --git a/aiogram/dispatcher/event/handler.py b/aiogram/dispatcher/event/handler.py
index 63f5130b..37a9ecb7 100644
--- a/aiogram/dispatcher/event/handler.py
+++ b/aiogram/dispatcher/event/handler.py
@@ -32,7 +32,9 @@ class CallableMixin:
         if self.spec.varkw:
             return kwargs
 
-        return {k: v for k, v in kwargs.items() if k in self.spec.args}
+        return {
+            k: v for k, v in kwargs.items() if k in self.spec.args or k in self.spec.kwonlyargs
+        }
 
     async def call(self, *args: Any, **kwargs: Any) -> Any:
         wrapped = partial(self.callback, *args, **self._prepare_kwargs(kwargs))


Test patch:
diff --git a/tests/test_dispatcher/test_event/test_handler.py b/tests/test_dispatcher/test_event/test_handler.py
index 168dac59..6bce7d98 100644
--- a/tests/test_dispatcher/test_event/test_handler.py
+++ b/tests/test_dispatcher/test_event/test_handler.py
@@ -22,6 +22,10 @@ async def callback3(foo: int, **kwargs):
     return locals()
 
 
+async def callback4(foo: int, *, bar: int, baz: int):
+    return locals()
+
+
 class Filter(BaseFilter):
     async def __call__(self, foo: int, bar: int, baz: int) -> Union[bool, Dict[str, Any]]:
         return locals()
@@ -95,11 +99,21 @@ class TestCallableMixin:
                 {"foo": 42, "spam": True, "baz": "fuz", "bar": "test"},
                 {"foo": 42, "baz": "fuz", "bar": "test"},
             ),
+            pytest.param(
+                functools.partial(callback2, bar="test"),
+                {"foo": 42, "spam": True, "baz": "fuz"},
+                {"foo": 42, "baz": "fuz"},
+            ),
             pytest.param(
                 callback3,
                 {"foo": 42, "spam": True, "baz": "fuz", "bar": "test"},
                 {"foo": 42, "spam": True, "baz": "fuz", "bar": "test"},
             ),
+            pytest.param(
+                callback4,
+                {"foo": 42, "spam": True, "baz": "fuz", "bar": "test"},
+                {"foo": 42, "baz": "fuz", "bar": "test"},
+            ),
             pytest.param(
                 Filter(), {"foo": 42, "spam": True, "baz": "fuz"}, {"foo": 42, "baz": "fuz"}
             ),