bpo-47062: Implement asyncio.Runner context manager (GH-31799) · python/cpython@4119d2d (original) (raw)
1
``
`-
all = 'run',
`
``
1
`+
all = ('Runner', 'run')
`
2
2
``
``
3
`+
import contextvars
`
``
4
`+
import enum
`
3
5
`from . import coroutines
`
4
6
`from . import events
`
5
7
`from . import tasks
`
6
8
``
7
9
``
``
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
+
8
110
`def run(main, *, debug=None):
`
9
111
`"""Execute the coroutine and return the result.
`
10
112
``
`@@ -30,26 +132,12 @@ async def main():
`
30
132
` asyncio.run(main())
`
31
133
` """
`
32
134
`if events._get_running_loop() is not None:
`
``
135
`+
fail fast with short traceback
`
33
136
`raise RuntimeError(
`
34
137
`"asyncio.run() cannot be called from a running event loop")
`
35
138
``
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)
`
53
141
``
54
142
``
55
143
`def _cancel_all_tasks(loop):
`