hardbyte / hardbyte/python-can
Async Listener
- Dominant language
- Python
- Stars
- 1.6k
- Forks
- 697
- PR merge metrics
- No merged PRs in 30d
Description
### Issue description
Currently, `Listener` objects are asyncio-ready but are not truely async.
Let me first clarify that `AsyncBufferedReader` is not a truely-async listener, it is a sync listener (sync `on_message_received` and `on_error` methods) that exposes an async buffer through the `get_message` method. It is pretty useful for a lot of cases but this is not the target of this feature request. A truely-async listener would allow for async `on_message_received` and `on_error` definitions.
Disclaimer: I haven't inspected the whole code so I'm not sure if there is anything of the underlying architecture that makes this kind of truely-async listeners an issue.
And what do I mean by "asyncio-ready" in the first sentence? `Notifier._on_message_available` is called when a new message can be received. `Notifier.listeners` can either be `Listener` instances or direct callbacks. Either way they are called, and if they return a coroutine (and the notifier was passed a loop), it will scheduler the corresponding task:
https://github.com/hardbyte/python-can/blob/dc0ae68acb28f01a503e084718cfa2acf39d1e16/can/notifier.py#L134-L143
Async callbacks can be provided to make use of this feature, but `Listener.__call__` never returns a coroutine:
https://github.com/hardbyte/python-can/blob/dc0ae68acb28f01a503e084718cfa2acf39d1e16/can/listener.py#L42-L43
### Solutions
Returning the result of `self.on_message_received` instead of implicitly returning `None` would work for both versions:
```py
def __call__(self, msg: Message):
return self.on_message_received(msg)
```
In the sync version, `self.on_message_receive(msg)` returns `None`, while the async version will return the coroutine, which is exactly the desired behavior. Regaring type-hinting of the result, `Optional[asyncio.coroutine]` works up to Python 3.9, not sure what should be the 3.10 one as `asyncio.coroutine` is no longer a thing.
I see two ways of providing a truely-async Listener:
1. Creating a new `AsyncListener` with the above version of `__call__`, and async signatures for `on_message_received` and `on_error`.
2. Updating the `Listener` class with the above version of `__call__`.
### Alternatives
Meanwhile, the user can always modify the `__call__` method of the subclass of `Listener` to the one provided above and it will work. I still think this should be provided to the user and not expected from him to discover that he needs to change that magic method of the subclass.
Contributor guide
Assessment
This issue has not been assessed yet.