diff options
author | Julian Berman <Julian@GrayVines.com> | 2013-12-25 13:27:46 -0500 |
---|---|---|
committer | Julian Berman <Julian@GrayVines.com> | 2013-12-25 13:27:46 -0500 |
commit | 44b35706a7476fb42ac47520860b63aeebfe39e5 (patch) | |
tree | d500f356fefdeaaf7b4b6fd279578bc0c8ac19b3 | |
parent | 8eae430d66d7032e76c57d36b69af79935e28ec1 (diff) | |
download | jsonschema-44b35706a7476fb42ac47520860b63aeebfe39e5.tar.gz |
Add absolute path and absolute schema path.
Closes #120
-rw-r--r-- | docs/errors.rst | 37 | ||||
-rw-r--r-- | jsonschema/exceptions.py | 61 | ||||
-rw-r--r-- | jsonschema/tests/test_validators.py | 91 |
3 files changed, 154 insertions, 35 deletions
diff --git a/docs/errors.rst b/docs/errors.rst index d49954f..1956c35 100644 --- a/docs/errors.rst +++ b/docs/errors.rst @@ -49,16 +49,40 @@ raised or returned, depending on which method or function is used. subschema from within the schema that was passed into the validator, or even an entirely different schema if a :validator:`$ref` was followed. - .. attribute:: schema_path + .. attribute:: relative_schema_path A :class:`collections.deque` containing the path to the failed validator within the schema. + .. attribute:: absolute_schema_path + + A :class:`collections.deque` containing the path to the failed + validator within the schema, but always relative to the + *original* schema as opposed to any subschema (i.e. the one + originally passed into a validator, *not* :attr:`schema`\). + + .. attribute:: schema_path + + Same as :attr:`relative_schema_path`. + + .. attribute:: relative_path + + A :class:`collections.deque` containing the path to the + offending element within the instance. The deque can be empty if + the error happened at the root of the instance. + + .. attribute:: absolute_path + + A :class:`collections.deque` containing the path to the + offending element within the instance. The absolute path + is always relative to the *original* instance that was + validated (i.e. the one passed into a validation method, *not* + :attr:`instance`\). The deque can be empty if the error happened + at the root of the instance. + .. attribute:: path - A :class:`collections.deque` containing the path to the offending - element within the instance. The deque can be empty if the error - happened at the root of the instance. + Same as :attr:`relative_path`. .. attribute:: instance @@ -82,6 +106,11 @@ raised or returned, depending on which method or function is used. object will be here. Currently this is only used for the exception raised by a failed format checker in :meth:`FormatChecker.check`. + .. attribute:: parent + + A validation error which this error is the :attr:`context` of. + ``None`` if there wasn't one. + In case an invalid schema itself is encountered, a :exc:`SchemaError` is raised. diff --git a/jsonschema/exceptions.py b/jsonschema/exceptions.py index 5979eff..ec9f521 100644 --- a/jsonschema/exceptions.py +++ b/jsonschema/exceptions.py @@ -1,4 +1,4 @@ -import collections +from collections import defaultdict, deque import itertools import pprint import textwrap @@ -15,18 +15,31 @@ _unset = _utils.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, + validator=_unset, + path=(), + cause=None, + context=(), + validator_value=_unset, + instance=_unset, + schema=_unset, + schema_path=(), + parent=None, ): self.message = message - self.path = collections.deque(path) - self.schema_path = collections.deque(schema_path) + self.path = self.relative_path = deque(path) + self.schema_path = self.relative_schema_path = 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 + self.parent = parent + + for error in context: + error.parent = self def __repr__(self): return "<%s: %r>" % (self.__class__.__name__, self.message) @@ -40,9 +53,6 @@ class _Error(Exception): ): 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(""" @@ -55,9 +65,9 @@ class _Error(Exception): """.rstrip() ) % ( self.validator, - schema_path, + _utils.format_as_index(list(self.relative_schema_path)[:-1]), _utils.indent(pschema), - path, + _utils.format_as_index(self.relative_path), _utils.indent(pinstance), ) @@ -68,18 +78,37 @@ class _Error(Exception): def create_from(cls, other): return cls(**other._contents()) + @property + def absolute_path(self): + parent = self.parent + if parent is None: + return self.relative_path + + path = deque(self.relative_path) + path.extendleft(parent.absolute_path) + return path + + @property + def absolute_schema_path(self): + parent = self.parent + if parent is None: + return self.relative_schema_path + + path = deque(self.relative_schema_path) + path.extendleft(parent.absolute_schema_path) + return path + def _set(self, **kwargs): for k, v in iteritems(kwargs): if getattr(self, k) is _unset: setattr(self, k, v) def _contents(self): - return dict( - (attr, getattr(self, attr)) for attr in ( - "message", "cause", "context", "path", "schema_path", - "validator", "validator_value", "instance", "schema" - ) + attrs = ( + "message", "cause", "context", "validator", "validator_value", + "path", "schema_path", "instance", "schema", "parent", ) + return dict((attr, getattr(self, attr)) for attr in attrs) class ValidationError(_Error): @@ -146,7 +175,7 @@ class ErrorTree(object): def __init__(self, errors=()): self.errors = {} - self._contents = collections.defaultdict(self.__class__) + self._contents = defaultdict(self.__class__) for error in errors: container = self diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py index 80db3a7..f88ef51 100644 --- a/jsonschema/tests/test_validators.py +++ b/jsonschema/tests/test_validators.py @@ -294,29 +294,55 @@ class TestValidationErrorDetails(unittest.TestCase): e = errors[0] self.assertEqual(e.validator, "anyOf") - self.assertEqual(e.schema_path, deque(["anyOf"])) self.assertEqual(e.validator_value, schema["anyOf"]) self.assertEqual(e.instance, instance) self.assertEqual(e.schema, schema) + self.assertIsNone(e.parent) + self.assertEqual(e.path, deque([])) + self.assertEqual(e.relative_path, deque([])) + self.assertEqual(e.absolute_path, deque([])) + + self.assertEqual(e.schema_path, deque(["anyOf"])) + self.assertEqual(e.relative_schema_path, deque(["anyOf"])) + self.assertEqual(e.absolute_schema_path, deque(["anyOf"])) + self.assertEqual(len(e.context), 2) e1, e2 = sorted_errors(e.context) self.assertEqual(e1.validator, "minimum") - self.assertEqual(e1.schema_path, deque([0, "minimum"])) self.assertEqual(e1.validator_value, schema["anyOf"][0]["minimum"]) self.assertEqual(e1.instance, instance) self.assertEqual(e1.schema, schema["anyOf"][0]) + self.assertIs(e1.parent, e) + self.assertEqual(e1.path, deque([])) - self.assertEqual(len(e1.context), 0) + self.assertEqual(e1.absolute_path, deque([])) + self.assertEqual(e1.relative_path, deque([])) + + self.assertEqual(e1.schema_path, deque([0, "minimum"])) + self.assertEqual(e1.relative_schema_path, deque([0, "minimum"])) + self.assertEqual( + e1.absolute_schema_path, deque(["anyOf", 0, "minimum"]), + ) + + self.assertFalse(e1.context) self.assertEqual(e2.validator, "type") - self.assertEqual(e2.schema_path, deque([1, "type"])) self.assertEqual(e2.validator_value, schema["anyOf"][1]["type"]) self.assertEqual(e2.instance, instance) self.assertEqual(e2.schema, schema["anyOf"][1]) + self.assertIs(e2.parent, e) + self.assertEqual(e2.path, deque([])) + self.assertEqual(e2.relative_path, deque([])) + self.assertEqual(e2.absolute_path, deque([])) + + self.assertEqual(e2.schema_path, deque([1, "type"])) + self.assertEqual(e2.relative_schema_path, deque([1, "type"])) + self.assertEqual(e2.absolute_schema_path, deque(["anyOf", 1, "type"])) + self.assertEqual(len(e2.context), 0) def test_type(self): @@ -339,36 +365,61 @@ class TestValidationErrorDetails(unittest.TestCase): e = errors[0] self.assertEqual(e.validator, "type") - self.assertEqual(e.schema_path, deque(["type"])) self.assertEqual(e.validator_value, schema["type"]) self.assertEqual(e.instance, instance) self.assertEqual(e.schema, schema) + self.assertIsNone(e.parent) + self.assertEqual(e.path, deque([])) + self.assertEqual(e.relative_path, deque([])) + self.assertEqual(e.absolute_path, deque([])) + + self.assertEqual(e.schema_path, deque(["type"])) + self.assertEqual(e.relative_schema_path, deque(["type"])) + self.assertEqual(e.absolute_schema_path, deque(["type"])) + self.assertEqual(len(e.context), 2) e1, e2 = sorted_errors(e.context) self.assertEqual(e1.validator, "type") - self.assertEqual(e1.schema_path, deque([0, "type"])) self.assertEqual(e1.validator_value, schema["type"][0]["type"]) self.assertEqual(e1.instance, instance) self.assertEqual(e1.schema, schema["type"][0]) + self.assertIs(e1.parent, e) + self.assertEqual(e1.path, deque([])) - self.assertEqual(len(e1.context), 0) + self.assertEqual(e1.relative_path, deque([])) + self.assertEqual(e1.absolute_path, deque([])) + + self.assertEqual(e1.schema_path, deque([0, "type"])) + self.assertEqual(e1.relative_schema_path, deque([0, "type"])) + self.assertEqual(e1.absolute_schema_path, deque(["type", 0, "type"])) + + self.assertFalse(e1.context) self.assertEqual(e2.validator, "enum") + self.assertEqual(e2.validator_value, [2]) + self.assertEqual(e2.instance, 1) + self.assertEqual(e2.schema, {u"enum" : [2]}) + self.assertIs(e2.parent, e) + + self.assertEqual(e2.path, deque(["foo"])) + self.assertEqual(e2.relative_path, deque(["foo"])) + self.assertEqual(e2.absolute_path, deque(["foo"])) + self.assertEqual( - list(e2.schema_path), - [1, "properties", "foo", "enum"] + e2.schema_path, deque([1, "properties", "foo", "enum"]), ) self.assertEqual( - e2.validator_value, - schema["type"][1]["properties"]["foo"]["enum"] + e2.relative_schema_path, deque([1, "properties", "foo", "enum"]), ) - self.assertEqual(e2.instance, instance["foo"]) - self.assertEqual(e2.schema, schema["type"][1]["properties"]["foo"]) - self.assertEqual(e2.path, deque(["foo"])) - self.assertEqual(len(e2.context), 0) + self.assertEqual( + e2.absolute_schema_path, + deque(["type", 1, "properties", "foo", "enum"]), + ) + + self.assertFalse(e2.context) def test_single_nesting(self): instance = {"foo" : 2, "bar" : [1], "baz" : 15, "quux" : "spam"} @@ -389,6 +440,16 @@ class TestValidationErrorDetails(unittest.TestCase): self.assertEqual(e3.path, deque(["baz"])) self.assertEqual(e4.path, deque(["foo"])) + self.assertEqual(e1.relative_path, deque(["bar"])) + self.assertEqual(e2.relative_path, deque(["baz"])) + self.assertEqual(e3.relative_path, deque(["baz"])) + self.assertEqual(e4.relative_path, deque(["foo"])) + + self.assertEqual(e1.absolute_path, deque(["bar"])) + self.assertEqual(e2.absolute_path, deque(["baz"])) + self.assertEqual(e3.absolute_path, deque(["baz"])) + self.assertEqual(e4.absolute_path, deque(["foo"])) + self.assertEqual(e1.validator, "minItems") self.assertEqual(e2.validator, "enum") self.assertEqual(e3.validator, "maximum") |