From e1396e45576eb7b33254c96774043d4dfcd7a6db Mon Sep 17 00:00:00 2001 From: PrettyWood Date: Mon, 29 Mar 2021 00:12:19 +0200 Subject: [PATCH] fix coverage --- pydantic/dataclasses.py | 4 +- setup.cfg | 5 +- tests/mypy/outputs/plugin-fail-strict.txt | 2 +- tests/mypy/outputs/plugin-fail.txt | 2 +- tests/test_dataclasses.py | 93 ++++++++++++++++++++++- 5 files changed, 96 insertions(+), 10 deletions(-) diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py index 4aecc7511eb..72e29be4761 100644 --- a/pydantic/dataclasses.py +++ b/pydantic/dataclasses.py @@ -190,8 +190,8 @@ def new_post_init( post_init(self, *args, **kwargs) if __pydantic_run_validation__: self.__pydantic_validate_values__() - if hasattr(self, '__post_init_post_parse__'): - self.__post_init_post_parse__(*args, **kwargs) + if hasattr(self, '__post_init_post_parse__'): + self.__post_init_post_parse__(*args, **kwargs) setattr(dc_cls, '__init__', new_init) setattr(dc_cls, '__post_init__', new_post_init) diff --git a/setup.cfg b/setup.cfg index 0544fcc309a..4a44ce11e93 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,8 @@ exclude_lines = raise NotImplemented if TYPE_CHECKING: @overload -omit = wrapper.py +omit = + pydantic/wrapper.py [coverage:paths] source = @@ -68,7 +69,7 @@ disallow_untyped_defs = True ;no_implicit_optional = True ;warn_return_any = True -exclude = wrapper.py +exclude = pydantic/wrapper.py [mypy-email_validator] ignore_missing_imports = true diff --git a/tests/mypy/outputs/plugin-fail-strict.txt b/tests/mypy/outputs/plugin-fail-strict.txt index d2d015863b4..c7e090a7317 100644 --- a/tests/mypy/outputs/plugin-fail-strict.txt +++ b/tests/mypy/outputs/plugin-fail-strict.txt @@ -34,6 +34,6 @@ 189: error: Name 'Missing' is not defined [name-defined] 197: error: No overload variant of "dataclass" matches argument type "Dict[, ]" [call-overload] 197: note: Possible overload variant: -197: note: def dataclass(*, init: bool = ..., repr: bool = ..., eq: bool = ..., order: bool = ..., unsafe_hash: bool = ..., frozen: bool = ..., config: Optional[Type[Any]] = ...) -> Callable[[Type[Any]], DataclassProxy] +197: note: def dataclass(*, init: bool = ..., repr: bool = ..., eq: bool = ..., order: bool = ..., unsafe_hash: bool = ..., frozen: bool = ..., config: Optional[Type[Any]] = ..., validate_on_init: Optional[bool] = ...) -> Callable[[Type[Any]], DataclassProxy] 197: note: <1 more non-matching overload not shown> 219: error: Property "y" defined in "FrozenModel" is read-only [misc] \ No newline at end of file diff --git a/tests/mypy/outputs/plugin-fail.txt b/tests/mypy/outputs/plugin-fail.txt index f80c231405e..b0c865db047 100644 --- a/tests/mypy/outputs/plugin-fail.txt +++ b/tests/mypy/outputs/plugin-fail.txt @@ -23,6 +23,6 @@ 189: error: Name 'Missing' is not defined [name-defined] 197: error: No overload variant of "dataclass" matches argument type "Dict[, ]" [call-overload] 197: note: Possible overload variant: -197: note: def dataclass(*, init: bool = ..., repr: bool = ..., eq: bool = ..., order: bool = ..., unsafe_hash: bool = ..., frozen: bool = ..., config: Optional[Type[Any]] = ...) -> Callable[[Type[Any]], DataclassProxy] +197: note: def dataclass(*, init: bool = ..., repr: bool = ..., eq: bool = ..., order: bool = ..., unsafe_hash: bool = ..., frozen: bool = ..., config: Optional[Type[Any]] = ..., validate_on_init: Optional[bool] = ...) -> Callable[[Type[Any]], DataclassProxy] 197: note: <1 more non-matching overload not shown> 219: error: Property "y" defined in "FrozenModel" is read-only [misc] \ No newline at end of file diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py index 48de30c5cb3..23baa111dc5 100644 --- a/tests/test_dataclasses.py +++ b/tests/test_dataclasses.py @@ -11,6 +11,9 @@ import pydantic from pydantic import BaseModel, ValidationError, validator +only_36 = pytest.mark.skipif(sys.version_info[:2] != (3, 6), reason='testing 3.6 behaviour only') +skip_pre_37 = pytest.mark.skipif(sys.version_info < (3, 7), reason='testing >= 3.7 behaviour only') + def test_simple(): @pydantic.dataclasses.dataclass @@ -159,6 +162,23 @@ def __post_init__(self): assert post_init_called +@skip_pre_37 +def test_post_init_validation(): + @dataclasses.dataclass + class DC: + a: int + + def __post_init__(self): + self.a *= 2 + + def __post_init_post_parse__(self): + self.a += 1 + + PydanticDC = pydantic.dataclasses.dataclass(DC) + assert DC(a='2').a == '22' + assert PydanticDC(a='2').a == 23 + + def test_post_init_inheritance_chain(): parent_post_init_called = False post_init_called = False @@ -651,6 +671,7 @@ class MyDataclass: MyDataclass(v=None) +@skip_pre_37 def test_override_builtin_dataclass(): @dataclasses.dataclass class File: @@ -660,26 +681,86 @@ class File: content: Optional[bytes] = None FileChecked = pydantic.dataclasses.dataclass(File) - f = FileChecked(hash='xxx', name=b'whatever.txt', size='456') - assert f.name == 'whatever.txt' - assert f.size == 456 + + f1 = File(hash='xxx', name=b'whatever.txt', size='456') + f2 = FileChecked(hash='xxx', name=b'whatever.txt', size='456') + + assert f1.name == b'whatever.txt' + assert f1.size == '456' + + assert f2.name == 'whatever.txt' + assert f2.size == 456 + + assert isinstance(f2, File) + + with pytest.raises(ValidationError) as e: + FileChecked(hash=[1], name='name', size=3) + assert e.value.errors() == [{'loc': ('hash',), 'msg': 'str type expected', 'type': 'type_error.str'}] + + +@only_36 +def test_override_builtin_dataclass__3_6(): + @dataclasses.dataclass + class File: + hash: str + name: Optional[str] + size: int + content: Optional[bytes] = None + + with pytest.warns( + UserWarning, match="Stdlib dataclass 'File' has been modified and validates everything by default" + ): + FileChecked = pydantic.dataclasses.dataclass(File) + + f1 = File(hash='xxx', name=b'whatever.txt', size='456') + f2 = FileChecked(hash='xxx', name=b'whatever.txt', size='456') + + assert f1.name == f2.name == 'whatever.txt' + assert f1.size == f2.size == 456 with pytest.raises(ValidationError) as e: FileChecked(hash=[1], name='name', size=3) assert e.value.errors() == [{'loc': ('hash',), 'msg': 'str type expected', 'type': 'type_error.str'}] +@only_36 +def test_override_builtin_dataclass__3_6_no_overwrite(): + @dataclasses.dataclass + class File: + hash: str + name: Optional[str] + size: int + content: Optional[bytes] = None + + FileChecked = pydantic.dataclasses.dataclass(File, validate_on_init=False) + + f1 = File(hash='xxx', name=b'whatever.txt', size='456') + f2 = FileChecked(hash='xxx', name=b'whatever.txt', size='456') + + assert f1.name == f2.name == b'whatever.txt' + assert f1.size == f2.size == '456' + + with pytest.raises(ValidationError) as e: + FileChecked(hash=[1], name='name', size=3, __pydantic_run_validation__=True) + assert e.value.errors() == [{'loc': ('hash',), 'msg': 'str type expected', 'type': 'type_error.str'}] + + +@skip_pre_37 def test_override_builtin_dataclass_2(): @dataclasses.dataclass class Meta: modified_date: Optional[datetime] seen_count: int + Meta(modified_date='not-validated', seen_count=0) + @pydantic.dataclasses.dataclass @dataclasses.dataclass class File(Meta): filename: str + Meta(modified_date='still-not-validated', seen_count=0) + f = File(filename=b'thefilename', modified_date='2020-01-01T00:00', seen_count='7') assert f.filename == 'thefilename' assert f.modified_date == datetime(2020, 1, 1, 0, 0) @@ -736,7 +817,10 @@ class File: filename: str meta: Meta - FileChecked = pydantic.dataclasses.dataclass(File) + if sys.version_info[:2] == (3, 6): + FileChecked = pydantic.dataclasses.dataclass(File, validate_on_init=False) + else: + FileChecked = pydantic.dataclasses.dataclass(File) assert FileChecked.__pydantic_model__.schema() == { 'definitions': { 'Meta': { @@ -1020,6 +1104,7 @@ class Thing(Base): class ValidatedThing(Base): y: str = dataclasses.field(default_factory=str) + assert Thing(x='hi').y == '' assert ValidatedThing(x='hi').y == ''