##// END OF EJS Templates
Add preprocessor to execute notebooks
Julia Evans -
Show More
@@ -0,0 +1,109 b''
1 """Module containing a preprocessor that removes the outputs from code cells"""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 #-----------------------------------------------------------------------------
7 # Imports
8 #-----------------------------------------------------------------------------
9
10 import os
11 import sys
12
13 from Queue import Empty
14 from IPython.kernel import KernelManager
15 from IPython.nbformat.current import reads, NotebookNode, writes
16
17 from .base import Preprocessor
18
19
20 #-----------------------------------------------------------------------------
21 # Classes
22 #-----------------------------------------------------------------------------
23 class ExecutePreprocessor(Preprocessor):
24 """
25 Executes all the cells in a notebook
26 """
27 def __init__(self, *args, **kwargs):
28 """
29 Start an kernel to run the Python code
30 """
31 super(ExecutePreprocessor, self).__init__(*args, **kwargs)
32 self.km = KernelManager()
33 # run %pylab inline, because some notebooks assume this
34 # even though they shouldn't
35 self.km.start_kernel(extra_arguments=['--pylab=inline'], stderr=open(os.devnull, 'w'))
36 self.kc = self.km.client()
37 self.kc.start_channels()
38 self.iopub = self.kc.iopub_channel
39 self.shell = self.kc.shell_channel
40
41 self.shell.execute("pass")
42 self.shell.get_msg()
43
44 def preprocess_cell(self, cell, resources, cell_index):
45 """
46 Apply a transformation on each code cell. See base.py for details.
47 """
48 if cell.cell_type != 'code':
49 return cell, resources
50 try:
51 outputs = self.run_cell(self.shell, self.iopub, cell)
52 except Exception as e:
53 print >> sys.stderr, "failed to run cell:", repr(e)
54 print >> sys.stderr, cell.input
55 sys.exit(1)
56 cell.outputs = outputs
57 return cell, resources
58
59 @staticmethod
60 def run_cell(shell, iopub, cell):
61 # print cell.input
62 shell.execute(cell.input)
63 # wait for finish, maximum 20s
64 shell.get_msg(timeout=20)
65 outs = []
66
67 while True:
68 try:
69 msg = iopub.get_msg(timeout=0.2)
70 except Empty:
71 break
72 msg_type = msg['msg_type']
73 if msg_type in ('status', 'pyin'):
74 continue
75 elif msg_type == 'clear_output':
76 outs = []
77 continue
78
79 content = msg['content']
80 # print msg_type, content
81 out = NotebookNode(output_type=msg_type)
82
83 if msg_type == 'stream':
84 out.stream = content['name']
85 out.text = content['data']
86 elif msg_type in ('display_data', 'pyout'):
87 out['metadata'] = content['metadata']
88 for mime, data in content['data'].iteritems():
89 attr = mime.split('/')[-1].lower()
90 # this gets most right, but fix svg+html, plain
91 attr = attr.replace('+xml', '').replace('plain', 'text')
92 setattr(out, attr, data)
93 if msg_type == 'pyout':
94 out.prompt_number = content['execution_count']
95 elif msg_type == 'pyerr':
96 out.ename = content['ename']
97 out.evalue = content['evalue']
98 out.traceback = content['traceback']
99 else:
100 print >> sys.stderr, "unhandled iopub msg:", msg_type
101
102 outs.append(out)
103 return outs
104
105
106 def __del__(self):
107 self.kc.stop_channels()
108 self.km.shutdown_kernel()
109 del self.km
@@ -0,0 +1,45 b''
1 """
2 Module with tests for the clearoutput preprocessor.
3 """
4
5 # Copyright (c) IPython Development Team.
6 # Distributed under the terms of the Modified BSD License.
7
8 #-----------------------------------------------------------------------------
9 # Imports
10 #-----------------------------------------------------------------------------
11 import copy
12
13 from IPython.nbformat import current as nbformat
14
15 from .base import PreprocessorTestsBase
16 from ..execute import ExecutePreprocessor
17
18
19 #-----------------------------------------------------------------------------
20 # Class
21 #-----------------------------------------------------------------------------
22
23 class TestExecute(PreprocessorTestsBase):
24 """Contains test functions for execute.py"""
25
26
27 def build_preprocessor(self):
28 """Make an instance of a preprocessor"""
29 preprocessor = ExecutePreprocessor()
30 preprocessor.enabled = True
31 return preprocessor
32
33 def test_constructor(self):
34 """Can a ExecutePreprocessor be constructed?"""
35 self.build_preprocessor()
36
37 def test_correct_output(self):
38 """Test that ExecutePreprocessor evaluates a cell to the right thing"""
39 nb = self.build_notebook()
40 res = self.build_resources()
41 nb.worksheets[0].cells[0].input = "print 'hi!'"
42 preprocessor = self.build_preprocessor()
43 nb, res = preprocessor(nb, res)
44 expected_outputs = [{'output_type': 'stream', 'stream': 'stdout', 'text': 'hi!\n'}]
45 assert nb.worksheets[0].cells[0].outputs == expected_outputs
@@ -1,276 +1,277 b''
1 1 """This module defines a base Exporter class. For Jinja template-based export,
2 2 see templateexporter.py.
3 3 """
4 4
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 from __future__ import print_function, absolute_import
18 18
19 19 # Stdlib imports
20 20 import io
21 21 import os
22 22 import copy
23 23 import collections
24 24 import datetime
25 25
26 26
27 27 # IPython imports
28 28 from IPython.config.configurable import LoggingConfigurable
29 29 from IPython.config import Config
30 30 from IPython.nbformat import current as nbformat
31 31 from IPython.utils.traitlets import MetaHasTraits, Unicode, List
32 32 from IPython.utils.importstring import import_item
33 33 from IPython.utils import text, py3compat
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Class
37 37 #-----------------------------------------------------------------------------
38 38
39 39 class ResourcesDict(collections.defaultdict):
40 40 def __missing__(self, key):
41 41 return ''
42 42
43 43
44 44 class Exporter(LoggingConfigurable):
45 45 """
46 46 Class containing methods that sequentially run a list of preprocessors on a
47 47 NotebookNode object and then return the modified NotebookNode object and
48 48 accompanying resources dict.
49 49 """
50 50
51 51 file_extension = Unicode(
52 52 'txt', config=True,
53 53 help="Extension of the file that should be written to disk"
54 54 )
55 55
56 56 # MIME type of the result file, for HTTP response headers.
57 57 # This is *not* a traitlet, because we want to be able to access it from
58 58 # the class, not just on instances.
59 59 output_mimetype = ''
60 60
61 61 #Configurability, allows the user to easily add filters and preprocessors.
62 62 preprocessors = List(config=True,
63 63 help="""List of preprocessors, by name or namespace, to enable.""")
64 64
65 65 _preprocessors = List()
66 66
67 67 default_preprocessors = List(['IPython.nbconvert.preprocessors.coalesce_streams',
68 68 'IPython.nbconvert.preprocessors.SVG2PDFPreprocessor',
69 69 'IPython.nbconvert.preprocessors.ExtractOutputPreprocessor',
70 70 'IPython.nbconvert.preprocessors.CSSHTMLHeaderPreprocessor',
71 71 'IPython.nbconvert.preprocessors.RevealHelpPreprocessor',
72 72 'IPython.nbconvert.preprocessors.LatexPreprocessor',
73 73 'IPython.nbconvert.preprocessors.ClearOutputPreprocessor',
74 'IPython.nbconvert.preprocessors.ExecutePreprocessor',
74 75 'IPython.nbconvert.preprocessors.HighlightMagicsPreprocessor'],
75 76 config=True,
76 77 help="""List of preprocessors available by default, by name, namespace,
77 78 instance, or type.""")
78 79
79 80
80 81 def __init__(self, config=None, **kw):
81 82 """
82 83 Public constructor
83 84
84 85 Parameters
85 86 ----------
86 87 config : config
87 88 User configuration instance.
88 89 """
89 90 with_default_config = self.default_config
90 91 if config:
91 92 with_default_config.merge(config)
92 93
93 94 super(Exporter, self).__init__(config=with_default_config, **kw)
94 95
95 96 self._init_preprocessors()
96 97
97 98
98 99 @property
99 100 def default_config(self):
100 101 return Config()
101 102
102 103 @nbformat.docstring_nbformat_mod
103 104 def from_notebook_node(self, nb, resources=None, **kw):
104 105 """
105 106 Convert a notebook from a notebook node instance.
106 107
107 108 Parameters
108 109 ----------
109 110 nb : :class:`~{nbformat_mod}.nbbase.NotebookNode`
110 111 Notebook node
111 112 resources : dict
112 113 Additional resources that can be accessed read/write by
113 114 preprocessors and filters.
114 115 **kw
115 116 Ignored (?)
116 117 """
117 118 nb_copy = copy.deepcopy(nb)
118 119 resources = self._init_resources(resources)
119 120
120 121 if 'language' in nb['metadata']:
121 122 resources['language'] = nb['metadata']['language'].lower()
122 123
123 124 # Preprocess
124 125 nb_copy, resources = self._preprocess(nb_copy, resources)
125 126
126 127 return nb_copy, resources
127 128
128 129
129 130 def from_filename(self, filename, resources=None, **kw):
130 131 """
131 132 Convert a notebook from a notebook file.
132 133
133 134 Parameters
134 135 ----------
135 136 filename : str
136 137 Full filename of the notebook file to open and convert.
137 138 """
138 139
139 140 # Pull the metadata from the filesystem.
140 141 if resources is None:
141 142 resources = ResourcesDict()
142 143 if not 'metadata' in resources or resources['metadata'] == '':
143 144 resources['metadata'] = ResourcesDict()
144 145 basename = os.path.basename(filename)
145 146 notebook_name = basename[:basename.rfind('.')]
146 147 resources['metadata']['name'] = notebook_name
147 148
148 149 modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename))
149 150 resources['metadata']['modified_date'] = modified_date.strftime(text.date_format)
150 151
151 152 with io.open(filename, encoding='utf-8') as f:
152 153 return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources, **kw)
153 154
154 155
155 156 def from_file(self, file_stream, resources=None, **kw):
156 157 """
157 158 Convert a notebook from a notebook file.
158 159
159 160 Parameters
160 161 ----------
161 162 file_stream : file-like object
162 163 Notebook file-like object to convert.
163 164 """
164 165 return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw)
165 166
166 167
167 168 def register_preprocessor(self, preprocessor, enabled=False):
168 169 """
169 170 Register a preprocessor.
170 171 Preprocessors are classes that act upon the notebook before it is
171 172 passed into the Jinja templating engine. preprocessors are also
172 173 capable of passing additional information to the Jinja
173 174 templating engine.
174 175
175 176 Parameters
176 177 ----------
177 178 preprocessor : preprocessor
178 179 """
179 180 if preprocessor is None:
180 181 raise TypeError('preprocessor')
181 182 isclass = isinstance(preprocessor, type)
182 183 constructed = not isclass
183 184
184 185 # Handle preprocessor's registration based on it's type
185 186 if constructed and isinstance(preprocessor, py3compat.string_types):
186 187 # Preprocessor is a string, import the namespace and recursively call
187 188 # this register_preprocessor method
188 189 preprocessor_cls = import_item(preprocessor)
189 190 return self.register_preprocessor(preprocessor_cls, enabled)
190 191
191 192 if constructed and hasattr(preprocessor, '__call__'):
192 193 # Preprocessor is a function, no need to construct it.
193 194 # Register and return the preprocessor.
194 195 if enabled:
195 196 preprocessor.enabled = True
196 197 self._preprocessors.append(preprocessor)
197 198 return preprocessor
198 199
199 200 elif isclass and isinstance(preprocessor, MetaHasTraits):
200 201 # Preprocessor is configurable. Make sure to pass in new default for
201 202 # the enabled flag if one was specified.
202 203 self.register_preprocessor(preprocessor(parent=self), enabled)
203 204
204 205 elif isclass:
205 206 # Preprocessor is not configurable, construct it
206 207 self.register_preprocessor(preprocessor(), enabled)
207 208
208 209 else:
209 210 # Preprocessor is an instance of something without a __call__
210 211 # attribute.
211 212 raise TypeError('preprocessor')
212 213
213 214
214 215 def _init_preprocessors(self):
215 216 """
216 217 Register all of the preprocessors needed for this exporter, disabled
217 218 unless specified explicitly.
218 219 """
219 220 self._preprocessors = []
220 221
221 222 # Load default preprocessors (not necessarly enabled by default).
222 223 for preprocessor in self.default_preprocessors:
223 224 self.register_preprocessor(preprocessor)
224 225
225 226 # Load user-specified preprocessors. Enable by default.
226 227 for preprocessor in self.preprocessors:
227 228 self.register_preprocessor(preprocessor, enabled=True)
228 229
229 230
230 231 def _init_resources(self, resources):
231 232
232 233 #Make sure the resources dict is of ResourcesDict type.
233 234 if resources is None:
234 235 resources = ResourcesDict()
235 236 if not isinstance(resources, ResourcesDict):
236 237 new_resources = ResourcesDict()
237 238 new_resources.update(resources)
238 239 resources = new_resources
239 240
240 241 #Make sure the metadata extension exists in resources
241 242 if 'metadata' in resources:
242 243 if not isinstance(resources['metadata'], ResourcesDict):
243 244 resources['metadata'] = ResourcesDict(resources['metadata'])
244 245 else:
245 246 resources['metadata'] = ResourcesDict()
246 247 if not resources['metadata']['name']:
247 248 resources['metadata']['name'] = 'Notebook'
248 249
249 250 #Set the output extension
250 251 resources['output_extension'] = self.file_extension
251 252 return resources
252 253
253 254
254 255 def _preprocess(self, nb, resources):
255 256 """
256 257 Preprocess the notebook before passing it into the Jinja engine.
257 258 To preprocess the notebook is to apply all of the
258 259
259 260 Parameters
260 261 ----------
261 262 nb : notebook node
262 263 notebook that is being exported.
263 264 resources : a dict of additional resources that
264 265 can be accessed read/write by preprocessors
265 266 """
266 267
267 268 # Do a copy.deepcopy first,
268 269 # we are never safe enough with what the preprocessors could do.
269 270 nbc = copy.deepcopy(nb)
270 271 resc = copy.deepcopy(resources)
271 272
272 273 #Run each preprocessor on the notebook. Carry the output along
273 274 #to each preprocessor
274 275 for preprocessor in self._preprocessors:
275 276 nbc, resc = preprocessor(nbc, resc)
276 277 return nbc, resc
@@ -1,13 +1,14 b''
1 1 # Class base Preprocessors
2 2 from .base import Preprocessor
3 3 from .convertfigures import ConvertFiguresPreprocessor
4 4 from .svg2pdf import SVG2PDFPreprocessor
5 5 from .extractoutput import ExtractOutputPreprocessor
6 6 from .revealhelp import RevealHelpPreprocessor
7 7 from .latex import LatexPreprocessor
8 8 from .csshtmlheader import CSSHTMLHeaderPreprocessor
9 9 from .highlightmagics import HighlightMagicsPreprocessor
10 10 from .clearoutput import ClearOutputPreprocessor
11 from .execute import ExecutePreprocessor
11 12
12 13 # decorated function Preprocessors
13 14 from .coalescestreams import coalesce_streams
General Comments 0
You need to be logged in to leave comments. Login now