diff options
Diffstat (limited to 'jsonschema')
-rw-r--r-- | jsonschema/__init__.py | 4 | ||||
-rw-r--r-- | jsonschema/exceptions.py | 76 | ||||
-rw-r--r-- | jsonschema/tests/test_exceptions.py | 62 | ||||
-rw-r--r-- | jsonschema/tests/test_validators.py | 65 | ||||
-rw-r--r-- | jsonschema/validators.py | 78 |
5 files changed, 142 insertions, 143 deletions
diff --git a/jsonschema/__init__.py b/jsonschema/__init__.py index 34ca151..bd2077d 100644 --- a/jsonschema/__init__.py +++ b/jsonschema/__init__.py @@ -10,13 +10,13 @@ instance under a schema, and will create a validator for you. """ from jsonschema.exceptions import ( - FormatError, RefResolutionError, SchemaError, ValidationError + ErrorTree, FormatError, RefResolutionError, SchemaError, ValidationError ) from jsonschema._format import ( FormatChecker, draft3_format_checker, draft4_format_checker, ) from jsonschema.validators import ( - ErrorTree, Draft3Validator, Draft4Validator, RefResolver, validate + Draft3Validator, Draft4Validator, RefResolver, validate ) diff --git a/jsonschema/exceptions.py b/jsonschema/exceptions.py index 26fafbe..6b863ec 100644 --- a/jsonschema/exceptions.py +++ b/jsonschema/exceptions.py @@ -136,6 +136,82 @@ class FormatError(Exception): __str__ = __unicode__ +class ErrorTree(object): + """ + ErrorTrees make it easier to check which validations failed. + + """ + + _instance = _unset + + def __init__(self, errors=()): + self.errors = {} + self._contents = collections.defaultdict(self.__class__) + + for error in errors: + container = self + for element in error.path: + container = container[element] + container.errors[error.validator] = error + + self._instance = error.instance + + def __contains__(self, index): + """ + Check whether ``instance[index]`` has any errors. + + """ + + return index in self._contents + + def __getitem__(self, index): + """ + Retrieve the child tree one level down at the given ``index``. + + If the index is not in the instance that this tree corresponds to and + is not known by this tree, whatever error would be raised by + ``instance.__getitem__`` will be propagated (usually this is some + subclass of :class:`LookupError`. + + """ + + if self._instance is not _unset and index not in self: + self._instance[index] + return self._contents[index] + + def __setitem__(self, index, value): + self._contents[index] = value + + def __iter__(self): + """ + Iterate (non-recursively) over the indices in the instance with errors. + + """ + + return iter(self._contents) + + def __len__(self): + """ + Same as :attr:`total_errors`. + + """ + + return self.total_errors + + def __repr__(self): + return "<%s (%s total errors)>" % (self.__class__.__name__, len(self)) + + @property + def total_errors(self): + """ + The total number of errors in the entire tree, including children. + + """ + + child_errors = sum(len(tree) for _, tree in iteritems(self._contents)) + return len(self.errors) + child_errors + + def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES): def relevance(error): validator = error.validator diff --git a/jsonschema/tests/test_exceptions.py b/jsonschema/tests/test_exceptions.py index 2014a64..0466596 100644 --- a/jsonschema/tests/test_exceptions.py +++ b/jsonschema/tests/test_exceptions.py @@ -1,3 +1,5 @@ +import mock + from jsonschema import Draft4Validator, exceptions from jsonschema.tests.compat import unittest @@ -208,3 +210,63 @@ class TestByRelevance(unittest.TestCase): match = max([strong, normal, weak], key=best_match) self.assertIs(match, strong) + + +class TestErrorTree(unittest.TestCase): + def test_it_knows_how_many_total_errors_it_contains(self): + errors = [mock.MagicMock() for _ in range(8)] + tree = exceptions.ErrorTree(errors) + self.assertEqual(tree.total_errors, 8) + + def test_it_contains_an_item_if_the_item_had_an_error(self): + errors = [exceptions.ValidationError("a message", path=["bar"])] + tree = exceptions.ErrorTree(errors) + self.assertIn("bar", tree) + + def test_it_does_not_contain_an_item_if_the_item_had_no_error(self): + errors = [exceptions.ValidationError("a message", path=["bar"])] + tree = exceptions.ErrorTree(errors) + self.assertNotIn("foo", tree) + + def test_validators_that_failed_appear_in_errors_dict(self): + error = exceptions.ValidationError("a message", validator="foo") + tree = exceptions.ErrorTree([error]) + self.assertEqual(tree.errors, {"foo" : error}) + + def test_it_creates_a_child_tree_for_each_nested_path(self): + errors = [ + exceptions.ValidationError("a bar message", path=["bar"]), + exceptions.ValidationError("a bar -> 0 message", path=["bar", 0]), + ] + tree = exceptions.ErrorTree(errors) + self.assertIn(0, tree["bar"]) + self.assertNotIn(1, tree["bar"]) + + def test_children_have_their_errors_dicts_built(self): + e1, e2 = ( + exceptions.ValidationError("1", validator="foo", path=["bar", 0]), + exceptions.ValidationError("2", validator="quux", path=["bar", 0]), + ) + tree = exceptions.ErrorTree([e1, e2]) + self.assertEqual(tree["bar"][0].errors, {"foo" : e1, "quux" : e2}) + + def test_it_does_not_contain_subtrees_that_are_not_in_the_instance(self): + error = exceptions.ValidationError("123", validator="foo", instance=[]) + tree = exceptions.ErrorTree([error]) + + with self.assertRaises(IndexError): + tree[0] + + def test_if_its_in_the_tree_anyhow_it_does_not_raise_an_error(self): + """ + If a validator is dumb (like :validator:`required` in draft 3) and + refers to a path that isn't in the instance, the tree still properly + returns a subtree for that path. + + """ + + error = exceptions.ValidationError( + "a message", validator="foo", instance={}, path=["foo"], + ) + tree = exceptions.ErrorTree([error]) + self.assertIsInstance(tree["foo"], exceptions.ErrorTree) diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py index 1081ec1..bfc5871 100644 --- a/jsonschema/tests/test_validators.py +++ b/jsonschema/tests/test_validators.py @@ -8,7 +8,7 @@ from jsonschema import FormatChecker, ValidationError from jsonschema.compat import PY3 from jsonschema.tests.compat import mock, unittest from jsonschema.validators import ( - RefResolutionError, UnknownType, ErrorTree, Draft3Validator, + RefResolutionError, UnknownType, Draft3Validator, Draft4Validator, RefResolver, create, extend, validator_for, validate, ) @@ -518,69 +518,6 @@ class TestValidationErrorDetails(unittest.TestCase): self.assertEqual(e2.validator, "minimum") -class TestErrorTree(unittest.TestCase): - def setUp(self): - self.validator = Draft3Validator({}) - - def test_it_knows_how_many_total_errors_it_contains(self): - errors = [mock.MagicMock() for _ in range(8)] - tree = ErrorTree(errors) - self.assertEqual(tree.total_errors, 8) - - def test_it_contains_an_item_if_the_item_had_an_error(self): - errors = [ValidationError("a message", path=["bar"])] - tree = ErrorTree(errors) - self.assertIn("bar", tree) - - def test_it_does_not_contain_an_item_if_the_item_had_no_error(self): - errors = [ValidationError("a message", path=["bar"])] - tree = ErrorTree(errors) - self.assertNotIn("foo", tree) - - def test_validators_that_failed_appear_in_errors_dict(self): - error = ValidationError("a message", validator="foo") - tree = ErrorTree([error]) - self.assertEqual(tree.errors, {"foo" : error}) - - def test_it_creates_a_child_tree_for_each_nested_path(self): - errors = [ - ValidationError("a bar message", path=["bar"]), - ValidationError("a bar -> 0 message", path=["bar", 0]), - ] - tree = ErrorTree(errors) - self.assertIn(0, tree["bar"]) - self.assertNotIn(1, tree["bar"]) - - def test_children_have_their_errors_dicts_built(self): - e1, e2 = ( - ValidationError("message 1", validator="foo", path=["bar", 0]), - ValidationError("message 2", validator="quux", path=["bar", 0]), - ) - tree = ErrorTree([e1, e2]) - self.assertEqual(tree["bar"][0].errors, {"foo" : e1, "quux" : e2}) - - def test_it_does_not_contain_subtrees_that_are_not_in_the_instance(self): - error = ValidationError("a message", validator="foo", instance=[]) - tree = ErrorTree([error]) - - with self.assertRaises(IndexError): - tree[0] - - def test_if_its_in_the_tree_anyhow_it_does_not_raise_an_error(self): - """ - If a validator is dumb (like :validator:`required` in draft 3) and - refers to a path that isn't in the instance, the tree still properly - returns a subtree for that path. - - """ - - error = ValidationError( - "a message", validator="foo", instance={}, path=["foo"], - ) - tree = ErrorTree([error]) - self.assertIsInstance(tree["foo"], ErrorTree) - - class ValidatorTestMixin(object): def setUp(self): self.instance = mock.Mock() diff --git a/jsonschema/validators.py b/jsonschema/validators.py index 7044428..fab8201 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -1,6 +1,5 @@ from __future__ import division, unicode_literals -import collections import contextlib import json import numbers @@ -15,6 +14,7 @@ from jsonschema.compat import ( PY3, Sequence, urljoin, urlsplit, urldefrag, unquote, urlopen, str_types, int_types, iteritems, ) +from jsonschema.exceptions import ErrorTree # For backwards compatibility from jsonschema.exceptions import RefResolutionError, SchemaError, UnknownType @@ -379,82 +379,6 @@ class RefResolver(object): return result -class ErrorTree(object): - """ - ErrorTrees make it easier to check which validations failed. - - """ - - _instance = _unset - - def __init__(self, errors=()): - self.errors = {} - self._contents = collections.defaultdict(self.__class__) - - for error in errors: - container = self - for element in error.path: - container = container[element] - container.errors[error.validator] = error - - self._instance = error.instance - - def __contains__(self, index): - """ - Check whether ``instance[index]`` has any errors. - - """ - - return index in self._contents - - def __getitem__(self, index): - """ - Retrieve the child tree one level down at the given ``index``. - - If the index is not in the instance that this tree corresponds to and - is not known by this tree, whatever error would be raised by - ``instance.__getitem__`` will be propagated (usually this is some - subclass of :class:`LookupError`. - - """ - - if self._instance is not _unset and index not in self: - self._instance[index] - return self._contents[index] - - def __setitem__(self, index, value): - self._contents[index] = value - - def __iter__(self): - """ - Iterate (non-recursively) over the indices in the instance with errors. - - """ - - return iter(self._contents) - - def __len__(self): - """ - Same as :attr:`total_errors`. - - """ - - return self.total_errors - - def __repr__(self): - return "<%s (%s total errors)>" % (self.__class__.__name__, len(self)) - - @property - def total_errors(self): - """ - The total number of errors in the entire tree, including children. - - """ - - child_errors = sum(len(tree) for _, tree in iteritems(self._contents)) - return len(self.errors) + child_errors - - def validator_for(schema, default=_unset): if default is _unset: default = Draft4Validator |