BeanieODM / beanie

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

[BUG] document class inherited from GenericModel with Link typed fields does not insert DBRef value after v1.21.0 #804

Open IterableTrucks opened 7 months ago

IterableTrucks commented 7 months ago

Describe the bug A document class with Link typed field owner inherited from its parent generic class does not insert DBRef value into MongoDB but the original User object with additional field revision_id=null instead. Pydantic version is 1.10.13.

To Reproduce

import asyncio
from typing import Optional, Generic, TypeVar

from pydantic import Field
from pydantic.generics import GenericModel
from beanie import Document, Link, init_beanie, BackLink
from beanie.operators import Or, And, In

class User(Document):
    name: str

T = TypeVar('T', bound=Document)

class DirectoryBase(GenericModel, Generic[T]):
    owner: Optional[Link[User]]
    files: Optional[list[BackLink[T]]] = Field(original_field='directory')

class Directory(Document, DirectoryBase['File']):
    name: str

class File(Document):
    name: str
    size: int
    directory: Optional[Link[Directory]]

async def main():
    await init_beanie(connection_string="mongodb://localhost:27017/test", document_models=[User, Directory, File])
    user = await User(name='user-1').insert()
    directory = await Directory(name="dir-2", owner=user).insert()
    fetched_dir = await Directory.get(directory.id)
    assert isinstance(fetched_dir.owner, Link)
if __name__ == "__main__":
    asyncio.run(main())

Expected behavior The inserted value of owner into Directory should be a DBRef referencing the user created before.

Additional context With pydantic-2.5.2 everything is ok:

import asyncio
from typing import Optional, Generic, TypeVar

from pydantic import BaseModel, Field
from beanie import Document, Link, init_beanie, BackLink
from beanie.operators import Or, And, In

class User(Document):
    name: str

T = TypeVar('T', bound=Document)

class DirectoryBase(BaseModel, Generic[T]):
    owner: Optional[Link[User]]
    files: Optional[list[BackLink[T]]] = Field(original_field='directory')

class Directory(Document, DirectoryBase['File']):
    name: str

class File(Document):
    name: str
    size: int
    directory: Optional[Link[Directory]]

async def main():
    await init_beanie(connection_string="mongodb://localhost:27017/test", document_models=[User, Directory, File])
    user = await User(name='user-1').insert()
    directory = await Directory(name="dir-2", owner=user).insert()
    fetched_dir = await Directory.get(directory.id)
    assert isinstance(fetched_dir.owner, Link)
if __name__ == "__main__":
    asyncio.run(main())

Maybe it's a compatibility related issue introduced by the pydantic v2 support from 1.21.0

roman-right commented 6 months ago

Hi @IterableTrucks , Thank you for the catch!