##// END OF EJS Templates
Merge pull request #6598 from minrk/nbformat-backport...
Matthias Bussonnier -
r18269:b95a72fb merge
parent child Browse files
Show More
@@ -0,0 +1,164 b''
1 {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
9 "name": ""
10 },
11 "nbformat": 3,
12 "nbformat_minor": 5,
13 "extra": 5,
14 "worksheets": [
15 {
16 "cells": [
17 {
18 "cell_type": "heading",
19 "level": 1,
20 "metadata": {},
21 "source": [
22 "nbconvert latex test"
23 ]
24 },
25 {
26 "cell_type": "markdown",
27 "extra_key": ["list"],
28 "metadata": {},
29 "source": [
30 "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis."
31 ]
32 },
33 {
34 "cell_type": "heading",
35 "level": 2,
36 "metadata": {},
37 "another_attr": {},
38 "source": [
39 "Printed Using Python"
40 ]
41 },
42 {
43 "cell_type": "code",
44 "collapsed": false,
45 "key": "value",
46 "input": [
47 "print(\"hello\")"
48 ],
49 "language": "python",
50 "metadata": {},
51 "outputs": [
52 {
53 "output_type": "stream",
54 "meta": 5,
55 "stream": "stdout",
56 "text": [
57 "hello\n"
58 ]
59 }
60 ],
61 "prompt_number": 1
62 },
63 {
64 "cell_type": "heading",
65 "level": 2,
66 "metadata": {},
67 "source": [
68 "Pyout"
69 ]
70 },
71 {
72 "cell_type": "code",
73 "collapsed": false,
74 "input": [
75 "from IPython.display import HTML\n",
76 "HTML(\"\"\"\n",
77 "<script>\n",
78 "console.log(\"hello\");\n",
79 "</script>\n",
80 "<b>HTML</b>\n",
81 "\"\"\")"
82 ],
83 "language": "python",
84 "metadata": {},
85 "outputs": [
86 {
87 "html": [
88 "\n",
89 "<script>\n",
90 "console.log(\"hello\");\n",
91 "</script>\n",
92 "<b>HTML</b>\n"
93 ],
94 "metadata": {},
95 "output_type": "pyout",
96 "key": "value",
97 "prompt_number": 3,
98 "text": [
99 "<IPython.core.display.HTML at 0x1112757d0>"
100 ]
101 }
102 ],
103 "prompt_number": 3
104 },
105 {
106 "cell_type": "code",
107 "collapsed": false,
108 "input": [
109 "%%javascript\n",
110 "console.log(\"hi\");"
111 ],
112 "language": "python",
113 "metadata": {},
114 "outputs": [
115 {
116 "javascript": [
117 "console.log(\"hi\");"
118 ],
119 "metadata": {},
120 "output_type": "display_data",
121 "text": [
122 "<IPython.core.display.Javascript at 0x1112b4b50>"
123 ]
124 }
125 ],
126 "prompt_number": 7
127 },
128 {
129 "cell_type": "heading",
130 "level": 3,
131 "metadata": {},
132 "source": [
133 "Image"
134 ]
135 },
136 {
137 "cell_type": "code",
138 "collapsed": false,
139 "input": [
140 "from IPython.display import Image\n",
141 "Image(\"http://ipython.org/_static/IPy_header.png\")"
142 ],
143 "language": "python",
144 "metadata": {},
145 "outputs": [
146 {
147 "metadata": {},
148 "output_type": "pyout",
149 "more": "data",
150 "png": "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\n",
151 "prompt_number": 6,
152 "text": [
153 "<IPython.core.display.Image at 0x111275490>"
154 ]
155 }
156 ],
157 "prompt_number": 6
158 }
159 ],
160 "keys": ["value", "s"],
161 "metadata": {}
162 }
163 ]
164 } No newline at end of file
@@ -0,0 +1,363 b''
1 {
2 "$schema": "http://json-schema.org/draft-04/schema#",
3 "description": "IPython Notebook v3.0 JSON schema.",
4 "type": "object",
5 "additionalProperties": false,
6 "required": ["metadata", "nbformat_minor", "nbformat", "worksheets"],
7 "properties": {
8 "metadata": {
9 "description": "Notebook root-level metadata.",
10 "type": "object",
11 "additionalProperties": true,
12 "properties": {
13 "kernel_info": {
14 "description": "Kernel information.",
15 "type": "object",
16 "required": ["name", "language"],
17 "properties": {
18 "name": {
19 "description": "Name of the kernel specification.",
20 "type": "string"
21 },
22 "language": {
23 "description": "The programming language which this kernel runs.",
24 "type": "string"
25 },
26 "codemirror_mode": {
27 "description": "The codemirror mode to use for code in this language.",
28 "type": "string"
29 }
30 }
31 },
32 "signature": {
33 "description": "Hash of the notebook.",
34 "type": "string"
35 }
36 }
37 },
38 "nbformat_minor": {
39 "description": "Notebook format (minor number). Incremented for backward compatible changes to the notebook format.",
40 "type": "integer",
41 "minimum": 0
42 },
43 "nbformat": {
44 "description": "Notebook format (major number). Incremented between backwards incompatible changes to the notebook format.",
45 "type": "integer",
46 "minimum": 3,
47 "maximum": 3
48 },
49 "orig_nbformat": {
50 "description": "Original notebook format (major number) before converting the notebook between versions.",
51 "type": "integer",
52 "minimum": 1
53 },
54 "worksheets" : {
55 "description": "Array of worksheets",
56 "type": "array",
57 "items": {"$ref": "#/definitions/worksheet"}
58 }
59 },
60
61 "definitions": {
62 "worksheet": {
63 "additionalProperties": false,
64 "required" : ["cells"],
65 "properties":{
66 "cells": {
67 "description": "Array of cells of the current notebook.",
68 "type": "array",
69 "items": {
70 "type": "object",
71 "oneOf": [
72 {"$ref": "#/definitions/raw_cell"},
73 {"$ref": "#/definitions/markdown_cell"},
74 {"$ref": "#/definitions/heading_cell"},
75 {"$ref": "#/definitions/code_cell"}
76 ]
77 }
78 },
79 "metadata": {
80 "type": "object",
81 "description": "metadata of the current worksheet"
82 }
83 }
84 },
85 "raw_cell": {
86 "description": "Notebook raw nbconvert cell.",
87 "type": "object",
88 "additionalProperties": false,
89 "required": ["cell_type", "source"],
90 "properties": {
91 "cell_type": {
92 "description": "String identifying the type of cell.",
93 "enum": ["raw"]
94 },
95 "metadata": {
96 "description": "Cell-level metadata.",
97 "type": "object",
98 "additionalProperties": true,
99 "properties": {
100 "format": {
101 "description": "Raw cell metadata format for nbconvert.",
102 "type": "string"
103 },
104 "name": {"$ref": "#/definitions/misc/metadata_name"},
105 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
106 }
107 },
108 "source": {"$ref": "#/definitions/misc/source"}
109 }
110 },
111
112 "markdown_cell": {
113 "description": "Notebook markdown cell.",
114 "type": "object",
115 "additionalProperties": false,
116 "required": ["cell_type", "source"],
117 "properties": {
118 "cell_type": {
119 "description": "String identifying the type of cell.",
120 "enum": ["markdown", "html"]
121 },
122 "metadata": {
123 "description": "Cell-level metadata.",
124 "type": "object",
125 "properties": {
126 "name": {"$ref": "#/definitions/misc/metadata_name"},
127 "tags": {"$ref": "#/definitions/misc/metadata_tags"}
128 },
129 "additionalProperties": true
130 },
131 "source": {"$ref": "#/definitions/misc/source"}
132 }
133 },
134
135 "heading_cell": {
136 "description": "Notebook heading cell.",
137 "type": "object",
138 "additionalProperties": false,
139 "required": ["cell_type", "source", "level"],
140 "properties": {
141 "cell_type": {
142 "description": "String identifying the type of cell.",
143 "enum": ["heading"]
144 },
145 "metadata": {
146 "description": "Cell-level metadata.",
147 "type": "object",
148 "additionalProperties": true
149 },
150 "source": {"$ref": "#/definitions/misc/source"},
151 "level": {
152 "description": "Level of heading cells.",
153 "type": "integer",
154 "minimum": 1
155 }
156 }
157 },
158
159 "code_cell": {
160 "description": "Notebook code cell.",
161 "type": "object",
162 "additionalProperties": false,
163 "required": ["cell_type", "input", "outputs", "language"],
164 "properties": {
165 "cell_type": {
166 "description": "String identifying the type of cell.",
167 "enum": ["code"]
168 },
169 "language": {
170 "description": "The cell's language (always Python)",
171 "type": "string"
172 },
173 "collapsed": {
174 "description": "Whether the cell is collapsed/expanded.",
175 "type": "boolean"
176 },
177 "metadata": {
178 "description": "Cell-level metadata.",
179 "type": "object",
180 "additionalProperties": true
181 },
182 "input": {"$ref": "#/definitions/misc/source"},
183 "outputs": {
184 "description": "Execution, display, or stream outputs.",
185 "type": "array",
186 "items": {"$ref": "#/definitions/output"}
187 },
188 "prompt_number": {
189 "description": "The code cell's prompt number. Will be null if the cell has not been run.",
190 "type": ["integer", "null"],
191 "minimum": 0
192 }
193 }
194 },
195 "output": {
196 "type": "object",
197 "oneOf": [
198 {"$ref": "#/definitions/pyout"},
199 {"$ref": "#/definitions/display_data"},
200 {"$ref": "#/definitions/stream"},
201 {"$ref": "#/definitions/pyerr"}
202 ]
203 },
204 "pyout": {
205 "description": "Result of executing a code cell.",
206 "type": "object",
207 "additionalProperties": false,
208 "required": ["output_type", "prompt_number"],
209 "properties": {
210 "output_type": {
211 "description": "Type of cell output.",
212 "enum": ["pyout"]
213 },
214 "prompt_number": {
215 "description": "A result's prompt number.",
216 "type": ["integer"],
217 "minimum": 0
218 },
219 "text": {"$ref": "#/definitions/misc/multiline_string"},
220 "latex": {"$ref": "#/definitions/misc/multiline_string"},
221 "png": {"$ref": "#/definitions/misc/multiline_string"},
222 "jpeg": {"$ref": "#/definitions/misc/multiline_string"},
223 "svg": {"$ref": "#/definitions/misc/multiline_string"},
224 "html": {"$ref": "#/definitions/misc/multiline_string"},
225 "javascript": {"$ref": "#/definitions/misc/multiline_string"},
226 "json": {"$ref": "#/definitions/misc/multiline_string"},
227 "pdf": {"$ref": "#/definitions/misc/multiline_string"},
228 "metadata": {"$ref": "#/definitions/misc/output_metadata"}
229 },
230 "patternProperties": {
231 "^[a-zA-Z0-9]+/[a-zA-Z0-9\\-\\+\\.]+$": {
232 "description": "mimetype output (e.g. text/plain), represented as either an array of strings or a string.",
233 "$ref": "#/definitions/misc/multiline_string"
234 }
235 }
236 },
237
238 "display_data": {
239 "description": "Data displayed as a result of code cell execution.",
240 "type": "object",
241 "additionalProperties": false,
242 "required": ["output_type"],
243 "properties": {
244 "output_type": {
245 "description": "Type of cell output.",
246 "enum": ["display_data"]
247 },
248 "text": {"$ref": "#/definitions/misc/multiline_string"},
249 "latex": {"$ref": "#/definitions/misc/multiline_string"},
250 "png": {"$ref": "#/definitions/misc/multiline_string"},
251 "jpeg": {"$ref": "#/definitions/misc/multiline_string"},
252 "svg": {"$ref": "#/definitions/misc/multiline_string"},
253 "html": {"$ref": "#/definitions/misc/multiline_string"},
254 "javascript": {"$ref": "#/definitions/misc/multiline_string"},
255 "json": {"$ref": "#/definitions/misc/multiline_string"},
256 "pdf": {"$ref": "#/definitions/misc/multiline_string"},
257 "metadata": {"$ref": "#/definitions/misc/output_metadata"}
258 },
259 "patternProperties": {
260 "[a-zA-Z0-9]+/[a-zA-Z0-9\\-\\+\\.]+$": {
261 "description": "mimetype output (e.g. text/plain), represented as either an array of strings or a string.",
262 "$ref": "#/definitions/misc/multiline_string"
263 }
264 }
265 },
266
267 "stream": {
268 "description": "Stream output from a code cell.",
269 "type": "object",
270 "additionalProperties": false,
271 "required": ["output_type", "stream", "text"],
272 "properties": {
273 "output_type": {
274 "description": "Type of cell output.",
275 "enum": ["stream"]
276 },
277 "stream": {
278 "description": "The stream type/destination.",
279 "type": "string"
280 },
281 "text": {
282 "description": "The stream's text output, represented as an array of strings.",
283 "$ref": "#/definitions/misc/multiline_string"
284 }
285 }
286 },
287
288 "pyerr": {
289 "description": "Output of an error that occurred during code cell execution.",
290 "type": "object",
291 "additionalProperties": false,
292 "required": ["output_type", "ename", "evalue", "traceback"],
293 "properties": {
294 "output_type": {
295 "description": "Type of cell output.",
296 "enum": ["pyerr"]
297 },
298 "metadata": {"$ref": "#/definitions/misc/output_metadata"},
299 "ename": {
300 "description": "The name of the error.",
301 "type": "string"
302 },
303 "evalue": {
304 "description": "The value, or message, of the error.",
305 "type": "string"
306 },
307 "traceback": {
308 "description": "The error's traceback, represented as an array of strings.",
309 "type": "array",
310 "items": {"type": "string"}
311 }
312 }
313 },
314
315 "misc": {
316 "metadata_name": {
317 "description": "The cell's name. If present, must be a non-empty string.",
318 "type": "string",
319 "pattern": "^.+$"
320 },
321 "metadata_tags": {
322 "description": "The cell's tags. Tags must be unique, and must not contain commas.",
323 "type": "array",
324 "uniqueItems": true,
325 "items": {
326 "type": "string",
327 "pattern": "^[^,]+$"
328 }
329 },
330 "source": {
331 "description": "Contents of the cell, represented as an array of lines.",
332 "$ref": "#/definitions/misc/multiline_string"
333 },
334 "prompt_number": {
335 "description": "The code cell's prompt number. Will be null if the cell has not been run.",
336 "type": ["integer", "null"],
337 "minimum": 0
338 },
339 "mimetype": {
340 "patternProperties": {
341 "^[a-zA-Z0-9\\-\\+]+/[a-zA-Z0-9\\-\\+]+": {
342 "description": "The cell's mimetype output (e.g. text/plain), represented as either an array of strings or a string.",
343 "$ref": "#/definitions/misc/multiline_string"
344 }
345 }
346 },
347 "output_metadata": {
348 "description": "Cell output metadata.",
349 "type": "object",
350 "additionalProperties": true
351 },
352 "multiline_string": {
353 "oneOf" : [
354 {"type": "string"},
355 {
356 "type": "array",
357 "items": {"type": "string"}
358 }
359 ]
360 }
361 }
362 }
363 }
@@ -176,6 +176,7 b' class FileContentsManager(ContentsManager):'
176 model['created'] = created
176 model['created'] = created
177 model['content'] = None
177 model['content'] = None
178 model['format'] = None
178 model['format'] = None
179 model['message'] = None
179 return model
180 return model
180
181
181 def _dir_model(self, name, path='', content=True):
182 def _dir_model(self, name, path='', content=True):
@@ -258,6 +259,7 b' class FileContentsManager(ContentsManager):'
258 self.mark_trusted_cells(nb, name, path)
259 self.mark_trusted_cells(nb, name, path)
259 model['content'] = nb
260 model['content'] = nb
260 model['format'] = 'json'
261 model['format'] = 'json'
262 self.validate_notebook_model(model)
261 return model
263 return model
262
264
263 def get_model(self, name, path='', content=True):
265 def get_model(self, name, path='', content=True):
@@ -301,7 +303,7 b' class FileContentsManager(ContentsManager):'
301 nb['metadata']['name'] = u''
303 nb['metadata']['name'] = u''
302
304
303 with atomic_writing(os_path, encoding='utf-8') as f:
305 with atomic_writing(os_path, encoding='utf-8') as f:
304 current.write(nb, f, u'json')
306 current.write(nb, f, version=nb.nbformat)
305
307
306 def _save_file(self, os_path, model, name='', path=''):
308 def _save_file(self, os_path, model, name='', path=''):
307 """save a non-notebook file"""
309 """save a non-notebook file"""
@@ -366,7 +368,14 b' class FileContentsManager(ContentsManager):'
366 except Exception as e:
368 except Exception as e:
367 raise web.HTTPError(400, u'Unexpected error while saving file: %s %s' % (os_path, e))
369 raise web.HTTPError(400, u'Unexpected error while saving file: %s %s' % (os_path, e))
368
370
371 validation_message = None
372 if model['type'] == 'notebook':
373 self.validate_notebook_model(model)
374 validation_message = model.get('message', None)
375
369 model = self.get_model(new_name, new_path, content=False)
376 model = self.get_model(new_name, new_path, content=False)
377 if validation_message:
378 model['message'] = validation_message
370 return model
379 return model
371
380
372 def update(self, model, name, path=''):
381 def update(self, model, name, path=''):
@@ -5,6 +5,7 b''
5
5
6 from fnmatch import fnmatch
6 from fnmatch import fnmatch
7 import itertools
7 import itertools
8 import json
8 import os
9 import os
9
10
10 from tornado.web import HTTPError
11 from tornado.web import HTTPError
@@ -216,6 +217,16 b' class ContentsManager(LoggingConfigurable):'
216 break
217 break
217 return name
218 return name
218
219
220 def validate_notebook_model(self, model):
221 """Add failed-validation message to model"""
222 try:
223 current.validate(model['content'])
224 except current.ValidationError as e:
225 model['message'] = 'Notebook Validation failed: {}:\n{}'.format(
226 e.message, json.dumps(e.instance, indent=1, default=lambda obj: '<UNKNOWN>'),
227 )
228 return model
229
219 def create_file(self, model=None, path='', ext='.ipynb'):
230 def create_file(self, model=None, path='', ext='.ipynb'):
220 """Create a new file or directory and return its model with no content."""
231 """Create a new file or directory and return its model with no content."""
221 path = path.strip('/')
232 path = path.strip('/')
@@ -304,6 +315,8 b' class ContentsManager(LoggingConfigurable):'
304 path : string
315 path : string
305 The notebook's directory (for logging)
316 The notebook's directory (for logging)
306 """
317 """
318 if nb['nbformat'] != current.nbformat:
319 return
307 if self.notary.check_cells(nb):
320 if self.notary.check_cells(nb):
308 self.notary.sign(nb)
321 self.notary.sign(nb)
309 else:
322 else:
@@ -311,13 +311,13 b' class TestContentsManager(TestCase):'
311 cm.mark_trusted_cells(nb, name, path)
311 cm.mark_trusted_cells(nb, name, path)
312 for cell in nb.worksheets[0].cells:
312 for cell in nb.worksheets[0].cells:
313 if cell.cell_type == 'code':
313 if cell.cell_type == 'code':
314 assert not cell.trusted
314 assert not cell.metadata.trusted
315
315
316 cm.trust_notebook(name, path)
316 cm.trust_notebook(name, path)
317 nb = cm.get_model(name, path)['content']
317 nb = cm.get_model(name, path)['content']
318 for cell in nb.worksheets[0].cells:
318 for cell in nb.worksheets[0].cells:
319 if cell.cell_type == 'code':
319 if cell.cell_type == 'code':
320 assert cell.trusted
320 assert cell.metadata.trusted
321
321
322 def test_check_and_sign(self):
322 def test_check_and_sign(self):
323 cm = self.contents_manager
323 cm = self.contents_manager
@@ -472,7 +472,7 b' define(['
472 } else {
472 } else {
473 this.set_input_prompt();
473 this.set_input_prompt();
474 }
474 }
475 this.output_area.trusted = data.trusted || false;
475 this.output_area.trusted = data.metadata.trusted || false;
476 this.output_area.fromJSON(data.outputs);
476 this.output_area.fromJSON(data.outputs);
477 if (data.collapsed !== undefined) {
477 if (data.collapsed !== undefined) {
478 if (data.collapsed) {
478 if (data.collapsed) {
@@ -495,8 +495,8 b' define(['
495 var outputs = this.output_area.toJSON();
495 var outputs = this.output_area.toJSON();
496 data.outputs = outputs;
496 data.outputs = outputs;
497 data.language = 'python';
497 data.language = 'python';
498 data.trusted = this.output_area.trusted;
498 data.metadata.trusted = this.output_area.trusted;
499 data.collapsed = this.collapsed;
499 data.collapsed = this.output_area.collapsed;
500 return data;
500 return data;
501 };
501 };
502
502
@@ -1792,6 +1792,7 b' define(['
1792 * @param {Object} data JSON representation of a notebook
1792 * @param {Object} data JSON representation of a notebook
1793 */
1793 */
1794 Notebook.prototype.fromJSON = function (data) {
1794 Notebook.prototype.fromJSON = function (data) {
1795
1795 var content = data.content;
1796 var content = data.content;
1796 var ncells = this.ncells();
1797 var ncells = this.ncells();
1797 var i;
1798 var i;
@@ -1941,6 +1942,7 b' define(['
1941 type : "PUT",
1942 type : "PUT",
1942 data : JSON.stringify(model),
1943 data : JSON.stringify(model),
1943 headers : {'Content-Type': 'application/json'},
1944 headers : {'Content-Type': 'application/json'},
1945 dataType : "json",
1944 success : $.proxy(this.save_notebook_success, this, start),
1946 success : $.proxy(this.save_notebook_success, this, start),
1945 error : $.proxy(this.save_notebook_error, this)
1947 error : $.proxy(this.save_notebook_error, this)
1946 };
1948 };
@@ -1970,6 +1972,30 b' define(['
1970 */
1972 */
1971 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1973 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1972 this.set_dirty(false);
1974 this.set_dirty(false);
1975 if (data.message) {
1976 // save succeeded, but validation failed.
1977 var body = $("<div>");
1978 var title = "Notebook validation failed";
1979
1980 body.append($("<p>").text(
1981 "The save operation succeeded," +
1982 " but the notebook does not appear to be valid." +
1983 " The validation error was:"
1984 )).append($("<div>").addClass("validation-error").append(
1985 $("<pre>").text(data.message)
1986 ));
1987 dialog.modal({
1988 notebook: this,
1989 keyboard_manager: this.keyboard_manager,
1990 title: title,
1991 body: body,
1992 buttons : {
1993 OK : {
1994 "class" : "btn-primary"
1995 }
1996 }
1997 });
1998 }
1973 this.events.trigger('notebook_saved.Notebook');
1999 this.events.trigger('notebook_saved.Notebook');
1974 this._update_autosave_interval(start);
2000 this._update_autosave_interval(start);
1975 if (this._checkpoint_after_save) {
2001 if (this._checkpoint_after_save) {
@@ -2244,7 +2270,57 b' define(['
2244 * @param {jqXHR} xhr jQuery Ajax object
2270 * @param {jqXHR} xhr jQuery Ajax object
2245 */
2271 */
2246 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2272 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2247 this.fromJSON(data);
2273 var failed;
2274 try {
2275 this.fromJSON(data);
2276 } catch (e) {
2277 failed = e;
2278 console.log("Notebook failed to load from JSON:", e);
2279 }
2280 if (failed || data.message) {
2281 // *either* fromJSON failed or validation failed
2282 var body = $("<div>");
2283 var title;
2284 if (failed) {
2285 title = "Notebook failed to load";
2286 body.append($("<p>").text(
2287 "The error was: "
2288 )).append($("<div>").addClass("js-error").text(
2289 failed.toString()
2290 )).append($("<p>").text(
2291 "See the error console for details."
2292 ));
2293 } else {
2294 title = "Notebook validation failed";
2295 }
2296
2297 if (data.message) {
2298 var msg;
2299 if (failed) {
2300 msg = "The notebook also failed validation:"
2301 } else {
2302 msg = "An invalid notebook may not function properly." +
2303 " The validation error was:"
2304 }
2305 body.append($("<p>").text(
2306 msg
2307 )).append($("<div>").addClass("validation-error").append(
2308 $("<pre>").text(data.message)
2309 ));
2310 }
2311
2312 dialog.modal({
2313 notebook: this,
2314 keyboard_manager: this.keyboard_manager,
2315 title: title,
2316 body: body,
2317 buttons : {
2318 OK : {
2319 "class" : "btn-primary"
2320 }
2321 }
2322 });
2323 }
2248 if (this.ncells() === 0) {
2324 if (this.ncells() === 0) {
2249 this.insert_cell_below('code');
2325 this.insert_cell_below('code');
2250 this.edit_mode(0);
2326 this.edit_mode(0);
@@ -4,6 +4,7 b' from .slides import SlidesExporter'
4 from .templateexporter import TemplateExporter
4 from .templateexporter import TemplateExporter
5 from .latex import LatexExporter
5 from .latex import LatexExporter
6 from .markdown import MarkdownExporter
6 from .markdown import MarkdownExporter
7 from .notebook import NotebookExporter
7 from .pdf import PDFExporter
8 from .pdf import PDFExporter
8 from .python import PythonExporter
9 from .python import PythonExporter
9 from .rst import RSTExporter
10 from .rst import RSTExporter
@@ -5,11 +5,18 b''
5
5
6 from .exporter import Exporter
6 from .exporter import Exporter
7 from IPython.nbformat import current as nbformat
7 from IPython.nbformat import current as nbformat
8 from IPython.utils.traitlets import Enum
8
9
9 class NotebookExporter(Exporter):
10 class NotebookExporter(Exporter):
10 """
11 """Exports to an IPython notebook."""
11 Exports an IPython notebook.
12
12 """
13 nbformat_version = Enum(list(range(2, nbformat.current_nbformat + 1)),
14 default_value=nbformat.current_nbformat,
15 config=True,
16 help="""The nbformat version to write.
17 Use this to downgrade notebooks.
18 """
19 )
13 def _file_extension_default(self):
20 def _file_extension_default(self):
14 return 'ipynb'
21 return 'ipynb'
15
22
@@ -17,5 +24,9 b' class NotebookExporter(Exporter):'
17
24
18 def from_notebook_node(self, nb, resources=None, **kw):
25 def from_notebook_node(self, nb, resources=None, **kw):
19 nb_copy, resources = super(NotebookExporter, self).from_notebook_node(nb, resources, **kw)
26 nb_copy, resources = super(NotebookExporter, self).from_notebook_node(nb, resources, **kw)
20 output = nbformat.writes_json(nb_copy)
27 if self.nbformat_version != nbformat.current_nbformat:
28 resources['output_suffix'] = '.v%i' % self.nbformat_version
29 else:
30 resources['output_suffix'] = '.nbconvert'
31 output = nbformat.writes(nb_copy, version=self.nbformat_version)
21 return output, resources
32 return output, resources
@@ -3,9 +3,13 b''
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
7
6 from .base import ExportersTestsBase
8 from .base import ExportersTestsBase
7 from ..notebook import NotebookExporter
9 from ..notebook import NotebookExporter
8
10
11 from IPython.testing.tools import assert_big_text_equal
12
9 class TestNotebookExporter(ExportersTestsBase):
13 class TestNotebookExporter(ExportersTestsBase):
10 """Contains test functions for notebook.py"""
14 """Contains test functions for notebook.py"""
11
15
@@ -17,6 +21,18 b' class TestNotebookExporter(ExportersTestsBase):'
17 """
21 """
18 with open(self._get_notebook()) as f:
22 with open(self._get_notebook()) as f:
19 file_contents = f.read()
23 file_contents = f.read()
20 (output, resources) = NotebookExporter().from_filename(self._get_notebook())
24 (output, resources) = self.exporter_class().from_filename(self._get_notebook())
21 assert len(output) > 0
25 assert len(output) > 0
22 assert output == file_contents
26 assert_big_text_equal(output, file_contents)
27
28 def test_downgrade_3(self):
29 exporter = self.exporter_class(nbformat_version=3)
30 (output, resources) = exporter.from_filename(self._get_notebook())
31 nb = json.loads(output)
32 self.assertEqual(nb['nbformat'], 3)
33
34 def test_downgrade_2(self):
35 exporter = self.exporter_class(nbformat_version=2)
36 (output, resources) = exporter.from_filename(self._get_notebook())
37 nb = json.loads(output)
38 self.assertEqual(nb['nbformat'], 2)
@@ -53,6 +53,7 b' nbconvert_aliases.update({'
53 'post': 'NbConvertApp.postprocessor_class',
53 'post': 'NbConvertApp.postprocessor_class',
54 'output': 'NbConvertApp.output_base',
54 'output': 'NbConvertApp.output_base',
55 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
55 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix',
56 'nbformat': 'NotebookExporter.nbformat_version',
56 })
57 })
57
58
58 nbconvert_flags = {}
59 nbconvert_flags = {}
@@ -92,7 +93,7 b' class NbConvertApp(BaseIPythonApplication):'
92 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
93 WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""")
93
94
94 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
95 output_base = Unicode('', config=True, help='''overwrite base name use for output files.
95 can only be use when converting one notebook at a time.
96 can only be used when converting one notebook at a time.
96 ''')
97 ''')
97
98
98 examples = Unicode(u"""
99 examples = Unicode(u"""
@@ -298,6 +299,8 b' class NbConvertApp(BaseIPythonApplication):'
298 exc_info=True)
299 exc_info=True)
299 self.exit(1)
300 self.exit(1)
300 else:
301 else:
302 if 'output_suffix' in resources and not self.output_base:
303 notebook_name += resources['output_suffix']
301 write_results = self.writer.write(output, resources, notebook_name=notebook_name)
304 write_results = self.writer.write(output, resources, notebook_name=notebook_name)
302
305
303 #Post-process if post processor has been defined.
306 #Post-process if post processor has been defined.
@@ -1,27 +1,14 b''
1 """
1 """Tests for the coalescestreams preprocessor"""
2 Module with tests for the coalescestreams preprocessor
3 """
4
2
5 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
8 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
12
5
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16 from IPython.nbformat import current as nbformat
6 from IPython.nbformat import current as nbformat
17
7
18 from .base import PreprocessorTestsBase
8 from .base import PreprocessorTestsBase
19 from ..coalescestreams import coalesce_streams
9 from ..coalescestreams import coalesce_streams
20
10
21
11
22 #-----------------------------------------------------------------------------
23 # Class
24 #-----------------------------------------------------------------------------
25 class TestCoalesceStreams(PreprocessorTestsBase):
12 class TestCoalesceStreams(PreprocessorTestsBase):
26 """Contains test functions for coalescestreams.py"""
13 """Contains test functions for coalescestreams.py"""
27
14
@@ -47,7 +34,7 b' class TestCoalesceStreams(PreprocessorTestsBase):'
47 nbformat.new_output(output_type="stream", stream="stdout", output_text="6"),
34 nbformat.new_output(output_type="stream", stream="stdout", output_text="6"),
48 nbformat.new_output(output_type="stream", stream="stdout", output_text="7")]
35 nbformat.new_output(output_type="stream", stream="stdout", output_text="7")]
49 cells=[nbformat.new_code_cell(input="# None", prompt_number=1,outputs=outputs)]
36 cells=[nbformat.new_code_cell(input="# None", prompt_number=1,outputs=outputs)]
50 worksheets = [nbformat.new_worksheet(name="worksheet1", cells=cells)]
37 worksheets = [nbformat.new_worksheet(cells=cells)]
51
38
52 nb = nbformat.new_notebook(name="notebook1", worksheets=worksheets)
39 nb = nbformat.new_notebook(name="notebook1", worksheets=worksheets)
53 res = self.build_resources()
40 res = self.build_resources()
@@ -64,7 +51,7 b' class TestCoalesceStreams(PreprocessorTestsBase):'
64 nbformat.new_output(output_type="stream", stream="stdout", output_text="\rc\n"),
51 nbformat.new_output(output_type="stream", stream="stdout", output_text="\rc\n"),
65 nbformat.new_output(output_type="stream", stream="stdout", output_text="z\rz\rd")]
52 nbformat.new_output(output_type="stream", stream="stdout", output_text="z\rz\rd")]
66 cells=[nbformat.new_code_cell(input="# None", prompt_number=1,outputs=outputs)]
53 cells=[nbformat.new_code_cell(input="# None", prompt_number=1,outputs=outputs)]
67 worksheets = [nbformat.new_worksheet(name="worksheet1", cells=cells)]
54 worksheets = [nbformat.new_worksheet(cells=cells)]
68
55
69 nb = nbformat.new_notebook(name="notebook1", worksheets=worksheets)
56 nb = nbformat.new_notebook(name="notebook1", worksheets=worksheets)
70 res = self.build_resources()
57 res = self.build_resources()
@@ -1,18 +1,7 b''
1 """
1 """Tests for the revealhelp preprocessor"""
2 Module with tests for the revealhelp preprocessor
3 """
4
2
5 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
8 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16
5
17 from IPython.nbformat import current as nbformat
6 from IPython.nbformat import current as nbformat
18
7
@@ -20,10 +9,6 b' from .base import PreprocessorTestsBase'
20 from ..revealhelp import RevealHelpPreprocessor
9 from ..revealhelp import RevealHelpPreprocessor
21
10
22
11
23 #-----------------------------------------------------------------------------
24 # Class
25 #-----------------------------------------------------------------------------
26
27 class Testrevealhelp(PreprocessorTestsBase):
12 class Testrevealhelp(PreprocessorTestsBase):
28 """Contains test functions for revealhelp.py"""
13 """Contains test functions for revealhelp.py"""
29
14
@@ -41,7 +26,7 b' class Testrevealhelp(PreprocessorTestsBase):'
41 nbformat.new_code_cell(input="", prompt_number=2, outputs=outputs),
26 nbformat.new_code_cell(input="", prompt_number=2, outputs=outputs),
42 nbformat.new_text_cell('markdown', source="", metadata=slide_metadata),
27 nbformat.new_text_cell('markdown', source="", metadata=slide_metadata),
43 nbformat.new_text_cell('markdown', source="", metadata=subslide_metadata)]
28 nbformat.new_text_cell('markdown', source="", metadata=subslide_metadata)]
44 worksheets = [nbformat.new_worksheet(name="worksheet1", cells=cells)]
29 worksheets = [nbformat.new_worksheet(cells=cells)]
45
30
46 return nbformat.new_notebook(name="notebook1", worksheets=worksheets)
31 return nbformat.new_notebook(name="notebook1", worksheets=worksheets)
47
32
@@ -1,23 +1,25 b''
1 """The official API for working with notebooks in the current format version."""
1 """The official API for working with notebooks in the current format version."""
2
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
3 from __future__ import print_function
6 from __future__ import print_function
4
7
5 import re
8 import re
6
9 import warnings
7 from IPython.utils.py3compat import unicode_type
8
10
9 from IPython.nbformat.v3 import (
11 from IPython.nbformat.v3 import (
10 NotebookNode,
12 NotebookNode,
11 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
13 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
12 parse_filename, new_metadata, new_author, new_heading_cell, nbformat,
14 parse_filename, new_metadata, new_author, new_heading_cell, nbformat,
13 nbformat_minor, nbformat_schema, to_notebook_json
15 nbformat_minor, nbformat_schema, to_notebook_json,
14 )
16 )
15 from IPython.nbformat import v3 as _v_latest
17 from IPython.nbformat import v3 as _v_latest
16
18
17 from .reader import reads as reader_reads
19 from .reader import reads as reader_reads
18 from .reader import versions
20 from .reader import versions
19 from .convert import convert
21 from .convert import convert
20 from .validator import validate
22 from .validator import validate, ValidationError
21
23
22 from IPython.utils.log import get_logger
24 from IPython.utils.log import get_logger
23
25
@@ -37,6 +39,11 b' class NBFormatError(ValueError):'
37 pass
39 pass
38
40
39
41
42 def _warn_format():
43 warnings.warn("""Non-JSON file support in nbformat is deprecated.
44 Use nbconvert to create files of other formats.""")
45
46
40 def parse_py(s, **kwargs):
47 def parse_py(s, **kwargs):
41 """Parse a string into a (nbformat, string) tuple."""
48 """Parse a string into a (nbformat, string) tuple."""
42 nbf = current_nbformat
49 nbf = current_nbformat
@@ -54,36 +61,18 b' def parse_py(s, **kwargs):'
54
61
55
62
56 def reads_json(nbjson, **kwargs):
63 def reads_json(nbjson, **kwargs):
57 """Read a JSON notebook from a string and return the NotebookNode
64 """DEPRECATED, use reads"""
58 object. Report if any JSON format errors are detected.
65 warnings.warn("reads_json is deprecated, use reads")
59
66 return reads(nbjson)
60 """
61 nb = reader_reads(nbjson, **kwargs)
62 nb_current = convert(nb, current_nbformat)
63 errors = validate(nb_current)
64 if errors:
65 get_logger().error(
66 "Notebook JSON is invalid (%d errors detected during read)",
67 len(errors))
68 return nb_current
69
70
67
71 def writes_json(nb, **kwargs):
68 def writes_json(nb, **kwargs):
72 """Take a NotebookNode object and write out a JSON string. Report if
69 """DEPRECATED, use writes"""
73 any JSON format errors are detected.
70 warnings.warn("writes_json is deprecated, use writes")
74
71 return writes(nb, **kwargs)
75 """
76 errors = validate(nb)
77 if errors:
78 get_logger().error(
79 "Notebook JSON is invalid (%d errors detected during write)",
80 len(errors))
81 nbjson = versions[current_nbformat].writes_json(nb, **kwargs)
82 return nbjson
83
84
72
85 def reads_py(s, **kwargs):
73 def reads_py(s, **kwargs):
86 """Read a .py notebook from a string and return the NotebookNode object."""
74 """DEPRECATED: use nbconvert"""
75 _warn_format()
87 nbf, nbm, s = parse_py(s, **kwargs)
76 nbf, nbm, s = parse_py(s, **kwargs)
88 if nbf in (2, 3):
77 if nbf in (2, 3):
89 nb = versions[nbf].to_notebook_py(s, **kwargs)
78 nb = versions[nbf].to_notebook_py(s, **kwargs)
@@ -91,16 +80,16 b' def reads_py(s, **kwargs):'
91 raise NBFormatError('Unsupported PY nbformat version: %i' % nbf)
80 raise NBFormatError('Unsupported PY nbformat version: %i' % nbf)
92 return nb
81 return nb
93
82
94
95 def writes_py(nb, **kwargs):
83 def writes_py(nb, **kwargs):
96 # nbformat 3 is the latest format that supports py
84 """DEPRECATED: use nbconvert"""
85 _warn_format()
97 return versions[3].writes_py(nb, **kwargs)
86 return versions[3].writes_py(nb, **kwargs)
98
87
99
88
100 # High level API
89 # High level API
101
90
102
91
103 def reads(s, format, **kwargs):
92 def reads(s, format='DEPRECATED', version=current_nbformat, **kwargs):
104 """Read a notebook from a string and return the NotebookNode object.
93 """Read a notebook from a string and return the NotebookNode object.
105
94
106 This function properly handles notebooks of any version. The notebook
95 This function properly handles notebooks of any version. The notebook
@@ -110,24 +99,24 b' def reads(s, format, **kwargs):'
110 ----------
99 ----------
111 s : unicode
100 s : unicode
112 The raw unicode string to read the notebook from.
101 The raw unicode string to read the notebook from.
113 format : (u'json', u'ipynb', u'py')
114 The format that the string is in.
115
102
116 Returns
103 Returns
117 -------
104 -------
118 nb : NotebookNode
105 nb : NotebookNode
119 The notebook that was read.
106 The notebook that was read.
120 """
107 """
121 format = unicode_type(format)
108 if format not in {'DEPRECATED', 'json'}:
122 if format == u'json' or format == u'ipynb':
109 _warn_format()
123 return reads_json(s, **kwargs)
110 nb = reader_reads(s, **kwargs)
124 elif format == u'py':
111 nb = convert(nb, version)
125 return reads_py(s, **kwargs)
112 try:
126 else:
113 validate(nb)
127 raise NBFormatError('Unsupported format: %s' % format)
114 except ValidationError as e:
115 get_logger().error("Notebook JSON is invalid: %s", e)
116 return nb
128
117
129
118
130 def writes(nb, format, **kwargs):
119 def writes(nb, format='DEPRECATED', version=current_nbformat, **kwargs):
131 """Write a notebook to a string in a given format in the current nbformat version.
120 """Write a notebook to a string in a given format in the current nbformat version.
132
121
133 This function always writes the notebook in the current nbformat version.
122 This function always writes the notebook in the current nbformat version.
@@ -136,24 +125,26 b' def writes(nb, format, **kwargs):'
136 ----------
125 ----------
137 nb : NotebookNode
126 nb : NotebookNode
138 The notebook to write.
127 The notebook to write.
139 format : (u'json', u'ipynb', u'py')
128 version : int
140 The format to write the notebook in.
129 The nbformat version to write.
130 Used for downgrading notebooks.
141
131
142 Returns
132 Returns
143 -------
133 -------
144 s : unicode
134 s : unicode
145 The notebook string.
135 The notebook string.
146 """
136 """
147 format = unicode_type(format)
137 if format not in {'DEPRECATED', 'json'}:
148 if format == u'json' or format == u'ipynb':
138 _warn_format()
149 return writes_json(nb, **kwargs)
139 nb = convert(nb, version)
150 elif format == u'py':
140 try:
151 return writes_py(nb, **kwargs)
141 validate(nb)
152 else:
142 except ValidationError as e:
153 raise NBFormatError('Unsupported format: %s' % format)
143 get_logger().error("Notebook JSON is invalid: %s", e)
144 return versions[version].writes_json(nb, **kwargs)
154
145
155
146
156 def read(fp, format, **kwargs):
147 def read(fp, format='DEPRECATED', **kwargs):
157 """Read a notebook from a file and return the NotebookNode object.
148 """Read a notebook from a file and return the NotebookNode object.
158
149
159 This function properly handles notebooks of any version. The notebook
150 This function properly handles notebooks of any version. The notebook
@@ -163,18 +154,16 b' def read(fp, format, **kwargs):'
163 ----------
154 ----------
164 fp : file
155 fp : file
165 Any file-like object with a read method.
156 Any file-like object with a read method.
166 format : (u'json', u'ipynb', u'py')
167 The format that the string is in.
168
157
169 Returns
158 Returns
170 -------
159 -------
171 nb : NotebookNode
160 nb : NotebookNode
172 The notebook that was read.
161 The notebook that was read.
173 """
162 """
174 return reads(fp.read(), format, **kwargs)
163 return reads(fp.read(), **kwargs)
175
164
176
165
177 def write(nb, fp, format, **kwargs):
166 def write(nb, fp, format='DEPRECATED', **kwargs):
178 """Write a notebook to a file in a given format in the current nbformat version.
167 """Write a notebook to a file in a given format in the current nbformat version.
179
168
180 This function always writes the notebook in the current nbformat version.
169 This function always writes the notebook in the current nbformat version.
@@ -185,28 +174,9 b' def write(nb, fp, format, **kwargs):'
185 The notebook to write.
174 The notebook to write.
186 fp : file
175 fp : file
187 Any file-like object with a write method.
176 Any file-like object with a write method.
188 format : (u'json', u'ipynb', u'py')
189 The format to write the notebook in.
190
191 Returns
192 -------
193 s : unicode
194 The notebook string.
195 """
177 """
196 return fp.write(writes(nb, format, **kwargs))
178 s = writes(nb, **kwargs)
197
179 if isinstance(s, bytes):
198 def _convert_to_metadata():
180 s = s.decode('utf8')
199 """Convert to a notebook having notebook metadata."""
181 return fp.write(s)
200 import glob
201 for fname in glob.glob('*.ipynb'):
202 print('Converting file:',fname)
203 with open(fname,'r') as f:
204 nb = read(f,u'json')
205 md = new_metadata()
206 if u'name' in nb:
207 md.name = nb.name
208 del nb[u'name']
209 nb.metadata = md
210 with open(fname,'w') as f:
211 write(nb, f, u'json')
212
182
@@ -1,14 +1,7 b''
1 """Functions for signing notebooks"""
1 """Functions for signing notebooks"""
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2014, The IPython Development Team
4 #
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
7 #-----------------------------------------------------------------------------
8
2
9 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
10 # Imports
4 # Distributed under the terms of the Modified BSD License.
11 #-----------------------------------------------------------------------------
12
5
13 import base64
6 import base64
14 from contextlib import contextmanager
7 from contextlib import contextmanager
@@ -24,15 +17,14 b' from IPython.core.application import BaseIPythonApplication, base_flags'
24
17
25 from .current import read, write
18 from .current import read, write
26
19
27 #-----------------------------------------------------------------------------
20
28 # Code
29 #-----------------------------------------------------------------------------
30 try:
21 try:
31 # Python 3
22 # Python 3
32 algorithms = hashlib.algorithms_guaranteed
23 algorithms = hashlib.algorithms_guaranteed
33 except AttributeError:
24 except AttributeError:
34 algorithms = hashlib.algorithms
25 algorithms = hashlib.algorithms
35
26
27
36 def yield_everything(obj):
28 def yield_everything(obj):
37 """Yield every item in a container as bytes
29 """Yield every item in a container as bytes
38
30
@@ -184,7 +176,7 b' class NotebookNotary(LoggingConfigurable):'
184 def mark_cells(self, nb, trusted):
176 def mark_cells(self, nb, trusted):
185 """Mark cells as trusted if the notebook's signature can be verified
177 """Mark cells as trusted if the notebook's signature can be verified
186
178
187 Sets ``cell.trusted = True | False`` on all code cells,
179 Sets ``cell.metadata.trusted = True | False`` on all code cells,
188 depending on whether the stored signature can be verified.
180 depending on whether the stored signature can be verified.
189
181
190 This function is the inverse of check_cells
182 This function is the inverse of check_cells
@@ -194,7 +186,7 b' class NotebookNotary(LoggingConfigurable):'
194 return
186 return
195 for cell in nb['worksheets'][0]['cells']:
187 for cell in nb['worksheets'][0]['cells']:
196 if cell['cell_type'] == 'code':
188 if cell['cell_type'] == 'code':
197 cell['trusted'] = trusted
189 cell['metadata']['trusted'] = trusted
198
190
199 def _check_cell(self, cell):
191 def _check_cell(self, cell):
200 """Do we trust an individual cell?
192 """Do we trust an individual cell?
@@ -208,7 +200,7 b' class NotebookNotary(LoggingConfigurable):'
208 it will always be trusted.
200 it will always be trusted.
209 """
201 """
210 # explicitly trusted
202 # explicitly trusted
211 if cell.pop("trusted", False):
203 if cell['metadata'].pop("trusted", False):
212 return True
204 return True
213
205
214 # explicitly safe output
206 # explicitly safe output
@@ -243,6 +235,7 b' class NotebookNotary(LoggingConfigurable):'
243 # only distrust a cell if it actually has some output to distrust
235 # only distrust a cell if it actually has some output to distrust
244 if not self._check_cell(cell):
236 if not self._check_cell(cell):
245 trusted = False
237 trusted = False
238
246 return trusted
239 return trusted
247
240
248
241
@@ -1,25 +1,19 b''
1 """
1 """
2 Contains tests class for current.py
2 Contains tests class for current.py
3 """
3 """
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
4
11 #-----------------------------------------------------------------------------
5 # Copyright (c) IPython Development Team.
12 # Imports
6 # Distributed under the terms of the Modified BSD License.
13 #-----------------------------------------------------------------------------
7
8 import io
9 import json
10 import tempfile
14
11
15 from .base import TestsBase
12 from .base import TestsBase
16
13
17 from ..reader import get_version
14 from ..reader import get_version
18 from ..current import read, current_nbformat
15 from ..current import read, current_nbformat, validate, writes
19
16
20 #-----------------------------------------------------------------------------
21 # Classes and functions
22 #-----------------------------------------------------------------------------
23
17
24 class TestCurrent(TestsBase):
18 class TestCurrent(TestsBase):
25
19
@@ -29,8 +23,19 b' class TestCurrent(TestsBase):'
29
23
30 # Open a version 2 notebook.
24 # Open a version 2 notebook.
31 with self.fopen(u'test2.ipynb', u'r') as f:
25 with self.fopen(u'test2.ipynb', u'r') as f:
32 nb = read(f, u'json')
26 nb = read(f)
33
27
34 # Check that the notebook was upgraded to the latest version automatically.
28 # Check that the notebook was upgraded to the latest version automatically.
35 (major, minor) = get_version(nb)
29 (major, minor) = get_version(nb)
36 self.assertEqual(major, current_nbformat)
30 self.assertEqual(major, current_nbformat)
31
32 def test_write_downgrade_2(self):
33 """dowgrade a v3 notebook to v2"""
34 # Open a version 3 notebook.
35 with self.fopen(u'test3.ipynb', 'r') as f:
36 nb = read(f, u'json')
37
38 jsons = writes(nb, version=2)
39 nb2 = json.loads(jsons)
40 (major, minor) = get_version(nb2)
41 self.assertEqual(major, 2)
@@ -83,21 +83,23 b' class TestNotary(TestsBase):'
83 cells = self.nb.worksheets[0].cells
83 cells = self.nb.worksheets[0].cells
84 self.notary.mark_cells(self.nb, False)
84 self.notary.mark_cells(self.nb, False)
85 for cell in cells:
85 for cell in cells:
86 self.assertNotIn('trusted', cell)
86 if cell.cell_type == 'code':
87 if cell.cell_type == 'code':
87 self.assertIn('trusted', cell)
88 self.assertIn('trusted', cell.metadata)
88 self.assertFalse(cell.trusted)
89 self.assertFalse(cell.metadata.trusted)
89 else:
90 else:
90 self.assertNotIn('trusted', cell)
91 self.assertNotIn('trusted', cell.metadata)
91
92
92 def test_mark_cells_trusted(self):
93 def test_mark_cells_trusted(self):
93 cells = self.nb.worksheets[0].cells
94 cells = self.nb.worksheets[0].cells
94 self.notary.mark_cells(self.nb, True)
95 self.notary.mark_cells(self.nb, True)
95 for cell in cells:
96 for cell in cells:
97 self.assertNotIn('trusted', cell)
96 if cell.cell_type == 'code':
98 if cell.cell_type == 'code':
97 self.assertIn('trusted', cell)
99 self.assertIn('trusted', cell.metadata)
98 self.assertTrue(cell.trusted)
100 self.assertTrue(cell.metadata.trusted)
99 else:
101 else:
100 self.assertNotIn('trusted', cell)
102 self.assertNotIn('trusted', cell.metadata)
101
103
102 def test_check_cells(self):
104 def test_check_cells(self):
103 nb = self.nb
105 nb = self.nb
@@ -1,23 +1,14 b''
1 """
1 """Test nbformat.validator"""
2 Contains tests class for validator.py
3 """
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2014 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
2
11 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
12 # Imports
4 # Distributed under the terms of the Modified BSD License.
13 #-----------------------------------------------------------------------------
14
5
15 import os
6 import os
16
7
17 from .base import TestsBase
8 from .base import TestsBase
18 from jsonschema import SchemaError
9 from jsonschema import ValidationError
19 from ..current import read
10 from ..current import read
20 from ..validator import schema_path, isvalid, validate, resolve_ref
11 from ..validator import isvalid, validate
21
12
22
13
23 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
@@ -26,22 +17,18 b' from ..validator import schema_path, isvalid, validate, resolve_ref'
26
17
27 class TestValidator(TestsBase):
18 class TestValidator(TestsBase):
28
19
29 def test_schema_path(self):
30 """Test that the schema path exists"""
31 self.assertEqual(os.path.exists(schema_path), True)
32
33 def test_nb2(self):
20 def test_nb2(self):
34 """Test that a v2 notebook converted to v3 passes validation"""
21 """Test that a v2 notebook converted to v3 passes validation"""
35 with self.fopen(u'test2.ipynb', u'r') as f:
22 with self.fopen(u'test2.ipynb', u'r') as f:
36 nb = read(f, u'json')
23 nb = read(f, u'json')
37 self.assertEqual(validate(nb), [])
24 validate(nb)
38 self.assertEqual(isvalid(nb), True)
25 self.assertEqual(isvalid(nb), True)
39
26
40 def test_nb3(self):
27 def test_nb3(self):
41 """Test that a v3 notebook passes validation"""
28 """Test that a v3 notebook passes validation"""
42 with self.fopen(u'test3.ipynb', u'r') as f:
29 with self.fopen(u'test3.ipynb', u'r') as f:
43 nb = read(f, u'json')
30 nb = read(f, u'json')
44 self.assertEqual(validate(nb), [])
31 validate(nb)
45 self.assertEqual(isvalid(nb), True)
32 self.assertEqual(isvalid(nb), True)
46
33
47 def test_invalid(self):
34 def test_invalid(self):
@@ -52,22 +39,16 b' class TestValidator(TestsBase):'
52 # - one cell has an invalid level
39 # - one cell has an invalid level
53 with self.fopen(u'invalid.ipynb', u'r') as f:
40 with self.fopen(u'invalid.ipynb', u'r') as f:
54 nb = read(f, u'json')
41 nb = read(f, u'json')
55 self.assertEqual(len(validate(nb)), 3)
42 with self.assertRaises(ValidationError):
43 validate(nb)
56 self.assertEqual(isvalid(nb), False)
44 self.assertEqual(isvalid(nb), False)
57
45
58 def test_resolve_ref(self):
46 def test_future(self):
59 """Test that references are correctly resolved"""
47 """Test than a notebook from the future with extra keys passes validation"""
60 # make sure it resolves the ref correctly
48 with self.fopen(u'test3plus.ipynb', u'r') as f:
61 json = {"abc": "def", "ghi": {"$ref": "/abc"}}
49 nb = read(f)
62 resolved = resolve_ref(json)
50 with self.assertRaises(ValidationError):
63 self.assertEqual(resolved, {"abc": "def", "ghi": "def"})
51 validate(nb, version=3)
64
65 # make sure it throws an error if the ref is not by itself
66 json = {"abc": "def", "ghi": {"$ref": "/abc", "foo": "bar"}}
67 with self.assertRaises(SchemaError):
68 resolved = resolve_ref(json)
69
52
70 # make sure it can handle json with no reference
53 self.assertEqual(isvalid(nb, version=3), False)
71 json = {"abc": "def"}
54 self.assertEqual(isvalid(nb), True)
72 resolved = resolve_ref(json)
73 self.assertEqual(resolved, json)
@@ -42,6 +42,9 b' from .convert import downgrade, upgrade'
42 # Code
42 # Code
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 nbformat = 2
46 nbformat_minor = 0
47
45 def parse_filename(fname):
48 def parse_filename(fname):
46 """Parse a notebook filename.
49 """Parse a notebook filename.
47
50
@@ -1,22 +1,7 b''
1 """Code for converting notebooks to and from the v2 format.
1 """Code for converting notebooks to and from the v2 format."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Brian Granger
6 * Min RK
7 * Jonathan Frederic
8 """
9
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2011 The IPython Development Team
12 #
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
16
17 #-----------------------------------------------------------------------------
18 # Imports
19 #-----------------------------------------------------------------------------
20
5
21 from .nbbase import (
6 from .nbbase import (
22 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output,
7 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output,
@@ -25,9 +10,21 b' from .nbbase import ('
25
10
26 from IPython.nbformat import v2
11 from IPython.nbformat import v2
27
12
28 #-----------------------------------------------------------------------------
13 def _unbytes(obj):
29 # Code
14 """There should be no bytes objects in a notebook
30 #-----------------------------------------------------------------------------
15
16 v2 stores png/jpeg as b64 ascii bytes
17 """
18 if isinstance(obj, dict):
19 for k,v in obj.items():
20 obj[k] = _unbytes(v)
21 elif isinstance(obj, list):
22 for i,v in enumerate(obj):
23 obj[i] = _unbytes(v)
24 elif isinstance(obj, bytes):
25 # only valid bytes are b64-encoded ascii
26 obj = obj.decode('ascii')
27 return obj
31
28
32 def upgrade(nb, from_version=2, from_minor=0):
29 def upgrade(nb, from_version=2, from_minor=0):
33 """Convert a notebook to v3.
30 """Convert a notebook to v3.
@@ -47,6 +44,10 b' def upgrade(nb, from_version=2, from_minor=0):'
47 nb.nbformat_minor = nbformat_minor
44 nb.nbformat_minor = nbformat_minor
48
45
49 nb.orig_nbformat = 2
46 nb.orig_nbformat = 2
47 nb = _unbytes(nb)
48 for ws in nb['worksheets']:
49 for cell in ws['cells']:
50 cell.setdefault('metadata', {})
50 return nb
51 return nb
51 elif from_version == 3:
52 elif from_version == 3:
52 if from_minor != nbformat_minor:
53 if from_minor != nbformat_minor:
@@ -22,7 +22,7 b' from IPython.utils.py3compat import cast_unicode, unicode_type'
22 # Change this when incrementing the nbformat version
22 # Change this when incrementing the nbformat version
23 nbformat = 3
23 nbformat = 3
24 nbformat_minor = 0
24 nbformat_minor = 0
25 nbformat_schema = 'v3.withref.json'
25 nbformat_schema = 'nbformat.v3.schema.json'
26
26
27 class NotebookNode(Struct):
27 class NotebookNode(Struct):
28 pass
28 pass
@@ -53,7 +53,6 b' def new_output(output_type, output_text=None, output_png=None,'
53 metadata = {}
53 metadata = {}
54 if not isinstance(metadata, dict):
54 if not isinstance(metadata, dict):
55 raise TypeError("metadata must be dict")
55 raise TypeError("metadata must be dict")
56 output.metadata = metadata
57
56
58 if output_type != 'pyerr':
57 if output_type != 'pyerr':
59 if output_text is not None:
58 if output_text is not None:
@@ -87,6 +86,8 b' def new_output(output_type, output_text=None, output_png=None,'
87
86
88 if output_type == u'stream':
87 if output_type == u'stream':
89 output.stream = 'stdout' if stream is None else cast_unicode(stream)
88 output.stream = 'stdout' if stream is None else cast_unicode(stream)
89 else:
90 output.metadata = metadata
90
91
91 return output
92 return output
92
93
@@ -121,21 +122,17 b' def new_text_cell(cell_type, source=None, rendered=None, metadata=None):'
121 cell_type = 'raw'
122 cell_type = 'raw'
122 if source is not None:
123 if source is not None:
123 cell.source = cast_unicode(source)
124 cell.source = cast_unicode(source)
124 if rendered is not None:
125 cell.rendered = cast_unicode(rendered)
126 cell.metadata = NotebookNode(metadata or {})
125 cell.metadata = NotebookNode(metadata or {})
127 cell.cell_type = cell_type
126 cell.cell_type = cell_type
128 return cell
127 return cell
129
128
130
129
131 def new_heading_cell(source=None, rendered=None, level=1, metadata=None):
130 def new_heading_cell(source=None, level=1, rendered=None, metadata=None):
132 """Create a new section cell with a given integer level."""
131 """Create a new section cell with a given integer level."""
133 cell = NotebookNode()
132 cell = NotebookNode()
134 cell.cell_type = u'heading'
133 cell.cell_type = u'heading'
135 if source is not None:
134 if source is not None:
136 cell.source = cast_unicode(source)
135 cell.source = cast_unicode(source)
137 if rendered is not None:
138 cell.rendered = cast_unicode(rendered)
139 cell.level = int(level)
136 cell.level = int(level)
140 cell.metadata = NotebookNode(metadata or {})
137 cell.metadata = NotebookNode(metadata or {})
141 return cell
138 return cell
@@ -144,8 +141,6 b' def new_heading_cell(source=None, rendered=None, level=1, metadata=None):'
144 def new_worksheet(name=None, cells=None, metadata=None):
141 def new_worksheet(name=None, cells=None, metadata=None):
145 """Create a worksheet by name with with a list of cells."""
142 """Create a worksheet by name with with a list of cells."""
146 ws = NotebookNode()
143 ws = NotebookNode()
147 if name is not None:
148 ws.name = cast_unicode(name)
149 if cells is None:
144 if cells is None:
150 ws.cells = []
145 ws.cells = []
151 else:
146 else:
@@ -1,34 +1,19 b''
1 """Read and write notebooks in JSON format.
1 """Read and write notebooks in JSON format."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 import copy
6 import copy
20 import json
7 import json
21
8
22 from .nbbase import from_dict
9 from .nbbase import from_dict
23 from .rwbase import (
10 from .rwbase import (
24 NotebookReader, NotebookWriter, restore_bytes, rejoin_lines, split_lines
11 NotebookReader, NotebookWriter, restore_bytes, rejoin_lines, split_lines,
12 strip_transient,
25 )
13 )
26
14
27 from IPython.utils import py3compat
15 from IPython.utils import py3compat
28
16
29 #-----------------------------------------------------------------------------
30 # Code
31 #-----------------------------------------------------------------------------
32
17
33 class BytesEncoder(json.JSONEncoder):
18 class BytesEncoder(json.JSONEncoder):
34 """A JSON encoder that accepts b64 (and other *ascii*) bytestrings."""
19 """A JSON encoder that accepts b64 (and other *ascii*) bytestrings."""
@@ -43,6 +28,7 b' class JSONReader(NotebookReader):'
43 def reads(self, s, **kwargs):
28 def reads(self, s, **kwargs):
44 nb = json.loads(s, **kwargs)
29 nb = json.loads(s, **kwargs)
45 nb = self.to_notebook(nb, **kwargs)
30 nb = self.to_notebook(nb, **kwargs)
31 nb = strip_transient(nb)
46 return nb
32 return nb
47
33
48 def to_notebook(self, d, **kwargs):
34 def to_notebook(self, d, **kwargs):
@@ -56,8 +42,10 b' class JSONWriter(NotebookWriter):'
56 kwargs['indent'] = 1
42 kwargs['indent'] = 1
57 kwargs['sort_keys'] = True
43 kwargs['sort_keys'] = True
58 kwargs['separators'] = (',',': ')
44 kwargs['separators'] = (',',': ')
45 nb = copy.deepcopy(nb)
46 nb = strip_transient(nb)
59 if kwargs.pop('split_lines', True):
47 if kwargs.pop('split_lines', True):
60 nb = split_lines(copy.deepcopy(nb))
48 nb = split_lines(nb)
61 return py3compat.str_to_unicode(json.dumps(nb, **kwargs), 'utf-8')
49 return py3compat.str_to_unicode(json.dumps(nb, **kwargs), 'utf-8')
62
50
63
51
@@ -1,30 +1,13 b''
1 """Base classes and utilities for readers and writers.
1 """Base classes and utilities for readers and writers."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 from base64 import encodestring, decodestring
6 from base64 import encodestring, decodestring
20 import pprint
21
7
22 from IPython.utils import py3compat
8 from IPython.utils import py3compat
23 from IPython.utils.py3compat import str_to_bytes, unicode_type, string_types
9 from IPython.utils.py3compat import str_to_bytes, unicode_type, string_types
24
10
25 #-----------------------------------------------------------------------------
26 # Code
27 #-----------------------------------------------------------------------------
28
11
29 def restore_bytes(nb):
12 def restore_bytes(nb):
30 """Restore bytes of image data from unicode-only formats.
13 """Restore bytes of image data from unicode-only formats.
@@ -157,6 +140,19 b' def base64_encode(nb):'
157 return nb
140 return nb
158
141
159
142
143 def strip_transient(nb):
144 """Strip transient values that shouldn't be stored in files.
145
146 This should be called in *both* read and write.
147 """
148 nb.pop('orig_nbformat', None)
149 nb.pop('orig_nbformat_minor', None)
150 for ws in nb['worksheets']:
151 for cell in ws['cells']:
152 cell.get('metadata', {}).pop('trusted', None)
153 return nb
154
155
160 class NotebookReader(object):
156 class NotebookReader(object):
161 """A class for reading notebooks."""
157 """A class for reading notebooks."""
162
158
@@ -13,12 +13,11 b' from ..nbbase import ('
13 png = encodestring(os.urandom(5)).decode('ascii')
13 png = encodestring(os.urandom(5)).decode('ascii')
14 jpeg = encodestring(os.urandom(6)).decode('ascii')
14 jpeg = encodestring(os.urandom(6)).decode('ascii')
15
15
16 ws = new_worksheet(name='worksheet1')
16 ws = new_worksheet()
17
17
18 ws.cells.append(new_text_cell(
18 ws.cells.append(new_text_cell(
19 u'html',
19 u'html',
20 source='Some NumPy Examples',
20 source='Some NumPy Examples',
21 rendered='Some NumPy Examples'
22 ))
21 ))
23
22
24
23
@@ -31,7 +30,6 b' ws.cells.append(new_code_cell('
31 ws.cells.append(new_text_cell(
30 ws.cells.append(new_text_cell(
32 u'markdown',
31 u'markdown',
33 source='A random array',
32 source='A random array',
34 rendered='A random array'
35 ))
33 ))
36
34
37 ws.cells.append(new_text_cell(
35 ws.cells.append(new_text_cell(
@@ -70,7 +68,7 b' ws.cells.append(new_code_cell('
70 output_png=png,
68 output_png=png,
71 output_jpeg=jpeg,
69 output_jpeg=jpeg,
72 output_svg=u'<svg>',
70 output_svg=u'<svg>',
73 output_json=u'json data',
71 output_json=u'{"json": "data"}',
74 output_javascript=u'var i=0;',
72 output_javascript=u'var i=0;',
75 prompt_number=3
73 prompt_number=3
76 ),new_output(
74 ),new_output(
@@ -81,7 +79,7 b' ws.cells.append(new_code_cell('
81 output_png=png,
79 output_png=png,
82 output_jpeg=jpeg,
80 output_jpeg=jpeg,
83 output_svg=u'<svg>',
81 output_svg=u'<svg>',
84 output_json=u'json data',
82 output_json=u'{"json": "data"}',
85 output_javascript=u'var i=0;'
83 output_javascript=u'var i=0;'
86 ),new_output(
84 ),new_output(
87 output_type=u'pyerr',
85 output_type=u'pyerr',
@@ -104,7 +102,7 b" md = new_metadata(name=u'My Notebook',license=u'BSD',created=u'8601_goes_here',"
104 modified=u'8601_goes_here',gistid=u'21341231',authors=authors)
102 modified=u'8601_goes_here',gistid=u'21341231',authors=authors)
105
103
106 nb0 = new_notebook(
104 nb0 = new_notebook(
107 worksheets=[ws, new_worksheet(name='worksheet2')],
105 worksheets=[ws, new_worksheet()],
108 metadata=md
106 metadata=md
109 )
107 )
110
108
@@ -1,9 +1,11 b''
1 import pprint
1 import copy
2 import json
2 from base64 import decodestring
3 from base64 import decodestring
3 from unittest import TestCase
4 from unittest import TestCase
4
5
5 from IPython.utils.py3compat import unicode_type
6 from IPython.utils.py3compat import unicode_type
6 from ..nbjson import reads, writes
7 from ..nbjson import reads, writes
8 from ..nbbase import from_dict
7 from .. import nbjson
9 from .. import nbjson
8 from .nbexamples import nb0
10 from .nbexamples import nb0
9
11
@@ -31,6 +33,35 b' class TestJSON(formattest.NBFormatTest, TestCase):'
31 s = writes(nb0, split_lines=True)
33 s = writes(nb0, split_lines=True)
32 self.assertEqual(nbjson.reads(s),nb0)
34 self.assertEqual(nbjson.reads(s),nb0)
33
35
36 def test_strip_transient(self):
37 """transient values aren't written to files"""
38 nb = copy.deepcopy(nb0)
39 nb.orig_nbformat = 2
40 nb.orig_nbformat_minor = 3
41 nb.worksheets[0].cells[0].metadata.trusted = False
42 nbs = nbjson.writes(nb)
43
44 nb2 = from_dict(json.loads(nbs))
45 self.assertNotIn('orig_nbformat', nb2)
46 self.assertNotIn('orig_nbformat_minor', nb2)
47 for cell in nb2.worksheets[0].cells:
48 self.assertNotIn('trusted', cell.metadata)
49
50 def test_to_json(self):
51 """to_notebook_json doesn't strip transient"""
52 nb = copy.deepcopy(nb0)
53 nb.orig_nbformat = 2
54 nb.orig_nbformat_minor = 3
55 nb.worksheets[0].cells[0].metadata.trusted = False
56 nbs = json.dumps(nb)
57 nb2 = nbjson.to_notebook(json.loads(nbs))
58
59 nb2 = from_dict(json.loads(nbs))
60 self.assertIn('orig_nbformat', nb2)
61 self.assertIn('orig_nbformat_minor', nb2)
62 cell = nb2.worksheets[0].cells[0]
63 self.assertIn('trusted', cell.metadata)
64
34 def test_read_png(self):
65 def test_read_png(self):
35 """PNG output data is b64 unicode"""
66 """PNG output data is b64 unicode"""
36 s = writes(nb0)
67 s = writes(nb0)
@@ -41,45 +41,37 b' class TestCell(TestCase):'
41 tc = new_text_cell(u'html')
41 tc = new_text_cell(u'html')
42 self.assertEqual(tc.cell_type, u'html')
42 self.assertEqual(tc.cell_type, u'html')
43 self.assertEqual(u'source' not in tc, True)
43 self.assertEqual(u'source' not in tc, True)
44 self.assertEqual(u'rendered' not in tc, True)
45
44
46 def test_html_cell(self):
45 def test_html_cell(self):
47 tc = new_text_cell(u'html', 'hi', 'hi')
46 tc = new_text_cell(u'html', 'hi')
48 self.assertEqual(tc.source, u'hi')
47 self.assertEqual(tc.source, u'hi')
49 self.assertEqual(tc.rendered, u'hi')
50
48
51 def test_empty_markdown_cell(self):
49 def test_empty_markdown_cell(self):
52 tc = new_text_cell(u'markdown')
50 tc = new_text_cell(u'markdown')
53 self.assertEqual(tc.cell_type, u'markdown')
51 self.assertEqual(tc.cell_type, u'markdown')
54 self.assertEqual(u'source' not in tc, True)
52 self.assertEqual(u'source' not in tc, True)
55 self.assertEqual(u'rendered' not in tc, True)
56
53
57 def test_markdown_cell(self):
54 def test_markdown_cell(self):
58 tc = new_text_cell(u'markdown', 'hi', 'hi')
55 tc = new_text_cell(u'markdown', 'hi')
59 self.assertEqual(tc.source, u'hi')
56 self.assertEqual(tc.source, u'hi')
60 self.assertEqual(tc.rendered, u'hi')
61
57
62 def test_empty_raw_cell(self):
58 def test_empty_raw_cell(self):
63 tc = new_text_cell(u'raw')
59 tc = new_text_cell(u'raw')
64 self.assertEqual(tc.cell_type, u'raw')
60 self.assertEqual(tc.cell_type, u'raw')
65 self.assertEqual(u'source' not in tc, True)
61 self.assertEqual(u'source' not in tc, True)
66 self.assertEqual(u'rendered' not in tc, True)
67
62
68 def test_raw_cell(self):
63 def test_raw_cell(self):
69 tc = new_text_cell(u'raw', 'hi', 'hi')
64 tc = new_text_cell(u'raw', 'hi')
70 self.assertEqual(tc.source, u'hi')
65 self.assertEqual(tc.source, u'hi')
71 self.assertEqual(tc.rendered, u'hi')
72
66
73 def test_empty_heading_cell(self):
67 def test_empty_heading_cell(self):
74 tc = new_heading_cell()
68 tc = new_heading_cell()
75 self.assertEqual(tc.cell_type, u'heading')
69 self.assertEqual(tc.cell_type, u'heading')
76 self.assertEqual(u'source' not in tc, True)
70 self.assertEqual(u'source' not in tc, True)
77 self.assertEqual(u'rendered' not in tc, True)
78
71
79 def test_heading_cell(self):
72 def test_heading_cell(self):
80 tc = new_heading_cell(u'hi', u'hi', level=2)
73 tc = new_heading_cell(u'hi', level=2)
81 self.assertEqual(tc.source, u'hi')
74 self.assertEqual(tc.source, u'hi')
82 self.assertEqual(tc.rendered, u'hi')
83 self.assertEqual(tc.level, 2)
75 self.assertEqual(tc.level, 2)
84
76
85
77
@@ -92,9 +84,8 b' class TestWorksheet(TestCase):'
92
84
93 def test_worksheet(self):
85 def test_worksheet(self):
94 cells = [new_code_cell(), new_text_cell(u'html')]
86 cells = [new_code_cell(), new_text_cell(u'html')]
95 ws = new_worksheet(cells=cells,name=u'foo')
87 ws = new_worksheet(cells=cells)
96 self.assertEqual(ws.cells,cells)
88 self.assertEqual(ws.cells,cells)
97 self.assertEqual(ws.name,u'foo')
98
89
99 class TestNotebook(TestCase):
90 class TestNotebook(TestCase):
100
91
@@ -1,112 +1,146 b''
1 # Copyright (c) IPython Development Team.
2 # Distributed under the terms of the Modified BSD License.
3
1 from __future__ import print_function
4 from __future__ import print_function
2 import json
5 import json
3 import os
6 import os
7 import warnings
4
8
5 try:
9 try:
6 from jsonschema import SchemaError
10 from jsonschema import ValidationError
7 from jsonschema import Draft3Validator as Validator
11 from jsonschema import Draft4Validator as Validator
8 except ImportError as e:
12 except ImportError as e:
9 verbose_msg = """
13 verbose_msg = """
10
14
11 IPython depends on the jsonschema package: https://pypi.python.org/pypi/jsonschema
15 IPython notebook format depends on the jsonschema package:
12
16
13 Please install it first.
17 https://pypi.python.org/pypi/jsonschema
14 """
15 raise ImportError(str(e) + verbose_msg)
16
17 try:
18 import jsonpointer as jsonpointer
19 except ImportError as e:
20 verbose_msg = """
21
22 IPython depends on the jsonpointer package: https://pypi.python.org/pypi/jsonpointer
23
18
24 Please install it first.
19 Please install it first.
25 """
20 """
26 raise ImportError(str(e) + verbose_msg)
21 raise ImportError(str(e) + verbose_msg)
27
22
28 from IPython.utils.py3compat import iteritems
23 from IPython.utils.importstring import import_item
29
30
24
31 from .current import nbformat, nbformat_schema
32 schema_path = os.path.join(
33 os.path.dirname(__file__), "v%d" % nbformat, nbformat_schema)
34
25
26 validators = {}
35
27
36 def isvalid(nbjson):
28 def _relax_additional_properties(obj):
29 """relax any `additionalProperties`"""
30 if isinstance(obj, dict):
31 for key, value in obj.items():
32 if key == 'additionalProperties':
33 print(obj)
34 value = True
35 else:
36 value = _relax_additional_properties(value)
37 obj[key] = value
38 elif isinstance(obj, list):
39 for i, value in enumerate(obj):
40 obj[i] = _relax_additional_properties(value)
41 return obj
42
43 def get_validator(version=None, version_minor=None):
44 """Load the JSON schema into a Validator"""
45 if version is None:
46 from .current import nbformat as version
47
48 v = import_item("IPython.nbformat.v%s" % version)
49 current_minor = v.nbformat_minor
50 if version_minor is None:
51 version_minor = current_minor
52
53 version_tuple = (version, version_minor)
54
55 if version_tuple not in validators:
56 try:
57 v.nbformat_schema
58 except AttributeError:
59 # no validator
60 return None
61 schema_path = os.path.join(os.path.dirname(v.__file__), v.nbformat_schema)
62 with open(schema_path) as f:
63 schema_json = json.load(f)
64
65 if current_minor < version_minor:
66 # notebook from the future, relax all `additionalProperties: False` requirements
67 schema_json = _relax_additional_properties(schema_json)
68
69 validators[version_tuple] = Validator(schema_json)
70 return validators[version_tuple]
71
72 def isvalid(nbjson, ref=None, version=None, version_minor=None):
37 """Checks whether the given notebook JSON conforms to the current
73 """Checks whether the given notebook JSON conforms to the current
38 notebook format schema. Returns True if the JSON is valid, and
74 notebook format schema. Returns True if the JSON is valid, and
39 False otherwise.
75 False otherwise.
40
76
41 To see the individual errors that were encountered, please use the
77 To see the individual errors that were encountered, please use the
42 `validate` function instead.
78 `validate` function instead.
43
44 """
79 """
45
80 try:
46 errors = validate(nbjson)
81 validate(nbjson, ref, version, version_minor)
47 return errors == []
82 except ValidationError:
83 return False
84 else:
85 return True
48
86
49
87
50 def validate(nbjson):
88 def better_validation_error(error, version, version_minor):
51 """Checks whether the given notebook JSON conforms to the current
89 """Get better ValidationError on oneOf failures
52 notebook format schema, and returns the list of errors.
53
90
91 oneOf errors aren't informative.
92 if it's a cell type or output_type error,
93 try validating directly based on the type for a better error message
54 """
94 """
95 key = error.schema_path[-1]
96 if key.endswith('Of'):
97
98 ref = None
99 if isinstance(error.instance, dict):
100 if 'cell_type' in error.instance:
101 ref = error.instance['cell_type'] + "_cell"
102 elif 'output_type' in error.instance:
103 ref = error.instance['output_type']
104
105 if ref:
106 try:
107 validate(error.instance,
108 ref,
109 version=version,
110 version_minor=version_minor,
111 )
112 except ValidationError as e:
113 return better_validation_error(e, version, version_minor)
114 except:
115 # if it fails for some reason,
116 # let the original error through
117 pass
118
119 return error
120
121
122 def validate(nbjson, ref=None, version=None, version_minor=None):
123 """Checks whether the given notebook JSON conforms to the current
124 notebook format schema.
55
125
56 # load the schema file
126 Raises ValidationError if not valid.
57 with open(schema_path, 'r') as fh:
58 schema_json = json.load(fh)
59
60 # resolve internal references
61 schema = resolve_ref(schema_json)
62 schema = jsonpointer.resolve_pointer(schema, '/notebook')
63
64 # count how many errors there are
65 v = Validator(schema)
66 errors = list(v.iter_errors(nbjson))
67 return errors
68
69
70 def resolve_ref(json, schema=None):
71 """Resolve internal references within the given JSON. This essentially
72 means that dictionaries of this form:
73
74 {"$ref": "/somepointer"}
75
76 will be replaced with the resolved reference to `/somepointer`.
77 This only supports local reference to the same JSON file.
78
79 """
127 """
128 if version is None:
129 from .reader import get_version
130 (version, version_minor) = get_version(nbjson)
80
131
81 if not schema:
132 validator = get_validator(version, version_minor)
82 schema = json
83
84 # if it's a list, resolve references for each item in the list
85 if type(json) is list:
86 resolved = []
87 for item in json:
88 resolved.append(resolve_ref(item, schema=schema))
89
90 # if it's a dictionary, resolve references for each item in the
91 # dictionary
92 elif type(json) is dict:
93 resolved = {}
94 for key, ref in iteritems(json):
95
96 # if the key is equal to $ref, then replace the entire
97 # dictionary with the resolved value
98 if key == '$ref':
99 if len(json) != 1:
100 raise SchemaError(
101 "objects containing a $ref should only have one item")
102 pointer = jsonpointer.resolve_pointer(schema, ref)
103 resolved = resolve_ref(pointer, schema=schema)
104
133
105 else:
134 if validator is None:
106 resolved[key] = resolve_ref(ref, schema=schema)
135 # no validator
136 warnings.warn("No schema for validating v%s notebooks" % version, UserWarning)
137 return
107
138
108 # otherwise it's a normal object, so just return it
139 try:
109 else:
140 if ref:
110 resolved = json
141 return validator.validate(nbjson, {'$ref' : '#/definitions/%s' % ref})
142 else:
143 return validator.validate(nbjson)
144 except ValidationError as e:
145 raise better_validation_error(e, version, version_minor)
111
146
112 return resolved
@@ -14,19 +14,11 b' itself from the command line. There are two ways of running this script:'
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 # Copyright (c) IPython Development Team.
18 # Copyright (C) 2009-2011 The IPython Development Team
18 # Distributed under the terms of the Modified BSD License.
19 #
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
23
19
24 #-----------------------------------------------------------------------------
25 # Imports
26 #-----------------------------------------------------------------------------
27 from __future__ import print_function
20 from __future__ import print_function
28
21
29 # Stdlib
30 import glob
22 import glob
31 from io import BytesIO
23 from io import BytesIO
32 import os
24 import os
@@ -35,7 +27,6 b' import sys'
35 from threading import Thread, Lock, Event
27 from threading import Thread, Lock, Event
36 import warnings
28 import warnings
37
29
38 # Now, proceed to import nose itself
39 import nose.plugins.builtin
30 import nose.plugins.builtin
40 from nose.plugins.xunit import Xunit
31 from nose.plugins.xunit import Xunit
41 from nose import SkipTest
32 from nose import SkipTest
@@ -43,7 +34,6 b' from nose.core import TestProgram'
43 from nose.plugins import Plugin
34 from nose.plugins import Plugin
44 from nose.util import safe_str
35 from nose.util import safe_str
45
36
46 # Our own imports
47 from IPython.utils.process import is_cmd_found
37 from IPython.utils.process import is_cmd_found
48 from IPython.utils.importstring import import_item
38 from IPython.utils.importstring import import_item
49 from IPython.testing.plugin.ipdoctest import IPythonDoctest
39 from IPython.testing.plugin.ipdoctest import IPythonDoctest
@@ -148,7 +138,6 b" have['mistune'] = test_for('mistune')"
148 have['requests'] = test_for('requests')
138 have['requests'] = test_for('requests')
149 have['sphinx'] = test_for('sphinx')
139 have['sphinx'] = test_for('sphinx')
150 have['jsonschema'] = test_for('jsonschema')
140 have['jsonschema'] = test_for('jsonschema')
151 have['jsonpointer'] = test_for('jsonpointer')
152 have['casperjs'] = is_cmd_found('casperjs')
141 have['casperjs'] = is_cmd_found('casperjs')
153 have['phantomjs'] = is_cmd_found('phantomjs')
142 have['phantomjs'] = is_cmd_found('phantomjs')
154 have['slimerjs'] = is_cmd_found('slimerjs')
143 have['slimerjs'] = is_cmd_found('slimerjs')
@@ -268,7 +257,7 b" test_sections['qt'].requires('zmq', 'qt', 'pygments')"
268
257
269 # html:
258 # html:
270 sec = test_sections['html']
259 sec = test_sections['html']
271 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema', 'jsonpointer')
260 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema')
272 # The notebook 'static' directory contains JS, css and other
261 # The notebook 'static' directory contains JS, css and other
273 # files for web serving. Occasionally projects may put a .py
262 # files for web serving. Occasionally projects may put a .py
274 # file in there (MathJax ships a conf.py), so we might as
263 # file in there (MathJax ships a conf.py), so we might as
@@ -286,7 +275,7 b" test_sections['config'].exclude('profile')"
286
275
287 # nbconvert:
276 # nbconvert:
288 sec = test_sections['nbconvert']
277 sec = test_sections['nbconvert']
289 sec.requires('pygments', 'jinja2', 'jsonschema', 'jsonpointer', 'mistune')
278 sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune')
290 # Exclude nbconvert directories containing config files used to test.
279 # Exclude nbconvert directories containing config files used to test.
291 # Executing the config files with iptest would cause an exception.
280 # Executing the config files with iptest would cause an exception.
292 sec.exclude('tests.files')
281 sec.exclude('tests.files')
@@ -296,7 +285,7 b" if not have['tornado']:"
296 sec.exclude('nbconvert.post_processors.tests.test_serve')
285 sec.exclude('nbconvert.post_processors.tests.test_serve')
297
286
298 # nbformat:
287 # nbformat:
299 test_sections['nbformat'].requires('jsonschema', 'jsonpointer')
288 test_sections['nbformat'].requires('jsonschema')
300
289
301 #-----------------------------------------------------------------------------
290 #-----------------------------------------------------------------------------
302 # Functions and classes
291 # Functions and classes
@@ -218,7 +218,7 b' def all_js_groups():'
218 class JSController(TestController):
218 class JSController(TestController):
219 """Run CasperJS tests """
219 """Run CasperJS tests """
220 requirements = ['zmq', 'tornado', 'jinja2', 'casperjs', 'sqlite3',
220 requirements = ['zmq', 'tornado', 'jinja2', 'casperjs', 'sqlite3',
221 'jsonschema', 'jsonpointer']
221 'jsonschema']
222 display_slimer_output = False
222 display_slimer_output = False
223
223
224 def __init__(self, section, xunit=True, engine='phantomjs'):
224 def __init__(self, section, xunit=True, engine='phantomjs'):
@@ -465,3 +465,23 b" def help_all_output_test(subcommand=''):"
465 nt.assert_in("Class parameters", out)
465 nt.assert_in("Class parameters", out)
466 return out, err
466 return out, err
467
467
468 def assert_big_text_equal(a, b, chunk_size=80):
469 """assert that large strings are equal
470
471 Zooms in on first chunk that differs,
472 to give better info than vanilla assertEqual for large text blobs.
473 """
474 for i in range(0, len(a), chunk_size):
475 chunk_a = a[i:i + chunk_size]
476 chunk_b = b[i:i + chunk_size]
477 nt.assert_equal(chunk_a, chunk_b, "[offset: %i]\n%r != \n%r" % (
478 i, chunk_a, chunk_b))
479
480 if len(a) > len(b):
481 nt.fail("Length doesn't match (%i > %i). Extra text:\n%r" % (
482 len(a), len(b), a[len(b):]
483 ))
484 elif len(a) < len(b):
485 nt.fail("Length doesn't match (%i < %i). Extra text:\n%r" % (
486 len(a), len(b), b[len(a):]
487 ))
@@ -275,7 +275,7 b' extras_require = dict('
275 doc = ['Sphinx>=1.1', 'numpydoc'],
275 doc = ['Sphinx>=1.1', 'numpydoc'],
276 test = ['nose>=0.10.1'],
276 test = ['nose>=0.10.1'],
277 terminal = [],
277 terminal = [],
278 nbformat = ['jsonschema>=2.0', 'jsonpointer>=1.3'],
278 nbformat = ['jsonschema>=2.0'],
279 notebook = ['tornado>=3.1', 'pyzmq>=2.1.11', 'jinja2', 'pygments', 'mistune>=0.3.1'],
279 notebook = ['tornado>=3.1', 'pyzmq>=2.1.11', 'jinja2', 'pygments', 'mistune>=0.3.1'],
280 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
280 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
281 )
281 )
@@ -194,7 +194,10 b' def find_package_data():'
194 'preprocessors/tests/files/*.*',
194 'preprocessors/tests/files/*.*',
195 ],
195 ],
196 'IPython.nbconvert.filters' : ['marked.js'],
196 'IPython.nbconvert.filters' : ['marked.js'],
197 'IPython.nbformat' : ['tests/*.ipynb','v3/v3.withref.json']
197 'IPython.nbformat' : [
198 'tests/*.ipynb',
199 'v3/nbformat.v3.schema.json',
200 ]
198 }
201 }
199
202
200 return package_data
203 return package_data
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now