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