DEV Community

Spike
Spike

Posted on • Edited on

Define ObjectId for Pydantic 2.x compatible

I will update later.

For now, use this code.

Test Versions

  • FastAPI: 0.100.0
  • python: 3.11.4
  • pydantic: 2.0.2
  • pymongo: 4.4.0
  • pyhumps: 3.8.0

✨ Add new type PydanticObjectId

from collections.abc import Callable
from typing import Annotated, Any

import humps
from bson import ObjectId
from pydantic import BaseModel, ConfigDict, GetJsonSchemaHandler
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema


class _ObjectIdPydanticAnnotation:
    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        _source_type: Any,
        _handler: Callable[[Any], core_schema.CoreSchema],
    ) -> core_schema.CoreSchema:
        def validate_from_str(v: str) -> ObjectId:
            return ObjectId(v)

        from_str_schema = core_schema.chain_schema(
            [
                core_schema.str_schema(),
                core_schema.no_info_plain_validator_function(validate_from_str),
            ]
        )

        return core_schema.json_or_python_schema(
            json_schema=from_str_schema,
            python_schema=core_schema.union_schema(
                [
                    core_schema.is_instance_schema(ObjectId),
                    from_str_schema,
                ]
            ),
            serialization=core_schema.plain_serializer_function_ser_schema(lambda v: str(v)),
        )

    @classmethod
    def __get_pydantic_json_schema__(
        cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
    ) -> JsonSchemaValue:
        return handler(core_schema.str_schema())


PydanticObjectId = Annotated[ObjectId, _ObjectIdPydanticAnnotation]


class BaseODM(BaseModel):
    model_config = ConfigDict(
        alias_generator=humps.camelize,
        arbitrary_types_allowed=True,
        frozen=True,
        from_attributes=True,
        populate_by_name=True,
        str_strip_whitespace=True,
    )

    id: PydanticObjectId

Enter fullscreen mode Exit fullscreen mode

👔 You don't have to convert data in FastAPI

With from_attributes=True, formerly known as orm_mode=True, you don't have to use the model_validate method.

import humps
from pydantic import BaseModel, ConfigDict


class EmbeddedBaseModel(BaseModel):
    model_config = ConfigDict(
        alias_generator=humps.camelize,
        arbitrary_types_allowed=True,
        frozen=True,
        from_attributes=True,  # 🪄 This is magic
        populate_by_name=True,
        str_strip_whitespace=True,
    )


class BaseODM(BaseModel):
    model_config = ConfigDict(
        alias_generator=humps.camelize,
        arbitrary_types_allowed=True,
        frozen=True,
        from_attributes=True,  # 🪄 This is magic
        populate_by_name=True,
        str_strip_whitespace=True,
    )

    id: PydanticObjectId
Enter fullscreen mode Exit fullscreen mode

Reference

Top comments (0)