Search code examples
pythonpydantic

Pydantic: Make field None in validator based on other field's value


I'm using the pydantic BaseModel with a validator like this:

from datetime import date
from typing import List, Optional
from pydantic import BaseModel, BaseConfig, validator

class Model(BaseModel):
    class Config(BaseConfig):
        allow_population_by_alias = True
        fields = {
            "some_date": {
                "alias": "some_list"
            }
        }
    some_date: Optional[date]
    some_list: List[date]

    @validator("some_date", pre=True, always=True)
    def validate_date(cls, value):
        if len(value) < 2: # here value is some_list
            return None
        return value[0] # return the first value - let's assume it's a date string

# This reproduces the problem
m = Model(some_list=['2019-01-03'])

I would like to compute the value of some_date based on the value of some_list and make it None if a certain condition met.

My JSON never contains the field some_date, it's always populated based on some_list hence pre=True, always=True. However the default validator for some_date will run after my custom one, which will fail if validate_date returns None.

Is there a way to create such a field which is only computed by another one and still can be Optional?


Solution

  • Update: As others pointed out, this can be done now with newer versions (>=0.20). See this answer. (Side note: even the OP's code works now, but doing it without alias is even better.)


    From skim reading documentation and source of pydantic, I tend to to say that pydantic's validation mechanism currently has very limited support for type-transformations (list -> date, list -> NoneType) within the validation functions.

    Taking a step back, however, your approach using an alias and the flag allow_population_by_alias seems a bit overloaded. some_date is needed only as a shortcut for some_list[0] if len(some_list) >= 2 else None, but it's never set independently from some_list. If that's really the case, why not opting for the following option?

    class Model(BaseModel):
        some_list: List[date] = ...
    
        @property 
        def some_date(self):
            return None if len(self.some_list) < 2 else self.some_list[0]