summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Berman <Julian@GrayVines.com>2013-12-25 13:27:46 -0500
committerJulian Berman <Julian@GrayVines.com>2013-12-25 13:27:46 -0500
commit44b35706a7476fb42ac47520860b63aeebfe39e5 (patch)
treed500f356fefdeaaf7b4b6fd279578bc0c8ac19b3
parent8eae430d66d7032e76c57d36b69af79935e28ec1 (diff)
downloadjsonschema-44b35706a7476fb42ac47520860b63aeebfe39e5.tar.gz
Add absolute path and absolute schema path.
Closes #120
-rw-r--r--docs/errors.rst37
-rw-r--r--jsonschema/exceptions.py61
-rw-r--r--jsonschema/tests/test_validators.py91
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")