##// END OF EJS Templates
import jsonpointer and schema from external
Matthias BUSSONNIER -
Show More
@@ -0,0 +1,4 b''
1 try:
2 from jsonpointer import *
3 except ImportError :
4 from _jsonpointer import *
@@ -0,0 +1,222 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 import urllib
44 from itertools import tee, izip
45
46
47 class JsonPointerException(Exception):
48 pass
49
50
51 _nothing = object()
52
53
54 def resolve_pointer(doc, pointer, default=_nothing):
55 """
56 Resolves pointer against doc and returns the referenced object
57
58 >>> obj = {"foo": {"anArray": [ {"prop": 44}], "another prop": {"baz": "A string" }}}
59
60 >>> resolve_pointer(obj, '') == obj
61 True
62
63 >>> resolve_pointer(obj, '/foo') == obj['foo']
64 True
65
66 >>> resolve_pointer(obj, '/foo/another%20prop') == obj['foo']['another prop']
67 True
68
69 >>> resolve_pointer(obj, '/foo/another%20prop/baz') == obj['foo']['another prop']['baz']
70 True
71
72 >>> resolve_pointer(obj, '/foo/anArray/0') == obj['foo']['anArray'][0]
73 True
74
75 >>> resolve_pointer(obj, '/some/path', None) == None
76 True
77
78 """
79
80 pointer = JsonPointer(pointer)
81 return pointer.resolve(doc, default)
82
83
84 def set_pointer(doc, pointer, value):
85 """
86 Set a field to a given value
87
88 The field is indicates by a base location that is given in the constructor,
89 and an optional relative location in the call to set. If the path doesn't
90 exist, it is created if possible
91
92 >>> obj = {"foo": 2}
93 >>> pointer = JsonPointer('/bar')
94 >>> pointer.set(obj, 'one', '0')
95 >>> pointer.set(obj, 'two', '1')
96 >>> obj
97 {'foo': 2, 'bar': ['one', 'two']}
98
99 >>> obj = {"foo": 2, "bar": []}
100 >>> pointer = JsonPointer('/bar')
101 >>> pointer.set(obj, 5, '0/x')
102 >>> obj
103 {'foo': 2, 'bar': [{'x': 5}]}
104
105 >>> obj = {'foo': 2, 'bar': [{'x': 5}]}
106 >>> pointer = JsonPointer('/bar/0')
107 >>> pointer.set(obj, 10, 'y/0')
108 >>> obj
109 {'foo': 2, 'bar': [{'y': [10], 'x': 5}]}
110 """
111
112 pointer = JsonPointer(pointer)
113 pointer.set(doc, value)
114
115
116 class JsonPointer(object):
117 """ A JSON Pointer that can reference parts of an JSON document """
118
119 def __init__(self, pointer):
120 parts = pointer.split('/')
121 if parts.pop(0) != '':
122 raise JsonPointerException('location must starts with /')
123
124 parts = map(urllib.unquote, parts)
125 parts = [part.replace('~1', '/') for part in parts]
126 parts = [part.replace('~0', '~') for part in parts]
127 self.parts = parts
128
129
130
131 def resolve(self, doc, default=_nothing):
132 """Resolves the pointer against doc and returns the referenced object"""
133
134 for part in self.parts:
135
136 try:
137 doc = self.walk(doc, part)
138 except JsonPointerException:
139 if default is _nothing:
140 raise
141 else:
142 return default
143
144 return doc
145
146
147 get = resolve
148
149
150 def set(self, doc, value, path=None):
151 """ Sets a field of doc to value
152
153 The location of the field is given by the pointers base location and
154 the optional path which is relative to the base location """
155
156 fullpath = list(self.parts)
157
158 if path:
159 fullpath += path.split('/')
160
161
162 for part, nextpart in pairwise(fullpath):
163 try:
164 doc = self.walk(doc, part)
165 except JsonPointerException:
166 step_val = [] if nextpart.isdigit() else {}
167 doc = self._set_value(doc, part, step_val)
168
169 self._set_value(doc, fullpath[-1], value)
170
171
172 @staticmethod
173 def _set_value(doc, part, value):
174 part = int(part) if part.isdigit() else part
175
176 if isinstance(doc, dict):
177 doc[part] = value
178
179 if isinstance(doc, list):
180 if len(doc) < part:
181 doc[part] = value
182
183 if len(doc) == part:
184 doc.append(value)
185
186 else:
187 raise IndexError
188
189 return doc[part]
190
191
192 def walk(self, doc, part):
193 """ Walks one step in doc and returns the referenced part """
194
195 # Its not clear if a location "1" should be considered as 1 or "1"
196 # We prefer the integer-variant if possible
197 part_variants = self._try_parse(part) + [part]
198
199 for variant in part_variants:
200 try:
201 return doc[variant]
202 except:
203 continue
204
205 raise JsonPointerException("'%s' not found in %s" % (part, doc))
206
207
208 @staticmethod
209 def _try_parse(val, cls=int):
210 try:
211 return [cls(val)]
212 except:
213 return []
214
215
216
217 def pairwise(iterable):
218 "s -> (s0,s1), (s1,s2), (s2, s3), ..."
219 a, b = tee(iterable)
220 next(b, None)
221 return izip(a, b)
222 __author__ = 'Stefan Kögl <stefan@skoegl.net>'
@@ -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, (653 lines changed) Show them Hide them
@@ -0,0 +1,653 b''
1 """
2 An implementation of JSON Schema for Python
3
4 The main functionality is provided by the :class:`Validator` class, with the
5 :function:`validate` function being the most common way to quickly create a
6 :class:`Validator` object and validate an instance with a given schema.
7
8 The :class:`Validator` class generally attempts to be as strict as possible
9 under the JSON Schema specification. See its docstring for details.
10
11 """
12
13 from __future__ import division, unicode_literals
14
15 import collections
16 import itertools
17 import operator
18 import re
19 import sys
20 import warnings
21
22
23 PY3 = sys.version_info[0] >= 3
24
25 if PY3:
26 basestring = unicode = str
27 iteritems = operator.methodcaller("items")
28 else:
29 from itertools import izip as zip
30 iteritems = operator.methodcaller("iteritems")
31
32
33 def _uniq(container):
34 """
35 Check if all of a container's elements are unique.
36
37 Successively tries first to rely that the elements are hashable, then
38 falls back on them being sortable, and finally falls back on brute
39 force.
40
41 """
42
43 try:
44 return len(set(container)) == len(container)
45 except TypeError:
46 try:
47 sort = sorted(container)
48 sliced = itertools.islice(container, 1, None)
49 for i, j in zip(container, sliced):
50 if i == j:
51 return False
52 except (NotImplementedError, TypeError):
53 seen = []
54 for e in container:
55 if e in seen:
56 return False
57 seen.append(e)
58 return True
59
60
61 __version__ = "0.5"
62
63
64 DRAFT_3 = {
65 "$schema" : "http://json-schema.org/draft-03/schema#",
66 "id" : "http://json-schema.org/draft-03/schema#",
67 "type" : "object",
68
69 "properties" : {
70 "type" : {
71 "type" : ["string", "array"],
72 "items" : {"type" : ["string", {"$ref" : "#"}]},
73 "uniqueItems" : True,
74 "default" : "any"
75 },
76 "properties" : {
77 "type" : "object",
78 "additionalProperties" : {"$ref" : "#", "type": "object"},
79 "default" : {}
80 },
81 "patternProperties" : {
82 "type" : "object",
83 "additionalProperties" : {"$ref" : "#"},
84 "default" : {}
85 },
86 "additionalProperties" : {
87 "type" : [{"$ref" : "#"}, "boolean"], "default" : {}
88 },
89 "items" : {
90 "type" : [{"$ref" : "#"}, "array"],
91 "items" : {"$ref" : "#"},
92 "default" : {}
93 },
94 "additionalItems" : {
95 "type" : [{"$ref" : "#"}, "boolean"], "default" : {}
96 },
97 "required" : {"type" : "boolean", "default" : False},
98 "dependencies" : {
99 "type" : ["string", "array", "object"],
100 "additionalProperties" : {
101 "type" : ["string", "array", {"$ref" : "#"}],
102 "items" : {"type" : "string"}
103 },
104 "default" : {}
105 },
106 "minimum" : {"type" : "number"},
107 "maximum" : {"type" : "number"},
108 "exclusiveMinimum" : {"type" : "boolean", "default" : False},
109 "exclusiveMaximum" : {"type" : "boolean", "default" : False},
110 "minItems" : {"type" : "integer", "minimum" : 0, "default" : 0},
111 "maxItems" : {"type" : "integer", "minimum" : 0},
112 "uniqueItems" : {"type" : "boolean", "default" : False},
113 "pattern" : {"type" : "string", "format" : "regex"},
114 "minLength" : {"type" : "integer", "minimum" : 0, "default" : 0},
115 "maxLength" : {"type" : "integer"},
116 "enum" : {"type" : "array", "minItems" : 1, "uniqueItems" : True},
117 "default" : {"type" : "any"},
118 "title" : {"type" : "string"},
119 "description" : {"type" : "string"},
120 "format" : {"type" : "string"},
121 "maxDecimal" : {"type" : "number", "minimum" : 0},
122 "divisibleBy" : {
123 "type" : "number",
124 "minimum" : 0,
125 "exclusiveMinimum" : True,
126 "default" : 1
127 },
128 "disallow" : {
129 "type" : ["string", "array"],
130 "items" : {"type" : ["string", {"$ref" : "#"}]},
131 "uniqueItems" : True
132 },
133 "extends" : {
134 "type" : [{"$ref" : "#"}, "array"],
135 "items" : {"$ref" : "#"},
136 "default" : {}
137 },
138 "id" : {"type" : "string", "format" : "uri"},
139 "$ref" : {"type" : "string", "format" : "uri"},
140 "$schema" : {"type" : "string", "format" : "uri"},
141 },
142 "dependencies" : {
143 "exclusiveMinimum" : "minimum", "exclusiveMaximum" : "maximum"
144 },
145 }
146
147 EPSILON = 10 ** -15
148
149
150 class SchemaError(Exception):
151 """
152 The provided schema is malformed.
153
154 The same attributes exist for ``SchemaError``s as for ``ValidationError``s.
155
156 """
157
158 validator = None
159
160 def __init__(self, message):
161 super(SchemaError, self).__init__(message)
162 self.message = message
163 self.path = []
164
165
166 class ValidationError(Exception):
167 """
168 The instance didn't properly validate with the provided schema.
169
170 Relevant attributes are:
171 * ``message`` : a human readable message explaining the error
172 * ``path`` : a list containing the path to the offending element (or []
173 if the error happened globally) in *reverse* order (i.e.
174 deepest index first).
175
176 """
177
178 # the failing validator will be set externally at whatever recursion level
179 # is immediately above the validation failure
180 validator = None
181
182 def __init__(self, message):
183 super(ValidationError, self).__init__(message)
184 self.message = message
185
186 # Any validator that recurses must append to the ValidationError's
187 # path (e.g., properties and items)
188 self.path = []
189
190
191 class Validator(object):
192 """
193 A JSON Schema validator.
194
195 """
196
197 DEFAULT_TYPES = {
198 "array" : list, "boolean" : bool, "integer" : int, "null" : type(None),
199 "number" : (int, float), "object" : dict, "string" : basestring,
200 }
201
202 def __init__(
203 self, version=DRAFT_3, unknown_type="skip",
204 unknown_property="skip", types=(),
205 ):
206 """
207 Initialize a Validator.
208
209 ``version`` specifies which version of the JSON Schema specification to
210 validate with. Currently only draft-03 is supported (and is the
211 default).
212
213 ``unknown_type`` and ``unknown_property`` control what to do when an
214 unknown type (resp. property) is encountered. By default, the
215 metaschema is respected (which e.g. for draft 3 allows a schema to have
216 additional properties), but if for some reason you want to modify this
217 behavior, you can do so without needing to modify the metaschema by
218 passing ``"error"`` or ``"warn"`` to these arguments.
219
220 ``types`` is a mapping (or iterable of 2-tuples) containing additional
221 types or alternate types to verify via the 'type' property. For
222 instance, the default types for the 'number' JSON Schema type are
223 ``int`` and ``float``. To override this behavior (e.g. for also
224 allowing ``decimal.Decimal``), pass ``types={"number" : (int, float,
225 decimal.Decimal)} *including* the default types if so desired, which
226 are fairly obvious but can be accessed via ``Validator.DEFAULT_TYPES``
227 if necessary.
228
229 """
230
231 self._unknown_type = unknown_type
232 self._unknown_property = unknown_property
233 self._version = version
234
235 self._types = dict(self.DEFAULT_TYPES)
236 self._types.update(types)
237 self._types["any"] = tuple(self._types.values())
238
239 def is_type(self, instance, type):
240 """
241 Check if an ``instance`` is of the provided ``type``.
242
243 """
244
245 py_type = self._types.get(type)
246
247 if py_type is None:
248 return self.schema_error(
249 self._unknown_type, "%r is not a known type" % (type,)
250 )
251
252 # the only thing we're careful about here is evading bool inheriting
253 # from int, so let's be even dirtier than usual
254
255 elif (
256 # it's not a bool, so no worries
257 not isinstance(instance, bool) or
258
259 # it is a bool, but we're checking for a bool, so no worries
260 (
261 py_type is bool or
262 isinstance(py_type, tuple) and bool in py_type
263 )
264
265 ):
266 return isinstance(instance, py_type)
267
268 def schema_error(self, level, msg):
269 if level == "skip":
270 return
271 elif level == "warn":
272 warnings.warn(msg)
273 else:
274 raise SchemaError(msg)
275
276 def is_valid(self, instance, schema, meta_validate=True):
277 """
278 Check if the ``instance`` is valid under the ``schema``.
279
280 Returns a bool indicating whether validation succeeded.
281
282 """
283
284 error = next(self.iter_errors(instance, schema, meta_validate), None)
285 return error is None
286
287 def iter_errors(self, instance, schema, meta_validate=True):
288 """
289 Lazily yield each of the errors in the given ``instance``.
290
291 If you are unsure whether your schema itself is valid,
292 ``meta_validate`` will first validate that the schema is valid before
293 attempting to validate the instance. ``meta_validate`` is ``True`` by
294 default, since setting it to ``False`` can lead to confusing error
295 messages with an invalid schema. If you're sure your schema is in fact
296 valid, or don't care, feel free to set this to ``False``. The meta
297 validation will be done using the appropriate ``version``.
298
299 """
300
301 if meta_validate:
302 for error in self.iter_errors(
303 schema, self._version, meta_validate=False
304 ):
305 s = SchemaError(error.message)
306 s.path = error.path
307 s.validator = error.validator
308 # I think we're safer raising these always, not yielding them
309 raise s
310
311 for k, v in iteritems(schema):
312 validator = getattr(self, "validate_%s" % (k.lstrip("$"),), None)
313
314 if validator is None:
315 errors = self.unknown_property(k, instance, schema)
316 else:
317 errors = validator(v, instance, schema)
318
319 for error in errors or ():
320 # if the validator hasn't already been set (due to recursion)
321 # make sure to set it
322 error.validator = error.validator or k
323 yield error
324
325 def validate(self, *args, **kwargs):
326 """
327 Validate an ``instance`` under the given ``schema``.
328
329 """
330
331 for error in self.iter_errors(*args, **kwargs):
332 raise error
333
334 def unknown_property(self, property, instance, schema):
335 self.schema_error(
336 self._unknown_property,
337 "%r is not a known schema property" % (property,)
338 )
339
340 def validate_type(self, types, instance, schema):
341 types = _list(types)
342
343 for type in types:
344 # Ouch. Brain hurts. Two paths here, either we have a schema, then
345 # check if the instance is valid under it
346 if ((
347 self.is_type(type, "object") and
348 self.is_valid(instance, type)
349
350 # Or we have a type as a string, just check if the instance is that
351 # type. Also, HACK: we can reach the `or` here if skip_types is
352 # something other than error. If so, bail out.
353
354 ) or (
355 self.is_type(type, "string") and
356 (self.is_type(instance, type) or type not in self._types)
357 )):
358 return
359 else:
360 yield ValidationError(
361 "%r is not of type %r" % (instance, _delist(types))
362 )
363
364 def validate_properties(self, properties, instance, schema):
365 if not self.is_type(instance, "object"):
366 return
367
368 for property, subschema in iteritems(properties):
369 if property in instance:
370 dependencies = _list(subschema.get("dependencies", []))
371 if self.is_type(dependencies, "object"):
372 for error in self.iter_errors(
373 instance, dependencies, meta_validate=False
374 ):
375 yield error
376 else:
377 for dependency in dependencies:
378 if dependency not in instance:
379 yield ValidationError(
380 "%r is a dependency of %r" % (dependency, property)
381 )
382
383 for error in self.iter_errors(
384 instance[property], subschema, meta_validate=False
385 ):
386 error.path.append(property)
387 yield error
388 elif subschema.get("required", False):
389 error = ValidationError(
390 "%r is a required property" % (property,)
391 )
392 error.path.append(property)
393 error.validator = "required"
394 yield error
395
396 def validate_patternProperties(self, patternProperties, instance, schema):
397 for pattern, subschema in iteritems(patternProperties):
398 for k, v in iteritems(instance):
399 if re.match(pattern, k):
400 for error in self.iter_errors(
401 v, subschema, meta_validate=False
402 ):
403 yield error
404
405 def validate_additionalProperties(self, aP, instance, schema):
406 if not self.is_type(instance, "object"):
407 return
408
409 # no viewkeys in <2.7, and pypy seems to fail on vk - vk anyhow, so...
410 extras = set(instance) - set(schema.get("properties", {}))
411
412 if self.is_type(aP, "object"):
413 for extra in extras:
414 for error in self.iter_errors(
415 instance[extra], aP, meta_validate=False
416 ):
417 yield error
418 elif not aP and extras:
419 error = "Additional properties are not allowed (%s %s unexpected)"
420 yield ValidationError(error % _extras_msg(extras))
421
422 def validate_items(self, items, instance, schema):
423 if not self.is_type(instance, "array"):
424 return
425
426 if self.is_type(items, "object"):
427 for index, item in enumerate(instance):
428 for error in self.iter_errors(
429 item, items, meta_validate=False
430 ):
431 error.path.append(index)
432 yield error
433 else:
434 for (index, item), subschema in zip(enumerate(instance), items):
435 for error in self.iter_errors(
436 item, subschema, meta_validate=False
437 ):
438 error.path.append(index)
439 yield error
440
441 def validate_additionalItems(self, aI, instance, schema):
442 if not self.is_type(instance, "array"):
443 return
444
445 if self.is_type(aI, "object"):
446 for item in instance[len(schema):]:
447 for error in self.iter_errors(item, aI, meta_validate=False):
448 yield error
449 elif not aI and len(instance) > len(schema.get("items", [])):
450 error = "Additional items are not allowed (%s %s unexpected)"
451 yield ValidationError(
452 error % _extras_msg(instance[len(schema) - 1:])
453 )
454
455 def validate_minimum(self, minimum, instance, schema):
456 if not self.is_type(instance, "number"):
457 return
458
459 instance = float(instance)
460 if schema.get("exclusiveMinimum", False):
461 failed = instance <= minimum
462 cmp = "less than or equal to"
463 else:
464 failed = instance < minimum
465 cmp = "less than"
466
467 if failed:
468 yield ValidationError(
469 "%r is %s the minimum of %r" % (instance, cmp, minimum)
470 )
471
472 def validate_maximum(self, maximum, instance, schema):
473 if not self.is_type(instance, "number"):
474 return
475
476 instance = float(instance)
477 if schema.get("exclusiveMaximum", False):
478 failed = instance >= maximum
479 cmp = "greater than or equal to"
480 else:
481 failed = instance > maximum
482 cmp = "greater than"
483
484 if failed:
485 yield ValidationError(
486 "%r is %s the maximum of %r" % (instance, cmp, maximum)
487 )
488
489 def validate_minItems(self, mI, instance, schema):
490 if self.is_type(instance, "array") and len(instance) < mI:
491 yield ValidationError("%r is too short" % (instance,))
492
493 def validate_maxItems(self, mI, instance, schema):
494 if self.is_type(instance, "array") and len(instance) > mI:
495 yield ValidationError("%r is too long" % (instance,))
496
497 def validate_uniqueItems(self, uI, instance, schema):
498 if uI and self.is_type(instance, "array") and not _uniq(instance):
499 yield ValidationError("%r has non-unique elements" % instance)
500
501 def validate_pattern(self, patrn, instance, schema):
502 if self.is_type(instance, "string") and not re.match(patrn, instance):
503 yield ValidationError("%r does not match %r" % (instance, patrn))
504
505 def validate_minLength(self, mL, instance, schema):
506 if self.is_type(instance, "string") and len(instance) < mL:
507 yield ValidationError("%r is too short" % (instance,))
508
509 def validate_maxLength(self, mL, instance, schema):
510 if self.is_type(instance, "string") and len(instance) > mL:
511 yield ValidationError("%r is too long" % (instance,))
512
513 def validate_enum(self, enums, instance, schema):
514 if instance not in enums:
515 yield ValidationError("%r is not one of %r" % (instance, enums))
516
517 def validate_divisibleBy(self, dB, instance, schema):
518 if not self.is_type(instance, "number"):
519 return
520
521 if isinstance(dB, float):
522 mod = instance % dB
523 failed = (mod > EPSILON) and (dB - mod) > EPSILON
524 else:
525 failed = instance % dB
526
527 if failed:
528 yield ValidationError("%r is not divisible by %r" % (instance, dB))
529
530 def validate_disallow(self, disallow, instance, schema):
531 for disallowed in _list(disallow):
532 if self.is_valid(instance, {"type" : [disallowed]}):
533 yield ValidationError(
534 "%r is disallowed for %r" % (disallowed, instance)
535 )
536
537 def validate_extends(self, extends, instance, schema):
538 if self.is_type(extends, "object"):
539 extends = [extends]
540 for subschema in extends:
541 for error in self.iter_errors(
542 instance, subschema, meta_validate=False
543 ):
544 yield error
545
546
547 for no_op in [ # handled in:
548 "dependencies", "required", # properties
549 "exclusiveMinimum", "exclusiveMaximum", # min*/max*
550 "default", "description", "format", "id", # no validation needed
551 "links", "name", "title",
552 "ref", "schema", # not yet supported
553 ]:
554 setattr(Validator, "validate_" + no_op, lambda *args, **kwargs : None)
555
556
557 class ErrorTree(object):
558 """
559 ErrorTrees make it easier to check which validations failed.
560
561 """
562
563 def __init__(self, errors=()):
564 self.errors = {}
565 self._contents = collections.defaultdict(self.__class__)
566
567 for error in errors:
568 container = self
569 for element in reversed(error.path):
570 container = container[element]
571 container.errors[error.validator] = error
572
573 def __contains__(self, k):
574 return k in self._contents
575
576 def __getitem__(self, k):
577 return self._contents[k]
578
579 def __setitem__(self, k, v):
580 self._contents[k] = v
581
582 def __iter__(self):
583 return iter(self._contents)
584
585 def __len__(self):
586 child_errors = sum(len(tree) for _, tree in iteritems(self._contents))
587 return len(self.errors) + child_errors
588
589 def __repr__(self):
590 return "<%s (%s errors)>" % (self.__class__.__name__, len(self))
591
592
593 def _extras_msg(extras):
594 """
595 Create an error message for extra items or properties.
596
597 """
598
599 if len(extras) == 1:
600 verb = "was"
601 else:
602 verb = "were"
603 return ", ".join(repr(extra) for extra in extras), verb
604
605
606 def _list(thing):
607 """
608 Wrap ``thing`` in a list if it's a single str.
609
610 Otherwise, return it unchanged.
611
612 """
613
614 if isinstance(thing, basestring):
615 return [thing]
616 return thing
617
618
619 def _delist(thing):
620 """
621 Unwrap ``thing`` to a single element if its a single str in a list.
622
623 Otherwise, return it unchanged.
624
625 """
626
627 if (
628 isinstance(thing, list) and
629 len(thing) == 1
630 and isinstance(thing[0], basestring)
631 ):
632 return thing[0]
633 return thing
634
635
636 def validate(
637 instance, schema, meta_validate=True, cls=Validator, *args, **kwargs
638 ):
639 """
640 Validate an ``instance`` under the given ``schema``.
641
642 By default, the :class:`Validator` class from this module is used to
643 perform the validation. To use another validator, pass it into the ``cls``
644 argument.
645
646 Any other provided positional and keyword arguments will be provided to the
647 ``cls``. See the :class:`Validator` class' docstring for details on the
648 arguments it accepts.
649
650 """
651
652 validator = cls(*args, **kwargs)
653 validator.validate(instance, schema, meta_validate=meta_validate)
@@ -158,7 +158,7 b''
158 158 },
159 159 "prompt_number": {
160 160 "type": ["integer","null"],
161 "required": true
161 "required": true,
162 162 "minimum": 0
163 163 },
164 164 "language": {
@@ -1,8 +1,8 b''
1 1 #!/usr/bin/env python
2 2 # -*- coding: utf8 -*-
3 3
4 from jsonschema import Validator, validate, ValidationError
5 import jsonpointer
4 from IPython.external.jsonschema import Validator, validate, ValidationError
5 import IPython.external.jsonpointer as jsonpointer
6 6 import argparse
7 7 import traceback
8 8 import json
General Comments 0
You need to be logged in to leave comments. Login now