summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authork clair <kclair@riseup.net>2012-10-09 12:36:56 -0700
committerk clair <kclair@riseup.net>2012-10-09 12:36:56 -0700
commit28442669df2ee646c3d8e8fc18bd37c663c6d1eb (patch)
tree5e75c466e80805d7b3c711ebd00d0874bb8029fe
add source files for the python jsonschema module
-rw-r--r--jsonschema-0.6.tar.gzbin0 -> 13779 bytes
-rw-r--r--jsonschema-0.6/CHANGELOG.rst42
-rw-r--r--jsonschema-0.6/COPYING19
-rw-r--r--jsonschema-0.6/PKG-INFO158
-rw-r--r--jsonschema-0.6/README.rst135
-rw-r--r--jsonschema-0.6/jsonschema.py658
-rw-r--r--jsonschema-0.6/setup.py38
-rw-r--r--jsonschema-0.6/tests.py796
8 files changed, 1846 insertions, 0 deletions
diff --git a/jsonschema-0.6.tar.gz b/jsonschema-0.6.tar.gz
new file mode 100644
index 0000000..51ee84b
--- /dev/null
+++ b/jsonschema-0.6.tar.gz
Binary files differ
diff --git a/jsonschema-0.6/CHANGELOG.rst b/jsonschema-0.6/CHANGELOG.rst
new file mode 100644
index 0000000..ccdebf2
--- /dev/null
+++ b/jsonschema-0.6/CHANGELOG.rst
@@ -0,0 +1,42 @@
+v0.6
+----
+
+* Bugfixes
+ * Issue #30 - Wrong behavior for the dependencies property validation
+ * Fix a miswritten test
+
+v0.5
+----
+
+* Bugfixes
+ * Issue #17 - require path for error objects
+ * Issue #18 - multiple type validation for non-objects
+
+
+v0.4
+----
+
+* Preliminary support for programmatic access to error details (Issue #5).
+ There are certainly some corner cases that don't do the right thing yet, but
+ this works mostly.
+
+ In order to make this happen (and also to clean things up a bit), a number
+ of deprecations are necessary:
+ * ``stop_on_error`` is deprecated in ``Validator.__init__``. Use
+ ``Validator.iter_errors()`` instead.
+ * ``number_types`` and ``string_types`` are deprecated there as well.
+ Use ``types={"number" : ..., "string" : ...}`` instead.
+ * ``meta_validate`` is also deprecated, and instead is now accepted as
+ an argument to ``validate``, ``iter_errors`` and ``is_valid``.
+
+* A bugfix or two
+
+v0.3
+----
+
+* Default for unknown types and properties is now to *not* error (consistent
+ with the schema).
+* Python 3 support
+* Removed dependency on SecureTypes now that the hash bug has been resolved.
+* "Numerous bug fixes" -- most notably, a divisibleBy error for floats and a
+ bunch of missing typechecks for irrelevant properties.
diff --git a/jsonschema-0.6/COPYING b/jsonschema-0.6/COPYING
new file mode 100644
index 0000000..d8338a3
--- /dev/null
+++ b/jsonschema-0.6/COPYING
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Julian Berman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/jsonschema-0.6/PKG-INFO b/jsonschema-0.6/PKG-INFO
new file mode 100644
index 0000000..eca632a
--- /dev/null
+++ b/jsonschema-0.6/PKG-INFO
@@ -0,0 +1,158 @@
+Metadata-Version: 1.1
+Name: jsonschema
+Version: 0.6
+Summary: An implementation of JSON-Schema validation for Python
+Home-page: http://github.com/Julian/jsonschema
+Author: Julian Berman
+Author-email: Julian@GrayVines.com
+License: MIT/X
+Description: ==========
+ jsonschema
+ ==========
+
+ ``jsonschema`` is an implementation of JSON Schema (currently in `Draft 3
+ <http://tools.ietf.org/html/draft-zyp-json-schema-03>`_) for Python (supporting
+ 2.6+ including Python 3).
+
+ ::
+
+ >>> from jsonschema import validate
+
+ >>> # A sample schema, like what we'd get from json.load()
+ >>> schema = {
+ ... "type" : "object",
+ ... "properties" : {
+ ... "price" : {"type" : "number"},
+ ... "name" : {"type" : "string"},
+ ... },
+ ... }
+
+ >>> # If no exception is raised by validate(), the instance is valid.
+ >>> validate({"name" : "Eggs", "price" : 34.99}, schema)
+
+ >>> validate(
+ ... {"name" : "Eggs", "price" : "Invalid"}, schema
+ ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ ValidationError: 'Invalid' is not of type 'number'
+
+
+ Features
+ --------
+
+ * Support for Draft 3 of the Schema with the exception of
+
+ * ``$ref``, and ``extends`` that use ``$ref``\s
+
+ * Lazy validation that can iteratively report *all* validation errors.
+
+ ::
+
+ >>> from jsonschema import Validator
+ >>> schema = {
+ ... "type" : "array",
+ ... "items" : {"enum" : [1, 2, 3]},
+ ... "maxItems" : 2,
+ ... }
+ >>> v = Validator()
+ >>> for error in sorted(v.iter_errors([2, 3, 4], schema), key=str):
+ ... print(error)
+ 4 is not one of [1, 2, 3]
+ [2, 3, 4] is too long
+
+ * Small and extensible
+
+ * Programmatic querying of which properties or items failed validation.
+
+ ::
+
+ >>> from jsonschema import ErrorTree, Validator
+ >>> schema = {
+ ... "type" : "array",
+ ... "items" : {"type" : "number", "enum" : [1, 2, 3]},
+ ... "minItems" : 3,
+ ... }
+ >>> instance = ["spam", 2]
+ >>> v = Validator()
+ >>> tree = ErrorTree(v.iter_errors(instance, schema))
+
+ >>> sorted(tree.errors)
+ ['minItems']
+
+ >>> 0 in tree
+ True
+
+ >>> 1 in tree
+ False
+
+ >>> sorted(tree[0].errors)
+ ['enum', 'type']
+
+ >>> print(tree[0].errors["type"].message)
+ 'spam' is not of type 'number'
+
+
+ Schema Versioning
+ -----------------
+
+ JSON Schema is, at the time of this writing, seemingly at Draft 3, with
+ preparations for Draft 4 underway. The ``Validator`` class and ``validate``
+ function take a ``version`` argument that you can use to specify what version
+ of the Schema you are validating under.
+
+ As of right now, Draft 3 (``jsonschema.DRAFT_3``) is the only supported
+ version, and the default when validating. Whether it will remain the default
+ version in the future when it is superceeded is undecided, so if you want to be
+ safe, *explicitly* declare which version to use when validating.
+
+
+ Release Notes
+ -------------
+
+ ``0.6`` fixes the behavior for the ``dependencies`` property, which was
+ mis-implemented.
+
+
+ Running the Test Suite
+ ----------------------
+
+ ``jsonschema`` uses the wonderful `Tox <http://tox.readthedocs.org>`_ for its
+ test suite. (It really is wonderful, if for some reason you haven't heard of
+ it, you really should use it for your projects).
+
+ Assuming you have ``tox`` installed (perhaps via ``pip install tox`` or your
+ package manager), just run ``tox`` in the directory of your source checkout to
+ run ``jsonschema``'s test suite on all of the versions of Python ``jsonschema``
+ supports. Note that you'll need to have all of those versions installed in
+ order to run the tests on each of them, otherwise ``tox`` will skip (and fail)
+ the tests on that version.
+
+
+ Contributing
+ ------------
+
+ I'm Julian Berman.
+
+ ``jsonschema`` is on `GitHub <http://github.com/Julian/jsonschema>`_.
+
+ Get in touch, via GitHub or otherwise, if you've got something to contribute,
+ it'd be most welcome!
+
+ You can also generally find me on Freenode (nick: ``tos9``) in various
+ channels, including ``#python``.
+
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.1
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
diff --git a/jsonschema-0.6/README.rst b/jsonschema-0.6/README.rst
new file mode 100644
index 0000000..28e0451
--- /dev/null
+++ b/jsonschema-0.6/README.rst
@@ -0,0 +1,135 @@
+==========
+jsonschema
+==========
+
+``jsonschema`` is an implementation of JSON Schema (currently in `Draft 3
+<http://tools.ietf.org/html/draft-zyp-json-schema-03>`_) for Python (supporting
+2.6+ including Python 3).
+
+::
+
+ >>> from jsonschema import validate
+
+ >>> # A sample schema, like what we'd get from json.load()
+ >>> schema = {
+ ... "type" : "object",
+ ... "properties" : {
+ ... "price" : {"type" : "number"},
+ ... "name" : {"type" : "string"},
+ ... },
+ ... }
+
+ >>> # If no exception is raised by validate(), the instance is valid.
+ >>> validate({"name" : "Eggs", "price" : 34.99}, schema)
+
+ >>> validate(
+ ... {"name" : "Eggs", "price" : "Invalid"}, schema
+ ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ ValidationError: 'Invalid' is not of type 'number'
+
+
+Features
+--------
+
+* Support for Draft 3 of the Schema with the exception of
+
+ * ``$ref``, and ``extends`` that use ``$ref``\s
+
+* Lazy validation that can iteratively report *all* validation errors.
+
+::
+
+ >>> from jsonschema import Validator
+ >>> schema = {
+ ... "type" : "array",
+ ... "items" : {"enum" : [1, 2, 3]},
+ ... "maxItems" : 2,
+ ... }
+ >>> v = Validator()
+ >>> for error in sorted(v.iter_errors([2, 3, 4], schema), key=str):
+ ... print(error)
+ 4 is not one of [1, 2, 3]
+ [2, 3, 4] is too long
+
+* Small and extensible
+
+* Programmatic querying of which properties or items failed validation.
+
+::
+
+ >>> from jsonschema import ErrorTree, Validator
+ >>> schema = {
+ ... "type" : "array",
+ ... "items" : {"type" : "number", "enum" : [1, 2, 3]},
+ ... "minItems" : 3,
+ ... }
+ >>> instance = ["spam", 2]
+ >>> v = Validator()
+ >>> tree = ErrorTree(v.iter_errors(instance, schema))
+
+ >>> sorted(tree.errors)
+ ['minItems']
+
+ >>> 0 in tree
+ True
+
+ >>> 1 in tree
+ False
+
+ >>> sorted(tree[0].errors)
+ ['enum', 'type']
+
+ >>> print(tree[0].errors["type"].message)
+ 'spam' is not of type 'number'
+
+
+Schema Versioning
+-----------------
+
+JSON Schema is, at the time of this writing, seemingly at Draft 3, with
+preparations for Draft 4 underway. The ``Validator`` class and ``validate``
+function take a ``version`` argument that you can use to specify what version
+of the Schema you are validating under.
+
+As of right now, Draft 3 (``jsonschema.DRAFT_3``) is the only supported
+version, and the default when validating. Whether it will remain the default
+version in the future when it is superceeded is undecided, so if you want to be
+safe, *explicitly* declare which version to use when validating.
+
+
+Release Notes
+-------------
+
+``0.6`` fixes the behavior for the ``dependencies`` property, which was
+mis-implemented.
+
+
+Running the Test Suite
+----------------------
+
+``jsonschema`` uses the wonderful `Tox <http://tox.readthedocs.org>`_ for its
+test suite. (It really is wonderful, if for some reason you haven't heard of
+it, you really should use it for your projects).
+
+Assuming you have ``tox`` installed (perhaps via ``pip install tox`` or your
+package manager), just run ``tox`` in the directory of your source checkout to
+run ``jsonschema``'s test suite on all of the versions of Python ``jsonschema``
+supports. Note that you'll need to have all of those versions installed in
+order to run the tests on each of them, otherwise ``tox`` will skip (and fail)
+the tests on that version.
+
+
+Contributing
+------------
+
+I'm Julian Berman.
+
+``jsonschema`` is on `GitHub <http://github.com/Julian/jsonschema>`_.
+
+Get in touch, via GitHub or otherwise, if you've got something to contribute,
+it'd be most welcome!
+
+You can also generally find me on Freenode (nick: ``tos9``) in various
+channels, including ``#python``.
diff --git a/jsonschema-0.6/jsonschema.py b/jsonschema-0.6/jsonschema.py
new file mode 100644
index 0000000..9910c84
--- /dev/null
+++ b/jsonschema-0.6/jsonschema.py
@@ -0,0 +1,658 @@
+"""
+An implementation of JSON Schema for Python
+
+The main functionality is provided by the :class:`Validator` class, with the
+:function:`validate` function being the most common way to quickly create a
+:class:`Validator` object and validate an instance with a given schema.
+
+The :class:`Validator` class generally attempts to be as strict as possible
+under the JSON Schema specification. See its docstring for details.
+
+"""
+
+from __future__ import division, unicode_literals
+
+import collections
+import itertools
+import operator
+import re
+import sys
+import warnings
+
+
+PY3 = sys.version_info[0] >= 3
+
+if PY3:
+ basestring = unicode = str
+ iteritems = operator.methodcaller("items")
+else:
+ from itertools import izip as zip
+ iteritems = operator.methodcaller("iteritems")
+
+
+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(container)) == len(container)
+ except TypeError:
+ try:
+ sort = sorted(container)
+ sliced = itertools.islice(container, 1, None)
+ for i, j in zip(container, sliced):
+ if i == j:
+ return False
+ except (NotImplementedError, TypeError):
+ seen = []
+ for e in container:
+ if e in seen:
+ return False
+ seen.append(e)
+ return True
+
+
+__version__ = "0.6"
+
+
+DRAFT_3 = {
+ "$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"
+ },
+}
+
+EPSILON = 10 ** -15
+
+
+class SchemaError(Exception):
+ """
+ The provided schema is malformed.
+
+ The same attributes exist for ``SchemaError``s as for ``ValidationError``s.
+
+ """
+
+ validator = None
+
+ def __init__(self, message):
+ super(SchemaError, self).__init__(message)
+ self.message = message
+ self.path = []
+
+
+class ValidationError(Exception):
+ """
+ The instance didn't properly validate with 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).
+
+ """
+
+ # the failing validator will be set externally at whatever recursion level
+ # is immediately above the validation failure
+ validator = None
+
+ def __init__(self, message):
+ super(ValidationError, self).__init__(message)
+ self.message = message
+
+ # Any validator that recurses must append to the ValidationError's
+ # path (e.g., properties and items)
+ self.path = []
+
+
+class Validator(object):
+ """
+ A JSON Schema validator.
+
+ """
+
+ DEFAULT_TYPES = {
+ "array" : list, "boolean" : bool, "integer" : int, "null" : type(None),
+ "number" : (int, float), "object" : dict, "string" : basestring,
+ }
+
+ def __init__(
+ self, version=DRAFT_3, unknown_type="skip",
+ unknown_property="skip", types=(),
+ ):
+ """
+ Initialize a Validator.
+
+ ``version`` specifies which version of the JSON Schema specification to
+ validate with. Currently only draft-03 is supported (and is the
+ default).
+
+ ``unknown_type`` and ``unknown_property`` control what to do when an
+ unknown type (resp. property) is encountered. By default, the
+ metaschema is respected (which e.g. for draft 3 allows a schema to have
+ additional properties), but if for some reason you want to modify this
+ behavior, you can do so without needing to modify the metaschema by
+ passing ``"error"`` or ``"warn"`` to these arguments.
+
+ ``types`` is a mapping (or iterable of 2-tuples) containing additional
+ types or alternate types to verify via the 'type' property. For
+ instance, the default types for the 'number' JSON Schema type are
+ ``int`` and ``float``. To override this behavior (e.g. for also
+ allowing ``decimal.Decimal``), pass ``types={"number" : (int, float,
+ decimal.Decimal)} *including* the default types if so desired, which
+ are fairly obvious but can be accessed via ``Validator.DEFAULT_TYPES``
+ if necessary.
+
+ """
+
+ self._unknown_type = unknown_type
+ self._unknown_property = unknown_property
+ self._version = version
+
+ self._types = dict(self.DEFAULT_TYPES)
+ self._types.update(types)
+ self._types["any"] = tuple(self._types.values())
+
+ def is_type(self, instance, type):
+ """
+ Check if an ``instance`` is of the provided ``type``.
+
+ """
+
+ py_type = self._types.get(type)
+
+ if py_type is None:
+ return self.schema_error(
+ self._unknown_type, "%r is not a known type" % (type,)
+ )
+
+ # the only thing we're careful about here is evading bool inheriting
+ # from int, so let's be even dirtier than usual
+
+ elif (
+ # it's not a bool, so no worries
+ not isinstance(instance, bool) or
+
+ # it is a bool, but we're checking for a bool, so no worries
+ (
+ py_type is bool or
+ isinstance(py_type, tuple) and bool in py_type
+ )
+
+ ):
+ return isinstance(instance, py_type)
+
+ def schema_error(self, level, msg):
+ if level == "skip":
+ return
+ elif level == "warn":
+ warnings.warn(msg)
+ else:
+ raise SchemaError(msg)
+
+ def is_valid(self, instance, schema, meta_validate=True):
+ """
+ Check if the ``instance`` is valid under the ``schema``.
+
+ Returns a bool indicating whether validation succeeded.
+
+ """
+
+ error = next(self.iter_errors(instance, schema, meta_validate), None)
+ return error is None
+
+ def iter_errors(self, instance, schema, meta_validate=True):
+ """
+ Lazily yield each of the errors in the given ``instance``.
+
+ If you are unsure whether your schema itself is valid,
+ ``meta_validate`` will first validate that the schema is valid before
+ attempting to validate the instance. ``meta_validate`` is ``True`` by
+ default, since setting it to ``False`` can lead to confusing error
+ messages with an invalid schema. If you're sure your schema is in fact
+ valid, or don't care, feel free to set this to ``False``. The meta
+ validation will be done using the appropriate ``version``.
+
+ """
+
+ if meta_validate:
+ for error in self.iter_errors(
+ schema, self._version, meta_validate=False
+ ):
+ s = SchemaError(error.message)
+ s.path = error.path
+ s.validator = error.validator
+ # I think we're safer raising these always, not yielding them
+ raise s
+
+ for k, v in iteritems(schema):
+ validator = getattr(self, "validate_%s" % (k.lstrip("$"),), None)
+
+ if validator is None:
+ errors = self.unknown_property(k, instance, schema)
+ else:
+ errors = validator(v, instance, schema)
+
+ for error in errors or ():
+ # if the validator hasn't already been set (due to recursion)
+ # make sure to set it
+ error.validator = error.validator or k
+ yield error
+
+ def validate(self, *args, **kwargs):
+ """
+ Validate an ``instance`` under the given ``schema``.
+
+ """
+
+ for error in self.iter_errors(*args, **kwargs):
+ raise error
+
+ def unknown_property(self, property, instance, schema):
+ self.schema_error(
+ self._unknown_property,
+ "%r is not a known schema property" % (property,)
+ )
+
+ def validate_type(self, types, instance, schema):
+ types = _list(types)
+
+ for type in types:
+ # Ouch. Brain hurts. Two paths here, either we have a schema, then
+ # check if the instance is valid under it
+ if ((
+ self.is_type(type, "object") and
+ self.is_valid(instance, type)
+
+ # Or we have a type as a string, just check if the instance is that
+ # type. Also, HACK: we can reach the `or` here if skip_types is
+ # something other than error. If so, bail out.
+
+ ) or (
+ self.is_type(type, "string") and
+ (self.is_type(instance, type) or type not in self._types)
+ )):
+ return
+ else:
+ yield ValidationError(
+ "%r is not of type %r" % (instance, _delist(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, meta_validate=False
+ ):
+ error.path.append(property)
+ yield error
+ elif subschema.get("required", False):
+ error = ValidationError(
+ "%r is a required property" % (property,)
+ )
+ error.path.append(property)
+ error.validator = "required"
+ yield error
+
+ def validate_patternProperties(self, patternProperties, instance, schema):
+ 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, meta_validate=False
+ ):
+ yield error
+
+ def validate_additionalProperties(self, aP, instance, schema):
+ if not self.is_type(instance, "object"):
+ return
+
+ # no viewkeys in <2.7, and pypy seems to fail on vk - vk anyhow, so...
+ extras = set(instance) - set(schema.get("properties", {}))
+
+ if self.is_type(aP, "object"):
+ for extra in extras:
+ for error in self.iter_errors(
+ instance[extra], aP, meta_validate=False
+ ):
+ 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):
+ 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, meta_validate=False
+ ):
+ 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, meta_validate=False
+ ):
+ error.path.append(index)
+ yield error
+ else:
+ for (index, item), subschema in zip(enumerate(instance), items):
+ for error in self.iter_errors(
+ item, subschema, meta_validate=False
+ ):
+ error.path.append(index)
+ yield error
+
+ def validate_additionalItems(self, aI, instance, schema):
+ if not self.is_type(instance, "array"):
+ return
+
+ if self.is_type(aI, "object"):
+ for item in instance[len(schema):]:
+ for error in self.iter_errors(item, aI, meta_validate=False):
+ 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) - 1:])
+ )
+
+ 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 > EPSILON) and (dB - mod) > EPSILON
+ 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, meta_validate=False
+ ):
+ yield error
+
+
+for no_op in [ # handled in:
+ "required", # properties
+ "exclusiveMinimum", "exclusiveMaximum", # min*/max*
+ "default", "description", "format", "id", # no validation needed
+ "links", "name", "title",
+ "ref", "schema", # not yet supported
+]:
+ setattr(Validator, "validate_" + no_op, lambda *args, **kwargs : None)
+
+
+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):
+ return self._contents[k]
+
+ def __setitem__(self, k, v):
+ self._contents[k] = v
+
+ def __iter__(self):
+ return iter(self._contents)
+
+ def __len__(self):
+ child_errors = sum(len(tree) for _, tree in iteritems(self._contents))
+ return len(self.errors) + child_errors
+
+ def __repr__(self):
+ return "<%s (%s errors)>" % (self.__class__.__name__, len(self))
+
+
+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 _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 validate(
+ instance, schema, meta_validate=True, cls=Validator, *args, **kwargs
+):
+ """
+ Validate an ``instance`` under the given ``schema``.
+
+ By default, the :class:`Validator` class from this module is used to
+ perform the validation. To use another validator, pass it into the ``cls``
+ argument.
+
+ Any other provided positional and keyword arguments will be provided to the
+ ``cls``. See the :class:`Validator` class' docstring for details on the
+ arguments it accepts.
+
+ """
+
+ validator = cls(*args, **kwargs)
+ validator.validate(instance, schema, meta_validate=meta_validate)
diff --git a/jsonschema-0.6/setup.py b/jsonschema-0.6/setup.py
new file mode 100644
index 0000000..7b69ae8
--- /dev/null
+++ b/jsonschema-0.6/setup.py
@@ -0,0 +1,38 @@
+from distutils.core import setup
+
+from jsonschema import __version__
+
+
+with open("README.rst") as readme:
+ long_description = readme.read()
+
+
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.1",
+ "Programming Language :: Python :: 3.2",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+]
+
+
+setup(
+ name="jsonschema",
+ version=__version__,
+ py_modules=["jsonschema"],
+ author="Julian Berman",
+ author_email="Julian@GrayVines.com",
+ classifiers=classifiers,
+ description="An implementation of JSON-Schema validation for Python",
+ license="MIT/X",
+ long_description=long_description,
+ url="http://github.com/Julian/jsonschema",
+)
diff --git a/jsonschema-0.6/tests.py b/jsonschema-0.6/tests.py
new file mode 100644
index 0000000..56b1481
--- /dev/null
+++ b/jsonschema-0.6/tests.py
@@ -0,0 +1,796 @@
+from __future__ import unicode_literals
+from decimal import Decimal
+from functools import wraps
+import sys
+import warnings
+
+if sys.version_info[:2] < (2, 7): # pragma: no cover
+ import unittest2 as unittest
+else:
+ import unittest
+
+
+from jsonschema import (
+ PY3, SchemaError, ValidationError, ErrorTree, Validator,
+ iteritems, validate
+)
+
+
+if PY3:
+ basestring = unicode = str
+
+
+class ParametrizedTestCase(type):
+ """
+ A (deliberately naive & specialized) parametrized test.
+
+ """
+
+ def __new__(cls, name, bases, attrs):
+ attr = {}
+
+ for k, v in iteritems(attrs):
+ parameters = getattr(v, "_parameters", None)
+
+ if parameters is not None:
+ for parameter in parameters:
+ parametrized_name, args = parameter[0], parameter[1:]
+ fn = partial(v, *args)
+
+ names = ["test", k]
+ if parametrized_name:
+ names.append(parametrized_name)
+
+ fn_name = "_".join(names)
+ if not PY3:
+ fn_name = fn_name.encode('utf8')
+ fn.__name__ = fn_name
+ attr[fn.__name__] = fn
+ else:
+ attr[k] = v
+
+ if not PY3:
+ name = name.encode('utf8')
+
+ return super(ParametrizedTestCase, cls).__new__(cls, name, bases, attr)
+
+
+# Inscrutable way to create metaclasses in a Python 2/3 compatible way
+# See: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/
+ParameterizedTestCase = ParametrizedTestCase(
+ 'ParameterizedTestCase', (object,), {}
+)
+
+
+def parametrized(*runs):
+ def parametrized_test(fn):
+ fn._parameters = runs
+ return fn
+ return parametrized_test
+
+
+def partial(fn, *args, **kwargs):
+ """
+ ``functools.partial`` for methods (suitable for binding).
+
+ """
+
+ @wraps(fn)
+ def _partial(self):
+ return fn(self, *args, **kwargs)
+ return _partial
+
+
+def validation_test(schema=(), initkwargs=(), **kwschema):
+ schema = dict(schema, **kwschema)
+ initkwargs = dict(initkwargs)
+
+ def _validation_test(self, expected, instance):
+ if expected == "valid":
+ validate(instance, schema, **initkwargs)
+ elif expected == "invalid":
+ with self.assertRaises(ValidationError):
+ validate(instance, schema, **initkwargs)
+ else: # pragma: no cover
+ raise ValueError("You spelled something wrong.")
+
+ return _validation_test
+
+
+class TestValidate(ParameterizedTestCase, unittest.TestCase):
+ integer = parametrized(
+ ("integer", "valid", 1),
+ ("number", "invalid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type="integer"))
+
+ number = parametrized(
+ ("integer", "valid", 1),
+ ("number", "valid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type="number"))
+
+ _string = [
+ ("integer", "invalid", 1),
+ ("number", "invalid", 1.1),
+ ("unicode", "valid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ ]
+
+ if not PY3:
+ # The JSON module in Python2 does not always produce unicode objects :/
+ _string.append(("bytestring", "valid", b"foo"))
+
+ string = parametrized(*_string)(validation_test(type="string"))
+
+ object = parametrized(
+ ("integer", "invalid", 1),
+ ("number", "invalid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "valid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type="object"))
+
+ array = parametrized(
+ ("integer", "invalid", 1),
+ ("number", "invalid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "valid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type="array"))
+
+ boolean = parametrized(
+ ("integer", "invalid", 1),
+ ("number", "invalid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("true", "valid", True),
+ ("false", "valid", False),
+ ("null", "invalid", None),
+ )(validation_test(type="boolean"))
+
+ null = parametrized(
+ ("integer", "invalid", 1),
+ ("number", "invalid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "valid", None),
+ )(validation_test(type="null"))
+
+ any = parametrized(
+ ("integer", "valid", 1),
+ ("number", "valid", 1.1),
+ ("string", "valid", "foo"),
+ ("object", "valid", {}),
+ ("array", "valid", []),
+ ("boolean", "valid", True),
+ ("null", "valid", None),
+ )(validation_test(type="any"))
+
+ multiple_types = parametrized(
+ ("integer", "valid", 1),
+ ("string", "valid", "foo"),
+ ("number", "invalid", 1.1),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type=["integer", "string"]))
+
+ multiple_types_schema = parametrized(
+ ("match", "valid", [1, 2]),
+ ("other_match", "valid", {"foo" : "bar"}),
+ ("number", "invalid", 1.1),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type=["array", {"type" : "object"}]))
+
+ multiple_types_subschema = parametrized(
+ ("integer", "valid", 1),
+ ("object_right_type", "valid", {"foo" : None}),
+ ("object_wrong_type", "invalid", {"foo" : 1}),
+ ("object_another_wrong_type", "invalid", {"foo" : 1.1}),
+ )(validation_test(
+ type=["integer", {"properties" : {"foo" : {"type" : "null"}}}]
+ ))
+
+ def test_multiple_types_nonobject(self):
+ """
+ Regression test for issue #18.
+
+ """
+ validate(
+ [1, 2, 3],
+ {"type" : [{"type" : ["string"]}, {"type" : ["array", "null"]}]}
+ )
+
+ properties = parametrized(
+ ("", "valid", {"foo" : 1, "bar" : "baz"}),
+ ("extra_property", "valid",
+ {"foo" : 1, "bar" : "baz", "quux" : 42}),
+ ("invalid_type", "invalid", {"foo" : 1, "bar" : []}),
+ )(validation_test(
+ {
+ "properties" : {
+ "foo" : {"type" : "number"},
+ "bar" : {"type" : "string"},
+ }
+ }
+ ))
+
+ patternProperties = parametrized(
+ ("single_match", "valid", {"foo" : 1}),
+ ("multiple_match", "valid", {"foo" : 1, "fah" : 2, "bar" : "baz"}),
+ ("single_mismatch", "invalid", {"foo" : "bar"}),
+ ("multiple_mismatch", "invalid", {"foo" : 1, "fah" : "bar"}),
+ )(validation_test(patternProperties={"f.*" : {"type" : "integer"}}))
+
+ multiple_patternProperties = parametrized(
+ ("match", "valid", {"a" : 21}),
+ ("other_match", "valid", {"aaaa" : 18}),
+ ("multiple_match", "valid", {"a" : 21, "aaaa" : 18}),
+ ("mismatch", "invalid", {"aaa" : "bar"}),
+ ("other_mismatch", "invalid", {"aaaa" : 31}),
+ ("multiple_mismatch", "invalid", {"aaa" : "foo", "aaaa" : 32}),
+ )(validation_test(patternProperties={
+ "a*" : {"type" : "integer"},
+ "aaa*" : {"maximum" : 20},
+ }
+ ))
+
+ def test_additionalProperties_allowed_by_default(self):
+ schema = {
+ "properties" : {
+ "foo" : {"type" : "number"},
+ "bar" : {"type" : "string"},
+ }
+ }
+ validate({"foo" : 1, "bar" : "baz", "quux" : False}, schema)
+
+ @parametrized(
+ ("", False),
+ ("schema", {"type" : "boolean"}),
+ )
+ def additionalProperties(self, aP):
+ schema = {
+ "properties" : {
+ "foo" : {"type" : "number"},
+ "bar" : {"type" : "string"},
+ },
+
+ "additionalProperties" : aP,
+ }
+
+ with self.assertRaises(ValidationError):
+ validate({"foo" : 1, "bar" : "baz", "quux" : "boom"}, schema)
+
+ def test_additionalProperties_ignores_nonobjects(self):
+ validate(None, {"additionalProperties" : False})
+
+ @parametrized(
+ ("single_extra", {"foo" : 2}, ["'foo' was unexpected)"]),
+ ("multiple_extras",
+ dict.fromkeys(["foo", "bar", "quux"]),
+ ["'bar'", "'foo'", "'quux'", "were unexpected)"],
+ ),
+ )
+ def additionalProperties_errorMessage(self, instance, errs):
+ schema = {"additionalProperties" : False}
+
+ with self.assertRaises(ValidationError) as error:
+ validate(instance, schema)
+
+ self.assertTrue(all(err in unicode(error.exception) for err in errs))
+
+ items = parametrized(
+ ("", "valid", [1, 2, 3]),
+ ("wrong_type", "invalid", [1, "x"]),
+ )(validation_test(items={"type" : "integer"}))
+
+ items_tuple_typing = parametrized(
+ ("", "valid", [1, "foo"]),
+ ("wrong_type", "invalid", ["foo", 1])
+ )(validation_test(items=[{"type" : "integer"}, {"type" : "string"}]))
+
+ def test_additionalItems_allowed_by_default(self):
+ validate(
+ [1, "foo", False],
+ {"items" : [{"type" : "integer"}, {"type" : "string"}]}
+ )
+
+ additionalItems = parametrized(
+ ("no_additional", "valid", [1, "foo"]),
+ ("additional", "invalid", [1, "foo", False]),
+ )(validation_test({
+ "items" : [{"type" : "integer"}, {"type" : "string"}],
+ "additionalItems" : False,
+ }))
+
+ additionalItems_schema = parametrized(
+ ("match", "valid", [1, "foo", 3]),
+ ("mismatch", "invalid", [1, "foo", "bar"]),
+ )(validation_test({
+ "items" : [{"type" : "integer"}, {"type" : "string"}],
+ "additionalItems" : {"type" : "integer"},
+ }))
+
+ def test_additionalItems_ignores_nonarrays(self):
+ validate(None, {"additionalItems" : False})
+
+ @parametrized(
+ ("single_extra", [2], "(2 was unexpected)"),
+ ("multiple_extras", [1, 2, 3], "(1, 2, 3 were unexpected)"),
+ )
+ def additionalItems_errorMessage(self, instance, err):
+ schema = {"additionalItems" : False}
+ self.assertRaisesRegexp(
+ ValidationError, err, validate, instance, schema
+ )
+
+ @parametrized(
+ ("false_by_default", "valid", {}, {}),
+ ("false_explicit", "valid", {"required" : False}, {}),
+ ("one", "valid", {"required" : True}, {}),
+ ("other", "invalid", {}, {"required" : True}),
+ ("both", "invalid", {"required" : True}, {"required" : True}),
+ )
+ def required(self, expect, foo, bar):
+ schema = {
+ "properties" : {
+ "foo" : {"type" : "number"},
+ "bar" : {"type" : "string"},
+ }
+ }
+
+ schema["properties"]["foo"].update(foo)
+ schema["properties"]["bar"].update(bar)
+
+ test = validation_test(schema)
+ test(self, expect, {"foo" : 1})
+
+ dependencies = parametrized(
+ ("neither", "valid", {}),
+ ("nondependant", "valid", {"foo" : 1}),
+ ("with_dependency", "valid", {"foo" : 1, "bar" : 2}),
+ ("missing_dependency", "invalid", {"bar" : 2}),
+ )(validation_test(dependencies={"bar": "foo"}))
+
+ multiple_dependencies = parametrized(
+ ("neither", "valid", {}),
+ ("nondependants", "valid", {"foo" : 1, "bar" : 2}),
+ ("with_dependencies", "valid", {"foo" : 1, "bar" : 2, "quux" : 3}),
+ ("missing_dependency", "invalid", {"foo" : 1, "quux" : 2}),
+ ("missing_other_dependency", "invalid", {"bar" : 1, "quux" : 2}),
+ ("missing_both_dependencies", "invalid", {"quux" : 1}),
+ )(validation_test(
+ dependencies={"quux" : ["foo", "bar"]}
+ ))
+
+ multiple_dependencies_subschema = parametrized(
+ ("", "valid", {"foo" : 1, "bar" : 2}),
+ ("wrong_type", "invalid", {"foo" : "quux", "bar" : 2}),
+ ("wrong_type_other", "invalid", {"foo" : 2, "bar" : "quux"}),
+ ("wrong_type_both", "invalid", {"foo" : "quux", "bar" : "quux"}),
+ )(validation_test(dependencies={
+ "bar" : {
+ "properties" : {
+ "foo" : {"type" : "integer"},
+ "bar" : {"type" : "integer"},
+ }}}))
+
+ def test_dependencies_error_message_has_single_element_not_list(self):
+ with self.assertRaises(ValidationError) as e:
+ validate({"bar" : 2}, {"dependencies" : {"bar" : "foo"}})
+ self.assertNotIn("'foo']", e.exception.message)
+ self.assertIn("'foo'", e.exception.message)
+
+ @parametrized(
+ ("", "valid", {}, 2.6),
+ ("fail", "invalid", {}, .6),
+ ("exclusiveMinimum", "valid", {"exclusiveMinimum" : True}, 1.2),
+ ("exclusiveMinimum_fail", "invalid",
+ {"exclusiveMinimum" : True}, 1.1),
+ )
+ def minimum(self, expect, eM, instance):
+ eM["minimum"] = 1.1
+ test = validation_test(eM)
+ test(self, expect, instance)
+
+ @parametrized(
+ ("", "valid", {}, 2.6),
+ ("fail", "invalid", {}, 3.5),
+ ("exclusiveMaximum", "valid", {"exclusiveMaximum" : True}, 2.2),
+ ("exclusiveMaximum_fail", "invalid",
+ {"exclusiveMaximum" : True}, 3.0),
+ )
+ def maximum(self, expect, eM, instance):
+ eM["maximum"] = 3.0
+ test = validation_test(eM)
+ test(self, expect, instance)
+
+ minItems = parametrized(
+ ("exact", "valid", [1]),
+ ("longer", "valid", [1, 2]),
+ ("too_short", "invalid", []),
+ ("ignores_strings", "valid", "a"),
+ )(validation_test(minItems=1))
+
+ maxItems = parametrized(
+ ("exact", "valid", [1, 2]),
+ ("shorter", "valid", [1]),
+ ("empty", "valid", []),
+ ("too_long", "invalid", [1, 2, 3]),
+ ("ignores_strings", "valid", "aaaa"),
+ )(validation_test(maxItems=2))
+
+ uniqueItems = parametrized(
+ ("unique", "valid", [1, 2]),
+ ("not_unique", "invalid", [1, 1]),
+ ("object_unique", "valid", [{"foo" : "bar"}, {"foo" : "baz"}]),
+ ("object_not_unique", "invalid", [{"foo" : "bar"}, {"foo" : "bar"}]),
+ ("array_unique", "valid", [["foo"], ["bar"]]),
+ ("array_not_unique", "invalid", [["foo"], ["foo"]]),
+ ("nested", "valid", [
+ {"foo" : {"bar" : {"baz" : "quux"}}},
+ {"foo" : {"bar" : {"baz" : "spam"}}},
+ ]),
+ ("nested_not_unique", "invalid", [
+ {"foo" : {"bar" : {"baz" : "quux"}}},
+ {"foo" : {"bar" : {"baz" : "quux"}}},
+ ])
+ )(validation_test(uniqueItems=True))
+
+ pattern = parametrized(
+ ("match", "valid", "aaa"),
+ ("mismatch", "invalid", "ab"),
+ ("ignores_other_stuff", "valid", True),
+ )(validation_test(pattern="^a*$"))
+
+ minLength = parametrized(
+ ("", "valid", "foo"),
+ ("too_short", "invalid", "f"),
+ ("ignores_arrays", "valid", [1]),
+ )(validation_test(minLength=2))
+
+ maxLength = parametrized(
+ ("", "valid", "f"),
+ ("too_long", "invalid", "foo"),
+ ("ignores_arrays", "valid", [1, 2, 3]),
+ )(validation_test(maxLength=2))
+
+ @parametrized(
+ ("integer", "valid", 1, [1, 2, 3]),
+ ("integer_fail", "invalid", 6, [1, 2, 3]),
+ ("string", "valid", "foo", ["foo", "bar"]),
+ ("string_fail", "invalid", "quux", ["foo", "bar"]),
+ ("bool", "valid", True, [True]),
+ ("bool_fail", "invalid", False, [True]),
+ ("object", "valid", {"foo" : "bar"}, [{"foo" : "bar"}]),
+ ("object_fail", "invalid", {"foo" : "bar"}, [{"foo" : "quux"}]),
+ )
+ def enum(self, expect, instance, enum):
+ test = validation_test(enum=enum)
+ test(self, expect, instance)
+
+ @parametrized(
+ ("int_by_int", "valid", 10, 2),
+ ("int_by_int_fail", "invalid", 7, 2),
+ ("number_by_number", "valid", 3.3, 1.1),
+ ("number_by_number_fail", "invalid", 3.5, 1.1),
+ ("number_by_number_small", "valid", .0075, .0001),
+ ("number_by_number_small_fail", "invalid", .00751, .0001),
+ ("number_by_number_again", "valid", 1.09, .01),
+ ("number_by_number_again_2", "valid", 1.89, .01),
+ )
+ def divisibleBy(self, expect, instance, dB):
+ test = validation_test(divisibleBy=dB)
+ test(self, expect, instance)
+
+ disallow = parametrized(
+ ("", "valid", "foo"),
+ ("disallowed", "invalid", 1),
+ )(validation_test(disallow="integer"))
+
+ multiple_disallow = parametrized(
+ ("", "valid", "foo"),
+ ("mismatch", "invalid", 1),
+ ("other_mismatch", "invalid", True),
+ )(validation_test(disallow=["integer", "boolean"]))
+
+ multiple_disallow_subschema = parametrized(
+ ("match", "valid", 1),
+ ("other_match", "valid", {"foo" : 1}),
+ ("mismatch", "invalid", "foo"),
+ ("other_mismatch", "invalid", {"foo" : "bar"}),
+ )(validation_test(
+ disallow=[
+ "string",
+ {"type" : "object", "properties" : {"foo" : {"type" : "string"}}},
+ ]
+ ))
+
+ @parametrized(
+ ("", "valid", {"foo" : "baz", "bar" : 2}),
+ ("mismatch_extends", "invalid", {"foo" : "baz"}),
+ ("mismatch_extended", "invalid", {"bar" : 2}),
+ ("wrong_type", "invalid", {"foo" : "baz", "bar" : "quux"}),
+ )
+ def extends(self, expect, instance):
+ schema = {
+ "properties" : {"bar" : {"type" : "integer", "required" : True}},
+ "extends" : {
+ "properties" : {
+ "foo" : {"type" : "string", "required" : True},
+ }
+ },
+ }
+
+ test = validation_test(**schema)
+ test(self, expect, instance)
+
+ @parametrized(
+ ("", "valid", {"foo" : "quux", "bar" : 2, "baz" : None}),
+ ("mismatch_first_extends", "invalid", {"bar" : 2, "baz" : None}),
+ ("mismatch_second_extends", "invalid", {"foo" : "quux", "bar" : 2}),
+ ("mismatch_both", "invalid", {"bar" : 2}),
+ )
+ def multiple_extends(self, expect, instance):
+ schema = {
+ "properties" : {"bar" : {"type" : "integer", "required" : True}},
+ "extends" : [
+ {
+ "properties" : {
+ "foo" : {"type" : "string", "required" : True},
+ }
+ },
+ {
+ "properties" : {
+ "baz" : {"type" : "null", "required" : True},
+ }
+ },
+ ],
+ }
+
+ test = validation_test(**schema)
+ test(self, expect, instance)
+
+ extends_simple_types = parametrized(
+ ("", "valid", 25),
+ ("mismatch_extends", "invalid", 35)
+ )(validation_test(minimum=20, extends={"maximum" : 30}))
+
+ def test_iter_errors(self):
+ instance = [1, 2]
+ schema = {
+ "disallow" : "array",
+ "enum" : [["a", "b", "c"], ["d", "e", "f"]],
+ "minItems" : 3
+ }
+
+ if PY3:
+ errors = sorted([
+ "'array' is disallowed for [1, 2]",
+ "[1, 2] is too short",
+ "[1, 2] is not one of [['a', 'b', 'c'], ['d', 'e', 'f']]",
+ ])
+ else:
+ errors = sorted([
+ "u'array' is disallowed for [1, 2]",
+ "[1, 2] is too short",
+ "[1, 2] is not one of [[u'a', u'b', u'c'], [u'd', u'e', u'f']]",
+ ])
+
+ self.assertEqual(
+ sorted(str(e) for e in Validator().iter_errors(instance, schema)),
+ errors,
+ )
+
+ def test_unknown_type_error(self):
+ with self.assertRaises(SchemaError):
+ validate(1, {"type" : "foo"}, unknown_type="error")
+
+ def test_unknown_type_warn(self):
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ validate(1, {"type" : "foo"}, unknown_type="warn")
+ self.assertEqual(len(w), 1)
+
+ def test_unknown_type_skip(self):
+ validate(1, {"type" : "foo"}, unknown_type="skip")
+
+ def test_unknown_property_error(self):
+ with self.assertRaises(SchemaError):
+ validate(1, {"foo" : "bar"}, unknown_property="error")
+
+ def test_unknown_property_warn(self):
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ validate(1, {"foo" : "bar"}, unknown_property="warn")
+ self.assertEqual(len(w), 1)
+
+ def test_unknown_property_skip(self):
+ validate(
+ 1,
+ {"foo" : "foo", "type" : "integer"},
+ unknown_property="skip"
+ )
+
+ decimal = parametrized(
+ ("integer", "valid", 1),
+ ("number", "valid", 1.1),
+ ("decimal", "valid", Decimal(1) / Decimal(8)),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(
+ initkwargs={"types" : {"number" : (int, float, Decimal)}},
+ type="number")
+ )
+
+ # TODO: we're in need of more meta schema tests
+ def test_invalid_properties(self):
+ with self.assertRaises(SchemaError):
+ validate({}, {"properties": {"test": True}})
+
+ def test_minItems_invalid_string(self):
+ with self.assertRaises(SchemaError):
+ validate([1], {"minItems" : "1"}) # needs to be an integer
+
+ def test_iter_errors_multiple_failures_one_validator(self):
+ instance = {"foo" : 2, "bar" : [1], "baz" : 15, "quux" : "spam"}
+ schema = {
+ "properties" : {
+ "foo" : {"type" : "string"},
+ "bar" : {"minItems" : 2},
+ "baz" : {"maximum" : 10, "enum" : [2, 4, 6, 8]},
+ }
+ }
+
+ errors = list(Validator().iter_errors(instance, schema))
+ self.assertEqual(len(errors), 4)
+
+
+class TestValidationErrorDetails(unittest.TestCase):
+ # TODO: These really need unit tests for each individual validator, rather
+ # than just these higher level tests.
+ def test_single_nesting(self):
+ instance = {"foo" : 2, "bar" : [1], "baz" : 15, "quux" : "spam"}
+ schema = {
+ "properties" : {
+ "foo" : {"type" : "string"},
+ "bar" : {"minItems" : 2},
+ "baz" : {"maximum" : 10, "enum" : [2, 4, 6, 8]},
+ }
+ }
+
+ errors = Validator().iter_errors(instance, schema)
+ e1, e2, e3, e4 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, ["bar"])
+ self.assertEqual(e2.path, ["baz"])
+ self.assertEqual(e3.path, ["baz"])
+ self.assertEqual(e4.path, ["foo"])
+
+ self.assertEqual(e1.validator, "minItems")
+ self.assertEqual(e2.validator, "enum")
+ self.assertEqual(e3.validator, "maximum")
+ self.assertEqual(e4.validator, "type")
+
+ def test_multiple_nesting(self):
+ instance = [1, {"foo" : 2, "bar" : {"baz" : [1]}}, "quux"]
+ schema = {
+ "type" : "string",
+ "items" : {
+ "type" : ["string", "object"],
+ "properties" : {
+ "foo" : {"enum" : [1, 3]},
+ "bar" : {
+ "type" : "array",
+ "properties" : {
+ "bar" : {"required" : True},
+ "baz" : {"minItems" : 2},
+ }
+ }
+ }
+ }
+ }
+
+ errors = Validator().iter_errors(instance, schema)
+ e1, e2, e3, e4, e5, e6 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, [])
+ self.assertEqual(e2.path, [0])
+ self.assertEqual(e3.path, ["bar", 1])
+ self.assertEqual(e4.path, ["bar", "bar", 1])
+ self.assertEqual(e5.path, ["baz", "bar", 1])
+ self.assertEqual(e6.path, ["foo", 1])
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "type")
+ self.assertEqual(e3.validator, "type")
+ self.assertEqual(e4.validator, "required")
+ self.assertEqual(e5.validator, "minItems")
+ self.assertEqual(e6.validator, "enum")
+
+
+class TestErrorTree(unittest.TestCase):
+ def test_tree(self):
+ instance = [1, {"foo" : 2, "bar" : {"baz" : [1]}}, "quux"]
+ schema = {
+ "type" : "string",
+ "items" : {
+ "type" : ["string", "object"],
+ "properties" : {
+ "foo" : {"enum" : [1, 3]},
+ "bar" : {
+ "type" : "array",
+ "properties" : {
+ "bar" : {"required" : True},
+ "baz" : {"minItems" : 2},
+ }
+ }
+ }
+ }
+ }
+
+ errors = sorted_errors(Validator().iter_errors(instance, schema))
+ e1, e2, e3, e4, e5, e6 = errors
+ tree = ErrorTree(errors)
+
+ self.assertEqual(len(tree), 6)
+
+ self.assertIn(0, tree)
+ self.assertIn(1, tree)
+ self.assertIn("bar", tree[1])
+ self.assertIn("foo", tree[1])
+ self.assertIn("baz", tree[1]["bar"])
+
+ self.assertEqual(tree.errors["type"], e1)
+ self.assertEqual(tree[0].errors["type"], e2)
+ self.assertEqual(tree[1]["bar"].errors["type"], e3)
+ self.assertEqual(tree[1]["bar"]["bar"].errors["required"], e4)
+ self.assertEqual(tree[1]["bar"]["baz"].errors["minItems"], e5)
+ self.assertEqual(tree[1]["foo"].errors["enum"], e6)
+
+
+class TestIgnorePropertiesForIrrelevantTypes(unittest.TestCase):
+ def test_minimum(self):
+ validate("x", {"type": ["string", "number"], "minimum": 10})
+
+ def test_maximum(self):
+ validate("x", {"type": ["string", "number"], "maximum": 10})
+
+ def test_properties(self):
+ validate(1, {"type": ["integer", "object"], "properties": {"x": {}}})
+
+ def test_items(self):
+ validate(
+ 1, {"type": ["integer", "array"], "items": {"type": "string"}}
+ )
+
+ def test_divisibleBy(self):
+ validate("x", {"type": ["integer", "string"], "divisibleBy": 10})
+
+
+def sorted_errors(errors):
+ return sorted(errors, key=lambda e : [str(err) for err in e.path])