from collections.abc import AsyncGenerator from contextlib import AbstractContextManager from contextlib import asynccontextmanager as asynccontextmanager from typing import TypeVar import anyio.to_thread from anyio import CapacityLimiter from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool # noqa from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa from starlette.concurrency import ( # noqa run_until_first_complete as run_until_first_complete, ) _T = TypeVar("_T") @asynccontextmanager async def contextmanager_in_threadpool( cm: AbstractContextManager[_T], ) -> AsyncGenerator[_T, None]: """Run a synchronous context manager in a thread pool. Enters and exits the context manager in a worker thread so that blocking ``__enter__`` / ``__exit__`` calls do not block the async event loop. The ``__exit__`` call uses its own :class:`~anyio.CapacityLimiter` to avoid deadlocks when the context manager holds resources from a bounded pool (e.g. database connections). """ exit_limiter = CapacityLimiter(1) try: yield await run_in_threadpool(cm.__enter__) except Exception as e: ok = bool( await anyio.to_thread.run_sync( cm.__exit__, type(e), e, e.__traceback__, limiter=exit_limiter ) ) if not ok: raise e else: await anyio.to_thread.run_sync( cm.__exit__, None, None, None, limiter=exit_limiter )