Show More
@@ -0,0 +1,4 b'' | |||||
|
1 | _jsonpointer.py | |||
|
2 | ||||
|
3 | extracted from jsonpointer.py following commit : | |||
|
4 | 97a06ecb9dde82bd54dc269052841035ca2a1390 |
@@ -0,0 +1,4 b'' | |||||
|
1 | try: | |||
|
2 | from jsonpointer import * | |||
|
3 | except ImportError : | |||
|
4 | from _jsonpointer import * |
@@ -0,0 +1,228 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | # | |||
|
3 | # python-json-pointer - An implementation of the JSON Pointer syntax | |||
|
4 | # https://github.com/stefankoegl/python-json-pointer | |||
|
5 | # | |||
|
6 | # Copyright (c) 2011 Stefan KΓΆgl <stefan@skoegl.net> | |||
|
7 | # All rights reserved. | |||
|
8 | # | |||
|
9 | # Redistribution and use in source and binary forms, with or without | |||
|
10 | # modification, are permitted provided that the following conditions | |||
|
11 | # are met: | |||
|
12 | # | |||
|
13 | # 1. Redistributions of source code must retain the above copyright | |||
|
14 | # notice, this list of conditions and the following disclaimer. | |||
|
15 | # 2. Redistributions in binary form must reproduce the above copyright | |||
|
16 | # notice, this list of conditions and the following disclaimer in the | |||
|
17 | # documentation and/or other materials provided with the distribution. | |||
|
18 | # 3. The name of the author may not be used to endorse or promote products | |||
|
19 | # derived from this software without specific prior written permission. | |||
|
20 | # | |||
|
21 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |||
|
22 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
|
23 | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |||
|
24 | # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |||
|
25 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||
|
26 | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
|
27 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||
|
28 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
|
29 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |||
|
30 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
|
31 | # | |||
|
32 | ||||
|
33 | """ Identify specific nodes in a JSON document according to | |||
|
34 | http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-04 """ | |||
|
35 | ||||
|
36 | # Will be parsed by setup.py to determine package metadata | |||
|
37 | __author__ = 'Stefan KΓΆgl <stefan@skoegl.net>' | |||
|
38 | __version__ = '0.3' | |||
|
39 | __website__ = 'https://github.com/stefankoegl/python-json-pointer' | |||
|
40 | __license__ = 'Modified BSD License' | |||
|
41 | ||||
|
42 | ||||
|
43 | try: | |||
|
44 | from urllib import unquote | |||
|
45 | from itertools import izip | |||
|
46 | except ImportError: # Python 3 | |||
|
47 | from urllib.parse import unquote | |||
|
48 | izip = zip | |||
|
49 | ||||
|
50 | from itertools import tee | |||
|
51 | ||||
|
52 | ||||
|
53 | class JsonPointerException(Exception): | |||
|
54 | pass | |||
|
55 | ||||
|
56 | ||||
|
57 | _nothing = object() | |||
|
58 | ||||
|
59 | ||||
|
60 | def resolve_pointer(doc, pointer, default=_nothing): | |||
|
61 | """ | |||
|
62 | Resolves pointer against doc and returns the referenced object | |||
|
63 | ||||
|
64 | >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}} | |||
|
65 | ||||
|
66 | >>> resolve_pointer(obj, '') == obj | |||
|
67 | True | |||
|
68 | ||||
|
69 | >>> resolve_pointer(obj, '/foo') == obj['foo'] | |||
|
70 | True | |||
|
71 | ||||
|
72 | >>> resolve_pointer(obj, '/foo/another%20prop') == obj['foo']['another prop'] | |||
|
73 | True | |||
|
74 | ||||
|
75 | >>> resolve_pointer(obj, '/foo/another%20prop/baz') == obj['foo']['another prop']['baz'] | |||
|
76 | True | |||
|
77 | ||||
|
78 | >>> resolve_pointer(obj, '/foo/anArray/0') == obj['foo']['anArray'][0] | |||
|
79 | True | |||
|
80 | ||||
|
81 | >>> resolve_pointer(obj, '/some/path', None) == None | |||
|
82 | True | |||
|
83 | ||||
|
84 | """ | |||
|
85 | ||||
|
86 | pointer = JsonPointer(pointer) | |||
|
87 | return pointer.resolve(doc, default) | |||
|
88 | ||||
|
89 | ||||
|
90 | def set_pointer(doc, pointer, value): | |||
|
91 | """ | |||
|
92 | Set a field to a given value | |||
|
93 | ||||
|
94 | The field is indicates by a base location that is given in the constructor, | |||
|
95 | and an optional relative location in the call to set. If the path doesn't | |||
|
96 | exist, it is created if possible | |||
|
97 | ||||
|
98 | >>> obj = {"foo": 2} | |||
|
99 | >>> pointer = JsonPointer('/bar') | |||
|
100 | >>> pointer.set(obj, 'one', '0') | |||
|
101 | >>> pointer.set(obj, 'two', '1') | |||
|
102 | >>> obj | |||
|
103 | {'foo': 2, 'bar': ['one', 'two']} | |||
|
104 | ||||
|
105 | >>> obj = {"foo": 2, "bar": []} | |||
|
106 | >>> pointer = JsonPointer('/bar') | |||
|
107 | >>> pointer.set(obj, 5, '0/x') | |||
|
108 | >>> obj | |||
|
109 | {'foo': 2, 'bar': [{'x': 5}]} | |||
|
110 | ||||
|
111 | >>> obj = {'foo': 2, 'bar': [{'x': 5}]} | |||
|
112 | >>> pointer = JsonPointer('/bar/0') | |||
|
113 | >>> pointer.set(obj, 10, 'y/0') | |||
|
114 | >>> obj == {'foo': 2, 'bar': [{'y': [10], 'x': 5}]} | |||
|
115 | True | |||
|
116 | """ | |||
|
117 | ||||
|
118 | pointer = JsonPointer(pointer) | |||
|
119 | pointer.set(doc, value) | |||
|
120 | ||||
|
121 | ||||
|
122 | class JsonPointer(object): | |||
|
123 | """ A JSON Pointer that can reference parts of an JSON document """ | |||
|
124 | ||||
|
125 | def __init__(self, pointer): | |||
|
126 | parts = pointer.split('/') | |||
|
127 | if parts.pop(0) != '': | |||
|
128 | raise JsonPointerException('location must starts with /') | |||
|
129 | ||||
|
130 | parts = map(unquote, parts) | |||
|
131 | parts = [part.replace('~1', '/') for part in parts] | |||
|
132 | parts = [part.replace('~0', '~') for part in parts] | |||
|
133 | self.parts = parts | |||
|
134 | ||||
|
135 | ||||
|
136 | ||||
|
137 | def resolve(self, doc, default=_nothing): | |||
|
138 | """Resolves the pointer against doc and returns the referenced object""" | |||
|
139 | ||||
|
140 | for part in self.parts: | |||
|
141 | ||||
|
142 | try: | |||
|
143 | doc = self.walk(doc, part) | |||
|
144 | except JsonPointerException: | |||
|
145 | if default is _nothing: | |||
|
146 | raise | |||
|
147 | else: | |||
|
148 | return default | |||
|
149 | ||||
|
150 | return doc | |||
|
151 | ||||
|
152 | ||||
|
153 | get = resolve | |||
|
154 | ||||
|
155 | ||||
|
156 | def set(self, doc, value, path=None): | |||
|
157 | """ Sets a field of doc to value | |||
|
158 | ||||
|
159 | The location of the field is given by the pointers base location and | |||
|
160 | the optional path which is relative to the base location """ | |||
|
161 | ||||
|
162 | fullpath = list(self.parts) | |||
|
163 | ||||
|
164 | if path: | |||
|
165 | fullpath += path.split('/') | |||
|
166 | ||||
|
167 | ||||
|
168 | for part, nextpart in pairwise(fullpath): | |||
|
169 | try: | |||
|
170 | doc = self.walk(doc, part) | |||
|
171 | except JsonPointerException: | |||
|
172 | step_val = [] if nextpart.isdigit() else {} | |||
|
173 | doc = self._set_value(doc, part, step_val) | |||
|
174 | ||||
|
175 | self._set_value(doc, fullpath[-1], value) | |||
|
176 | ||||
|
177 | ||||
|
178 | @staticmethod | |||
|
179 | def _set_value(doc, part, value): | |||
|
180 | part = int(part) if part.isdigit() else part | |||
|
181 | ||||
|
182 | if isinstance(doc, dict): | |||
|
183 | doc[part] = value | |||
|
184 | ||||
|
185 | if isinstance(doc, list): | |||
|
186 | if len(doc) < part: | |||
|
187 | doc[part] = value | |||
|
188 | ||||
|
189 | if len(doc) == part: | |||
|
190 | doc.append(value) | |||
|
191 | ||||
|
192 | else: | |||
|
193 | raise IndexError | |||
|
194 | ||||
|
195 | return doc[part] | |||
|
196 | ||||
|
197 | ||||
|
198 | def walk(self, doc, part): | |||
|
199 | """ Walks one step in doc and returns the referenced part """ | |||
|
200 | ||||
|
201 | # Its not clear if a location "1" should be considered as 1 or "1" | |||
|
202 | # We prefer the integer-variant if possible | |||
|
203 | part_variants = self._try_parse(part) + [part] | |||
|
204 | ||||
|
205 | for variant in part_variants: | |||
|
206 | try: | |||
|
207 | return doc[variant] | |||
|
208 | except: | |||
|
209 | continue | |||
|
210 | ||||
|
211 | raise JsonPointerException("'%s' not found in %s" % (part, doc)) | |||
|
212 | ||||
|
213 | ||||
|
214 | @staticmethod | |||
|
215 | def _try_parse(val, cls=int): | |||
|
216 | try: | |||
|
217 | return [cls(val)] | |||
|
218 | except: | |||
|
219 | return [] | |||
|
220 | ||||
|
221 | ||||
|
222 | ||||
|
223 | def pairwise(iterable): | |||
|
224 | "s -> (s0,s1), (s1,s2), (s2, s3), ..." | |||
|
225 | a, b = tee(iterable) | |||
|
226 | for _ in b: | |||
|
227 | break | |||
|
228 | return izip(a, b) |
@@ -0,0 +1,19 b'' | |||||
|
1 | Copyright (c) 2011 Julian Berman | |||
|
2 | ||||
|
3 | Permission is hereby granted, free of charge, to any person obtaining a copy | |||
|
4 | of this software and associated documentation files (the "Software"), to deal | |||
|
5 | in the Software without restriction, including without limitation the rights | |||
|
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
|
7 | copies of the Software, and to permit persons to whom the Software is | |||
|
8 | furnished to do so, subject to the following conditions: | |||
|
9 | ||||
|
10 | The above copyright notice and this permission notice shall be included in | |||
|
11 | all copies or substantial portions of the Software. | |||
|
12 | ||||
|
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
|
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
|
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
|
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
|
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
|
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
|
19 | THE SOFTWARE. |
@@ -0,0 +1,4 b'' | |||||
|
1 | try: | |||
|
2 | from jsonschema import * | |||
|
3 | except ImportError : | |||
|
4 | from _jsonschema import * |
This diff has been collapsed as it changes many lines, (734 lines changed) Show them Hide them | |||||
@@ -0,0 +1,734 b'' | |||||
|
1 | """ | |||
|
2 | An implementation of JSON Schema for Python | |||
|
3 | ||||
|
4 | The main functionality is provided by the validator classes for each of the | |||
|
5 | supported JSON Schema versions. | |||
|
6 | ||||
|
7 | Most commonly, the :function:`validate` function is the quickest way to simply | |||
|
8 | validate a given instance under a schema, and will create a validator for you. | |||
|
9 | ||||
|
10 | """ | |||
|
11 | ||||
|
12 | from __future__ import division, unicode_literals | |||
|
13 | ||||
|
14 | import collections | |||
|
15 | import itertools | |||
|
16 | import operator | |||
|
17 | import re | |||
|
18 | import sys | |||
|
19 | import warnings | |||
|
20 | ||||
|
21 | ||||
|
22 | __version__ = "0.7" | |||
|
23 | ||||
|
24 | FLOAT_TOLERANCE = 10 ** -15 | |||
|
25 | PY3 = sys.version_info[0] >= 3 | |||
|
26 | ||||
|
27 | if PY3: | |||
|
28 | basestring = unicode = str | |||
|
29 | iteritems = operator.methodcaller("items") | |||
|
30 | from urllib.parse import unquote | |||
|
31 | else: | |||
|
32 | from itertools import izip as zip | |||
|
33 | iteritems = operator.methodcaller("iteritems") | |||
|
34 | from urllib import unquote | |||
|
35 | ||||
|
36 | ||||
|
37 | class UnknownType(Exception): | |||
|
38 | """ | |||
|
39 | An unknown type was given. | |||
|
40 | ||||
|
41 | """ | |||
|
42 | ||||
|
43 | ||||
|
44 | class InvalidRef(Exception): | |||
|
45 | """ | |||
|
46 | An invalid reference was given. | |||
|
47 | ||||
|
48 | """ | |||
|
49 | ||||
|
50 | ||||
|
51 | class SchemaError(Exception): | |||
|
52 | """ | |||
|
53 | The provided schema is malformed. | |||
|
54 | ||||
|
55 | The same attributes exist for ``SchemaError``s as for ``ValidationError``s. | |||
|
56 | ||||
|
57 | """ | |||
|
58 | ||||
|
59 | validator = None | |||
|
60 | ||||
|
61 | def __init__(self, message): | |||
|
62 | super(SchemaError, self).__init__(message) | |||
|
63 | self.message = message | |||
|
64 | self.path = [] | |||
|
65 | ||||
|
66 | ||||
|
67 | class ValidationError(Exception): | |||
|
68 | """ | |||
|
69 | The instance didn't properly validate with the provided schema. | |||
|
70 | ||||
|
71 | Relevant attributes are: | |||
|
72 | * ``message`` : a human readable message explaining the error | |||
|
73 | * ``path`` : a list containing the path to the offending element (or [] | |||
|
74 | if the error happened globally) in *reverse* order (i.e. | |||
|
75 | deepest index first). | |||
|
76 | ||||
|
77 | """ | |||
|
78 | ||||
|
79 | # the failing validator will be set externally at whatever recursion level | |||
|
80 | # is immediately above the validation failure | |||
|
81 | validator = None | |||
|
82 | ||||
|
83 | def __init__(self, message): | |||
|
84 | super(ValidationError, self).__init__(message) | |||
|
85 | self.message = message | |||
|
86 | ||||
|
87 | # Any validator that recurses must append to the ValidationError's | |||
|
88 | # path (e.g., properties and items) | |||
|
89 | self.path = [] | |||
|
90 | ||||
|
91 | ||||
|
92 | class Draft3Validator(object): | |||
|
93 | """ | |||
|
94 | A validator for JSON Schema draft 3. | |||
|
95 | ||||
|
96 | """ | |||
|
97 | ||||
|
98 | DEFAULT_TYPES = { | |||
|
99 | "array" : list, "boolean" : bool, "integer" : int, "null" : type(None), | |||
|
100 | "number" : (int, float), "object" : dict, "string" : basestring, | |||
|
101 | } | |||
|
102 | ||||
|
103 | def __init__(self, schema, types=()): | |||
|
104 | """ | |||
|
105 | Initialize a validator. | |||
|
106 | ||||
|
107 | ``schema`` should be a *valid* JSON Schema object already converted to | |||
|
108 | a native Python object (typically a dict via ``json.load``). | |||
|
109 | ||||
|
110 | ``types`` is a mapping (or iterable of 2-tuples) containing additional | |||
|
111 | types or alternate types to verify via the 'type' property. For | |||
|
112 | instance, the default types for the 'number' JSON Schema type are | |||
|
113 | ``int`` and ``float``. To override this behavior (e.g. for also | |||
|
114 | allowing ``decimal.Decimal``), pass ``types={"number" : (int, float, | |||
|
115 | decimal.Decimal)} *including* the default types if so desired, which | |||
|
116 | are fairly obvious but can be accessed via the ``DEFAULT_TYPES`` | |||
|
117 | attribute on this class if necessary. | |||
|
118 | ||||
|
119 | """ | |||
|
120 | ||||
|
121 | self._types = dict(self.DEFAULT_TYPES) | |||
|
122 | self._types.update(types) | |||
|
123 | self._types["any"] = tuple(self._types.values()) | |||
|
124 | ||||
|
125 | self.schema = schema | |||
|
126 | ||||
|
127 | def is_type(self, instance, type): | |||
|
128 | """ | |||
|
129 | Check if an ``instance`` is of the provided (JSON Schema) ``type``. | |||
|
130 | ||||
|
131 | """ | |||
|
132 | ||||
|
133 | if type not in self._types: | |||
|
134 | raise UnknownType(type) | |||
|
135 | type = self._types[type] | |||
|
136 | ||||
|
137 | # bool inherits from int, so ensure bools aren't reported as integers | |||
|
138 | if isinstance(instance, bool): | |||
|
139 | type = _flatten(type) | |||
|
140 | if int in type and bool not in type: | |||
|
141 | return False | |||
|
142 | return isinstance(instance, type) | |||
|
143 | ||||
|
144 | def is_valid(self, instance, _schema=None): | |||
|
145 | """ | |||
|
146 | Check if the ``instance`` is valid under the current schema. | |||
|
147 | ||||
|
148 | Returns a bool indicating whether validation succeeded. | |||
|
149 | ||||
|
150 | """ | |||
|
151 | ||||
|
152 | error = next(self.iter_errors(instance, _schema), None) | |||
|
153 | return error is None | |||
|
154 | ||||
|
155 | @classmethod | |||
|
156 | def check_schema(cls, schema): | |||
|
157 | """ | |||
|
158 | Validate a ``schema`` against the meta-schema to see if it is valid. | |||
|
159 | ||||
|
160 | """ | |||
|
161 | ||||
|
162 | for error in cls(cls.META_SCHEMA).iter_errors(schema): | |||
|
163 | s = SchemaError(error.message) | |||
|
164 | s.path = error.path | |||
|
165 | s.validator = error.validator | |||
|
166 | # I think we're safer raising these always, not yielding them | |||
|
167 | raise s | |||
|
168 | ||||
|
169 | def iter_errors(self, instance, _schema=None): | |||
|
170 | """ | |||
|
171 | Lazily yield each of the errors in the given ``instance``. | |||
|
172 | ||||
|
173 | """ | |||
|
174 | ||||
|
175 | if _schema is None: | |||
|
176 | _schema = self.schema | |||
|
177 | ||||
|
178 | for k, v in iteritems(_schema): | |||
|
179 | validator = getattr(self, "validate_%s" % (k.lstrip("$"),), None) | |||
|
180 | ||||
|
181 | if validator is None: | |||
|
182 | continue | |||
|
183 | ||||
|
184 | errors = validator(v, instance, _schema) or () | |||
|
185 | for error in errors: | |||
|
186 | # if the validator hasn't already been set (due to recursion) | |||
|
187 | # make sure to set it | |||
|
188 | error.validator = error.validator or k | |||
|
189 | yield error | |||
|
190 | ||||
|
191 | def validate(self, *args, **kwargs): | |||
|
192 | """ | |||
|
193 | Validate an ``instance`` under the given ``schema``. | |||
|
194 | ||||
|
195 | """ | |||
|
196 | ||||
|
197 | for error in self.iter_errors(*args, **kwargs): | |||
|
198 | raise error | |||
|
199 | ||||
|
200 | def validate_type(self, types, instance, schema): | |||
|
201 | types = _list(types) | |||
|
202 | ||||
|
203 | for type in types: | |||
|
204 | # Ouch. Brain hurts. Two paths here, either we have a schema, then | |||
|
205 | # check if the instance is valid under it | |||
|
206 | if (( | |||
|
207 | self.is_type(type, "object") and | |||
|
208 | self.is_valid(instance, type) | |||
|
209 | ||||
|
210 | # Or we have a type as a string, just check if the instance is that | |||
|
211 | # type. Also, HACK: we can reach the `or` here if skip_types is | |||
|
212 | # something other than error. If so, bail out. | |||
|
213 | ||||
|
214 | ) or ( | |||
|
215 | self.is_type(type, "string") and | |||
|
216 | (self.is_type(instance, type) or type not in self._types) | |||
|
217 | )): | |||
|
218 | return | |||
|
219 | else: | |||
|
220 | yield ValidationError(_types_msg(instance, types)) | |||
|
221 | ||||
|
222 | def validate_properties(self, properties, instance, schema): | |||
|
223 | if not self.is_type(instance, "object"): | |||
|
224 | return | |||
|
225 | ||||
|
226 | for property, subschema in iteritems(properties): | |||
|
227 | if property in instance: | |||
|
228 | for error in self.iter_errors(instance[property], subschema): | |||
|
229 | error.path.append(property) | |||
|
230 | yield error | |||
|
231 | elif subschema.get("required", False): | |||
|
232 | error = ValidationError( | |||
|
233 | "%r is a required property" % (property,) | |||
|
234 | ) | |||
|
235 | error.path.append(property) | |||
|
236 | error.validator = "required" | |||
|
237 | yield error | |||
|
238 | ||||
|
239 | def validate_patternProperties(self, patternProperties, instance, schema): | |||
|
240 | for pattern, subschema in iteritems(patternProperties): | |||
|
241 | for k, v in iteritems(instance): | |||
|
242 | if re.match(pattern, k): | |||
|
243 | for error in self.iter_errors(v, subschema): | |||
|
244 | yield error | |||
|
245 | ||||
|
246 | def validate_additionalProperties(self, aP, instance, schema): | |||
|
247 | if not self.is_type(instance, "object"): | |||
|
248 | return | |||
|
249 | ||||
|
250 | extras = set(_find_additional_properties(instance, schema)) | |||
|
251 | ||||
|
252 | if self.is_type(aP, "object"): | |||
|
253 | for extra in extras: | |||
|
254 | for error in self.iter_errors(instance[extra], aP): | |||
|
255 | yield error | |||
|
256 | elif not aP and extras: | |||
|
257 | error = "Additional properties are not allowed (%s %s unexpected)" | |||
|
258 | yield ValidationError(error % _extras_msg(extras)) | |||
|
259 | ||||
|
260 | def validate_dependencies(self, dependencies, instance, schema): | |||
|
261 | if not self.is_type(instance, "object"): | |||
|
262 | return | |||
|
263 | ||||
|
264 | for property, dependency in iteritems(dependencies): | |||
|
265 | if property not in instance: | |||
|
266 | continue | |||
|
267 | ||||
|
268 | if self.is_type(dependency, "object"): | |||
|
269 | for error in self.iter_errors(instance, dependency): | |||
|
270 | yield error | |||
|
271 | else: | |||
|
272 | dependencies = _list(dependency) | |||
|
273 | for dependency in dependencies: | |||
|
274 | if dependency not in instance: | |||
|
275 | yield ValidationError( | |||
|
276 | "%r is a dependency of %r" % (dependency, property) | |||
|
277 | ) | |||
|
278 | ||||
|
279 | def validate_items(self, items, instance, schema): | |||
|
280 | if not self.is_type(instance, "array"): | |||
|
281 | return | |||
|
282 | ||||
|
283 | if self.is_type(items, "object"): | |||
|
284 | for index, item in enumerate(instance): | |||
|
285 | for error in self.iter_errors(item, items): | |||
|
286 | error.path.append(index) | |||
|
287 | yield error | |||
|
288 | else: | |||
|
289 | for (index, item), subschema in zip(enumerate(instance), items): | |||
|
290 | for error in self.iter_errors(item, subschema): | |||
|
291 | error.path.append(index) | |||
|
292 | yield error | |||
|
293 | ||||
|
294 | def validate_additionalItems(self, aI, instance, schema): | |||
|
295 | if not self.is_type(instance, "array"): | |||
|
296 | return | |||
|
297 | if not self.is_type(schema.get("items"), "array"): | |||
|
298 | return | |||
|
299 | ||||
|
300 | if self.is_type(aI, "object"): | |||
|
301 | for item in instance[len(schema):]: | |||
|
302 | for error in self.iter_errors(item, aI): | |||
|
303 | yield error | |||
|
304 | elif not aI and len(instance) > len(schema.get("items", [])): | |||
|
305 | error = "Additional items are not allowed (%s %s unexpected)" | |||
|
306 | yield ValidationError( | |||
|
307 | error % _extras_msg(instance[len(schema.get("items", [])):]) | |||
|
308 | ) | |||
|
309 | ||||
|
310 | def validate_minimum(self, minimum, instance, schema): | |||
|
311 | if not self.is_type(instance, "number"): | |||
|
312 | return | |||
|
313 | ||||
|
314 | instance = float(instance) | |||
|
315 | if schema.get("exclusiveMinimum", False): | |||
|
316 | failed = instance <= minimum | |||
|
317 | cmp = "less than or equal to" | |||
|
318 | else: | |||
|
319 | failed = instance < minimum | |||
|
320 | cmp = "less than" | |||
|
321 | ||||
|
322 | if failed: | |||
|
323 | yield ValidationError( | |||
|
324 | "%r is %s the minimum of %r" % (instance, cmp, minimum) | |||
|
325 | ) | |||
|
326 | ||||
|
327 | def validate_maximum(self, maximum, instance, schema): | |||
|
328 | if not self.is_type(instance, "number"): | |||
|
329 | return | |||
|
330 | ||||
|
331 | instance = float(instance) | |||
|
332 | if schema.get("exclusiveMaximum", False): | |||
|
333 | failed = instance >= maximum | |||
|
334 | cmp = "greater than or equal to" | |||
|
335 | else: | |||
|
336 | failed = instance > maximum | |||
|
337 | cmp = "greater than" | |||
|
338 | ||||
|
339 | if failed: | |||
|
340 | yield ValidationError( | |||
|
341 | "%r is %s the maximum of %r" % (instance, cmp, maximum) | |||
|
342 | ) | |||
|
343 | ||||
|
344 | def validate_minItems(self, mI, instance, schema): | |||
|
345 | if self.is_type(instance, "array") and len(instance) < mI: | |||
|
346 | yield ValidationError("%r is too short" % (instance,)) | |||
|
347 | ||||
|
348 | def validate_maxItems(self, mI, instance, schema): | |||
|
349 | if self.is_type(instance, "array") and len(instance) > mI: | |||
|
350 | yield ValidationError("%r is too long" % (instance,)) | |||
|
351 | ||||
|
352 | def validate_uniqueItems(self, uI, instance, schema): | |||
|
353 | if uI and self.is_type(instance, "array") and not _uniq(instance): | |||
|
354 | yield ValidationError("%r has non-unique elements" % instance) | |||
|
355 | ||||
|
356 | def validate_pattern(self, patrn, instance, schema): | |||
|
357 | if self.is_type(instance, "string") and not re.match(patrn, instance): | |||
|
358 | yield ValidationError("%r does not match %r" % (instance, patrn)) | |||
|
359 | ||||
|
360 | def validate_minLength(self, mL, instance, schema): | |||
|
361 | if self.is_type(instance, "string") and len(instance) < mL: | |||
|
362 | yield ValidationError("%r is too short" % (instance,)) | |||
|
363 | ||||
|
364 | def validate_maxLength(self, mL, instance, schema): | |||
|
365 | if self.is_type(instance, "string") and len(instance) > mL: | |||
|
366 | yield ValidationError("%r is too long" % (instance,)) | |||
|
367 | ||||
|
368 | def validate_enum(self, enums, instance, schema): | |||
|
369 | if instance not in enums: | |||
|
370 | yield ValidationError("%r is not one of %r" % (instance, enums)) | |||
|
371 | ||||
|
372 | def validate_divisibleBy(self, dB, instance, schema): | |||
|
373 | if not self.is_type(instance, "number"): | |||
|
374 | return | |||
|
375 | ||||
|
376 | if isinstance(dB, float): | |||
|
377 | mod = instance % dB | |||
|
378 | failed = (mod > FLOAT_TOLERANCE) and (dB - mod) > FLOAT_TOLERANCE | |||
|
379 | else: | |||
|
380 | failed = instance % dB | |||
|
381 | ||||
|
382 | if failed: | |||
|
383 | yield ValidationError("%r is not divisible by %r" % (instance, dB)) | |||
|
384 | ||||
|
385 | def validate_disallow(self, disallow, instance, schema): | |||
|
386 | for disallowed in _list(disallow): | |||
|
387 | if self.is_valid(instance, {"type" : [disallowed]}): | |||
|
388 | yield ValidationError( | |||
|
389 | "%r is disallowed for %r" % (disallowed, instance) | |||
|
390 | ) | |||
|
391 | ||||
|
392 | def validate_extends(self, extends, instance, schema): | |||
|
393 | if self.is_type(extends, "object"): | |||
|
394 | extends = [extends] | |||
|
395 | for subschema in extends: | |||
|
396 | for error in self.iter_errors(instance, subschema): | |||
|
397 | yield error | |||
|
398 | ||||
|
399 | def validate_ref(self, ref, instance, schema): | |||
|
400 | if ref != "#" and not ref.startswith("#/"): | |||
|
401 | warnings.warn("jsonschema only supports json-pointer $refs") | |||
|
402 | return | |||
|
403 | ||||
|
404 | resolved = resolve_json_pointer(self.schema, ref) | |||
|
405 | for error in self.iter_errors(instance, resolved): | |||
|
406 | yield error | |||
|
407 | ||||
|
408 | ||||
|
409 | Draft3Validator.META_SCHEMA = { | |||
|
410 | "$schema" : "http://json-schema.org/draft-03/schema#", | |||
|
411 | "id" : "http://json-schema.org/draft-03/schema#", | |||
|
412 | "type" : "object", | |||
|
413 | ||||
|
414 | "properties" : { | |||
|
415 | "type" : { | |||
|
416 | "type" : ["string", "array"], | |||
|
417 | "items" : {"type" : ["string", {"$ref" : "#"}]}, | |||
|
418 | "uniqueItems" : True, | |||
|
419 | "default" : "any" | |||
|
420 | }, | |||
|
421 | "properties" : { | |||
|
422 | "type" : "object", | |||
|
423 | "additionalProperties" : {"$ref" : "#", "type": "object"}, | |||
|
424 | "default" : {} | |||
|
425 | }, | |||
|
426 | "patternProperties" : { | |||
|
427 | "type" : "object", | |||
|
428 | "additionalProperties" : {"$ref" : "#"}, | |||
|
429 | "default" : {} | |||
|
430 | }, | |||
|
431 | "additionalProperties" : { | |||
|
432 | "type" : [{"$ref" : "#"}, "boolean"], "default" : {} | |||
|
433 | }, | |||
|
434 | "items" : { | |||
|
435 | "type" : [{"$ref" : "#"}, "array"], | |||
|
436 | "items" : {"$ref" : "#"}, | |||
|
437 | "default" : {} | |||
|
438 | }, | |||
|
439 | "additionalItems" : { | |||
|
440 | "type" : [{"$ref" : "#"}, "boolean"], "default" : {} | |||
|
441 | }, | |||
|
442 | "required" : {"type" : "boolean", "default" : False}, | |||
|
443 | "dependencies" : { | |||
|
444 | "type" : ["string", "array", "object"], | |||
|
445 | "additionalProperties" : { | |||
|
446 | "type" : ["string", "array", {"$ref" : "#"}], | |||
|
447 | "items" : {"type" : "string"} | |||
|
448 | }, | |||
|
449 | "default" : {} | |||
|
450 | }, | |||
|
451 | "minimum" : {"type" : "number"}, | |||
|
452 | "maximum" : {"type" : "number"}, | |||
|
453 | "exclusiveMinimum" : {"type" : "boolean", "default" : False}, | |||
|
454 | "exclusiveMaximum" : {"type" : "boolean", "default" : False}, | |||
|
455 | "minItems" : {"type" : "integer", "minimum" : 0, "default" : 0}, | |||
|
456 | "maxItems" : {"type" : "integer", "minimum" : 0}, | |||
|
457 | "uniqueItems" : {"type" : "boolean", "default" : False}, | |||
|
458 | "pattern" : {"type" : "string", "format" : "regex"}, | |||
|
459 | "minLength" : {"type" : "integer", "minimum" : 0, "default" : 0}, | |||
|
460 | "maxLength" : {"type" : "integer"}, | |||
|
461 | "enum" : {"type" : "array", "minItems" : 1, "uniqueItems" : True}, | |||
|
462 | "default" : {"type" : "any"}, | |||
|
463 | "title" : {"type" : "string"}, | |||
|
464 | "description" : {"type" : "string"}, | |||
|
465 | "format" : {"type" : "string"}, | |||
|
466 | "maxDecimal" : {"type" : "number", "minimum" : 0}, | |||
|
467 | "divisibleBy" : { | |||
|
468 | "type" : "number", | |||
|
469 | "minimum" : 0, | |||
|
470 | "exclusiveMinimum" : True, | |||
|
471 | "default" : 1 | |||
|
472 | }, | |||
|
473 | "disallow" : { | |||
|
474 | "type" : ["string", "array"], | |||
|
475 | "items" : {"type" : ["string", {"$ref" : "#"}]}, | |||
|
476 | "uniqueItems" : True | |||
|
477 | }, | |||
|
478 | "extends" : { | |||
|
479 | "type" : [{"$ref" : "#"}, "array"], | |||
|
480 | "items" : {"$ref" : "#"}, | |||
|
481 | "default" : {} | |||
|
482 | }, | |||
|
483 | "id" : {"type" : "string", "format" : "uri"}, | |||
|
484 | "$ref" : {"type" : "string", "format" : "uri"}, | |||
|
485 | "$schema" : {"type" : "string", "format" : "uri"}, | |||
|
486 | }, | |||
|
487 | "dependencies" : { | |||
|
488 | "exclusiveMinimum" : "minimum", "exclusiveMaximum" : "maximum" | |||
|
489 | }, | |||
|
490 | } | |||
|
491 | ||||
|
492 | ||||
|
493 | class Validator(Draft3Validator): | |||
|
494 | """ | |||
|
495 | Deprecated: Use :class:`Draft3Validator` instead. | |||
|
496 | ||||
|
497 | """ | |||
|
498 | ||||
|
499 | def __init__( | |||
|
500 | self, version=None, unknown_type="skip", unknown_property="skip", | |||
|
501 | *args, **kwargs | |||
|
502 | ): | |||
|
503 | super(Validator, self).__init__({}, *args, **kwargs) | |||
|
504 | warnings.warn( | |||
|
505 | "Validator is deprecated and will be removed. " | |||
|
506 | "Use Draft3Validator instead.", | |||
|
507 | DeprecationWarning, stacklevel=2, | |||
|
508 | ) | |||
|
509 | ||||
|
510 | ||||
|
511 | class ErrorTree(object): | |||
|
512 | """ | |||
|
513 | ErrorTrees make it easier to check which validations failed. | |||
|
514 | ||||
|
515 | """ | |||
|
516 | ||||
|
517 | def __init__(self, errors=()): | |||
|
518 | self.errors = {} | |||
|
519 | self._contents = collections.defaultdict(self.__class__) | |||
|
520 | ||||
|
521 | for error in errors: | |||
|
522 | container = self | |||
|
523 | for element in reversed(error.path): | |||
|
524 | container = container[element] | |||
|
525 | container.errors[error.validator] = error | |||
|
526 | ||||
|
527 | def __contains__(self, k): | |||
|
528 | return k in self._contents | |||
|
529 | ||||
|
530 | def __getitem__(self, k): | |||
|
531 | return self._contents[k] | |||
|
532 | ||||
|
533 | def __setitem__(self, k, v): | |||
|
534 | self._contents[k] = v | |||
|
535 | ||||
|
536 | def __iter__(self): | |||
|
537 | return iter(self._contents) | |||
|
538 | ||||
|
539 | def __len__(self): | |||
|
540 | child_errors = sum(len(tree) for _, tree in iteritems(self._contents)) | |||
|
541 | return len(self.errors) + child_errors | |||
|
542 | ||||
|
543 | def __repr__(self): | |||
|
544 | return "<%s (%s errors)>" % (self.__class__.__name__, len(self)) | |||
|
545 | ||||
|
546 | ||||
|
547 | def resolve_json_pointer(schema, ref): | |||
|
548 | """ | |||
|
549 | Resolve a local reference ``ref`` within the given root ``schema``. | |||
|
550 | ||||
|
551 | ``ref`` should be a local ref whose ``#`` is still present. | |||
|
552 | ||||
|
553 | """ | |||
|
554 | ||||
|
555 | if ref == "#": | |||
|
556 | return schema | |||
|
557 | ||||
|
558 | parts = ref.lstrip("#/").split("/") | |||
|
559 | ||||
|
560 | parts = map(unquote, parts) | |||
|
561 | parts = [part.replace('~1', '/').replace('~0', '~') for part in parts] | |||
|
562 | ||||
|
563 | try: | |||
|
564 | for part in parts: | |||
|
565 | schema = schema[part] | |||
|
566 | except KeyError: | |||
|
567 | raise InvalidRef("Unresolvable json-pointer %r" % ref) | |||
|
568 | else: | |||
|
569 | return schema | |||
|
570 | ||||
|
571 | ||||
|
572 | def _find_additional_properties(instance, schema): | |||
|
573 | """ | |||
|
574 | Return the set of additional properties for the given ``instance``. | |||
|
575 | ||||
|
576 | Weeds out properties that should have been validated by ``properties`` and | |||
|
577 | / or ``patternProperties``. | |||
|
578 | ||||
|
579 | Assumes ``instance`` is dict-like already. | |||
|
580 | ||||
|
581 | """ | |||
|
582 | ||||
|
583 | properties = schema.get("properties", {}) | |||
|
584 | patterns = "|".join(schema.get("patternProperties", {})) | |||
|
585 | for property in instance: | |||
|
586 | if property not in properties: | |||
|
587 | if patterns and re.search(patterns, property): | |||
|
588 | continue | |||
|
589 | yield property | |||
|
590 | ||||
|
591 | ||||
|
592 | def _extras_msg(extras): | |||
|
593 | """ | |||
|
594 | Create an error message for extra items or properties. | |||
|
595 | ||||
|
596 | """ | |||
|
597 | ||||
|
598 | if len(extras) == 1: | |||
|
599 | verb = "was" | |||
|
600 | else: | |||
|
601 | verb = "were" | |||
|
602 | return ", ".join(repr(extra) for extra in extras), verb | |||
|
603 | ||||
|
604 | ||||
|
605 | def _types_msg(instance, types): | |||
|
606 | """ | |||
|
607 | Create an error message for a failure to match the given types. | |||
|
608 | ||||
|
609 | If the ``instance`` is an object and contains a ``name`` property, it will | |||
|
610 | be considered to be a description of that object and used as its type. | |||
|
611 | ||||
|
612 | Otherwise the message is simply the reprs of the given ``types``. | |||
|
613 | ||||
|
614 | """ | |||
|
615 | ||||
|
616 | reprs = [] | |||
|
617 | for type in types: | |||
|
618 | try: | |||
|
619 | reprs.append(repr(type["name"])) | |||
|
620 | except Exception: | |||
|
621 | reprs.append(repr(type)) | |||
|
622 | return "%r is not of type %s" % (instance, ", ".join(reprs)) | |||
|
623 | ||||
|
624 | ||||
|
625 | def _flatten(suitable_for_isinstance): | |||
|
626 | """ | |||
|
627 | isinstance() can accept a bunch of really annoying different types: | |||
|
628 | * a single type | |||
|
629 | * a tuple of types | |||
|
630 | * an arbitrary nested tree of tuples | |||
|
631 | ||||
|
632 | Return a flattened tuple of the given argument. | |||
|
633 | ||||
|
634 | """ | |||
|
635 | ||||
|
636 | types = set() | |||
|
637 | ||||
|
638 | if not isinstance(suitable_for_isinstance, tuple): | |||
|
639 | suitable_for_isinstance = (suitable_for_isinstance,) | |||
|
640 | for thing in suitable_for_isinstance: | |||
|
641 | if isinstance(thing, tuple): | |||
|
642 | types.update(_flatten(thing)) | |||
|
643 | else: | |||
|
644 | types.add(thing) | |||
|
645 | return tuple(types) | |||
|
646 | ||||
|
647 | ||||
|
648 | def _list(thing): | |||
|
649 | """ | |||
|
650 | Wrap ``thing`` in a list if it's a single str. | |||
|
651 | ||||
|
652 | Otherwise, return it unchanged. | |||
|
653 | ||||
|
654 | """ | |||
|
655 | ||||
|
656 | if isinstance(thing, basestring): | |||
|
657 | return [thing] | |||
|
658 | return thing | |||
|
659 | ||||
|
660 | ||||
|
661 | def _delist(thing): | |||
|
662 | """ | |||
|
663 | Unwrap ``thing`` to a single element if its a single str in a list. | |||
|
664 | ||||
|
665 | Otherwise, return it unchanged. | |||
|
666 | ||||
|
667 | """ | |||
|
668 | ||||
|
669 | if ( | |||
|
670 | isinstance(thing, list) and | |||
|
671 | len(thing) == 1 | |||
|
672 | and isinstance(thing[0], basestring) | |||
|
673 | ): | |||
|
674 | return thing[0] | |||
|
675 | return thing | |||
|
676 | ||||
|
677 | ||||
|
678 | def _uniq(container): | |||
|
679 | """ | |||
|
680 | Check if all of a container's elements are unique. | |||
|
681 | ||||
|
682 | Successively tries first to rely that the elements are hashable, then | |||
|
683 | falls back on them being sortable, and finally falls back on brute | |||
|
684 | force. | |||
|
685 | ||||
|
686 | """ | |||
|
687 | ||||
|
688 | try: | |||
|
689 | return len(set(container)) == len(container) | |||
|
690 | except TypeError: | |||
|
691 | try: | |||
|
692 | sort = sorted(container) | |||
|
693 | sliced = itertools.islice(container, 1, None) | |||
|
694 | for i, j in zip(container, sliced): | |||
|
695 | if i == j: | |||
|
696 | return False | |||
|
697 | except (NotImplementedError, TypeError): | |||
|
698 | seen = [] | |||
|
699 | for e in container: | |||
|
700 | if e in seen: | |||
|
701 | return False | |||
|
702 | seen.append(e) | |||
|
703 | return True | |||
|
704 | ||||
|
705 | ||||
|
706 | def validate(instance, schema, cls=Draft3Validator, *args, **kwargs): | |||
|
707 | """ | |||
|
708 | Validate an ``instance`` under the given ``schema``. | |||
|
709 | ||||
|
710 | First verifies that the provided schema is itself valid, since not doing so | |||
|
711 | can lead to less obvious failures when validating. If you know it is or | |||
|
712 | don't care, use ``YourValidator(schema).validate(instance)`` directly | |||
|
713 | instead (e.g. ``Draft3Validator``). | |||
|
714 | ||||
|
715 | ``cls`` is a validator class that will be used to validate the instance. | |||
|
716 | By default this is a draft 3 validator. Any other provided positional and | |||
|
717 | keyword arguments will be provided to this class when constructing a | |||
|
718 | validator. | |||
|
719 | ||||
|
720 | """ | |||
|
721 | ||||
|
722 | ||||
|
723 | meta_validate = kwargs.pop("meta_validate", None) | |||
|
724 | ||||
|
725 | if meta_validate is not None: | |||
|
726 | warnings.warn( | |||
|
727 | "meta_validate is deprecated and will be removed. If you do not " | |||
|
728 | "want to validate a schema, use Draft3Validator.validate instead.", | |||
|
729 | DeprecationWarning, stacklevel=2, | |||
|
730 | ) | |||
|
731 | ||||
|
732 | if meta_validate is not False: # yes this is needed since True was default | |||
|
733 | cls.check_schema(schema) | |||
|
734 | cls(schema, *args, **kwargs).validate(instance) |
@@ -0,0 +1,171 b'' | |||||
|
1 | { | |||
|
2 | "description": "custom json structure with references to generate notebook schema", | |||
|
3 | "notebook":{ | |||
|
4 | "type": "object", | |||
|
5 | "description": "notebook v3.0 root schema", | |||
|
6 | "$schema": "http://json-schema.org/draft-03/schema", | |||
|
7 | "id": "#notebook", | |||
|
8 | "required": true, | |||
|
9 | "additionalProperties": false, | |||
|
10 | "properties":{ | |||
|
11 | "metadata": { | |||
|
12 | "type": "object", | |||
|
13 | "id": "metadata", | |||
|
14 | "required": true, | |||
|
15 | "description": "the metadata atribute can contain any additionnal information", | |||
|
16 | "additionalProperties": true, | |||
|
17 | "properties":{ | |||
|
18 | "name": { | |||
|
19 | "id": "name", | |||
|
20 | "description": "the title of the notebook", | |||
|
21 | "type": "string", | |||
|
22 | "id": "name", | |||
|
23 | "required": true | |||
|
24 | } | |||
|
25 | } | |||
|
26 | }, | |||
|
27 | "nbformat_minor": { | |||
|
28 | "description": "Notebook format, minor number. Incremented for slight variation of notebook format.", | |||
|
29 | "type": "integer", | |||
|
30 | "minimum": 0, | |||
|
31 | "id": "nbformat_minor", | |||
|
32 | "required": true | |||
|
33 | }, | |||
|
34 | "nbformat": { | |||
|
35 | "description": "Notebook format, major number. Incremented between backward incompatible change is introduced.", | |||
|
36 | "type": "integer", | |||
|
37 | "minimum": 3, | |||
|
38 | "id": "nbformat", | |||
|
39 | "required": true | |||
|
40 | }, | |||
|
41 | "worksheets": { | |||
|
42 | "description": "Array of worksheet, not used by the current implementation of ipython yet", | |||
|
43 | "type": "array", | |||
|
44 | "id": "worksheets", | |||
|
45 | "required": true, | |||
|
46 | "items": {"$ref": "/worksheet"} | |||
|
47 | } | |||
|
48 | } | |||
|
49 | }, | |||
|
50 | ||||
|
51 | "worksheet": { | |||
|
52 | "additionalProperties": false, | |||
|
53 | "properties":{ | |||
|
54 | "cells": { | |||
|
55 | "type": "array", | |||
|
56 | "$schema": "http://json-schema.org/draft-03/schema", | |||
|
57 | "description": "array of cells of the current worksheet", | |||
|
58 | "id": "#cells", | |||
|
59 | "required": true, | |||
|
60 | "items": {"$ref": "/any_cell"} | |||
|
61 | ||||
|
62 | }, | |||
|
63 | "metadata": { | |||
|
64 | "type": "object", | |||
|
65 | "description": "metadata of the current worksheet", | |||
|
66 | "id": "metadata", | |||
|
67 | "required": true | |||
|
68 | } | |||
|
69 | } | |||
|
70 | }, | |||
|
71 | ||||
|
72 | "text_cell": { | |||
|
73 | "type": "object", | |||
|
74 | "description": "scheme for text cel and childrenm (level only optionnal argument for HEader cell)", | |||
|
75 | "$schema": "http://json-schema.org/draft-03/schema", | |||
|
76 | "id": "#cell", | |||
|
77 | "required": true, | |||
|
78 | "additionalProperties": false, | |||
|
79 | "properties":{ | |||
|
80 | "cell_type": { | |||
|
81 | "type": "string", | |||
|
82 | "id": "cell_type", | |||
|
83 | "required": true | |||
|
84 | }, | |||
|
85 | "level": { | |||
|
86 | "type": "integer", | |||
|
87 | "minimum": 1, | |||
|
88 | "maximum": 6, | |||
|
89 | "id": "level", | |||
|
90 | "required": false | |||
|
91 | }, | |||
|
92 | "metadata": { | |||
|
93 | "type": "object", | |||
|
94 | "id": "metadata", | |||
|
95 | "required": true | |||
|
96 | }, | |||
|
97 | "source": { | |||
|
98 | "description": "for code cell, the source code", | |||
|
99 | "type": "array", | |||
|
100 | "id": "source", | |||
|
101 | "required": true, | |||
|
102 | "items": | |||
|
103 | { | |||
|
104 | "type": "string", | |||
|
105 | "description": "each item represent one line of the source code written, terminated by \n", | |||
|
106 | "id": "0", | |||
|
107 | "required": true | |||
|
108 | } | |||
|
109 | } | |||
|
110 | } | |||
|
111 | ||||
|
112 | }, | |||
|
113 | ||||
|
114 | "any_cell": { | |||
|
115 | "description": "Meta cell type that match any cell type", | |||
|
116 | "type": [{"$ref": "/text_cell"},{"$ref":"/code_cell"}], | |||
|
117 | "$schema": "http://json-schema.org/draft-03/schema" | |||
|
118 | }, | |||
|
119 | ||||
|
120 | "code_cell":{ | |||
|
121 | "type": "object", | |||
|
122 | "$schema": "http://json-schema.org/draft-03/schema", | |||
|
123 | "description": "Cell used to execute code", | |||
|
124 | "id": "#cell", | |||
|
125 | "required": true, | |||
|
126 | "additionalProperties": false, | |||
|
127 | "properties":{ | |||
|
128 | "cell_type": { | |||
|
129 | "type": "string", | |||
|
130 | "id": "cell_type", | |||
|
131 | "required": true | |||
|
132 | }, | |||
|
133 | "metadata": { | |||
|
134 | "type": "object", | |||
|
135 | "id": "metadata", | |||
|
136 | "required": true | |||
|
137 | }, | |||
|
138 | "collapsed": { | |||
|
139 | "type": "boolean", | |||
|
140 | "required": true | |||
|
141 | }, | |||
|
142 | "input": { | |||
|
143 | "description": "user input for text cells", | |||
|
144 | "type": "array", | |||
|
145 | "id": "input", | |||
|
146 | "required": true, | |||
|
147 | "items": | |||
|
148 | { | |||
|
149 | "type": "string", | |||
|
150 | "id": "input", | |||
|
151 | "required": true | |||
|
152 | } | |||
|
153 | }, | |||
|
154 | "outputs": { | |||
|
155 | "description": "output for code cell, to be definied", | |||
|
156 | "required": true, | |||
|
157 | "type": "array" | |||
|
158 | }, | |||
|
159 | "prompt_number": { | |||
|
160 | "type": ["integer","null"], | |||
|
161 | "required": true, | |||
|
162 | "minimum": 0 | |||
|
163 | }, | |||
|
164 | "language": { | |||
|
165 | "type": "string", | |||
|
166 | "required": true | |||
|
167 | } | |||
|
168 | } | |||
|
169 | ||||
|
170 | } | |||
|
171 | } |
@@ -0,0 +1,88 b'' | |||||
|
1 | #!/usr/bin/env python | |||
|
2 | # -*- coding: utf8 -*- | |||
|
3 | ||||
|
4 | from IPython.external.jsonschema import Draft3Validator, validate, ValidationError | |||
|
5 | import IPython.external.jsonpointer as jsonpointer | |||
|
6 | from IPython.external import argparse | |||
|
7 | import traceback | |||
|
8 | import json | |||
|
9 | ||||
|
10 | def nbvalidate(nbjson, schema='v3.withref.json', key=None,verbose=True): | |||
|
11 | v3schema = resolve_ref(json.load(open(schema,'r'))) | |||
|
12 | if key : | |||
|
13 | v3schema = jsonpointer.resolve_pointer(v3schema,key) | |||
|
14 | errors = 0 | |||
|
15 | v = Draft3Validator(v3schema); | |||
|
16 | for error in v.iter_errors(nbjson): | |||
|
17 | errors = errors + 1 | |||
|
18 | if verbose: | |||
|
19 | print(error) | |||
|
20 | return errors | |||
|
21 | ||||
|
22 | def resolve_ref(json, base=None): | |||
|
23 | """return a json with resolved internal references | |||
|
24 | ||||
|
25 | only support local reference to the same json | |||
|
26 | """ | |||
|
27 | if not base : | |||
|
28 | base = json | |||
|
29 | ||||
|
30 | temp = None | |||
|
31 | if type(json) is list: | |||
|
32 | temp = []; | |||
|
33 | for item in json: | |||
|
34 | temp.append(resolve_ref(item, base=base)) | |||
|
35 | elif type(json) is dict: | |||
|
36 | temp = {}; | |||
|
37 | for key,value in json.iteritems(): | |||
|
38 | if key == '$ref': | |||
|
39 | return resolve_ref(jsonpointer.resolve_pointer(base,value), base=base) | |||
|
40 | else : | |||
|
41 | temp[key]=resolve_ref(value, base=base) | |||
|
42 | else : | |||
|
43 | return json | |||
|
44 | return temp | |||
|
45 | ||||
|
46 | def convert(namein, nameout, indent=2): | |||
|
47 | """resolve the references of namein, save the result in nameout""" | |||
|
48 | jsn = None | |||
|
49 | with open(namein) as file : | |||
|
50 | jsn = json.load(file) | |||
|
51 | v = resolve_ref(jsn, base=jsn) | |||
|
52 | x = jsonpointer.resolve_pointer(v, '/notebook') | |||
|
53 | with open(nameout,'w') as file: | |||
|
54 | json.dump(x,file,indent=indent) | |||
|
55 | ||||
|
56 | ||||
|
57 | if __name__ == '__main__': | |||
|
58 | parser = argparse.ArgumentParser() | |||
|
59 | parser.add_argument('-s', '--schema', | |||
|
60 | type=str, default='v3.withref.json') | |||
|
61 | ||||
|
62 | parser.add_argument('-k', '--key', | |||
|
63 | type=str, default='/notebook', | |||
|
64 | help='subkey to extract json schema from json file') | |||
|
65 | ||||
|
66 | parser.add_argument("-v", "--verbose", action="store_true", | |||
|
67 | help="increase output verbosity") | |||
|
68 | ||||
|
69 | parser.add_argument('filename', | |||
|
70 | type=str, | |||
|
71 | help="file to validate", | |||
|
72 | nargs='*', | |||
|
73 | metavar='names') | |||
|
74 | ||||
|
75 | args = parser.parse_args() | |||
|
76 | for name in args.filename : | |||
|
77 | nerror = nbvalidate(json.load(open(name,'r')), | |||
|
78 | schema=args.schema, | |||
|
79 | key=args.key, | |||
|
80 | verbose=args.verbose) | |||
|
81 | if nerror is 0: | |||
|
82 | print u"[Pass]",name | |||
|
83 | else : | |||
|
84 | print u"[ ]",name,'(%d)'%(nerror) | |||
|
85 | if args.verbose : | |||
|
86 | print '==================================================' | |||
|
87 | ||||
|
88 |
General Comments 0
You need to be logged in to leave comments.
Login now