I'm trying to create a model that accepts a string datetime in a certain format, but if the string value is an empty string "", it should be in the model as type None.
I'm using the BeforeValidator which I think is the correct way to go.
With my code below, I expect the parse_datetime function to return None type and then pass it to the pydantic validator.
Can someone lead me in the right direction?
from pydantic import (
BaseModel,
Field,
ValidationError,
BeforeValidator,
)
from typing import Annotated, Any, Union
from datetime import datetime, date
def parse_datetime(value: str):
print(len(value.strip()))
if len(value.strip()) > 0:
try:
return datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
except Exception as e:
raise ValueError(str(e))
else:
print("returning None")
value = None
return value
date_time = Annotated[datetime, BeforeValidator(parse_datetime)]
class Model(BaseModel):
dt: date_time | None
using an empty string, I get a validation error
data = {"dt": ""}
try:
Model.model_validate(data)
except ValidationError as e:
print(e)
1 validation error for Model
dt
Input should be a valid datetime [type=datetime_type, input_value=None, input_type=NoneType]
however passing it a None type works
data = {"dt": None}
try:
Model.model_validate(data)
except ValidationError as e:
print(e)
I was confused as well why this didn't work. The reason is that the BeforeValidator
runs before the inner validation logic (see docs). If you had taken the PlainValidator
which runs instead of the inner validation logic (see docs), it works just fine.
So the problem with the BeforeValidator
is in the type of the annotation. You call the BeforeValidator
on the datatype datetime
only (Annotated[datetime, ...]
). So the inner validation logic accepts only datetime
and not None
.
If you change the annotation to Annotated[datetime | None, BeforeValidator(parse_datetime)]
it will work correctly.
from pydantic import (
BaseModel,
BeforeValidator,
)
from typing import Annotated
from datetime import datetime
def parse_datetime(value: str):
if value.strip():
try:
return datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
except Exception as e:
raise ValueError(str(e))
else:
return None
date_time = Annotated[datetime | None, BeforeValidator(parse_datetime)]
class Model(BaseModel):
dt: date_time
Model.model_validate({'dt': ' '})
# Model(dt=None)