@@ -571,12 +571,23 @@ def _pre_run(self, pre_run: Optional[Callable[[], None]] = None) -> None:
571571 c ()
572572 del self .pre_run_callables [:]
573573
574- async def run_async (self , pre_run : Optional [Callable [[], None ]] = None ) -> _AppResult :
574+ async def run_async (self , pre_run : Optional [Callable [[], None ]] = None ,
575+ set_exception_handler : bool = True ) -> _AppResult :
575576 """
576577 Run the prompt_toolkit :class:`~prompt_toolkit.application.Application`
577578 until :meth:`~prompt_toolkit.application.Application.exit` has been
578579 called. Return the value that was passed to
579580 :meth:`~prompt_toolkit.application.Application.exit`.
581+
582+ This is the main entry point for a prompt_toolkit
583+ :class:`~prompt_toolkit.application.Application` and usually the only
584+ place where the event loop is actually running.
585+
586+ :param pre_run: Optional callable, which is called right after the
587+ "reset" of the application.
588+ :param set_exception_handler: When set, in case of an exception, go out
589+ of the alternate screen and hide the application, display the
590+ exception, and wait for the user to press ENTER.
580591 """
581592 assert not self ._is_running , 'Application is already running.'
582593
@@ -709,22 +720,32 @@ def flush_input() -> None:
709720
710721 async def _run_async2 () -> _AppResult :
711722 self ._is_running = True
712- with set_app (self ):
713- try :
714- result = await _run_async ()
715- finally :
716- # Wait for the background tasks to be done. This needs to
717- # go in the finally! If `_run_async` raises
718- # `KeyboardInterrupt`, we still want to wait for the
719- # background tasks.
720- await self .cancel_and_wait_for_background_tasks ()
721-
722- # Set the `_is_running` flag to `False`. Normally this
723- # happened already in the finally block in `run_async`
724- # above, but in case of exceptions, that's not always the
725- # case.
726- self ._is_running = False
727- return result
723+
724+ loop = get_event_loop ()
725+ if set_exception_handler :
726+ previous_exc_handler = loop .get_exception_handler ()
727+ loop .set_exception_handler (self ._handle_exception )
728+
729+ try :
730+ with set_app (self ):
731+ try :
732+ result = await _run_async ()
733+ finally :
734+ # Wait for the background tasks to be done. This needs to
735+ # go in the finally! If `_run_async` raises
736+ # `KeyboardInterrupt`, we still want to wait for the
737+ # background tasks.
738+ await self .cancel_and_wait_for_background_tasks ()
739+
740+ # Set the `_is_running` flag to `False`. Normally this
741+ # happened already in the finally block in `run_async`
742+ # above, but in case of exceptions, that's not always the
743+ # case.
744+ self ._is_running = False
745+ return result
746+ finally :
747+ if set_exception_handler :
748+ loop .set_exception_handler (previous_exc_handler )
728749
729750 return await _run_async2 ()
730751
@@ -733,46 +754,36 @@ def run(self, pre_run: Optional[Callable[[], None]] = None,
733754 """
734755 A blocking 'run' call that waits until the UI is finished.
735756
757+ :param pre_run: Optional callable, which is called right after the
758+ "reset" of the application.
736759 :param set_exception_handler: When set, in case of an exception, go out
737760 of the alternate screen and hide the application, display the
738761 exception, and wait for the user to press ENTER.
739762 """
740- loop = get_event_loop ()
741-
742- def run () -> _AppResult :
743- coro = self .run_async (pre_run = pre_run )
744- return get_event_loop ().run_until_complete (coro )
745-
746- def handle_exception (loop , context : Dict [str , Any ]) -> None :
747- " Print the exception, using run_in_terminal. "
748- # For Python 2: we have to get traceback at this point, because
749- # we're still in the 'except:' block of the event loop where the
750- # traceback is still available. Moving this code in the
751- # 'print_exception' coroutine will loose the exception.
752- tb = get_traceback_from_context (context )
753- formatted_tb = '' .join (format_tb (tb ))
754-
755- async def in_term () -> None :
756- async with in_terminal ():
757- # Print output. Similar to 'loop.default_exception_handler',
758- # but don't use logger. (This works better on Python 2.)
759- print ('\n Unhandled exception in event loop:' )
760- print (formatted_tb )
761- print ('Exception %s' % (context .get ('exception' ), ))
762-
763- await _do_wait_for_enter ('Press ENTER to continue...' )
764- ensure_future (in_term ())
765-
766- if set_exception_handler :
767- # Run with patched exception handler.
768- previous_exc_handler = loop .get_exception_handler ()
769- loop .set_exception_handler (handle_exception )
770- try :
771- return run ()
772- finally :
773- loop .set_exception_handler (previous_exc_handler )
774- else :
775- return run ()
763+ return get_event_loop ().run_until_complete (self .run_async (pre_run = pre_run ))
764+
765+ def _handle_exception (self , loop , context : Dict [str , Any ]) -> None :
766+ """
767+ Handler for event loop exceptions.
768+ This will print the exception, using run_in_terminal.
769+ """
770+ # For Python 2: we have to get traceback at this point, because
771+ # we're still in the 'except:' block of the event loop where the
772+ # traceback is still available. Moving this code in the
773+ # 'print_exception' coroutine will loose the exception.
774+ tb = get_traceback_from_context (context )
775+ formatted_tb = '' .join (format_tb (tb ))
776+
777+ async def in_term () -> None :
778+ async with in_terminal ():
779+ # Print output. Similar to 'loop.default_exception_handler',
780+ # but don't use logger. (This works better on Python 2.)
781+ print ('\n Unhandled exception in event loop:' )
782+ print (formatted_tb )
783+ print ('Exception %s' % (context .get ('exception' ), ))
784+
785+ await _do_wait_for_enter ('Press ENTER to continue...' )
786+ ensure_future (in_term ())
776787
777788 def create_background_task (self , coroutine : Awaitable [None ]) -> 'asyncio.Task[None]' :
778789 """
0 commit comments