1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471 |
- """
- An implementation of JSON Schema for Python
- The main functionality is provided by the validator classes for each of the
- supported JSON Schema versions.
- Most commonly, :func:`validate` is the quickest way to simply validate a given
- instance under a schema, and will create a validator for you.
- """
- from __future__ import division, unicode_literals
- import contextlib
- import datetime
- import itertools
- import collections
- import json
- import numbers
- import operator
- import pprint
- import re
- import socket
- import sys
- import textwrap
- try:
- from collections import MutableMapping
- except ImportError:
- from collections.abc import MutableMapping
- try:
- import requests
- except ImportError:
- requests = None
- __version__ = "1.3.0"
- PY3 = sys.version_info[0] >= 3
- if PY3:
- from urllib import parse as urlparse
- from urllib.parse import unquote
- from urllib.request import urlopen
- basestring = unicode = str
- long = int
- iteritems = operator.methodcaller("items")
- else:
- from itertools import izip as zip
- from urllib import unquote
- from urllib2 import urlopen
- import urlparse
- iteritems = operator.methodcaller("iteritems")
- FLOAT_TOLERANCE = 10 ** -15
- validators = {}
- class _Unset(object):
- """
- An as-of-yet unset attribute.
- """
- def __repr__(self):
- return "<unset>"
- _unset = _Unset()
- class _Error(Exception):
- def __init__(
- self, message, validator=_unset, path=(), cause=None, context=(),
- validator_value=_unset, instance=_unset, schema=_unset, schema_path=(),
- ):
- self.message = message
- self.path = collections.deque(path)
- self.schema_path = collections.deque(schema_path)
- self.context = list(context)
- self.cause = self.__cause__ = cause
- self.validator = validator
- self.validator_value = validator_value
- self.instance = instance
- self.schema = schema
- @classmethod
- def create_from(cls, other):
- return cls(
- message=other.message,
- cause=other.cause,
- context=other.context,
- path=other.path,
- schema_path=other.schema_path,
- validator=other.validator,
- validator_value=other.validator_value,
- instance=other.instance,
- schema=other.schema,
- )
- def _set(self, **kwargs):
- for k, v in iteritems(kwargs):
- if getattr(self, k) is _unset:
- setattr(self, k, v)
- def __repr__(self):
- return "<%s: %r>" % (self.__class__.__name__, self.message)
- def __str__(self):
- return unicode(self).encode("utf-8")
- def __unicode__(self):
- if _unset in (
- self.validator, self.validator_value, self.instance, self.schema,
- ):
- return self.message
- path = _format_as_index(self.path)
- schema_path = _format_as_index(list(self.schema_path)[:-1])
- pschema = pprint.pformat(self.schema, width=72)
- pinstance = pprint.pformat(self.instance, width=72)
- return self.message + textwrap.dedent("""
- Failed validating %r in schema%s:
- %s
- On instance%s:
- %s
- """.rstrip()
- ) % (
- self.validator,
- schema_path,
- _indent(pschema),
- path,
- _indent(pinstance),
- )
- if PY3:
- __str__ = __unicode__
- class FormatError(Exception):
- def __init__(self, message, cause=None):
- super(FormatError, self).__init__(message, cause)
- self.message = message
- self.cause = self.__cause__ = cause
- def __str__(self):
- return self.message.encode("utf-8")
- def __unicode__(self):
- return self.message
- if PY3:
- __str__ = __unicode__
- class SchemaError(_Error): pass
- class ValidationError(_Error): pass
- class RefResolutionError(Exception): pass
- class UnknownType(Exception): pass
- class _URIDict(MutableMapping):
- """
- Dictionary which uses normalized URIs as keys.
- """
- def normalize(self, uri):
- return urlparse.urlsplit(uri).geturl()
- def __init__(self, *args, **kwargs):
- self.store = dict()
- self.store.update(*args, **kwargs)
- def __getitem__(self, uri):
- return self.store[self.normalize(uri)]
- def __setitem__(self, uri, value):
- self.store[self.normalize(uri)] = value
- def __delitem__(self, uri):
- del self.store[self.normalize(uri)]
- def __iter__(self):
- return iter(self.store)
- def __len__(self):
- return len(self.store)
- def __repr__(self):
- return repr(self.store)
- meta_schemas = _URIDict()
- def validates(version):
- """
- Register the decorated validator for a ``version`` of the specification.
- Registered validators and their meta schemas will be considered when
- parsing ``$schema`` properties' URIs.
- :argument str version: an identifier to use as the version's name
- :returns: a class decorator to decorate the validator with the version
- """
- def _validates(cls):
- validators[version] = cls
- if "id" in cls.META_SCHEMA:
- meta_schemas[cls.META_SCHEMA["id"]] = cls
- return cls
- return _validates
- class ValidatorMixin(object):
- """
- Concrete implementation of :class:`IValidator`.
- Provides default implementations of each method. Validation of schema
- properties is dispatched to ``validate_property`` methods. E.g., to
- implement a validator for a ``maximum`` property, create a
- ``validate_maximum`` method. Validator methods should yield zero or more
- :exc:`ValidationError``\s to signal failed validation.
- """
- DEFAULT_TYPES = {
- "array" : list, "boolean" : bool, "integer" : (int, long),
- "null" : type(None), "number" : numbers.Number, "object" : dict,
- "string" : basestring,
- }
- def __init__(self, schema, types=(), resolver=None, format_checker=None):
- self._types = dict(self.DEFAULT_TYPES)
- self._types.update(types)
- if resolver is None:
- resolver = RefResolver.from_schema(schema)
- self.resolver = resolver
- self.format_checker = format_checker
- self.schema = schema
- def is_type(self, instance, type):
- if type not in self._types:
- raise UnknownType(type)
- pytypes = self._types[type]
- # bool inherits from int, so ensure bools aren't reported as integers
- if isinstance(instance, bool):
- pytypes = _flatten(pytypes)
- num = any(issubclass(pytype, numbers.Number) for pytype in pytypes)
- if num and bool not in pytypes:
- return False
- return isinstance(instance, pytypes)
- def is_valid(self, instance, _schema=None):
- error = next(self.iter_errors(instance, _schema), None)
- return error is None
- @classmethod
- def check_schema(cls, schema):
- for error in cls(cls.META_SCHEMA).iter_errors(schema):
- raise SchemaError.create_from(error)
- def iter_errors(self, instance, _schema=None):
- if _schema is None:
- _schema = self.schema
- with self.resolver.in_scope(_schema.get("id", "")):
- ref = _schema.get("$ref")
- if ref is not None:
- validators = [("$ref", ref)]
- else:
- validators = iteritems(_schema)
- for k, v in validators:
- validator_attr = "validate_%s" % (k.lstrip("$"),)
- validator = getattr(self, validator_attr, None)
- if validator is None:
- continue
- errors = validator(v, instance, _schema) or ()
- for error in errors:
- # set details if they weren't already set by the called fn
- error._set(
- validator=k,
- validator_value=v,
- instance=instance,
- schema=_schema,
- )
- if k != "$ref":
- error.schema_path.appendleft(k)
- yield error
- def descend(self, instance, schema, path=None, schema_path=None):
- for error in self.iter_errors(instance, schema):
- if path is not None:
- error.path.appendleft(path)
- if schema_path is not None:
- error.schema_path.appendleft(schema_path)
- yield error
- def validate(self, *args, **kwargs):
- for error in self.iter_errors(*args, **kwargs):
- raise error
- class _Draft34CommonMixin(object):
- """
- Contains the validator methods common to both JSON schema drafts.
- """
- def validate_patternProperties(self, patternProperties, instance, schema):
- if not self.is_type(instance, "object"):
- return
- for pattern, subschema in iteritems(patternProperties):
- for k, v in iteritems(instance):
- if re.search(pattern, k):
- for error in self.descend(
- v, subschema, path=k, schema_path=pattern
- ):
- yield error
- def validate_additionalProperties(self, aP, instance, schema):
- if not self.is_type(instance, "object"):
- return
- extras = set(_find_additional_properties(instance, schema))
- if self.is_type(aP, "object"):
- for extra in extras:
- for error in self.descend(instance[extra], aP, path=extra):
- yield error
- elif not aP and extras:
- error = "Additional properties are not allowed (%s %s unexpected)"
- yield ValidationError(error % _extras_msg(extras))
- def validate_items(self, items, instance, schema):
- if not self.is_type(instance, "array"):
- return
- if self.is_type(items, "object"):
- for index, item in enumerate(instance):
- for error in self.descend(item, items, path=index):
- yield error
- else:
- for (index, item), subschema in zip(enumerate(instance), items):
- for error in self.descend(
- item, subschema, path=index, schema_path=index
- ):
- yield error
- def validate_additionalItems(self, aI, instance, schema):
- if (
- not self.is_type(instance, "array") or
- self.is_type(schema.get("items", {}), "object")
- ):
- return
- if self.is_type(aI, "object"):
- for index, item in enumerate(
- instance[len(schema.get("items", [])):]):
- for error in self.descend(item, aI, path=index):
- yield error
- elif not aI and len(instance) > len(schema.get("items", [])):
- error = "Additional items are not allowed (%s %s unexpected)"
- yield ValidationError(
- error % _extras_msg(instance[len(schema.get("items", [])):])
- )
- def validate_minimum(self, minimum, instance, schema):
- if not self.is_type(instance, "number"):
- return
- instance = float(instance)
- if schema.get("exclusiveMinimum", False):
- failed = instance <= minimum
- cmp = "less than or equal to"
- else:
- failed = instance < minimum
- cmp = "less than"
- if failed:
- yield ValidationError(
- "%r is %s the minimum of %r" % (instance, cmp, minimum)
- )
- def validate_maximum(self, maximum, instance, schema):
- if not self.is_type(instance, "number"):
- return
- instance = float(instance)
- if schema.get("exclusiveMaximum", False):
- failed = instance >= maximum
- cmp = "greater than or equal to"
- else:
- failed = instance > maximum
- cmp = "greater than"
- if failed:
- yield ValidationError(
- "%r is %s the maximum of %r" % (instance, cmp, maximum)
- )
- def _validate_multipleOf(self, dB, instance, schema):
- if not self.is_type(instance, "number"):
- return
- if isinstance(dB, float):
- mod = instance % dB
- failed = (mod > FLOAT_TOLERANCE) and (dB - mod) > FLOAT_TOLERANCE
- else:
- failed = instance % dB
- if failed:
- yield ValidationError(
- "%r is not a multiple of %r" % (instance, dB)
- )
- def validate_minItems(self, mI, instance, schema):
- if self.is_type(instance, "array") and len(instance) < mI:
- yield ValidationError("%r is too short" % (instance,))
- def validate_maxItems(self, mI, instance, schema):
- if self.is_type(instance, "array") and len(instance) > mI:
- yield ValidationError("%r is too long" % (instance,))
- def validate_uniqueItems(self, uI, instance, schema):
- if uI and self.is_type(instance, "array") and not _uniq(instance):
- yield ValidationError("%r has non-unique elements" % instance)
- def validate_pattern(self, patrn, instance, schema):
- if self.is_type(instance, "string") and not re.search(patrn, instance):
- yield ValidationError("%r does not match %r" % (instance, patrn))
- def validate_format(self, format, instance, schema):
- if (
- self.format_checker is not None and
- self.is_type(instance, "string")
- ):
- try:
- self.format_checker.check(instance, format)
- except FormatError as error:
- yield ValidationError(error.message, cause=error.cause)
- def validate_minLength(self, mL, instance, schema):
- if self.is_type(instance, "string") and len(instance) < mL:
- yield ValidationError("%r is too short" % (instance,))
- def validate_maxLength(self, mL, instance, schema):
- if self.is_type(instance, "string") and len(instance) > mL:
- yield ValidationError("%r is too long" % (instance,))
- def validate_dependencies(self, dependencies, instance, schema):
- if not self.is_type(instance, "object"):
- return
- for property, dependency in iteritems(dependencies):
- if property not in instance:
- continue
- if self.is_type(dependency, "object"):
- for error in self.descend(
- instance, dependency, schema_path=property
- ):
- yield error
- else:
- dependencies = _list(dependency)
- for depend in dependencies:
- if depend not in instance:
- yield ValidationError(
- "%r is a dependency of %r" % (depend, property)
- )
- def validate_enum(self, enums, instance, schema):
- if instance not in enums:
- yield ValidationError("%r is not one of %r" % (instance, enums))
- def validate_ref(self, ref, instance, schema):
- with self.resolver.resolving(ref) as resolved:
- for error in self.descend(instance, resolved):
- yield error
- @validates("draft3")
- class Draft3Validator(ValidatorMixin, _Draft34CommonMixin, object):
- """
- A validator for JSON Schema draft 3.
- """
- def validate_type(self, types, instance, schema):
- types = _list(types)
- all_errors = []
- for index, type in enumerate(types):
- if type == "any":
- return
- if self.is_type(type, "object"):
- errors = list(self.descend(instance, type, schema_path=index))
- if not errors:
- return
- all_errors.extend(errors)
- elif self.is_type(type, "string"):
- if self.is_type(instance, type):
- return
- else:
- yield ValidationError(
- _types_msg(instance, types), context=all_errors,
- )
- def validate_properties(self, properties, instance, schema):
- if not self.is_type(instance, "object"):
- return
- for property, subschema in iteritems(properties):
- if property in instance:
- for error in self.descend(
- instance[property],
- subschema,
- path=property,
- schema_path=property,
- ):
- yield error
- elif subschema.get("required", False):
- error = ValidationError("%r is a required property" % property)
- error._set(
- validator="required",
- validator_value=subschema["required"],
- instance=instance,
- schema=schema,
- )
- error.path.appendleft(property)
- error.schema_path.extend([property, "required"])
- yield error
- def validate_disallow(self, disallow, instance, schema):
- for disallowed in _list(disallow):
- if self.is_valid(instance, {"type" : [disallowed]}):
- yield ValidationError(
- "%r is disallowed for %r" % (disallowed, instance)
- )
- def validate_extends(self, extends, instance, schema):
- if self.is_type(extends, "object"):
- for error in self.descend(instance, extends):
- yield error
- return
- for index, subschema in enumerate(extends):
- for error in self.descend(instance, subschema, schema_path=index):
- yield error
- validate_divisibleBy = _Draft34CommonMixin._validate_multipleOf
- META_SCHEMA = {
- "$schema" : "http://json-schema.org/draft-03/schema#",
- "id" : "http://json-schema.org/draft-03/schema#",
- "type" : "object",
- "properties" : {
- "type" : {
- "type" : ["string", "array"],
- "items" : {"type" : ["string", {"$ref" : "#"}]},
- "uniqueItems" : True,
- "default" : "any"
- },
- "properties" : {
- "type" : "object",
- "additionalProperties" : {"$ref" : "#", "type": "object"},
- "default" : {}
- },
- "patternProperties" : {
- "type" : "object",
- "additionalProperties" : {"$ref" : "#"},
- "default" : {}
- },
- "additionalProperties" : {
- "type" : [{"$ref" : "#"}, "boolean"], "default" : {}
- },
- "items" : {
- "type" : [{"$ref" : "#"}, "array"],
- "items" : {"$ref" : "#"},
- "default" : {}
- },
- "additionalItems" : {
- "type" : [{"$ref" : "#"}, "boolean"], "default" : {}
- },
- "required" : {"type" : "boolean", "default" : False},
- "dependencies" : {
- "type" : ["string", "array", "object"],
- "additionalProperties" : {
- "type" : ["string", "array", {"$ref" : "#"}],
- "items" : {"type" : "string"}
- },
- "default" : {}
- },
- "minimum" : {"type" : "number"},
- "maximum" : {"type" : "number"},
- "exclusiveMinimum" : {"type" : "boolean", "default" : False},
- "exclusiveMaximum" : {"type" : "boolean", "default" : False},
- "minItems" : {"type" : "integer", "minimum" : 0, "default" : 0},
- "maxItems" : {"type" : "integer", "minimum" : 0},
- "uniqueItems" : {"type" : "boolean", "default" : False},
- "pattern" : {"type" : "string", "format" : "regex"},
- "minLength" : {"type" : "integer", "minimum" : 0, "default" : 0},
- "maxLength" : {"type" : "integer"},
- "enum" : {"type" : "array", "minItems" : 1, "uniqueItems" : True},
- "default" : {"type" : "any"},
- "title" : {"type" : "string"},
- "description" : {"type" : "string"},
- "format" : {"type" : "string"},
- "maxDecimal" : {"type" : "number", "minimum" : 0},
- "divisibleBy" : {
- "type" : "number",
- "minimum" : 0,
- "exclusiveMinimum" : True,
- "default" : 1
- },
- "disallow" : {
- "type" : ["string", "array"],
- "items" : {"type" : ["string", {"$ref" : "#"}]},
- "uniqueItems" : True
- },
- "extends" : {
- "type" : [{"$ref" : "#"}, "array"],
- "items" : {"$ref" : "#"},
- "default" : {}
- },
- "id" : {"type" : "string", "format" : "uri"},
- "$ref" : {"type" : "string", "format" : "uri"},
- "$schema" : {"type" : "string", "format" : "uri"},
- },
- "dependencies" : {
- "exclusiveMinimum" : "minimum", "exclusiveMaximum" : "maximum"
- },
- }
- @validates("draft4")
- class Draft4Validator(ValidatorMixin, _Draft34CommonMixin, object):
- """
- A validator for JSON Schema draft 4.
- """
- def validate_type(self, types, instance, schema):
- types = _list(types)
- if not any(self.is_type(instance, type) for type in types):
- yield ValidationError(_types_msg(instance, types))
- def validate_properties(self, properties, instance, schema):
- if not self.is_type(instance, "object"):
- return
- for property, subschema in iteritems(properties):
- if property in instance:
- for error in self.descend(
- instance[property],
- subschema,
- path=property,
- schema_path=property,
- ):
- yield error
- def validate_required(self, required, instance, schema):
- if not self.is_type(instance, "object"):
- return
- for property in required:
- if property not in instance:
- yield ValidationError("%r is a required property" % property)
- def validate_minProperties(self, mP, instance, schema):
- if self.is_type(instance, "object") and len(instance) < mP:
- yield ValidationError("%r is too short" % (instance,))
- def validate_maxProperties(self, mP, instance, schema):
- if not self.is_type(instance, "object"):
- return
- if self.is_type(instance, "object") and len(instance) > mP:
- yield ValidationError("%r is too short" % (instance,))
- def validate_allOf(self, allOf, instance, schema):
- for index, subschema in enumerate(allOf):
- for error in self.descend(instance, subschema, schema_path=index):
- yield error
- def validate_oneOf(self, oneOf, instance, schema):
- first_valid = ""
- subschemas = enumerate(oneOf)
- all_errors = []
- for index, subschema in subschemas:
- errors = list(self.descend(instance, subschema, schema_path=index))
- if not errors:
- first_valid = subschema
- break
- all_errors.extend(errors)
- else:
- yield ValidationError(
- "%r is not valid under any of the given schemas" % (instance,),
- context=all_errors,
- )
- more_valid = [s for i, s in subschemas if self.is_valid(instance, s)]
- if more_valid:
- more_valid.append(first_valid)
- reprs = ", ".join(repr(schema) for schema in more_valid)
- yield ValidationError(
- "%r is valid under each of %s" % (instance, reprs)
- )
- def validate_anyOf(self, anyOf, instance, schema):
- all_errors = []
- for index, subschema in enumerate(anyOf):
- errors = list(self.descend(instance, subschema, schema_path=index))
- if not errors:
- break
- all_errors.extend(errors)
- else:
- yield ValidationError(
- "%r is not valid under any of the given schemas" % (instance,),
- context=all_errors,
- )
- def validate_not(self, not_schema, instance, schema):
- if self.is_valid(instance, not_schema):
- yield ValidationError(
- "%r is not allowed for %r" % (not_schema, instance)
- )
- validate_multipleOf = _Draft34CommonMixin._validate_multipleOf
- META_SCHEMA = {
- "id": "http://json-schema.org/draft-04/schema#",
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Core schema meta-schema",
- "definitions": {
- "schemaArray": {
- "type": "array",
- "minItems": 1,
- "items": {"$ref": "#"}
- },
- "positiveInteger": {
- "type": "integer",
- "minimum": 0
- },
- "positiveIntegerDefault0": {
- "allOf": [
- {"$ref": "#/definitions/positiveInteger"}, {"default": 0}
- ]
- },
- "simpleTypes": {
- "enum": [
- "array",
- "boolean",
- "integer",
- "null",
- "number",
- "object",
- "string",
- ]
- },
- "stringArray": {
- "type": "array",
- "items": {"type": "string"},
- "minItems": 1,
- "uniqueItems": True
- }
- },
- "type": "object",
- "properties": {
- "id": {
- "type": "string",
- "format": "uri"
- },
- "$schema": {
- "type": "string",
- "format": "uri"
- },
- "title": {
- "type": "string"
- },
- "description": {
- "type": "string"
- },
- "default": {},
- "multipleOf": {
- "type": "number",
- "minimum": 0,
- "exclusiveMinimum": True
- },
- "maximum": {
- "type": "number"
- },
- "exclusiveMaximum": {
- "type": "boolean",
- "default": False
- },
- "minimum": {
- "type": "number"
- },
- "exclusiveMinimum": {
- "type": "boolean",
- "default": False
- },
- "maxLength": {"$ref": "#/definitions/positiveInteger"},
- "minLength": {"$ref": "#/definitions/positiveIntegerDefault0"},
- "pattern": {
- "type": "string",
- "format": "regex"
- },
- "additionalItems": {
- "anyOf": [
- {"type": "boolean"},
- {"$ref": "#"}
- ],
- "default": {}
- },
- "items": {
- "anyOf": [
- {"$ref": "#"},
- {"$ref": "#/definitions/schemaArray"}
- ],
- "default": {}
- },
- "maxItems": {"$ref": "#/definitions/positiveInteger"},
- "minItems": {"$ref": "#/definitions/positiveIntegerDefault0"},
- "uniqueItems": {
- "type": "boolean",
- "default": False
- },
- "maxProperties": {"$ref": "#/definitions/positiveInteger"},
- "minProperties": {"$ref": "#/definitions/positiveIntegerDefault0"},
- "required": {"$ref": "#/definitions/stringArray"},
- "additionalProperties": {
- "anyOf": [
- {"type": "boolean"},
- {"$ref": "#"}
- ],
- "default": {}
- },
- "definitions": {
- "type": "object",
- "additionalProperties": {"$ref": "#"},
- "default": {}
- },
- "properties": {
- "type": "object",
- "additionalProperties": {"$ref": "#"},
- "default": {}
- },
- "patternProperties": {
- "type": "object",
- "additionalProperties": {"$ref": "#"},
- "default": {}
- },
- "dependencies": {
- "type": "object",
- "additionalProperties": {
- "anyOf": [
- {"$ref": "#"},
- {"$ref": "#/definitions/stringArray"}
- ]
- }
- },
- "enum": {
- "type": "array",
- "minItems": 1,
- "uniqueItems": True
- },
- "type": {
- "anyOf": [
- {"$ref": "#/definitions/simpleTypes"},
- {
- "type": "array",
- "items": {"$ref": "#/definitions/simpleTypes"},
- "minItems": 1,
- "uniqueItems": True
- }
- ]
- },
- "allOf": {"$ref": "#/definitions/schemaArray"},
- "anyOf": {"$ref": "#/definitions/schemaArray"},
- "oneOf": {"$ref": "#/definitions/schemaArray"},
- "not": {"$ref": "#"}
- },
- "dependencies": {
- "exclusiveMaximum": ["maximum"],
- "exclusiveMinimum": ["minimum"]
- },
- "default": {}
- }
- class FormatChecker(object):
- """
- A ``format`` property checker.
- JSON Schema does not mandate that the ``format`` property actually do any
- validation. If validation is desired however, instances of this class can
- be hooked into validators to enable format validation.
- :class:`FormatChecker` objects always return ``True`` when asked about
- formats that they do not know how to validate.
- To check a custom format using a function that takes an instance and
- returns a ``bool``, use the :meth:`FormatChecker.checks` or
- :meth:`FormatChecker.cls_checks` decorators.
- :argument iterable formats: the known formats to validate. This argument
- can be used to limit which formats will be used
- during validation.
- >>> checker = FormatChecker(formats=("date-time", "regex"))
- """
- checkers = {}
- def __init__(self, formats=None):
- if formats is None:
- self.checkers = self.checkers.copy()
- else:
- self.checkers = dict((k, self.checkers[k]) for k in formats)
- def checks(self, format, raises=()):
- """
- Register a decorated function as validating a new format.
- :argument str format: the format that the decorated function will check
- :argument Exception raises: the exception(s) raised by the decorated
- function when an invalid instance is found. The exception object
- will be accessible as the :attr:`ValidationError.cause` attribute
- of the resulting validation error.
- """
- def _checks(func):
- self.checkers[format] = (func, raises)
- return func
- return _checks
- cls_checks = classmethod(checks)
- def check(self, instance, format):
- """
- Check whether the instance conforms to the given format.
- :argument instance: the instance to check
- :type: any primitive type (str, number, bool)
- :argument str format: the format that instance should conform to
- :raises: :exc:`FormatError` if instance does not conform to format
- """
- if format in self.checkers:
- func, raises = self.checkers[format]
- result, cause = None, None
- try:
- result = func(instance)
- except raises as e:
- cause = e
- if not result:
- raise FormatError(
- "%r is not a %r" % (instance, format), cause=cause,
- )
- def conforms(self, instance, format):
- """
- Check whether the instance conforms to the given format.
- :argument instance: the instance to check
- :type: any primitive type (str, number, bool)
- :argument str format: the format that instance should conform to
- :rtype: bool
- """
- try:
- self.check(instance, format)
- except FormatError:
- return False
- else:
- return True
- _draft_checkers = {"draft3": [], "draft4": []}
- def _checks_drafts(both=None, draft3=None, draft4=None, raises=()):
- draft3 = draft3 or both
- draft4 = draft4 or both
- def wrap(func):
- if draft3:
- _draft_checkers["draft3"].append(draft3)
- func = FormatChecker.cls_checks(draft3, raises)(func)
- if draft4:
- _draft_checkers["draft4"].append(draft4)
- func = FormatChecker.cls_checks(draft4, raises)(func)
- return func
- return wrap
- @_checks_drafts("email")
- def is_email(instance):
- return "@" in instance
- _checks_drafts(draft3="ip-address", draft4="ipv4", raises=socket.error)(
- socket.inet_aton
- )
- if hasattr(socket, "inet_pton"):
- @_checks_drafts("ipv6", raises=socket.error)
- def is_ipv6(instance):
- return socket.inet_pton(socket.AF_INET6, instance)
- @_checks_drafts(draft3="host-name", draft4="hostname")
- def is_host_name(instance):
- pattern = "^[A-Za-z0-9][A-Za-z0-9\.\-]{1,255}$"
- if not re.match(pattern, instance):
- return False
- components = instance.split(".")
- for component in components:
- if len(component) > 63:
- return False
- return True
- try:
- import rfc3987
- except ImportError:
- pass
- else:
- @_checks_drafts("uri", raises=ValueError)
- def is_uri(instance):
- return rfc3987.parse(instance, rule="URI_reference")
- try:
- import isodate
- except ImportError:
- pass
- else:
- _err = (ValueError, isodate.ISO8601Error)
- _checks_drafts("date-time", raises=_err)(isodate.parse_datetime)
- _checks_drafts("regex", raises=re.error)(re.compile)
- @_checks_drafts(draft3="date", raises=ValueError)
- def is_date(instance):
- return datetime.datetime.strptime(instance, "%Y-%m-%d")
- @_checks_drafts(draft3="time", raises=ValueError)
- def is_time(instance):
- return datetime.datetime.strptime(instance, "%H:%M:%S")
- try:
- import webcolors
- except ImportError:
- pass
- else:
- def is_css_color_code(instance):
- return webcolors.normalize_hex(instance)
- @_checks_drafts(draft3="color", raises=(ValueError, TypeError))
- def is_css21_color(instance):
- if instance.lower() in webcolors.css21_names_to_hex:
- return True
- return is_css_color_code(instance)
- def is_css3_color(instance):
- if instance.lower() in webcolors.css3_names_to_hex:
- return True
- return is_css_color_code(instance)
- draft3_format_checker = FormatChecker(_draft_checkers["draft3"])
- draft4_format_checker = FormatChecker(_draft_checkers["draft4"])
- class RefResolver(object):
- """
- Resolve JSON References.
- :argument str base_uri: URI of the referring document
- :argument referrer: the actual referring document
- :argument dict store: a mapping from URIs to documents to cache
- :argument bool cache_remote: whether remote refs should be cached after
- first resolution
- :argument dict handlers: a mapping from URI schemes to functions that
- should be used to retrieve them
- """
- def __init__(
- self, base_uri, referrer, store=(), cache_remote=True, handlers=(),
- ):
- self.base_uri = base_uri
- self.resolution_scope = base_uri
- self.referrer = referrer
- self.cache_remote = cache_remote
- self.handlers = dict(handlers)
- self.store = _URIDict(
- (id, validator.META_SCHEMA)
- for id, validator in iteritems(meta_schemas)
- )
- self.store.update(store)
- @classmethod
- def from_schema(cls, schema, *args, **kwargs):
- """
- Construct a resolver from a JSON schema object.
- :argument schema schema: the referring schema
- :rtype: :class:`RefResolver`
- """
- return cls(schema.get("id", ""), schema, *args, **kwargs)
- @contextlib.contextmanager
- def in_scope(self, scope):
- old_scope = self.resolution_scope
- self.resolution_scope = urlparse.urljoin(old_scope, scope)
- try:
- yield
- finally:
- self.resolution_scope = old_scope
- @contextlib.contextmanager
- def resolving(self, ref):
- """
- Context manager which resolves a JSON ``ref`` and enters the
- resolution scope of this ref.
- :argument str ref: reference to resolve
- """
- full_uri = urlparse.urljoin(self.resolution_scope, ref)
- uri, fragment = urlparse.urldefrag(full_uri)
- if uri in self.store:
- document = self.store[uri]
- elif not uri or uri == self.base_uri:
- document = self.referrer
- else:
- try:
- document = self.resolve_remote(uri)
- except Exception as exc:
- raise RefResolutionError(exc)
- old_base_uri, old_referrer = self.base_uri, self.referrer
- self.base_uri, self.referrer = uri, document
- try:
- with self.in_scope(uri):
- yield self.resolve_fragment(document, fragment)
- finally:
- self.base_uri, self.referrer = old_base_uri, old_referrer
- def resolve_fragment(self, document, fragment):
- """
- Resolve a ``fragment`` within the referenced ``document``.
- :argument document: the referrant document
- :argument str fragment: a URI fragment to resolve within it
- """
- fragment = fragment.lstrip("/")
- parts = unquote(fragment).split("/") if fragment else []
- for part in parts:
- part = part.replace("~1", "/").replace("~0", "~")
- if part not in document:
- raise RefResolutionError(
- "Unresolvable JSON pointer: %r" % fragment
- )
- document = document[part]
- return document
- def resolve_remote(self, uri):
- """
- Resolve a remote ``uri``.
- Does not check the store first, but stores the retrieved document in
- the store if :attr:`RefResolver.cache_remote` is True.
- .. note::
- If the requests_ library is present, ``jsonschema`` will use it to
- request the remote ``uri``, so that the correct encoding is
- detected and used.
- If it isn't, or if the scheme of the ``uri`` is not ``http`` or
- ``https``, UTF-8 is assumed.
- :argument str uri: the URI to resolve
- :returns: the retrieved document
- .. _requests: http://pypi.python.org/pypi/requests/
- """
- scheme = urlparse.urlsplit(uri).scheme
- if scheme in self.handlers:
- result = self.handlers[scheme](uri)
- elif (
- scheme in ["http", "https"] and
- requests and
- getattr(requests.Response, "json", None) is not None
- ):
- # Requests has support for detecting the correct encoding of
- # json over http
- if callable(requests.Response.json):
- result = requests.get(uri).json()
- else:
- result = requests.get(uri).json
- else:
- # Otherwise, pass off to urllib and assume utf-8
- result = json.loads(urlopen(uri).read().decode("utf-8"))
- if self.cache_remote:
- self.store[uri] = result
- return result
- class ErrorTree(object):
- """
- ErrorTrees make it easier to check which validations failed.
- """
- _instance = _unset
- def __init__(self, errors=()):
- self.errors = {}
- self._contents = collections.defaultdict(self.__class__)
- for error in errors:
- container = self
- for element in error.path:
- container = container[element]
- container.errors[error.validator] = error
- self._instance = error.instance
- def __contains__(self, k):
- return k in self._contents
- def __getitem__(self, k):
- """
- Retrieve the child tree with key ``k``.
- If the key is not in the instance that this tree corresponds to and is
- not known by this tree, whatever error would be raised by
- ``instance.__getitem__`` will be propagated (usually this is some
- subclass of :class:`LookupError`.
- """
- if self._instance is not _unset and k not in self:
- self._instance[k]
- return self._contents[k]
- def __setitem__(self, k, v):
- self._contents[k] = v
- def __iter__(self):
- return iter(self._contents)
- def __len__(self):
- return self.total_errors
- def __repr__(self):
- return "<%s (%s total errors)>" % (self.__class__.__name__, len(self))
- @property
- def total_errors(self):
- """
- The total number of errors in the entire tree, including children.
- """
- child_errors = sum(len(tree) for _, tree in iteritems(self._contents))
- return len(self.errors) + child_errors
- def _indent(string, times=1):
- """
- A dumb version of :func:`textwrap.indent` from Python 3.3.
- """
- return "\n".join(" " * (4 * times) + line for line in string.splitlines())
- def _format_as_index(indices):
- """
- Construct a single string containing indexing operations for the indices.
- For example, [1, 2, "foo"] -> [1][2]["foo"]
- :type indices: sequence
- """
- if not indices:
- return ""
- return "[%s]" % "][".join(repr(index) for index in indices)
- def _find_additional_properties(instance, schema):
- """
- Return the set of additional properties for the given ``instance``.
- Weeds out properties that should have been validated by ``properties`` and
- / or ``patternProperties``.
- Assumes ``instance`` is dict-like already.
- """
- properties = schema.get("properties", {})
- patterns = "|".join(schema.get("patternProperties", {}))
- for property in instance:
- if property not in properties:
- if patterns and re.search(patterns, property):
- continue
- yield property
- def _extras_msg(extras):
- """
- Create an error message for extra items or properties.
- """
- if len(extras) == 1:
- verb = "was"
- else:
- verb = "were"
- return ", ".join(repr(extra) for extra in extras), verb
- def _types_msg(instance, types):
- """
- Create an error message for a failure to match the given types.
- If the ``instance`` is an object and contains a ``name`` property, it will
- be considered to be a description of that object and used as its type.
- Otherwise the message is simply the reprs of the given ``types``.
- """
- reprs = []
- for type in types:
- try:
- reprs.append(repr(type["name"]))
- except Exception:
- reprs.append(repr(type))
- return "%r is not of type %s" % (instance, ", ".join(reprs))
- def _flatten(suitable_for_isinstance):
- """
- isinstance() can accept a bunch of really annoying different types:
- * a single type
- * a tuple of types
- * an arbitrary nested tree of tuples
- Return a flattened tuple of the given argument.
- """
- types = set()
- if not isinstance(suitable_for_isinstance, tuple):
- suitable_for_isinstance = (suitable_for_isinstance,)
- for thing in suitable_for_isinstance:
- if isinstance(thing, tuple):
- types.update(_flatten(thing))
- else:
- types.add(thing)
- return tuple(types)
- def _list(thing):
- """
- Wrap ``thing`` in a list if it's a single str.
- Otherwise, return it unchanged.
- """
- if isinstance(thing, basestring):
- return [thing]
- return thing
- def _unbool(element, true=object(), false=object()):
- """
- A hack to make True and 1 and False and 0 unique for _uniq.
- """
- if element is True:
- return true
- elif element is False:
- return false
- return element
- def _uniq(container):
- """
- Check if all of a container's elements are unique.
- Successively tries first to rely that the elements are hashable, then
- falls back on them being sortable, and finally falls back on brute
- force.
- """
- try:
- return len(set(_unbool(i) for i in container)) == len(container)
- except TypeError:
- try:
- sort = sorted(_unbool(i) for i in container)
- sliced = itertools.islice(sort, 1, None)
- for i, j in zip(sort, sliced):
- if i == j:
- return False
- except (NotImplementedError, TypeError):
- seen = []
- for e in container:
- e = _unbool(e)
- if e in seen:
- return False
- seen.append(e)
- return True
- def validate(instance, schema, cls=None, *args, **kwargs):
- if cls is None:
- cls = meta_schemas.get(schema.get("$schema", ""), Draft4Validator)
- cls.check_schema(schema)
- cls(schema, *args, **kwargs).validate(instance)
|