summaryrefslogtreecommitdiffstats
path: root/Lib/contextlib.py
diff options
context:
space:
mode:
authorJelle Zijlstra <jelle.zijlstra@gmail.com>2017-05-01 01:25:58 (GMT)
committerYury Selivanov <yselivanov@gmail.com>2017-05-01 01:25:58 (GMT)
commit2e624690bd74071358566300b7ef0bc45f444a30 (patch)
treef96176f5997f38c00974854907b586ce887981a3 /Lib/contextlib.py
parent9dc2b3809f38be2e403ee264958106badfda142d (diff)
downloadcpython-2e624690bd74071358566300b7ef0bc45f444a30.zip
cpython-2e624690bd74071358566300b7ef0bc45f444a30.tar.gz
cpython-2e624690bd74071358566300b7ef0bc45f444a30.tar.bz2
bpo-29679: Implement @contextlib.asynccontextmanager (#360)
Diffstat (limited to 'Lib/contextlib.py')
-rw-r--r--Lib/contextlib.py99
1 files changed, 93 insertions, 6 deletions
diff --git a/Lib/contextlib.py b/Lib/contextlib.py
index 5e47054..c53b35e 100644
--- a/Lib/contextlib.py
+++ b/Lib/contextlib.py
@@ -4,9 +4,9 @@ import sys
from collections import deque
from functools import wraps
-__all__ = ["contextmanager", "closing", "AbstractContextManager",
- "ContextDecorator", "ExitStack", "redirect_stdout",
- "redirect_stderr", "suppress"]
+__all__ = ["asynccontextmanager", "contextmanager", "closing",
+ "AbstractContextManager", "ContextDecorator", "ExitStack",
+ "redirect_stdout", "redirect_stderr", "suppress"]
class AbstractContextManager(abc.ABC):
@@ -54,8 +54,8 @@ class ContextDecorator(object):
return inner
-class _GeneratorContextManager(ContextDecorator, AbstractContextManager):
- """Helper for @contextmanager decorator."""
+class _GeneratorContextManagerBase:
+ """Shared functionality for @contextmanager and @asynccontextmanager."""
def __init__(self, func, args, kwds):
self.gen = func(*args, **kwds)
@@ -71,6 +71,12 @@ class _GeneratorContextManager(ContextDecorator, AbstractContextManager):
# for the class instead.
# See http://bugs.python.org/issue19404 for more details.
+
+class _GeneratorContextManager(_GeneratorContextManagerBase,
+ AbstractContextManager,
+ ContextDecorator):
+ """Helper for @contextmanager decorator."""
+
def _recreate_cm(self):
# _GCM instances are one-shot context managers, so the
# CM must be recreated each time a decorated function is
@@ -121,12 +127,61 @@ class _GeneratorContextManager(ContextDecorator, AbstractContextManager):
# fixes the impedance mismatch between the throw() protocol
# and the __exit__() protocol.
#
+ # This cannot use 'except BaseException as exc' (as in the
+ # async implementation) to maintain compatibility with
+ # Python 2, where old-style class exceptions are not caught
+ # by 'except BaseException'.
if sys.exc_info()[1] is value:
return False
raise
raise RuntimeError("generator didn't stop after throw()")
+class _AsyncGeneratorContextManager(_GeneratorContextManagerBase):
+ """Helper for @asynccontextmanager."""
+
+ async def __aenter__(self):
+ try:
+ return await self.gen.__anext__()
+ except StopAsyncIteration:
+ raise RuntimeError("generator didn't yield") from None
+
+ async def __aexit__(self, typ, value, traceback):
+ if typ is None:
+ try:
+ await self.gen.__anext__()
+ except StopAsyncIteration:
+ return
+ else:
+ raise RuntimeError("generator didn't stop")
+ else:
+ if value is None:
+ value = typ()
+ # See _GeneratorContextManager.__exit__ for comments on subtleties
+ # in this implementation
+ try:
+ await self.gen.athrow(typ, value, traceback)
+ raise RuntimeError("generator didn't stop after throw()")
+ except StopAsyncIteration as exc:
+ return exc is not value
+ except RuntimeError as exc:
+ if exc is value:
+ return False
+ # Avoid suppressing if a StopIteration exception
+ # was passed to throw() and later wrapped into a RuntimeError
+ # (see PEP 479 for sync generators; async generators also
+ # have this behavior). But do this only if the exception wrapped
+ # by the RuntimeError is actully Stop(Async)Iteration (see
+ # issue29692).
+ if isinstance(value, (StopIteration, StopAsyncIteration)):
+ if exc.__cause__ is value:
+ return False
+ raise
+ except BaseException as exc:
+ if exc is not value:
+ raise
+
+
def contextmanager(func):
"""@contextmanager decorator.
@@ -153,7 +208,6 @@ def contextmanager(func):
<body>
finally:
<cleanup>
-
"""
@wraps(func)
def helper(*args, **kwds):
@@ -161,6 +215,39 @@ def contextmanager(func):
return helper
+def asynccontextmanager(func):
+ """@asynccontextmanager decorator.
+
+ Typical usage:
+
+ @asynccontextmanager
+ async def some_async_generator(<arguments>):
+ <setup>
+ try:
+ yield <value>
+ finally:
+ <cleanup>
+
+ This makes this:
+
+ async with some_async_generator(<arguments>) as <variable>:
+ <body>
+
+ equivalent to this:
+
+ <setup>
+ try:
+ <variable> = <value>
+ <body>
+ finally:
+ <cleanup>
+ """
+ @wraps(func)
+ def helper(*args, **kwds):
+ return _AsyncGeneratorContextManager(func, args, kwds)
+ return helper
+
+
class closing(AbstractContextManager):
"""Context to automatically close something at the end of a block.