BeanieODM / beanie

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

[BUG] RootModels with custom __iter__ methods break Beanie save() #830

Open slingshotvfx opened 5 months ago

slingshotvfx commented 5 months ago

Describe the bug Re-implementing __iter__ methods on RootModels is common and even recommended in the in the Pydantic docs: https://docs.pydantic.dev/latest/concepts/models/#rootmodel-and-custom-root-types)

Doing so seems to break model.save() in Beanie with: TypeError: cannot unpack non-iterable int object

To Reproduce

from beanie import Document, init_beanie
from mongomock_motor import AsyncMongoMockClient
from pydantic import RootModel

class RootSubModel(RootModel):
    root: list = []

    def __iter__(self):
        return iter(self.root)

class Base(Document):
    a: RootSubModel = RootSubModel()

async def main():
    client = AsyncMongoMockClient()
    await init_beanie(database=getattr(client, "test"), document_models=[Base])

    db_obj = await Base(a=[1, 2, 3]).insert()
    db_obj.a.root.append(4)
    await db_obj.save()

Traceback:

  File "\dev\beanie_rootmodel.py", line 27, in main
    await db_obj.save()
  File "\.venv\Lib\site-packages\beanie\odm\actions.py", line 219, in wrapper
    result = await f(self, *args, skip_actions=skip_actions, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\.venv\Lib\site-packages\beanie\odm\utils\state.py", line 66, in wrapper
    result = await f(self, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\.venv\Lib\site-packages\beanie\odm\utils\self_validation.py", line 12, in wrapper
    return await f(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\.venv\Lib\site-packages\beanie\odm\documents.py", line 545, in save
    return await self.update(
           ^^^^^^^^^^^^^^^^^^
  File "\.venv\Lib\site-packages\beanie\odm\actions.py", line 219, in wrapper
    result = await f(self, *args, skip_actions=skip_actions, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\.venv\Lib\site-packages\beanie\odm\utils\state.py", line 66, in wrapper
    result = await f(self, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\.venv\Lib\site-packages\beanie\odm\documents.py", line 674, in update
    merge_models(self, result)
  File "\.venv\Lib\site-packages\beanie\odm\utils\parsing.py", line 33, in merge_models
    merge_models(left_value, right_value)
  File "\.venv\Lib\site-packages\beanie\odm\utils\parsing.py", line 25, in merge_models
    for k, right_value in right.__iter__():
        ^^^^^^^^^^^^^^
TypeError: cannot unpack non-iterable int object

Additional context python v3.12.1 pydantic 2.5.3 pydantic-core 2.14.6 pydantic-settings 2.1.0 beanie 1.24.0

Thanks for all your work on Beanie!

slingshotvfx commented 5 months ago

Should be noted that I fixed this for now by iterating root.items():

    def __iter__(self):
        return iter(self.root.items())

However, this might not work for everyone's codebase if they expect a RootModel to behave like a dictionary and it's not what pydantic explicitly suggests in their docs.

roman-right commented 4 months ago

Hi @slingshotvfx , Thank you for the catch. Yup, it should expect other types. I'll think how to fix this. Thank you for the catch