I'm using a simple script to test how contextlib.contextmanager and contextlib.asynccontextmanager works.
After some investigations, it seems that if we missmatch the sync or async context manager with the sync or async function, the context manager is not called well.
sync open
sync_sync
sync close
---
async open
async_async
async close
---
sync open
sync_async
sync close
---
async open
async_sync
async close
But for the two last cases, the output is:
/home/n42/tmp/2024-01-13-contextlib/main.py:44: RuntimeWarning: coroutine 'async_sync' was never awaited
async_sync()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
---
sync open
sync close
sync_async
For the first one that means that the function decorated with @contextlib.contextmanager is not awaited, wich is normal.
Potential reason
But for the last one we have a more concerning output:
sync open
sync close
sync_async
This means that if you decorate an async function with @contextlib.contextmanager, the context manager is opened (sync open) then closed (sync close) and after that the async function is called (sync_async).
As it doesn't raise any error, it's hard to understand why it's not working...
Solution
Afer some investigations, I have found that we can use the asyncio.iscoroutinefunction function to check if the function is async or not and then use the right decorator.
agno open
sync
agno close
---
agno open
async
agno close
I'm wondering if it can be implemented in the standard library to, by default close the resource after the function is awaited, even if the function is async.
Note: this solution works for sync function decorators, for async functions decorators, we need to update the with into async with in the two wrappers.
What do you think of implementing on the contextlib.contextmanager decorator the ability to decorate both sync and async functions, then if the resultant decorator is used on sync or async function it always work as expected ?
I've successfully implemented it on my personal fork, I can create a PR if you want.
Even if async_() is an asynchronous function, otherdecorator(async_) may return a synchronous function that returns an awaitable. Your agnosticcontextmanager will fail to recognize it as asynchronouse. So the behavior will depend on the implementation detail of otherdecorator.
Even if async_() is an asynchronous function, otherdecorator(async_) may return a synchronous function that returns an awaitable. Your agnosticcontextmanager will fail to recognize it as asynchronouse. So the behavior will depend on the implementation detail of otherdecorator.
I think inspect.markcoroutinefunction is intended for exactly this purpose. For some precedent, the stdlib unittest.mock.patch already does something similar to the feature request here:
Activity
sunmy2019 commented on Jan 13, 2024
This is expected. That's the reason they both exist.
I think you have misunderstood this.
Resources are always closed. It's the coroutine that is left out.
This is a feature request, not a bug. It's nice to have, but also too trivial to add. I have no option on this.
QuentinN42 commented on Jan 14, 2024
What do you think of implementing on the contextlib.contextmanager decorator the ability to decorate both sync and async functions, then if the resultant decorator is used on sync or async function it always work as expected ?
I've successfully implemented it on my personal fork, I can create a PR if you want.
sunmy2019 commented on Jan 14, 2024
I recommend you post it here and see people's feedback. All feature requests go here.
https://discuss.python.org/c/ideas/6
serhiy-storchaka commented on Jan 15, 2024
There is a reason why asynccontextmanager was added. Your agnosticcontextmanager will not work if the decorated function has other decorator.
Even if async_() is an asynchronous function, otherdecorator(async_) may return a synchronous function that returns an awaitable. Your agnosticcontextmanager will fail to recognize it as asynchronouse. So the behavior will depend on the implementation detail of otherdecorator.
aabmass commented on Jan 18, 2024
I think inspect.markcoroutinefunction is intended for exactly this purpose. For some precedent, the stdlib unittest.mock.patch already does something similar to the feature request here:
cpython/Lib/unittest/mock.py
Lines 1369 to 1374 in 7fa511b