classification
Title: iscoroutinefunction broken with cython - allow tagging of functions as async?
Type: enhancement Stage: patch review
Components: asyncio Versions: Python 3.10
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: asvetlov, dhiltonp, eamanu, fornellas, scoder, yselivanov
Priority: normal Keywords: patch

Created on 2019-09-19 18:38 by dhiltonp, last changed 2020-09-22 11:36 by scoder.

Pull Requests
URL Status Linked Edit
PR 16292 closed dhiltonp, 2019-09-19 18:39
Messages (10)
msg352812 - (view) Author: David Hilton (dhiltonp) * Date: 2019-09-19 18:38
If a python piece of code imports cython code with async defs, `asyncio.iscoroutinefunction` cannot determine that the code is async.

https://github.com/cython/cython/issues/2273#issuecomment-531537624

scoder is open to marking async defs so that they can be identified, just like `asyncio.coroutine`:

https://github.com/python/cpython/blob/ae239f6b0626e926613a4a1dbafa323bd41fec32/Lib/asyncio/coroutines.py#L156

However, that is an internal interface and `@coroutine` is deprecated.

--------------

Can we have some official way of marking functions as async that will not be deprecated?

The easiest would be for `asyncio.iscoroutinefunction` to look for `_is_coroutine = True`, and promise to keep looking for that value.

This would also allow for functools.partial to easily mark that it's returning an async function, which some people seem to care about.
msg352817 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2019-09-19 22:46
I think we need better name than just `_is_coroutine`.
All async function properties as dunder named, the new *official* name should follow this convention as well
msg352832 - (view) Author: Stefan Behnel (scoder) * (Python committer) Date: 2019-09-20 04:23
My usual first reaction is: "if you need to find out whether the return value of a callable will be an Awaitable or not, without calling it, then you're probably doing something wrong in your design".

However,
a) there is code that tries this already (and it falls short in various ways while trying)
b) asyncio has a function `iscoroutinefunction` which *seems* to fulfil this need but does not achieve it (because not everything that returns an Awaitable is a "coroutine function")
c) asyncio has an internal protocol for marking things as "is a coroutine", which comes close to but isn't "returns an Awaitable when called"

So – should there be an official protocol for marking callables as returning an Awaitable? Should we look at annotations for that? Anything else? Or do we consider this intention inherently flawed?
msg352843 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2019-09-20 08:47
I think `func.__awaitable__ = True` can serve all possible situations.

We need it for async mocks (now the library use flawless `_is_coroutine` approach. 

`__awaitable__` can also cover cases where a regular function returns awaitable object or there is a class with `def __await__(self)` method, `functools.partial` -- all of them are awaitable.

Async functions can be modified to provide the property out of the box.

Regarding the question "is a check for awaitableness a good design or not"?
I agree that a check for awaitableness is useless *just before* calling the function.
There is another very useful case where the check is important: registering a callback for later usage.
For example, we have a web server framework. It is built around a mapping of URL paths to async functions, e.g. path('/hello', say_hello) in Django style. It's crucial to check if say_hello() is an async function *on the application configuration stage*, not on viewing the particular http://localhost/hello page in a browser.

So, I think an official protocol makes a lot of sense, let's implement it in 3.9
msg352906 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2019-09-20 22:35
> If a python piece of code imports cython code with async defs, `asyncio.iscoroutinefunction` cannot determine that the code is async.


Well, this seems to be a regression.  IIRC Cython used to emulate a Python code object (__code__) for functions it compiled along with the appropriate CO_COROUTINE flag set.  Wasn't that the case?
msg352940 - (view) Author: Stefan Behnel (scoder) * (Python committer) Date: 2019-09-21 13:23
> along with the appropriate CO_COROUTINE flag set

No, it never did that, for safety reasons. It's difficult to say if enabling these flags is the right thing to do, because it's unclear what assumptions code that tests for them would make. In CPython itself, there do not seem to be any "excessive" assumptions specific to that flag – also because Cython functions are not Python functions, and thus the flag will never be looked at:

https://github.com/python/cpython/blob/5b9ff7a0dcb16d6f5c3cd4f1f52e0ca6a4bde586/Lib/inspect.py#L178-L180

Thus, setting the CO_COROUTINE and CO_ASYNC_GENERATOR code flags has no effect for Cython functions.
msg358224 - (view) Author: Fabio Pugliese Ornellas (fornellas) Date: 2019-12-10 21:06
It is worth noting that test frameworks can greatly benefit from iscoroutinefunction to work.

I'm the main author of TestSlide, which provides more strict mocking for Python. I recently added async support, so we can detect bugs such as configuring a sync mock for something that is async (https://testslide.readthedocs.io/en/2.0.2/strict_mock/index.html#coroutine-functions-async-def). It works just fine, as long as iscoroutinefunction works, which is not the case for async Cython (a big chunk of TestSlide's use cases).

+1 an Andrew's func.__awaitable__ idea: interpreter can provide that for `async def` and one can also do that for anything that implements __call__ to communicate that the callable is async.
msg358842 - (view) Author: Stefan Behnel (scoder) * (Python committer) Date: 2019-12-24 07:50
FWIW, it seems reasonable to have a protocol for this.
msg358843 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2019-12-24 08:24
I'd like to hear Yuri's opinion for the idea of adding an attribute.
Another question is the name: I have proposed __awaitable__ but maybe __async__ is slightly better?
msg377313 - (view) Author: Stefan Behnel (scoder) * (Python committer) Date: 2020-09-22 11:36
FYI, https://github.com/cython/cython/pull/3427 has been merged into Cython. In Cython 3.0, compiled coroutines will disguise as non-compiled coroutines, from the PoV of asyncio.

Restricting this ticket to Py3.10 since we're rather discussing a new feature now.
History
Date User Action Args
2020-09-22 11:36:05scodersetmessages: + msg377313
versions: + Python 3.10, - Python 3.7, Python 3.8, Python 3.9
2019-12-24 08:24:44asvetlovsetmessages: + msg358843
2019-12-24 07:50:33scodersetmessages: + msg358842
2019-12-10 21:06:26fornellassetnosy: + fornellas
messages: + msg358224
2019-09-21 13:23:26scodersetmessages: + msg352940
2019-09-20 22:35:34yselivanovsetmessages: + msg352906
2019-09-20 13:25:10eamanusetnosy: + eamanu
2019-09-20 08:47:04asvetlovsetmessages: + msg352843
2019-09-20 04:23:03scodersetmessages: + msg352832
2019-09-19 22:46:25asvetlovsetmessages: + msg352817
2019-09-19 19:30:29zach.waresetnosy: + scoder

versions: - Python 2.7, Python 3.5, Python 3.6
2019-09-19 18:39:19dhiltonpsetkeywords: + patch
stage: patch review
pull_requests: + pull_request15877
2019-09-19 18:38:39dhiltonpcreate