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