Obtain object/field instance causing validation error
I can't work out how to obtain a reference to the object instance or field causing a validation error when calling cattrs.structure
For example:
from attrs import define
from cattrs import structure, BaseValidationError
data = [
{
'id': 'aaa',
'title': 'atitle',
},
{
'XX': 'bbb',
'title': 'btitle'
}
]
@define
class Example:
id: str
title: str
try:
example = structure(data, list[Example])
except BaseValidationError as e:
print(f"Exception: {e}")
example = None
print(example)
In the example above, one of the list items causes an IterableValidationError (as expected), and within the exception there is a ClassValidationError sub-exception (and KeyErrors within that).
The question is, how do I obtain the instance of the object causing the exception? The reasons for needing this information, is that the dictionary will be decorated with additional metadata (line number information) as the dict is a result from parsing with the ruamel.yaml library. I need to have the object instance causing the error as this will give better error messages in my use-case
thanks
Exceptions from (un)structuring should be ExceptionGroup which you should be able to handle to get what you want: https://catt.rs/en/stable/validation.html
Hi,
so I think you have a couple of options here. One would be using the cattrs.transform_error helper. In this case, it will produce a list of error messages like this:
['required field missing @ $[1].id']
You could parse the path string after the @ symbol to dig out the actual payload. $ means the root object, [N] means to index into it at N, and .id means the id attribute.
The second option is to take the source of the transform_error function and just copy it into your codebase and adjust it. transform_error is essentially a tree walker (since the exceptions returned form a tree), and not very complex.
Here's what it might look like:
from typing import Any
from attrs import define
from cattrs import BaseValidationError, structure
from cattrs.errors import ClassValidationError, IterableValidationError
data = [{"id": "aaa", "title": "atitle"}, {"XX": "bbb", "title": "btitle"}]
@define
class Example:
id: str
title: str
def get_error_leaves(
exc: ClassValidationError | IterableValidationError | BaseException, root: Any
) -> list[Any]:
error_payloads = []
if isinstance(exc, IterableValidationError):
with_notes, without = exc.group_exceptions()
for exc, note in with_notes:
if isinstance(exc, (ClassValidationError, IterableValidationError)):
error_payloads.extend(get_error_leaves(exc, root[note.index]))
else:
error_payloads.append(root)
elif isinstance(exc, ClassValidationError):
with_notes, without = exc.group_exceptions()
for exc, note in with_notes:
if isinstance(exc, (ClassValidationError, IterableValidationError)):
error_payloads.extend(get_error_leaves(exc, root[note.name]))
else:
error_payloads.append(root)
else:
error_payloads.append(root)
return error_payloads
try:
example = structure(data, list[Example])
except BaseValidationError as e:
print(f"Exception: {e}")
print(get_error_leaves(e, data))
example = get_error_leaves(e, data)[0]
print(example)
I put this together in five minutes so it's probably not production-quality, just a starting point for you.
Let me know if you have any other questions, will close this now to keep the issue list tidy.