Show More
This diff has been collapsed as it changes many lines, (587 lines changed) Show them Hide them | |||
@@ -1,12 +1,11 b'' | |||
|
1 | 1 | """ |
|
2 | 2 | An implementation of JSON Schema for Python |
|
3 | 3 | |
|
4 |
The main functionality is provided by 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 | |
@@ -20,131 +19,33 b' 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): |
@@ -188,9 +89,9 b' class ValidationError(Exception):' | |||
|
188 | 89 | self.path = [] |
|
189 | 90 | |
|
190 | 91 | |
|
191 | class Validator(object): | |
|
92 | class Draft3Validator(object): | |
|
192 | 93 | """ |
|
193 |
A JSON Schema |
|
|
94 | A validator for JSON Schema draft 3. | |
|
194 | 95 | |
|
195 | 96 | """ |
|
196 | 97 | |
@@ -199,23 +100,12 b' class Validator(object):' | |||
|
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 |
|
|
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 |
@@ -223,100 +113,76 b' class Validator(object):' | |||
|
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 `` |
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
144 | def is_valid(self, instance, _schema=None): | |
|
277 | 145 | """ |
|
278 |
Check if the ``instance`` is valid under the |
|
|
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 |
|
|
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 |
|
|
306 | 164 |
|
|
307 | 165 |
|
|
308 | 166 |
|
|
309 | 167 |
|
|
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 |
@@ -331,12 +197,6 b' class Validator(object):' | |||
|
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 | |
@@ -357,9 +217,7 b' class Validator(object):' | |||
|
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"): |
@@ -367,22 +225,7 b' class Validator(object):' | |||
|
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): |
@@ -397,59 +240,71 b' class Validator(object):' | |||
|
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 |
|
|
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) |
|
|
307 | error % _extras_msg(instance[len(schema.get("items", [])):]) | |
|
453 | 308 | ) |
|
454 | 309 | |
|
455 | 310 | def validate_minimum(self, minimum, instance, schema): |
@@ -520,7 +375,7 b' class Validator(object):' | |||
|
520 | 375 | |
|
521 | 376 | if isinstance(dB, float): |
|
522 | 377 | mod = instance % dB |
|
523 |
failed = (mod > |
|
|
378 | failed = (mod > FLOAT_TOLERANCE) and (dB - mod) > FLOAT_TOLERANCE | |
|
524 | 379 | else: |
|
525 | 380 | failed = instance % dB |
|
526 | 381 | |
@@ -538,20 +393,119 b' class Validator(object):' | |||
|
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): |
@@ -590,6 +544,51 b' class ErrorTree(object):' | |||
|
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. |
@@ -603,6 +602,49 b' def _extras_msg(extras):' | |||
|
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. |
@@ -633,21 +675,60 b' def _delist(thing):' | |||
|
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,19 +1,19 b'' | |||
|
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) |
General Comments 0
You need to be logged in to leave comments.
Login now