| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  | __all__ = ('Runner', 'run') | 
					
						
							| 
									
										
										
										
											2017-12-14 09:42:21 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  | import contextvars | 
					
						
							|  |  |  | import enum | 
					
						
							| 
									
										
										
										
											2022-03-30 15:15:06 +03:00
										 |  |  | import functools | 
					
						
							|  |  |  | import threading | 
					
						
							|  |  |  | import signal | 
					
						
							| 
									
										
										
										
											2017-12-14 09:42:21 -05:00
										 |  |  | from . import coroutines | 
					
						
							|  |  |  | from . import events | 
					
						
							| 
									
										
										
										
											2022-03-30 15:15:06 +03:00
										 |  |  | from . import exceptions | 
					
						
							| 
									
										
										
										
											2018-01-21 14:56:59 -05:00
										 |  |  | from . import tasks | 
					
						
							| 
									
										
										
										
											2022-09-28 23:09:42 +05:30
										 |  |  | from . import constants | 
					
						
							| 
									
										
										
										
											2017-12-14 09:42:21 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  | class _State(enum.Enum): | 
					
						
							|  |  |  |     CREATED = "created" | 
					
						
							|  |  |  |     INITIALIZED = "initialized" | 
					
						
							|  |  |  |     CLOSED = "closed" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Runner: | 
					
						
							|  |  |  |     """A context manager that controls event loop life cycle.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     The context manager always creates a new event loop, | 
					
						
							|  |  |  |     allows to run async functions inside it, | 
					
						
							|  |  |  |     and properly finalizes the loop at the context manager exit. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     If debug is True, the event loop will be run in debug mode. | 
					
						
							| 
									
										
										
										
											2022-03-26 00:26:23 +02:00
										 |  |  |     If loop_factory is passed, it is used for new event loop creation. | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     asyncio.run(main(), debug=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     is a shortcut for | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     with asyncio.Runner(debug=True) as runner: | 
					
						
							|  |  |  |         runner.run(main()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     The run() method can be called multiple times within the runner's context. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     This can be useful for interactive console (e.g. IPython), | 
					
						
							|  |  |  |     unittest runners, console tools, -- everywhere when async code | 
					
						
							|  |  |  |     is called from existing sync framework and where the preferred single | 
					
						
							|  |  |  |     asyncio.run() call doesn't work. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Note: the class is final, it is not intended for inheritance. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-26 00:26:23 +02:00
										 |  |  |     def __init__(self, *, debug=None, loop_factory=None): | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  |         self._state = _State.CREATED | 
					
						
							|  |  |  |         self._debug = debug | 
					
						
							| 
									
										
										
										
											2022-03-26 00:26:23 +02:00
										 |  |  |         self._loop_factory = loop_factory | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  |         self._loop = None | 
					
						
							|  |  |  |         self._context = None | 
					
						
							| 
									
										
										
										
											2022-03-30 15:15:06 +03:00
										 |  |  |         self._interrupt_count = 0 | 
					
						
							| 
									
										
										
										
											2022-07-06 20:48:21 +05:30
										 |  |  |         self._set_event_loop = False | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __enter__(self): | 
					
						
							|  |  |  |         self._lazy_init() | 
					
						
							|  |  |  |         return self | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __exit__(self, exc_type, exc_val, exc_tb): | 
					
						
							|  |  |  |         self.close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def close(self): | 
					
						
							|  |  |  |         """Shutdown and close event loop.""" | 
					
						
							|  |  |  |         if self._state is not _State.INITIALIZED: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             loop = self._loop | 
					
						
							|  |  |  |             _cancel_all_tasks(loop) | 
					
						
							|  |  |  |             loop.run_until_complete(loop.shutdown_asyncgens()) | 
					
						
							| 
									
										
										
										
											2022-09-28 23:09:42 +05:30
										 |  |  |             loop.run_until_complete( | 
					
						
							|  |  |  |                 loop.shutdown_default_executor(constants.THREAD_JOIN_TIMEOUT)) | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  |         finally: | 
					
						
							| 
									
										
										
										
											2022-07-06 20:48:21 +05:30
										 |  |  |             if self._set_event_loop: | 
					
						
							|  |  |  |                 events.set_event_loop(None) | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  |             loop.close() | 
					
						
							|  |  |  |             self._loop = None | 
					
						
							|  |  |  |             self._state = _State.CLOSED | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_loop(self): | 
					
						
							|  |  |  |         """Return embedded event loop.""" | 
					
						
							|  |  |  |         self._lazy_init() | 
					
						
							|  |  |  |         return self._loop | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def run(self, coro, *, context=None): | 
					
						
							|  |  |  |         """Run a coroutine inside the embedded event loop.""" | 
					
						
							|  |  |  |         if not coroutines.iscoroutine(coro): | 
					
						
							|  |  |  |             raise ValueError("a coroutine was expected, got {!r}".format(coro)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if events._get_running_loop() is not None: | 
					
						
							|  |  |  |             # fail fast with short traceback | 
					
						
							|  |  |  |             raise RuntimeError( | 
					
						
							|  |  |  |                 "Runner.run() cannot be called from a running event loop") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self._lazy_init() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if context is None: | 
					
						
							|  |  |  |             context = self._context | 
					
						
							|  |  |  |         task = self._loop.create_task(coro, context=context) | 
					
						
							| 
									
										
										
										
											2022-03-30 15:15:06 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (threading.current_thread() is threading.main_thread() | 
					
						
							|  |  |  |             and signal.getsignal(signal.SIGINT) is signal.default_int_handler | 
					
						
							|  |  |  |         ): | 
					
						
							|  |  |  |             sigint_handler = functools.partial(self._on_sigint, main_task=task) | 
					
						
							| 
									
										
										
										
											2022-04-25 16:56:20 +01:00
										 |  |  |             try: | 
					
						
							|  |  |  |                 signal.signal(signal.SIGINT, sigint_handler) | 
					
						
							|  |  |  |             except ValueError: | 
					
						
							|  |  |  |                 # `signal.signal` may throw if `threading.main_thread` does | 
					
						
							|  |  |  |                 # not support signals (e.g. embedded interpreter with signals | 
					
						
							|  |  |  |                 # not registered - see gh-91880) | 
					
						
							| 
									
										
										
										
											2022-04-30 06:23:54 +01:00
										 |  |  |                 sigint_handler = None | 
					
						
							| 
									
										
										
										
											2022-03-30 15:15:06 +03:00
										 |  |  |         else: | 
					
						
							|  |  |  |             sigint_handler = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self._interrupt_count = 0 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return self._loop.run_until_complete(task) | 
					
						
							|  |  |  |         except exceptions.CancelledError: | 
					
						
							| 
									
										
										
										
											2022-07-28 21:17:54 +05:30
										 |  |  |             if self._interrupt_count > 0: | 
					
						
							|  |  |  |                 uncancel = getattr(task, "uncancel", None) | 
					
						
							|  |  |  |                 if uncancel is not None and uncancel() == 0: | 
					
						
							|  |  |  |                     raise KeyboardInterrupt() | 
					
						
							|  |  |  |             raise  # CancelledError | 
					
						
							| 
									
										
										
										
											2022-03-30 15:15:06 +03:00
										 |  |  |         finally: | 
					
						
							|  |  |  |             if (sigint_handler is not None | 
					
						
							|  |  |  |                 and signal.getsignal(signal.SIGINT) is sigint_handler | 
					
						
							|  |  |  |             ): | 
					
						
							|  |  |  |                 signal.signal(signal.SIGINT, signal.default_int_handler) | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def _lazy_init(self): | 
					
						
							|  |  |  |         if self._state is _State.CLOSED: | 
					
						
							|  |  |  |             raise RuntimeError("Runner is closed") | 
					
						
							|  |  |  |         if self._state is _State.INITIALIZED: | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2022-03-26 00:26:23 +02:00
										 |  |  |         if self._loop_factory is None: | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  |             self._loop = events.new_event_loop() | 
					
						
							| 
									
										
										
										
											2022-08-15 22:32:47 +05:30
										 |  |  |             if not self._set_event_loop: | 
					
						
							|  |  |  |                 # Call set_event_loop only once to avoid calling | 
					
						
							|  |  |  |                 # attach_loop multiple times on child watchers | 
					
						
							|  |  |  |                 events.set_event_loop(self._loop) | 
					
						
							|  |  |  |                 self._set_event_loop = True | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2022-03-26 00:26:23 +02:00
										 |  |  |             self._loop = self._loop_factory() | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  |         if self._debug is not None: | 
					
						
							|  |  |  |             self._loop.set_debug(self._debug) | 
					
						
							|  |  |  |         self._context = contextvars.copy_context() | 
					
						
							|  |  |  |         self._state = _State.INITIALIZED | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-30 15:15:06 +03:00
										 |  |  |     def _on_sigint(self, signum, frame, main_task): | 
					
						
							|  |  |  |         self._interrupt_count += 1 | 
					
						
							|  |  |  |         if self._interrupt_count == 1 and not main_task.done(): | 
					
						
							|  |  |  |             main_task.cancel() | 
					
						
							|  |  |  |             # wakeup loop if it is blocked by select() with long timeout | 
					
						
							|  |  |  |             self._loop.call_soon_threadsafe(lambda: None) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         raise KeyboardInterrupt() | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-14 23:48:51 +05:30
										 |  |  | def run(main, *, debug=None, loop_factory=None): | 
					
						
							| 
									
										
										
										
											2019-09-30 20:12:21 -04:00
										 |  |  |     """Execute the coroutine and return the result.
 | 
					
						
							| 
									
										
										
										
											2017-12-14 09:42:21 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     This function runs the passed coroutine, taking care of | 
					
						
							| 
									
										
										
										
											2022-09-28 23:09:42 +05:30
										 |  |  |     managing the asyncio event loop, finalizing asynchronous | 
					
						
							|  |  |  |     generators and closing the default executor. | 
					
						
							| 
									
										
										
										
											2017-12-14 09:42:21 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     This function cannot be called when another asyncio event loop is | 
					
						
							|  |  |  |     running in the same thread. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     If debug is True, the event loop will be run in debug mode. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     This function always creates a new event loop and closes it at the end. | 
					
						
							|  |  |  |     It should be used as a main entry point for asyncio programs, and should | 
					
						
							|  |  |  |     ideally only be called once. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 23:09:42 +05:30
										 |  |  |     The executor is given a timeout duration of 5 minutes to shutdown. | 
					
						
							|  |  |  |     If the executor hasn't finished within that duration, a warning is | 
					
						
							|  |  |  |     emitted and the executor is closed. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-14 09:42:21 -05:00
										 |  |  |     Example: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         async def main(): | 
					
						
							|  |  |  |             await asyncio.sleep(1) | 
					
						
							|  |  |  |             print('hello') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         asyncio.run(main()) | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if events._get_running_loop() is not None: | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  |         # fail fast with short traceback | 
					
						
							| 
									
										
										
										
											2017-12-14 09:42:21 -05:00
										 |  |  |         raise RuntimeError( | 
					
						
							|  |  |  |             "asyncio.run() cannot be called from a running event loop") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-14 23:48:51 +05:30
										 |  |  |     with Runner(debug=debug, loop_factory=loop_factory) as runner: | 
					
						
							| 
									
										
										
										
											2022-03-24 21:51:16 +02:00
										 |  |  |         return runner.run(main) | 
					
						
							| 
									
										
										
										
											2018-01-21 14:56:59 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _cancel_all_tasks(loop): | 
					
						
							| 
									
										
										
										
											2018-05-28 17:54:02 -04:00
										 |  |  |     to_cancel = tasks.all_tasks(loop) | 
					
						
							| 
									
										
										
										
											2018-01-21 14:56:59 -05:00
										 |  |  |     if not to_cancel: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for task in to_cancel: | 
					
						
							|  |  |  |         task.cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-28 10:21:17 +02:00
										 |  |  |     loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True)) | 
					
						
							| 
									
										
										
										
											2018-01-21 14:56:59 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for task in to_cancel: | 
					
						
							|  |  |  |         if task.cancelled(): | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         if task.exception() is not None: | 
					
						
							|  |  |  |             loop.call_exception_handler({ | 
					
						
							|  |  |  |                 'message': 'unhandled exception during asyncio.run() shutdown', | 
					
						
							|  |  |  |                 'exception': task.exception(), | 
					
						
							|  |  |  |                 'task': task, | 
					
						
							|  |  |  |             }) |