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