I am trying to add custom steps to the Pydantic model __init__
method.
Pseudo Sample Code :
class Model(BaseModel):
a: int
b: Optional[List[int]] = None
c: Optional[int] = None
def __init__(self, *args, **kwargs):
super.__init__(*args, **kwargs)
self.method()
def method(self):
assert self.a is not None, "Value for a is not set" # As a safety precaution let's say
self.c = sum(self.b) # Adds all the values in B and sets the sum to C
When I initialise the method
[In]: x = Model(a=1, b=[1, 2, 3, 4])
[Out]: pydantic.error_wrappers.ValidationError: 1 validation error for A
response -> Model -> 0
Value for a is not set (type=value_error)
I get this error even thou I have set both a and b in the model. Can someone help me out
The same problem works if the method is called outside the __init__
method, but I want the method to be called upon initialisation and not manually
Pseudo Sample Code :
class Model(BaseModel):
a: int
b: Optional[List[int]] = None
c: Optional[int] = None
def method(self):
assert self.a is not None, "Value for a is not set" # As a safety precaution let's say
self.c = sum(self.b) # Adds all the values in B and sets the sum to C
When I initialise the method
[In]: x = Model(a=1, b=[1, 2, 3, 4])
[Out]: Model(a=1, b=[1, 2 , 3 , 4], c=None)
[In]: x.method()
[Out]: Model(a=1, b=[1, 2 , 3 , 4], c=10)
Note: I'm restricted with the pydantic version
'1.10.14'
and cannot upgrade it for several reasons.
After getting a few answers and help, I went with the validator method. But I went through few hiccups, which I have explained below
Updated pseudo code :
from pydantic import validator
class Model(BaseModel):
a: int
b: Optional[List[int]] = None
c: Optional[int] = None
@root_validator(pre=False, allow_reuse=False)
def method(cls, values, **kwargs):
assert values["b"] is not None, "Value for b is not set"
values["c"] = sum(values["b"])
return values
def serialise(self):
...
// Creates protobuf message
@classmethod
def deserialize(cls, message) -> "Model":
...
// reads the protobuf message and creates the cls
return cls(
a = message.a
c = message.c
)
>>> x = Model(a=1, b=[1, 2, 3, 4]))
Model(a=1 b=[1, 2, 3, 4] c=10)
>>>
>>> x.serialise()
xxxxxxxxxxxxxxxxxxxxx
>>>
>>> y = Model.desirealise("xxxxxxxxxxxxxxxxxxxxx")
Value for b is not set
In this case, I don't want to run the validator when c is manually set during the model is initiated.
Hence, I set skip_on_failure
flag to True for the validator and I have made the below changes to prevent the check from failing.
from pydantic import validator
class Model(BaseModel):
a: int
b: Optional[List[int]] = None
c: Optional[int] = None
@root_validator(pre=False, allow_reuse=False)
def method(cls, values, **kwargs):
if values["c"] is not None:
return values
assert values["b"] is not None, "Value for b is not set"
values["c"] sum(values["b"])
return values
def serialise(self):
...
// Creates protobuf message
@classmethod
def deserialize(cls, message) -> "Model":
...
// reads the protobuf message and creates the cls
return cls(
a = message.a
c = message.c
)
>>> x = Model(a=1, b=[1, 2, 3, 4]))
Model(a=1 b=[1, 2, 3, 4] c=10)
>>>
>>> x.serialise()
xxxxxxxxxxxxxxxxxxxxx
>>>
>>> y = Model.desirealise("xxxxxxxxxxxxxxxxxxxxx")
Model(a=1, b=None, c=10)
This code resolves my issues and works properly with the system, but I want to know if this is a best practice or If any other way is there to do this.
If you want to both validate and add logic to your property during initialization, you can use model_validator
:
from pydantic import BaseModel
from pydantic import model_validator
class Model(BaseModel):
a: int
b: Optional[List[int]] = None
c: Optional[int] = None
@model_validator(mode='after')
def method(self):
assert self.a is not None, "Value for a is not set" # As a safety precausion let say
self.c = sum(self.b) # Adds all the value in B and sets the sum to C
print(Model(a=1, b=[1, 2, 3, 4]))
# a=1 b=[1, 2, 3, 4] c=10
But I think it's a better approach to separate responsabilities with @model_validator
and @computed_field
:
from pydantic import computed_field
from pydantic import model_validator
class Model(BaseModel):
a: int
b: Optional[List[int]] = None
@model_validator(mode='after')
def method(self):
assert self.a is not None, "Value for a is not set"
@computed_field
@property
def c(self) -> int:
return sum(self.b)
print(Model(a=1, b=[1, 2, 3, 4]))
# a=1 b=[1, 2, 3, 4] c=10
Since pydantic v1 don't offer computed_field
, you can still rely on model/field validations. In this case, the validator
decorator:
from pydantic import validator
class Model(BaseModel):
a: int
b: Optional[List[int]] = None
c: Optional[int] = None
@validator("c", always=True)
def method(cls, v, values, **kwargs):
assert values["a"] is not None, "Value for a is not set"
return sum(values["b"])
print(Model(a=1, b=[1, 2, 3, 4]))
# a=1 b=[1, 2, 3, 4] c=10