I am trying to use a field_validator to validate a field based on the presence of another, optional field. So this is really about validating one input based on another input (the classic "password1/password2" tutorial case, albeit validating password1 on optional password2).
Example: If a given plant='flower', then it must have a color (which is optional, because other plants may not have a color).
from typing import Optional
from pydantic import BaseModel, field_validator
from pydantic_core.core_schema import FieldValidationInfo
class MyClass(BaseModel):
plant: str
color: Optional[str] = None
@field_validator('plant')
def flowers_have_color(cls, v, info: FieldValidationInfo):
if v == 'flower':
if info.data['color'] is None:
raise ValueError("if 'plant' is a flower, it must have a color")
return v
Expected behavior:
>>> MyClass(plant='tree') # ok
MyClass(plant='tree', color=None)
>>> MyClass(plant='tree', color='red') # ok
MyClass(plant='tree', color='red')
>>> MyClass(plant='flower') # raise
>>> MyClass(plant='flower', color='red') # ok
MyClass(plant='flower', color='red')
This raises the following error:
MyClass(plant='flower', color='red')
>> KeyError: 'color'
While the above code invokes the field_validator when passing a "flower" plant (as expected), it does not see the color inside the validator, because the color is Optional. Indeed we see
print(info.data.keys())
dict_keys([])
How can I make the optional field color available to the field_validator (in general, or at least if it is provided)?
EDIT: As per comment below (thanks to Yurii Motov) this can be solved by using a model_validator (see below). But seems like an overkill to me, given I really just want to validate a single field (no need for self, classmethods or objects, etc.), and anything needed for that validation is input to the class constructor. It feels weird that field_validator would not be able to work in this situation - and vice versa, if validating "any input against another input" were not to be possible with field_validator, then why have it in pydantic in the first place and not replace any and all field_validators by model_validators?
@model_validator(mode='after')
def plants_must_have_color(self):
if self.plant == 'flower':
if self.color is None:
raise ValueError("'flower' must have a color")
return self