diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index 7222051..6623fae 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -12,17 +12,28 @@ Python semantics. import ast +import asyncio import inspect class _AsyncIORunner: + def __init__(self): + self._loop = None + + @property + def loop(self): + """Always returns a non-closed event loop""" + if self._loop is None or self._loop.is_closed(): + policy = asyncio.get_event_loop_policy() + self._loop = policy.new_event_loop() + policy.set_event_loop(self._loop) + return self._loop + def __call__(self, coro): """ Handler for asyncio autoawait """ - import asyncio - - return asyncio.get_event_loop_policy().get_event_loop().run_until_complete(coro) + return self.loop.run_until_complete(coro) def __str__(self): return "asyncio" diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index a3a55fc..355f2b0 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -1058,6 +1058,22 @@ def test_run_cell_async(): assert result.result == 5 +def test_run_cell_await(): + ip.run_cell("import asyncio") + result = ip.run_cell("await asyncio.sleep(0.01); 10") + assert ip.user_ns["_"] == 10 + + +def test_run_cell_asyncio_run(): + ip.run_cell("import asyncio") + result = ip.run_cell("await asyncio.sleep(0.01); 1") + assert ip.user_ns["_"] == 1 + result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2") + assert ip.user_ns["_"] == 2 + result = ip.run_cell("await asyncio.sleep(0.01); 3") + assert ip.user_ns["_"] == 3 + + def test_should_run_async(): assert not ip.should_run_async("a = 5") assert ip.should_run_async("await x") diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index fdd6d90..641a6ff 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -970,7 +970,11 @@ def test_script_config(): @pytest.fixture def event_loop(): - yield asyncio.get_event_loop_policy().get_event_loop() + policy = asyncio.get_event_loop_policy() + loop = policy.new_event_loop() + policy.set_event_loop(loop) + yield loop + loop.close() @dec.skip_win32 diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 7207a57..5e7d95f 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -504,13 +504,15 @@ class TerminalInteractiveShell(InteractiveShell): # If we don't do this, people could spawn coroutine with a # while/true inside which will freeze the prompt. + policy = asyncio.get_event_loop_policy() try: - old_loop = asyncio.get_running_loop() + old_loop = policy.get_event_loop() except RuntimeError: - # This happens when the user used `asyncio.run()`. + # This happens when the the event loop is closed, + # e.g. by calling `asyncio.run()`. old_loop = None - asyncio.set_event_loop(self.pt_loop) + policy.set_event_loop(self.pt_loop) try: with patch_stdout(raw=True): text = self.pt_app.prompt( @@ -518,7 +520,8 @@ class TerminalInteractiveShell(InteractiveShell): **self._extra_prompt_options()) finally: # Restore the original event loop. - asyncio.set_event_loop(old_loop) + if old_loop is not None: + policy.set_event_loop(old_loop) return text