Skip to content

Commit

Permalink
fix: stdlib dataclass converted into pydantic dataclass still equals …
Browse files Browse the repository at this point in the history
…its stdlib dataclass equivalent

closes #2162
  • Loading branch information
PrettyWood committed Dec 8, 2020
1 parent de0657e commit a949c0a
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 1 deletion.
1 change: 1 addition & 0 deletions changes/2162-PrettyWood.md
@@ -0,0 +1 @@
fix: stdlib `dataclass` converted into _pydantic_ `dataclass` still equals its stdlib `dataclass` equivalent
10 changes: 10 additions & 0 deletions pydantic/dataclasses.py
Expand Up @@ -110,6 +110,15 @@ def _pydantic_post_init(self: 'Dataclass', *initvars: Any) -> None:
if post_init_post_parse is not None:
post_init_post_parse(self, *initvars)

def _converted_pydantic_dataclass_eq(self: 'Dataclass', other: Any) -> bool:
"""
To still support equality between a stdlib dataclass and its converted pydantic dataclass
we add a custom `__eq__` method on the converted pydantic dataclass
"""
stdlib_dc = self.__class__.__bases__[0]
dc_fields = {k: v for k, v in self.__dict__.items() if k != '__initialised__'}
return stdlib_dc(**dc_fields) == other

# If the class is already a dataclass, __post_init__ will not be called automatically
# so no validation will be added.
# We hence create dynamically a new dataclass:
Expand All @@ -129,6 +138,7 @@ def _pydantic_post_init(self: 'Dataclass', *initvars: Any) -> None:
(_cls,),
{
'__annotations__': _cls.__annotations__,
'__eq__': _converted_pydantic_dataclass_eq,
'__post_init__': _pydantic_post_init,
# attrs for pickle to find this class
'__module__': __name__,
Expand Down
46 changes: 45 additions & 1 deletion tests/test_dataclasses.py
Expand Up @@ -3,7 +3,7 @@
from collections.abc import Hashable
from datetime import datetime
from pathlib import Path
from typing import ClassVar, Dict, FrozenSet, List, Optional
from typing import Any, ClassVar, Dict, FrozenSet, Generator, List, Optional, Tuple

import pytest

Expand Down Expand Up @@ -833,3 +833,47 @@ class Config:
# ensure the restored dataclass is still a pydantic dataclass
with pytest.raises(ValidationError, match='value\n +value is not a valid integer'):
restored_obj.dataclass.value = 'value of a wrong type'


def gen_dataclasses_tuple() -> Generator[Tuple[Any, Any, bool], None, None]:
@dataclasses.dataclass(frozen=True)
class StdLibFoo:
a: str
b: int

@pydantic.dataclasses.dataclass(frozen=True)
class PydanticFoo:
a: str
b: int

@dataclasses.dataclass(frozen=True)
class StdLibBar:
c: StdLibFoo

@pydantic.dataclasses.dataclass(frozen=True)
class PydanticBar:
c: PydanticFoo

@dataclasses.dataclass(frozen=True)
class StdLibBaz:
c: PydanticFoo

@pydantic.dataclasses.dataclass(frozen=True)
class PydanticBaz:
c: StdLibFoo

yield StdLibFoo, StdLibBar, True
yield PydanticFoo, PydanticBar, True
yield PydanticFoo, StdLibBaz, True
yield StdLibFoo, PydanticBaz, False


@pytest.mark.parametrize('Dataclass1,Dataclass2,is_identical', gen_dataclasses_tuple())
def test_dataclass_equality(Dataclass1, Dataclass2, is_identical):
foo = Dataclass1(a='Foo', b=1)
bar = Dataclass2(c=foo)

assert dataclasses.asdict(foo) == dataclasses.asdict(bar.c)
assert dataclasses.astuple(foo) == dataclasses.astuple(bar.c)
assert (foo is bar.c) is is_identical
assert foo == bar.c

0 comments on commit a949c0a

Please sign in to comment.