You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Cannot use model_copy(deep=True) on a model instance which was created using model_construct, when there is a model_post_init in the class hierarchy.
#9122
Closed
1 task done
babygrimes opened this issue
Mar 27, 2024
· 4 comments
· Fixed by #9168
This standalone code produces the issue. When constructing a model instance using .model_construct instead of .model_validate or __init__, and if the model class defines model_post_init (on the class, or earlier in the hierarchy - both will trigger the issue), the below error is produced.
If the m.model_copy(deep=True) line is changed to m.model_validate(m, from_attributes=True, strict=True) (just to show the difference, and is what I'm currently using as a workaround) - everything appears to work as expected.
Additionally, you can comment out the model_post_init in the example below, and everything again works as expected (though I really want to use model_post_init in my real code for various reasons).
I traced this as far as I could into main.py:model_construct and it appears to be related to lines 238-249. It appears in the model_construct path there is no initialization of __pydantic_private__, resulting in the stack trace here:
File "/home/dave/test_deepcopy.py", line 21, in <module>
main()
File "/home/dave/test_deepcopy.py", line 17, in main
m.model_copy(deep=True)
File "/home/dave/axial/src/services/axial-api.git/venv/lib/python3.11/site-packages/pydantic/main.py", line 266, in model_copy
copied = self.__deepcopy__() if deep else self.__copy__()
^^^^^^^^^^^^^^^^^^^
File "/home/dave/axial/src/services/axial-api.git/venv/lib/python3.11/site-packages/pydantic/main.py", line 723, in __deepcopy__
if self.__pydantic_private__ is None:
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/dave/axial/src/services/axial-api.git/venv/lib/python3.11/site-packages/pydantic/main.py", line 764, in __getattr__
return super().__getattribute__(item) # Raises AttributeError if appropriate
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'Model' object has no attribute '__pydantic_private__'. Did you mean: '__pydantic_complete__'?```
### Example Code
```Python
#!/usr/bin/env python3
# coding: utf-8
from typing import Any
import pydantic
class Model(pydantic.BaseModel, revalidate_instances="always"):
id: int # noqa: A003
def model_post_init(self, context: Any) -> None:
super().model_post_init(context)
def main() -> None:
m = Model.model_construct({"id": 1})
m.model_copy(deep=True)
if __name__ == "__main__":
main()
It occurs to me that maybe this simple patch would be sufficient to fix this. The thinking is that __pydantic_private__ is allowed to be any of:
An unset attribute
None
Dictionary of private attributes
The issue that is triggering the bug is that it is unset when the __deepcopy__ call is made, but the internals assume it is either None/Dict. This patch adds coverage of "Unset" (pass).
(venv) [dmgrime@dave-laptop:~]$ diff -u venv/lib/python3.11/site-packages/pydantic/main.py /tmp
--- venv/lib/python3.11/site-packages/pydantic/main.py 2024-04-03 09:41:34.015680649 -0400
+++ /tmp/main.py 2024-04-03 09:40:54.425678622 -0400
@@ -720,7 +720,9 @@
# and attempting a deepcopy would be marginally slower.
_object_setattr(m, '__pydantic_fields_set__', copy(self.__pydantic_fields_set__))
- if self.__pydantic_private__ is None:
+ if not hasattr(self, "__pydantic_private__"):
+ pass
+ elif self.__pydantic_private__ is None:
_object_setattr(m, '__pydantic_private__', None)
else:
_object_setattr(
(venv) [dmgrime@dave-laptop:~]$
Initial Checks
Description
This standalone code produces the issue. When constructing a model instance using
.model_construct
instead of.model_validate
or__init__
, and if the model class definesmodel_post_init
(on the class, or earlier in the hierarchy - both will trigger the issue), the below error is produced.If the
m.model_copy(deep=True)
line is changed tom.model_validate(m, from_attributes=True, strict=True)
(just to show the difference, and is what I'm currently using as a workaround) - everything appears to work as expected.Additionally, you can comment out the
model_post_init
in the example below, and everything again works as expected (though I really want to usemodel_post_init
in my real code for various reasons).I traced this as far as I could into
main.py:model_construct
and it appears to be related to lines 238-249. It appears in themodel_construct
path there is no initialization of__pydantic_private__
, resulting in the stack trace here:Python, Pydantic & OS Version
The text was updated successfully, but these errors were encountered: