8000 Add error messages for json by Kludex · Pull Request #637 · pydantic/pydantic-core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add error messages for json #637

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/errors/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,13 @@ impl ErrorType {
pub fn message_template_json(&self) -> &'static str {
match self {
Self::NoneRequired => "Input should be null",
Self::ListType => "Input should be a valid array",
Self::DataclassType { .. } => "Input should be an object",
Self::ListType | Self::TupleType | Self::IterableType | Self::SetType | Self::FrozenSetType => {
"Input should be a valid array"
}
Self::DictAttributesType | Self::DictType | Self::DataclassType { .. } => "Input should be an object",
Self::TimeDeltaType => "Input should be a valid duration",
Self::TimeDeltaParsing { .. } => "Input should be a valid duration, {error}",
Self::ArgumentsType => "Arguments must be an array or an object",
_ => self.message_template_python(),
}
}
Expand Down
29 changes: 29 additions & 0 deletions tests/validators/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1458,3 +1458,32 @@ class SubModel(Model):
assert dc.x == 1
assert dc.x2 == 2
assert dataclasses.asdict(dc) == {'x': 1, 'x2': 2}


def test_dataclass_json():
schema = core_schema.dataclass_schema(
FooDataclass,
core_schema.dataclass_args_schema(
'FooDataclass',
[
core_schema.dataclass_field(name='a', schema=core_schema.str_schema()),
core_schema.dataclass_field(name='b', schema=core_schema.bool_schema()),
],
),
['a', 'b'],
)
v = SchemaValidator(schema)
assert v.validate_json('{"a": "hello", "b": true}') == FooDataclass(a='hello', b=True)

with pytest.raises(ValidationError) as exc_info:
v.validate_json('["a", "b"]')

assert exc_info.value.errors(include_url=False) == [
{
'ctx': {'dataclass_name': 'FooDataclass'},
'input': ['a', 'b'],
'loc': (),
'msg': 'Input should be an object',
'type': 'dataclass_type',
}
]
12 changes: 11 additions & 1 deletion tests/validators/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def test_dict(py_and_json: PyAndJson):
v = py_and_json({'type': 'dict', 'strict': True, 'keys_schema': {'type': 'int'}, 'values_schema': {'type': 'int'}})
assert v.validate_test({'1': 2, '3': 4}) == {1: 2, 3: 4}
assert v.validate_test({}) == {}
with pytest.raises(ValidationError, match='Input should be a valid dictionary'):
with pytest.raises(ValidationError, match=re.escape('[type=dict_type, input_value=[], input_type=list]')):
v.validate_test([])


Expand Down Expand Up @@ -225,3 +225,13 @@ def test_dict_length_constraints(kwargs: Dict[str, Any], input_value, expected):
v.validate_python(input_value)
else:
assert v.validate_python(input_value) == expected


def test_json_dict():
v = SchemaValidator({'type': 'dict', 'keys_schema': {'type': 'int'}, 'values_schema': {'type': 'int'}})
assert v.validate_json('{"1": 2, "3": 4}') == {1: 2, 3: 4}
with pytest.raises(ValidationError) as exc_info:
v.validate_json('1')
assert exc_info.value.errors(include_url=False) == [
{'type': 'dict_type', 'loc': (), 'msg': 'Input should be an object', 'input': 1}
]
10 changes: 5 additions & 5 deletions tests/validators/test_frozenset.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ def test_no_copy():
'input_value,expected',
[
([1, 2.5, '3'], {1, 2.5, '3'}),
('foo', Err('Input should be a valid frozenset')),
(1, Err('Input should be a valid frozenset')),
(1.0, Err('Input should be a valid frozenset')),
(False, Err('Input should be a valid frozenset')),
('foo', Err("[type=frozen_set_type, input_value='foo', input_type=str]")),
(1, Err('[type=frozen_set_type, input_value=1, input_type=int]')),
(1.0, Err('[type=frozen_set_type, input_value=1.0, input_type=float]')),
(False, Err('[type=frozen_set_type, input_value=False, input_type=bool]')),
],
)
def test_frozenset_no_validators_both(py_and_json: PyAndJson, input_value, expected):
Expand Down Expand Up @@ -237,7 +237,7 @@ def test_union_frozenset_int_frozenset_str(input_value, expected):

