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