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_post_init() reassigns __dict__ and removes properties added to the object, for example in __new__(), breaking integration with SQLAlchemy and other libraries #3043

Closed
DomWeldon opened this issue Jul 29, 2021 · 3 comments · Fixed by #3093
Labels
bug V1 Bug related to Pydantic V1.X

Comments

@DomWeldon
Copy link

DomWeldon commented Jul 29, 2021

Checks

  • [ x] I added a descriptive title to this issue
  • [ x] I have searched (google, github) for similar issues and couldn't find anything
  • [ x] 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.8.2
            pydantic compiled: True
                 install path: /somewhere/on/doms/filesystem/.venv/lib/python3.9/site-packages/pydantic
               python version: 3.9.5 (default, Jun 13 2021, 13:20:20)  [GCC 9.3.0]
                     platform: Linux-5.8.0-59-generic-x86_64-with-glibc2.31
     optional deps. installed: ['typing-extensions']

Related issues: #1089 #2924 .

Summary of Problem

The pydantic dataclass decorator calls object.__setattr__() on self.__dict__ for a dataclass post init, using only the output of validate_model(), which removes any other attributes in the object's namespace, causing many issues including those well described for SQLAlchemy.

Full code example

"""Comparison: pydantic basemodels, dataclasses, and stdlib dataclasses"""
# Standard Library
import dataclasses
import typing

# Third Party Libraries
import pydantic


class StandardClass:
    """Class which modifies instance creation."""
    a: str

    def __new__(cls, *args, **kwargs):
        instance = super().__new__(cls)

        instance._special_property = 1

        return instance


StandardLibDataclass = dataclasses.dataclass(StandardClass)
PydanticDataclass = pydantic.dataclasses.dataclass(StandardClass)

class PydanticModel(pydantic.BaseModel):
    a: str

clases_to_test = {
    "StandardLibDataclass": StandardLibDataclass,
    "PydanticDataclass": PydanticDataclass,
}


test_string = "string"
for name, class_ in clases_to_test.items():
    instance = class_(a=test_string)
    if not hasattr(instance, "_special_property"):
        print(f"⚠️ Something's up with {name}")
        print(instance.__dict__)
    assert instance._special_property == 1
    assert instance.a == test_string
    print(f"✔️ {name}")

Output

✔️ StandardLibDataclass
⚠️ Something's up with PydanticDataclass
{'a': 'string', '__initialised__': True}
Traceback (most recent call last):
  File "/somewhere/on/doms/filesystem/dataclasses_vs_pydantic.py", line 40, in <module>
    assert instance._special_property == 1
AttributeError: 'StandardClass' object has no attribute '_special_property'

As you can see from the example above, this behaviour is particular to pydantic's dataclass implementation, and does not match with standard library dataclasses.

The fix for this could be very simple: simply calling self.__dict__.update() with the validated values, rather than re-assigning __dict__. This fixed the issue in my minimal test case for SQLAlchemy integration, but I am not sure of the wider consequences of the fix yet.

I also realise there may be a philosophy behind the decision to use this approach (and tests with pydantic.BaseModel-derived classes are similar), so appreciate any input if this is the case. However, based on PEP 557 I believe that a dataclass shouldn't alter class/instance behaviour in this way.

I'm working on a PR suggesting this fix, but it's the first time I've checked out this repo and am still getting the dev environment setup.

@DomWeldon DomWeldon added the bug V1 Bug related to Pydantic V1.X label Jul 29, 2021
@DomWeldon
Copy link
Author

Updated the issue above to correct an issue with the example code.

zulrang added a commit to zulrang/pydantic that referenced this issue Aug 13, 2021
…added to the object, for example in __new__(), breaking integration with SQLAlchemy and other libraries pydantic#3043
@zulrang
Copy link
Contributor

zulrang commented Aug 13, 2021

@DomWeldon I've created a PR to fix this and added your test, but full credit should go to you.

@DomWeldon
Copy link
Author

Thanks @zulrang - although lots of the investigative work was done by @zzzeek who deserves the credit here!

PrettyWood pushed a commit that referenced this issue Sep 4, 2021
…3093)

* Fix: _pydantic_post_init() reassigns __dict__ and removes properties added to the object, for example in __new__(), breaking integration with SQLAlchemy and other libraries #3043

* Added changes file
jpribyl pushed a commit to liquet-ai/pydantic that referenced this issue Oct 7, 2021
…ydantic#3093)

* Fix: _pydantic_post_init() reassigns __dict__ and removes properties added to the object, for example in __new__(), breaking integration with SQLAlchemy and other libraries pydantic#3043

* Added changes file
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
Development

Successfully merging a pull request may close this issue.

2 participants