From 4f19d2b94a9d9411d09bb38301efee0610b5e85e Mon Sep 17 00:00:00 2001 From: Nikita Grishko Date: Wed, 13 Jun 2018 21:11:01 +0300 Subject: [PATCH] Strict validation of `list`, `set` and `tuple` (#86) (#200) * Strict validation of `list`, `set` and `tuple` (#86) * review fixes --- HISTORY.rst | 4 +++ pydantic/validators.py | 16 ++++++---- tests/test_types.py | 69 +++++++++++++++++++++++++++++++++--------- 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index b8125b0..d3edd1e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,10 @@ History ------- +v0.10.1 (2018-xx-xx) +.................... +* make ``list``, ``tuple`` and ``set`` types stricter #86 + v0.10.0 (2018-06-11) .................... * add ``Config.allow_population_by_alias`` #160, thanks @bendemaree diff --git a/pydantic/validators.py b/pydantic/validators.py index c51750f..4d4fa25 100644 --- a/pydantic/validators.py +++ b/pydantic/validators.py @@ -1,3 +1,4 @@ +import inspect from collections import OrderedDict from datetime import date, datetime, time, timedelta from decimal import Decimal, DecimalException @@ -133,25 +134,28 @@ def dict_validator(v) -> dict: def list_validator(v) -> list: if isinstance(v, list): return v - - with change_exception(errors.ListError, TypeError): + elif isinstance(v, (tuple, set)) or inspect.isgenerator(v): return list(v) + else: + raise errors.ListError() def tuple_validator(v) -> tuple: if isinstance(v, tuple): return v - - with change_exception(errors.TupleError, TypeError): + elif isinstance(v, (list, set)) or inspect.isgenerator(v): return tuple(v) + else: + raise errors.TupleError() def set_validator(v) -> set: if isinstance(v, set): return v - - with change_exception(errors.SetError, TypeError): + elif isinstance(v, (list, tuple)) or inspect.isgenerator(v): return set(v) + else: + raise errors.SetError() def enum_validator(v, field, config, **kwargs) -> Enum: diff --git a/tests/test_types.py b/tests/test_types.py index 36dded8..320476f 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -428,16 +428,29 @@ def test_dict(): ] -def test_list(): +@pytest.mark.parametrize('value,result', ( + ([1, 2, '3'], [1, 2, '3']), + ((1, 2, '3'), [1, 2, '3']), + ({1, 2, '3'}, [1, 2, '3']), + ((i**2 for i in range(5)), [0, 1, 4, 9, 16]), +)) +def test_list_success(value, result): class Model(BaseModel): v: list - assert Model(v=[1, 2, '3']).v == [1, 2, '3'] - assert Model(v='xyz').v == ['x', 'y', 'z'] - assert Model(v=(i**2 for i in range(5))).v == [0, 1, 4, 9, 16] + assert Model(v=value).v == result + + +@pytest.mark.parametrize('value', ( + 123, + '123', +)) +def test_list_fails(value): + class Model(BaseModel): + v: list with pytest.raises(ValidationError) as exc_info: - Model(v=1) + Model(v=value) assert exc_info.value.errors() == [ { 'loc': ('v',), @@ -466,16 +479,29 @@ def test_ordered_dict(): ] -def test_tuple(): +@pytest.mark.parametrize('value,result', ( + ([1, 2, '3'], (1, 2, '3')), + ((1, 2, '3'), (1, 2, '3')), + ({1, 2, '3'}, (1, 2, '3')), + ((i**2 for i in range(5)), (0, 1, 4, 9, 16)), +)) +def test_tuple_success(value, result): class Model(BaseModel): v: tuple - assert Model(v=(1, 2, '3')).v == (1, 2, '3') - assert Model(v='xyz').v == ('x', 'y', 'z') - assert Model(v=(i**2 for i in range(5))).v == (0, 1, 4, 9, 16) + assert Model(v=value).v == result + + +@pytest.mark.parametrize('value', ( + 123, + '123', +)) +def test_tuple_fails(value): + class Model(BaseModel): + v: tuple with pytest.raises(ValidationError) as exc_info: - Model(v=1) + Model(v=value) assert exc_info.value.errors() == [ { 'loc': ('v',), @@ -485,16 +511,29 @@ def test_tuple(): ] -def test_set(): +@pytest.mark.parametrize('value,result', ( + ({1, 2, 2, '3'}, {1, 2, '3'}), + ((1, 2, 2, '3'), {1, 2, '3'}), + ([1, 2, 2, '3'], {1, 2, '3'}), + ({i**2 for i in range(5)}, {0, 1, 4, 9, 16}), +)) +def test_set_success(value, result): class Model(BaseModel): v: set - assert Model(v={1, 2, 2, '3'}).v == {1, 2, '3'} - assert Model(v='xyzxyz').v == {'x', 'y', 'z'} - assert Model(v={i**2 for i in range(5)}).v == {0, 1, 4, 9, 16} + assert Model(v=value).v == result + + +@pytest.mark.parametrize('value', ( + 123, + '123', +)) +def test_set_fails(value): + class Model(BaseModel): + v: set with pytest.raises(ValidationError) as exc_info: - Model(v=1) + Model(v=value) assert exc_info.value.errors() == [ { 'loc': ('v',),