BeanieODM / beanie

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

The Document Model Is Corrupted After Saving Changes When a Nested `BaseModel` Is Used in a Dictionary #264

Closed estiller closed 1 year ago

estiller commented 2 years ago

Hi, First of all, thanks for publishing beanie as an open-source. I think it is awesome!

I am using the latest version of beanie (currently 1.11.0) on both Python 3.9 and Python 3.10. I encountered a weird problem:

  1. Define a document that contains a nested model in a dictionary.
  2. Create an instance of the document.
  3. Save the document.
  4. Try to access the nested model in the saved document, and you get an error.

I managed to reproduce this issue in a standalone manner. Please see the below code for reproduction and the temporary workaround I am currently using.

import asyncio

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

CONNECTION_URI = "XXX_CONNECTION_URI_XXX"
DATABASE = "database"

class Book(BaseModel):
    name: str

class Library(Document):
    books: dict[str, Book]

async def main():
    client = AsyncIOMotorClient(CONNECTION_URI)
    database = client[DATABASE]
    await init_beanie(database, document_models=[Library])

    library = Library(
        books={
            "book": Book(name="my book")
        }
    )

    print(library.books["book"].__class__)  # <class '__main__.Book'>
    print(library.books["book"].name)       # my book

    await library.create()                  # this call "corrupts" the document model

    print(library.books["book"].__class__)  # <class 'dict'>
    try:
        print(library.books["book"].name)   # AttributeError: 'dict' object has no attribute 'name'
    except AttributeError as err:
        print(f"AttributeError: {err}")

    # Workaround - make a fresh copy of the document with Pydantic
    library_copy = Library.parse_obj(library.dict())
    print(library_copy.books["book"].__class__)  # <class '__main__.Book'>
    print(library_copy.books["book"].name)       # my book

if __name__ == "__main__":
    asyncio.run(main())

Am I doing anything wrong, or using anything that is not meant to be supported? Or is this indeed a bug?

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 30 days with no activity.

github-actions[bot] commented 1 year ago

This issue was closed because it has been stalled for 14 days with no activity.