1"""The basic dict based notebook format.
2
3The Python representation of a notebook is a nested structure of
4dictionary subclasses that support attribute access.
5The functions in this module are merely
6helpers to build the structs in the right form.
7"""
8
9# Copyright (c) IPython Development Team.
10# Distributed under the terms of the Modified BSD License.
11from __future__ import annotations
12
13import warnings
14
15from nbformat._struct import Struct
16
17# -----------------------------------------------------------------------------
18# Code
19# -----------------------------------------------------------------------------
20
21# Change this when incrementing the nbformat version
22nbformat = 3
23nbformat_minor = 0
24nbformat_schema = {(3, 0): "nbformat.v3.schema.json"}
25
26
27class NotebookNode(Struct):
28 """A notebook node object."""
29
30
31def from_dict(d):
32 """Create notebook node(s) from an object."""
33 if isinstance(d, dict):
34 newd = NotebookNode()
35 for k, v in d.items():
36 newd[k] = from_dict(v)
37 return newd
38 if isinstance(d, (tuple, list)):
39 return [from_dict(i) for i in d]
40 return d
41
42
43def str_passthrough(obj):
44 """
45 Used to be cast_unicode, add this temporarily to make sure no further breakage.
46 """
47 if not isinstance(obj, str):
48 raise AssertionError
49 return obj
50
51
52def cast_str(obj):
53 """Cast an object as a string."""
54 if isinstance(obj, bytes):
55 # really this should never happened, it should
56 # have been base64 encoded before.
57 warnings.warn(
58 "A notebook got bytes instead of likely base64 encoded values."
59 "The content will likely be corrupted.",
60 UserWarning,
61 stacklevel=3,
62 )
63 return obj.decode("ascii", "replace")
64 if not isinstance(obj, str):
65 raise AssertionError
66 return obj
67
68
69def new_output(
70 output_type,
71 output_text=None,
72 output_png=None,
73 output_html=None,
74 output_svg=None,
75 output_latex=None,
76 output_json=None,
77 output_javascript=None,
78 output_jpeg=None,
79 prompt_number=None,
80 ename=None,
81 evalue=None,
82 traceback=None,
83 stream=None,
84 metadata=None,
85):
86 """Create a new output, to go in the ``cell.outputs`` list of a code cell."""
87 output = NotebookNode()
88 output.output_type = str(output_type)
89
90 if metadata is None:
91 metadata = {}
92 if not isinstance(metadata, dict):
93 msg = "metadata must be dict"
94 raise TypeError(msg)
95
96 if output_type in {"pyout", "display_data"}:
97 output.metadata = metadata
98
99 if output_type != "pyerr":
100 if output_text is not None:
101 output.text = str_passthrough(output_text)
102 if output_png is not None:
103 output.png = cast_str(output_png)
104 if output_jpeg is not None:
105 output.jpeg = cast_str(output_jpeg)
106 if output_html is not None:
107 output.html = str_passthrough(output_html)
108 if output_svg is not None:
109 output.svg = str_passthrough(output_svg)
110 if output_latex is not None:
111 output.latex = str_passthrough(output_latex)
112 if output_json is not None:
113 output.json = str_passthrough(output_json)
114 if output_javascript is not None:
115 output.javascript = str_passthrough(output_javascript)
116
117 if output_type == "pyout" and prompt_number is not None:
118 output.prompt_number = int(prompt_number)
119
120 if output_type == "pyerr":
121 if ename is not None:
122 output.ename = str_passthrough(ename)
123 if evalue is not None:
124 output.evalue = str_passthrough(evalue)
125 if traceback is not None:
126 output.traceback = [str_passthrough(frame) for frame in list(traceback)]
127
128 if output_type == "stream":
129 output.stream = "stdout" if stream is None else str_passthrough(stream)
130
131 return output
132
133
134def new_code_cell(
135 input=None,
136 prompt_number=None,
137 outputs=None,
138 language="python",
139 collapsed=False,
140 metadata=None,
141):
142 """Create a new code cell with input and output"""
143 cell = NotebookNode()
144 cell.cell_type = "code"
145 if language is not None:
146 cell.language = str_passthrough(language)
147 if input is not None:
148 cell.input = str_passthrough(input)
149 if prompt_number is not None:
150 cell.prompt_number = int(prompt_number)
151 if outputs is None:
152 cell.outputs = []
153 else:
154 cell.outputs = outputs
155 if collapsed is not None:
156 cell.collapsed = bool(collapsed)
157 cell.metadata = NotebookNode(metadata or {})
158
159 return cell
160
161
162def new_text_cell(cell_type, source=None, rendered=None, metadata=None):
163 """Create a new text cell."""
164 cell = NotebookNode()
165 # VERSIONHACK: plaintext -> raw
166 # handle never-released plaintext name for raw cells
167 if cell_type == "plaintext":
168 cell_type = "raw"
169 if source is not None:
170 cell.source = str_passthrough(source)
171 cell.metadata = NotebookNode(metadata or {})
172 cell.cell_type = cell_type
173 return cell
174
175
176def new_heading_cell(source=None, level=1, rendered=None, metadata=None):
177 """Create a new section cell with a given integer level."""
178 cell = NotebookNode()
179 cell.cell_type = "heading"
180 if source is not None:
181 cell.source = str_passthrough(source)
182 cell.level = int(level)
183 cell.metadata = NotebookNode(metadata or {})
184 return cell
185
186
187def new_worksheet(name=None, cells=None, metadata=None):
188 """Create a worksheet by name with with a list of cells."""
189 ws = NotebookNode()
190 if cells is None:
191 ws.cells = []
192 else:
193 ws.cells = list(cells)
194 ws.metadata = NotebookNode(metadata or {})
195 return ws
196
197
198def new_notebook(name=None, metadata=None, worksheets=None):
199 """Create a notebook by name, id and a list of worksheets."""
200 nb = NotebookNode()
201 nb.nbformat = nbformat
202 nb.nbformat_minor = nbformat_minor
203 if worksheets is None:
204 nb.worksheets = []
205 else:
206 nb.worksheets = list(worksheets)
207 if metadata is None:
208 nb.metadata = new_metadata()
209 else:
210 nb.metadata = NotebookNode(metadata)
211 if name is not None:
212 nb.metadata.name = str_passthrough(name)
213 return nb
214
215
216def new_metadata(
217 name=None,
218 authors=None,
219 license=None,
220 created=None,
221 modified=None,
222 gistid=None,
223):
224 """Create a new metadata node."""
225 metadata = NotebookNode()
226 if name is not None:
227 metadata.name = str_passthrough(name)
228 if authors is not None:
229 metadata.authors = list(authors)
230 if created is not None:
231 metadata.created = str_passthrough(created)
232 if modified is not None:
233 metadata.modified = str_passthrough(modified)
234 if license is not None:
235 metadata.license = str_passthrough(license)
236 if gistid is not None:
237 metadata.gistid = str_passthrough(gistid)
238 return metadata
239
240
241def new_author(name=None, email=None, affiliation=None, url=None):
242 """Create a new author."""
243 author = NotebookNode()
244 if name is not None:
245 author.name = str_passthrough(name)
246 if email is not None:
247 author.email = str_passthrough(email)
248 if affiliation is not None:
249 author.affiliation = str_passthrough(affiliation)
250 if url is not None:
251 author.url = str_passthrough(url)
252 return author