Skip to main content

Lib/contextlib.py (part 6)

Source:

cpython 3.14 @ ab2d84fe1023/Lib/contextlib.py

This annotation covers the async context manager utilities. See lib_contextlib5_detail for contextmanager, ExitStack, suppress, and redirect_stdout.

Map

LinesSymbolRole
1-80asynccontextmanagerDecorator to make async with from an async generator
81-160AsyncContextDecoratorMixin enabling async with objects as decorators
161-260AsyncExitStack.__aenter__ / __aexit__Async version of ExitStack
261-360AsyncExitStack.enter_async_contextPush an async context manager
361-500AsyncExitStack.push_async_callbackRegister an async cleanup callback

Reading

asynccontextmanager

# CPython: Lib/contextlib.py:240 asynccontextmanager
def asynccontextmanager(func):
@wraps(func)
def helper(*args, **kwds):
return _AsyncGeneratorContextManager(func, args, kwds)
return helper

class _AsyncGeneratorContextManager(AbstractAsyncContextManager):
async def __aenter__(self):
try:
return await anext(self.gen)
except StopAsyncIteration:
raise RuntimeError("generator didn't yield") from None

async def __aexit__(self, typ, value, traceback):
if typ is None:
try:
await anext(self.gen)
except StopAsyncIteration:
return False
raise RuntimeError("generator didn't stop")
else:
try:
await self.gen.athrow(typ, value, traceback)
except StopAsyncIteration as exc:
return exc is not value
...

@asynccontextmanager is the async analogue of @contextmanager. The decorated function must be an async generator with exactly one yield. __aenter__ drives the generator to the yield; __aexit__ resumes or throws the exception into it.

AsyncExitStack

# CPython: Lib/contextlib.py:520 AsyncExitStack
class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
async def enter_async_context(self, cm):
_cm_type = type(cm)
_exit = _cm_type.__aexit__
result = await _cm_type.__aenter__(cm)
self._push_async_cm_exit(cm, _exit)
return result

async def __aexit__(self, *exc_details):
pending_raise = False
while self._exit_callbacks:
is_sync, cb = self._exit_callbacks.pop()
try:
if is_sync:
cb_suppress = cb(*exc_details)
else:
cb_suppress = await cb(*exc_details)
...
except:
...

AsyncExitStack handles a mix of sync and async context managers. enter_async_context registers __aexit__ for async cleanup; enter_context (inherited) handles sync ones. During __aexit__, sync callbacks are called directly, async callbacks are awaited.

push_async_callback

# CPython: Lib/contextlib.py:560 push_async_callback
def push_async_callback(self, callback, /, *args, **kwds):
async def _exit_wrapper(exc_type, exc, tb):
await callback(*args, **kwds)
self._exit_callbacks.append((False, _exit_wrapper)) # False = async
return callback

push_async_callback(some_async_fn, arg) registers an async callable that runs during __aexit__ regardless of exceptions. Unlike push_async_context_manager, it doesn't need __aenter__/__aexit__ — it's just a coroutine function.

gopy notes

asynccontextmanager is module/contextlib.AsyncContextManager in module/contextlib/module.go. _AsyncGeneratorContextManager.__aenter__ calls objects.GeneratorSend(nil) on the async generator. AsyncExitStack is module/contextlib.AsyncExitStack with a []exitCallback that tracks sync/async flag.