Reputation: 426
How do you update multiple properties on a pydantic model that are validated together and dependent upon each other?
Here is a contrived but simple example:
from pydantic import BaseModel, root_validator
class Example(BaseModel):
a: int
b: int
@root_validator
def test(cls, values):
if values['a'] != values['b']:
raise ValueError('a and b must be equal')
return values
class Config:
validate_assignment = True
example = Example(a=1, b=1)
example.a = 2 # <-- error raised here because a is 2 and b is still 1
example.b = 2 # <-- don't get a chance to do this
Error:
ValidationError: 1 validation error for Example
__root__
a and b must be equal (type=value_error)
Both a
and b
having a value of 2
is valid, but they can't be updated one at a time without triggering the validation error.
Is there a way to put the validation on hold until both are set? Or a way to somehow update both of them at the same time? Thanks!
Upvotes: 4
Views: 5710
Reputation: 426
I found a couple solutions that works well for my use case.
__dict__
of the pydantic instance directly if it passes -- see update
methoddelay_validation
methodfrom pydantic import BaseModel, root_validator
from contextlib import contextmanager
import copy
class Example(BaseModel):
a: int
b: int
@root_validator
def enforce_equal(cls, values):
if values['a'] != values['b']:
raise ValueError('a and b must be equal')
return values
class Config:
validate_assignment = True
def update(self, **kwargs):
self.__class__.validate(self.__dict__ | kwargs)
self.__dict__.update(kwargs)
@contextmanager
def delay_validation(self):
original_dict = copy.deepcopy(self.__dict__)
self.__config__.validate_assignment = False
try:
yield
finally:
self.__config__.validate_assignment = True
try:
self.__class__.validate(self.__dict__)
except:
self.__dict__.update(original_dict)
raise
example = Example(a=1, b=1)
# ================== This didn't work: ===================
# example.a = 2 # <-- error raised here because a is 2 and b is still 1
# example.b = 2 # <-- don't get a chance to do this
# ==================== update method: ====================
# No error raised
example.update(a=2, b=2)
# Error raised as expected - a and b must be equal
example.update(a=3, b=4)
# Error raised as expected - a and b must be equal
example.update(a=5)
# # =============== delay validation method: ===============
# No error raised
with example.delay_validation():
example.a = 2
example.b = 2
# Error raised as expected - a and b must be equal
with example.delay_validation():
example.a = 3
example.b = 4
# Error raised as expected - a and b must be equal
with example.delay_validation():
example.a = 5
Upvotes: 4
Reputation: 783
You can make a workaround building a setter.
from pydantic import BaseModel, root_validator
class Example(BaseModel):
a: int
b: int
@root_validator
def test(cls, values):
if values['a'] != values['b']:
raise ValueError('a and b must be equal')
return values
class Config:
validate_assignment = True
def set_a_and_b(self, value):
self.Config.validate_assignment = False
self.a, self.b = value, value
self.Config.validate_assignment = True
PoC:
>>> example = Example(a=1, b=1)
>>> example.a = 2
Traceback (most recent call last):
File "D:\temp\venv\lib\site-packages\IPython\core\interactiveshell.py", line 3398, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-4-950b5db07c46>", line 1, in <cell line: 1>
example.a =2
File "pydantic\main.py", line 393, in pydantic.main.BaseModel.__setattr__
pydantic.error_wrappers.ValidationError: 1 validation error for Example
__root__
a and b must be equal (type=value_error)
>>> example.set_a_and_b(2) # <========= workaround
>>> example
Example(a=2, b=2)
>>> example.a = 3
Traceback (most recent call last):
File "D:\temp\venv\lib\site-packages\IPython\core\interactiveshell.py", line 3398, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-8-d93e8eb8a0e3>", line 1, in <cell line: 1>
example.a = 3
File "pydantic\main.py", line 393, in pydantic.main.BaseModel.__setattr__
pydantic.error_wrappers.ValidationError: 1 validation error for Example
__root__
a and b must be equal (type=value_error)
But maybe in your real case you should use some setters and getters instead (or with) standard validation
Upvotes: 1