Skip to content

Commit b5505b4

Browse files
committed
Add asyncio.run from CPython 3.7
1 parent 6679ea1 commit b5505b4

5 files changed

Lines changed: 165 additions & 0 deletions

File tree

Lib/asyncio/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""The asyncio package, tracking PEP 3156."""
22

3+
# flake8: noqa
34
import sys
45

56
import selectors
@@ -11,13 +12,15 @@
1112
except ImportError:
1213
import _overlapped # Will also be exported.
1314

15+
1416
# This relies on each of the submodules having an __all__ variable.
1517
from .base_events import *
1618
from .coroutines import *
1719
from .events import *
1820
from .futures import *
1921
from .locks import *
2022
from .protocols import *
23+
from .runners import *
2124
from .queues import *
2225
from .streams import *
2326
from .subprocess import *
@@ -30,6 +33,7 @@
3033
futures.__all__ +
3134
locks.__all__ +
3235
protocols.__all__ +
36+
runners.__all__ +
3337
queues.__all__ +
3438
streams.__all__ +
3539
subprocess.__all__ +

Lib/asyncio/runners.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
__all__ = ['run']
2+
3+
from . import coroutines
4+
from . import events
5+
from . import tasks
6+
7+
8+
def run(main, *, debug=False):
9+
"""Run a coroutine.
10+
11+
This function runs the passed coroutine, taking care of
12+
managing the asyncio event loop and finalizing asynchronous
13+
generators.
14+
15+
This function cannot be called when another asyncio event loop is
16+
running in the same thread.
17+
18+
If debug is True, the event loop will be run in debug mode.
19+
20+
This function always creates a new event loop and closes it at the end.
21+
It should be used as a main entry point for asyncio programs, and should
22+
ideally only be called once.
23+
24+
Example:
25+
26+
async def main():
27+
await asyncio.sleep(1)
28+
print('hello')
29+
30+
asyncio.run(main())
31+
"""
32+
if events._get_running_loop() is not None:
33+
raise RuntimeError(
34+
"asyncio.run() cannot be called from a running event loop")
35+
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+
loop.set_debug(debug)
43+
return loop.run_until_complete(main)
44+
finally:
45+
try:
46+
_cancel_all_tasks(loop)
47+
loop.run_until_complete(loop.shutdown_asyncgens())
48+
finally:
49+
events.set_event_loop(None)
50+
loop.close()
51+
52+
53+
def _cancel_all_tasks(loop):
54+
to_cancel = tasks.all_tasks(loop)
55+
if not to_cancel:
56+
return
57+
58+
for task in to_cancel:
59+
task.cancel()
60+
61+
loop.run_until_complete(
62+
tasks.gather(*to_cancel, loop=loop, return_exceptions=True))
63+
64+
for task in to_cancel:
65+
if task.cancelled():
66+
continue
67+
if task.exception() is not None:
68+
loop.call_exception_handler({
69+
'message': 'unhandled exception during asyncio.run() shutdown',
70+
'exception': task.exception(),
71+
'task': task,
72+
})

Lib/asyncio/tasks.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,30 @@
2020
from .coroutines import coroutine
2121

2222

23+
def current_task(loop=None):
24+
"""Return a currently executed task."""
25+
if loop is None:
26+
loop = events.get_running_loop()
27+
return _current_tasks.get(loop)
28+
29+
30+
def all_tasks(loop=None):
31+
"""Return a set of all tasks for the loop."""
32+
if loop is None:
33+
loop = events.get_running_loop()
34+
return {t for t in _all_tasks
35+
if futures._get_loop(t) is loop and not t.done()}
36+
37+
38+
def _all_tasks_compat(loop=None):
39+
# Different from "all_task()" by returning *all* Tasks, including
40+
# the completed ones. Used to implement deprecated "Tasks.all_task()"
41+
# method.
42+
if loop is None:
43+
loop = events.get_event_loop()
44+
return {t for t in _all_tasks if futures._get_loop(t) is loop}
45+
46+
2347
class Task(futures.Future):
2448
"""A coroutine wrapped in a Future."""
2549

@@ -707,3 +731,56 @@ def callback():
707731

708732
loop.call_soon_threadsafe(callback)
709733
return future
734+
735+
736+
# WeakSet containing all alive tasks.
737+
_all_tasks = weakref.WeakSet()
738+
739+
# Dictionary containing tasks that are currently active in
740+
# all running event loops. {EventLoop: Task}
741+
_current_tasks = {}
742+
743+
744+
def _register_task(task):
745+
"""Register a new task in asyncio as executed by loop."""
746+
_all_tasks.add(task)
747+
748+
749+
def _enter_task(loop, task):
750+
current_task = _current_tasks.get(loop)
751+
if current_task is not None:
752+
raise RuntimeError(f"Cannot enter into task {task!r} while another "
753+
f"task {current_task!r} is being executed.")
754+
_current_tasks[loop] = task
755+
756+
757+
def _leave_task(loop, task):
758+
current_task = _current_tasks.get(loop)
759+
if current_task is not task:
760+
raise RuntimeError(f"Leaving task {task!r} does not match "
761+
f"the current task {current_task!r}.")
762+
del _current_tasks[loop]
763+
764+
765+
def _unregister_task(task):
766+
"""Unregister a task."""
767+
_all_tasks.discard(task)
768+
769+
770+
_py_register_task = _register_task
771+
_py_unregister_task = _unregister_task
772+
_py_enter_task = _enter_task
773+
_py_leave_task = _leave_task
774+
775+
776+
try:
777+
from _asyncio import (_register_task, _unregister_task,
778+
_enter_task, _leave_task,
779+
_all_tasks, _current_tasks)
780+
except ImportError:
781+
pass
782+
else:
783+
_c_register_task = _register_task
784+
_c_unregister_task = _unregister_task
785+
_c_enter_task = _enter_task
786+
_c_leave_task = _leave_task

vm/src/exceptions.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::function::PyFuncArgs;
2+
use crate::obj::objiter;
23
use crate::obj::objtraceback::PyTracebackRef;
34
use crate::obj::objtuple::{PyTuple, PyTupleRef};
45
use crate::obj::objtype;
@@ -551,4 +552,10 @@ pub fn init(context: &PyContext) {
551552
extend_class!(context, import_error_type, {
552553
"__init__" => context.new_rustfunc(import_error_init)
553554
});
555+
556+
extend_class!(context, &context.exceptions.stop_iteration, {
557+
"value" => context.new_rustfunc(|obj: PyObjectRef, vm: &VirtualMachine| {
558+
objiter::stop_iter_value(vm, &obj)
559+
}),
560+
});
554561
}

vm/src/stdlib/io.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ impl PyStringIORef {
166166
None => Err(vm.new_value_error("Error Performing Operation".to_string())),
167167
}
168168
}
169+
170+
fn close(self, _vm: &VirtualMachine) {
171+
// TODO: discard the text buffer on close
172+
}
169173
}
170174

171175
fn string_io_new(
@@ -759,6 +763,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
759763
"getvalue" => ctx.new_rustfunc(PyStringIORef::getvalue),
760764
"tell" => ctx.new_rustfunc(PyStringIORef::tell),
761765
"readline" => ctx.new_rustfunc(PyStringIORef::readline),
766+
"close" => ctx.new_rustfunc(PyStringIORef::close),
762767
});
763768

764769
//BytesIO: in-memory bytes

0 commit comments

Comments
 (0)