Skip to content

Commit e1c1e69

Browse files
committed
Implement uvloop.run()
1 parent 178df25 commit e1c1e69

File tree

6 files changed

+174
-5
lines changed

6 files changed

+174
-5
lines changed

Diff for: README.rst

+21
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,27 @@ uvloop with::
5353
Using uvloop
5454
------------
5555

56+
As of uvloop 0.18, the preferred way of using it is via the
57+
``uvloop.run()`` helper function:
58+
59+
60+
.. code:: python
61+
62+
import uvloop
63+
64+
async def main():
65+
# Main entry-point.
66+
...
67+
68+
uvloop.run(main())
69+
70+
``uvloop.run()`` works by simply configuring ``asyncio.run()``
71+
to use uvloop, passing all of the arguments to it, such as ``debug``,
72+
e.g. ``uvloop.run(main(), debug=True)``.
73+
74+
With Python 3.11 and earlier the following alternative
75+
snippet can be used:
76+
5677
.. code:: python
5778
5879
import asyncio

Diff for: tests/test_dealloc.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,12 @@ def test_dealloc_1(self):
3030
async def test():
3131
prog = '''\
3232
import uvloop
33-
import asyncio
3433
3534
async def foo():
3635
return 42
3736
3837
def main():
39-
uvloop.install()
40-
loop = asyncio.get_event_loop()
38+
loop = uvloop.new_event_loop()
4139
loop.set_debug(True)
4240
loop.run_until_complete(foo())
4341
# Do not close the loop on purpose: let __dealloc__ methods run.

Diff for: tests/test_process.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -851,8 +851,7 @@ async def test():
851851
)
852852
await proc.communicate()
853853
854-
uvloop.install()
855-
asyncio.run(test())
854+
uvloop.run(test())
856855
857856
stdin, stdout, stderr = dups
858857
(r, w), = pipes

Diff for: tests/test_runner.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import asyncio
2+
import unittest
3+
import uvloop
4+
5+
6+
class TestSourceCode(unittest.TestCase):
7+
8+
def test_uvloop_run_1(self):
9+
CNT = 0
10+
11+
async def main():
12+
nonlocal CNT
13+
CNT += 1
14+
15+
loop = asyncio.get_running_loop()
16+
17+
self.assertTrue(isinstance(loop, uvloop.Loop))
18+
self.assertTrue(loop.get_debug())
19+
20+
return 'done'
21+
22+
result = uvloop.run(main(), debug=True)
23+
24+
self.assertEqual(result, 'done')
25+
self.assertEqual(CNT, 1)
26+
27+
def test_uvloop_run_2(self):
28+
29+
async def main():
30+
pass
31+
32+
coro = main()
33+
with self.assertRaisesRegex(TypeError, ' a non-uvloop event loop'):
34+
uvloop.run(
35+
coro,
36+
loop_factory=asyncio.DefaultEventLoopPolicy().new_event_loop,
37+
)
38+
39+
coro.close()

Diff for: uvloop/__init__.py

+95
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import asyncio as __asyncio
22
import typing as _typing
3+
import sys as _sys
4+
import warnings as _warnings
35

46
from asyncio.events import BaseDefaultEventLoopPolicy as __BasePolicy
57

@@ -22,9 +24,102 @@ def new_event_loop() -> Loop:
2224

2325
def install() -> None:
2426
"""A helper function to install uvloop policy."""
27+
if _sys.version_info[:2] >= (3, 12):
28+
_warnings.warn(
29+
'uvloop.install() is deprecated in favor of uvloop.run() '
30+
'starting with Python 3.12.',
31+
DeprecationWarning,
32+
stacklevel=1,
33+
)
2534
__asyncio.set_event_loop_policy(EventLoopPolicy())
2635

2736

37+
def run(main, *, loop_factory=new_event_loop, debug=None, **run_kwargs):
38+
"""The preferred way of running a coroutine with uvloop."""
39+
40+
async def wrapper():
41+
# If `loop_factory` is provided we want it to return
42+
# either uvloop.Loop or a subtype of it, assuming the user
43+
# is using `uvloop.run()` intentionally.
44+
loop = __asyncio._get_running_loop()
45+
if not isinstance(loop, Loop):
46+
raise TypeError('uvloop.run() uses a non-uvloop event loop')
47+
return await main
48+
49+
vi = _sys.version_info[:2]
50+
51+
if vi <= (3, 10):
52+
# Copied from python/cpython
53+
54+
if __asyncio._get_running_loop() is not None:
55+
raise RuntimeError(
56+
"asyncio.run() cannot be called from a running event loop")
57+
58+
if not __asyncio.iscoroutine(main):
59+
raise ValueError("a coroutine was expected, got {!r}".format(main))
60+
61+
loop = loop_factory()
62+
try:
63+
__asyncio.set_event_loop(loop)
64+
if debug is not None:
65+
loop.set_debug(debug)
66+
return loop.run_until_complete(wrapper())
67+
finally:
68+
try:
69+
_cancel_all_tasks(loop)
70+
loop.run_until_complete(loop.shutdown_asyncgens())
71+
loop.run_until_complete(loop.shutdown_default_executor())
72+
finally:
73+
__asyncio.set_event_loop(None)
74+
loop.close()
75+
76+
elif vi == (3, 11):
77+
if __asyncio._get_running_loop() is not None:
78+
raise RuntimeError(
79+
"asyncio.run() cannot be called from a running event loop")
80+
81+
with __asyncio.Runner(
82+
loop_factory=loop_factory,
83+
debug=debug,
84+
**run_kwargs
85+
) as runner:
86+
return runner.run(wrapper())
87+
88+
else:
89+
assert vi >= (3, 12)
90+
return __asyncio.run(
91+
wrapper(),
92+
loop_factory=loop_factory,
93+
debug=debug,
94+
**run_kwargs
95+
)
96+
97+
98+
def _cancel_all_tasks(loop):
99+
# Copied from python/cpython
100+
101+
to_cancel = __asyncio.all_tasks(loop)
102+
if not to_cancel:
103+
return
104+
105+
for task in to_cancel:
106+
task.cancel()
107+
108+
loop.run_until_complete(
109+
__asyncio.gather(*to_cancel, return_exceptions=True)
110+
)
111+
112+
for task in to_cancel:
113+
if task.cancelled():
114+
continue
115+
if task.exception() is not None:
116+
loop.call_exception_handler({
117+
'message': 'unhandled exception during asyncio.run() shutdown',
118+
'exception': task.exception(),
119+
'task': task,
120+
})
121+
122+
28123
class EventLoopPolicy(__BasePolicy):
29124
"""Event loop policy.
30125

Diff for: uvloop/__init__.pyi

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import sys
2+
3+
from asyncio import AbstractEventLoop
4+
from collections.abc import Callable, Coroutine
5+
from contextvars import Context
6+
from typing import Any, TypeVar
7+
8+
9+
_T = TypeVar('_T')
10+
11+
12+
def run(
13+
main: Coroutine[Any, Any, _T],
14+
*,
15+
debug: bool | None = ...,
16+
loop_factory: Callable[[], AbstractEventLoop] | None = ...,
17+
) -> _T: ...

0 commit comments

Comments
 (0)