mirror of
https://github.com/python/cpython.git
synced 2025-08-04 17:08:35 +00:00
bpo-40816 Add AsyncContextDecorator class (GH-20516)
Co-authored-by: Yury Selivanov <yury@edgedb.com>
This commit is contained in:
parent
048a35659a
commit
178695b7ae
4 changed files with 114 additions and 1 deletions
|
@ -126,6 +126,31 @@ Functions and classes provided:
|
||||||
|
|
||||||
.. versionadded:: 3.7
|
.. 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)
|
.. function:: closing(thing)
|
||||||
|
|
||||||
|
@ -384,6 +409,43 @@ Functions and classes provided:
|
||||||
.. versionadded:: 3.2
|
.. 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()
|
.. class:: ExitStack()
|
||||||
|
|
||||||
A context manager that is designed to make it easy to programmatically
|
A context manager that is designed to make it easy to programmatically
|
||||||
|
|
|
@ -80,6 +80,22 @@ class ContextDecorator(object):
|
||||||
return inner
|
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:
|
class _GeneratorContextManagerBase:
|
||||||
"""Shared functionality for @contextmanager and @asynccontextmanager."""
|
"""Shared functionality for @contextmanager and @asynccontextmanager."""
|
||||||
|
|
||||||
|
@ -167,9 +183,16 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
|
||||||
|
|
||||||
|
|
||||||
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
|
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
|
||||||
AbstractAsyncContextManager):
|
AbstractAsyncContextManager,
|
||||||
|
AsyncContextDecorator):
|
||||||
"""Helper for @asynccontextmanager."""
|
"""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):
|
async def __aenter__(self):
|
||||||
try:
|
try:
|
||||||
return await self.gen.__anext__()
|
return await self.gen.__anext__()
|
||||||
|
|
|
@ -278,6 +278,33 @@ class AsyncContextManagerTestCase(unittest.TestCase):
|
||||||
async with woohoo(self=11, func=22, args=33, kwds=44) as target:
|
async with woohoo(self=11, func=22, args=33, kwds=44) as target:
|
||||||
self.assertEqual(target, (11, 22, 33, 44))
|
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 AclosingTestCase(unittest.TestCase):
|
class AclosingTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Add AsyncContextDecorator to contextlib to support async context manager as a decorator.
|
Loading…
Add table
Add a link
Reference in a new issue