Types for asynchronous generators too general

Although the following program successfully runs (and prints 42), neither pyright nor mypy accept it:

import asyncio
from collections.abc import AsyncGenerator

async def gen() -> AsyncGenerator[int, None]:
    yield 42

async def main() -> None:
    print(await asyncio.create_task(anext(gen())))

asyncio.run(main())

Pyright produces the following type error (mypy’s error is similar):

Argument of type "Awaitable[int]" cannot be assigned to parameter "coro" of type "_CoroutineLike[_T@create_task]" in function "create_task"
  "Awaitable[int]" is not assignable to "Coroutine[Any, Any, _T@create_task]"

The reason for this type error is that the definition of the collections.abc.AsyncGenerator protocol on typeshed says that __anext__ returns an awaitable whereas asyncio.create_task expects an actual coroutine, both in its type and at runtime. The program above runs successfully nevertheless because the __anext__ method of an asynchronous generator defined using the async def/yield syntax does indeed return a coroutine. Thus, both pyright and mypy reject a very legitimate program.

There’s a second protocol for asynchronous generators, namely types.AcyncGeneratorType. Its docs say

types.AsyncGeneratorType

The type of asynchronous generator-iterator objects, created by asynchronous generator functions.

and its definition on typeshed says that __anext__ returns a coroutine. Thus, the program above would type check if the return type of gen was changed to types.AsyncGeneratorType[int, None]. However, pyright and mypy insist that the return type of gen must be collections.abc.AsyncGenerator (or one of its super types).

I would like to solve this problem and arrive at a situation where the program above can be typed in a way that is accepted by the common type checkers for Python. I can think of two approaches:

  1. Change the definition of collections.abc.AsyncGenerator such that __anext__ returns a coroutine rather than just any awaitable.
  2. Change the type checkers to allow for annotating gen’s return type as types.AsyncGeneratorType and adjust their type inference accordingly.

Which of these two approaches is the right one to pursue? Is there even a clear cut winner or is this something that needs detailed discussion?

1 Like

Considering that AsyncGenerator.__anext__() returns a coroutine at runtime, an exploratory PR to typeshed with this change makes the most sense to me.

1 Like

As suggested by @srittau, I went down path 1 and made a PR to typeshed, which has just been merged.

2 Likes