def test_frozenset_as_dict_keys(py_and_json: PyAndJson):
v = py_and_json({'type': 'dict', 'keys_schema': {'type': 'frozenset'}, 'values_schema': {'type': 'int'}})
with pytest.raises(ValidationError, match=re.escape('Input should be a valid frozenset')):
with pytest.raises(ValidationError, match=re.escape("[type=int_parsing, input_value='bar', input_type=str]")):
v.validate_test({'foo': 'bar'})


Expand Down
14 changes: 4 additions & 10 deletions tests/validators/test_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,8 @@
([1, 2, '3'], [1, 2, 3]),
({1: 2, 3: 4}, [1, 3]),
('123', [1, 2, 3]),
(5, Err('Input should be iterable [type=iterable_type, input_value=5, input_type=int]')),
(
[1, 'wrong'],
Err(
'Input should be a valid integer, unable to parse string as an integer '
"[type=int_parsing, input_value='wrong', input_type=str]"
),
),
(5, Err('[type=iterable_type, input_value=5, input_type=int]')),
([1, 'wrong'], Err("[type=int_parsing, input_value='wrong', input_type=str]")),
],
ids=repr,
)
Expand All @@ -46,7 +40,7 @@ def test_generator_json_int(py_and_json: PyAndJson, input_value, expected):
)
def test_generator_json_hide_input(py_and_json: PyAndJson, config, input_str):
v = py_and_json({'type': 'generator', 'items_schema': {'type': 'int'}}, config)
with pytest.raises(ValidationError, match=re.escape(f'Input should be iterable [{input_str}]')):
with pytest.raises(ValidationError, match=re.escape(f'[{input_str}]')):
list(v.validate_test(5))


Expand All @@ -57,7 +51,7 @@ def test_generator_json_hide_input(py_and_json: PyAndJson, config, input_str):
([1, 2, '3'], [1, 2, '3']),
({'1': 2, '3': 4}, ['1', '3']),
('123', ['1', '2', '3']),
(5, Err('Input should be iterable [type=iterable_type, input_value=5, input_type=int]')),
(5, Err('[type=iterable_type, input_value=5, input_type=int]')),
([1, 'wrong'], [1, 'wrong']),
],
ids=repr,
Expand Down
3 changes: 3 additions & 0 deletions tests/validators/test_model_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,9 @@ def test_from_attributes_type_error():
}
]

with pytest.raises(ValidationError) as exc_info:
v.validate_json('123')


def test_from_attributes_by_name():
v = SchemaValidator(
Expand Down
14 changes: 7 additions & 7 deletions tests/validators/test_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
([1, 2, 3], {1, 2, 3}),
([1, 2, '3'], {1, 2, 3}),
([1, 2, 3, 2, 3], {1, 2, 3}),
(5, Err('Input should be a valid set [type=set_type, input_value=5, input_type=int]')),
(5, Err('[type=set_type, input_value=5, input_type=int]')),
],
)
def test_set_ints_both(py_and_json: PyAndJson, input_value, expected):
Expand All @@ -38,16 +38,16 @@ def test_set_no_validators_both(py_and_json: PyAndJson, input_value, expected):
'input_value,expected',
[
([1, 2.5, '3'], {1, 2.5, '3'}),
('foo', Err('Input should be a valid set')),
(1, Err('Input should be a valid set')),
(1.0, Err('Input should be a valid set')),
(False, Err('Input should be a valid set')),
('foo', Err('[type=set_type, input_value=foo, input_type=str]')),
(1, Err('[type=set_type, input_value=1.0, input_type=float]')),
(1.0, Err('[type=set_type, input_value=1.0, input_type=float]')),
(False, Err('[type=set_type, input_value=False, input_type=bool]')),
],
)
def test_frozenset_no_validators_both(py_and_json: PyAndJson, input_value, expected):
v = py_and_json({'type': 'set'})
if isinstance(expected, Err):
with pytest.raises(ValidationError, match=re.escape(expected.message)):
with pytest.raises(ValidationError, match=expected.message):
v.validate_test(input_value)
else:
assert v.validate_test(input_value) == expected
Expand Down Expand Up @@ -215,7 +215,7 @@ def test_union_set_int_set_str(input_value, expected):

def test_set_as_dict_keys(py_and_json: PyAndJson):
v = py_and_json({'type': 'dict', 'keys_schema': {'type': 'set'}, 'values_schema': {'type': 'int'}})
with pytest.raises(ValidationError, match=re.escape('Input should be a valid set')):
with pytest.raises(ValidationError, match=re.escape("[type=set_type, input_value='foo', input_type=str]")):
v.validate_test({'foo': 'bar'})


