Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the proper way to type hint the return value of an @asynccontextmanager?

What is the proper way to add type hints for the return of a function with the @asynccontextmanager decorator? Here are two attempts I made that both fail.

from contextlib import asynccontextmanager
from typing import AsyncContextManager


async def caller():
    async with return_str() as i:
        print(i)

    async with return_AsyncContextManager() as j:
        print(j)


@asynccontextmanager
async def return_str() -> str:
    yield "hello"


@asynccontextmanager
async def return_AsyncContextManager() -> AsyncContextManager[str]:
    yield "world"

For both i and j Pylance in vscode shows type Any. Other ideas I've considered:

  • I thought maybe I could pass in the type info to the decorator itself (like @asynccontextmanager(cls=str), but I can't find any examples of that, or any description of the args I could pass in.
  • async with return_str() as i: # type: str doesn't work either. Even if it did, I'd rather hint at the function definition, not at every invocation. Type comments are not very DRY.
  • I tried to create an AsyncContextManager class with __aenter()__ and __aexit()__ functions, but was not successful. I would fall back to that if it worked, but I'd prefer to make the decorator work because it's much more concise.

Here's a screencap of me hovering the return_AsyncContextManager() function, and showing the Pylance popup saying it returns AsyncContextManager[_T] enter image description here

like image 852
Eric Grunzke Avatar asked Dec 31 '25 20:12

Eric Grunzke


1 Answers

You have to hint AsyncIterator as the return type, like this:

@asynccontextmanager
async def my_acm() -> AsyncIterator[str]:
    yield "this works"


async def caller():
    async with my_acm() as val:
        print(val)

This is because the yield keyword is used to create generators. Consider:

def a_number_generator():
    for x in range(10):  # type: int
        yield x

g = a_number_generator() # g is type Generator[int]

This makes sense given the type hints for @asynccontextmanager:
asynccontextmanager(func: Callable[_P, AsyncIterator[_T_co]]) -> Callable[_P, _AsyncGeneratorContextManager[_T_co]]

That's a lot to parse but it says that the asynccontextmanager takes a function which returns AsyncIterator and transforms it into a new function that returns AsyncContextManager. The generic types _P and _T_co are preserved as well.

Here is a screenshot showing the type hint transferring into the caller function.

enter image description here

like image 133
Eric Grunzke Avatar answered Jan 03 '26 08:01

Eric Grunzke



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!