Skip to content

Commit fdf42b8

Browse files
authored
add tests for validation context (#220)
* add tests for validatoin context * fix tests and use py_and_json
1 parent 8f60642 commit fdf42b8

File tree

2 files changed

+103
-10
lines changed

2 files changed

+103
-10
lines changed

tests/conftest.py

+12-10
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,22 @@ def __init__(self, schema, validator_type: 'Literal["json", "python"] | None' =
4545
self.validator = SchemaValidator(schema)
4646
self.validator_type = validator_type
4747

48-
def validate_python(self, py_input, strict: 'bool | None' = None):
49-
return self.validator.validate_python(py_input, strict)
48+
def validate_python(self, py_input, strict: 'bool | None' = None, context: Any = None):
49+
return self.validator.validate_python(py_input, strict, context)
5050

51-
def validate_test(self, py_input, strict: 'bool | None' = None):
51+
def validate_test(self, py_input, strict: 'bool | None' = None, context: Any = None):
5252
if self.validator_type == 'json':
53-
return self.validator.validate_json(json.dumps(py_input), strict)
54-
elif self.validator_type == 'python':
55-
return self.validator.validate_python(py_input, strict)
53+
return self.validator.validate_json(json.dumps(py_input), strict, context)
54+
else:
55+
assert self.validator_type == 'python', self.validator_type
56+
return self.validator.validate_python(py_input, strict, context)
5657

57-
def isinstance_test(self, py_input, strict: 'bool | None' = None):
58+
def isinstance_test(self, py_input, strict: 'bool | None' = None, context: Any = None):
5859
if self.validator_type == 'json':
59-
return self.validator.isinstance_json(json.dumps(py_input), strict)
60-
elif self.validator_type == 'python':
61-
return self.validator.isinstance_python(py_input, strict)
60+
return self.validator.isinstance_json(json.dumps(py_input), strict, context)
61+
else:
62+
assert self.validator_type == 'python', self.validator_type
63+
return self.validator.isinstance_python(py_input, strict, context)
6264

6365

6466
PyAndJson = Type[PyAndJsonValidator]

tests/test_validation_context.py

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import pytest
2+
3+
from pydantic_core import ValidationError
4+
5+
from .conftest import PyAndJson
6+
7+
8+
def test_after(py_and_json: PyAndJson):
9+
def f(input_value, *, context, **kwargs):
10+
return input_value + f'| context: {context}'
11+
12+
v = py_and_json({'type': 'function', 'mode': 'after', 'function': f, 'schema': 'str'})
13+
14+
assert v.validate_test('foobar') == 'foobar| context: None'
15+
assert v.validate_test('foobar', None, {1: 10}) == 'foobar| context: {1: 10}'
16+
assert v.validate_test('foobar', None, 'frogspawn') == 'foobar| context: frogspawn'
17+
18+
19+
def test_mutable_context(py_and_json: PyAndJson):
20+
def f(input_value, *, context, **kwargs):
21+
context['foo'] = input_value
22+
return input_value
23+
24+
v = py_and_json({'type': 'function', 'mode': 'before', 'function': f, 'schema': 'str'})
25+
mutable_context = {}
26+
assert v.validate_test('foobar', None, mutable_context) == 'foobar'
27+
assert mutable_context == {'foo': 'foobar'}
28+
29+
30+
def test_typed_dict(py_and_json: PyAndJson):
31+
def f1(input_value, *, context, **kwargs):
32+
context['f1'] = input_value
33+
return input_value + f'| context: {context}'
34+
35+
def f2(input_value, *, context, **kwargs):
36+
context['f2'] = input_value
37+
return input_value + f'| context: {context}'
38+
39+
v = py_and_json(
40+
{
41+
'type': 'typed-dict',
42+
'fields': {
43+
'f1': {'schema': {'type': 'function', 'mode': 'plain', 'function': f1}},
44+
'f2': {'schema': {'type': 'function', 'mode': 'plain', 'function': f2}},
45+
},
46+
}
47+
)
48+
49+
assert v.validate_test({'f1': '1', 'f2': '2'}, None, {'x': 'y'}) == {
50+
'f1': "1| context: {'x': 'y', 'f1': '1'}",
51+
'f2': "2| context: {'x': 'y', 'f1': '1', 'f2': '2'}",
52+
}
53+
54+
55+
def test_wrap(py_and_json: PyAndJson):
56+
def f(input_value, *, validator, context, **kwargs):
57+
return validator(input_value) + f'| context: {context}'
58+
59+
v = py_and_json({'type': 'function', 'mode': 'wrap', 'function': f, 'schema': 'str'})
60+
61+
assert v.validate_test('foobar') == 'foobar| context: None'
62+
assert v.validate_test('foobar', None, {1: 10}) == 'foobar| context: {1: 10}'
63+
assert v.validate_test('foobar', None, 'frogspawn') == 'foobar| context: frogspawn'
64+
65+
66+
def test_isinstance(py_and_json: PyAndJson):
67+
def f(input_value, *, validator, context, **kwargs):
68+
if 'error' in context:
69+
raise ValueError('wrong')
70+
return validator(input_value)
71+
72+
v = py_and_json({'type': 'function', 'mode': 'wrap', 'function': f, 'schema': 'str'})
73+
74+
assert v.validate_python('foobar', None, {}) == 'foobar'
75+
76+
# internal error!, use generic bit of error message to match both cpython and pypy
77+
with pytest.raises(TypeError, match='is not iterable'):
78+
v.validate_test('foobar')
79+
80+
with pytest.raises(TypeError, match='is not iterable'):
81+
v.isinstance_test('foobar')
82+
83+
with pytest.raises(ValidationError, match=r'Value error, wrong \[kind=value_error,'):
84+
v.validate_test('foobar', None, {'error'})
85+
86+
assert v.isinstance_test('foobar', None, {}) is True
87+
88+
with pytest.raises(TypeError, match='is not iterable'):
89+
v.isinstance_test('foobar')
90+
91+
assert v.isinstance_test('foobar', None, {'error'}) is False

0 commit comments

Comments
 (0)