Reputation: 577
I am trying to set a super-class field in a subclass using validator as follows:
Approach 1
from typing import List
from pydantic import BaseModel, validator, root_validator
class ClassSuper(BaseModel):
field1: int = 0
class ClassSub(ClassSuper):
field2: List[int]
@validator('field1')
def validate_field1(cls, v, values):
return len(values["field2"])
sub = ClassSub(field2=[1, 2, 3])
print(sub.field1) # It prints 0, but expected it to print 3
If I run the code above it prints 0
, but I expected it to print 3
(which is basically len(field2)
). However, if I use @root_validator()
instead, I get the expected result.
Approach 2
from typing import List
from pydantic import BaseModel, validator, root_validator
class ClassSuper(BaseModel):
field1: int = 0
class ClassSub(ClassSuper):
field2: List[int]
@root_validator()
def validate_field1(cls, values):
values["field1"] = len(values["field2"])
return values
sub = ClassSub(field2=[1, 2, 3])
print(sub.field1) # This prints 3, as expected
New to using pydantic and I am bit puzzled what I am doing wrong with the Approach 1. Thank you for your help.
Upvotes: 3
Views: 855
Reputation: 18458
The reason your Approach 1 does not work is because by default, validators for a field are not called, when the value for that field is not supplied (see docs).
Your validate_field1
is never even called. If you add always=True
to your @validator
, the method is called, even if you don't provide a value for field1
.
However, if you try that, you'll see that it will still not work, but instead throw an error about the key "field2"
not being present in values
.
This in turn is due to the fact that validators are called in the order they were defined. In this case, field1
is defined before field2
, which means that field2
is not yet validated by the time validate_field1
is called. And values
only contains previously-validated fields (see docs). Thus, at the time validate_field1
is called, values
is simply an empty dictionary.
Using the @root_validator
is the correct approach here because it receives the entire model's data, regardless of whether or not field values were supplied explicitly or by default.
And just as a side note: If you don't need to specify any parameters for it, you can use @root_validator
without the parantheses.
And as another side note: If you are using Python 3.9+
, you can use the regular list
class as the type annotation. (See standard generic alias types) That means field2: list[int]
without the need for typing.List
.
Hope this helps.
Upvotes: 3