Error handling¶
Validobj aims to be good at providing good error messages by default, and enough metadata so that errors can be repurposed or improved. That metadata is enough to for example attach YAML line numbers to the sources of error.
Generic errors¶
All exceptions raised by Validobj are subclasses of
validobj.errors.ValidationError
. Therefore it can be used in an
except
block to tell if the validation succeeded.
>>> try:
... result = validobj.parse_input([1, 2, "3"], list)
... except validobj.ValidationError as e:
... print("Validation failed")
... else:
... print("Validation succeeded")
...
Validation succeeded
Errors for Unions¶
Errors for union types (including typing.Optional
) result in a
validobj.errors.UnionValidationError
being raised. The exception
contains a causes
attribute containing the reasons for the mismatch for
each of the individual types.
>>> from typing import Optional, List
>>> validobj.parse_input([1, "dos"], Optional[List[int]])
Traceback (most recent call last):
...
UnionValidationError: No match for any possible type:
Not a valid match for 'typing.List[int]': Cannot process list item 2.
Not a valid match for 'NoneType': Expecting value of type 'NoneType', not list.
Key mismatch¶
The optional and required keys in a mapping can be specified through the definition of a dataclass. For example:
import dataclasses
from typing import Optional, List
@dataclasses.dataclass
class User:
name: str
phone: Optional[str] = None
tasks: List[str] = dataclasses.field(default_factory=list)
means that the class User
has one required field, name
(because it
doesn’t have a default) and two optional fields, phone
and tasks
(because they have a simple value default and a default factory respectively).
When provided values do not match the specification, either because unknown
keys are provided or required keys are missing, a
validobj.errors.WrongKeysError
is raised. The error knows about
unknown, missing and valid keys and stores them in as the unkown
,
missing
and valid
attributes respectively. Suggestions will be given
for invalid keys that look like typos.
>>> validobj.parse_input({
... 'phone': '555-1337-000', 'address': 'Somewhereville', 'nme': 'Zahari'}, User
... )
Traceback (most recent call last):
...
WrongKeysError: Cannot process value into 'User' because fields do not match.
The following required keys are missing: {'name'}. The following keys are unknown: {'nme', 'address'}.
Alternatives to invalid value 'nme' include:
- name
All valid options are:
- name
- phone
- tasks
The attributes of the exception can be inspected:
>>> from validobj.errors import WrongKeysError
>>> try:
... validobj.parse_input({'phone': '555-1337-000',
... 'address': 'Somewhereville', 'nme': 'Zahari'},
... User)
... except WrongKeysError as e:
... print(f'The missing keys are {sorted(e.missing)!r}')
...
The missing keys are ['name']
Wrong keys¶
When a given value in a mapping fails to be processed, the original exception
is wrapped with a validobj.errors.WrongFieldError
so that it is its
__cause__
. The problematic field is stored in the wrong_field
attribute:
>>> validobj.parse_input({'name': 11}, User)
Traceback (most recent call last):
...
WrongTypeError: Expecting value of type 'str', not int.
The above exception was the direct cause of the following exception:
...
WrongFieldError: Cannot process field 'name' of value into the corresponding field of 'User'
Wrong list items¶
Analogously to mapping keys, when a given list item fails to
conform to the specification, a validobj.errors.WrongListItemError
is raised. The problematic index is stored in the wrong_index
attribute of
the exception. The original error is stored as the __cause__
.
>>> validobj.parse_input([{'name': "Eleven"}, {'name': 11}], List[User])
Traceback (most recent call last):
...
WrongTypeError: Expecting value of type 'str', not int.
...
The above exception was the direct cause of the following exception:
...
Traceback (most recent call last):
...
WrongFieldError: Cannot process field 'name' of value into the corresponding field of 'User'
...
The above exception was the direct cause of the following exception:
...
Traceback (most recent call last):
...
WrongListItemError: Cannot process list item 2.
Note that there are as many levels of chaining as necessary.
Wrong enum elements¶
Wrong enum elements will result in a
validobj.errors.NotAnEnumItemError
. These errors know about the
original enum class and will suggest fixes to the typos. Additionally
enum.Flag
combinations will behave like lists and raise a
validobj.errors.WrongListItemError
.
>>> import enum
>>> import validobj
>>> class DiskPermissions(enum.Flag):
... READ = enum.auto()
... WRITE = enum.auto()
... EXECUTE = enum.auto()
...
>>> validobj.parse_input(['EXECUTE', 'RAED'], DiskPermissions)
NotAnEnumItemError Traceback (most recent call last)
...
NotAnEnumItemError: 'RAED' is not a valid member of 'DiskPermissions'.
Alternatives to invalid value 'RAED' include:
- READ
All valid values are:
- READ
- WRITE
- EXECUTE
The above exception was the direct cause of the following exception:
...
WrongListItemError: Cannot process item 2 into 'DiskPermissions'.
Wrong Literals¶
Wrong Literals provide an exception with the tested and valid values:
>>> import validobj
>>> import typing
>>> validobj.parse_input(5, typing.Literal[6, typing.Literal[7], 8])
Traceback (most recent call last):
...
WrongLiteralError: Wrong literal. Expecting one of '[6, 7, 8]'. Got '5'