Skip to content

Commit

Permalink
Don't use nest-asyncio
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart committed Sep 7, 2022
1 parent 4e99419 commit 64c400a
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 71 deletions.
25 changes: 7 additions & 18 deletions nbclient/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,22 @@ async def _test():

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
event_loop = loop.run_until_complete(_test())
assert event_loop is loop
finally:
asyncio._set_running_loop(None) # it seems nest_asyncio doesn't reset this
event_loop = loop.run_until_complete(_test())
assert event_loop is loop
loop.close()


def test_nested_asyncio_with_no_ioloop():
asyncio.set_event_loop(None)
try:
assert some_async_function() == 42
finally:
asyncio._set_running_loop(None) # it seems nest_asyncio doesn't reset this
assert some_async_function() == 42


def test_nested_asyncio_with_tornado():
# This tests if tornado accepts the pure-Python Futures, see
# https://github.com/tornadoweb/tornado/issues/2753
# https://github.com/erdewit/nest_asyncio/issues/23
# This tests if tornado accepts the pure-Python Futures
asyncio.set_event_loop(asyncio.new_event_loop())
ioloop = tornado.ioloop.IOLoop.current()

async def some_async_function():
future: asyncio.Future = asyncio.ensure_future(asyncio.sleep(0.1))
# this future is a different future after nested-asyncio has patched
# the asyncio module, check if tornado likes it:
future: asyncio.Task = asyncio.create_task(asyncio.sleep(0.1))
ioloop.add_future(future, lambda f: f.result()) # type:ignore
await future
return 42
Expand All @@ -56,8 +46,7 @@ def some_sync_function():
async def run():
# calling some_async_function directly should work
assert await some_async_function() == 42
# but via a sync function (using nested-asyncio) can lead to issues:
# https://github.com/tornadoweb/tornado/issues/2753
# but via a sync function can lead to issues
assert some_sync_function() == 42

ioloop.run_sync(run)
Expand Down
70 changes: 18 additions & 52 deletions nbclient/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,64 +6,30 @@
import asyncio
import inspect
import sys
from typing import Any, Awaitable, Callable, Optional, TypeVar, Union
from typing import Any, Awaitable, Callable, Coroutine, Optional, TypeVar, Union


def check_ipython() -> None:
# original from vaex/asyncio.py
IPython = sys.modules.get('IPython')
if IPython:
version_str = IPython.__version__
# We get rid of any trailing ".dev"
version_str = version_str.replace(".dev", "")

IPython_version = tuple(map(int, version_str.split('.')))
if IPython_version < (7, 0, 0):
raise RuntimeError(
f'You are using IPython {IPython.__version__} '
'while we require 7.0.0+, please update IPython'
)


def check_patch_tornado() -> None:
"""If tornado is imported, add the patched asyncio.Future to its tuple of acceptable Futures"""
# original from vaex/asyncio.py
if 'tornado' in sys.modules:
import tornado.concurrent

if asyncio.Future not in tornado.concurrent.FUTURES:
tornado.concurrent.FUTURES = tornado.concurrent.FUTURES + ( # type: ignore
asyncio.Future,
)
T = TypeVar("T")


def just_run(coro: Awaitable) -> Any:
"""Make the coroutine run, even if there is an event loop running (using nest_asyncio)"""
def just_run(coro: Coroutine[Any, Any, T]) -> T:
"""Make the coroutine run, even if there is already a running event loop"""
try:
loop = asyncio.get_running_loop()
loop_prev = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop is None:
had_running_loop = False
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
else:
had_running_loop = True
if had_running_loop:
# if there is a running loop, we patch using nest_asyncio
# to have reentrant event loops
check_ipython()
import nest_asyncio

nest_asyncio.apply()
check_patch_tornado()
return loop.run_until_complete(coro)


T = TypeVar("T")


def run_sync(coro: Callable[..., Awaitable[T]]) -> Callable[..., T]:
loop_prev = None
loop = loop_prev or asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# private API, but present from CPython 3.3ish-3.10+
asyncio.events._set_running_loop(None)
res: T = asyncio.run(coro)
if loop_prev:
asyncio.set_event_loop(loop_prev)
asyncio.events._set_running_loop(loop_prev)
return res


def run_sync(coro: Callable[..., Coroutine[Any, Any, T]]) -> Callable[..., T]:
"""Runs a coroutine and blocks until it has executed.
An event loop is created if no one already exists. If an event loop is
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
jupyter_client>=6.1.5
nbformat>=5.0
nest_asyncio
traitlets>=5.2.2

0 comments on commit 64c400a

Please sign in to comment.