How can I make a pydantic validator validate input against another class object (not another input arg, but really another object sitting in the class definition, such as a property, or function). --> I would love to call self.xx
.
So, I know how to validate one input arg against another input arg (e.g., validating that the color
is green if plant
is a tree):
from pydantic import BaseModel, validator
class MyClass(BaseModel):
plant: str
color: str
@validator('color')
def tree_is_green(cls, v, values, **kwargs):
if values['plant'] == 'tree':
if v != 'green':
raise ValueError('tree must be green')
return v
It will bark at blue trees, etc., while roses can be of any color:
my_plant = MyClass(color='blue', plant='rose') # works
my_plant = MyClass(color='blue', plant='tree') # fails
my_plant = MyClass(color='green', plant='tree') # works
So far, so good.
Now, how can I generalize this, and use a property
? Say the validator becomes a bit more complicated than just a ==
, and I don't want to copy/paste all the functionality of my functions and properties into the validator.
from pydantic import BaseModel, validator
class MyClass(BaseModel):
plant: str
color: str
@property
def is_tree(self) -> bool:
return self.plant == 'tree'
@validator('color')
def tree_is_green(cls, v, values, **kwargs):
if values['is_tree'] is True:
if v != 'green':
raise ValueError('tree must be green')
return v
The above code fails always, no matter what instance I try to create:
my_plant = MyClass(color='green', plant='tree')
>>> KeyError: 'is_tree'
I tried to replace the values[]
by a desperate attempt to cls
, but to no avail. Using the below will never return an error - and it's simply not validating anything:
from pydantic import BaseModel, validator
class MyClass(BaseModel):
plant: str
color: str
@property
def is_tree(self) -> bool:
return self.plant == 'tree'
@validator('color')
def tree_is_green(cls, v, values, **kwargs):
if cls.is_tree is True:
if v != 'green':
raise ValueError('tree must be green')
return v
leads to unwanted output:
my_plant = MyClass(color='blue', plant='tree')
print(my_plant)
plant='tree' color='blue'
Is there any way to construct a validator that is based on functionality/objects of the class, rather than only the input args, or static variables? Conceptually, it should work, as the property is_tree
does not depend on color
, so any form of circular reference is excluded.. and if that blew up, I would humbly accept it and think of a better validator..
Since a pydantic validator
is a classmethod
, it unfortunately won't be able to use the @property
as you're expecting. The @property
is designed to work on an instance of MyClass
, similar to any other instance method; however, during the "validation" stage of pydantic, the instance isn't yet created, and it's calling validator
s as class methods, so it only has access to other classmethod
s and class attributes.
You could switch is_tree
to a classmethod
and pass it the plant value:
from pydantic import BaseModel, validator
class MyClass(BaseModel):
plant: str
color: str
@classmethod
def is_tree(cls, plant_val) -> bool:
return plant_val == 'tree'
@validator('color')
def tree_is_green(cls, v, values):
if cls.is_tree(values['plant']) and v != 'green':
raise ValueError('tree must be green')
return v
MyClass(color='blue', plant='rose') # no problems
MyClass(color='green', plant='tree') # no problems
MyClass(color='blue', plant='tree') # ValidationError: color -> tree must be green