Skip to content

Commit 0e7d150

Browse files
committed
Make nest_asyncio optional
1 parent 5fc8bf4 commit 0e7d150

File tree

2 files changed

+36
-68
lines changed

2 files changed

+36
-68
lines changed

binder/run_nbclient.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"nb = nbf.read('./empty_notebook.ipynb', nbf.NO_CONVERT)\n",
4747
"\n",
4848
"# Execute our in-memory notebook, which will now have outputs\n",
49-
"nb = nbclient.execute(nb)"
49+
"nb = nbclient.execute(nb, nest_asyncio=True)"
5050
]
5151
},
5252
{

nbclient/client.py

Lines changed: 35 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
# contextlib, and we `await yield_()` instead of just `yield`
77
from async_generator import asynccontextmanager, async_generator, yield_
88

9-
import nest_asyncio
10-
119
from time import monotonic
1210
from queue import Empty
1311
import asyncio
@@ -97,6 +95,21 @@ class NotebookClient(LoggingConfigurable):
9795
),
9896
).tag(config=True)
9997

98+
nest_asyncio = Bool(
99+
False,
100+
help=dedent(
101+
"""
102+
If False (default), then blocking functions such as `execute`
103+
assume that no event loop is already running. These functions
104+
run their async counterparts (e.g. `async_execute`) in an event
105+
loop with `asyncio.run_until_complete`, which will fail if an
106+
event loop is already running. This can be the case if nbclient
107+
is used e.g. in a Jupyter Notebook. In that case, `nest_asyncio`
108+
should be set to True.
109+
"""
110+
),
111+
).tag(config=True)
112+
100113
force_raise_errors = Bool(
101114
False,
102115
help=dedent(
@@ -367,21 +380,9 @@ async def setup_kernel(self, **kwargs):
367380
self.kc.stop_channels()
368381
self.kc = None
369382

370-
def execute(self, **kwargs):
371-
"""
372-
Executes each code cell (blocking).
373-
374-
Returns
375-
-------
376-
nb : NotebookNode
377-
The executed notebook.
378-
"""
379-
loop = get_loop()
380-
return loop.run_until_complete(self.async_execute(**kwargs))
381-
382383
async def async_execute(self, **kwargs):
383384
"""
384-
Executes each code cell asynchronously.
385+
Executes each code cell.
385386
386387
Returns
387388
-------
@@ -550,48 +551,9 @@ def _check_raise_for_error(self, cell, exec_reply):
550551
if (exec_reply is not None) and exec_reply['content']['status'] == 'error':
551552
raise CellExecutionError.from_cell_and_msg(cell, exec_reply['content'])
552553

553-
def execute_cell(self, cell, cell_index, execution_count=None, store_history=True):
554-
"""
555-
Executes a single code cell (blocking).
556-
557-
To execute all cells see :meth:`execute`.
558-
559-
Parameters
560-
----------
561-
cell : nbformat.NotebookNode
562-
The cell which is currently being processed.
563-
cell_index : int
564-
The position of the cell within the notebook object.
565-
execution_count : int
566-
The execution count to be assigned to the cell (default: Use kernel response)
567-
store_history : bool
568-
Determines if history should be stored in the kernel (default: False).
569-
Specific to ipython kernels, which can store command histories.
570-
571-
Returns
572-
-------
573-
output : dict
574-
The execution output payload (or None for no output).
575-
576-
Raises
577-
------
578-
CellExecutionError
579-
If execution failed and should raise an exception, this will be raised
580-
with defaults about the failure.
581-
582-
Returns
583-
-------
584-
cell : NotebookNode
585-
The cell which was just processed.
586-
"""
587-
loop = get_loop()
588-
return loop.run_until_complete(
589-
self.async_execute_cell(cell, cell_index, execution_count, store_history)
590-
)
591-
592554
async def async_execute_cell(self, cell, cell_index, execution_count=None, store_history=True):
593555
"""
594-
Executes a single code cell asynchronously.
556+
Executes a single code cell.
595557
596558
To execute all cells see :meth:`execute`.
597559
@@ -788,6 +750,24 @@ def _get_buffer_data(self, msg):
788750
return encoded_buffers
789751

790752

753+
def make_blocking(async_method):
754+
def blocking_method(self, *args, **kwargs):
755+
try:
756+
loop = asyncio.get_event_loop()
757+
except RuntimeError:
758+
loop = asyncio.new_event_loop()
759+
asyncio.set_event_loop(loop)
760+
if self.nest_asyncio:
761+
import nest_asyncio
762+
nest_asyncio.apply(loop)
763+
return loop.run_until_complete(async_method(self, *args, **kwargs))
764+
return blocking_method
765+
766+
767+
NotebookClient.execute = make_blocking(NotebookClient.async_execute)
768+
NotebookClient.execute_cell = make_blocking(NotebookClient.async_execute_cell)
769+
770+
791771
def execute(nb, cwd=None, km=None, **kwargs):
792772
"""Execute a notebook's code, updating outputs within the notebook object.
793773
@@ -809,15 +789,3 @@ def execute(nb, cwd=None, km=None, **kwargs):
809789
if cwd is not None:
810790
resources['metadata'] = {'path': cwd}
811791
return NotebookClient(nb=nb, resources=resources, km=km, **kwargs).execute()
812-
813-
814-
def get_loop():
815-
try:
816-
loop = asyncio.get_event_loop()
817-
except RuntimeError:
818-
loop = asyncio.new_event_loop()
819-
asyncio.set_event_loop(loop)
820-
return loop
821-
822-
823-
nest_asyncio.apply()

0 commit comments

Comments
 (0)