##// END OF EJS Templates
Merge pull request #7492 from minrk/missing-stream...
Thomas Kluyver -
r19979:edd163fc merge
parent child Browse files
Show More
@@ -1,253 +1,253 b''
1 """Code for converting notebooks to and from v3."""
1 """Code for converting notebooks to and from v3."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import json
6 import json
7 import re
7 import re
8
8
9 from .nbbase import (
9 from .nbbase import (
10 nbformat, nbformat_minor,
10 nbformat, nbformat_minor,
11 NotebookNode,
11 NotebookNode,
12 )
12 )
13
13
14 from IPython.nbformat import v3
14 from IPython.nbformat import v3
15 from IPython.utils.log import get_logger
15 from IPython.utils.log import get_logger
16
16
17 def _warn_if_invalid(nb, version):
17 def _warn_if_invalid(nb, version):
18 """Log validation errors, if there are any."""
18 """Log validation errors, if there are any."""
19 from IPython.nbformat import validate, ValidationError
19 from IPython.nbformat import validate, ValidationError
20 try:
20 try:
21 validate(nb, version=version)
21 validate(nb, version=version)
22 except ValidationError as e:
22 except ValidationError as e:
23 get_logger().error("Notebook JSON is not valid v%i: %s", version, e)
23 get_logger().error("Notebook JSON is not valid v%i: %s", version, e)
24
24
25 def upgrade(nb, from_version=3, from_minor=0):
25 def upgrade(nb, from_version=3, from_minor=0):
26 """Convert a notebook to v4.
26 """Convert a notebook to v4.
27
27
28 Parameters
28 Parameters
29 ----------
29 ----------
30 nb : NotebookNode
30 nb : NotebookNode
31 The Python representation of the notebook to convert.
31 The Python representation of the notebook to convert.
32 from_version : int
32 from_version : int
33 The original version of the notebook to convert.
33 The original version of the notebook to convert.
34 from_minor : int
34 from_minor : int
35 The original minor version of the notebook to convert (only relevant for v >= 3).
35 The original minor version of the notebook to convert (only relevant for v >= 3).
36 """
36 """
37 if from_version == 3:
37 if from_version == 3:
38 # Validate the notebook before conversion
38 # Validate the notebook before conversion
39 _warn_if_invalid(nb, from_version)
39 _warn_if_invalid(nb, from_version)
40
40
41 # Mark the original nbformat so consumers know it has been converted
41 # Mark the original nbformat so consumers know it has been converted
42 orig_nbformat = nb.pop('orig_nbformat', None)
42 orig_nbformat = nb.pop('orig_nbformat', None)
43 nb.metadata.orig_nbformat = orig_nbformat or 3
43 nb.metadata.orig_nbformat = orig_nbformat or 3
44
44
45 # Mark the new format
45 # Mark the new format
46 nb.nbformat = nbformat
46 nb.nbformat = nbformat
47 nb.nbformat_minor = nbformat_minor
47 nb.nbformat_minor = nbformat_minor
48
48
49 # remove worksheet(s)
49 # remove worksheet(s)
50 nb['cells'] = cells = []
50 nb['cells'] = cells = []
51 # In the unlikely event of multiple worksheets,
51 # In the unlikely event of multiple worksheets,
52 # they will be flattened
52 # they will be flattened
53 for ws in nb.pop('worksheets', []):
53 for ws in nb.pop('worksheets', []):
54 # upgrade each cell
54 # upgrade each cell
55 for cell in ws['cells']:
55 for cell in ws['cells']:
56 cells.append(upgrade_cell(cell))
56 cells.append(upgrade_cell(cell))
57 # upgrade metadata
57 # upgrade metadata
58 nb.metadata.pop('name', '')
58 nb.metadata.pop('name', '')
59 nb.metadata.pop('signature', '')
59 nb.metadata.pop('signature', '')
60 # Validate the converted notebook before returning it
60 # Validate the converted notebook before returning it
61 _warn_if_invalid(nb, nbformat)
61 _warn_if_invalid(nb, nbformat)
62 return nb
62 return nb
63 elif from_version == 4:
63 elif from_version == 4:
64 # nothing to do
64 # nothing to do
65 if from_minor != nbformat_minor:
65 if from_minor != nbformat_minor:
66 nb.metadata.orig_nbformat_minor = from_minor
66 nb.metadata.orig_nbformat_minor = from_minor
67 nb.nbformat_minor = nbformat_minor
67 nb.nbformat_minor = nbformat_minor
68
68
69 return nb
69 return nb
70 else:
70 else:
71 raise ValueError('Cannot convert a notebook directly from v%s to v4. ' \
71 raise ValueError('Cannot convert a notebook directly from v%s to v4. ' \
72 'Try using the IPython.nbformat.convert module.' % from_version)
72 'Try using the IPython.nbformat.convert module.' % from_version)
73
73
74 def upgrade_cell(cell):
74 def upgrade_cell(cell):
75 """upgrade a cell from v3 to v4
75 """upgrade a cell from v3 to v4
76
76
77 heading cell:
77 heading cell:
78 - -> markdown heading
78 - -> markdown heading
79 code cell:
79 code cell:
80 - remove language metadata
80 - remove language metadata
81 - cell.input -> cell.source
81 - cell.input -> cell.source
82 - cell.prompt_number -> cell.execution_count
82 - cell.prompt_number -> cell.execution_count
83 - update outputs
83 - update outputs
84 """
84 """
85 cell.setdefault('metadata', NotebookNode())
85 cell.setdefault('metadata', NotebookNode())
86 if cell.cell_type == 'code':
86 if cell.cell_type == 'code':
87 cell.pop('language', '')
87 cell.pop('language', '')
88 if 'collapsed' in cell:
88 if 'collapsed' in cell:
89 cell.metadata['collapsed'] = cell.pop('collapsed')
89 cell.metadata['collapsed'] = cell.pop('collapsed')
90 cell.source = cell.pop('input', '')
90 cell.source = cell.pop('input', '')
91 cell.execution_count = cell.pop('prompt_number', None)
91 cell.execution_count = cell.pop('prompt_number', None)
92 cell.outputs = upgrade_outputs(cell.outputs)
92 cell.outputs = upgrade_outputs(cell.outputs)
93 elif cell.cell_type == 'heading':
93 elif cell.cell_type == 'heading':
94 cell.cell_type = 'markdown'
94 cell.cell_type = 'markdown'
95 level = cell.pop('level', 1)
95 level = cell.pop('level', 1)
96 cell.source = u'{hashes} {single_line}'.format(
96 cell.source = u'{hashes} {single_line}'.format(
97 hashes='#' * level,
97 hashes='#' * level,
98 single_line = ' '.join(cell.get('source', '').splitlines()),
98 single_line = ' '.join(cell.get('source', '').splitlines()),
99 )
99 )
100 elif cell.cell_type == 'html':
100 elif cell.cell_type == 'html':
101 # Technically, this exists. It will never happen in practice.
101 # Technically, this exists. It will never happen in practice.
102 cell.cell_type = 'markdown'
102 cell.cell_type = 'markdown'
103 return cell
103 return cell
104
104
105 def downgrade_cell(cell):
105 def downgrade_cell(cell):
106 """downgrade a cell from v4 to v3
106 """downgrade a cell from v4 to v3
107
107
108 code cell:
108 code cell:
109 - set cell.language
109 - set cell.language
110 - cell.input <- cell.source
110 - cell.input <- cell.source
111 - cell.prompt_number <- cell.execution_count
111 - cell.prompt_number <- cell.execution_count
112 - update outputs
112 - update outputs
113 markdown cell:
113 markdown cell:
114 - single-line heading -> heading cell
114 - single-line heading -> heading cell
115 """
115 """
116 if cell.cell_type == 'code':
116 if cell.cell_type == 'code':
117 cell.language = 'python'
117 cell.language = 'python'
118 cell.input = cell.pop('source', '')
118 cell.input = cell.pop('source', '')
119 cell.prompt_number = cell.pop('execution_count', None)
119 cell.prompt_number = cell.pop('execution_count', None)
120 cell.collapsed = cell.metadata.pop('collapsed', False)
120 cell.collapsed = cell.metadata.pop('collapsed', False)
121 cell.outputs = downgrade_outputs(cell.outputs)
121 cell.outputs = downgrade_outputs(cell.outputs)
122 elif cell.cell_type == 'markdown':
122 elif cell.cell_type == 'markdown':
123 source = cell.get('source', '')
123 source = cell.get('source', '')
124 if '\n' not in source and source.startswith('#'):
124 if '\n' not in source and source.startswith('#'):
125 prefix, text = re.match(r'(#+)\s*(.*)', source).groups()
125 prefix, text = re.match(r'(#+)\s*(.*)', source).groups()
126 cell.cell_type = 'heading'
126 cell.cell_type = 'heading'
127 cell.source = text
127 cell.source = text
128 cell.level = len(prefix)
128 cell.level = len(prefix)
129 return cell
129 return cell
130
130
131 _mime_map = {
131 _mime_map = {
132 "text" : "text/plain",
132 "text" : "text/plain",
133 "html" : "text/html",
133 "html" : "text/html",
134 "svg" : "image/svg+xml",
134 "svg" : "image/svg+xml",
135 "png" : "image/png",
135 "png" : "image/png",
136 "jpeg" : "image/jpeg",
136 "jpeg" : "image/jpeg",
137 "latex" : "text/latex",
137 "latex" : "text/latex",
138 "json" : "application/json",
138 "json" : "application/json",
139 "javascript" : "application/javascript",
139 "javascript" : "application/javascript",
140 };
140 };
141
141
142 def to_mime_key(d):
142 def to_mime_key(d):
143 """convert dict with v3 aliases to plain mime-type keys"""
143 """convert dict with v3 aliases to plain mime-type keys"""
144 for alias, mime in _mime_map.items():
144 for alias, mime in _mime_map.items():
145 if alias in d:
145 if alias in d:
146 d[mime] = d.pop(alias)
146 d[mime] = d.pop(alias)
147 return d
147 return d
148
148
149 def from_mime_key(d):
149 def from_mime_key(d):
150 """convert dict with mime-type keys to v3 aliases"""
150 """convert dict with mime-type keys to v3 aliases"""
151 for alias, mime in _mime_map.items():
151 for alias, mime in _mime_map.items():
152 if mime in d:
152 if mime in d:
153 d[alias] = d.pop(mime)
153 d[alias] = d.pop(mime)
154 return d
154 return d
155
155
156 def upgrade_output(output):
156 def upgrade_output(output):
157 """upgrade a single code cell output from v3 to v4
157 """upgrade a single code cell output from v3 to v4
158
158
159 - pyout -> execute_result
159 - pyout -> execute_result
160 - pyerr -> error
160 - pyerr -> error
161 - output.type -> output.data.mime/type
161 - output.type -> output.data.mime/type
162 - mime-type keys
162 - mime-type keys
163 - stream.stream -> stream.name
163 - stream.stream -> stream.name
164 """
164 """
165 if output['output_type'] in {'pyout', 'display_data'}:
165 if output['output_type'] in {'pyout', 'display_data'}:
166 output.setdefault('metadata', NotebookNode())
166 output.setdefault('metadata', NotebookNode())
167 if output['output_type'] == 'pyout':
167 if output['output_type'] == 'pyout':
168 output['output_type'] = 'execute_result'
168 output['output_type'] = 'execute_result'
169 output['execution_count'] = output.pop('prompt_number', None)
169 output['execution_count'] = output.pop('prompt_number', None)
170
170
171 # move output data into data sub-dict
171 # move output data into data sub-dict
172 data = {}
172 data = {}
173 for key in list(output):
173 for key in list(output):
174 if key in {'output_type', 'execution_count', 'metadata'}:
174 if key in {'output_type', 'execution_count', 'metadata'}:
175 continue
175 continue
176 data[key] = output.pop(key)
176 data[key] = output.pop(key)
177 to_mime_key(data)
177 to_mime_key(data)
178 output['data'] = data
178 output['data'] = data
179 to_mime_key(output.metadata)
179 to_mime_key(output.metadata)
180 if 'application/json' in data:
180 if 'application/json' in data:
181 data['application/json'] = json.loads(data['application/json'])
181 data['application/json'] = json.loads(data['application/json'])
182 # promote ascii bytes (from v2) to unicode
182 # promote ascii bytes (from v2) to unicode
183 for key in ('image/png', 'image/jpeg'):
183 for key in ('image/png', 'image/jpeg'):
184 if key in data and isinstance(data[key], bytes):
184 if key in data and isinstance(data[key], bytes):
185 data[key] = data[key].decode('ascii')
185 data[key] = data[key].decode('ascii')
186 elif output['output_type'] == 'pyerr':
186 elif output['output_type'] == 'pyerr':
187 output['output_type'] = 'error'
187 output['output_type'] = 'error'
188 elif output['output_type'] == 'stream':
188 elif output['output_type'] == 'stream':
189 output['name'] = output.pop('stream')
189 output['name'] = output.pop('stream', 'stdout')
190 return output
190 return output
191
191
192 def downgrade_output(output):
192 def downgrade_output(output):
193 """downgrade a single code cell output to v3 from v4
193 """downgrade a single code cell output to v3 from v4
194
194
195 - pyout <- execute_result
195 - pyout <- execute_result
196 - pyerr <- error
196 - pyerr <- error
197 - output.data.mime/type -> output.type
197 - output.data.mime/type -> output.type
198 - un-mime-type keys
198 - un-mime-type keys
199 - stream.stream <- stream.name
199 - stream.stream <- stream.name
200 """
200 """
201 if output['output_type'] in {'execute_result', 'display_data'}:
201 if output['output_type'] in {'execute_result', 'display_data'}:
202 if output['output_type'] == 'execute_result':
202 if output['output_type'] == 'execute_result':
203 output['output_type'] = 'pyout'
203 output['output_type'] = 'pyout'
204 output['prompt_number'] = output.pop('execution_count', None)
204 output['prompt_number'] = output.pop('execution_count', None)
205
205
206 # promote data dict to top-level output namespace
206 # promote data dict to top-level output namespace
207 data = output.pop('data', {})
207 data = output.pop('data', {})
208 if 'application/json' in data:
208 if 'application/json' in data:
209 data['application/json'] = json.dumps(data['application/json'])
209 data['application/json'] = json.dumps(data['application/json'])
210 from_mime_key(data)
210 from_mime_key(data)
211 output.update(data)
211 output.update(data)
212 from_mime_key(output.get('metadata', {}))
212 from_mime_key(output.get('metadata', {}))
213 elif output['output_type'] == 'error':
213 elif output['output_type'] == 'error':
214 output['output_type'] = 'pyerr'
214 output['output_type'] = 'pyerr'
215 elif output['output_type'] == 'stream':
215 elif output['output_type'] == 'stream':
216 output['stream'] = output.pop('name')
216 output['stream'] = output.pop('name')
217 return output
217 return output
218
218
219 def upgrade_outputs(outputs):
219 def upgrade_outputs(outputs):
220 """upgrade outputs of a code cell from v3 to v4"""
220 """upgrade outputs of a code cell from v3 to v4"""
221 return [upgrade_output(op) for op in outputs]
221 return [upgrade_output(op) for op in outputs]
222
222
223 def downgrade_outputs(outputs):
223 def downgrade_outputs(outputs):
224 """downgrade outputs of a code cell to v3 from v4"""
224 """downgrade outputs of a code cell to v3 from v4"""
225 return [downgrade_output(op) for op in outputs]
225 return [downgrade_output(op) for op in outputs]
226
226
227 def downgrade(nb):
227 def downgrade(nb):
228 """Convert a v4 notebook to v3.
228 """Convert a v4 notebook to v3.
229
229
230 Parameters
230 Parameters
231 ----------
231 ----------
232 nb : NotebookNode
232 nb : NotebookNode
233 The Python representation of the notebook to convert.
233 The Python representation of the notebook to convert.
234 """
234 """
235 if nb.nbformat != nbformat:
235 if nb.nbformat != nbformat:
236 return nb
236 return nb
237
237
238 # Validate the notebook before conversion
238 # Validate the notebook before conversion
239 _warn_if_invalid(nb, nbformat)
239 _warn_if_invalid(nb, nbformat)
240
240
241 nb.nbformat = v3.nbformat
241 nb.nbformat = v3.nbformat
242 nb.nbformat_minor = v3.nbformat_minor
242 nb.nbformat_minor = v3.nbformat_minor
243 cells = [ downgrade_cell(cell) for cell in nb.pop('cells') ]
243 cells = [ downgrade_cell(cell) for cell in nb.pop('cells') ]
244 nb.worksheets = [v3.new_worksheet(cells=cells)]
244 nb.worksheets = [v3.new_worksheet(cells=cells)]
245 nb.metadata.setdefault('name', '')
245 nb.metadata.setdefault('name', '')
246
246
247 # Validate the converted notebook before returning it
247 # Validate the converted notebook before returning it
248 _warn_if_invalid(nb, v3.nbformat)
248 _warn_if_invalid(nb, v3.nbformat)
249
249
250 nb.orig_nbformat = nb.metadata.pop('orig_nbformat', nbformat)
250 nb.orig_nbformat = nb.metadata.pop('orig_nbformat', nbformat)
251 nb.orig_nbformat_minor = nb.metadata.pop('orig_nbformat_minor', nbformat_minor)
251 nb.orig_nbformat_minor = nb.metadata.pop('orig_nbformat_minor', nbformat_minor)
252
252
253 return nb
253 return nb
General Comments 0
You need to be logged in to leave comments. Login now