Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/nbformat/v4/convert.py: 11%
152 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
1"""Code for converting notebooks to and from v3."""
3# Copyright (c) IPython Development Team.
4# Distributed under the terms of the Modified BSD License.
6import json
7import re
9from traitlets.log import get_logger
11from nbformat import v3, validator
13from .nbbase import NotebookNode, nbformat, nbformat_minor, random_cell_id
16def _warn_if_invalid(nb, version):
17 """Log validation errors, if there are any."""
18 from nbformat import ValidationError, validate
20 try:
21 validate(nb, version=version)
22 except ValidationError as e:
23 get_logger().error("Notebook JSON is not valid v%i: %s", version, e)
26def upgrade(nb, from_version=None, from_minor=None): # noqa
27 """Convert a notebook to latest v4.
29 Parameters
30 ----------
31 nb : NotebookNode
32 The Python representation of the notebook to convert.
33 from_version : int
34 The original version of the notebook to convert.
35 from_minor : int
36 The original minor version of the notebook to convert (only relevant for v >= 3).
37 """
38 if not from_version:
39 from_version = nb["nbformat"]
40 if not from_minor:
41 if "nbformat_minor" not in nb:
42 if from_version == 4: # noqa
43 msg = "The v4 notebook does not include the nbformat minor, which is needed."
44 raise validator.ValidationError(msg)
45 else:
46 from_minor = 0
47 else:
48 from_minor = nb["nbformat_minor"]
50 if from_version == 3: # noqa
51 # Validate the notebook before conversion
52 _warn_if_invalid(nb, from_version)
54 # Mark the original nbformat so consumers know it has been converted
55 orig_nbformat = nb.pop("orig_nbformat", None)
56 orig_nbformat_minor = nb.pop("orig_nbformat_minor", None)
57 nb.metadata.orig_nbformat = orig_nbformat or 3
58 nb.metadata.orig_nbformat_minor = orig_nbformat_minor or 0
60 # Mark the new format
61 nb.nbformat = nbformat
62 nb.nbformat_minor = nbformat_minor
64 # remove worksheet(s)
65 nb["cells"] = cells = []
66 # In the unlikely event of multiple worksheets,
67 # they will be flattened
68 for ws in nb.pop("worksheets", []):
69 # upgrade each cell
70 for cell in ws["cells"]:
71 cells.append(upgrade_cell(cell))
72 # upgrade metadata
73 nb.metadata.pop("name", "")
74 nb.metadata.pop("signature", "")
75 # Validate the converted notebook before returning it
76 _warn_if_invalid(nb, nbformat)
77 return nb
78 elif from_version == 4: # noqa
79 if from_minor == nbformat_minor:
80 return nb
82 # other versions migration code e.g.
83 # if from_minor < 3:
84 # if from_minor < 4:
86 if from_minor < 5: # noqa
87 for cell in nb.cells:
88 cell.id = random_cell_id()
90 nb.metadata.orig_nbformat_minor = from_minor
91 nb.nbformat_minor = nbformat_minor
93 return nb
94 else:
95 raise ValueError(
96 "Cannot convert a notebook directly from v%s to v4. "
97 "Try using the nbformat.convert module." % from_version
98 )
101def upgrade_cell(cell):
102 """upgrade a cell from v3 to v4
104 heading cell:
105 - -> markdown heading
106 code cell:
107 - remove language metadata
108 - cell.input -> cell.source
109 - cell.prompt_number -> cell.execution_count
110 - update outputs
111 """
112 cell.setdefault("metadata", NotebookNode())
113 cell.id = random_cell_id()
114 if cell.cell_type == "code":
115 cell.pop("language", "")
116 if "collapsed" in cell:
117 cell.metadata["collapsed"] = cell.pop("collapsed")
118 cell.source = cell.pop("input", "")
119 cell.execution_count = cell.pop("prompt_number", None)
120 cell.outputs = upgrade_outputs(cell.outputs)
121 elif cell.cell_type == "heading":
122 cell.cell_type = "markdown"
123 level = cell.pop("level", 1)
124 cell.source = "{hashes} {single_line}".format(
125 hashes="#" * level,
126 single_line=" ".join(cell.get("source", "").splitlines()),
127 )
128 elif cell.cell_type == "html":
129 # Technically, this exists. It will never happen in practice.
130 cell.cell_type = "markdown"
131 return cell
134def downgrade_cell(cell):
135 """downgrade a cell from v4 to v3
137 code cell:
138 - set cell.language
139 - cell.input <- cell.source
140 - cell.prompt_number <- cell.execution_count
141 - update outputs
142 markdown cell:
143 - single-line heading -> heading cell
144 """
145 if cell.cell_type == "code":
146 cell.language = "python"
147 cell.input = cell.pop("source", "")
148 cell.prompt_number = cell.pop("execution_count", None)
149 cell.collapsed = cell.metadata.pop("collapsed", False)
150 cell.outputs = downgrade_outputs(cell.outputs)
151 elif cell.cell_type == "markdown":
152 source = cell.get("source", "")
153 if "\n" not in source and source.startswith("#"):
154 match = re.match(r"(#+)\s*(.*)", source)
155 assert match is not None # noqa
156 prefix, text = match.groups()
157 cell.cell_type = "heading"
158 cell.source = text
159 cell.level = len(prefix)
160 cell.pop("id", None)
161 cell.pop("attachments", None)
162 return cell
165_mime_map = {
166 "text": "text/plain",
167 "html": "text/html",
168 "svg": "image/svg+xml",
169 "png": "image/png",
170 "jpeg": "image/jpeg",
171 "latex": "text/latex",
172 "json": "application/json",
173 "javascript": "application/javascript",
174}
177def to_mime_key(d):
178 """convert dict with v3 aliases to plain mime-type keys"""
179 for alias, mime in _mime_map.items():
180 if alias in d:
181 d[mime] = d.pop(alias)
182 return d
185def from_mime_key(d):
186 """convert dict with mime-type keys to v3 aliases"""
187 d2 = {}
188 for alias, mime in _mime_map.items():
189 if mime in d:
190 d2[alias] = d[mime]
191 return d2
194def upgrade_output(output):
195 """upgrade a single code cell output from v3 to v4
197 - pyout -> execute_result
198 - pyerr -> error
199 - output.type -> output.data.mime/type
200 - mime-type keys
201 - stream.stream -> stream.name
202 """
203 if output["output_type"] in {"pyout", "display_data"}:
204 output.setdefault("metadata", NotebookNode())
205 if output["output_type"] == "pyout":
206 output["output_type"] = "execute_result"
207 output["execution_count"] = output.pop("prompt_number", None)
209 # move output data into data sub-dict
210 data = {}
211 for key in list(output):
212 if key in {"output_type", "execution_count", "metadata"}:
213 continue
214 data[key] = output.pop(key)
215 to_mime_key(data)
216 output["data"] = data
217 to_mime_key(output.metadata)
218 if "application/json" in data:
219 data["application/json"] = json.loads(data["application/json"])
220 # promote ascii bytes (from v2) to unicode
221 for key in ("image/png", "image/jpeg"):
222 if key in data and isinstance(data[key], bytes):
223 data[key] = data[key].decode("ascii")
224 elif output["output_type"] == "pyerr":
225 output["output_type"] = "error"
226 elif output["output_type"] == "stream":
227 output["name"] = output.pop("stream", "stdout")
228 return output
231def downgrade_output(output):
232 """downgrade a single code cell output to v3 from v4
234 - pyout <- execute_result
235 - pyerr <- error
236 - output.data.mime/type -> output.type
237 - un-mime-type keys
238 - stream.stream <- stream.name
239 """
240 if output["output_type"] in {"execute_result", "display_data"}:
241 if output["output_type"] == "execute_result":
242 output["output_type"] = "pyout"
243 output["prompt_number"] = output.pop("execution_count", None)
245 # promote data dict to top-level output namespace
246 data = output.pop("data", {})
247 if "application/json" in data:
248 data["application/json"] = json.dumps(data["application/json"])
249 data = from_mime_key(data)
250 output.update(data)
251 from_mime_key(output.get("metadata", {}))
252 elif output["output_type"] == "error":
253 output["output_type"] = "pyerr"
254 elif output["output_type"] == "stream":
255 output["stream"] = output.pop("name")
256 return output
259def upgrade_outputs(outputs):
260 """upgrade outputs of a code cell from v3 to v4"""
261 return [upgrade_output(op) for op in outputs]
264def downgrade_outputs(outputs):
265 """downgrade outputs of a code cell to v3 from v4"""
266 return [downgrade_output(op) for op in outputs]
269def downgrade(nb):
270 """Convert a v4 notebook to v3.
272 Parameters
273 ----------
274 nb : NotebookNode
275 The Python representation of the notebook to convert.
276 """
277 if nb.nbformat != nbformat:
278 return nb
280 # Validate the notebook before conversion
281 _warn_if_invalid(nb, nbformat)
283 nb.nbformat = v3.nbformat
284 nb.nbformat_minor = v3.nbformat_minor
285 cells = [downgrade_cell(cell) for cell in nb.pop("cells")]
286 nb.worksheets = [v3.new_worksheet(cells=cells)]
287 nb.metadata.setdefault("name", "")
289 # Validate the converted notebook before returning it
290 _warn_if_invalid(nb, v3.nbformat)
292 nb.orig_nbformat = nb.metadata.pop("orig_nbformat", nbformat)
293 nb.orig_nbformat_minor = nb.metadata.pop("orig_nbformat_minor", nbformat_minor)
295 return nb