Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make nest_asyncio optional #37

Merged
merged 14 commits into from
Mar 25, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion binder/run_nbclient.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"nb = nbf.read('./empty_notebook.ipynb', nbf.NO_CONVERT)\n",
"\n",
"# Execute our in-memory notebook, which will now have outputs\n",
"nb = nbclient.execute(nb)"
"nb = nbclient.execute(nb, nest_asyncio=True)"
]
},
{
Expand Down
117 changes: 56 additions & 61 deletions nbclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
# contextlib, and we `await yield_()` instead of just `yield`
from async_generator import asynccontextmanager, async_generator, yield_

import nest_asyncio

from time import monotonic
from queue import Empty
import asyncio
Expand Down Expand Up @@ -97,6 +95,21 @@ class NotebookClient(LoggingConfigurable):
),
).tag(config=True)

nest_asyncio = Bool(
False,
help=dedent(
"""
If False (default), then blocking functions such as `execute`
assume that no event loop is already running. These functions
run their async counterparts (e.g. `async_execute`) in an event
loop with `asyncio.run_until_complete`, which will fail if an
event loop is already running. This can be the case if nbclient
is used e.g. in a Jupyter Notebook. In that case, `nest_asyncio`
should be set to True.
"""
),
).tag(config=True)

force_raise_errors = Bool(
False,
help=dedent(
Expand Down Expand Up @@ -281,6 +294,43 @@ def __init__(self, nb, km=None, **kw):
self.km = km
self.reset_execution_trackers()

def run_blocking(self, coro):
"""Runs a coroutine and blocks until it has executed.

An event loop is created if no one already exists. If an event loop is
already running, this event loop execution is nested into the already
running one if `nest_asyncio` is set to True.

Parameters
----------
coro : coroutine
The coroutine to be executed.

Returns
-------
result :
Whatever the coroutine returns.
"""
try:
loop = asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
if self.nest_asyncio:
import nest_asyncio
nest_asyncio.apply(loop)
try:
result = loop.run_until_complete(coro)
except RuntimeError as e:
if str(e) == 'This event loop is already running':
raise RuntimeError(
'You are trying to run nbclient in an environment where an '
'event loop is already running. Please pass `nest_asyncio=True` in '
'`NotebookClient.execute` and such methods.'
)
raise
return result

def reset_execution_trackers(self):
"""Resets any per-execution trackers.
"""
Expand Down Expand Up @@ -368,20 +418,11 @@ async def setup_kernel(self, **kwargs):
self.kc = None

def execute(self, **kwargs):
"""
davidbrochart marked this conversation as resolved.
Show resolved Hide resolved
Executes each code cell (blocking).

Returns
-------
nb : NotebookNode
The executed notebook.
"""
loop = get_loop()
return loop.run_until_complete(self.async_execute(**kwargs))
return self.run_blocking(self.async_execute(**kwargs))

async def async_execute(self, **kwargs):
"""
Executes each code cell asynchronously.
Executes each code cell.

Returns
-------
Expand Down Expand Up @@ -551,47 +592,13 @@ def _check_raise_for_error(self, cell, exec_reply):
raise CellExecutionError.from_cell_and_msg(cell, exec_reply['content'])

def execute_cell(self, cell, cell_index, execution_count=None, store_history=True):
"""
davidbrochart marked this conversation as resolved.
Show resolved Hide resolved
Executes a single code cell (blocking).

To execute all cells see :meth:`execute`.

Parameters
----------
cell : nbformat.NotebookNode
The cell which is currently being processed.
cell_index : int
The position of the cell within the notebook object.
execution_count : int
The execution count to be assigned to the cell (default: Use kernel response)
store_history : bool
Determines if history should be stored in the kernel (default: False).
Specific to ipython kernels, which can store command histories.

Returns
-------
output : dict
The execution output payload (or None for no output).

Raises
------
CellExecutionError
If execution failed and should raise an exception, this will be raised
with defaults about the failure.

Returns
-------
cell : NotebookNode
The cell which was just processed.
"""
loop = get_loop()
return loop.run_until_complete(
return self.run_blocking(
self.async_execute_cell(cell, cell_index, execution_count, store_history)
)

async def async_execute_cell(self, cell, cell_index, execution_count=None, store_history=True):
"""
Executes a single code cell asynchronously.
Executes a single code cell.

To execute all cells see :meth:`execute`.

Expand Down Expand Up @@ -809,15 +816,3 @@ def execute(nb, cwd=None, km=None, **kwargs):
if cwd is not None:
resources['metadata'] = {'path': cwd}
return NotebookClient(nb=nb, resources=resources, km=km, **kwargs).execute()


def get_loop():
try:
loop = asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop


nest_asyncio.apply()