summaryrefslogtreecommitdiff
path: root/src/leap/base/jsonschema.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/base/jsonschema.py')
-rw-r--r--src/leap/base/jsonschema.py791
1 files changed, 0 insertions, 791 deletions
diff --git a/src/leap/base/jsonschema.py b/src/leap/base/jsonschema.py
deleted file mode 100644
index 56689b08..00000000
--- a/src/leap/base/jsonschema.py
+++ /dev/null
@@ -1,791 +0,0 @@
-"""
-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 collections
-import json
-import itertools
-import operator
-import re
-import sys
-
-
-__version__ = "0.8.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
- 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 = {}
-
-
-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
- return cls
- return _validates
-
-
-class UnknownType(Exception):
- """
- An attempt was made to check if an instance was of an unknown type.
-
- """
-
-
-class RefResolutionError(Exception):
- """
- A JSON reference failed to resolve.
-
- """
-
-
-class SchemaError(Exception):
- """
- The provided schema is malformed.
-
- The same attributes are present as for :exc:`ValidationError`\s.
-
- """
-
- def __init__(self, message, validator=None, path=()):
- super(SchemaError, self).__init__(message, validator, path)
- self.message = message
- self.path = list(path)
- self.validator = validator
-
- def __str__(self):
- return self.message
-
-
-class ValidationError(Exception):
- """
- The instance didn't properly validate under the provided schema.
-
- Relevant attributes are:
- * ``message`` : a human readable message explaining the error
- * ``path`` : a list containing the path to the offending element (or []
- if the error happened globally) in *reverse* order (i.e.
- deepest index first).
-
- """
-
- def __init__(self, message, validator=None, path=()):
- # Any validator that recurses (e.g. properties and items) must append
- # to the ValidationError's path to properly maintain where in the
- # instance the error occurred
- super(ValidationError, self).__init__(message, validator, path)
- self.message = message
- self.path = list(path)
- self.validator = validator
-
- def __str__(self):
- return self.message
-
-
-@validates("draft3")
-class Draft3Validator(object):
- """
- A validator for JSON Schema draft 3.
-
- """
-
- DEFAULT_TYPES = {
- "array": list, "boolean": bool, "integer": int, "null": type(None),
- "number": (int, float), "object": dict, "string": basestring,
- }
-
- def __init__(self, schema, types=(), resolver=None):
- self._types = dict(self.DEFAULT_TYPES)
- self._types.update(types)
-
- if resolver is None:
- resolver = RefResolver.from_schema(schema)
-
- self.resolver = resolver
- self.schema = schema
-
- def is_type(self, instance, type):
- if type == "any":
- return True
- elif type not in self._types:
- raise UnknownType(type)
- type = self._types[type]
-
- # bool inherits from int, so ensure bools aren't reported as integers
- if isinstance(instance, bool):
- type = _flatten(type)
- if int in type and bool not in type:
- return False
- return isinstance(instance, type)
-
- 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(
- error.message, validator=error.validator, path=error.path,
- )
-
- def iter_errors(self, instance, _schema=None):
- if _schema is None:
- _schema = self.schema
-
- for k, v in iteritems(_schema):
- validator = getattr(self, "validate_%s" % (k.lstrip("$"),), None)
-
- if validator is None:
- continue
-
- errors = validator(v, instance, _schema) or ()
- for error in errors:
- # set the validator if it wasn't already set by the called fn
- if error.validator is None:
- error.validator = k
- yield error
-
- def validate(self, *args, **kwargs):
- for error in self.iter_errors(*args, **kwargs):
- raise error
-
- def validate_type(self, types, instance, schema):
- types = _list(types)
-
- for type in types:
- if self.is_type(type, "object"):
- if self.is_valid(instance, type):
- return
- elif self.is_type(type, "string"):
- if self.is_type(instance, type):
- return
- else:
- 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.iter_errors(instance[property], subschema):
- error.path.append(property)
- yield error
- elif subschema.get("required", False):
- yield ValidationError(
- "%r is a required property" % (property,),
- validator="required",
- path=[property],
- )
-
- 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.match(pattern, k):
- for error in self.iter_errors(v, subschema):
- 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.iter_errors(instance[extra], aP):
- yield error
- elif not aP and extras:
- error = "Additional properties are not allowed (%s %s unexpected)"
- yield ValidationError(error % _extras_msg(extras))
-
- 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.iter_errors(instance, dependency):
- yield error
- else:
- dependencies = _list(dependency)
- for dependency in dependencies:
- if dependency not in instance:
- yield ValidationError(
- "%r is a dependency of %r" % (dependency, property)
- )
-
- 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.iter_errors(item, items):
- error.path.append(index)
- yield error
- else:
- for (index, item), subschema in zip(enumerate(instance), items):
- for error in self.iter_errors(item, subschema):
- error.path.append(index)
- yield error
-
- def validate_additionalItems(self, aI, instance, schema):
- if (
- not self.is_type(instance, "array") or
- not self.is_type(schema.get("items"), "array")
- ):
- return
-
- if self.is_type(aI, "object"):
- for item in instance[len(schema):]:
- for error in self.iter_errors(item, aI):
- 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_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.match(patrn, instance):
- yield ValidationError("%r does not match %r" % (instance, patrn))
-
- 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_enum(self, enums, instance, schema):
- if instance not in enums:
- yield ValidationError("%r is not one of %r" % (instance, enums))
-
- def validate_divisibleBy(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 divisible by %r" % (instance, dB))
-
- 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"):
- extends = [extends]
- for subschema in extends:
- for error in self.iter_errors(instance, subschema):
- yield error
-
- def validate_ref(self, ref, instance, schema):
- resolved = self.resolver.resolve(ref)
- for error in self.iter_errors(instance, resolved):
- yield error
-
-
-Draft3Validator.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"
- },
-}
-
-
-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
-
- """
-
- def __init__(self, base_uri, referrer, store=()):
- self.base_uri = base_uri
- self.referrer = referrer
- self.store = dict(store, **_meta_schemas())
-
- @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)
-
- def resolve(self, ref):
- """
- Resolve a JSON ``ref``.
-
- :argument str ref: reference to resolve
- :returns: the referrant document
-
- """
-
- base_uri = self.base_uri
- uri, fragment = urlparse.urldefrag(urlparse.urljoin(base_uri, ref))
-
- if uri in self.store:
- document = self.store[uri]
- elif not uri or uri == self.base_uri:
- document = self.referrer
- else:
- document = self.resolve_remote(uri)
-
- return self.resolve_fragment(document, fragment.lstrip("/"))
-
- 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
-
- """
-
- 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.
-
- :argument str uri: the URI to resolve
- :returns: the retrieved document
-
- """
-
- return json.load(urlopen(uri))
-
-
-class ErrorTree(object):
- """
- ErrorTrees make it easier to check which validations failed.
-
- """
-
- def __init__(self, errors=()):
- self.errors = {}
- self._contents = collections.defaultdict(self.__class__)
-
- for error in errors:
- container = self
- for element in reversed(error.path):
- container = container[element]
- container.errors[error.validator] = error
-
- def __contains__(self, k):
- return k in self._contents
-
- def __getitem__(self, k):
- """
- Retrieve the child tree with key ``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 _meta_schemas():
- """
- Collect the urls and meta schemas from each known validator.
-
- """
-
- meta_schemas = (v.META_SCHEMA for v in validators.values())
- return dict((urlparse.urldefrag(m["id"])[0], m) for m in meta_schemas)
-
-
-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 _delist(thing):
- """
- Unwrap ``thing`` to a single element if its a single str in a list.
-
- Otherwise, return it unchanged.
-
- """
-
- if (
- isinstance(thing, list) and
- len(thing) == 1
- and isinstance(thing[0], basestring)
- ):
- return thing[0]
- 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=Draft3Validator, *args, **kwargs):
- """
- Validate an ``instance`` under the given ``schema``.
-
- >>> validate([2, 3, 4], {"maxItems" : 2})
- Traceback (most recent call last):
- ...
- ValidationError: [2, 3, 4] is too long
-
- :func:`validate` will first verify that the provided schema is itself
- valid, since not doing so can lead to less obvious error messages and fail
- in less obvious or consistent ways. If you know you have a valid schema
- already or don't care, you might prefer using the ``validate`` method
- directly on a specific validator (e.g. :meth:`Draft3Validator.validate`).
-
- ``cls`` is a validator class that will be used to validate the instance.
- By default this is a draft 3 validator. Any other provided positional and
- keyword arguments will be provided to this class when constructing a
- validator.
-
- :raises:
- :exc:`ValidationError` if the instance is invalid
-
- :exc:`SchemaError` if the schema itself is invalid
-
- """
-
- cls.check_schema(schema)
- cls(schema, *args, **kwargs).validate(instance)