-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
Initial Checks
- I confirm that I'm using Pydantic V2
Description
pydantic/pydantic-core@37ec6e7 introduced a regression in which objects on fields annotated as datetime, date, and time will be silently handled as if the object was of the incorrect type (falling through to infer_to_python unconditionally). This is most evident when using a union type, where I would expect that if serializing as a datetime fails, Pydantic would try the next available type. The logic seems to have been borrowed from the existing timedelta serialization which was also buggy in a similar way (verified on 2.11.7).
The actual issue seems to be in pydantic-core. The TypeSerializer used to downcast to ensure that the values are the correct type, but it does not do so now, only doing so whenever mode='json' is passed in. Indeed, the code works correctly if I pass mode="json" to BaseModel.model_dump().
This particular bug is insidious because it happens that most of the time, infer_to_python actually does the right thing. But with a specific user-defined type, one can see that we are silently doing the wrong thing.
I have a PR to fix this within pydantic-core.
Example Code
from datetime import date, datetime, time, timedelta
from pydantic import BaseModel
from pydantic_core import core_schema
class SampleType:
@classmethod
def __get_pydantic_core_schema__(cls, source, handler) -> core_schema.CoreSchema:
serialization_schema = core_schema.plain_serializer_function_ser_schema(lambda v: None)
json_validation_schema = core_schema.no_info_plain_validator_function(
function=lambda v: v, serialization=serialization_schema
)
python_validation_schema = core_schema.any_schema()
return core_schema.json_or_python_schema(
json_schema=json_validation_schema,
python_schema=python_validation_schema,
serialization=serialization_schema,
)
class SampleModel(BaseModel):
d: datetime | SampleType
dt: datetime | SampleType
t: time | SampleType
td: timedelta | SampleType
print(SampleModel(d='z', dt='z', t='z', td='z').model_dump())
# Expected: {'d': None, 'dt': None, 't': None, 'td': None} (from SampleType)
# Actual: {'d': 'z', 'dt': 'z', 't': 'z', 'td': 'z'} (from pydantic-core temporal serializers)Python, Pydantic & OS Version
pydantic version: 2.13.0a0+dev
pydantic-core version: 2.41.4
pydantic-core build: profile=release pgo=false
python version: 3.11.4 (main, Jul 22 2023, 23:52:07) [Clang 14.0.0 (clang-1400.0.29.202)]
platform: macOS-15.7.1-arm64-arm-64bit
related packages: typing_extensions-4.14.1
commit: c8035874e