Skip to content

Commit

Permalink
Revert behavior change in error reporting for function after validato…
Browse files Browse the repository at this point in the history
…rs (#495)
  • Loading branch information
adriangb authored Mar 29, 2023
1 parent 7662bdd commit 6a830dc
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 24 deletions.
6 changes: 3 additions & 3 deletions src/validators/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@ impl FunctionAfterValidator {
extra: &Extra,
) -> ValResult<'data, PyObject> {
let info = ValidationInfo::new(py, extra, &self.config, self.is_field_validator)?;
let input = call(input, extra)?;
let v = call(input, extra)?;
self.func
.call1(py, (input.to_object(py), info))
.map_err(|e| convert_err(py, e, input.into_ref(py)))
.call1(py, (v.to_object(py), info))
.map_err(|e| convert_err(py, e, input))
}
}

Expand Down
33 changes: 33 additions & 0 deletions tests/validators/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,39 @@ def f(input_value, validator, info):
v.validate_python(4)


def test_function_after():
def f(input_value, _info):
return input_value + ' Changed'

v = SchemaValidator(
{'type': 'function-after', 'function': {'type': 'general', 'function': f}, 'schema': {'type': 'str'}}
)

assert v.validate_python('input value') == 'input value Changed'


def test_function_after_raise():
def f(input_value, info):
raise ValueError('foobar')

v = SchemaValidator(
{'type': 'function-after', 'function': {'type': 'general', 'function': f}, 'schema': {'type': 'str'}}
)

with pytest.raises(ValidationError) as exc_info:
assert v.validate_python('input value') == 'input value Changed'
# debug(str(exc_info.value))
assert exc_info.value.errors() == [
{
'type': 'value_error',
'loc': (),
'msg': 'Value error, foobar',
'input': 'input value',
'ctx': {'error': 'foobar'},
}
]


def test_function_after_config():
f_kwargs = None

Expand Down
130 changes: 109 additions & 21 deletions tests/validators/test_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import re
from copy import deepcopy
from typing import Any, List
from typing import Any, Callable, Dict, List, Set, Tuple

import pytest

Expand Down Expand Up @@ -88,33 +88,121 @@ def __setattr__(self, key, value):
assert setattr_calls == []


def test_model_class_root_validator():
def test_model_class_root_validator_wrap():
class MyModel:
pass
def __init__(self, **kwargs: Any) -> None:
self.__dict__.update(kwargs)

def f(input_value, validator, info):
def f(
input_value: Dict[str, Any],
validator: Callable[[Dict[str, Any]], Dict[str, Any]],
info: core_schema.ValidationInfo,
):
assert input_value['field_a'] == 123
output = validator(input_value)
return str(output)
return output

v = SchemaValidator(
schema = core_schema.model_schema(
MyModel,
core_schema.general_wrap_validator_function(
f,
core_schema.typed_dict_schema(
{'field_a': core_schema.typed_dict_field(core_schema.int_schema())}, return_fields_set=True
),
),
)

v = SchemaValidator(schema)
m = v.validate_python({'field_a': 123})
assert m.field_a == 123

with pytest.raises(ValidationError) as e:
v.validate_python({'field_a': 456})

assert e.value.errors() == [
{
'type': 'function-wrap',
'function': {'type': 'general', 'function': f},
'schema': {
'type': 'model',
'cls': MyModel,
'schema': {
'type': 'typed-dict',
'return_fields_set': True,
'fields': {'field_a': {'type': 'typed-dict-field', 'schema': {'type': 'str'}}},
},
},
'type': 'assertion_error',
'loc': (),
'msg': 'Assertion failed, assert 456 == 123',
'input': {'field_a': 456},
'ctx': {'error': 'assert 456 == 123'},
}
]


def test_model_class_root_validator_before():
class MyModel:
def __init__(self, **kwargs: Any) -> None:
self.__dict__.update(kwargs)

def f(input_value: Dict[str, Any], info: core_schema.ValidationInfo):
assert input_value['field_a'] == 123
return input_value

schema = core_schema.model_schema(
MyModel,
core_schema.general_before_validator_function(
f,
core_schema.typed_dict_schema(
{'field_a': core_schema.typed_dict_field(core_schema.int_schema())}, return_fields_set=True
),
),
)

v = SchemaValidator(schema)
m = v.validate_python({'field_a': 123})
assert m.field_a == 123

with pytest.raises(ValidationError) as e:
v.validate_python({'field_a': 456})

assert e.value.errors() == [
{
'type': 'assertion_error',
'loc': (),
'msg': 'Assertion failed, assert 456 == 123',
'input': {'field_a': 456},
'ctx': {'error': 'assert 456 == 123'},
}
]


def test_model_class_root_validator_after():
class MyModel:
def __init__(self, **kwargs: Any) -> None:
self.__dict__.update(kwargs)

def f(input_value_and_fields_set: Tuple[Dict[str, Any], Set[str]], info: core_schema.ValidationInfo):
input_value, _ = input_value_and_fields_set
assert input_value['field_a'] == 123
return input_value_and_fields_set

schema = core_schema.model_schema(
MyModel,
core_schema.general_after_validator_function(
f,
core_schema.typed_dict_schema(
{'field_a': core_schema.typed_dict_field(core_schema.int_schema())}, return_fields_set=True
),
),
)
assert 'expect_fields_set:true' in plain_repr(v)
m = v.validate_python({'field_a': 'test'})
assert isinstance(m, str)
assert 'test_model_class_root_validator.<locals>.MyModel' in m

v = SchemaValidator(schema)
m = v.validate_python({'field_a': 123})
assert m.field_a == 123

with pytest.raises(ValidationError) as e:
v.validate_python({'field_a': 456})

assert e.value.errors() == [
{
'type': 'assertion_error',
'loc': (),
'msg': 'Assertion failed, assert 456 == 123',
'input': {'field_a': 456},
'ctx': {'error': 'assert 456 == 123'},
}
]


@pytest.mark.parametrize('mode', ['before', 'after', 'wrap'])
Expand Down

0 comments on commit 6a830dc

Please sign in to comment.