##// END OF EJS Templates
Merge pull request #2396 from Carreau/json_schema...
Brian E. Granger -
r9085:ac9b1252 merge
parent child Browse files
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,3 b''
1 _jsonschema from commit :
2 3ddd80543bd6da56eeea84b2f364febaeadf31b9
3
@@ -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