1# Copyright (c) IPython Development Team.
2# Distributed under the terms of the Modified BSD License.
3
4"""Module containing a preprocessor that executes the code cells
5and updates outputs"""
6
7from __future__ import annotations
8
9import typing as t
10from warnings import warn
11
12from jupyter_client.manager import KernelManager
13from nbclient.client import NotebookClient
14from nbclient.client import execute as _execute
15
16# Backwards compatibility for imported name
17from nbclient.exceptions import CellExecutionError # noqa: F401
18from nbformat import NotebookNode
19
20from .base import Preprocessor
21
22
23def executenb(*args, **kwargs):
24 """DEPRECATED."""
25
26 warn(
27 "The 'nbconvert.preprocessors.execute.executenb' function was moved to nbclient.execute. "
28 "We recommend importing that library directly.",
29 FutureWarning,
30 stacklevel=2,
31 )
32 return _execute(*args, **kwargs)
33
34
35# We inherit from both classes to allow for traitlets to resolve as they did pre-6.0.
36# This unfortunately makes for some ugliness around initialization as NotebookClient
37# assumes it's a constructed class with a nb object that we have to hack around.
38class ExecutePreprocessor(Preprocessor, NotebookClient):
39 """
40 Executes all the cells in a notebook
41 """
42
43 def __init__(self, **kw):
44 """Initialize the preprocessor."""
45 nb = kw.get("nb")
46 if nb is None:
47 nb = NotebookNode()
48 Preprocessor.__init__(self, nb=nb, **kw)
49 NotebookClient.__init__(self, nb, **kw)
50
51 def _check_assign_resources(self, resources):
52 if resources or not hasattr(self, "resources"):
53 self.resources = resources
54
55 def preprocess(
56 self, nb: NotebookNode, resources: t.Any = None, km: KernelManager | None = None
57 ) -> tuple[NotebookNode, dict[str, t.Any]]:
58 """
59 Preprocess notebook executing each code cell.
60
61 The input argument *nb* is modified in-place.
62
63 Note that this function recalls NotebookClient.__init__, which may look wrong.
64 However since the preprocess call acts line an init on execution state it's expected.
65 Therefore, we need to capture it here again to properly reset because traitlet
66 assignments are not passed. There is a risk if traitlets apply any side effects for
67 dual init.
68 The risk should be manageable, and this approach minimizes side-effects relative
69 to other alternatives.
70
71 One alternative but rejected implementation would be to copy the client's init internals
72 which has already gotten out of sync with nbclient 0.5 release before nbconvert 6.0 released.
73
74 Parameters
75 ----------
76 nb : NotebookNode
77 Notebook being executed.
78 resources : dictionary (optional)
79 Additional resources used in the conversion process. For example,
80 passing ``{'metadata': {'path': run_path}}`` sets the
81 execution path to ``run_path``.
82 km: KernelManager (optional)
83 Optional kernel manager. If none is provided, a kernel manager will
84 be created.
85
86 Returns
87 -------
88 nb : NotebookNode
89 The executed notebook.
90 resources : dictionary
91 Additional resources used in the conversion process.
92 """
93 NotebookClient.__init__(self, nb, km)
94 self.reset_execution_trackers()
95 self._check_assign_resources(resources)
96
97 with self.setup_kernel():
98 assert self.kc
99 info_msg = self.wait_for_reply(self.kc.kernel_info())
100 assert info_msg
101 self.nb.metadata["language_info"] = info_msg["content"]["language_info"]
102 for index, cell in enumerate(self.nb.cells):
103 self.preprocess_cell(cell, resources, index)
104 self.set_widgets_metadata()
105
106 return self.nb, self.resources
107
108 def preprocess_cell(self, cell, resources, index):
109 """
110 Override if you want to apply some preprocessing to each cell.
111 Must return modified cell and resource dictionary.
112
113 Parameters
114 ----------
115 cell : NotebookNode cell
116 Notebook cell being processed
117 resources : dictionary
118 Additional resources used in the conversion process. Allows
119 preprocessors to pass variables into the Jinja engine.
120 index : int
121 Index of the cell being processed
122 """
123 self._check_assign_resources(resources)
124 cell = self.execute_cell(cell, index, store_history=True)
125 return cell, self.resources