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

1"""Code for converting notebooks to and from v3.""" 

2 

3# Copyright (c) IPython Development Team. 

4# Distributed under the terms of the Modified BSD License. 

5 

6import json 

7import re 

8 

9from traitlets.log import get_logger 

10 

11from nbformat import v3, validator 

12 

13from .nbbase import NotebookNode, nbformat, nbformat_minor, random_cell_id 

14 

15 

16def _warn_if_invalid(nb, version): 

17 """Log validation errors, if there are any.""" 

18 from nbformat import ValidationError, validate 

19 

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) 

24 

25 

26def upgrade(nb, from_version=None, from_minor=None): # noqa 

27 """Convert a notebook to latest v4. 

28 

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"] 

49 

50 if from_version == 3: # noqa 

51 # Validate the notebook before conversion 

52 _warn_if_invalid(nb, from_version) 

53 

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 

59 

60 # Mark the new format 

61 nb.nbformat = nbformat 

62 nb.nbformat_minor = nbformat_minor 

63 

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 

81 

82 # other versions migration code e.g. 

83 # if from_minor < 3: 

84 # if from_minor < 4: 

85 

86 if from_minor < 5: # noqa 

87 for cell in nb.cells: 

88 cell.id = random_cell_id() 

89 

90 nb.metadata.orig_nbformat_minor = from_minor 

91 nb.nbformat_minor = nbformat_minor 

92 

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 ) 

99 

100 

101def upgrade_cell(cell): 

102 """upgrade a cell from v3 to v4 

103 

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 

132 

133 

134def downgrade_cell(cell): 

135 """downgrade a cell from v4 to v3 

136 

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 

163 

164 

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} 

175 

176 

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 

183 

184 

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 

192 

193 

194def upgrade_output(output): 

195 """upgrade a single code cell output from v3 to v4 

196 

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) 

208 

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 

229 

230 

231def downgrade_output(output): 

232 """downgrade a single code cell output to v3 from v4 

233 

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) 

244 

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 

257 

258 

259def upgrade_outputs(outputs): 

260 """upgrade outputs of a code cell from v3 to v4""" 

261 return [upgrade_output(op) for op in outputs] 

262 

263 

264def downgrade_outputs(outputs): 

265 """downgrade outputs of a code cell to v3 from v4""" 

266 return [downgrade_output(op) for op in outputs] 

267 

268 

269def downgrade(nb): 

270 """Convert a v4 notebook to v3. 

271 

272 Parameters 

273 ---------- 

274 nb : NotebookNode 

275 The Python representation of the notebook to convert. 

276 """ 

277 if nb.nbformat != nbformat: 

278 return nb 

279 

280 # Validate the notebook before conversion 

281 _warn_if_invalid(nb, nbformat) 

282 

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", "") 

288 

289 # Validate the converted notebook before returning it 

290 _warn_if_invalid(nb, v3.nbformat) 

291 

292 nb.orig_nbformat = nb.metadata.pop("orig_nbformat", nbformat) 

293 nb.orig_nbformat_minor = nb.metadata.pop("orig_nbformat_minor", nbformat_minor) 

294 

295 return nb