pydantic
pydantic copied to clipboard
model_serializer doesn't return dataclasses as custom type
Initial Checks
- [X] I confirm that I'm using Pydantic V2
Description
I've been exploring the idea to parse pydantic models into a ORM object using custom serializer. For testing I've been using dataclassses as ORM classes and discovered that they are serialized to JSON even when returned explicitly in model serializer. Same happens with pydantic objects.
But when using plain python classes, I get the expected behavior - serialization returns instances of custom "ORM" classes.
It'd be nice if documentation mentioned that pydantic classes and dataclasses can't be returned as-is when serializing.
Example Code
from dataclasses import dataclass
from typing import Any, ClassVar, cast
from pydantic import BaseModel, SerializationInfo, SerializerFunctionWrapHandler, model_serializer
class CustomBase(BaseModel):
orm_model: ClassVar
@model_serializer(mode="wrap")
def wrap_orm_serialize(self, handler: SerializerFunctionWrapHandler, info: SerializationInfo):
serialized = handler(self)
context = cast(dict[str, Any], info.context) if info.context else {}
if context.get("to_orm"):
ormed = self.orm_model(**serialized)
return ormed
return serialized
@dataclass
class DataclassTestOneOrm:
one: str
@dataclass
class DataclassTestTwoOrm:
two: int
to: DataclassTestOneOrm
class TestOne(CustomBase):
orm_model = DataclassTestOneOrm
one: str
class TestTwo(CustomBase):
orm_model = DataclassTestTwoOrm
two: int
to: TestOne
to = TestOne(one="ONE")
tt = TestTwo(two=222, to=to)
dtt = tt.model_dump()
print(f"{dtt=}") # dtt={'two': 222, 'to': {'one': 'ONE'}}
dtt = tt.model_dump(context={"to_orm": True})
print(f"{dtt=}") # dtt={'two': 222, 'to': {'one': 'ONE'}}
# Though the following works as expected
class PlainTestOneOrm:
one: str
def __init__(self, one) -> None:
self.one = one
def __repr__(self) -> str:
return f"{self.__class__.__name__}(one={self.one})"
class PlainTestTwoOrm:
two: int
to: PlainTestOneOrm
def __init__(self, two, to) -> None:
self.two = two
self.to = to
def __repr__(self) -> str:
return f"{self.__class__.__name__}(two={self.two}, to={self.to})"
class TestOne(CustomBase):
orm_model = PlainTestOneOrm
one: str
class TestTwo(CustomBase):
orm_model = PlainTestTwoOrm
two: int
to: TestOne
to = TestOne(one="ONE")
tt = TestTwo(two=222, to=to)
dtt = tt.model_dump() # dtt={'two': 222, 'to': {'one': 'ONE'}}
print(f"{dtt=}")
dtt = tt.model_dump(context={"to_orm": True})
print(f"{dtt=}") # dtt=PlainTestTwoOrm(two=222, to=PlainTestOneOrm(one=ONE))
Python, Pydantic & OS Version
pydantic version: 2.7.1
pydantic-core version: 2.18.2
pydantic-core build: profile=release pgo=true
install path: /home/maksim_borisov/repos/hogwarts/api-scratchpad/.venv/lib/python3.12/site-packages/pydantic
python version: 3.12.4 (main, Jun 7 2024, 00:00:00) [GCC 14.1.1 20240607 (Red Hat 14.1.1-5)]
platform: Linux-5.15.153.1-microsoft-standard-WSL2-x86_64-with-glibc2.39
related packages: pyright-1.1.376 fastapi-0.111.0 typing_extensions-4.12.2 pydantic-settings-2.2.1 pyright-1.1.376 fastapi-0.111.0 typing_extensions-4.12.2 pydantic-settings-2.2.1
commit: unknown