##// END OF EJS Templates
update Jsonschema version
Matthias BUSSONNIER -
Show More
@@ -0,0 +1,3
1 _jsonschema from commit :
2 3ddd80543bd6da56eeea84b2f364febaeadf31b9
3
This diff has been collapsed as it changes many lines, (587 lines changed) Show them Hide them
@@ -1,653 +1,734
1 1 """
2 2 An implementation of JSON Schema for Python
3 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.
4 The main functionality is provided by the validator classes for each of the
5 supported JSON Schema versions.
7 6
8 The :class:`Validator` class generally attempts to be as strict as possible
9 under the JSON Schema specification. See its docstring for details.
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.
10 9
11 10 """
12 11
13 12 from __future__ import division, unicode_literals
14 13
15 14 import collections
16 15 import itertools
17 16 import operator
18 17 import re
19 18 import sys
20 19 import warnings
21 20
22 21
22 __version__ = "0.7"
23
24 FLOAT_TOLERANCE = 10 ** -15
23 25 PY3 = sys.version_info[0] >= 3
24 26
25 27 if PY3:
26 28 basestring = unicode = str
27 29 iteritems = operator.methodcaller("items")
30 from urllib.parse import unquote
28 31 else:
29 32 from itertools import izip as zip
30 33 iteritems = operator.methodcaller("iteritems")
34 from urllib import unquote
31 35
32 36
33 def _uniq(container):
37 class UnknownType(Exception):
34 38 """
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.
39 An unknown type was given.
40 40
41 41 """
42 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 43
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 }
44 class InvalidRef(Exception):
45 """
46 An invalid reference was given.
146 47
147 EPSILON = 10 ** -15
48 """
148 49
149 50
150 51 class SchemaError(Exception):
151 52 """
152 53 The provided schema is malformed.
153 54
154 55 The same attributes exist for ``SchemaError``s as for ``ValidationError``s.
155 56
156 57 """
157 58
158 59 validator = None
159 60
160 61 def __init__(self, message):
161 62 super(SchemaError, self).__init__(message)
162 63 self.message = message
163 64 self.path = []
164 65
165 66
166 67 class ValidationError(Exception):
167 68 """
168 69 The instance didn't properly validate with the provided schema.
169 70
170 71 Relevant attributes are:
171 72 * ``message`` : a human readable message explaining the error
172 73 * ``path`` : a list containing the path to the offending element (or []
173 74 if the error happened globally) in *reverse* order (i.e.
174 75 deepest index first).
175 76
176 77 """
177 78
178 79 # the failing validator will be set externally at whatever recursion level
179 80 # is immediately above the validation failure
180 81 validator = None
181 82
182 83 def __init__(self, message):
183 84 super(ValidationError, self).__init__(message)
184 85 self.message = message
185 86
186 87 # Any validator that recurses must append to the ValidationError's
187 88 # path (e.g., properties and items)
188 89 self.path = []
189 90
190 91
191 class Validator(object):
92 class Draft3Validator(object):
192 93 """
193 A JSON Schema validator.
94 A validator for JSON Schema draft 3.
194 95
195 96 """
196 97
197 98 DEFAULT_TYPES = {
198 99 "array" : list, "boolean" : bool, "integer" : int, "null" : type(None),
199 100 "number" : (int, float), "object" : dict, "string" : basestring,
200 101 }
201 102
202 def __init__(
203 self, version=DRAFT_3, unknown_type="skip",
204 unknown_property="skip", types=(),
205 ):
103 def __init__(self, schema, types=()):
206 104 """
207 Initialize a Validator.
105 Initialize a validator.
208 106
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.
107 ``schema`` should be a *valid* JSON Schema object already converted to
108 a native Python object (typically a dict via ``json.load``).
219 109
220 110 ``types`` is a mapping (or iterable of 2-tuples) containing additional
221 111 types or alternate types to verify via the 'type' property. For
222 112 instance, the default types for the 'number' JSON Schema type are
223 113 ``int`` and ``float``. To override this behavior (e.g. for also
224 114 allowing ``decimal.Decimal``), pass ``types={"number" : (int, float,
225 115 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.
116 are fairly obvious but can be accessed via the ``DEFAULT_TYPES``
117 attribute on this class if necessary.
228 118
229 119 """
230 120
231 self._unknown_type = unknown_type
232 self._unknown_property = unknown_property
233 self._version = version
234
235 121 self._types = dict(self.DEFAULT_TYPES)
236 122 self._types.update(types)
237 123 self._types["any"] = tuple(self._types.values())
238 124
125 self.schema = schema
126
239 127 def is_type(self, instance, type):
240 128 """
241 Check if an ``instance`` is of the provided ``type``.
129 Check if an ``instance`` is of the provided (JSON Schema) ``type``.
242 130
243 131 """
244 132
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 )
133 if type not in self._types:
134 raise UnknownType(type)
135 type = self._types[type]
251 136
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)
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)
275 143
276 def is_valid(self, instance, schema, meta_validate=True):
144 def is_valid(self, instance, _schema=None):
277 145 """
278 Check if the ``instance`` is valid under the ``schema``.
146 Check if the ``instance`` is valid under the current schema.
279 147
280 148 Returns a bool indicating whether validation succeeded.
281 149
282 150 """
283 151
284 error = next(self.iter_errors(instance, schema, meta_validate), None)
152 error = next(self.iter_errors(instance, _schema), None)
285 153 return error is None
286 154
287 def iter_errors(self, instance, schema, meta_validate=True):
155 @classmethod
156 def check_schema(cls, schema):
288 157 """
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``.
158 Validate a ``schema`` against the meta-schema to see if it is valid.
298 159
299 160 """
300 161
301 if meta_validate:
302 for error in self.iter_errors(
303 schema, self._version, meta_validate=False
304 ):
162 for error in cls(cls.META_SCHEMA).iter_errors(schema):
305 163 s = SchemaError(error.message)
306 164 s.path = error.path
307 165 s.validator = error.validator
308 166 # I think we're safer raising these always, not yielding them
309 167 raise s
310 168
311 for k, v in iteritems(schema):
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):
312 179 validator = getattr(self, "validate_%s" % (k.lstrip("$"),), None)
313 180
314 181 if validator is None:
315 errors = self.unknown_property(k, instance, schema)
316 else:
317 errors = validator(v, instance, schema)
182 continue
318 183
319 for error in errors or ():
184 errors = validator(v, instance, _schema) or ()
185 for error in errors:
320 186 # if the validator hasn't already been set (due to recursion)
321 187 # make sure to set it
322 188 error.validator = error.validator or k
323 189 yield error
324 190
325 191 def validate(self, *args, **kwargs):
326 192 """
327 193 Validate an ``instance`` under the given ``schema``.
328 194
329 195 """
330 196
331 197 for error in self.iter_errors(*args, **kwargs):
332 198 raise error
333 199
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 200 def validate_type(self, types, instance, schema):
341 201 types = _list(types)
342 202
343 203 for type in types:
344 204 # Ouch. Brain hurts. Two paths here, either we have a schema, then
345 205 # check if the instance is valid under it
346 206 if ((
347 207 self.is_type(type, "object") and
348 208 self.is_valid(instance, type)
349 209
350 210 # Or we have a type as a string, just check if the instance is that
351 211 # type. Also, HACK: we can reach the `or` here if skip_types is
352 212 # something other than error. If so, bail out.
353 213
354 214 ) or (
355 215 self.is_type(type, "string") and
356 216 (self.is_type(instance, type) or type not in self._types)
357 217 )):
358 218 return
359 219 else:
360 yield ValidationError(
361 "%r is not of type %r" % (instance, _delist(types))
362 )
220 yield ValidationError(_types_msg(instance, types))
363 221
364 222 def validate_properties(self, properties, instance, schema):
365 223 if not self.is_type(instance, "object"):
366 224 return
367 225
368 226 for property, subschema in iteritems(properties):
369 227 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 ):
228 for error in self.iter_errors(instance[property], subschema):
386 229 error.path.append(property)
387 230 yield error
388 231 elif subschema.get("required", False):
389 232 error = ValidationError(
390 233 "%r is a required property" % (property,)
391 234 )
392 235 error.path.append(property)
393 236 error.validator = "required"
394 237 yield error
395 238
396 239 def validate_patternProperties(self, patternProperties, instance, schema):
397 240 for pattern, subschema in iteritems(patternProperties):
398 241 for k, v in iteritems(instance):
399 242 if re.match(pattern, k):
400 for error in self.iter_errors(
401 v, subschema, meta_validate=False
402 ):
243 for error in self.iter_errors(v, subschema):
403 244 yield error
404 245
405 246 def validate_additionalProperties(self, aP, instance, schema):
406 247 if not self.is_type(instance, "object"):
407 248 return
408 249
409 # no viewkeys in <2.7, and pypy seems to fail on vk - vk anyhow, so...
410 extras = set(instance) - set(schema.get("properties", {}))
250 extras = set(_find_additional_properties(instance, schema))
411 251
412 252 if self.is_type(aP, "object"):
413 253 for extra in extras:
414 for error in self.iter_errors(
415 instance[extra], aP, meta_validate=False
416 ):
254 for error in self.iter_errors(instance[extra], aP):
417 255 yield error
418 256 elif not aP and extras:
419 257 error = "Additional properties are not allowed (%s %s unexpected)"
420 258 yield ValidationError(error % _extras_msg(extras))
421 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
422 279 def validate_items(self, items, instance, schema):
423 280 if not self.is_type(instance, "array"):
424 281 return
425 282
426 283 if self.is_type(items, "object"):
427 284 for index, item in enumerate(instance):
428 for error in self.iter_errors(
429 item, items, meta_validate=False
430 ):
285 for error in self.iter_errors(item, items):
431 286 error.path.append(index)
432 287 yield error
433 288 else:
434 289 for (index, item), subschema in zip(enumerate(instance), items):
435 for error in self.iter_errors(
436 item, subschema, meta_validate=False
437 ):
290 for error in self.iter_errors(item, subschema):
438 291 error.path.append(index)
439 292 yield error
440 293
441 294 def validate_additionalItems(self, aI, instance, schema):
442 295 if not self.is_type(instance, "array"):
443 296 return
297 if not self.is_type(schema.get("items"), "array"):
298 return
444 299
445 300 if self.is_type(aI, "object"):
446 301 for item in instance[len(schema):]:
447 for error in self.iter_errors(item, aI, meta_validate=False):
302 for error in self.iter_errors(item, aI):
448 303 yield error
449 304 elif not aI and len(instance) > len(schema.get("items", [])):
450 305 error = "Additional items are not allowed (%s %s unexpected)"
451 306 yield ValidationError(
452 error % _extras_msg(instance[len(schema) - 1:])
307 error % _extras_msg(instance[len(schema.get("items", [])):])
453 308 )
454 309
455 310 def validate_minimum(self, minimum, instance, schema):
456 311 if not self.is_type(instance, "number"):
457 312 return
458 313
459 314 instance = float(instance)
460 315 if schema.get("exclusiveMinimum", False):
461 316 failed = instance <= minimum
462 317 cmp = "less than or equal to"
463 318 else:
464 319 failed = instance < minimum
465 320 cmp = "less than"
466 321
467 322 if failed:
468 323 yield ValidationError(
469 324 "%r is %s the minimum of %r" % (instance, cmp, minimum)
470 325 )
471 326
472 327 def validate_maximum(self, maximum, instance, schema):
473 328 if not self.is_type(instance, "number"):
474 329 return
475 330
476 331 instance = float(instance)
477 332 if schema.get("exclusiveMaximum", False):
478 333 failed = instance >= maximum
479 334 cmp = "greater than or equal to"
480 335 else:
481 336 failed = instance > maximum
482 337 cmp = "greater than"
483 338
484 339 if failed:
485 340 yield ValidationError(
486 341 "%r is %s the maximum of %r" % (instance, cmp, maximum)
487 342 )
488 343
489 344 def validate_minItems(self, mI, instance, schema):
490 345 if self.is_type(instance, "array") and len(instance) < mI:
491 346 yield ValidationError("%r is too short" % (instance,))
492 347
493 348 def validate_maxItems(self, mI, instance, schema):
494 349 if self.is_type(instance, "array") and len(instance) > mI:
495 350 yield ValidationError("%r is too long" % (instance,))
496 351
497 352 def validate_uniqueItems(self, uI, instance, schema):
498 353 if uI and self.is_type(instance, "array") and not _uniq(instance):
499 354 yield ValidationError("%r has non-unique elements" % instance)
500 355
501 356 def validate_pattern(self, patrn, instance, schema):
502 357 if self.is_type(instance, "string") and not re.match(patrn, instance):
503 358 yield ValidationError("%r does not match %r" % (instance, patrn))
504 359
505 360 def validate_minLength(self, mL, instance, schema):
506 361 if self.is_type(instance, "string") and len(instance) < mL:
507 362 yield ValidationError("%r is too short" % (instance,))
508 363
509 364 def validate_maxLength(self, mL, instance, schema):
510 365 if self.is_type(instance, "string") and len(instance) > mL:
511 366 yield ValidationError("%r is too long" % (instance,))
512 367
513 368 def validate_enum(self, enums, instance, schema):
514 369 if instance not in enums:
515 370 yield ValidationError("%r is not one of %r" % (instance, enums))
516 371
517 372 def validate_divisibleBy(self, dB, instance, schema):
518 373 if not self.is_type(instance, "number"):
519 374 return
520 375
521 376 if isinstance(dB, float):
522 377 mod = instance % dB
523 failed = (mod > EPSILON) and (dB - mod) > EPSILON
378 failed = (mod > FLOAT_TOLERANCE) and (dB - mod) > FLOAT_TOLERANCE
524 379 else:
525 380 failed = instance % dB
526 381
527 382 if failed:
528 383 yield ValidationError("%r is not divisible by %r" % (instance, dB))
529 384
530 385 def validate_disallow(self, disallow, instance, schema):
531 386 for disallowed in _list(disallow):
532 387 if self.is_valid(instance, {"type" : [disallowed]}):
533 388 yield ValidationError(
534 389 "%r is disallowed for %r" % (disallowed, instance)
535 390 )
536 391
537 392 def validate_extends(self, extends, instance, schema):
538 393 if self.is_type(extends, "object"):
539 394 extends = [extends]
540 395 for subschema in extends:
541 for error in self.iter_errors(
542 instance, subschema, meta_validate=False
543 ):
396 for error in self.iter_errors(instance, subschema):
544 397 yield error
545 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
546 403
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)
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 )
555 509
556 510
557 511 class ErrorTree(object):
558 512 """
559 513 ErrorTrees make it easier to check which validations failed.
560 514
561 515 """
562 516
563 517 def __init__(self, errors=()):
564 518 self.errors = {}
565 519 self._contents = collections.defaultdict(self.__class__)
566 520
567 521 for error in errors:
568 522 container = self
569 523 for element in reversed(error.path):
570 524 container = container[element]
571 525 container.errors[error.validator] = error
572 526
573 527 def __contains__(self, k):
574 528 return k in self._contents
575 529
576 530 def __getitem__(self, k):
577 531 return self._contents[k]
578 532
579 533 def __setitem__(self, k, v):
580 534 self._contents[k] = v
581 535
582 536 def __iter__(self):
583 537 return iter(self._contents)
584 538
585 539 def __len__(self):
586 540 child_errors = sum(len(tree) for _, tree in iteritems(self._contents))
587 541 return len(self.errors) + child_errors
588 542
589 543 def __repr__(self):
590 544 return "<%s (%s errors)>" % (self.__class__.__name__, len(self))
591 545
592 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
593 592 def _extras_msg(extras):
594 593 """
595 594 Create an error message for extra items or properties.
596 595
597 596 """
598 597
599 598 if len(extras) == 1:
600 599 verb = "was"
601 600 else:
602 601 verb = "were"
603 602 return ", ".join(repr(extra) for extra in extras), verb
604 603
605 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
606 648 def _list(thing):
607 649 """
608 650 Wrap ``thing`` in a list if it's a single str.
609 651
610 652 Otherwise, return it unchanged.
611 653
612 654 """
613 655
614 656 if isinstance(thing, basestring):
615 657 return [thing]
616 658 return thing
617 659
618 660
619 661 def _delist(thing):
620 662 """
621 663 Unwrap ``thing`` to a single element if its a single str in a list.
622 664
623 665 Otherwise, return it unchanged.
624 666
625 667 """
626 668
627 669 if (
628 670 isinstance(thing, list) and
629 671 len(thing) == 1
630 672 and isinstance(thing[0], basestring)
631 673 ):
632 674 return thing[0]
633 675 return thing
634 676
635 677
636 def validate(
637 instance, schema, meta_validate=True, cls=Validator, *args, **kwargs
638 ):
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):
639 707 """
640 708 Validate an ``instance`` under the given ``schema``.
641 709
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.
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``).
645 714
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.
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.
649 719
650 720 """
651 721
652 validator = cls(*args, **kwargs)
653 validator.validate(instance, schema, meta_validate=meta_validate)
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)
@@ -1,88 +1,88
1 1 #!/usr/bin/env python
2 2 # -*- coding: utf8 -*-
3 3
4 from IPython.external.jsonschema import Validator, validate, ValidationError
4 from IPython.external.jsonschema import Draft3Validator, validate, ValidationError
5 5 import IPython.external.jsonpointer as jsonpointer
6 6 import argparse
7 7 import traceback
8 8 import json
9 9
10 v = Validator();
11 10 def nbvalidate(nbjson, schema='v3.withref.json', key=None,verbose=True):
12 11 v3schema = resolve_ref(json.load(open(schema,'r')))
13 12 if key :
14 13 v3schema = jsonpointer.resolve_pointer(v3schema,key)
15 14 errors = 0
16 for error in v.iter_errors(nbjson, v3schema):
15 v = Draft3Validator(v3schema);
16 for error in v.iter_errors(nbjson):
17 17 errors = errors + 1
18 18 if verbose:
19 19 print(error)
20 20 return errors
21 21
22 22 def resolve_ref(json, base=None):
23 23 """return a json with resolved internal references
24 24
25 25 only support local reference to the same json
26 26 """
27 27 if not base :
28 28 base = json
29 29
30 30 temp = None
31 31 if type(json) is list:
32 32 temp = [];
33 33 for item in json:
34 34 temp.append(resolve_ref(item, base=base))
35 35 elif type(json) is dict:
36 36 temp = {};
37 37 for key,value in json.iteritems():
38 38 if key == '$ref':
39 39 return resolve_ref(jsonpointer.resolve_pointer(base,value), base=base)
40 40 else :
41 41 temp[key]=resolve_ref(value, base=base)
42 42 else :
43 43 return json
44 44 return temp
45 45
46 46 def convert(namein, nameout, indent=2):
47 47 """resolve the references of namein, save the result in nameout"""
48 48 jsn = None
49 49 with open(namein) as file :
50 50 jsn = json.load(file)
51 51 v = resolve_ref(jsn, base=jsn)
52 52 x = jsonpointer.resolve_pointer(v, '/notebook')
53 53 with open(nameout,'w') as file:
54 54 json.dump(x,file,indent=indent)
55 55
56 56
57 57 if __name__ == '__main__':
58 58 parser = argparse.ArgumentParser()
59 59 parser.add_argument('-s', '--schema',
60 60 type=str, default='v3.withref.json')
61 61
62 62 parser.add_argument('-k', '--key',
63 63 type=str, default='/notebook',
64 64 help='subkey to extract json schema from json file')
65 65
66 66 parser.add_argument("-v", "--verbose", action="store_true",
67 67 help="increase output verbosity")
68 68
69 69 parser.add_argument('filename',
70 70 type=str,
71 71 help="file to validate",
72 72 nargs='*',
73 73 metavar='names')
74 74
75 75 args = parser.parse_args()
76 76 for name in args.filename :
77 77 nerror = nbvalidate(json.load(open(name,'r')),
78 78 schema=args.schema,
79 79 key=args.key,
80 80 verbose=args.verbose)
81 81 if nerror is 0:
82 82 print u"[Pass]",name
83 83 else :
84 84 print u"[ ]",name,'(%d)'%(nerror)
85 85 if args.verbose :
86 86 print '=================================================='
87 87
88 88
General Comments 0
You need to be logged in to leave comments. Login now