Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document how to type hint for an async function used as a callback #424

Closed
brettcannon opened this issue May 7, 2017 · 11 comments · Fixed by python/cpython#20386
Closed
Labels
topic: documentation Documentation-related issues and PRs

Comments

@brettcannon
Copy link
Member

I have (roughly) the following code:

async def foo(x: int): pass
callback = foo  # What does this type to?

The problem is I don't know what to type callback to. It isn't a Callable as mypy says that "Function does not return a value" when I call await callback(42). It isn't an AsyncIterable, AsyncIterator, or an AsyncGenerator as it simply isn't. It isn't a Coroutine as mypy says that they can't be called.

So am I missing something on how to type this, or does there need to be an AsyncCallable added to typing?

@JelleZijlstra
Copy link
Member

I think you can use Callable[[int], Awaitable[None]].

@ilevkivskyi
Copy link
Member

Yes, in general case it should be like this:

async def bar(x: int) -> str:
    return str(x)

cbar: Callable[[int], Awaitable[str]] = bar

So that in your case it should probably be:

async def foo(x: int): pass
callback: Callable[[int], Awaitable[None]] = foo

Also note that without an explicit return annotation for foo mypy infers Callable[[int], Awaitable[Any]], according to PEP 484 all missing annotations are treated as Any.

What are the exact steps to get Function does not return a value error? Are you using --strict-optional, a similar (spurious) error was reported recently in mypy, it can be quite misleading.

@brettcannon brettcannon changed the title type hint for an async function used as a callback Document how to type hint for an async function used as a callback May 7, 2017
@brettcannon
Copy link
Member Author

I've changed the title of this issue to make a documentation thing so that an example in the Callable docs and show how to do this.

@ilevkivskyi As for triggering the error, if I type hint the callback as Callable[[Any], None] it causes that error message.

@terrisgit
Copy link

terrisgit commented Jul 3, 2018

From a newbie's point of view, it seems like Coroutine was added as a short-hand way to declare this (eg #251) but I can't figure out how to use it in Python 3.6. Maybe I need 3.7.

@JelleZijlstra
Copy link
Member

I don't think 3.7 makes a difference here, and you more likely want Awaitable instead of Coroutine. What issues are you running into?

@terrisgit
Copy link

terrisgit commented Jul 3, 2018

Maybe a better question is: is Coroutine documented somewhere with an example? I will use Callable[[int], Awaitable[None]] but I do not understand the generic Coroutine and it merely bugs me, nothing more. I can ignore it. Thank you.

@JelleZijlstra
Copy link
Member

Not sure, but there are very few contexts where you'd actually need it. To declare an async callback, you would use Awaitable like in Ivan's and my messages further up in this thread.

@terrisgit
Copy link

terrisgit commented Jul 5, 2018

I see the return value of an async function is now Coroutine instead of Awaitable. At any rate, this explains why Callable should be used instead of Coroutine. python/mypy@6519eb6

@vladap
Copy link

vladap commented Oct 31, 2019

Callable[[int], Awaitable[None]] is as well satisfied by any def without async keyword which returns asyncio Future, Task or coroutine object. Type Coroutine specifies awaitables which support send() and throw() methods and the complete type is Coroutine[send_type, throw_type, return_type], each parameter is covariant.

Coroutine[Any, Any, None] is the inferred type which is returned by your async def taking int and returning None when executed without await.

Future/Task means you allow to schedule/execute tasks before a callback function starts or returns. Unlike when returning coroutine object which is scheduled at the code line where you await async def. If Future/Task should be disallowed then the type of the callback would have to be Callable[[int], Coroutine[Any, Any, None]]. It still supports plain def which returns coroutine object but it should not matter.

And a closing note because of the similarity, Generator[send_type, throw_type, return_type] is an unrelated type to Coroutine[send_type, throw_type, return_type] hence they are not allowed as a callback nor async functions defined via @asyncio.coroutine annotation.

Welcome to simplicity of typing:)

@dimaqq
Copy link

dimaqq commented Nov 25, 2021

I wonder if perhaps typing got any simpler in the last 2~4 years?

I'd love to write something simpler than Callable[[], Awaitable[None]] for simple cases like this:

async def startup():
    ...

app.add_middleware(MyThing, startup=startup)

class MyThing:
    def __init__(self, app: ..., startup: Callable[[], Awaitable[None]])

Perhaps an alias, like AsyncFunction[] or AsyncCallable[] would be easier on the eyes and newcomers?

@gvanrossum
Copy link
Member

There’s a proposal on typing-sig that would allow writing ‘async () -> None’ for that case.

https://mail.python.org/archives/list/[email protected]/message/JZLYRAXJV34WAV5TKEOMA32V7ZLPOBFC/

JelleZijlstra pushed a commit to python/cpython that referenced this issue May 1, 2022
miss-islington pushed a commit to miss-islington/cpython that referenced this issue May 1, 2022
miss-islington added a commit to python/cpython that referenced this issue May 1, 2022
miss-islington added a commit to python/cpython that referenced this issue May 1, 2022
hello-adam pushed a commit to hello-adam/cpython that referenced this issue Jun 2, 2022
jessecomeau87 pushed a commit to jessecomeau87/Python that referenced this issue May 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: documentation Documentation-related issues and PRs
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants