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

asynccontextmanager does not support callable classes #32

Open
mrosales opened this issue May 2, 2020 · 4 comments
Open

asynccontextmanager does not support callable classes #32

mrosales opened this issue May 2, 2020 · 4 comments

Comments

@mrosales
Copy link

mrosales commented May 2, 2020

I was expecting something like the following example to work, but it does not. This originally came up in: fastapi/fastapi#1204

from async_generator import asynccontextmanager

class GenClass:
    async def __call__(self):
        yield "hello"


cm = asynccontextmanager(GenClass())

with cm as value:
    print(value)

The result is an exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/.../lib/python3.6/site-packages/async_generator/_util.py", line 100, in asynccontextmanager
    "must be an async generator (native or from async_generator; "
TypeError: must be an async generator (native or from async_generator; if using @async_generator then @acontextmanager must be on top.
@oremanj
Copy link
Member

oremanj commented May 2, 2020

Put @asynccontextmanager on the class's __call__ method, not on the class itself. That will produce a class whose instances yield async generators when they are called. If that doesn't meet your needs, can you clarify where it falls short?

@asynccontextmanager is intended to be used as a function decorator. That is, it accepts as argument an async generator function (i.e., a function that returns an async generator iterator) and returns a function that returns an async context manager. So even if it did support being applied to a class instance, you would use async with cm() as value, not with cm as value. But @asynccontextmanager does check specifically for an async generator function, not just a thing-that-when-called-returns-an-async-generator, so this won't work. You can fool it (look at the definition of isasyncgenfunction in _impl.py to see what you need to spoof) but decorating the __call__ method will likely work much better.

@oremanj
Copy link
Member

oremanj commented May 2, 2020

I just looked at the linked issue. If you are given the class definition and can't modify it, you could do something like cm = asynccontextmanager(MyClass.__call__); async with cm(MyClass()) as value: ...

@mrosales
Copy link
Author

mrosales commented May 3, 2020

Sure, you're recommendation of a workaround makes sense and that's more or less what I did to unblock the python3.6 failure on the MR that I opened for the linked project issue, but I think the functionality that I was trying to call out is slightly different.

First, the example was a typo, you are correct that it should be async with, but the point that I was trying to show is that the TypeErrror was thrown on the line before it. I was filing this as a bug because mostly because it differs from the behavior of the native python3.7+ function in contextlib

# python 3.6
from async_generator import asynccontextmanager

# this line throws a TypeError
cm = asynccontextmanager(GenClass())
# python 3.7
from contextlib import asynccontextmanager

# this works just fine
cm = asynccontextmanager(GenClass())

@graingert
Copy link
Member

@mrosales fyi this works on the contextlib2 backport:

from contextlib2 import asynccontextmanager


class GenClass:
    async def __call__(self):
        yield "hello"


cm = asynccontextmanager(GenClass())

async def amain():
    async with cm() as value:
        print(value)

import anyio

anyio.run(amain)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants