Skip to content
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

Pydantic dataclasses break __eq__ method generated by stdlib dataclasses when upgrading from v1.6.1 to v1.7.2 #2162

Closed
3 tasks done
jtrh opened this issue Nov 30, 2020 · 2 comments · Fixed by #2557
Closed
3 tasks done
Labels
bug V1 Bug related to Pydantic V1.X

Comments

@jtrh
Copy link

jtrh commented Nov 30, 2020

Checks

  • I added a descriptive title to this issue
  • I have searched (google, github) for similar issues and couldn't find anything
  • I have read and followed the docs and still think this is a bug

Bug

Output of python -c "import pydantic.utils; print(pydantic.utils.version_info())":

             pydantic version: 1.7.2
            pydantic compiled: True
                 install path: /home/jtrh/.virtualenvs/tmp-efd3764f8d079be/lib/python3.8/site-packages/pydantic
               python version: 3.8.5 (default, Jul 28 2020, 12:59:40)  [GCC 9.3.0]
                     platform: Linux-5.4.0-54-generic-x86_64-with-glibc2.29
     optional deps. installed: []
import dataclasses
import unittest

import pydantic
import pydantic.dataclasses


class PydanticConfig:
    arbitrary_types_allowed = True


@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, config=PydanticConfig)
class PydanticBar:
    c: PydanticFoo


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


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


class DataClassTest(unittest.TestCase):
    def _assert(self, foo, bar):
        self.assertEqual(dataclasses.asdict(foo), dataclasses.asdict(bar.c))
        self.assertEqual(dataclasses.astuple(foo), dataclasses.astuple(bar.c))
        self.assertEqual(foo, bar.c)

    def test_stdlib_only(self):
        foo = StdLibFoo(a='Foo', b=1)
        bar = StdLibBar(c=foo)

        self._assert(foo, bar)

    def test_pydantic_only(self):
        foo = PydanticFoo(a='Foo', b=1)
        bar = PydanticBar(c=foo)

        self._assert(foo, bar)

    def test_pydantic_and_stdlib(self):
        foo = PydanticFoo(a='Foo', b=1)
        bar = StdLibBaz(c=foo)

        self._assert(foo, bar)


    def test_stdlib_and_pydantic(self):
        foo = StdLibFoo(a='Foo', b=1)
        bar = PydanticBaz(c=foo)

        self._assert(foo, bar)


if __name__ == '__main__':
    print(pydantic.version.version_info()); print(); unittest.main(verbosity=2)

Output with pydantic==1.7.2:

             pydantic version: 1.7.2
            pydantic compiled: True
                 install path: /home/jtrh/.virtualenvs/tmp-efd3764f8d079be/lib/python3.8/site-packages/pydantic
               python version: 3.8.5 (default, Jul 28 2020, 12:59:40)  [GCC 9.3.0]
                     platform: Linux-5.4.0-54-generic-x86_64-with-glibc2.29
     optional deps. installed: []

test_pydantic_and_stdlib (__main__.DataClassTest) ... ok
test_pydantic_only (__main__.DataClassTest) ... ok
test_stdlib_and_pydantic (__main__.DataClassTest) ... FAIL
test_stdlib_only (__main__.DataClassTest) ... ok

======================================================================
FAIL: test_stdlib_and_pydantic (__main__.DataClassTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "pydantic_dataclass_equality_error_tests_2020_11_30.py", line 73, in test_stdlib_and_pydantic
    self._assert(foo, bar)
  File "pydantic_dataclass_equality_error_tests_2020_11_30.py", line 48, in _assert
    self.assertEqual(foo, bar.c)
AssertionError: StdLibFoo(a='Foo', b=1) != StdLibFoo(a='Foo', b=1)

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=1)

Output with pydantic==1.6.1:

             pydantic version: 1.6.1
            pydantic compiled: True
                 install path: /home/jtrh/.virtualenvs/tmp-75057fed9773783/lib/python3.8/site-packages/pydantic
               python version: 3.8.5 (default, Jul 28 2020, 12:59:40)  [GCC 9.3.0]
                     platform: Linux-5.4.0-54-generic-x86_64-with-glibc2.29
     optional deps. installed: []

test_pydantic_and_stdlib (__main__.DataClassTest) ... ok
test_pydantic_only (__main__.DataClassTest) ... ok
test_stdlib_and_pydantic (__main__.DataClassTest) ... ok
test_stdlib_only (__main__.DataClassTest) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

I think this issue may be related to the automatic conversion of standard library dataclasses to Pydantic dataclasses. The __eq__ method generated by the standard library checks that both instances are of the same class (see dataclasses._cmp_fn and dataclasses._process_class), which is true in 1.6.1 (assert foo.__class__ is bar.c.__class__ succeeds), but not in 1.7.2 (assert foo.__class__ is bar.c.__class__ fails, but assert issubclass(bar.c.__class__, foo.__class__) succeeds).

