4
4
from collections import deque
5
5
from functools import wraps
6
6
7
- __all__ = ["contextmanager " , "closing " , "AbstractContextManager " ,
8
- "ContextDecorator " , "ExitStack " , "redirect_stdout " ,
9
- "redirect_stderr" , "suppress" ]
7
+ __all__ = ["asynccontextmanager " , "contextmanager " , "closing " ,
8
+ "AbstractContextManager " , "ContextDecorator " , "ExitStack " ,
9
+ "redirect_stdout" , " redirect_stderr" , "suppress" ]
10
10
11
11
12
12
class AbstractContextManager (abc .ABC ):
@@ -54,8 +54,8 @@ def inner(*args, **kwds):
54
54
return inner
55
55
56
56
57
- class _GeneratorContextManager ( ContextDecorator , AbstractContextManager ) :
58
- """Helper for @contextmanager decorator ."""
57
+ class _GeneratorContextManagerBase :
58
+ """Shared functionality for @contextmanager and @asynccontextmanager ."""
59
59
60
60
def __init__ (self , func , args , kwds ):
61
61
self .gen = func (* args , ** kwds )
@@ -71,6 +71,12 @@ def __init__(self, func, args, kwds):
71
71
# for the class instead.
72
72
# See http://bugs.python.org/issue19404 for more details.
73
73
74
+
75
+ class _GeneratorContextManager (_GeneratorContextManagerBase ,
76
+ AbstractContextManager ,
77
+ ContextDecorator ):
78
+ """Helper for @contextmanager decorator."""
79
+
74
80
def _recreate_cm (self ):
75
81
# _GCM instances are one-shot context managers, so the
76
82
# CM must be recreated each time a decorated function is
@@ -121,12 +127,61 @@ def __exit__(self, type, value, traceback):
121
127
# fixes the impedance mismatch between the throw() protocol
122
128
# and the __exit__() protocol.
123
129
#
130
+ # This cannot use 'except BaseException as exc' (as in the
131
+ # async implementation) to maintain compatibility with
132
+ # Python 2, where old-style class exceptions are not caught
133
+ # by 'except BaseException'.
124
134
if sys .exc_info ()[1 ] is value :
125
135
return False
126
136
raise
127
137
raise RuntimeError ("generator didn't stop after throw()" )
128
138
129
139
140
+ class _AsyncGeneratorContextManager (_GeneratorContextManagerBase ):
141
+ """Helper for @asynccontextmanager."""
142
+
143
+ async def __aenter__ (self ):
144
+ try :
145
+ return await self .gen .__anext__ ()
146
+ except StopAsyncIteration :
147
+ raise RuntimeError ("generator didn't yield" ) from None
148
+
149
+ async def __aexit__ (self , typ , value , traceback ):
150
+ if typ is None :
151
+ try :
152
+ await self .gen .__anext__ ()
153
+ except StopAsyncIteration :
154
+ return
155
+ else :
156
+ raise RuntimeError ("generator didn't stop" )
157
+ else :
158
+ if value is None :
159
+ value = typ ()
160
+ # See _GeneratorContextManager.__exit__ for comments on subtleties
161
+ # in this implementation
162
+ try :
163
+ await self .gen .athrow (typ , value , traceback )
164
+ raise RuntimeError ("generator didn't stop after throw()" )
165
+ except StopAsyncIteration as exc :
166
+ return exc is not value
167
+ except RuntimeError as exc :
168
+ if exc is value :
169
+ return False
170
+ # Avoid suppressing if a StopIteration exception
171
+ # was passed to throw() and later wrapped into a RuntimeError
172
+ # (see PEP 479 for sync generators; async generators also
173
+ # have this behavior). But do this only if the exception wrapped
174
+ # by the RuntimeError is actully Stop(Async)Iteration (see
175
+ # issue29692).
176
+ if isinstance (value , (StopIteration , StopAsyncIteration )):
177
+ if exc .__cause__ is value :
178
+ return False
179
+ raise
180
+ except BaseException as exc :
181
+ if exc is not value :
182
+ raise
183
+
184
+
130
185
def contextmanager (func ):
131
186
"""@contextmanager decorator.
132
187
@@ -153,14 +208,46 @@ def some_generator(<arguments>):
153
208
<body>
154
209
finally:
155
210
<cleanup>
156
-
157
211
"""
158
212
@wraps (func )
159
213
def helper (* args , ** kwds ):
160
214
return _GeneratorContextManager (func , args , kwds )
161
215
return helper
162
216
163
217
218
+ def asynccontextmanager (func ):
219
+ """@asynccontextmanager decorator.
220
+
221
+ Typical usage:
222
+
223
+ @asynccontextmanager
224
+ async def some_async_generator(<arguments>):
225
+ <setup>
226
+ try:
227
+ yield <value>
228
+ finally:
229
+ <cleanup>
230
+
231
+ This makes this:
232
+
233
+ async with some_async_generator(<arguments>) as <variable>:
234
+ <body>
235
+
236
+ equivalent to this:
237
+
238
+ <setup>
239
+ try:
240
+ <variable> = <value>
241
+ <body>
242
+ finally:
243
+ <cleanup>
244
+ """
245
+ @wraps (func )
246
+ def helper (* args , ** kwds ):
247
+ return _AsyncGeneratorContextManager (func , args , kwds )
248
+ return helper
249
+
250
+
164
251
class closing (AbstractContextManager ):
165
252
"""Context to automatically close something at the end of a block.
166
253
0 commit comments