beanie icon indicating copy to clipboard operation
beanie copied to clipboard

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

Open estiller opened this issue 3 years ago • 0 comments

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?

estiller avatar May 10 '22 07:05 estiller