Search code examples
pythonpython-3.xpydantic-v2

Pydantic 2.7.0 Model accepting String datetime or None


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)

Solution

  • 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)