Skip to content

Commit 6995283

Browse files
committed
Move ErrorTree closer to its friends.
1 parent bab6e74 commit 6995283

5 files changed

Lines changed: 142 additions & 143 deletions

File tree

jsonschema/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
"""
1111

1212
from jsonschema.exceptions import (
13-
FormatError, RefResolutionError, SchemaError, ValidationError
13+
ErrorTree, FormatError, RefResolutionError, SchemaError, ValidationError
1414
)
1515
from jsonschema._format import (
1616
FormatChecker, draft3_format_checker, draft4_format_checker,
1717
)
1818
from jsonschema.validators import (
19-
ErrorTree, Draft3Validator, Draft4Validator, RefResolver, validate
19+
Draft3Validator, Draft4Validator, RefResolver, validate
2020
)
2121

2222

jsonschema/exceptions.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,82 @@ def __unicode__(self):
136136
__str__ = __unicode__
137137

138138

139+
class ErrorTree(object):
140+
"""
141+
ErrorTrees make it easier to check which validations failed.
142+
143+
"""
144+
145+
_instance = _unset
146+
147+
def __init__(self, errors=()):
148+
self.errors = {}
149+
self._contents = collections.defaultdict(self.__class__)
150+
151+
for error in errors:
152+
container = self
153+
for element in error.path:
154+
container = container[element]
155+
container.errors[error.validator] = error
156+
157+
self._instance = error.instance
158+
159+
def __contains__(self, index):
160+
"""
161+
Check whether ``instance[index]`` has any errors.
162+
163+
"""
164+
165+
return index in self._contents
166+
167+
def __getitem__(self, index):
168+
"""
169+
Retrieve the child tree one level down at the given ``index``.
170+
171+
If the index is not in the instance that this tree corresponds to and
172+
is not known by this tree, whatever error would be raised by
173+
``instance.__getitem__`` will be propagated (usually this is some
174+
subclass of :class:`LookupError`.
175+
176+
"""
177+
178+
if self._instance is not _unset and index not in self:
179+
self._instance[index]
180+
return self._contents[index]
181+
182+
def __setitem__(self, index, value):
183+
self._contents[index] = value
184+
185+
def __iter__(self):
186+
"""
187+
Iterate (non-recursively) over the indices in the instance with errors.
188+
189+
"""
190+
191+
return iter(self._contents)
192+
193+
def __len__(self):
194+
"""
195+
Same as :attr:`total_errors`.
196+
197+
"""
198+
199+
return self.total_errors
200+
201+
def __repr__(self):
202+
return "<%s (%s total errors)>" % (self.__class__.__name__, len(self))
203+
204+
@property
205+
def total_errors(self):
206+
"""
207+
The total number of errors in the entire tree, including children.
208+
209+
"""
210+
211+
child_errors = sum(len(tree) for _, tree in iteritems(self._contents))
212+
return len(self.errors) + child_errors
213+
214+
139215
def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES):
140216
def relevance(error):
141217
validator = error.validator

jsonschema/tests/test_exceptions.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import mock
2+
13
from jsonschema import Draft4Validator, exceptions
24
from jsonschema.tests.compat import unittest
35

@@ -208,3 +210,63 @@ def test_strong_validators_are_higher_priority(self):
208210

209211
match = max([strong, normal, weak], key=best_match)
210212
self.assertIs(match, strong)
213+
214+
215+
class TestErrorTree(unittest.TestCase):
216+
def test_it_knows_how_many_total_errors_it_contains(self):
217+
errors = [mock.MagicMock() for _ in range(8)]
218+
tree = exceptions.ErrorTree(errors)
219+
self.assertEqual(tree.total_errors, 8)
220+
221+
def test_it_contains_an_item_if_the_item_had_an_error(self):
222+
errors = [exceptions.ValidationError("a message", path=["bar"])]
223+
tree = exceptions.ErrorTree(errors)
224+
self.assertIn("bar", tree)
225+
226+
def test_it_does_not_contain_an_item_if_the_item_had_no_error(self):
227+
errors = [exceptions.ValidationError("a message", path=["bar"])]
228+
tree = exceptions.ErrorTree(errors)
229+
self.assertNotIn("foo", tree)
230+
231+
def test_validators_that_failed_appear_in_errors_dict(self):
232+
error = exceptions.ValidationError("a message", validator="foo")
233+
tree = exceptions.ErrorTree([error])
234+
self.assertEqual(tree.errors, {"foo" : error})
235+
236+
def test_it_creates_a_child_tree_for_each_nested_path(self):
237+
errors = [
238+
exceptions.ValidationError("a bar message", path=["bar"]),
239+
exceptions.ValidationError("a bar -> 0 message", path=["bar", 0]),
240+
]
241+
tree = exceptions.ErrorTree(errors)
242+
self.assertIn(0, tree["bar"])
243+
self.assertNotIn(1, tree["bar"])
244+
245+
def test_children_have_their_errors_dicts_built(self):
246+
e1, e2 = (
247+
exceptions.ValidationError("1", validator="foo", path=["bar", 0]),
248+
exceptions.ValidationError("2", validator="quux", path=["bar", 0]),
249+
)
250+
tree = exceptions.ErrorTree([e1, e2])
251+
self.assertEqual(tree["bar"][0].errors, {"foo" : e1, "quux" : e2})
252+
253+
def test_it_does_not_contain_subtrees_that_are_not_in_the_instance(self):
254+
error = exceptions.ValidationError("123", validator="foo", instance=[])
255+
tree = exceptions.ErrorTree([error])
256+
257+
with self.assertRaises(IndexError):
258+
tree[0]
259+
260+
def test_if_its_in_the_tree_anyhow_it_does_not_raise_an_error(self):
261+
"""
262+
If a validator is dumb (like :validator:`required` in draft 3) and
263+
refers to a path that isn't in the instance, the tree still properly
264+
returns a subtree for that path.
265+
266+
"""
267+
268+
error = exceptions.ValidationError(
269+
"a message", validator="foo", instance={}, path=["foo"],
270+
)
271+
tree = exceptions.ErrorTree([error])
272+
self.assertIsInstance(tree["foo"], exceptions.ErrorTree)

jsonschema/tests/test_validators.py

Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from jsonschema.compat import PY3
99
from jsonschema.tests.compat import mock, unittest
1010
from jsonschema.validators import (
11-
RefResolutionError, UnknownType, ErrorTree, Draft3Validator,
11+
RefResolutionError, UnknownType, Draft3Validator,
1212
Draft4Validator, RefResolver, create, extend, validator_for, validate,
1313
)
1414

@@ -518,69 +518,6 @@ def test_additionalItems_with_items(self):
518518
self.assertEqual(e2.validator, "minimum")
519519

520520

521-
class TestErrorTree(unittest.TestCase):
522-
def setUp(self):
523-
self.validator = Draft3Validator({})
524-
525-
def test_it_knows_how_many_total_errors_it_contains(self):
526-
errors = [mock.MagicMock() for _ in range(8)]
527-
tree = ErrorTree(errors)
528-
self.assertEqual(tree.total_errors, 8)
529-
530-
def test_it_contains_an_item_if_the_item_had_an_error(self):
531-
errors = [ValidationError("a message", path=["bar"])]
532-
tree = ErrorTree(errors)
533-
self.assertIn("bar", tree)
534-
535-
def test_it_does_not_contain_an_item_if_the_item_had_no_error(self):
536-
errors = [ValidationError("a message", path=["bar"])]
537-
tree = ErrorTree(errors)
538-
self.assertNotIn("foo", tree)
539-
540-
def test_validators_that_failed_appear_in_errors_dict(self):
541-
error = ValidationError("a message", validator="foo")
542-
tree = ErrorTree([error])
543-
self.assertEqual(tree.errors, {"foo" : error})
544-
545-
def test_it_creates_a_child_tree_for_each_nested_path(self):
546-
errors = [
547-
ValidationError("a bar message", path=["bar"]),
548-
ValidationError("a bar -> 0 message", path=["bar", 0]),
549-
]
550-
tree = ErrorTree(errors)
551-
self.assertIn(0, tree["bar"])
552-
self.assertNotIn(1, tree["bar"])
553-
554-
def test_children_have_their_errors_dicts_built(self):
555-
e1, e2 = (
556-
ValidationError("message 1", validator="foo", path=["bar", 0]),
557-
ValidationError("message 2", validator="quux", path=["bar", 0]),
558-
)
559-
tree = ErrorTree([e1, e2])
560-
self.assertEqual(tree["bar"][0].errors, {"foo" : e1, "quux" : e2})
561-
562-
def test_it_does_not_contain_subtrees_that_are_not_in_the_instance(self):
563-
error = ValidationError("a message", validator="foo", instance=[])
564-
tree = ErrorTree([error])
565-
566-
with self.assertRaises(IndexError):
567-
tree[0]
568-
569-
def test_if_its_in_the_tree_anyhow_it_does_not_raise_an_error(self):
570-
"""
571-
If a validator is dumb (like :validator:`required` in draft 3) and
572-
refers to a path that isn't in the instance, the tree still properly
573-
returns a subtree for that path.
574-
575-
"""
576-
577-
error = ValidationError(
578-
"a message", validator="foo", instance={}, path=["foo"],
579-
)
580-
tree = ErrorTree([error])
581-
self.assertIsInstance(tree["foo"], ErrorTree)
582-
583-
584521
class ValidatorTestMixin(object):
585522
def setUp(self):
586523
self.instance = mock.Mock()

jsonschema/validators.py

Lines changed: 1 addition & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import division, unicode_literals
22

3-
import collections
43
import contextlib
54
import json
65
import numbers
@@ -15,6 +14,7 @@
1514
PY3, Sequence, urljoin, urlsplit, urldefrag, unquote, urlopen,
1615
str_types, int_types, iteritems,
1716
)
17+
from jsonschema.exceptions import ErrorTree # For backwards compatibility
1818
from jsonschema.exceptions import RefResolutionError, SchemaError, UnknownType
1919

2020

@@ -379,82 +379,6 @@ def resolve_remote(self, uri):
379379
return result
380380

381381

382-
class ErrorTree(object):
383-
"""
384-
ErrorTrees make it easier to check which validations failed.
385-
386-
"""
387-
388-
_instance = _unset
389-
390-
def __init__(self, errors=()):
391-
self.errors = {}
392-
self._contents = collections.defaultdict(self.__class__)
393-
394-
for error in errors:
395-
container = self
396-
for element in error.path:
397-
container = container[element]
398-
container.errors[error.validator] = error
399-
400-
self._instance = error.instance
401-
402-
def __contains__(self, index):
403-
"""
404-
Check whether ``instance[index]`` has any errors.
405-
406-
"""
407-
408-
return index in self._contents
409-
410-
def __getitem__(self, index):
411-
"""
412-
Retrieve the child tree one level down at the given ``index``.
413-
414-
If the index is not in the instance that this tree corresponds to and
415-
is not known by this tree, whatever error would be raised by
416-
``instance.__getitem__`` will be propagated (usually this is some
417-
subclass of :class:`LookupError`.
418-
419-
"""
420-
421-
if self._instance is not _unset and index not in self:
422-
self._instance[index]
423-
return self._contents[index]
424-
425-
def __setitem__(self, index, value):
426-
self._contents[index] = value
427-
428-
def __iter__(self):
429-
"""
430-
Iterate (non-recursively) over the indices in the instance with errors.
431-
432-
"""
433-
434-
return iter(self._contents)
435-
436-
def __len__(self):
437-
"""
438-
Same as :attr:`total_errors`.
439-
440-
"""
441-
442-
return self.total_errors
443-
444-
def __repr__(self):
445-
return "<%s (%s total errors)>" % (self.__class__.__name__, len(self))
446-
447-
@property
448-
def total_errors(self):
449-
"""
450-
The total number of errors in the entire tree, including children.
451-
452-
"""
453-
454-
child_errors = sum(len(tree) for _, tree in iteritems(self._contents))
455-
return len(self.errors) + child_errors
456-
457-
458382
def validator_for(schema, default=_unset):
459383
if default is _unset:
460384
default = Draft4Validator

0 commit comments

Comments
 (0)