##// END OF EJS Templates
Use Draft4 JSON Schema for both v3 and v4...
MinRK -
Show More
@@ -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"]
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", "collapsed", "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 }
@@ -1,5 +1,8 b''
1 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 6 from __future__ import print_function
4 7
5 8 import re
@@ -17,7 +20,7 b' from IPython.nbformat import v3 as _v_latest'
17 20 from .reader import reads as reader_reads
18 21 from .reader import versions
19 22 from .convert import convert
20 from .validator import validate
23 from .validator import validate, ValidationError
21 24
22 25 from IPython.utils.log import get_logger
23 26
@@ -60,11 +63,10 b' def reads_json(nbjson, **kwargs):'
60 63 """
61 64 nb = reader_reads(nbjson, **kwargs)
62 65 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))
66 try:
67 validate(nb_current)
68 except ValidationError as e:
69 get_logger().error("Notebook JSON is invalid: %s", e)
68 70 return nb_current
69 71
70 72
@@ -73,11 +75,10 b' def writes_json(nb, **kwargs):'
73 75 any JSON format errors are detected.
74 76
75 77 """
76 errors = validate(nb)
77 if errors:
78 get_logger().error(
79 "Notebook JSON is invalid (%d errors detected during write)",
80 len(errors))
78 try:
79 validate(nb)
80 except ValidationError as e:
81 get_logger().error("Notebook JSON is invalid: %s", e)
81 82 nbjson = versions[current_nbformat].writes_json(nb, **kwargs)
82 83 return nbjson
83 84
@@ -1,23 +1,14 b''
1 """
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 #-----------------------------------------------------------------------------
1 """Test nbformat.validator"""
10 2
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
14 5
15 6 import os
16 7
17 8 from .base import TestsBase
18 from jsonschema import SchemaError
9 from jsonschema import ValidationError
19 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 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 20 def test_nb2(self):
34 21 """Test that a v2 notebook converted to v3 passes validation"""
35 22 with self.fopen(u'test2.ipynb', u'r') as f:
36 23 nb = read(f, u'json')
37 self.assertEqual(validate(nb), [])
24 validate(nb)
38 25 self.assertEqual(isvalid(nb), True)
39 26
40 27 def test_nb3(self):
41 28 """Test that a v3 notebook passes validation"""
42 29 with self.fopen(u'test3.ipynb', u'r') as f:
43 30 nb = read(f, u'json')
44 self.assertEqual(validate(nb), [])
31 validate(nb)
45 32 self.assertEqual(isvalid(nb), True)
46 33
47 34 def test_invalid(self):
@@ -52,22 +39,7 b' class TestValidator(TestsBase):'
52 39 # - one cell has an invalid level
53 40 with self.fopen(u'invalid.ipynb', u'r') as f:
54 41 nb = read(f, u'json')
55 self.assertEqual(len(validate(nb)), 3)
42 with self.assertRaises(ValidationError):
43 validate(nb)
56 44 self.assertEqual(isvalid(nb), False)
57 45
58 def test_resolve_ref(self):
59 """Test that references are correctly resolved"""
60 # make sure it resolves the ref correctly
61 json = {"abc": "def", "ghi": {"$ref": "/abc"}}
62 resolved = resolve_ref(json)
63 self.assertEqual(resolved, {"abc": "def", "ghi": "def"})
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
70 # make sure it can handle json with no reference
71 json = {"abc": "def"}
72 resolved = resolve_ref(json)
73 self.assertEqual(resolved, json)
@@ -22,7 +22,7 b' from IPython.utils.py3compat import cast_unicode, unicode_type'
22 22 # Change this when incrementing the nbformat version
23 23 nbformat = 3
24 24 nbformat_minor = 0
25 nbformat_schema = 'v3.withref.json'
25 nbformat_schema = 'nbformat.v3.schema.json'
26 26
27 27 class NotebookNode(Struct):
28 28 pass
@@ -1,112 +1,72 b''
1 # Copyright (c) IPython Development Team.
2 # Distributed under the terms of the Modified BSD License.
3
1 4 from __future__ import print_function
2 5 import json
3 6 import os
4 7
5 8 try:
6 from jsonschema import SchemaError
7 from jsonschema import Draft3Validator as Validator
9 from jsonschema import ValidationError
10 from jsonschema import Draft4Validator as Validator
8 11 except ImportError as e:
9 12 verbose_msg = """
10 13
11 IPython depends on the jsonschema package: https://pypi.python.org/pypi/jsonschema
14 IPython notebook format depends on the jsonschema package:
12 15
13 Please install it first.
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
16 https://pypi.python.org/pypi/jsonschema
23 17
24 18 Please install it first.
25 19 """
26 20 raise ImportError(str(e) + verbose_msg)
27 21
28 from IPython.utils.py3compat import iteritems
22 from IPython.utils.importstring import import_item
23
29 24
25 validators = {}
30 26
31 from .current import nbformat, nbformat_schema
32 schema_path = os.path.join(
33 os.path.dirname(__file__), "v%d" % nbformat, nbformat_schema)
27 def get_validator(version=None):
28 """Load the JSON schema into a Validator"""
29 if version is None:
30 from .current import nbformat as version
34 31
32 if version not in validators:
33 v = import_item("IPython.nbformat.v%s" % version)
34 schema_path = os.path.join(os.path.dirname(v.__file__), v.nbformat_schema)
35 with open(schema_path) as f:
36 schema_json = json.load(f)
37 validators[version] = Validator(schema_json)
38 return validators[version]
35 39
36 def isvalid(nbjson):
40 def isvalid(nbjson, ref=None, version=None):
37 41 """Checks whether the given notebook JSON conforms to the current
38 42 notebook format schema. Returns True if the JSON is valid, and
39 43 False otherwise.
40 44
41 45 To see the individual errors that were encountered, please use the
42 46 `validate` function instead.
43
44 47 """
45
46 errors = validate(nbjson)
47 return errors == []
48 try:
49 validate(nbjson, ref, version)
50 except ValidationError:
51 return False
52 else:
53 return True
48 54
49 55
50 def validate(nbjson):
56 def validate(nbjson, ref=None, version=None):
51 57 """Checks whether the given notebook JSON conforms to the current
52 notebook format schema, and returns the list of errors.
53
54 """
55
56 # load the schema file
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.
58 notebook format schema.
78 59
60 Raises ValidationError if not valid.
79 61 """
62 if version is None:
63 from .current import nbformat
64 version = nbjson.get('nbformat', nbformat)
80 65
81 if not schema:
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
105 else:
106 resolved[key] = resolve_ref(ref, schema=schema)
66 validator = get_validator(version)
107 67
108 # otherwise it's a normal object, so just return it
68 if ref:
69 return validator.validate(nbjson, {'$ref' : '#/definitions/%s' % ref})
109 70 else:
110 resolved = json
71 return validator.validate(nbjson)
111 72
112 return resolved
@@ -218,7 +218,7 b' def all_js_groups():'
218 218 class JSController(TestController):
219 219 """Run CasperJS tests """
220 220 requirements = ['zmq', 'tornado', 'jinja2', 'casperjs', 'sqlite3',
221 'jsonschema', 'jsonpointer']
221 'jsonschema']
222 222 display_slimer_output = False
223 223
224 224 def __init__(self, section, xunit=True, engine='phantomjs'):
@@ -275,7 +275,7 b' extras_require = dict('
275 275 doc = ['Sphinx>=1.1', 'numpydoc'],
276 276 test = ['nose>=0.10.1'],
277 277 terminal = [],
278 nbformat = ['jsonschema>=2.0', 'jsonpointer>=1.3'],
278 nbformat = ['jsonschema>=2.0'],
279 279 notebook = ['tornado>=3.1', 'pyzmq>=2.1.11', 'jinja2', 'pygments', 'mistune>=0.3.1'],
280 280 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
281 281 )
@@ -194,7 +194,10 b' def find_package_data():'
194 194 'preprocessors/tests/files/*.*',
195 195 ],
196 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 203 return package_data
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now