Async context manager for dynamic management of a stack of exit callbacks. For example: async with AsyncExitStack() as stack: connections = [await stack.enter_async_context(get_connection()) for i in range(5)] # All opened connections will aut
| 626 | |
| 627 | # Inspired by discussions on https://bugs.python.org/issue29302 |
| 628 | class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager): |
| 629 | """Async context manager for dynamic management of a stack of exit |
| 630 | callbacks. |
| 631 | |
| 632 | For example: |
| 633 | async with AsyncExitStack() as stack: |
| 634 | connections = [await stack.enter_async_context(get_connection()) |
| 635 | for i in range(5)] |
| 636 | # All opened connections will automatically be released at the |
| 637 | # end of the async with statement, even if attempts to open a |
| 638 | # connection later in the list raise an exception. |
| 639 | """ |
| 640 | |
| 641 | @staticmethod |
| 642 | def _create_async_cb_wrapper(callback, /, *args, **kwds): |
| 643 | async def _exit_wrapper(exc_type, exc, tb): |
| 644 | await callback(*args, **kwds) |
| 645 | return _exit_wrapper |
| 646 | |
| 647 | async def enter_async_context(self, cm): |
| 648 | """Enters the supplied async context manager. |
| 649 | |
| 650 | If successful, also pushes its __aexit__ method as a callback and |
| 651 | returns the result of the __aenter__ method. |
| 652 | """ |
| 653 | _enter = _lookup_special(cm, '__aenter__', _sentinel) |
| 654 | if _enter is _sentinel: |
| 655 | cls = type(cm) |
| 656 | raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does " |
| 657 | f"not support the asynchronous context manager protocol") |
| 658 | _exit = _lookup_special(cm, '__aexit__', _sentinel) |
| 659 | if _exit is _sentinel: |
| 660 | cls = type(cm) |
| 661 | raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does " |
| 662 | f"not support the asynchronous context manager protocol") |
| 663 | result = await _enter() |
| 664 | self._push_exit_callback(_exit, False) |
| 665 | return result |
| 666 | |
| 667 | def push_async_exit(self, exit): |
| 668 | """Registers a coroutine function with the standard __aexit__ method |
| 669 | signature. |
| 670 | |
| 671 | Can suppress exceptions the same way __aexit__ method can. |
| 672 | Also accepts any object with an __aexit__ method (registering a call |
| 673 | to the method instead of the object itself). |
| 674 | """ |
| 675 | exit_method = _lookup_special(exit, '__aexit__', exit) |
| 676 | self._push_exit_callback(exit_method, False) |
| 677 | return exit # Allow use as a decorator |
| 678 | |
| 679 | def push_async_callback(self, callback, /, *args, **kwds): |
| 680 | """Registers an arbitrary coroutine function and arguments. |
| 681 | |
| 682 | Cannot suppress exceptions. |
| 683 | """ |
| 684 | _exit_wrapper = self._create_async_cb_wrapper(callback, *args, **kwds) |
| 685 |
no outgoing calls
searching dependent graphs…