Skip to content
This repository was archived by the owner on Nov 23, 2017. It is now read-only.

Commit db57e91

Browse files
committed
Add asyncio.run_forever(); add tests.
1 parent b8b0fa0 commit db57e91

File tree

5 files changed

+358
-99
lines changed

5 files changed

+358
-99
lines changed

Diff for: asyncio/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from .futures import *
2525
from .locks import *
2626
from .protocols import *
27-
from .run import *
27+
from .runners import *
2828
from .queues import *
2929
from .streams import *
3030
from .subprocess import *
@@ -37,12 +37,12 @@
3737
futures.__all__ +
3838
locks.__all__ +
3939
protocols.__all__ +
40+
runners.__all__ +
4041
queues.__all__ +
4142
streams.__all__ +
4243
subprocess.__all__ +
4344
tasks.__all__ +
44-
transports.__all__ +
45-
['run']) # Will fix this later.
45+
transports.__all__)
4646

4747
if sys.platform == 'win32': # pragma: no cover
4848
from .windows_events import *

Diff for: asyncio/run.py

-96
This file was deleted.

Diff for: asyncio/runners.py

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""asyncio.run() and asyncio.run_forever() functions."""
2+
3+
__all__ = ['run', 'run_forever']
4+
5+
import inspect
6+
import threading
7+
8+
from . import coroutines
9+
from . import events
10+
11+
12+
def _cleanup(loop):
13+
try:
14+
# `shutdown_asyncgens` was added in Python 3.6; not all
15+
# event loops might support it.
16+
shutdown_asyncgens = loop.shutdown_asyncgens
17+
except AttributeError:
18+
pass
19+
else:
20+
loop.run_until_complete(shutdown_asyncgens())
21+
finally:
22+
events.set_event_loop(None)
23+
loop.close()
24+
25+
26+
def run(main, *, debug=False):
27+
"""Run a coroutine.
28+
29+
This function runs the passed coroutine, taking care of
30+
managing the asyncio event loop and finalizing asynchronous
31+
generators.
32+
33+
This function must be called from the main thread, and it
34+
cannot be called when another asyncio event loop is running.
35+
36+
If debug is True, the event loop will be run in debug mode.
37+
38+
This function should be used as a main entry point for
39+
asyncio programs, and should not be used to call asynchronous
40+
APIs.
41+
42+
Example::
43+
44+
async def main():
45+
await asyncio.sleep(1)
46+
print('hello')
47+
48+
asyncio.run(main())
49+
"""
50+
if events._get_running_loop() is not None:
51+
raise RuntimeError(
52+
"asyncio.run() cannot be called from a running event loop")
53+
if not isinstance(threading.current_thread(), threading._MainThread):
54+
raise RuntimeError(
55+
"asyncio.run() must be called from the main thread")
56+
if not coroutines.iscoroutine(main):
57+
raise ValueError("a coroutine was expected, got {!r}".format(main))
58+
59+
loop = events.new_event_loop()
60+
try:
61+
events.set_event_loop(loop)
62+
63+
if debug:
64+
loop.set_debug(True)
65+
66+
return loop.run_until_complete(main)
67+
finally:
68+
_cleanup(loop)
69+
70+
71+
def run_forever(main, *, debug=False):
72+
"""Run asyncio loop.
73+
74+
main must be an asynchronous generator with one yield, separating
75+
program initialization from cleanup logic.
76+
77+
If debug is True, the event loop will be run in debug mode.
78+
79+
This function should be used as a main entry point for
80+
asyncio programs, and should not be used to call asynchronous
81+
APIs.
82+
83+
Example:
84+
85+
async def main():
86+
server = await asyncio.start_server(...)
87+
try:
88+
yield # <- Let event loop run forever.
89+
except KeyboardInterrupt:
90+
print('^C received; exiting.')
91+
finally:
92+
server.close()
93+
await server.wait_closed()
94+
95+
asyncio.run_forever(main())
96+
"""
97+
if not hasattr(inspect, 'isasyncgen'):
98+
raise NotImplementedError
99+
100+
if events._get_running_loop() is not None:
101+
raise RuntimeError(
102+
"asyncio.run_forever() cannot be called from a running event loop")
103+
if not isinstance(threading.current_thread(), threading._MainThread):
104+
raise RuntimeError(
105+
"asyncio.run() must be called from the main thread")
106+
if not inspect.isasyncgen(main):
107+
raise ValueError(
108+
"an asynchronous generator was expected, got {!r}".format(main))
109+
110+
loop = events.new_event_loop()
111+
try:
112+
events.set_event_loop(loop)
113+
if debug:
114+
loop.set_debug(True)
115+
116+
ret = None
117+
try:
118+
ret = loop.run_until_complete(main.asend(None))
119+
except StopAsyncIteration as ex:
120+
return
121+
if ret is not None:
122+
raise RuntimeError("only empty yield is supported")
123+
124+
yielded_twice = False
125+
try:
126+
loop.run_forever()
127+
except BaseException as ex:
128+
try:
129+
loop.run_until_complete(main.athrow(ex))
130+
except StopAsyncIteration as ex:
131+
pass
132+
else:
133+
yielded_twice = True
134+
else:
135+
try:
136+
loop.run_until_complete(main.asend(None))
137+
except StopAsyncIteration as ex:
138+
pass
139+
else:
140+
yielded_twice = True
141+
142+
if yielded_twice:
143+
raise RuntimeError("only one yield is supported")
144+
145+
finally:
146+
_cleanup(loop)

Diff for: runtests.py

+4
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ def list_dir(prefix, dir):
112112
print("Skipping '{0}': need at least Python 3.5".format(modname),
113113
file=sys.stderr)
114114
continue
115+
if modname == 'test_run' and (sys.version_info < (3, 6)):
116+
print("Skipping '{0}': need at least Python 3.6".format(modname),
117+
file=sys.stderr)
118+
continue
115119
try:
116120
loader = importlib.machinery.SourceFileLoader(modname, sourcefile)
117121
mods.append((loader.load_module(), sourcefile))

0 commit comments

Comments
 (0)