BeanieODM / beanie

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

[BUG] Beanie cannot insert document when id field is an UUID #777

Closed tkaraouzene closed 6 months ago

tkaraouzene commented 8 months ago

Hi everyone!

Describe the bug Beanie cannot insert document when id Field is an UUID

Additional context

To Reproduce

from beanie import Document

class MyDocument(Document):
    id: UUID = Field(default_factory=uuid4)
    name: str

document = MyDocument(name="toto")
document.insert()

It raises following error:

..\.nox\dev\lib\site-packages\beanie\odm\actions.py:219: in wrapper
    result = await f(self, *args, skip_actions=skip_actions, **kwargs)
..\.nox\dev\lib\site-packages\beanie\odm\utils\state.py:76: in wrapper
    result = await f(self, *args, **kwargs)
..\.nox\dev\lib\site-packages\beanie\odm\utils\state.py:66: in wrapper
    result = await f(self, *args, **kwargs)
..\.nox\dev\lib\site-packages\beanie\odm\utils\self_validation.py:12: in wrapper
    return await f(self, *args, **kwargs)
..\.nox\dev\lib\site-packages\beanie\odm\documents.py:312: in insert
    new_id = parse_object_as(
..\.nox\dev\lib\site-packages\beanie\odm\utils\pydantic.py:16: in parse_object_as
    return TypeAdapter(object_type).validate_python(data)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <pydantic.type_adapter.TypeAdapter object at 0x0000021AF77A3FA0>
_TypeAdapter__object = ObjectId('654df3118b373d559886cac2')

    def validate_python(
        self,
        __object: Any,
        *,
        strict: bool | None = None,
        from_attributes: bool | None = None,
        context: dict[str, Any] | None = None,
    ) -> T:
        """Validate a Python object against the model.

        Args:
            __object: The Python object to validate against the model.
            strict: Whether to strictly check types.
            from_attributes: Whether to extract data from object attributes.
            context: Additional context to pass to the validator.

        Returns:
            The validated object.
        """
>       return self.validator.validate_python(__object, strict=strict, from_attributes=from_attributes, context=context)
E       pydantic_core._pydantic_core.ValidationError: 1 validation error for uuid
E         UUID input should be a string, bytes or UUID object [type=uuid_type, input_value=ObjectId('654df3118b373d559886cac2'), input_type=ObjectId]
E           For further information visit https://errors.pydantic.dev/2.4/v/uuid_type

It seems the error comes from parse_object_as cannot convert new_id from PydanticObjectId to UUID but it should be possible according to the documentation: https://beanie-odm.dev/tutorial/defining-a-document/#id

For information, running following lines works, however I really prefer keep UUID instead of PydanticObjectId:

from beanie import Document

class MyDocument(Document):
    name: str

document = MyDocument(name="toto")
document.insert()
tkaraouzene commented 8 months ago

After a small investigation, it seems the issue occurs here

Indeed, if I run in debug mode I can see that

self.id
Out[2]: UUID('68d76e1a-65d3-4d39-be25-b8fd241edd4c')

When

result.inserted_id
Out[3]: ObjectId('654e26d163172aba437b7f72')

So it seems the provided id is not considered at document insertion.

If I modify my document as follow:

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

All works! It is a good workaround but whatever it is not the expected behavior moreover it was working properly with pydantic v1

TyroneTang commented 8 months ago

To also add on to @tkaraouzene 's comment, I have this issue when saving my documents as a Link Write / Object.

Although documents (only link documents, i.e. child documents, parent document does not save at all) with UUID still saves into database (objectId now in UUID format), however python still flags out this error:

ValueError: cannot encode native uuid.UUID with UuidRepresentation.UNSPECIFIED. UUIDs can be manually converted to bson.Binary instances using bson.Binary.from_uuid() or a different UuidRepresentation can be configured. See the documentation for UuidRepresentation for more information.

To Reproduce:

from beanie import Document, Link, WriteRules

class Content(Document):
    id: UUID = Field(default_factory=uuid4) # error here (1)
    publisher = str
    category = str

class Book(Document):
    id: UUID = Field(default_factory=uuid4) # error here (1)
    book_pub: int
    book_content: Link[Content] | None

async def create_example_book() -> None:
      new_content = Content(
            publisher = "publisher_zebra",
            category = "science_fiction"
      )

      new_book_data = Book(
            book_pub=12345, 
            book_content=new_content,  
      )

     await new_book_data.save(link_rule=WriteRules.WRITE) # error here (1)

error (1) :  Failed to start mongodb.  ValueError: cannot encode native uuid.UUID with UuidRepresentation.UNSPECIFIED. UUIDs can be manually converted to bson.Binary instances using bson.Binary.from_uuid() or a different UuidRepresentation can be configured. See the documentation for UuidRepresentation for more information.
RomainDGrey commented 8 months ago

In order to solve this issue, I think it's necessary to precise the uuidRepresentation in your AsyncIOMotorClient : Exemple with FastAPI app.db = AsyncIOMotorClient([...], uuidRepresentation='standard').your_db

github-actions[bot] commented 7 months ago

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

github-actions[bot] commented 6 months ago

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