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

pytest returtn "AttributeError: 'Person' object has no attribute '__pydantic_extra__'" in V2 #7556

Closed
1 task done
zhengye1 opened this issue Sep 21, 2023 · 7 comments
Closed
1 task done
Labels
bug V2 Bug related to Pydantic V2 unconfirmed Bug not yet confirmed as valid/applicable

Comments

@zhengye1
Copy link

zhengye1 commented Sep 21, 2023

Initial Checks

  • I confirm that I'm using Pydantic V2

Description

Behavior for below test case is different, in v1 it run it successfully and pass, but in the v2 I got following:

test setup failed
@pytest.fixture
   def person():
       def newinit(self, x, y):
           self._x = x
           self._y = y
   
       with patch.object(Person, '__init__', lambda self: newinit(self, 3, 4)):
>           person = Person()

PythonRandomTest.py:27: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
PythonRandomTest.py:26: in <lambda>
   with patch.object(Person, '__init__', lambda self: newinit(self, 3, 4)):
PythonRandomTest.py:23: in newinit
   self._x = x
../.local/lib/python3.10/site-packages/pydantic/main.py:743: in __setattr__
   if self.__pydantic_private__ is None or name not in self.__private_attributes__:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'Person' object has no attribute '__pydantic_extra__'") raised in repr()] Person object at 0x7f1e4ae5c9a0>
item = '__pydantic_private__'

   def __getattr__(self, item: str) -> Any:
       private_attributes = object.__getattribute__(self, '__private_attributes__')
       if item in private_attributes:
           attribute = private_attributes[item]
           if hasattr(attribute, '__get__'):
               return attribute.__get__(self, type(self))  # type: ignore
   
           try:
               # Note: self.__pydantic_private__ cannot be None if self.__private_attributes__ has items
               return self.__pydantic_private__[item]  # type: ignore
           except KeyError as exc:
               raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') from exc
       else:
>           pydantic_extra = object.__getattribute__(self, '__pydantic_extra__')
E           AttributeError: 'Person' object has no attribute '__pydantic_extra__'

../.local/lib/python3.10/site-packages/pydantic/main.py:723: AttributeError

Example Code

from unittest.mock import patch

import pytest
from pydantic import BaseModel, PrivateAttr


class Person(BaseModel):
    _x: int = PrivateAttr(default=0)
    _y: int = PrivateAttr(default=0)

    def __init__(self, x, y):
        super().__init__() # Thanks [andresliszt](https://github.com/andresliszt)'s comment, I add this line but still not work
        self._x = x
        self._y = y

    def do_work(self):
        return self._x + self._y


@pytest.fixture
def person():
    def newinit(self, x, y):
        self._x = x
        self._y = y

    with patch.object(Person, '__init__', lambda self: newinit(self, 3, 4)):
        person = Person()
        return person


def test_result(person):
    assert person.do_work() == 7

Python, Pydantic & OS Version

pydantic version: 2.3.0
python version: 3.10.12
@zhengye1 zhengye1 added bug V2 Bug related to Pydantic V2 unconfirmed Bug not yet confirmed as valid/applicable labels Sep 21, 2023
@andresliszt
Copy link
Contributor

andresliszt commented Sep 21, 2023

I'm not sure about you set up but you are not calling super init --- If you want to set attributes (self._x = ...) at init time you have to call pydantic's constructor first

def __init__(self, x, y):
     super().__init__()
     self._x = x
     self._y = y
  

@zhengye1
Copy link
Author

tried, still same error message

@andresliszt
Copy link
Contributor

andresliszt commented Sep 21, 2023

Take a look at your patch patch.object(Person, '__init__', lambda self: newinit(self, 3, 4)) you are overwriting the init of your class, so super is never called ... but what's the idea of newinit mock? why not this:

def test_result():
    person = Person(x = 3, y = 4)
    assert person.do_work() == 7

@zhengye1
Copy link
Author

Because the original code , the test case was writting this way... , instead of this

    def newinit(self, x, y):
        self._x = x
        self._y = y

the original code in that part is like this:

    def newinit(self,):
        self._x = AsyncMock()
        self._y = AsyncMock()

Sorry I cannot show the full code for the original part due to some policy in there..

@andresliszt
Copy link
Contributor

Got it, the problem is that you are not patching the init properly

@samuelcolvin
Copy link
Member

samuelcolvin commented Sep 22, 2023

I"m getting this too while working on #6820, in my case it's related to calling repr() on a model when it's partially constructed, I'll fix that and it might be related. Different issue.

@samuelcolvin
Copy link
Member

Your problem is that by patching __init__, you're skipping the call to validation, which sets some attributes, pydantic then expects those attributes to be set.

If you really want to do something like this, you can set them manually like this:

    def newinit(self, x, y):
        object.__setattr__(self, '__pydantic_extra__', {})
        object.__setattr__(self, '__pydantic_private__', {})
        object.__setattr__(self, '__pydantic_fields_set__', set())
        self._x = x
        self._y = y

But (while not knowing anything about what you're trying to achieve), I suspect what you're doing in conceptually wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V2 Bug related to Pydantic V2 unconfirmed Bug not yet confirmed as valid/applicable
Projects
None yet
Development

No branches or pull requests

3 participants