@jtrh jtrh added the bug V1 Bug related to Pydantic V1.X label Nov 30, 2020
@PrettyWood
Copy link
Member

Hi @jtrh and thanks for reporting!
I guess we could do something like PrettyWood@543c0e4 to keep the original __eq__. Maybe add a fallback on the default __eq__.

PrettyWood added a commit to PrettyWood/pydantic that referenced this issue Dec 7, 2020
… can still equal its stdlib dataclass equivalent

closes pydantic#2162
PrettyWood added a commit to PrettyWood/pydantic that referenced this issue Dec 8, 2020
@samuelcolvin
Copy link
Member

samuelcolvin commented Jan 1, 2021

see my comments on this matter on #2182. @jtrh I'd love your feedback.

jtrobles-cdd added a commit to cordada/lib-cl-sii-python that referenced this issue Aug 31, 2022
jtrobles-cdd added a commit to cordada/lib-cl-sii-python that referenced this issue Aug 31, 2022
Changelog:

- 1.10.1 (2022-08-31):
  https://github.com/pydantic/pydantic/blob/v1.10.1/HISTORY.md#v1101-2022-08-31
- 1.10.0 (2022-08-30):
  https://github.com/pydantic/pydantic/blob/v1.10.0/HISTORY.md#v1100-2022-08-30
- …
- 1.9.0 (2021-12-31):
  https://github.com/pydantic/pydantic/blob/v1.10.0/HISTORY.md#v190-2021-12-31
- 1.8.2 (2021-05-11):
  https://pydantic-docs.helpmanual.io/changelog/#v182-2021-05-11
- 1.8.1 (2021-03-03):
  https://pydantic-docs.helpmanual.io/changelog/#v181-2021-03-03
- 1.8 (2021-02-26):
  https://pydantic-docs.helpmanual.io/changelog/#v18-2021-02-26
- …
- 1.7 (2020-10-26):
  https://pydantic-docs.helpmanual.io/changelog/#v17-2020-10-26

Code diff: pydantic/pydantic@v1.6.2...v1.10.1

---

Add version exclusions for Pydantic 1.7.*, 1.8.*, and 1.9.* to
Setuptools configuration (`setup.py`) because those versions have a
buggy `dataclasses` implementation
(see GitHub issue pydantic/pydantic#2162,
created by @jtrh).
jtrobles-cdd added a commit to cordada/lib-cl-sii-python that referenced this issue Aug 31, 2022
Changelog:

- 1.10.1 (2022-08-31):
  https://github.com/pydantic/pydantic/blob/v1.10.1/HISTORY.md#v1101-2022-08-31
- 1.10.0 (2022-08-30):
  https://github.com/pydantic/pydantic/blob/v1.10.0/HISTORY.md#v1100-2022-08-30
- …
- 1.9.0 (2021-12-31):
  https://github.com/pydantic/pydantic/blob/v1.10.0/HISTORY.md#v190-2021-12-31
- 1.8.2 (2021-05-11):
  https://pydantic-docs.helpmanual.io/changelog/#v182-2021-05-11
- 1.8.1 (2021-03-03):
  https://pydantic-docs.helpmanual.io/changelog/#v181-2021-03-03
- 1.8 (2021-02-26):
  https://pydantic-docs.helpmanual.io/changelog/#v18-2021-02-26
- …
- 1.7 (2020-10-26):
  https://pydantic-docs.helpmanual.io/changelog/#v17-2020-10-26

Code diff: pydantic/pydantic@v1.6.2...v1.10.1

---

Add version exclusions for Pydantic 1.7.*, 1.8.*, and 1.9.* to
Setuptools configuration (`setup.py`) because those versions have a
buggy `dataclasses` implementation
(see GitHub issue pydantic/pydantic#2162,
created by @jtrh).

```python
>>> import packaging.specifiers
>>> pydantic_specifier_set = packaging.specifiers.SpecifierSet('>=1.6.2,!=1.7.*,!=1.8.*,!=1.9.*')
>>> pydantic_specifier_set.contains('1.6.2')
True
>>> pydantic_specifier_set.contains('1.7')
False
>>> pydantic_specifier_set.contains('1.7.0')
False
>>> pydantic_specifier_set.contains('1.8.1')
False
>>> pydantic_specifier_set.contains('1.9.2')
False
>>> pydantic_specifier_set.contains('1.10.0a1')
False
>>> pydantic_specifier_set.contains('1.10.0b1')
False
>>> pydantic_specifier_set.contains('1.10.0')
True
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V1 Bug related to Pydantic V1.X
Projects
None yet
3 participants