BeanieODM / beanie

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

[BUG] Document with UUID as Ids and Link/BackLink are badly fetched #825

Open jeremos opened 6 months ago

jeremos commented 6 months ago

Hi, Thank you again for your work. I noticed a strange result with mongo documents that use UUID as primary keys.

Describe the bug Using UUID as replacement of standard mongo objectId with Linked documents generate bad objects classes when they are fetched.

The first line below show a working result with mongo object ids and two linked documents The second line is the same object model but with UUID for object ids => the doc is not correctly fetched

AnotherDocWithID(id=ObjectId('6599416910d3a060bc93a548'), revision_id=None, name='BID', doc=DocWithID(id=ObjectId('6599416910d3a060bc93a547'), revision_id=None, name='AID'))

AnotherDocWithUUID(id=UUID('0189d40d-dc3a-439e-8b4a-e545af29f2b8'), revision_id=None, name='B', doc=<beanie.odm.fields.Link object at 0x10603e150>)

Here the full script to reproduce

import asyncio
import uuid
from pprint import pprint
from uuid import UUID, uuid4

import motor.motor_asyncio
from beanie import Document, Link, init_beanie
from pydantic import Field, BaseModel

class DocWithUUID(Document, BaseModel):
    id: UUID = Field(default_factory=uuid4, alias="_id", serialization_alias="id")
    name: str = Field()

class AnotherDocWithUUID(Document, BaseModel):
    id: UUID = Field(default_factory=uuid4, alias="_id", serialization_alias="id")
    name: str = Field()
    doc: Link[DocWithUUID]

class DocWithID(Document, BaseModel):
    name: str = Field()

class AnotherDocWithID(Document, BaseModel):
    name: str = Field()
    doc: Link[DocWithID]

async def app():
    mongodb_client = motor.motor_asyncio.AsyncIOMotorClient(
        f"mongodb://127.0.0.1:27017/?uuidRepresentation=pythonLegacy")
    database = mongodb_client['testdb']
    await init_beanie(database, allow_index_dropping=True, document_models=[DocWithUUID, AnotherDocWithUUID, DocWithID, AnotherDocWithID])
    await DocWithUUID.delete_all()
    await AnotherDocWithUUID.delete_all()

    await DocWithID.delete_all()
    await AnotherDocWithID.delete_all()

    a = DocWithUUID(id=uuid.UUID('FABCDF32-694C-4F55-9914-EF36F0692A51'), name='A')
    await a.create()

    b = AnotherDocWithUUID(id=uuid.UUID('0189D40D-DC3A-439E-8B4A-E545AF29F2B8'), name='B', doc=a)
    await b.create()

    aid = DocWithID(name='AID')
    await aid.create()

    bid = AnotherDocWithID(name='BID', doc=aid)
    await bid.create()

    result_id = await AnotherDocWithID.get(bid.id, fetch_links=True)
    pprint(result_id)

    result_uuid = await AnotherDocWithUUID.get(b.id, fetch_links=True)
    pprint(result_uuid)

asyncio.run(app())

Expected behavior DocWithUUID should be fetched

Additional context Beanie is great !

roman-right commented 6 months ago

Hi @jeremos , As I remember there was a config for the motor client to handle it properly. Let me double check. Probably I can handle it on Beanie side. Thank you for the catch!

jeremos commented 6 months ago

Hi @roman-right I have checked Motor and the only param I found is uuidRepresentation with a default value to UNSPECIFIED. This param is in my sample code and the value to use is "pythonLegacy". So, yep, it's probably on the beanie side. Many thanks

ldorigo commented 5 months ago

FYI: for this work (at least for us) it's sufficient to initialize beanie as follows:

    client = AsyncIOMotorClient(mongo_url, uuidRepresentation="standard")
    await init_beanie(database=client.get_database(), document_models=DOCUMENT_MODELS)

I think it should probably still be handled on Beanie's side? And in particular @roman-right, in migrations (beanie/migrations/database.py) since there beanie itself takes care of initializing the client and so we can't change the parameter (had to resort to monkey-patching the library)