##// END OF EJS Templates
convert v3->v4: only set collapsed metadata if value is defined
MinRK -
Show More
@@ -1,248 +1,249 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.current import validate, ValidationError
19 from IPython.nbformat.current 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 # Validate the converted notebook before returning it
59 # Validate the converted notebook before returning it
60 _warn_if_invalid(nb, nbformat)
60 _warn_if_invalid(nb, nbformat)
61 return nb
61 return nb
62 elif from_version == 4:
62 elif from_version == 4:
63 # nothing to do
63 # nothing to do
64 if from_minor != nbformat_minor:
64 if from_minor != nbformat_minor:
65 nb.metadata.orig_nbformat_minor = from_minor
65 nb.metadata.orig_nbformat_minor = from_minor
66 nb.nbformat_minor = nbformat_minor
66 nb.nbformat_minor = nbformat_minor
67
67
68 return nb
68 return nb
69 else:
69 else:
70 raise ValueError('Cannot convert a notebook directly from v%s to v4. ' \
70 raise ValueError('Cannot convert a notebook directly from v%s to v4. ' \
71 'Try using the IPython.nbformat.convert module.' % from_version)
71 'Try using the IPython.nbformat.convert module.' % from_version)
72
72
73 def upgrade_cell(cell):
73 def upgrade_cell(cell):
74 """upgrade a cell from v3 to v4
74 """upgrade a cell from v3 to v4
75
75
76 heading cell -> markdown heading
76 heading cell -> markdown heading
77 code cell:
77 code cell:
78 - remove language metadata
78 - remove language metadata
79 - cell.input -> cell.source
79 - cell.input -> cell.source
80 - cell.prompt_number -> cell.execution_count
80 - cell.prompt_number -> cell.execution_count
81 - update outputs
81 - update outputs
82 """
82 """
83 cell.setdefault('metadata', NotebookNode())
83 cell.setdefault('metadata', NotebookNode())
84 if cell.cell_type == 'code':
84 if cell.cell_type == 'code':
85 cell.pop('language', '')
85 cell.pop('language', '')
86 cell.metadata.collapsed = cell.pop('collapsed')
86 if 'collapsed' in cell:
87 cell.metadata['collapsed'] = cell.pop('collapsed')
87 cell.source = cell.pop('input', '')
88 cell.source = cell.pop('input', '')
88 cell.execution_count = cell.pop('prompt_number', None)
89 cell.execution_count = cell.pop('prompt_number', None)
89 cell.outputs = upgrade_outputs(cell.outputs)
90 cell.outputs = upgrade_outputs(cell.outputs)
90 elif cell.cell_type == 'heading':
91 elif cell.cell_type == 'heading':
91 cell.cell_type = 'markdown'
92 cell.cell_type = 'markdown'
92 level = cell.pop('level', 1)
93 level = cell.pop('level', 1)
93 cell.source = '{hashes} {single_line}'.format(
94 cell.source = '{hashes} {single_line}'.format(
94 hashes='#' * level,
95 hashes='#' * level,
95 single_line = ' '.join(cell.get('source', '').splitlines()),
96 single_line = ' '.join(cell.get('source', '').splitlines()),
96 )
97 )
97 elif cell.cell_type == 'html':
98 elif cell.cell_type == 'html':
98 # Technically, this exists. It will never happen in practice.
99 # Technically, this exists. It will never happen in practice.
99 cell.cell_type = 'markdown'
100 cell.cell_type = 'markdown'
100 return cell
101 return cell
101
102
102 def downgrade_cell(cell):
103 def downgrade_cell(cell):
103 """downgrade a cell from v4 to v3
104 """downgrade a cell from v4 to v3
104
105
105 code cell:
106 code cell:
106 - set cell.language
107 - set cell.language
107 - cell.input <- cell.source
108 - cell.input <- cell.source
108 - cell.prompt_number <- cell.execution_count
109 - cell.prompt_number <- cell.execution_count
109 - update outputs
110 - update outputs
110 markdown cell:
111 markdown cell:
111 - single-line heading -> heading cell
112 - single-line heading -> heading cell
112 """
113 """
113 if cell.cell_type == 'code':
114 if cell.cell_type == 'code':
114 cell.language = 'python'
115 cell.language = 'python'
115 cell.input = cell.pop('source', '')
116 cell.input = cell.pop('source', '')
116 cell.prompt_number = cell.pop('execution_count', None)
117 cell.prompt_number = cell.pop('execution_count', None)
117 cell.collapsed = cell.metadata.pop('collapsed', False)
118 cell.collapsed = cell.metadata.pop('collapsed', False)
118 cell.outputs = downgrade_outputs(cell.outputs)
119 cell.outputs = downgrade_outputs(cell.outputs)
119 elif cell.cell_type == 'markdown':
120 elif cell.cell_type == 'markdown':
120 source = cell.get('source', '')
121 source = cell.get('source', '')
121 if '\n' not in source and source.startswith('#'):
122 if '\n' not in source and source.startswith('#'):
122 prefix, text = re.match(r'(#+)\s*(.*)', source).groups()
123 prefix, text = re.match(r'(#+)\s*(.*)', source).groups()
123 cell.cell_type = 'heading'
124 cell.cell_type = 'heading'
124 cell.source = text
125 cell.source = text
125 cell.level = len(prefix)
126 cell.level = len(prefix)
126 return cell
127 return cell
127
128
128 _mime_map = {
129 _mime_map = {
129 "text" : "text/plain",
130 "text" : "text/plain",
130 "html" : "text/html",
131 "html" : "text/html",
131 "svg" : "image/svg+xml",
132 "svg" : "image/svg+xml",
132 "png" : "image/png",
133 "png" : "image/png",
133 "jpeg" : "image/jpeg",
134 "jpeg" : "image/jpeg",
134 "latex" : "text/latex",
135 "latex" : "text/latex",
135 "json" : "application/json",
136 "json" : "application/json",
136 "javascript" : "application/javascript",
137 "javascript" : "application/javascript",
137 };
138 };
138
139
139 def to_mime_key(d):
140 def to_mime_key(d):
140 """convert dict with v3 aliases to plain mime-type keys"""
141 """convert dict with v3 aliases to plain mime-type keys"""
141 for alias, mime in _mime_map.items():
142 for alias, mime in _mime_map.items():
142 if alias in d:
143 if alias in d:
143 d[mime] = d.pop(alias)
144 d[mime] = d.pop(alias)
144 return d
145 return d
145
146
146 def from_mime_key(d):
147 def from_mime_key(d):
147 """convert dict with mime-type keys to v3 aliases"""
148 """convert dict with mime-type keys to v3 aliases"""
148 for alias, mime in _mime_map.items():
149 for alias, mime in _mime_map.items():
149 if mime in d:
150 if mime in d:
150 d[alias] = d.pop(mime)
151 d[alias] = d.pop(mime)
151 return d
152 return d
152
153
153 def upgrade_output(output):
154 def upgrade_output(output):
154 """upgrade a single code cell output from v3 to v4
155 """upgrade a single code cell output from v3 to v4
155
156
156 - pyout -> execute_result
157 - pyout -> execute_result
157 - pyerr -> error
158 - pyerr -> error
158 - output.type -> output.data.mime/type
159 - output.type -> output.data.mime/type
159 - mime-type keys
160 - mime-type keys
160 - stream.stream -> stream.name
161 - stream.stream -> stream.name
161 """
162 """
162 if output['output_type'] in {'pyout', 'display_data'}:
163 if output['output_type'] in {'pyout', 'display_data'}:
163 output.setdefault('metadata', NotebookNode())
164 output.setdefault('metadata', NotebookNode())
164 if output['output_type'] == 'pyout':
165 if output['output_type'] == 'pyout':
165 output['output_type'] = 'execute_result'
166 output['output_type'] = 'execute_result'
166 output['execution_count'] = output.pop('prompt_number', None)
167 output['execution_count'] = output.pop('prompt_number', None)
167
168
168 # move output data into data sub-dict
169 # move output data into data sub-dict
169 data = {}
170 data = {}
170 for key in list(output):
171 for key in list(output):
171 if key in {'output_type', 'execution_count', 'metadata'}:
172 if key in {'output_type', 'execution_count', 'metadata'}:
172 continue
173 continue
173 data[key] = output.pop(key)
174 data[key] = output.pop(key)
174 to_mime_key(data)
175 to_mime_key(data)
175 output['data'] = data
176 output['data'] = data
176 to_mime_key(output.metadata)
177 to_mime_key(output.metadata)
177 if 'application/json' in data:
178 if 'application/json' in data:
178 data['application/json'] = json.loads(data['application/json'])
179 data['application/json'] = json.loads(data['application/json'])
179 # promote ascii bytes (from v2) to unicode
180 # promote ascii bytes (from v2) to unicode
180 for key in ('image/png', 'image/jpeg'):
181 for key in ('image/png', 'image/jpeg'):
181 if key in data and isinstance(data[key], bytes):
182 if key in data and isinstance(data[key], bytes):
182 data[key] = data[key].decode('ascii')
183 data[key] = data[key].decode('ascii')
183 elif output['output_type'] == 'pyerr':
184 elif output['output_type'] == 'pyerr':
184 output['output_type'] = 'error'
185 output['output_type'] = 'error'
185 elif output['output_type'] == 'stream':
186 elif output['output_type'] == 'stream':
186 output['name'] = output.pop('stream')
187 output['name'] = output.pop('stream')
187 return output
188 return output
188
189
189 def downgrade_output(output):
190 def downgrade_output(output):
190 """downgrade a single code cell output to v3 from v4
191 """downgrade a single code cell output to v3 from v4
191
192
192 - pyout <- execute_result
193 - pyout <- execute_result
193 - pyerr <- error
194 - pyerr <- error
194 - output.data.mime/type -> output.type
195 - output.data.mime/type -> output.type
195 - un-mime-type keys
196 - un-mime-type keys
196 - stream.stream <- stream.name
197 - stream.stream <- stream.name
197 """
198 """
198 if output['output_type'] in {'execute_result', 'display_data'}:
199 if output['output_type'] in {'execute_result', 'display_data'}:
199 if output['output_type'] == 'execute_result':
200 if output['output_type'] == 'execute_result':
200 output['output_type'] = 'pyout'
201 output['output_type'] = 'pyout'
201 output['prompt_number'] = output.pop('execution_count', None)
202 output['prompt_number'] = output.pop('execution_count', None)
202
203
203 # promote data dict to top-level output namespace
204 # promote data dict to top-level output namespace
204 data = output.pop('data', {})
205 data = output.pop('data', {})
205 if 'application/json' in data:
206 if 'application/json' in data:
206 data['application/json'] = json.dumps(data['application/json'])
207 data['application/json'] = json.dumps(data['application/json'])
207 from_mime_key(data)
208 from_mime_key(data)
208 output.update(data)
209 output.update(data)
209 from_mime_key(output.get('metadata', {}))
210 from_mime_key(output.get('metadata', {}))
210 elif output['output_type'] == 'error':
211 elif output['output_type'] == 'error':
211 output['output_type'] = 'pyerr'
212 output['output_type'] = 'pyerr'
212 elif output['output_type'] == 'stream':
213 elif output['output_type'] == 'stream':
213 output['stream'] = output.pop('name')
214 output['stream'] = output.pop('name')
214 return output
215 return output
215
216
216 def upgrade_outputs(outputs):
217 def upgrade_outputs(outputs):
217 """upgrade outputs of a code cell from v3 to v4"""
218 """upgrade outputs of a code cell from v3 to v4"""
218 return [upgrade_output(op) for op in outputs]
219 return [upgrade_output(op) for op in outputs]
219
220
220 def downgrade_outputs(outputs):
221 def downgrade_outputs(outputs):
221 """downgrade outputs of a code cell to v3 from v4"""
222 """downgrade outputs of a code cell to v3 from v4"""
222 return [downgrade_output(op) for op in outputs]
223 return [downgrade_output(op) for op in outputs]
223
224
224 def downgrade(nb):
225 def downgrade(nb):
225 """Convert a v4 notebook to v3.
226 """Convert a v4 notebook to v3.
226
227
227 Parameters
228 Parameters
228 ----------
229 ----------
229 nb : NotebookNode
230 nb : NotebookNode
230 The Python representation of the notebook to convert.
231 The Python representation of the notebook to convert.
231 """
232 """
232 if nb.nbformat != nbformat:
233 if nb.nbformat != nbformat:
233 return nb
234 return nb
234
235
235 # Validate the notebook before conversion
236 # Validate the notebook before conversion
236 _warn_if_invalid(nb, nbformat)
237 _warn_if_invalid(nb, nbformat)
237
238
238 nb.nbformat = v3.nbformat
239 nb.nbformat = v3.nbformat
239 nb.nbformat_minor = v3.nbformat_minor
240 nb.nbformat_minor = v3.nbformat_minor
240 cells = [ downgrade_cell(cell) for cell in nb.pop('cells') ]
241 cells = [ downgrade_cell(cell) for cell in nb.pop('cells') ]
241 nb.worksheets = [v3.new_worksheet(cells=cells)]
242 nb.worksheets = [v3.new_worksheet(cells=cells)]
242 nb.metadata.setdefault('name', '')
243 nb.metadata.setdefault('name', '')
243 nb.metadata.pop('orig_nbformat', None)
244 nb.metadata.pop('orig_nbformat', None)
244 nb.metadata.pop('orig_nbformat_minor', None)
245 nb.metadata.pop('orig_nbformat_minor', None)
245
246
246 # Validate the converted notebook before returning it
247 # Validate the converted notebook before returning it
247 _warn_if_invalid(nb, v3.nbformat)
248 _warn_if_invalid(nb, v3.nbformat)
248 return nb
249 return nb
General Comments 0
You need to be logged in to leave comments. Login now