Break backwards compat: ValidationError.path is now in sorted order.
This commit is contained in:
@@ -15,5 +15,6 @@ Creating Validation Errors
|
|||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
Any validating function that recurses into an instance (e.g. ``properties`` or
|
Any validating function that recurses into an instance (e.g. ``properties`` or
|
||||||
``items``) must append to the :exc:`ValidationError.path` attribute of the
|
``items``) must call ``appendleft`` on the :exc:`ValidationError.path`
|
||||||
error in order to properly maintain where in the instance the error occurred.
|
attribute of the error in order to properly maintain where in the instance the
|
||||||
|
error occurred.
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ raised or returned, depending on which method or function is used.
|
|||||||
|
|
||||||
.. attribute:: path
|
.. attribute:: path
|
||||||
|
|
||||||
A list containing the path to the offending element (or [] if the error
|
A deque containing the path to the offending element (or an empty deque
|
||||||
happened globally) in *reverse* order (i.e. deepest index first).
|
if the error happened globally).
|
||||||
|
|
||||||
|
|
||||||
In case an invalid schema itself is encountered, a :exc:`SchemaError` is
|
In case an invalid schema itself is encountered, a :exc:`SchemaError` is
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class _Error(Exception):
|
|||||||
def __init__(self, message, validator=None, path=()):
|
def __init__(self, message, validator=None, path=()):
|
||||||
super(_Error, self).__init__(message, validator, path)
|
super(_Error, self).__init__(message, validator, path)
|
||||||
self.message = message
|
self.message = message
|
||||||
self.path = list(path)
|
self.path = collections.deque(path)
|
||||||
self.validator = validator
|
self.validator = validator
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -202,12 +202,12 @@ class _Draft34CommonMixin(object):
|
|||||||
if self.is_type(items, "object"):
|
if self.is_type(items, "object"):
|
||||||
for index, item in enumerate(instance):
|
for index, item in enumerate(instance):
|
||||||
for error in self.iter_errors(item, items):
|
for error in self.iter_errors(item, items):
|
||||||
error.path.append(index)
|
error.path.appendleft(index)
|
||||||
yield error
|
yield error
|
||||||
else:
|
else:
|
||||||
for (index, item), subschema in zip(enumerate(instance), items):
|
for (index, item), subschema in zip(enumerate(instance), items):
|
||||||
for error in self.iter_errors(item, subschema):
|
for error in self.iter_errors(item, subschema):
|
||||||
error.path.append(index)
|
error.path.appendleft(index)
|
||||||
yield error
|
yield error
|
||||||
|
|
||||||
def validate_additionalItems(self, aI, instance, schema):
|
def validate_additionalItems(self, aI, instance, schema):
|
||||||
@@ -366,7 +366,7 @@ class Draft3Validator(ValidatorMixin, _Draft34CommonMixin, object):
|
|||||||
for property, subschema in iteritems(properties):
|
for property, subschema in iteritems(properties):
|
||||||
if property in instance:
|
if property in instance:
|
||||||
for error in self.iter_errors(instance[property], subschema):
|
for error in self.iter_errors(instance[property], subschema):
|
||||||
error.path.append(property)
|
error.path.appendleft(property)
|
||||||
yield error
|
yield error
|
||||||
elif subschema.get("required", False):
|
elif subschema.get("required", False):
|
||||||
yield ValidationError(
|
yield ValidationError(
|
||||||
@@ -495,7 +495,7 @@ class Draft4Validator(ValidatorMixin, _Draft34CommonMixin, object):
|
|||||||
for property, subschema in iteritems(properties):
|
for property, subschema in iteritems(properties):
|
||||||
if property in instance:
|
if property in instance:
|
||||||
for error in self.iter_errors(instance[property], subschema):
|
for error in self.iter_errors(instance[property], subschema):
|
||||||
error.path.append(property)
|
error.path.appendleft(property)
|
||||||
yield error
|
yield error
|
||||||
|
|
||||||
def validate_required(self, required, instance, schema):
|
def validate_required(self, required, instance, schema):
|
||||||
@@ -1054,7 +1054,7 @@ class ErrorTree(object):
|
|||||||
|
|
||||||
for error in errors:
|
for error in errors:
|
||||||
container = self
|
container = self
|
||||||
for element in reversed(error.path):
|
for element in error.path:
|
||||||
container = container[element]
|
container = container[element]
|
||||||
container.errors[error.validator] = error
|
container.errors[error.validator] = error
|
||||||
|
|
||||||
|
|||||||
26
tests.py
26
tests.py
@@ -365,10 +365,10 @@ class TestValidationErrorDetails(unittest.TestCase):
|
|||||||
errors = self.validator.iter_errors(instance, schema)
|
errors = self.validator.iter_errors(instance, schema)
|
||||||
e1, e2, e3, e4 = sorted_errors(errors)
|
e1, e2, e3, e4 = sorted_errors(errors)
|
||||||
|
|
||||||
self.assertEqual(e1.path, ["bar"])
|
self.assertItemsEqual(e1.path, ["bar"])
|
||||||
self.assertEqual(e2.path, ["baz"])
|
self.assertItemsEqual(e2.path, ["baz"])
|
||||||
self.assertEqual(e3.path, ["baz"])
|
self.assertItemsEqual(e3.path, ["baz"])
|
||||||
self.assertEqual(e4.path, ["foo"])
|
self.assertItemsEqual(e4.path, ["foo"])
|
||||||
|
|
||||||
self.assertEqual(e1.validator, "minItems")
|
self.assertEqual(e1.validator, "minItems")
|
||||||
self.assertEqual(e2.validator, "enum")
|
self.assertEqual(e2.validator, "enum")
|
||||||
@@ -397,12 +397,12 @@ class TestValidationErrorDetails(unittest.TestCase):
|
|||||||
errors = self.validator.iter_errors(instance, schema)
|
errors = self.validator.iter_errors(instance, schema)
|
||||||
e1, e2, e3, e4, e5, e6 = sorted_errors(errors)
|
e1, e2, e3, e4, e5, e6 = sorted_errors(errors)
|
||||||
|
|
||||||
self.assertEqual(e1.path, [])
|
self.assertItemsEqual(e1.path, [])
|
||||||
self.assertEqual(e2.path, [0])
|
self.assertItemsEqual(e2.path, [0])
|
||||||
self.assertEqual(e3.path, ["bar", 1])
|
self.assertItemsEqual(e3.path, [1, "bar"])
|
||||||
self.assertEqual(e4.path, ["bar", "bar", 1])
|
self.assertItemsEqual(e4.path, [1, "bar", "bar"])
|
||||||
self.assertEqual(e5.path, ["baz", "bar", 1])
|
self.assertItemsEqual(e5.path, [1, "bar", "baz"])
|
||||||
self.assertEqual(e6.path, ["foo", 1])
|
self.assertItemsEqual(e6.path, [1, "foo"])
|
||||||
|
|
||||||
self.assertEqual(e1.validator, "type")
|
self.assertEqual(e1.validator, "type")
|
||||||
self.assertEqual(e2.validator, "type")
|
self.assertEqual(e2.validator, "type")
|
||||||
@@ -439,7 +439,7 @@ class TestErrorTree(unittest.TestCase):
|
|||||||
def test_it_creates_a_child_tree_for_each_nested_path(self):
|
def test_it_creates_a_child_tree_for_each_nested_path(self):
|
||||||
errors = [
|
errors = [
|
||||||
ValidationError("a bar message", path=["bar"]),
|
ValidationError("a bar message", path=["bar"]),
|
||||||
ValidationError("a bar -> 0 message", path=[0, "bar"]),
|
ValidationError("a bar -> 0 message", path=["bar", 0]),
|
||||||
]
|
]
|
||||||
tree = ErrorTree(errors)
|
tree = ErrorTree(errors)
|
||||||
self.assertIn(0, tree["bar"])
|
self.assertIn(0, tree["bar"])
|
||||||
@@ -447,8 +447,8 @@ class TestErrorTree(unittest.TestCase):
|
|||||||
|
|
||||||
def test_children_have_their_errors_dicts_built(self):
|
def test_children_have_their_errors_dicts_built(self):
|
||||||
e1, e2 = (
|
e1, e2 = (
|
||||||
ValidationError("message 1", validator="foo", path=[0, "bar"]),
|
ValidationError("message 1", validator="foo", path=["bar", 0]),
|
||||||
ValidationError("message 2", validator="quux", path=[0, "bar"]),
|
ValidationError("message 2", validator="quux", path=["bar", 0]),
|
||||||
)
|
)
|
||||||
tree = ErrorTree([e1, e2])
|
tree = ErrorTree([e1, e2])
|
||||||
self.assertEqual(tree["bar"][0].errors, {"foo" : e1, "quux" : e2})
|
self.assertEqual(tree["bar"][0].errors, {"foo" : e1, "quux" : e2})
|
||||||
|
|||||||
Reference in New Issue
Block a user