blob: eb5946145b47ee1423958e7b1033a8543b59abb2 [file] [log] [blame]
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00001"""Utilities for with-statement contexts. See PEP 343."""
Brett Cannon9e080e02016-04-08 12:15:27 -07002import abc
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00003import sys
Jelle Zijlstra57161aa2017-06-09 08:21:47 -07004import _collections_abc
Nick Coghlan3267a302012-05-21 22:54:43 +10005from collections import deque
Christian Heimes81ee3ef2008-05-04 22:42:01 +00006from functools import wraps
Guido van Rossum48b069a2020-04-07 09:50:06 -07007from types import MethodType, GenericAlias
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00008
Jesse-Bakker0784a2e2017-11-23 01:23:28 +01009__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
Jelle Zijlstra176baa32017-12-13 17:19:17 -080010 "AbstractContextManager", "AbstractAsyncContextManager",
Ilya Kulakov1aa094f2018-01-25 12:51:18 -080011 "AsyncExitStack", "ContextDecorator", "ExitStack",
Tom Gringauzd0d4a452020-11-18 01:18:05 +020012 "redirect_stdout", "redirect_stderr", "suppress", "aclosing"]
Brett Cannon9e080e02016-04-08 12:15:27 -070013
14
15class AbstractContextManager(abc.ABC):
16
17 """An abstract base class for context managers."""
18
Guido van Rossum48b069a2020-04-07 09:50:06 -070019 __class_getitem__ = classmethod(GenericAlias)
20
Brett Cannon9e080e02016-04-08 12:15:27 -070021 def __enter__(self):
22 """Return `self` upon entering the runtime context."""
23 return self
24
25 @abc.abstractmethod
26 def __exit__(self, exc_type, exc_value, traceback):
27 """Raise any exception triggered within the runtime context."""
28 return None
29
30 @classmethod
31 def __subclasshook__(cls, C):
32 if cls is AbstractContextManager:
Jelle Zijlstra57161aa2017-06-09 08:21:47 -070033 return _collections_abc._check_methods(C, "__enter__", "__exit__")
Brett Cannon9e080e02016-04-08 12:15:27 -070034 return NotImplemented
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000035
Michael Foordb3a89842010-06-30 12:17:50 +000036
Jelle Zijlstra176baa32017-12-13 17:19:17 -080037class AbstractAsyncContextManager(abc.ABC):
38
39 """An abstract base class for asynchronous context managers."""
40
Guido van Rossum48b069a2020-04-07 09:50:06 -070041 __class_getitem__ = classmethod(GenericAlias)
42
Jelle Zijlstra176baa32017-12-13 17:19:17 -080043 async def __aenter__(self):
44 """Return `self` upon entering the runtime context."""
45 return self
46
47 @abc.abstractmethod
48 async def __aexit__(self, exc_type, exc_value, traceback):
49 """Raise any exception triggered within the runtime context."""
50 return None
51
52 @classmethod
53 def __subclasshook__(cls, C):
54 if cls is AbstractAsyncContextManager:
55 return _collections_abc._check_methods(C, "__aenter__",
56 "__aexit__")
57 return NotImplemented
58
59
Michael Foordb3a89842010-06-30 12:17:50 +000060class ContextDecorator(object):
61 "A base class or mixin that enables context managers to work as decorators."
Nick Coghlan0ded3e32011-05-05 23:49:25 +100062
63 def _recreate_cm(self):
64 """Return a recreated instance of self.
Nick Coghlanfdc2c552011-05-06 00:02:12 +100065
Nick Coghlan3267a302012-05-21 22:54:43 +100066 Allows an otherwise one-shot context manager like
Nick Coghlan0ded3e32011-05-05 23:49:25 +100067 _GeneratorContextManager to support use as
Nick Coghlan3267a302012-05-21 22:54:43 +100068 a decorator via implicit recreation.
Nick Coghlanfdc2c552011-05-06 00:02:12 +100069
Nick Coghlan3267a302012-05-21 22:54:43 +100070 This is a private interface just for _GeneratorContextManager.
71 See issue #11647 for details.
Nick Coghlan0ded3e32011-05-05 23:49:25 +100072 """
73 return self
74
Michael Foordb3a89842010-06-30 12:17:50 +000075 def __call__(self, func):
76 @wraps(func)
77 def inner(*args, **kwds):
Nick Coghlan0ded3e32011-05-05 23:49:25 +100078 with self._recreate_cm():
Michael Foordb3a89842010-06-30 12:17:50 +000079 return func(*args, **kwds)
80 return inner
81
82
Kazantcev Andrey178695b2020-11-05 11:52:24 +030083class AsyncContextDecorator(object):
84 "A base class or mixin that enables async context managers to work as decorators."
85
86 def _recreate_cm(self):
87 """Return a recreated instance of self.
88 """
89 return self
90
91 def __call__(self, func):
92 @wraps(func)
93 async def inner(*args, **kwds):
94 async with self._recreate_cm():
95 return await func(*args, **kwds)
96 return inner
97
98
Jelle Zijlstra2e624692017-04-30 18:25:58 -070099class _GeneratorContextManagerBase:
100 """Shared functionality for @contextmanager and @asynccontextmanager."""
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000101
Serhiy Storchaka101ff352015-06-28 17:06:07 +0300102 def __init__(self, func, args, kwds):
Nick Coghlan0ded3e32011-05-05 23:49:25 +1000103 self.gen = func(*args, **kwds)
104 self.func, self.args, self.kwds = func, args, kwds
Nick Coghlan059def52013-10-26 18:08:15 +1000105 # Issue 19330: ensure context manager instances have good docstrings
106 doc = getattr(func, "__doc__", None)
107 if doc is None:
108 doc = type(self).__doc__
109 self.__doc__ = doc
110 # Unfortunately, this still doesn't provide good help output when
111 # inspecting the created context manager instances, since pydoc
112 # currently bypasses the instance docstring and shows the docstring
113 # for the class instead.
114 # See http://bugs.python.org/issue19404 for more details.
Nick Coghlan0ded3e32011-05-05 23:49:25 +1000115
Jelle Zijlstra2e624692017-04-30 18:25:58 -0700116
117class _GeneratorContextManager(_GeneratorContextManagerBase,
118 AbstractContextManager,
119 ContextDecorator):
120 """Helper for @contextmanager decorator."""
121
Nick Coghlan0ded3e32011-05-05 23:49:25 +1000122 def _recreate_cm(self):
123 # _GCM instances are one-shot context managers, so the
124 # CM must be recreated each time a decorated function is
125 # called
Serhiy Storchaka101ff352015-06-28 17:06:07 +0300126 return self.__class__(self.func, self.args, self.kwds)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000127
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000128 def __enter__(self):
Martin Teichmanndd0e0872018-01-28 05:17:46 +0100129 # do not keep args and kwds alive unnecessarily
130 # they are only needed for recreation, which is not possible anymore
131 del self.args, self.kwds, self.func
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000132 try:
Georg Brandla18af4e2007-04-21 15:47:16 +0000133 return next(self.gen)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000134 except StopIteration:
Nick Coghlan8608d262013-10-20 00:30:51 +1000135 raise RuntimeError("generator didn't yield") from None
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000136
137 def __exit__(self, type, value, traceback):
138 if type is None:
139 try:
Georg Brandla18af4e2007-04-21 15:47:16 +0000140 next(self.gen)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000141 except StopIteration:
svelankar00c75e92017-04-11 05:11:13 -0400142 return False
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000143 else:
144 raise RuntimeError("generator didn't stop")
145 else:
Guido van Rossum2cc30da2007-11-02 23:46:40 +0000146 if value is None:
147 # Need to force instantiation so we can reliably
148 # tell if we get the same exception back
149 value = type()
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000150 try:
151 self.gen.throw(type, value, traceback)
Guido van Rossumb940e112007-01-10 16:19:56 +0000152 except StopIteration as exc:
Yury Selivanov8170e8c2015-05-09 11:44:30 -0400153 # Suppress StopIteration *unless* it's the same exception that
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000154 # was passed to throw(). This prevents a StopIteration
Yury Selivanov8170e8c2015-05-09 11:44:30 -0400155 # raised inside the "with" statement from being suppressed.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000156 return exc is not value
Yury Selivanov8170e8c2015-05-09 11:44:30 -0400157 except RuntimeError as exc:
Nathaniel J. Smithaf88e7e2017-02-12 03:37:24 -0800158 # Don't re-raise the passed in exception. (issue27122)
Gregory P. Smithba2ecd62016-06-14 09:19:20 -0700159 if exc is value:
160 return False
Yury Selivanov8170e8c2015-05-09 11:44:30 -0400161 # Likewise, avoid suppressing if a StopIteration exception
162 # was passed to throw() and later wrapped into a RuntimeError
163 # (see PEP 479).
svelankar00c75e92017-04-11 05:11:13 -0400164 if type is StopIteration and exc.__cause__ is value:
Yury Selivanov8170e8c2015-05-09 11:44:30 -0400165 return False
166 raise
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000167 except:
168 # only re-raise if it's *not* the exception that was
169 # passed to throw(), because __exit__() must not raise
170 # an exception unless __exit__() itself failed. But throw()
171 # has to raise the exception to signal propagation, so this
172 # fixes the impedance mismatch between the throw() protocol
173 # and the __exit__() protocol.
174 #
Jelle Zijlstra2e624692017-04-30 18:25:58 -0700175 # This cannot use 'except BaseException as exc' (as in the
176 # async implementation) to maintain compatibility with
177 # Python 2, where old-style class exceptions are not caught
178 # by 'except BaseException'.
svelankar00c75e92017-04-11 05:11:13 -0400179 if sys.exc_info()[1] is value:
180 return False
181 raise
182 raise RuntimeError("generator didn't stop after throw()")
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000183
184
Jelle Zijlstra176baa32017-12-13 17:19:17 -0800185class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
Kazantcev Andrey178695b2020-11-05 11:52:24 +0300186 AbstractAsyncContextManager,
187 AsyncContextDecorator):
Jelle Zijlstra2e624692017-04-30 18:25:58 -0700188 """Helper for @asynccontextmanager."""
189
Kazantcev Andrey178695b2020-11-05 11:52:24 +0300190 def _recreate_cm(self):
191 # _AGCM instances are one-shot context managers, so the
192 # ACM must be recreated each time a decorated function is
193 # called
194 return self.__class__(self.func, self.args, self.kwds)
195
Jelle Zijlstra2e624692017-04-30 18:25:58 -0700196 async def __aenter__(self):
197 try:
198 return await self.gen.__anext__()
199 except StopAsyncIteration:
200 raise RuntimeError("generator didn't yield") from None
201
202 async def __aexit__(self, typ, value, traceback):
203 if typ is None:
204 try:
205 await self.gen.__anext__()
206 except StopAsyncIteration:
207 return
208 else:
209 raise RuntimeError("generator didn't stop")
210 else:
211 if value is None:
212 value = typ()
213 # See _GeneratorContextManager.__exit__ for comments on subtleties
214 # in this implementation
215 try:
216 await self.gen.athrow(typ, value, traceback)
Yury Selivanov52698c72018-06-07 20:31:26 -0400217 raise RuntimeError("generator didn't stop after athrow()")
Jelle Zijlstra2e624692017-04-30 18:25:58 -0700218 except StopAsyncIteration as exc:
219 return exc is not value
220 except RuntimeError as exc:
221 if exc is value:
222 return False
223 # Avoid suppressing if a StopIteration exception
224 # was passed to throw() and later wrapped into a RuntimeError
225 # (see PEP 479 for sync generators; async generators also
226 # have this behavior). But do this only if the exception wrapped
227 # by the RuntimeError is actully Stop(Async)Iteration (see
228 # issue29692).
229 if isinstance(value, (StopIteration, StopAsyncIteration)):
230 if exc.__cause__ is value:
231 return False
232 raise
233 except BaseException as exc:
234 if exc is not value:
235 raise
236
237
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000238def contextmanager(func):
239 """@contextmanager decorator.
240
241 Typical usage:
242
243 @contextmanager
244 def some_generator(<arguments>):
245 <setup>
246 try:
247 yield <value>
248 finally:
249 <cleanup>
250
251 This makes this:
252
253 with some_generator(<arguments>) as <variable>:
254 <body>
255
256 equivalent to this:
257
258 <setup>
259 try:
260 <variable> = <value>
261 <body>
262 finally:
263 <cleanup>
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000264 """
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000265 @wraps(func)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000266 def helper(*args, **kwds):
Serhiy Storchaka101ff352015-06-28 17:06:07 +0300267 return _GeneratorContextManager(func, args, kwds)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000268 return helper
269
270
Jelle Zijlstra2e624692017-04-30 18:25:58 -0700271def asynccontextmanager(func):
272 """@asynccontextmanager decorator.
273
274 Typical usage:
275
276 @asynccontextmanager
277 async def some_async_generator(<arguments>):
278 <setup>
279 try:
280 yield <value>
281 finally:
282 <cleanup>
283
284 This makes this:
285
286 async with some_async_generator(<arguments>) as <variable>:
287 <body>
288
289 equivalent to this:
290
291 <setup>
292 try:
293 <variable> = <value>
294 <body>
295 finally:
296 <cleanup>
297 """
298 @wraps(func)
299 def helper(*args, **kwds):
300 return _AsyncGeneratorContextManager(func, args, kwds)
301 return helper
302
303
Brett Cannon9e080e02016-04-08 12:15:27 -0700304class closing(AbstractContextManager):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000305 """Context to automatically close something at the end of a block.
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000306
307 Code like this:
308
309 with closing(<module>.open(<arguments>)) as f:
310 <block>
311
312 is equivalent to this:
313
314 f = <module>.open(<arguments>)
315 try:
316 <block>
317 finally:
318 f.close()
319
320 """
Thomas Wouters477c8d52006-05-27 19:21:47 +0000321 def __init__(self, thing):
322 self.thing = thing
323 def __enter__(self):
324 return self.thing
325 def __exit__(self, *exc_info):
326 self.thing.close()
Nick Coghlan3267a302012-05-21 22:54:43 +1000327
Berker Peksagbb44fe02014-11-28 23:28:06 +0200328
Joongi Kim6e8dcda2020-11-02 17:02:48 +0900329class aclosing(AbstractAsyncContextManager):
330 """Async context manager for safely finalizing an asynchronously cleaned-up
331 resource such as an async generator, calling its ``aclose()`` method.
332
333 Code like this:
334
335 async with aclosing(<module>.fetch(<arguments>)) as agen:
336 <block>
337
338 is equivalent to this:
339
340 agen = <module>.fetch(<arguments>)
341 try:
342 <block>
343 finally:
344 await agen.aclose()
345
346 """
347 def __init__(self, thing):
348 self.thing = thing
349 async def __aenter__(self):
350 return self.thing
351 async def __aexit__(self, *exc_info):
352 await self.thing.aclose()
353
354
Brett Cannon9e080e02016-04-08 12:15:27 -0700355class _RedirectStream(AbstractContextManager):
Berker Peksagbb44fe02014-11-28 23:28:06 +0200356
357 _stream = None
358
359 def __init__(self, new_target):
360 self._new_target = new_target
361 # We use a list of old targets to make this CM re-entrant
362 self._old_targets = []
363
364 def __enter__(self):
365 self._old_targets.append(getattr(sys, self._stream))
366 setattr(sys, self._stream, self._new_target)
367 return self._new_target
368
369 def __exit__(self, exctype, excinst, exctb):
370 setattr(sys, self._stream, self._old_targets.pop())
371
372
373class redirect_stdout(_RedirectStream):
374 """Context manager for temporarily redirecting stdout to another file.
Nick Coghlan059def52013-10-26 18:08:15 +1000375
376 # How to send help() to stderr
377 with redirect_stdout(sys.stderr):
378 help(dir)
379
380 # How to write help() to a file
381 with open('help.txt', 'w') as f:
382 with redirect_stdout(f):
383 help(pow)
384 """
Nick Coghlan8608d262013-10-20 00:30:51 +1000385
Berker Peksagbb44fe02014-11-28 23:28:06 +0200386 _stream = "stdout"
Nick Coghlan8608d262013-10-20 00:30:51 +1000387
Nick Coghlan8608d262013-10-20 00:30:51 +1000388
Berker Peksagbb44fe02014-11-28 23:28:06 +0200389class redirect_stderr(_RedirectStream):
390 """Context manager for temporarily redirecting stderr to another file."""
391
392 _stream = "stderr"
Raymond Hettinger088cbf22013-10-10 00:46:57 -0700393
Nick Coghlan8608d262013-10-20 00:30:51 +1000394
Brett Cannon9e080e02016-04-08 12:15:27 -0700395class suppress(AbstractContextManager):
Nick Coghlan240f86d2013-10-17 23:40:57 +1000396 """Context manager to suppress specified exceptions
Raymond Hettingere318a882013-03-10 22:26:51 -0700397
Nick Coghlan8608d262013-10-20 00:30:51 +1000398 After the exception is suppressed, execution proceeds with the next
399 statement following the with statement.
Raymond Hettingere318a882013-03-10 22:26:51 -0700400
Nick Coghlan8608d262013-10-20 00:30:51 +1000401 with suppress(FileNotFoundError):
402 os.remove(somefile)
403 # Execution still resumes here if the file was already removed
Raymond Hettingere318a882013-03-10 22:26:51 -0700404 """
Nick Coghlan059def52013-10-26 18:08:15 +1000405
406 def __init__(self, *exceptions):
407 self._exceptions = exceptions
408
409 def __enter__(self):
410 pass
411
412 def __exit__(self, exctype, excinst, exctb):
413 # Unlike isinstance and issubclass, CPython exception handling
414 # currently only looks at the concrete type hierarchy (ignoring
415 # the instance and subclass checking hooks). While Guido considers
416 # that a bug rather than a feature, it's a fairly hard one to fix
417 # due to various internal implementation details. suppress provides
418 # the simpler issubclass based semantics, rather than trying to
419 # exactly reproduce the limitations of the CPython interpreter.
420 #
421 # See http://bugs.python.org/issue12029 for more details
422 return exctype is not None and issubclass(exctype, self._exceptions)
423
Nick Coghlan3267a302012-05-21 22:54:43 +1000424
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800425class _BaseExitStack:
426 """A base class for ExitStack and AsyncExitStack."""
Nick Coghlan3267a302012-05-21 22:54:43 +1000427
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800428 @staticmethod
429 def _create_exit_wrapper(cm, cm_exit):
jdemeyer23ab5ee2018-04-13 14:22:46 +0200430 return MethodType(cm_exit, cm)
Nick Coghlan3267a302012-05-21 22:54:43 +1000431
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800432 @staticmethod
Serhiy Storchaka2085bd02019-06-01 11:00:15 +0300433 def _create_cb_wrapper(callback, /, *args, **kwds):
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800434 def _exit_wrapper(exc_type, exc, tb):
435 callback(*args, **kwds)
436 return _exit_wrapper
Nick Coghlan3267a302012-05-21 22:54:43 +1000437
Nick Coghlan3267a302012-05-21 22:54:43 +1000438 def __init__(self):
439 self._exit_callbacks = deque()
440
441 def pop_all(self):
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800442 """Preserve the context stack by transferring it to a new instance."""
Nick Coghlan3267a302012-05-21 22:54:43 +1000443 new_stack = type(self)()
444 new_stack._exit_callbacks = self._exit_callbacks
445 self._exit_callbacks = deque()
446 return new_stack
447
Nick Coghlan3267a302012-05-21 22:54:43 +1000448 def push(self, exit):
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800449 """Registers a callback with the standard __exit__ method signature.
Nick Coghlan3267a302012-05-21 22:54:43 +1000450
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800451 Can suppress exceptions the same way __exit__ method can.
Nick Coghlan3267a302012-05-21 22:54:43 +1000452 Also accepts any object with an __exit__ method (registering a call
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800453 to the method instead of the object itself).
Nick Coghlan3267a302012-05-21 22:54:43 +1000454 """
455 # We use an unbound method rather than a bound method to follow
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800456 # the standard lookup behaviour for special methods.
Nick Coghlan3267a302012-05-21 22:54:43 +1000457 _cb_type = type(exit)
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800458
Nick Coghlan3267a302012-05-21 22:54:43 +1000459 try:
460 exit_method = _cb_type.__exit__
461 except AttributeError:
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800462 # Not a context manager, so assume it's a callable.
463 self._push_exit_callback(exit)
Nick Coghlan3267a302012-05-21 22:54:43 +1000464 else:
465 self._push_cm_exit(exit, exit_method)
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800466 return exit # Allow use as a decorator.
Nick Coghlan3267a302012-05-21 22:54:43 +1000467
468 def enter_context(self, cm):
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800469 """Enters the supplied context manager.
Nick Coghlan3267a302012-05-21 22:54:43 +1000470
471 If successful, also pushes its __exit__ method as a callback and
472 returns the result of the __enter__ method.
473 """
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800474 # We look up the special methods on the type to match the with
475 # statement.
Nick Coghlan3267a302012-05-21 22:54:43 +1000476 _cm_type = type(cm)
477 _exit = _cm_type.__exit__
478 result = _cm_type.__enter__(cm)
479 self._push_cm_exit(cm, _exit)
480 return result
481
Serhiy Storchaka142566c2019-06-05 18:22:31 +0300482 def callback(self, callback, /, *args, **kwds):
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800483 """Registers an arbitrary callback and arguments.
484
485 Cannot suppress exceptions.
486 """
487 _exit_wrapper = self._create_cb_wrapper(callback, *args, **kwds)
488
489 # We changed the signature, so using @wraps is not appropriate, but
490 # setting __wrapped__ may still help with introspection.
491 _exit_wrapper.__wrapped__ = callback
492 self._push_exit_callback(_exit_wrapper)
493 return callback # Allow use as a decorator
494
495 def _push_cm_exit(self, cm, cm_exit):
496 """Helper to correctly register callbacks to __exit__ methods."""
497 _exit_wrapper = self._create_exit_wrapper(cm, cm_exit)
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800498 self._push_exit_callback(_exit_wrapper, True)
499
500 def _push_exit_callback(self, callback, is_sync=True):
501 self._exit_callbacks.append((is_sync, callback))
502
503
504# Inspired by discussions on http://bugs.python.org/issue13585
505class ExitStack(_BaseExitStack, AbstractContextManager):
506 """Context manager for dynamic management of a stack of exit callbacks.
507
508 For example:
509 with ExitStack() as stack:
510 files = [stack.enter_context(open(fname)) for fname in filenames]
511 # All opened files will automatically be closed at the end of
512 # the with statement, even if attempts to open files later
513 # in the list raise an exception.
514 """
515
516 def __enter__(self):
517 return self
Nick Coghlan3267a302012-05-21 22:54:43 +1000518
Nick Coghlan3267a302012-05-21 22:54:43 +1000519 def __exit__(self, *exc_details):
Nick Coghlan1a33b2f2013-10-01 23:24:56 +1000520 received_exc = exc_details[0] is not None
521
Nick Coghlan77452fc2012-06-01 22:48:32 +1000522 # We manipulate the exception state so it behaves as though
523 # we were actually nesting multiple with statements
524 frame_exc = sys.exc_info()[1]
525 def _fix_exception_context(new_exc, old_exc):
Nick Coghlanadd94c92014-01-24 23:05:45 +1000526 # Context may not be correct, so find the end of the chain
Nick Coghlan77452fc2012-06-01 22:48:32 +1000527 while 1:
528 exc_context = new_exc.__context__
Nick Coghlan09761e72014-01-22 22:24:46 +1000529 if exc_context is old_exc:
530 # Context is already set correctly (see issue 20317)
531 return
532 if exc_context is None or exc_context is frame_exc:
Nick Coghlan77452fc2012-06-01 22:48:32 +1000533 break
534 new_exc = exc_context
Nick Coghlan09761e72014-01-22 22:24:46 +1000535 # Change the end of the chain to point to the exception
536 # we expect it to reference
Nick Coghlan77452fc2012-06-01 22:48:32 +1000537 new_exc.__context__ = old_exc
538
Nick Coghlana5bd2a12012-06-01 00:00:38 +1000539 # Callbacks are invoked in LIFO order to match the behaviour of
540 # nested context managers
541 suppressed_exc = False
Nick Coghlan1a33b2f2013-10-01 23:24:56 +1000542 pending_raise = False
Nick Coghlana5bd2a12012-06-01 00:00:38 +1000543 while self._exit_callbacks:
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800544 is_sync, cb = self._exit_callbacks.pop()
545 assert is_sync
Nick Coghlan3267a302012-05-21 22:54:43 +1000546 try:
Nick Coghlana5bd2a12012-06-01 00:00:38 +1000547 if cb(*exc_details):
548 suppressed_exc = True
Nick Coghlan1a33b2f2013-10-01 23:24:56 +1000549 pending_raise = False
Nick Coghlan3267a302012-05-21 22:54:43 +1000550 exc_details = (None, None, None)
Nick Coghlana5bd2a12012-06-01 00:00:38 +1000551 except:
552 new_exc_details = sys.exc_info()
Nick Coghlan77452fc2012-06-01 22:48:32 +1000553 # simulate the stack of exceptions by setting the context
554 _fix_exception_context(new_exc_details[1], exc_details[1])
Nick Coghlan1a33b2f2013-10-01 23:24:56 +1000555 pending_raise = True
Nick Coghlana5bd2a12012-06-01 00:00:38 +1000556 exc_details = new_exc_details
Nick Coghlan1a33b2f2013-10-01 23:24:56 +1000557 if pending_raise:
558 try:
559 # bare "raise exc_details[1]" replaces our carefully
560 # set-up context
561 fixed_ctx = exc_details[1].__context__
562 raise exc_details[1]
563 except BaseException:
564 exc_details[1].__context__ = fixed_ctx
565 raise
566 return received_exc and suppressed_exc
Jesse-Bakker0784a2e2017-11-23 01:23:28 +0100567
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800568 def close(self):
569 """Immediately unwind the context stack."""
570 self.__exit__(None, None, None)
571
572
573# Inspired by discussions on https://bugs.python.org/issue29302
574class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
575 """Async context manager for dynamic management of a stack of exit
576 callbacks.
577
578 For example:
579 async with AsyncExitStack() as stack:
580 connections = [await stack.enter_async_context(get_connection())
581 for i in range(5)]
582 # All opened connections will automatically be released at the
583 # end of the async with statement, even if attempts to open a
584 # connection later in the list raise an exception.
585 """
586
587 @staticmethod
588 def _create_async_exit_wrapper(cm, cm_exit):
jdemeyer23ab5ee2018-04-13 14:22:46 +0200589 return MethodType(cm_exit, cm)
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800590
591 @staticmethod
Serhiy Storchaka2085bd02019-06-01 11:00:15 +0300592 def _create_async_cb_wrapper(callback, /, *args, **kwds):
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800593 async def _exit_wrapper(exc_type, exc, tb):
594 await callback(*args, **kwds)
595 return _exit_wrapper
596
597 async def enter_async_context(self, cm):
598 """Enters the supplied async context manager.
599
600 If successful, also pushes its __aexit__ method as a callback and
601 returns the result of the __aenter__ method.
602 """
603 _cm_type = type(cm)
604 _exit = _cm_type.__aexit__
605 result = await _cm_type.__aenter__(cm)
606 self._push_async_cm_exit(cm, _exit)
607 return result
608
609 def push_async_exit(self, exit):
610 """Registers a coroutine function with the standard __aexit__ method
611 signature.
612
613 Can suppress exceptions the same way __aexit__ method can.
614 Also accepts any object with an __aexit__ method (registering a call
615 to the method instead of the object itself).
616 """
617 _cb_type = type(exit)
618 try:
619 exit_method = _cb_type.__aexit__
620 except AttributeError:
621 # Not an async context manager, so assume it's a coroutine function
622 self._push_exit_callback(exit, False)
623 else:
624 self._push_async_cm_exit(exit, exit_method)
625 return exit # Allow use as a decorator
626
Serhiy Storchaka142566c2019-06-05 18:22:31 +0300627 def push_async_callback(self, callback, /, *args, **kwds):
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800628 """Registers an arbitrary coroutine function and arguments.
629
630 Cannot suppress exceptions.
631 """
632 _exit_wrapper = self._create_async_cb_wrapper(callback, *args, **kwds)
633
634 # We changed the signature, so using @wraps is not appropriate, but
635 # setting __wrapped__ may still help with introspection.
636 _exit_wrapper.__wrapped__ = callback
637 self._push_exit_callback(_exit_wrapper, False)
638 return callback # Allow use as a decorator
639
640 async def aclose(self):
641 """Immediately unwind the context stack."""
642 await self.__aexit__(None, None, None)
643
644 def _push_async_cm_exit(self, cm, cm_exit):
645 """Helper to correctly register coroutine function to __aexit__
646 method."""
647 _exit_wrapper = self._create_async_exit_wrapper(cm, cm_exit)
Ilya Kulakov1aa094f2018-01-25 12:51:18 -0800648 self._push_exit_callback(_exit_wrapper, False)
649
650 async def __aenter__(self):
651 return self
652
653 async def __aexit__(self, *exc_details):
654 received_exc = exc_details[0] is not None
655
656 # We manipulate the exception state so it behaves as though
657 # we were actually nesting multiple with statements
658 frame_exc = sys.exc_info()[1]
659 def _fix_exception_context(new_exc, old_exc):
660 # Context may not be correct, so find the end of the chain
661 while 1:
662 exc_context = new_exc.__context__
663 if exc_context is old_exc:
664 # Context is already set correctly (see issue 20317)
665 return
666 if exc_context is None or exc_context is frame_exc:
667 break
668 new_exc = exc_context
669 # Change the end of the chain to point to the exception
670 # we expect it to reference
671 new_exc.__context__ = old_exc
672
673 # Callbacks are invoked in LIFO order to match the behaviour of
674 # nested context managers
675 suppressed_exc = False
676 pending_raise = False
677 while self._exit_callbacks:
678 is_sync, cb = self._exit_callbacks.pop()
679 try:
680 if is_sync:
681 cb_suppress = cb(*exc_details)
682 else:
683 cb_suppress = await cb(*exc_details)
684
685 if cb_suppress:
686 suppressed_exc = True
687 pending_raise = False
688 exc_details = (None, None, None)
689 except:
690 new_exc_details = sys.exc_info()
691 # simulate the stack of exceptions by setting the context
692 _fix_exception_context(new_exc_details[1], exc_details[1])
693 pending_raise = True
694 exc_details = new_exc_details
695 if pending_raise:
696 try:
697 # bare "raise exc_details[1]" replaces our carefully
698 # set-up context
699 fixed_ctx = exc_details[1].__context__
700 raise exc_details[1]
701 except BaseException:
702 exc_details[1].__context__ = fixed_ctx
703 raise
704 return received_exc and suppressed_exc
705
Jesse-Bakker0784a2e2017-11-23 01:23:28 +0100706
Tom Gringauza1171672020-11-09 14:34:07 +0200707class nullcontext(AbstractContextManager, AbstractAsyncContextManager):
Jesse-Bakker0784a2e2017-11-23 01:23:28 +0100708 """Context manager that does no additional processing.
709
710 Used as a stand-in for a normal context manager, when a particular
711 block of code is only sometimes used with a normal context manager:
712
713 cm = optional_cm if condition else nullcontext()
714 with cm:
715 # Perform operation, using optional_cm if condition is True
716 """
717
718 def __init__(self, enter_result=None):
719 self.enter_result = enter_result
720
721 def __enter__(self):
722 return self.enter_result
723
724 def __exit__(self, *excinfo):
725 pass
Tom Gringauza1171672020-11-09 14:34:07 +0200726
727 async def __aenter__(self):
728 return self.enter_result
729
730 async def __aexit__(self, *excinfo):
731 pass