Files
pydantic/tests/test_error_wrappers.py
T
Nikita Grishko 4f4e22ef47 Error context and message (#183)
* POC of error context and message

* Move type errors to the `errors.py` module; Change errors interface a bit

* Rename `.as_dict()` to `.dict()`

* Fix `PydanticErrorMixin` constructor

* Rename `exceptions.py` to `error_wrappers.py`

* Do not include nullable `ctx`

* Fix tests

* Added `int_validator`; Added `IntegerError`

* Added `float_validator`; Added `FloatError`

* Get rid of `__mro__` in prior of `exc.code`

* Removed `min_number_size` and `max_number_size` from config (#174)

* Added `NumberMinSizeError` and `NumberMaxSizeError`

* Added `NoneIsNotAllowedError`

* Added `EnumError`

* Added `path_validator`; Added `PathError`

* Added `DictError`

* Added `ListError`

* Added `TupleError`

* Added `SetError`

* Added `datetime` related errors

* Added `bytes` and `str` related errors

* Added `SequenceError`

* Improved code coverage

* Display error context in string representation of validation error

* Redefine error message templates using config

* Review fixes

* Updated changelog
2018-05-31 14:35:38 +01:00

325 lines
7.5 KiB
Python

from typing import Dict, List, Union
from uuid import UUID, uuid4
import pytest
from pydantic import UUID1, BaseModel, conint, errors
from pydantic.error_wrappers import ValidationError, flatten_errors, get_exc_type
@pytest.mark.parametrize('result,expected', (
(
'display_errors',
"""\
a
value is not a valid integer (type=type_error.integer)
b -> x
field required (type=value_error.missing)
b -> z
field required (type=value_error.missing)
c -> 0 -> x
value is not a valid integer (type=type_error.integer)
d
value is not a valid integer (type=type_error.integer)
d
value is not a valid uuid (type=type_error.uuid)
e -> __key__
value is not a valid integer (type=type_error.integer)
f -> 0
value is not a valid integer (type=type_error.integer)
f -> 0
none is not an allow value (type=type_error.none.not_allowed)
g
uuid version 1 expected (type=value_error.uuid.version; required_version=1)
h
yet another error message template 42 (type=value_error.number.min_size; limit_value=42)""",
),
(
'flatten_errors',
[
{
'loc': (
'a',
),
'msg': 'value is not a valid integer',
'type': 'type_error.integer',
},
{
'loc': (
'b',
'x',
),
'msg': 'field required',
'type': 'value_error.missing',
},
{
'loc': (
'b',
'z',
),
'msg': 'field required',
'type': 'value_error.missing',
},
{
'loc': (
'c',
0,
'x',
),
'msg': 'value is not a valid integer',
'type': 'type_error.integer',
},
{
'loc': (
'd',
),
'msg': 'value is not a valid integer',
'type': 'type_error.integer',
},
{
'loc': (
'd',
),
'msg': 'value is not a valid uuid',
'type': 'type_error.uuid',
},
{
'loc': (
'e',
'__key__',
),
'msg': 'value is not a valid integer',
'type': 'type_error.integer',
},
{
'loc': (
'f',
0,
),
'msg': 'value is not a valid integer',
'type': 'type_error.integer',
},
{
'loc': (
'f',
0,
),
'msg': 'none is not an allow value',
'type': 'type_error.none.not_allowed',
},
{
'loc': (
'g',
),
'msg': 'uuid version 1 expected',
'type': 'value_error.uuid.version',
'ctx': {
'required_version': 1,
},
},
{
'loc': (
'h',
),
'msg': 'yet another error message template 42',
'type': 'value_error.number.min_size',
'ctx': {
'limit_value': 42,
},
}
],
),
(
'json',
"""\
[
{
"loc": [
"a"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
},
{
"loc": [
"b",
"x"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"b",
"z"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"c",
0,
"x"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
},
{
"loc": [
"d"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
},
{
"loc": [
"d"
],
"msg": "value is not a valid uuid",
"type": "type_error.uuid"
},
{
"loc": [
"e",
"__key__"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
},
{
"loc": [
"f",
0
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
},
{
"loc": [
"f",
0
],
"msg": "none is not an allow value",
"type": "type_error.none.not_allowed"
},
{
"ctx": {
"required_version": 1
},
"loc": [
"g"
],
"msg": "uuid version 1 expected",
"type": "value_error.uuid.version"
},
{
"ctx": {
"limit_value": 42
},
"loc": [
"h"
],
"msg": "yet another error message template 42",
"type": "value_error.number.min_size"
}
]"""
),
(
'__str__',
"""\
validation errors
a
value is not a valid integer (type=type_error.integer)
b -> x
field required (type=value_error.missing)
b -> z
field required (type=value_error.missing)
c -> 0 -> x
value is not a valid integer (type=type_error.integer)
d
value is not a valid integer (type=type_error.integer)
d
value is not a valid uuid (type=type_error.uuid)
e -> __key__
value is not a valid integer (type=type_error.integer)
f -> 0
value is not a valid integer (type=type_error.integer)
f -> 0
none is not an allow value (type=type_error.none.not_allowed)
g
uuid version 1 expected (type=value_error.uuid.version; required_version=1)
h
yet another error message template 42 (type=value_error.number.min_size; limit_value=42)"""
),
))
def test_validation_error(result, expected):
class SubModel(BaseModel):
x: int
y: int
z: str
class Model(BaseModel):
a: int
b: SubModel
c: List[SubModel]
d: Union[int, UUID]
e: Dict[int, str]
f: List[Union[int, str]]
g: UUID1
h: conint(gt=42)
class Config:
error_msg_templates = {
'value_error.number.min_size': 'yet another error message template {limit_value}',
}
with pytest.raises(ValidationError) as exc_info:
Model.parse_obj({
'a': 'not_int',
'b': {
'y': 42,
},
'c': [
{
'x': 'not_int',
'y': 42,
'z': 'string',
},
],
'd': 'string',
'e': {
'not_int': 'string',
},
'f': [
None,
],
'g': uuid4(),
'h': 21,
})
result = getattr(exc_info.value, result)
if callable(result):
result = result()
assert result == expected
def test_flatten_errors_unknown_error_object():
with pytest.raises(RuntimeError):
list(flatten_errors([object]))
@pytest.mark.parametrize('exc,type_', (
(TypeError(), 'type_error'),
(ValueError(), 'value_error'),
(errors.DecimalIsNotFiniteError(), 'value_error.decimal.not_finite'),
))
def test_get_exc_type(exc, type_):
if isinstance(type_, str):
assert get_exc_type(exc) == type_
else:
with pytest.raises(type_) as exc_info:
get_exc_type(exc)
assert isinstance(exc_info.value, type_)