77
88__all__ = ["asynccontextmanager" ,"contextmanager" ,"closing" ,"nullcontext" ,
99"AbstractContextManager" ,"AbstractAsyncContextManager" ,
10- "ContextDecorator" ,"ExitStack" ,
10+ "AsyncExitStack" , " ContextDecorator" ,"ExitStack" ,
1111"redirect_stdout" ,"redirect_stderr" ,"suppress" ]
1212
1313
@@ -365,85 +365,102 @@ def __exit__(self, exctype, excinst, exctb):
365365return exctype is not None and issubclass (exctype ,self ._exceptions )
366366
367367
368- # Inspired by discussions on http://bugs.python.org/issue13585
369- class ExitStack (AbstractContextManager ):
370- """Context manager for dynamic management of a stack of exit callbacks
368+ class _BaseExitStack :
369+ """A base class for ExitStack and AsyncExitStack."""
371370
372- For example:
371+ @staticmethod
372+ def _create_exit_wrapper (cm ,cm_exit ):
373+ def _exit_wrapper (exc_type ,exc ,tb ):
374+ return cm_exit (cm ,exc_type ,exc ,tb )
375+ return _exit_wrapper
373376
374- with ExitStack() as stack:
375- files = [stack.enter_context(open(fname)) for fname in filenames]
376- # All opened files will automatically be closed at the end of
377- # the with statement, even if attempts to open files later
378- # in the list raise an exception
377+ @ staticmethod
378+ def _create_cb_wrapper ( callback , * args , ** kwds ):
379+ def _exit_wrapper ( exc_type , exc , tb ):
380+ callback ( * args , ** kwds )
381+ return _exit_wrapper
379382
380- """
381383def __init__ (self ):
382384self ._exit_callbacks = deque ()
383385
384386def pop_all (self ):
385- """Preserve the context stack by transferring it to a new instance"""
387+ """Preserve the context stack by transferring it to a new instance. """
386388new_stack = type (self )()
387389new_stack ._exit_callbacks = self ._exit_callbacks
388390self ._exit_callbacks = deque ()
389391return new_stack
390392
391- def _push_cm_exit (self ,cm ,cm_exit ):
392- """Helper to correctly register callbacks to __exit__ methods"""
393- def _exit_wrapper (* exc_details ):
394- return cm_exit (cm ,* exc_details )
395- _exit_wrapper .__self__ = cm
396- self .push (_exit_wrapper )
397-
398393def push (self ,exit ):
399- """Registers a callback with the standard __exit__ method signature
400-
401- Can suppress exceptions the same way __exit__ methods can.
394+ """Registers a callback with the standard __exit__ method signature.
402395
396+ Can suppress exceptions the same way __exit__ method can.
403397 Also accepts any object with an __exit__ method (registering a call
404- to the method instead of the object itself)
398+ to the method instead of the object itself).
405399 """
406400# We use an unbound method rather than a bound method to follow
407- # the standard lookup behaviour for special methods
401+ # the standard lookup behaviour for special methods.
408402_cb_type = type (exit )
403+
409404try :
410405exit_method = _cb_type .__exit__
411406except AttributeError :
412- # Not a context manager, so assumeits a callable
413- self ._exit_callbacks . append (exit )
407+ # Not a context manager, so assumeit's a callable.
408+ self ._push_exit_callback (exit )
414409else :
415410self ._push_cm_exit (exit ,exit_method )
416- return exit # Allow use as a decorator
417-
418- def callback (self ,callback ,* args ,** kwds ):
419- """Registers an arbitrary callback and arguments.
420-
421- Cannot suppress exceptions.
422- """
423- def _exit_wrapper (exc_type ,exc ,tb ):
424- callback (* args ,** kwds )
425- # We changed the signature, so using @wraps is not appropriate, but
426- # setting __wrapped__ may still help with introspection
427- _exit_wrapper .__wrapped__ = callback
428- self .push (_exit_wrapper )
429- return callback # Allow use as a decorator
411+ return exit # Allow use as a decorator.
430412
431413def enter_context (self ,cm ):
432- """Enters the supplied context manager
414+ """Enters the supplied context manager.
433415
434416 If successful, also pushes its __exit__ method as a callback and
435417 returns the result of the __enter__ method.
436418 """
437- # We look up the special methods on the type to match the with statement
419+ # We look up the special methods on the type to match the with
420+ # statement.
438421_cm_type = type (cm )
439422_exit = _cm_type .__exit__
440423result = _cm_type .__enter__ (cm )
441424self ._push_cm_exit (cm ,_exit )
442425return result
443426
444- def close (self ):
445- """Immediately unwind the context stack"""
446- self .__exit__ (None ,None ,None )
427+ def callback (self ,callback ,* args ,** kwds ):
428+ """Registers an arbitrary callback and arguments.
429+
430+ Cannot suppress exceptions.
431+ """
432+ _exit_wrapper = self ._create_cb_wrapper (callback ,* args ,** kwds )
433+
434+ # We changed the signature, so using @wraps is not appropriate, but
435+ # setting __wrapped__ may still help with introspection.
436+ _exit_wrapper .__wrapped__ = callback
437+ self ._push_exit_callback (_exit_wrapper )
438+ return callback # Allow use as a decorator
439+
440+ def _push_cm_exit (self ,cm ,cm_exit ):
441+ """Helper to correctly register callbacks to __exit__ methods."""
442+ _exit_wrapper = self ._create_exit_wrapper (cm ,cm_exit )
443+ _exit_wrapper .__self__ = cm
444+ self ._push_exit_callback (_exit_wrapper ,True )
445+
446+ def _push_exit_callback (self ,callback ,is_sync = True ):
447+ self ._exit_callbacks .append ((is_sync ,callback ))
448+
449+
450+ # Inspired by discussions on http://bugs.python.org/issue13585
451+ class ExitStack (_BaseExitStack ,AbstractContextManager ):
452+ """Context manager for dynamic management of a stack of exit callbacks.
453+
454+ For example:
455+ with ExitStack() as stack:
456+ files = [stack.enter_context(open(fname)) for fname in filenames]
457+ # All opened files will automatically be closed at the end of
458+ # the with statement, even if attempts to open files later
459+ # in the list raise an exception.
460+ """
461+
462+ def __enter__ (self ):
463+ return self
447464
448465def __exit__ (self ,* exc_details ):
449466received_exc = exc_details [0 ]is not None
@@ -470,7 +487,8 @@ def _fix_exception_context(new_exc, old_exc):
470487suppressed_exc = False
471488pending_raise = False
472489while self ._exit_callbacks :
473- cb = self ._exit_callbacks .pop ()
490+ is_sync ,cb = self ._exit_callbacks .pop ()
491+ assert is_sync
474492try :
475493if cb (* exc_details ):
476494suppressed_exc = True
@@ -493,6 +511,147 @@ def _fix_exception_context(new_exc, old_exc):
493511raise
494512return received_exc and suppressed_exc
495513
514+ def close (self ):
515+ """Immediately unwind the context stack."""
516+ self .__exit__ (None ,None ,None )
517+
518+
519+ # Inspired by discussions on https://bugs.python.org/issue29302
520+ class AsyncExitStack (_BaseExitStack ,AbstractAsyncContextManager ):
521+ """Async context manager for dynamic management of a stack of exit
522+ callbacks.
523+
524+ For example:
525+ async with AsyncExitStack() as stack:
526+ connections = [await stack.enter_async_context(get_connection())
527+ for i in range(5)]
528+ # All opened connections will automatically be released at the
529+ # end of the async with statement, even if attempts to open a
530+ # connection later in the list raise an exception.
531+ """
532+
533+ @staticmethod
534+ def _create_async_exit_wrapper (cm ,cm_exit ):
535+ async def _exit_wrapper (exc_type ,exc ,tb ):
536+ return await cm_exit (cm ,exc_type ,exc ,tb )
537+ return _exit_wrapper
538+
539+ @staticmethod
540+ def _create_async_cb_wrapper (callback ,* args ,** kwds ):
541+ async def _exit_wrapper (exc_type ,exc ,tb ):
542+ await callback (* args ,** kwds )
543+ return _exit_wrapper
544+
545+ async def enter_async_context (self ,cm ):
546+ """Enters the supplied async context manager.
547+
548+ If successful, also pushes its __aexit__ method as a callback and
549+ returns the result of the __aenter__ method.
550+ """
551+ _cm_type = type (cm )
552+ _exit = _cm_type .__aexit__
553+ result = await _cm_type .__aenter__ (cm )
554+ self ._push_async_cm_exit (cm ,_exit )
555+ return result
556+
557+ def push_async_exit (self ,exit ):
558+ """Registers a coroutine function with the standard __aexit__ method
559+ signature.
560+
561+ Can suppress exceptions the same way __aexit__ method can.
562+ Also accepts any object with an __aexit__ method (registering a call
563+ to the method instead of the object itself).
564+ """
565+ _cb_type = type (exit )
566+ try :
567+ exit_method = _cb_type .__aexit__
568+ except AttributeError :
569+ # Not an async context manager, so assume it's a coroutine function
570+ self ._push_exit_callback (exit ,False )
571+ else :
572+ self ._push_async_cm_exit (exit ,exit_method )
573+ return exit # Allow use as a decorator
574+
575+ def push_async_callback (self ,callback ,* args ,** kwds ):
576+ """Registers an arbitrary coroutine function and arguments.
577+
578+ Cannot suppress exceptions.
579+ """
580+ _exit_wrapper = self ._create_async_cb_wrapper (callback ,* args ,** kwds )
581+
582+ # We changed the signature, so using @wraps is not appropriate, but
583+ # setting __wrapped__ may still help with introspection.
584+ _exit_wrapper .__wrapped__ = callback
585+ self ._push_exit_callback (_exit_wrapper ,False )
586+ return callback # Allow use as a decorator
587+
588+ async def aclose (self ):
589+ """Immediately unwind the context stack."""
590+ await self .__aexit__ (None ,None ,None )
591+
592+ def _push_async_cm_exit (self ,cm ,cm_exit ):
593+ """Helper to correctly register coroutine function to __aexit__
594+ method."""
595+ _exit_wrapper = self ._create_async_exit_wrapper (cm ,cm_exit )
596+ _exit_wrapper .__self__ = cm
597+ self ._push_exit_callback (_exit_wrapper ,False )
598+
599+ async def __aenter__ (self ):
600+ return self
601+
602+ async def __aexit__ (self ,* exc_details ):
603+ received_exc = exc_details [0 ]is not None
604+
605+ # We manipulate the exception state so it behaves as though
606+ # we were actually nesting multiple with statements
607+ frame_exc = sys .exc_info ()[1 ]
608+ def _fix_exception_context (new_exc ,old_exc ):
609+ # Context may not be correct, so find the end of the chain
610+ while 1 :
611+ exc_context = new_exc .__context__
612+ if exc_context is old_exc :
613+ # Context is already set correctly (see issue 20317)
614+ return
615+ if exc_context is None or exc_context is frame_exc :
616+ break
617+ new_exc = exc_context
618+ # Change the end of the chain to point to the exception
619+ # we expect it to reference
620+ new_exc .__context__ = old_exc
621+
622+ # Callbacks are invoked in LIFO order to match the behaviour of
623+ # nested context managers
624+ suppressed_exc = False
625+ pending_raise = False
626+ while self ._exit_callbacks :
627+ is_sync ,cb = self ._exit_callbacks .pop ()
628+ try :
629+ if is_sync :
630+ cb_suppress = cb (* exc_details )
631+ else :
632+ cb_suppress = await cb (* exc_details )
633+
634+ if cb_suppress :
635+ suppressed_exc = True
636+ pending_raise = False
637+ exc_details = (None ,None ,None )
638+ except :
639+ new_exc_details = sys .exc_info ()
640+ # simulate the stack of exceptions by setting the context
641+ _fix_exception_context (new_exc_details [1 ],exc_details [1 ])
642+ pending_raise = True
643+ exc_details = new_exc_details
644+ if pending_raise :
645+ try :
646+ # bare "raise exc_details[1]" replaces our carefully
647+ # set-up context
648+ fixed_ctx = exc_details [1 ].__context__
649+ raise exc_details [1 ]
650+ except BaseException :
651+ exc_details [1 ].__context__ = fixed_ctx
652+ raise
653+ return received_exc and suppressed_exc
654+
496655
497656class nullcontext (AbstractContextManager ):
498657"""Context manager that does no additional processing.