diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 0aa4ad76523480..6872de382e897c 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -126,6 +126,31 @@ Functions and classes provided: .. versionadded:: 3.7 + Context managers defined with :func:`asynccontextmanager` can be used + either as decorators or with :keyword:`async with` statements:: + + import time + + async def timeit(): + now = time.monotonic() + try: + yield + finally: + print(f'it took {time.monotonic() - now}s to run') + + @timeit() + async def main(): + # ... async code ... + + When used as a decorator, a new generator instance is implicitly created on + each function call. This allows the otherwise "one-shot" context managers + created by :func:`asynccontextmanager` to meet the requirement that context + managers support multiple invocations in order to be used as decorators. + + .. versionchanged:: 3.10 + Async context managers created with :func:`asynccontextmanager` can + be used as decorators. + .. function:: closing(thing) @@ -351,6 +376,43 @@ Functions and classes provided: .. versionadded:: 3.2 +.. class:: AsyncContextManager + + Similar as ContextManger only for async + + Example of ``ContextDecorator``:: + + from asyncio import run + from contextlib import AsyncContextDecorator + + class mycontext(AsyncContextDecorator): + async def __aenter__(self): + print('Starting') + return self + + async def __aexit__(self, *exc): + print('Finishing') + return False + + >>> @mycontext() + ... async def function(): + ... print('The bit in the middle') + ... + >>> run(function()) + Starting + The bit in the middle + Finishing + + >>> async def function(): + ... async with mycontext(): + ... print('The bit in the middle') + ... + >>> run(function()) + Starting + The bit in the middle + Finishing + + .. class:: ExitStack() A context manager that is designed to make it easy to programmatically diff --git a/Lib/contextlib.py b/Lib/contextlib.py index ff92d9f913f4c2..6838bea96a96c6 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -80,6 +80,22 @@ def inner(*args, **kwds): return inner +class AsyncContextDecorator(object): + "A base class or mixin that enables async context managers to work as decorators." + + def _recreate_cm(self): + """Return a recreated instance of self. + """ + return self + + def __call__(self, func): + @wraps(func) + async def inner(*args, **kwds): + async with self._recreate_cm(): + return await func(*args, **kwds) + return inner + + class _GeneratorContextManagerBase: """Shared functionality for @contextmanager and @asynccontextmanager.""" @@ -167,9 +183,16 @@ def __exit__(self, type, value, traceback): class _AsyncGeneratorContextManager(_GeneratorContextManagerBase, - AbstractAsyncContextManager): + AbstractAsyncContextManager, + AsyncContextDecorator): """Helper for @asynccontextmanager.""" + def _recreate_cm(self): + # _AGCM instances are one-shot context managers, so the + # ACM must be recreated each time a decorated function is + # called + return self.__class__(self.func, self.args, self.kwds) + async def __aenter__(self): try: return await self.gen.__anext__() diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 43fb7fced1bfdb..a631218e158389 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -278,6 +278,33 @@ async def woohoo(self, func, args, kwds): async with woohoo(self=11, func=22, args=33, kwds=44) as target: self.assertEqual(target, (11, 22, 33, 44)) + @_async_test + async def test_recursive(self): + depth = 0 + ncols = 0 + + @asynccontextmanager + async def woohoo(): + nonlocal ncols + ncols += 1 + + nonlocal depth + before = depth + depth += 1 + yield + depth -= 1 + self.assertEqual(depth, before) + + @woohoo() + async def recursive(): + if depth < 10: + await recursive() + + await recursive() + + self.assertEqual(ncols, 10) + self.assertEqual(depth, 0) + class TestAsyncExitStack(TestBaseExitStack, unittest.TestCase): class SyncAsyncExitStack(AsyncExitStack): diff --git a/Misc/NEWS.d/next/Library/2020-05-29-15-25-41.bpo-40816.w61Pob.rst b/Misc/NEWS.d/next/Library/2020-05-29-15-25-41.bpo-40816.w61Pob.rst new file mode 100644 index 00000000000000..66b75779784655 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-05-29-15-25-41.bpo-40816.w61Pob.rst @@ -0,0 +1 @@ +Add AsyncContextDecorator to contextlib to support async context manager as a decorator. \ No newline at end of file
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: