Skip to content

Commit 44b3570

Browse files
committed
Add absolute path and absolute schema path.
Closes #120
1 parent 8eae430 commit 44b3570

File tree

3 files changed

+154
-35
lines changed

3 files changed

+154
-35
lines changed

docs/errors.rst

+33-4
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,40 @@ raised or returned, depending on which method or function is used.
4949
subschema from within the schema that was passed into the validator, or
5050
even an entirely different schema if a :validator:`$ref` was followed.
5151

52-
.. attribute:: schema_path
52+
.. attribute:: relative_schema_path
5353

5454
A :class:`collections.deque` containing the path to the failed
5555
validator within the schema.
5656

57+
.. attribute:: absolute_schema_path
58+
59+
A :class:`collections.deque` containing the path to the failed
60+
validator within the schema, but always relative to the
61+
*original* schema as opposed to any subschema (i.e. the one
62+
originally passed into a validator, *not* :attr:`schema`\).
63+
64+
.. attribute:: schema_path
65+
66+
Same as :attr:`relative_schema_path`.
67+
68+
.. attribute:: relative_path
69+
70+
A :class:`collections.deque` containing the path to the
71+
offending element within the instance. The deque can be empty if
72+
the error happened at the root of the instance.
73+
74+
.. attribute:: absolute_path
75+
76+
A :class:`collections.deque` containing the path to the
77+
offending element within the instance. The absolute path
78+
is always relative to the *original* instance that was
79+
validated (i.e. the one passed into a validation method, *not*
80+
:attr:`instance`\). The deque can be empty if the error happened
81+
at the root of the instance.
82+
5783
.. attribute:: path
5884

59-
A :class:`collections.deque` containing the path to the offending
60-
element within the instance. The deque can be empty if the error
61-
happened at the root of the instance.
85+
Same as :attr:`relative_path`.
6286

6387
.. attribute:: instance
6488

@@ -82,6 +106,11 @@ raised or returned, depending on which method or function is used.
82106
object will be here. Currently this is only used for the exception
83107
raised by a failed format checker in :meth:`FormatChecker.check`.
84108

109+
.. attribute:: parent
110+
111+
A validation error which this error is the :attr:`context` of.
112+
``None`` if there wasn't one.
113+
85114

86115
In case an invalid schema itself is encountered, a :exc:`SchemaError` is
87116
raised.

jsonschema/exceptions.py

+45-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import collections
1+
from collections import defaultdict, deque
22
import itertools
33
import pprint
44
import textwrap
@@ -15,18 +15,31 @@
1515

1616
class _Error(Exception):
1717
def __init__(
18-
self, message, validator=_unset, path=(), cause=None, context=(),
19-
validator_value=_unset, instance=_unset, schema=_unset, schema_path=(),
18+
self,
19+
message,
20+
validator=_unset,
21+
path=(),
22+
cause=None,
23+
context=(),
24+
validator_value=_unset,
25+
instance=_unset,
26+
schema=_unset,
27+
schema_path=(),
28+
parent=None,
2029
):
2130
self.message = message
22-
self.path = collections.deque(path)
23-
self.schema_path = collections.deque(schema_path)
31+
self.path = self.relative_path = deque(path)
32+
self.schema_path = self.relative_schema_path = deque(schema_path)
2433
self.context = list(context)
2534
self.cause = self.__cause__ = cause
2635
self.validator = validator
2736
self.validator_value = validator_value
2837
self.instance = instance
2938
self.schema = schema
39+
self.parent = parent
40+
41+
for error in context:
42+
error.parent = self
3043

3144
def __repr__(self):
3245
return "<%s: %r>" % (self.__class__.__name__, self.message)
@@ -40,9 +53,6 @@ def __unicode__(self):
4053
):
4154
return self.message
4255

43-
path = _utils.format_as_index(self.path)
44-
schema_path = _utils.format_as_index(list(self.schema_path)[:-1])
45-
4656
pschema = pprint.pformat(self.schema, width=72)
4757
pinstance = pprint.pformat(self.instance, width=72)
4858
return self.message + textwrap.dedent("""
@@ -55,9 +65,9 @@ def __unicode__(self):
5565
""".rstrip()
5666
) % (
5767
self.validator,
58-
schema_path,
68+
_utils.format_as_index(list(self.relative_schema_path)[:-1]),
5969
_utils.indent(pschema),
60-
path,
70+
_utils.format_as_index(self.relative_path),
6171
_utils.indent(pinstance),
6272
)
6373

@@ -68,18 +78,37 @@ def __unicode__(self):
6878
def create_from(cls, other):
6979
return cls(**other._contents())
7080

