Skip to content

Commit 8738ad4

Browse files
committed
remove calls to asyncio.set_event_loop
fixes prompt-toolkit#1955
1 parent cd7c6a2 commit 8738ad4

File tree

2 files changed

+97
-10
lines changed

2 files changed

+97
-10
lines changed
+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from __future__ import annotations
2+
3+
import asyncio
4+
import sys
5+
from collections.abc import Callable, Coroutine
6+
from typing import Any, TypeVar
7+
8+
__all__ = ["EventLoop", "asyncio_run"]
9+
10+
_T = TypeVar("_T")
11+
12+
if sys.version_info >= (3, 13):
13+
from asyncio import EventLoop
14+
elif sys.platform == "win32":
15+
from asyncio import ProactorEventLoop as EventLoop
16+
else:
17+
from asyncio import SelectorEventLoop as EventLoop
18+
19+
if sys.version_info >= (3, 12):
20+
asyncio_run = asyncio.run
21+
elif sys.version_info >= (3, 11):
22+
23+
def asyncio_run(
24+
main: Coroutine[Any, Any, _T],
25+
*,
26+
debug: bool = False,
27+
loop_factory: Callable[[], asyncio.AbstractEventLoop] | None = None,
28+
) -> _T:
29+
# asyncio.run from Python 3.12
30+
# https://docs.python.org/3/license.html#psf-license
31+
with asyncio.Runner(debug=debug, loop_factory=loop_factory) as runner:
32+
return runner.run(main)
33+
34+
else:
35+
# modified version of asyncio.run from Python 3.10 to add loop_factory kwarg
36+
# https://docs.python.org/3/license.html#psf-license
37+
def asyncio_run(
38+
main: Coroutine[Any, Any, _T],
39+
*,
40+
debug: bool = False,
41+
loop_factory: Callable[[], asyncio.AbstractEventLoop] | None = None,
42+
) -> _T:
43+
try:
44+
asyncio.get_running_loop()
45+
except RuntimeError:
46+
pass
47+
else:
48+
raise RuntimeError("asyncio.run() cannot be called from a running event loop")
49+
50+
if not asyncio.iscoroutine(main):
51+
raise ValueError(f"a coroutine was expected, got {main!r}")
52+
53+
if loop_factory is None:
54+
loop = asyncio.new_event_loop()
55+
else:
56+
loop = loop_factory()
57+
try:
58+
if loop_factory is None:
59+
asyncio.set_event_loop(loop)
60+
if debug is not None:
61+
loop.set_debug(debug)
62+
return loop.run_until_complete(main)
63+
finally:
64+
try:
65+
_cancel_all_tasks(loop)
66+
loop.run_until_complete(loop.shutdown_asyncgens())
67+
loop.run_until_complete(loop.shutdown_default_executor())
68+
finally:
69+
if loop_factory is None:
70+
asyncio.set_event_loop(None)
71+
loop.close()
72+
73+
def _cancel_all_tasks(loop: asyncio.AbstractEventLoop) -> None:
74+
to_cancel = asyncio.all_tasks(loop)
75+
if not to_cancel:
76+
return
77+
78+
for task in to_cancel:
79+
task.cancel()
80+
81+
loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True))
82+
83+
for task in to_cancel:
84+
if task.cancelled():
85+
continue
86+
if task.exception() is not None:
87+
loop.call_exception_handler(
88+
{
89+
"message": "unhandled exception during asyncio.run() shutdown",
90+
"exception": task.exception(),
91+
"task": task,
92+
}
93+
)

src/prompt_toolkit/application/application.py

+4-10
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
)
8787
from prompt_toolkit.utils import Event, in_main_thread
8888

89+
from ._compat import asyncio_run, EventLoop
8990
from .current import get_app_session, set_app
9091
from .run_in_terminal import in_terminal, run_in_terminal
9192

@@ -971,14 +972,7 @@ def _called_from_ipython() -> bool:
971972
return False
972973

973974
if inputhook is not None:
974-
# Create new event loop with given input hook and run the app.
975-
# In Python 3.12, we can use asyncio.run(loop_factory=...)
976-
# For now, use `run_until_complete()`.
977-
loop = new_eventloop_with_inputhook(inputhook)
978-
result = loop.run_until_complete(coro)
979-
loop.run_until_complete(loop.shutdown_asyncgens())
980-
loop.close()
981-
return result
975+
return asyncio_run(coro, loop_factory=lambda: new_eventloop_with_inputhook(inputhook))
982976

983977
elif _called_from_ipython():
984978
# workaround to make input hooks work for IPython until
@@ -992,14 +986,14 @@ def _called_from_ipython() -> bool:
992986
loop = asyncio.get_event_loop()
993987
except RuntimeError:
994988
# No loop installed. Run like usual.
995-
return asyncio.run(coro)
989+
return asyncio_run(coro, loop_factory=EventLoop)
996990
else:
997991
# Use existing loop.
998992
return loop.run_until_complete(coro)
999993

1000994
else:
1001995
# No loop installed. Run like usual.
1002-
return asyncio.run(coro)
996+
return asyncio_run(coro, loop_factory=EventLoop)
1003997

1004998
def _handle_exception(
1005999
self, loop: AbstractEventLoop, context: dict[str, Any]

0 commit comments

Comments
 (0)