BeanieODM / beanie

Asynchronous Python ODM for MongoDB
http://beanie-odm.dev/
Apache License 2.0
2k stars 211 forks source link

[BUG] TypeError: expected 1 argument, got 0 when beanie.Document has method wrapped in pydantic.validate_call #695

Closed jhamman closed 1 year ago

jhamman commented 1 year ago

Describe the bug I am upgrading to Pydantic 2.0 and am hitting a surprising error. If my beanie.Document has a custom method on it wrapped in validate_call, Beanie fails to initialize.

To Reproduce

from beanie import Document, init_beanie
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import validate_call

client = AsyncIOMotorClient()
db = client.get_database('foo')

class MyDoc(Document):
    name: str

    @validate_call
    def foo(self, bar: str) -> None:
        print(f'foo {bar}')

await init_beanie(db, document_models=[MyDoc])

This raises the following error:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[29], line 8
      4     @validate_call
      5     def foo(self, bar: str) -> None:
      6         print(f'foo {bar}')
----> 8 await init_beanie(db, document_models=[MyDoc])

File ~/workdir/lib/python3.10/site-packages/beanie/odm/utils/init.py:735, in init_beanie(database, connection_string, document_models, allow_index_dropping, recreate_views)
    714 async def init_beanie(
    715     database: AsyncIOMotorDatabase = None,
    716     connection_string: Optional[str] = None,
   (...)
    721     recreate_views: bool = False,
    722 ):
    723     """
    724     Beanie initialization
    725 
   (...)
    732     :return: None
    733     """
--> 735     await Initializer(
    736         database=database,
    737         connection_string=connection_string,
    738         document_models=document_models,
    739         allow_index_dropping=allow_index_dropping,
    740         recreate_views=recreate_views,
    741     )

File ~/workdir/lib/python3.10/site-packages/beanie/odm/utils/init.py:121, in Initializer.__await__(self)
    119 def __await__(self):
    120     for model in self.document_models:
--> 121         yield from self.init_class(model).__await__()

File ~/workdir/lib/python3.10/site-packages/beanie/odm/utils/init.py:702, in Initializer.init_class(self, cls)
    699 self.check_deprecations(cls)
    701 if issubclass(cls, Document):
--> 702     await self.init_document(cls)
    704 if issubclass(cls, View):
    705     await self.init_view(cls)

File ~/workdir/lib/python3.10/site-packages/beanie/odm/utils/init.py:562, in Initializer.init_document(self, cls)
    560 self.init_document_fields(cls)
    561 self.init_cache(cls)
--> 562 self.init_actions(cls)
    564 self.inited_classes.append(cls)
    566 return output

File ~/workdir/lib/python3.10/site-packages/beanie/odm/utils/init.py:386, in Initializer.init_actions(cls)
    384 ActionRegistry.clean_actions(cls)
    385 for attr in dir(cls):
--> 386     f = getattr(cls, attr)
    387     if inspect.isfunction(f):
    388         if hasattr(f, "has_action"):

File ~/workdir/lib/python3.10/site-packages/pydantic/_internal/_validate_call.py:109, in ValidateCallWrapper.__get__(self, obj, objtype)
    106 if obj is None:
    107     try:
    108         # Handle the case where a method is accessed as a class attribute
--> 109         return objtype.__getattribute__(objtype, self._name)  # type: ignore
    110     except AttributeError:
    111         # This will happen the first time the attribute is accessed
    112         pass

File ~/workdir/lib/python3.10/site-packages/lazy_model/parser/new.py:98, in LazyModel.__getattribute__(self, item)
     97 def __getattribute__(self, item):
---> 98     res = super(LazyModel, self).__getattribute__(item)
     99     if res is NAO:
    100         field_info = self.model_fields.get(item)

TypeError: expected 1 argument, got 0

Expected behavior I expect that Beanie is able to initialize Documents with custom methods on them. (this approach worked with Pydantic 1)

Additional context Version info: python : 3.10 beanie : 1.21.0 pydantic : 2.3.0 motor : 3.3.1

roman-right commented 1 year ago

This is such a weird problem. In the Pydantic code, it is used against the class, not the instance:

return objtype.__getattribute__(objtype, self._name)

This causes an error. I'm trying to handle this, but I'm not sure if it will work soon.

roman-right commented 1 year ago

Ah, no. It looks like I figured out how to handle this. Please, try https://github.com/roman-right/beanie/pull/669

jhamman commented 1 year ago

Thanks for the quick response @roman-right - testing this out now.

jhamman commented 1 year ago

@roman-right - the changes in #669 seem to have unblocked me. Looking forward to seeing that release out in the wild!