81+
@property
82+
def absolute_path(self):
83+
parent = self.parent
84+
if parent is None:
85+
return self.relative_path
86+
87+
path = deque(self.relative_path)
88+
path.extendleft(parent.absolute_path)
89+
return path
90+
91+
@property
92+
def absolute_schema_path(self):
93+
parent = self.parent
94+
if parent is None:
95+
return self.relative_schema_path
96+
97+
path = deque(self.relative_schema_path)
98+
path.extendleft(parent.absolute_schema_path)
99+
return path
100+
71101
def _set(self, **kwargs):
72102
for k, v in iteritems(kwargs):
73103
if getattr(self, k) is _unset:
74104
setattr(self, k, v)
75105

76106
def _contents(self):
77-
return dict(
78-
(attr, getattr(self, attr)) for attr in (
79-
"message", "cause", "context", "path", "schema_path",
80-
"validator", "validator_value", "instance", "schema"
81-
)
107+
attrs = (
108+
"message", "cause", "context", "validator", "validator_value",
109+
"path", "schema_path", "instance", "schema", "parent",
82110
)
111+
return dict((attr, getattr(self, attr)) for attr in attrs)
83112

84113

85114
class ValidationError(_Error):
@@ -146,7 +175,7 @@ class ErrorTree(object):
146175

147176
def __init__(self, errors=()):
148177
self.errors = {}
149-
self._contents = collections.defaultdict(self.__class__)
178+
self._contents = defaultdict(self.__class__)
150179

151180
for error in errors:
152181
container = self

jsonschema/tests/test_validators.py

+76-15
Original file line numberDiff line numberDiff line change
@@ -294,29 +294,55 @@ def test_anyOf(self):
294294
e = errors[0]
295295

296296
self.assertEqual(e.validator, "anyOf")
297-
self.assertEqual(e.schema_path, deque(["anyOf"]))
298297
self.assertEqual(e.validator_value, schema["anyOf"])
299298
self.assertEqual(e.instance, instance)
300299
self.assertEqual(e.schema, schema)
300+
self.assertIsNone(e.parent)
301+
301302
self.assertEqual(e.path, deque([]))
303+
self.assertEqual(e.relative_path, deque([]))
304+
self.assertEqual(e.absolute_path, deque([]))
305+
306+
self.assertEqual(e.schema_path, deque(["anyOf"]))
307+
self.assertEqual(e.relative_schema_path, deque(["anyOf"]))
308+
self.assertEqual(e.absolute_schema_path, deque(["anyOf"]))
309+
302310
self.assertEqual(len(e.context), 2)
303311

304312
e1, e2 = sorted_errors(e.context)
305313

306314
self.assertEqual(e1.validator, "minimum")
307-
self.assertEqual(e1.schema_path, deque([0, "minimum"]))
308315
self.assertEqual(e1.validator_value, schema["anyOf"][0]["minimum"])
309316
self.assertEqual(e1.instance, instance)
310317
self.assertEqual(e1.schema, schema["anyOf"][0])
318+
self.assertIs(e1.parent, e)
319+
311320
self.assertEqual(e1.path, deque([]))
312-
self.assertEqual(len(e1.context), 0)
321+
self.assertEqual(e1.absolute_path, deque([]))
322+
self.assertEqual(e1.relative_path, deque([]))
323+
324+
self.assertEqual(e1.schema_path, deque([0, "minimum"]))
325+
self.assertEqual(e1.relative_schema_path, deque([0, "minimum"]))
326+
self.assertEqual(
327+
e1.absolute_schema_path, deque(["anyOf", 0, "minimum"]),
328+
)
329+
330+
self.assertFalse(e1.context)
313331

314332
self.assertEqual(e2.validator, "type")
315-
self.assertEqual(e2.schema_path, deque([1, "type"]))
316333
self.assertEqual(e2.validator_value, schema["anyOf"][1]["type"])
317334
self.assertEqual(e2.instance, instance)
318335
self.assertEqual(e2.schema, schema["anyOf"][1])
336+
self.assertIs(e2.parent, e)
337+
319338
self.assertEqual(e2.path, deque([]))
339+
self.assertEqual(e2.relative_path, deque([]))
340+
self.assertEqual(e2.absolute_path, deque([]))
341+
342+
self.assertEqual(e2.schema_path, deque([1, "type"]))
343+
self.assertEqual(e2.relative_schema_path, deque([1, "type"]))
344+
self.assertEqual(e2.absolute_schema_path, deque(["anyOf", 1, "type"]))
345+
320346
self.assertEqual(len(e2.context), 0)
321347

322348
def test_type(self):
@@ -339,36 +365,61 @@ def test_type(self):
339365
e = errors[0]
340366

341367
self.assertEqual(e.validator, "type")
342-
self.assertEqual(e.schema_path, deque(["type"]))
343368
self.assertEqual(e.validator_value, schema["type"])
344369
self.assertEqual(e.instance, instance)
345370
self.assertEqual(e.schema, schema)
371+
self.assertIsNone(e.parent)
372+
346373
self.assertEqual(e.path, deque([]))
374+
self.assertEqual(e.relative_path, deque([]))
375+
self.assertEqual(e.absolute_path, deque([]))
376+
377+
self.assertEqual(e.schema_path, deque(["type"]))
378+
self.assertEqual(e.relative_schema_path, deque(["type"]))
379+
self.assertEqual(e.absolute_schema_path, deque(["type"]))
380+
347381
self.assertEqual(len(e.context), 2)
348382

349383
e1, e2 = sorted_errors(e.context)
350384

351385
self.assertEqual(e1.validator, "type")
352-
self.assertEqual(e1.schema_path, deque([0, "type"]))
353386
self.assertEqual(e1.validator_value, schema["type"][0]["type"])
354387
self.assertEqual(e1.instance, instance)
355388
self.assertEqual(e1.schema, schema["type"][0])
389+
self.assertIs(e1.parent, e)
390+
356391
self.assertEqual(e1.path, deque([]))
357-
self.assertEqual(len(e1.context), 0)
392+
self.assertEqual(e1.relative_path, deque([]))
393+
self.assertEqual(e1.absolute_path, deque([]))
394+
395+
self.assertEqual(e1.schema_path, deque([0, "type"]))
396+
self.assertEqual(e1.relative_schema_path, deque([0, "type"]))
397+
self.assertEqual(e1.absolute_schema_path, deque(["type", 0, "type"]))
398+
399+
self.assertFalse(e1.context)
358400

359401
self.assertEqual(e2.validator, "enum")
402+
self.assertEqual(e2.validator_value, [2])
403+
self.assertEqual(e2.instance, 1)
404+
self.assertEqual(e2.schema, {u"enum" : [2]})
405+
self.assertIs(e2.parent, e)
406+
407+
self.assertEqual(e2.path, deque(["foo"]))
408+
self.assertEqual(e2.relative_path, deque(["foo"]))
409+
self.assertEqual(e2.absolute_path, deque(["foo"]))
410+
360411
self.assertEqual(
361-
list(e2.schema_path),
362-
[1, "properties", "foo", "enum"]
412+
e2.schema_path, deque([1, "properties", "foo", "enum"]),
363413
)
364414
self.assertEqual(
365-
e2.validator_value,
366-
schema["type"][1]["properties"]["foo"]["enum"]
415+
e2.relative_schema_path, deque([1, "properties", "foo", "enum"]),
367416
)
368-
self.assertEqual(e2.instance, instance["foo"])
369-
self.assertEqual(e2.schema, schema["type"][1]["properties"]["foo"])
370-
self.assertEqual(e2.path, deque(["foo"]))
371-
self.assertEqual(len(e2.context), 0)
417+
self.assertEqual(
418+
e2.absolute_schema_path,
419+
deque(["type", 1, "properties", "foo", "enum"]),
420+
)
421+
422+
self.assertFalse(e2.context)
372423

373424
def test_single_nesting(self):
374425
instance = {"foo" : 2, "bar" : [1], "baz" : 15, "quux" : "spam"}
@@ -389,6 +440,16 @@ def test_single_nesting(self):
389440
self.assertEqual(e3.path, deque(["baz"]))
390441
self.assertEqual(e4.path, deque(["foo"]))
391442

443+
self.assertEqual(e1.relative_path, deque(["bar"]))
444+
self.assertEqual(e2.relative_path, deque(["baz"]))
445+
self.assertEqual(e3.relative_path, deque(["baz"]))
446+
self.assertEqual(e4.relative_path, deque(["foo"]))
447+
448+
self.assertEqual(e1.absolute_path, deque(["bar"]))
449+
self.assertEqual(e2.absolute_path, deque(["baz"]))
450+
self.assertEqual(e3.absolute_path, deque(["baz"]))
451+
self.assertEqual(e4.absolute_path, deque(["foo"]))
452+
392453
self.assertEqual(e1.validator, "minItems")
393454
self.assertEqual(e2.validator, "enum")
394455
self.assertEqual(e3.validator, "maximum")

0 commit comments

Comments
 (0)