execute.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. """Module containing a preprocessor that executes the code cells
  2. and updates outputs"""
  3. # Copyright (c) IPython Development Team.
  4. # Distributed under the terms of the Modified BSD License.
  5. import base64
  6. from textwrap import dedent
  7. from contextlib import contextmanager
  8. try:
  9. from time import monotonic # Py 3
  10. except ImportError:
  11. from time import time as monotonic # Py 2
  12. try:
  13. from queue import Empty # Py 3
  14. except ImportError:
  15. from Queue import Empty # Py 2
  16. try:
  17. TimeoutError # Py 3
  18. except NameError:
  19. TimeoutError = RuntimeError # Py 2
  20. from traitlets import List, Unicode, Bool, Enum, Any, Type, Dict, Integer, default
  21. from nbformat.v4 import output_from_msg
  22. from .base import Preprocessor
  23. from ..utils.exceptions import ConversionException
  24. class DeadKernelError(RuntimeError):
  25. pass
  26. class CellExecutionComplete(Exception):
  27. """
  28. Used as a control signal for cell execution across run_cell and
  29. process_message function calls. Raised when all execution requests
  30. are completed and no further messages are expected from the kernel
  31. over zeromq channels.
  32. """
  33. pass
  34. class CellExecutionError(ConversionException):
  35. """
  36. Custom exception to propagate exceptions that are raised during
  37. notebook execution to the caller. This is mostly useful when
  38. using nbconvert as a library, since it allows to deal with
  39. failures gracefully.
  40. """
  41. def __init__(self, traceback):
  42. super(CellExecutionError, self).__init__(traceback)
  43. self.traceback = traceback
  44. def __str__(self):
  45. s = self.__unicode__()
  46. if not isinstance(s, str):
  47. s = s.encode('utf8', 'replace')
  48. return s
  49. def __unicode__(self):
  50. return self.traceback
  51. @classmethod
  52. def from_cell_and_msg(cls, cell, msg):
  53. """Instantiate from a code cell object and a message contents
  54. (message is either execute_reply or error)
  55. """
  56. tb = '\n'.join(msg.get('traceback', []))
  57. return cls(exec_err_msg.format(cell=cell, traceback=tb,
  58. ename=msg.get('ename', '<Error>'),
  59. evalue=msg.get('evalue', '')
  60. ))
  61. exec_err_msg = u"""\
  62. An error occurred while executing the following cell:
  63. ------------------
  64. {cell.source}
  65. ------------------
  66. {traceback}
  67. {ename}: {evalue}
  68. """
  69. class ExecutePreprocessor(Preprocessor):
  70. """
  71. Executes all the cells in a notebook
  72. """
  73. timeout = Integer(30, allow_none=True,
  74. help=dedent(
  75. """
  76. The time to wait (in seconds) for output from executions.
  77. If a cell execution takes longer, an exception (TimeoutError
  78. on python 3+, RuntimeError on python 2) is raised.
  79. `None` or `-1` will disable the timeout. If `timeout_func` is set,
  80. it overrides `timeout`.
  81. """
  82. )
  83. ).tag(config=True)
  84. timeout_func = Any(
  85. default_value=None,
  86. allow_none=True,
  87. help=dedent(
  88. """
  89. A callable which, when given the cell source as input,
  90. returns the time to wait (in seconds) for output from cell
  91. executions. If a cell execution takes longer, an exception
  92. (TimeoutError on python 3+, RuntimeError on python 2) is
  93. raised.
  94. Returning `None` or `-1` will disable the timeout for the cell.
  95. Not setting `timeout_func` will cause the preprocessor to
  96. default to using the `timeout` trait for all cells. The
  97. `timeout_func` trait overrides `timeout` if it is not `None`.
  98. """
  99. )
  100. ).tag(config=True)
  101. interrupt_on_timeout = Bool(False,
  102. help=dedent(
  103. """
  104. If execution of a cell times out, interrupt the kernel and
  105. continue executing other cells rather than throwing an error and
  106. stopping.
  107. """
  108. )
  109. ).tag(config=True)
  110. startup_timeout = Integer(60,
  111. help=dedent(
  112. """
  113. The time to wait (in seconds) for the kernel to start.
  114. If kernel startup takes longer, a RuntimeError is
  115. raised.
  116. """
  117. )
  118. ).tag(config=True)
  119. allow_errors = Bool(False,
  120. help=dedent(
  121. """
  122. If `False` (default), when a cell raises an error the
  123. execution is stopped and a `CellExecutionError`
  124. is raised.
  125. If `True`, execution errors are ignored and the execution
  126. is continued until the end of the notebook. Output from
  127. exceptions is included in the cell output in both cases.
  128. """
  129. )
  130. ).tag(config=True)
  131. force_raise_errors = Bool(False,
  132. help=dedent(
  133. """
  134. If False (default), errors from executing the notebook can be
  135. allowed with a `raises-exception` tag on a single cell, or the
  136. `allow_errors` configurable option for all cells. An allowed error
  137. will be recorded in notebook output, and execution will continue.
  138. If an error occurs when it is not explicitly allowed, a
  139. `CellExecutionError` will be raised.
  140. If True, `CellExecutionError` will be raised for any error that occurs
  141. while executing the notebook. This overrides both the
  142. `allow_errors` option and the `raises-exception` cell tag.
  143. """
  144. )
  145. ).tag(config=True)
  146. extra_arguments = List(Unicode())
  147. kernel_name = Unicode('',
  148. help=dedent(
  149. """
  150. Name of kernel to use to execute the cells.
  151. If not set, use the kernel_spec embedded in the notebook.
  152. """
  153. )
  154. ).tag(config=True)
  155. raise_on_iopub_timeout = Bool(False,
  156. help=dedent(
  157. """
  158. If `False` (default), then the kernel will continue waiting for
  159. iopub messages until it receives a kernel idle message, or until a
  160. timeout occurs, at which point the currently executing cell will be
  161. skipped. If `True`, then an error will be raised after the first
  162. timeout. This option generally does not need to be used, but may be
  163. useful in contexts where there is the possibility of executing
  164. notebooks with memory-consuming infinite loops.
  165. """
  166. )
  167. ).tag(config=True)
  168. store_widget_state = Bool(True,
  169. help=dedent(
  170. """
  171. If `True` (default), then the state of the Jupyter widgets created
  172. at the kernel will be stored in the metadata of the notebook.
  173. """
  174. )
  175. ).tag(config=True)
  176. iopub_timeout = Integer(4, allow_none=False,
  177. help=dedent(
  178. """
  179. The time to wait (in seconds) for IOPub output. This generally
  180. doesn't need to be set, but on some slow networks (such as CI
  181. systems) the default timeout might not be long enough to get all
  182. messages.
  183. """
  184. )
  185. ).tag(config=True)
  186. shutdown_kernel = Enum(['graceful', 'immediate'],
  187. default_value='graceful',
  188. help=dedent(
  189. """
  190. If `graceful` (default), then the kernel is given time to clean
  191. up after executing all cells, e.g., to execute its `atexit` hooks.
  192. If `immediate`, then the kernel is signaled to immediately
  193. terminate.
  194. """
  195. )
  196. ).tag(config=True)
  197. ipython_hist_file = Unicode(
  198. default_value=':memory:',
  199. help="""Path to file to use for SQLite history database for an IPython kernel.
  200. The specific value `:memory:` (including the colon
  201. at both end but not the back ticks), avoids creating a history file. Otherwise, IPython
  202. will create a history file for each kernel.
  203. When running kernels simultaneously (e.g. via multiprocessing) saving history a single
  204. SQLite file can result in database errors, so using `:memory:` is recommended in non-interactive
  205. contexts.
  206. """).tag(config=True)
  207. kernel_manager_class = Type(
  208. config=True,
  209. help='The kernel manager class to use.'
  210. )
  211. @default('kernel_manager_class')
  212. def _kernel_manager_class_default(self):
  213. """Use a dynamic default to avoid importing jupyter_client at startup"""
  214. try:
  215. from jupyter_client import KernelManager
  216. except ImportError:
  217. raise ImportError("`nbconvert --execute` requires the jupyter_client package: `pip install jupyter_client`")
  218. return KernelManager
  219. _display_id_map = Dict(
  220. help=dedent(
  221. """
  222. mapping of locations of outputs with a given display_id
  223. tracks cell index and output index within cell.outputs for
  224. each appearance of the display_id
  225. {
  226. 'display_id': {
  227. cell_idx: [output_idx,]
  228. }
  229. }
  230. """))
  231. def start_new_kernel(self, **kwargs):
  232. """Creates a new kernel manager and kernel client.
  233. Parameters
  234. ----------
  235. kwargs :
  236. Any options for `self.kernel_manager_class.start_kernel()`. Because
  237. that defaults to KernelManager, this will likely include options
  238. accepted by `KernelManager.start_kernel()``, which includes `cwd`.
  239. Returns
  240. -------
  241. km : KernelManager
  242. A kernel manager as created by self.kernel_manager_class.
  243. kc : KernelClient
  244. Kernel client as created by the kernel manager `km`.
  245. """
  246. if not self.kernel_name:
  247. self.kernel_name = self.nb.metadata.get(
  248. 'kernelspec', {}).get('name', 'python')
  249. km = self.kernel_manager_class(kernel_name=self.kernel_name,
  250. config=self.config)
  251. if km.ipykernel and self.ipython_hist_file:
  252. self.extra_arguments += ['--HistoryManager.hist_file={}'.format(self.ipython_hist_file)]
  253. km.start_kernel(extra_arguments=self.extra_arguments, **kwargs)
  254. kc = km.client()
  255. kc.start_channels()
  256. try:
  257. kc.wait_for_ready(timeout=self.startup_timeout)
  258. except RuntimeError:
  259. kc.stop_channels()
  260. km.shutdown_kernel()
  261. raise
  262. kc.allow_stdin = False
  263. return km, kc
  264. @contextmanager
  265. def setup_preprocessor(self, nb, resources, km=None, **kwargs):
  266. """
  267. Context manager for setting up the class to execute a notebook.
  268. The assigns `nb` to `self.nb` where it will be modified in-place. It also creates
  269. and assigns the Kernel Manager (`self.km`) and Kernel Client(`self.kc`).
  270. It is intended to yield to a block that will execute codeself.
  271. When control returns from the yield it stops the client's zmq channels, shuts
  272. down the kernel, and removes the now unused attributes.
  273. Parameters
  274. ----------
  275. nb : NotebookNode
  276. Notebook being executed.
  277. resources : dictionary
  278. Additional resources used in the conversion process. For example,
  279. passing ``{'metadata': {'path': run_path}}`` sets the
  280. execution path to ``run_path``.
  281. km : KernerlManager (optional)
  282. Optional kernel manager. If none is provided, a kernel manager will
  283. be created.
  284. Returns
  285. -------
  286. nb : NotebookNode
  287. The executed notebook.
  288. resources : dictionary
  289. Additional resources used in the conversion process.
  290. """
  291. path = resources.get('metadata', {}).get('path', '') or None
  292. self.nb = nb
  293. # clear display_id map
  294. self._display_id_map = {}
  295. self.widget_state = {}
  296. self.widget_buffers = {}
  297. if km is None:
  298. kwargs["cwd"] = path
  299. self.km, self.kc = self.start_new_kernel(**kwargs)
  300. try:
  301. # Yielding unbound args for more easier understanding and downstream consumption
  302. yield nb, self.km, self.kc
  303. finally:
  304. self.kc.stop_channels()
  305. self.km.shutdown_kernel(now=self.shutdown_kernel == 'immediate')
  306. for attr in ['nb', 'km', 'kc']:
  307. delattr(self, attr)
  308. else:
  309. self.km = km
  310. if not km.has_kernel:
  311. km.start_kernel(extra_arguments=self.extra_arguments, **kwargs)
  312. self.kc = km.client()
  313. self.kc.start_channels()
  314. try:
  315. self.kc.wait_for_ready(timeout=self.startup_timeout)
  316. except RuntimeError:
  317. self.kc.stop_channels()
  318. raise
  319. self.kc.allow_stdin = False
  320. try:
  321. yield nb, self.km, self.kc
  322. finally:
  323. for attr in ['nb', 'km', 'kc']:
  324. delattr(self, attr)
  325. def preprocess(self, nb, resources=None, km=None):
  326. """
  327. Preprocess notebook executing each code cell.
  328. The input argument `nb` is modified in-place.
  329. Parameters
  330. ----------
  331. nb : NotebookNode
  332. Notebook being executed.
  333. resources : dictionary (optional)
  334. Additional resources used in the conversion process. For example,
  335. passing ``{'metadata': {'path': run_path}}`` sets the
  336. execution path to ``run_path``.
  337. km: KernelManager (optional)
  338. Optional kernel manager. If none is provided, a kernel manager will
  339. be created.
  340. Returns
  341. -------
  342. nb : NotebookNode
  343. The executed notebook.
  344. resources : dictionary
  345. Additional resources used in the conversion process.
  346. """
  347. if not resources:
  348. resources = {}
  349. with self.setup_preprocessor(nb, resources, km=km):
  350. self.log.info("Executing notebook with kernel: %s" % self.kernel_name)
  351. nb, resources = super(ExecutePreprocessor, self).preprocess(nb, resources)
  352. info_msg = self._wait_for_reply(self.kc.kernel_info())
  353. nb.metadata['language_info'] = info_msg['content']['language_info']
  354. self.set_widgets_metadata()
  355. return nb, resources
  356. def set_widgets_metadata(self):
  357. if self.widget_state:
  358. self.nb.metadata.widgets = {
  359. 'application/vnd.jupyter.widget-state+json': {
  360. 'state': {
  361. model_id: _serialize_widget_state(state)
  362. for model_id, state in self.widget_state.items() if '_model_name' in state
  363. },
  364. 'version_major': 2,
  365. 'version_minor': 0,
  366. }
  367. }
  368. for key, widget in self.nb.metadata.widgets['application/vnd.jupyter.widget-state+json']['state'].items():
  369. buffers = self.widget_buffers.get(key)
  370. if buffers:
  371. widget['buffers'] = buffers
  372. def preprocess_cell(self, cell, resources, cell_index, store_history=True):
  373. """
  374. Executes a single code cell. See base.py for details.
  375. To execute all cells see :meth:`preprocess`.
  376. """
  377. if cell.cell_type != 'code' or not cell.source.strip():
  378. return cell, resources
  379. reply, outputs = self.run_cell(cell, cell_index, store_history)
  380. # Backwards compatibility for processes that wrap run_cell
  381. cell.outputs = outputs
  382. cell_allows_errors = (self.allow_errors or "raises-exception"
  383. in cell.metadata.get("tags", []))
  384. if self.force_raise_errors or not cell_allows_errors:
  385. for out in cell.outputs:
  386. if out.output_type == 'error':
  387. raise CellExecutionError.from_cell_and_msg(cell, out)
  388. if (reply is not None) and reply['content']['status'] == 'error':
  389. raise CellExecutionError.from_cell_and_msg(cell, reply['content'])
  390. return cell, resources
  391. def _update_display_id(self, display_id, msg):
  392. """Update outputs with a given display_id"""
  393. if display_id not in self._display_id_map:
  394. self.log.debug("display id %r not in %s", display_id, self._display_id_map)
  395. return
  396. if msg['header']['msg_type'] == 'update_display_data':
  397. msg['header']['msg_type'] = 'display_data'
  398. try:
  399. out = output_from_msg(msg)
  400. except ValueError:
  401. self.log.error("unhandled iopub msg: " + msg['msg_type'])
  402. return
  403. for cell_idx, output_indices in self._display_id_map[display_id].items():
  404. cell = self.nb['cells'][cell_idx]
  405. outputs = cell['outputs']
  406. for output_idx in output_indices:
  407. outputs[output_idx]['data'] = out['data']
  408. outputs[output_idx]['metadata'] = out['metadata']
  409. def _poll_for_reply(self, msg_id, cell=None, timeout=None):
  410. try:
  411. # check with timeout if kernel is still alive
  412. msg = self.kc.shell_channel.get_msg(timeout=timeout)
  413. if msg['parent_header'].get('msg_id') == msg_id:
  414. return msg
  415. except Empty:
  416. # received no message, check if kernel is still alive
  417. self._check_alive()
  418. # kernel still alive, wait for a message
  419. def _get_timeout(self, cell):
  420. if self.timeout_func is not None and cell is not None:
  421. timeout = self.timeout_func(cell)
  422. else:
  423. timeout = self.timeout
  424. if not timeout or timeout < 0:
  425. timeout = None
  426. return timeout
  427. def _handle_timeout(self):
  428. self.log.error(
  429. "Timeout waiting for execute reply (%is)." % self.timeout)
  430. if self.interrupt_on_timeout:
  431. self.log.error("Interrupting kernel")
  432. self.km.interrupt_kernel()
  433. else:
  434. raise TimeoutError("Cell execution timed out")
  435. def _check_alive(self):
  436. if not self.kc.is_alive():
  437. self.log.error(
  438. "Kernel died while waiting for execute reply.")
  439. raise DeadKernelError("Kernel died")
  440. def _wait_for_reply(self, msg_id, cell=None):
  441. # wait for finish, with timeout
  442. timeout = self._get_timeout(cell)
  443. cummulative_time = 0
  444. timeout_interval = 5
  445. while True:
  446. try:
  447. msg = self.kc.shell_channel.get_msg(timeout=timeout_interval)
  448. except Empty:
  449. self._check_alive()
  450. cummulative_time += timeout_interval
  451. if timeout and cummulative_time > timeout:
  452. self._handle_timeout()
  453. break
  454. else:
  455. if msg['parent_header'].get('msg_id') == msg_id:
  456. return msg
  457. def _timeout_with_deadline(self, timeout, deadline):
  458. if deadline is not None and deadline - monotonic() < timeout:
  459. timeout = deadline - monotonic()
  460. if timeout < 0:
  461. timeout = 0
  462. return timeout
  463. def _passed_deadline(self, deadline):
  464. if deadline is not None and deadline - monotonic() <= 0:
  465. self._handle_timeout()
  466. return True
  467. return False
  468. def run_cell(self, cell, cell_index=0, store_history=True):
  469. parent_msg_id = self.kc.execute(cell.source,
  470. store_history=store_history, stop_on_error=not self.allow_errors)
  471. self.log.debug("Executing cell:\n%s", cell.source)
  472. exec_timeout = self._get_timeout(cell)
  473. deadline = None
  474. if exec_timeout is not None:
  475. deadline = monotonic() + exec_timeout
  476. cell.outputs = []
  477. self.clear_before_next_output = False
  478. # This loop resolves #659. By polling iopub_channel's and shell_channel's
  479. # output we avoid dropping output and important signals (like idle) from
  480. # iopub_channel. Prior to this change, iopub_channel wasn't polled until
  481. # after exec_reply was obtained from shell_channel, leading to the
  482. # aforementioned dropped data.
  483. # These two variables are used to track what still needs polling:
  484. # more_output=true => continue to poll the iopub_channel
  485. more_output = True
  486. # polling_exec_reply=true => continue to poll the shell_channel
  487. polling_exec_reply = True
  488. while more_output or polling_exec_reply:
  489. if polling_exec_reply:
  490. if self._passed_deadline(deadline):
  491. polling_exec_reply = False
  492. continue
  493. # Avoid exceeding the execution timeout (deadline), but stop
  494. # after at most 1s so we can poll output from iopub_channel.
  495. timeout = self._timeout_with_deadline(1, deadline)
  496. exec_reply = self._poll_for_reply(parent_msg_id, cell, timeout)
  497. if exec_reply is not None:
  498. polling_exec_reply = False
  499. if more_output:
  500. try:
  501. timeout = self.iopub_timeout
  502. if polling_exec_reply:
  503. # Avoid exceeding the execution timeout (deadline) while
  504. # polling for output.
  505. timeout = self._timeout_with_deadline(timeout, deadline)
  506. msg = self.kc.iopub_channel.get_msg(timeout=timeout)
  507. except Empty:
  508. if polling_exec_reply:
  509. # Still waiting for execution to finish so we expect that
  510. # output may not always be produced yet.
  511. continue
  512. if self.raise_on_iopub_timeout:
  513. raise TimeoutError("Timeout waiting for IOPub output")
  514. else:
  515. self.log.warning("Timeout waiting for IOPub output")
  516. more_output = False
  517. continue
  518. if msg['parent_header'].get('msg_id') != parent_msg_id:
  519. # not an output from our execution
  520. continue
  521. try:
  522. # Will raise CellExecutionComplete when completed
  523. self.process_message(msg, cell, cell_index)
  524. except CellExecutionComplete:
  525. more_output = False
  526. # Return cell.outputs still for backwards compatibility
  527. return exec_reply, cell.outputs
  528. def process_message(self, msg, cell, cell_index):
  529. """
  530. Processes a kernel message, updates cell state, and returns the
  531. resulting output object that was appended to cell.outputs.
  532. The input argument `cell` is modified in-place.
  533. Parameters
  534. ----------
  535. msg : dict
  536. The kernel message being processed.
  537. cell : nbformat.NotebookNode
  538. The cell which is currently being processed.
  539. cell_index : int
  540. The position of the cell within the notebook object.
  541. Returns
  542. -------
  543. output : dict
  544. The execution output payload (or None for no output).
  545. Raises
  546. ------
  547. CellExecutionComplete
  548. Once a message arrives which indicates computation completeness.
  549. """
  550. msg_type = msg['msg_type']
  551. self.log.debug("msg_type: %s", msg_type)
  552. content = msg['content']
  553. self.log.debug("content: %s", content)
  554. display_id = content.get('transient', {}).get('display_id', None)
  555. if display_id and msg_type in {'execute_result', 'display_data', 'update_display_data'}:
  556. self._update_display_id(display_id, msg)
  557. # set the prompt number for the input and the output
  558. if 'execution_count' in content:
  559. cell['execution_count'] = content['execution_count']
  560. if msg_type == 'status':
  561. if content['execution_state'] == 'idle':
  562. raise CellExecutionComplete()
  563. elif msg_type == 'clear_output':
  564. self.clear_output(cell.outputs, msg, cell_index)
  565. elif msg_type.startswith('comm'):
  566. self.handle_comm_msg(cell.outputs, msg, cell_index)
  567. # Check for remaining messages we don't process
  568. elif msg_type not in ['execute_input', 'update_display_data']:
  569. # Assign output as our processed "result"
  570. return self.output(cell.outputs, msg, display_id, cell_index)
  571. def output(self, outs, msg, display_id, cell_index):
  572. msg_type = msg['msg_type']
  573. try:
  574. out = output_from_msg(msg)
  575. except ValueError:
  576. self.log.error("unhandled iopub msg: " + msg_type)
  577. return
  578. if self.clear_before_next_output:
  579. self.log.debug('Executing delayed clear_output')
  580. outs[:] = []
  581. self.clear_display_id_mapping(cell_index)
  582. self.clear_before_next_output = False
  583. if display_id:
  584. # record output index in:
  585. # _display_id_map[display_id][cell_idx]
  586. cell_map = self._display_id_map.setdefault(display_id, {})
  587. output_idx_list = cell_map.setdefault(cell_index, [])
  588. output_idx_list.append(len(outs))
  589. outs.append(out)
  590. return out
  591. def clear_output(self, outs, msg, cell_index):
  592. content = msg['content']
  593. if content.get('wait'):
  594. self.log.debug('Wait to clear output')
  595. self.clear_before_next_output = True
  596. else:
  597. self.log.debug('Immediate clear output')
  598. outs[:] = []
  599. self.clear_display_id_mapping(cell_index)
  600. def clear_display_id_mapping(self, cell_index):
  601. for display_id, cell_map in self._display_id_map.items():
  602. if cell_index in cell_map:
  603. cell_map[cell_index] = []
  604. def handle_comm_msg(self, outs, msg, cell_index):
  605. content = msg['content']
  606. data = content['data']
  607. if self.store_widget_state and 'state' in data: # ignore custom msg'es
  608. self.widget_state.setdefault(content['comm_id'], {}).update(data['state'])
  609. if 'buffer_paths' in data and data['buffer_paths']:
  610. self.widget_buffers[content['comm_id']] = _get_buffer_data(msg)
  611. def executenb(nb, cwd=None, km=None, **kwargs):
  612. """Execute a notebook's code, updating outputs within the notebook object.
  613. This is a convenient wrapper around ExecutePreprocessor. It returns the
  614. modified notebook object.
  615. Parameters
  616. ----------
  617. nb : NotebookNode
  618. The notebook object to be executed
  619. cwd : str, optional
  620. If supplied, the kernel will run in this directory
  621. km : KernelManager, optional
  622. If supplied, the specified kernel manager will be used for code execution.
  623. kwargs :
  624. Any other options for ExecutePreprocessor, e.g. timeout, kernel_name
  625. """
  626. resources = {}
  627. if cwd is not None:
  628. resources['metadata'] = {'path': cwd}
  629. ep = ExecutePreprocessor(**kwargs)
  630. return ep.preprocess(nb, resources, km=km)[0]
  631. def _serialize_widget_state(state):
  632. """Serialize a widget state, following format in @jupyter-widgets/schema."""
  633. return {
  634. 'model_name': state.get('_model_name'),
  635. 'model_module': state.get('_model_module'),
  636. 'model_module_version': state.get('_model_module_version'),
  637. 'state': state,
  638. }
  639. def _get_buffer_data(msg):
  640. encoded_buffers = []
  641. paths = msg['content']['data']['buffer_paths']
  642. buffers = msg['buffers']
  643. for path, buffer in zip(paths, buffers):
  644. encoded_buffers.append({
  645. 'data': base64.b64encode(buffer).decode('utf-8'),
  646. 'encoding': 'base64',
  647. 'path': path
  648. })
  649. return encoded_buffers