I have created a Pydantic model for datetime that will handle parsing a JSON object that looks like { "date": "2021-07-01", "time": "12:36:23" }
into datetime(2021, 7, 1, 12, 36, 23)
. It also generates the correct JSON Schema for the model.
class TimestampWithSplit(RootModel):
root: datetime
@classmethod
def __get_pydantic_core_schema__(
cls, source: Type[Any], handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
return core_schema.chain_schema(
[
core_schema.typed_dict_schema(
{
"date": core_schema.typed_dict_field(core_schema.date_schema()),
"time": core_schema.typed_dict_field(core_schema.time_schema()),
}
),
core_schema.no_info_plain_validator_function(cls.validate_to_datetime),
]
)
@staticmethod
def validate_to_datetime(value: dict) -> datetime:
return datetime.combine(value["date"], value["time"])
I’m trying to now do two things:
- I now want to add descriptions to the generated json schema. Currently
TimestampWithSplit.model_json_schema()
returns
{'properties': {'date': {'format': 'date', 'title': 'Date', 'type': 'string'},
'time': {'format': 'time', 'title': 'Time', 'type': 'string'}},
'required': ['date', 'time'],
'type': 'object'}
and I want to add
{'properties': {'date': {'format': 'date', 'title': 'Date', 'type': 'string', 'description': 'ISO format date, blah blah'},
'time': {'format': 'time', 'title': 'Time', 'type': 'string', 'description': 'ISO format time, blah blah'}},
'required': ['date', 'time'],
'type': 'object'}
- Add a custom validator for the date field so it can parse single digit day and month numbers. I’d normally do this like this:
def validator(value: str):
try:
return datetime.strptime(value, "%Y-%m-%d").date()
except ValueError:
return None
and add that validator as a plain validator on the field. But I’m not sure how to incorporate that into what I’ve got.
Am I going down the wrong path? How do I have a model that has date and time as separate fields but returns a datetime
from model_validate_json
while also customising the JSON schema and date validation?
0
I may be unclear on your goal, so I apologize if this isn’t applicable, but it looks like you would be better off using a BaseModel
with date
and time
properties rather than using your RootModel
solution. Given code like this:
import datetime
from pydantic import BaseModel, Field, computed_field, field_validator
class TimestampWithSplit(BaseModel):
date: datetime.date = Field(
..., description="ISO format date", repr=False, exclude=True
)
time: datetime.time = Field(
..., description="ISO format time", repr=False, exclude=True
)
@computed_field
@property
def timestamp(self) -> datetime.datetime:
return datetime.datetime.combine(self.date, self.time)
We can initialize it from a dictionary:
>>> t=TimestampWithSplit.model_validate({'date': '2024-09-24', 'time': '11:00:00'})
>>> t
TimestampWithSplit(timestamp=datetime.datetime(2024, 9, 24, 11, 0))
We can initialize it using keyword parameters:
>>> t=TimestampWithSplit(date='2024-09-24', time='11:00:00')
>>> t
TimestampWithSplit(timestamp=datetime.datetime(2024, 9, 24, 11, 0))
If you wanted to support single digit months/days, like 2024-9-1
, you could add a field validator for the date
field. Maybe something like this:
@field_validator("date", mode="before")
@classmethod
def validate_date(cls, v: str | datetime.date) -> datetime.date:
if isinstance(v, datetime.date):
return v
elif isinstance(v, str):
return datetime.datetime.strptime(v, "%Y-%m-%d").date()
else:
raise ValueError("invalid date")
This gives you the schema you want, including the field descriptions:
>>> print(json.dumps(t.schema(), indent=2))
{
"properties": {
"date": {
"description": "ISO format date",
"format": "date",
"title": "Date",
"type": "string"
},
"time": {
"description": "ISO format time",
"format": "time",
"title": "Time",
"type": "string"
}
},
"required": [
"date",
"time"
],
"title": "TimestampWithSplit",
"type": "object"
}
And the serialization of this type includes only the combined timestamp
field:
>>> t.model_dump_json()
'{"timestamp":"2024-09-24T11:00:00"}'
1