Expand Down
10 changes: 9 additions & 1 deletion tests/validators/test_tagged_union.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from enum import Enum

import pytest
from dirty_equals import IsAnyStr

from pydantic_core import SchemaError, SchemaValidator, ValidationError

Expand Down Expand Up @@ -71,7 +72,14 @@
'not a dict',
Err(
'dict_type',
[{'type': 'dict_type', 'loc': (), 'msg': 'Input should be a valid dictionary', 'input': 'not a dict'}],
[
{
'type': 'dict_type',
'loc': (),
'msg': IsAnyStr(regex='Input should be (a valid dictionary|an object)'),
'input': 'not a dict',
}
],
),
),
],
Expand Down
18 changes: 6 additions & 12 deletions tests/validators/test_timedelta.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ def test_timedelta(input_value, expected):
'input_value,expected',
[
('"P0Y0M3D2WT1H2M3.5S"', timedelta(days=3, weeks=2, hours=1, minutes=2, seconds=3, milliseconds=500)),
('"errordata"', Err('Input should be a valid timedelta, invalid digit in duration [type=time_delta_parsing')),
('true', Err('Input should be a valid timedelta [type=time_delta_type')),
('"errordata"', Err('Input should be a valid duration, invalid digit in duration [type=time_delta_parsing')),
('true', Err('Input should be a valid duration [type=time_delta_type')),
('3601', timedelta(hours=1, seconds=1)),
('3601.123456', timedelta(hours=1, seconds=1, microseconds=123456)),
('-3601', timedelta(hours=-2, seconds=3599)),
Expand Down Expand Up @@ -106,8 +106,8 @@ def test_timedelta_strict(input_value, expected):
'input_value,expected',
[
('"P0Y0M3D2WT1H2M3.5S"', timedelta(days=3, weeks=2, hours=1, minutes=2, seconds=3, milliseconds=500)),
('"12345"', Err('Input should be a valid timedelta')),
('true', Err('Input should be a valid timedelta [type=time_delta_type')),
('"12345"', Err('Input should be a valid duration')),
('true', Err('Input should be a valid duration [type=time_delta_type')),
],
)
def test_timedelta_strict_json(input_value, expected):
Expand Down Expand Up @@ -187,21 +187,15 @@ def test_dict_key(py_and_json: PyAndJson):
v = py_and_json({'type': 'dict', 'keys_schema': {'type': 'timedelta'}, 'values_schema': {'type': 'int'}})
assert v.validate_test({'P2DT1H': 2, 'P2DT2H& E962 #39;: 4}) == {timedelta(days=2, hours=1): 2, timedelta(days=2, hours=2): 4}

with pytest.raises(
ValidationError,
match=re.escape('Input should be a valid timedelta, invalid digit in duration [type=time_delta_parsing'),
):
with pytest.raises(ValidationError, match=re.escape('[type=time_delta_parsing')):
v.validate_test({'errordata': 2})


def test_dict_value(py_and_json: PyAndJson):
v = py_and_json({'type': 'dict', 'keys_schema': {'type': 'int'}, 'values_schema': {'type': 'timedelta'}})
assert v.validate_test({2: 'P2DT1H', 4: 'P2DT2H'}) == {2: timedelta(days=2, hours=1), 4: timedelta(days=2, hours=2)}

with pytest.raises(
ValidationError,
match=re.escape('Input should be a valid timedelta, invalid digit in duration [type=time_delta_parsing'),
):
with pytest.raises(ValidationError, match=re.escape('[type=time_delta_parsing')):
v.validate_test({4: 'errordata'})


Expand Down
9 changes: 2 additions & 7 deletions tests/validators/test_tuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,13 @@
'mode,items,input_value,expected',
[
('variable', {'type': 'int'}, [1, 2, 3], (1, 2, 3)),
(
'variable',
{'type': 'int'},
1,
Err('Input should be a valid tuple [type=tuple_type, input_value=1, input_type=int]'),
),
('variable', {'type': 'int'}, 1, Err('[type=tuple_type, input_value=1, input_type=int]')),
('positional', [{'type': 'int'}, {'type': 'int'}, {'type': 'int'}], [1, 2, '3'], (1, 2, 3)),
(
'positional',
[{'type': 'int'}, {'type': 'int'}, {'type': 'int'}],
5,
Err('Input should be a valid tuple [type=tuple_type, input_value=5, input_type=int]'),
Err('[type=tuple_type, input_value=5, input_type=int]'),
),
],
ids=repr,
Expand Down
0