diff options
author | Julian Berman <Julian@GrayVines.com> | 2013-05-12 21:05:49 -0400 |
---|---|---|
committer | Julian Berman <Julian@GrayVines.com> | 2013-05-12 21:05:49 -0400 |
commit | ed11586f4cc62fc9b48a0d1b328be489effabd0b (patch) | |
tree | 548dc947556c8fe2680eedcd647a50f3665369c1 /jsonschema/__init__.py | |
parent | 298106763a15e8c55302120fe35910074b007988 (diff) | |
download | jsonschema-ed11586f4cc62fc9b48a0d1b328be489effabd0b.tar.gz |
Break out into _format and validators.
Diffstat (limited to 'jsonschema/__init__.py')
-rw-r--r-- | jsonschema/__init__.py | 1269 |
1 files changed, 6 insertions, 1263 deletions
diff --git a/jsonschema/__init__.py b/jsonschema/__init__.py index 60a0013..d66af99 100644 --- a/jsonschema/__init__.py +++ b/jsonschema/__init__.py @@ -9,1270 +9,13 @@ instance under a schema, and will create a validator for you. """ -from __future__ import division, unicode_literals - -import collections -import contextlib -import datetime -import json -import numbers -import pprint -import re -import socket -import textwrap - -try: - import requests -except ImportError: - requests = None - __version__ = "1.4.0-dev" -from .compat import ( - PY3, Sequence, - urlparse, unquote, urlopen, - basestring, unicode, long, - iteritems, +from jsonschema._format import ( + FormatChecker, FormatError, draft3_format_checker, draft4_format_checker, ) -from . import _utils - - -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 = _utils.format_as_index(self.path) - schema_path = _utils.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, - _utils.indent(pschema), - path, - _utils.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 - - -meta_schemas = _utils.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 = _utils.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(_utils.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 % _utils.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 % - _utils.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 _utils.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 = _utils.ensure_list(dependency) - for dependency in dependencies: - if dependency not in instance: - yield ValidationError( - "%r is a dependency of %r" % (dependency, 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 = _utils.ensure_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( - _utils.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 _utils.ensure_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 = _utils.ensure_list(types) - - if not any(self.is_type(instance, type) for type in types): - yield ValidationError(_utils.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): - 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 +from jsonschema.validators import ( + RefResolutionError, SchemaError, ValidationError, UnknownType, + ErrorTree, Draft3Validator, Draft4Validator, RefResolver, ValidatorMixin, + validate, validates, ) - - -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 = _utils.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 isinstance(document, Sequence): - # Array indexes should be turned into integers - try: - part = int(part) - except ValueError: - pass - try: - document = document[part] - except (TypeError, LookupError): - raise RefResolutionError( - "Unresolvable JSON pointer: %r" % fragment - ) - - 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 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) |