1- __all__ = 'run' ,
1+ __all__ = ( 'Runner' , 'run' )
22
3+ import contextvars
4+ import enum
35from . import coroutines
46from . import events
57from . import tasks
68
79
10+ class _State (enum .Enum ):
11+ CREATED = "created"
12+ INITIALIZED = "initialized"
13+ CLOSED = "closed"
14+
15+
16+ class Runner :
17+ """A context manager that controls event loop life cycle.
18+
19+ The context manager always creates a new event loop,
20+ allows to run async functions inside it,
21+ and properly finalizes the loop at the context manager exit.
22+
23+ If debug is True, the event loop will be run in debug mode.
24+ If factory is passed, it is used for new event loop creation.
25+
26+ asyncio.run(main(), debug=True)
27+
28+ is a shortcut for
29+
30+ with asyncio.Runner(debug=True) as runner:
31+ runner.run(main())
32+
33+ The run() method can be called multiple times within the runner's context.
34+
35+ This can be useful for interactive console (e.g. IPython),
36+ unittest runners, console tools, -- everywhere when async code
37+ is called from existing sync framework and where the preferred single
38+ asyncio.run() call doesn't work.
39+
40+ """
41+
42+ # Note: the class is final, it is not intended for inheritance.
43+
44+ def __init__ (self , * , debug = None , factory = None ):
45+ self ._state = _State .CREATED
46+ self ._debug = debug
47+ self ._factory = factory
48+ self ._loop = None
49+ self ._context = None
50+
51+ def __enter__ (self ):
52+ self ._lazy_init ()
53+ return self
54+
55+ def __exit__ (self , exc_type , exc_val , exc_tb ):
56+ self .close ()
57+
58+ def close (self ):
59+ """Shutdown and close event loop."""
60+ if self ._state is not _State .INITIALIZED :
61+ return
62+ try :
63+ loop = self ._loop
64+ _cancel_all_tasks (loop )
65+ loop .run_until_complete (loop .shutdown_asyncgens ())
66+ loop .run_until_complete (loop .shutdown_default_executor ())
67+ finally :
68+ loop .close ()
69+ self ._loop = None
70+ self ._state = _State .CLOSED
71+
72+ def get_loop (self ):
73+ """Return embedded event loop."""
74+ self ._lazy_init ()
75+ return self ._loop
76+
77+ def run (self , coro , * , context = None ):
78+ """Run a coroutine inside the embedded event loop."""
79+ if not coroutines .iscoroutine (coro ):
80+ raise ValueError ("a coroutine was expected, got {!r}" .format (coro ))
81+
82+ if events ._get_running_loop () is not None :
83+ # fail fast with short traceback
84+ raise RuntimeError (
85+ "Runner.run() cannot be called from a running event loop" )
86+
87+ self ._lazy_init ()
88+
89+ if context is None :
90+ context = self ._context
91+ task = self ._loop .create_task (coro , context = context )
92+ return self ._loop .run_until_complete (task )
93+
94+ def _lazy_init (self ):
95+ if self ._state is _State .CLOSED :
96+ raise RuntimeError ("Runner is closed" )
97+ if self ._state is _State .INITIALIZED :
98+ return
99+ if self ._factory is None :
100+ self ._loop = events .new_event_loop ()
101+ else :
102+ self ._loop = self ._factory ()
103+ if self ._debug is not None :
104+ self ._loop .set_debug (self ._debug )
105+ self ._context = contextvars .copy_context ()
106+ self ._state = _State .INITIALIZED
107+
108+
109+
8110def run (main , * , debug = None ):
9111 """Execute the coroutine and return the result.
10112
@@ -30,26 +132,12 @@ async def main():
30132 asyncio.run(main())
31133 """
32134 if events ._get_running_loop () is not None :
135+ # fail fast with short traceback
33136 raise RuntimeError (
34137 "asyncio.run() cannot be called from a running event loop" )
35138
36- if not coroutines .iscoroutine (main ):
37- raise ValueError ("a coroutine was expected, got {!r}" .format (main ))
38-
39- loop = events .new_event_loop ()
40- try :
41- events .set_event_loop (loop )
42- if debug is not None :
43- loop .set_debug (debug )
44- return loop .run_until_complete (main )
45- finally :
46- try :
47- _cancel_all_tasks (loop )
48- loop .run_until_complete (loop .shutdown_asyncgens ())
49- loop .run_until_complete (loop .shutdown_default_executor ())
50- finally :
51- events .set_event_loop (None )
52- loop .close ()
139+ with Runner (debug = debug ) as runner :
140+ return runner .run (main )
53141
54142
55143def _cancel_all_tasks (loop ):
0 commit comments