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

Commit

Permalink
Add asyncio.run_forever(); add tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
1st1 committed Nov 16, 2016
1 parent b8b0fa0 commit 3c90364
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 99 deletions.
6 changes: 3 additions & 3 deletions asyncio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from .futures import *
from .locks import *
from .protocols import *
from .run import *
from .runners import *
from .queues import *
from .streams import *
from .subprocess import *
Expand All @@ -37,12 +37,12 @@
futures.__all__ +
locks.__all__ +
protocols.__all__ +
runners.__all__ +
queues.__all__ +
streams.__all__ +
subprocess.__all__ +
tasks.__all__ +
transports.__all__ +
['run']) # Will fix this later.
transports.__all__)

if sys.platform == 'win32': # pragma: no cover
from .windows_events import *
Expand Down
96 changes: 0 additions & 96 deletions asyncio/run.py

This file was deleted.

146 changes: 146 additions & 0 deletions asyncio/runners.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"""asyncio.run() and asyncio.run_forever() functions."""

__all__ = ['run', 'run_forever']

import inspect
import threading

from . import coroutines
from . import events


def _cleanup(loop):
try:
# `shutdown_asyncgens` was added in Python 3.6; not all
# event loops might support it.
shutdown_asyncgens = loop.shutdown_asyncgens
except AttributeError:
pass
else:
loop.run_until_complete(shutdown_asyncgens())
finally:
events.set_event_loop(None)
loop.close()


def run(main, *, debug=False):
"""Run a coroutine.
This function runs the passed coroutine, taking care of
managing the asyncio event loop and finalizing asynchronous
generators.
This function must be called from the main thread, and it
cannot be called when another asyncio event loop is running.
If debug is True, the event loop will be run in debug mode.
This function should be used as a main entry point for
asyncio programs, and should not be used to call asynchronous
APIs.
Example::
async def main():
await asyncio.sleep(1)
print('hello')
asyncio.run(main())
"""
if events._get_running_loop() is not None:
raise RuntimeError(
"asyncio.run() cannot be called from a running event loop")
if not isinstance(threading.current_thread(), threading._MainThread):
raise RuntimeError(
"asyncio.run() must be called from the main thread")
if not coroutines.iscoroutine(main):
raise ValueError("a coroutine was expected, got {!r}".format(main))

loop = events.new_event_loop()
try:
events.set_event_loop(loop)

if debug:
loop.set_debug(True)

return loop.run_until_complete(main)
finally:
_cleanup(loop)


def run_forever(main, *, debug=False):
"""Run asyncio loop.
main must be an asynchronous generator with one yield, separating
program initialization from cleanup logic.
If debug is True, the event loop will be run in debug mode.
This function should be used as a main entry point for
asyncio programs, and should not be used to call asynchronous
APIs.
Example:
async def main():
server = await asyncio.start_server(...)
try:
yield # <- Let event loop run forever.
except KeyboardInterrupt:
print('^C received; exiting.')
finally:
server.close()
await server.wait_closed()
asyncio.run_forever(main())
"""
if not hasattr(inspect, 'isasyncgen'):
raise NotImplementedError

if events._get_running_loop() is not None:
raise RuntimeError(
"asyncio.run_forever() cannot be called from a running event loop")
if not isinstance(threading.current_thread(), threading._MainThread):
raise RuntimeError(
"asyncio.run() must be called from the main thread")
if not inspect.isasyncgen(main):
raise ValueError(
"an asynchronous generator was expected, got {!r}".format(main))

loop = events.new_event_loop()
try:
events.set_event_loop(loop)
if debug:
loop.set_debug(True)

ret = None
try:
ret = loop.run_until_complete(main.asend(None))
except StopAsyncIteration as ex:
return
if ret is not None:
raise RuntimeError("only empty yield is supported")

yielded_twice = False
try:
loop.run_forever()
except BaseException as ex:
try:
loop.run_until_complete(main.athrow(ex))
except StopAsyncIteration as ex:
pass
else:
yielded_twice = True
else:
try:
loop.run_until_complete(main.asend(None))
except StopAsyncIteration as ex:
pass
else:
yielded_twice = True

if yielded_twice:
raise RuntimeError("only one yield is supported")

finally:
_cleanup(loop)
4 changes: 4 additions & 0 deletions runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ def list_dir(prefix, dir):
print("Skipping '{0}': need at least Python 3.5".format(modname),
file=sys.stderr)
continue
if modname == 'test_runner' and (sys.version_info < (3, 6)):
print("Skipping '{0}': need at least Python 3.6".format(modname),
file=sys.stderr)
continue
try:
loader = importlib.machinery.SourceFileLoader(modname, sourcefile)
mods.append((loader.load_module(), sourcefile))
Expand Down
Loading

0 comments on commit 3c90364

Please sign in to comment.