英文:
Using Pydantic parent attribute to validate child?
问题
class Item(BaseModel):
value: int
@validator("value")
@classmethod
def validate_value(cls, value, values):
multiplier = values.get("multiplier", 1)
return value * multiplier
英文:
Is it possible to use a containing object's attribute during the validation of a child object in a pydantic model?
Given the json data:
# example.json
{
"multiplier": 5,
"field_1": {
"value": 1
},
"field_2": {
"value": 2
}
}
and the corresponding Pydantic model:
# example.py
from pydantic import BaseModel, validator
class Item(BaseModel):
value: int
class Container(BaseModel):
multiplier: int
field_1: Item
field_2: Item
is it possible to use the Container
object's multiplier
attribute during validation of the Item
values? For instance, I'd like to do something like this to Item
at runtime:
class Item(BaseModel):
value: int
@validator("value")
@classmethod
def validate_value(cls, value):
return value # * multiplier # <--- can I get access to Container's multiplier here?
but I cannot determine if it possible to get access to the Container.multiplier
value in a case like this?
In my actual use case, the nesting is much, much deeper and so I would prefer not have the validator up at the Container
level as access becomes fairly complicated, but I also do not want to duplicate the multiplier
value down at the Item level? Is there any way to pass parameters up and down the object hierarchy within a model of this sort?
答案1
得分: 1
The requested code has been translated.
英文:
Simplest approach is to perform validation on the parent instead of the child:
from pydantic import BaseModel, validator
class Item(BaseModel):
value: int
class Container(BaseModel):
multiplier: int
field_1: Item
field_2: Item
@validator("field_1", "field_2")
def validate_value(cls, v, values):
"""Validate each item"""
m = values["multiplier"]
If you want to define the validator on the child, you can create a function and then call the validation function from the parent:
class Item(BaseModel):
value: int
@classmethod
def validate_value(cls, v, **kwargs):
"""Validate each item"""
m = kwargs.get("multiplier")
if m * v.value < 10:
raise ValueError
return v
class Container(BaseModel):
multiplier: int
field_1: Item
field_2: Item
@validator("field_1", "field_2", pre=False)
def validate_items(cls, v, values):
return Item.validate_value(v, **values)
Another alternative is to pass the multiplier
as a private model attribute to the children, then the children can use the pydantic validation function, but you'll still need to assign dynamically to the children:
from pydantic import BaseModel, Field, validator
class Item(BaseModel):
multiplier: int # exclude from parent serialization, workaround for validation
value: int
@validator("value", pre=False)
def validate_value(cls, v, values):
"""Validate each item"""
m = values["multiplier"]
if m * v < 10:
raise ValueError
return v
class Container(BaseModel):
multiplier: int
field_1: Item = Field(..., exclude={'multiplier'})
field_2: Item = Field(..., exclude={'multiplier'})
@validator("field_1", "field_2", pre=True)
def validate_items(cls, v, values):
# Construct from a value, another workaround
if isinstance(v, int):
return Item(value=v, multiplier=values["multiplier"])
elif isinstance(v, Item):
return Item(value=v.value, multiplier=values["multiplier"])
if __name__ == '__main__':
c = Container(
multiplier=1,
field_1=11,
field_2=22
)
答案2
得分: 1
Pydantic 2,结合 Python 的 contextvars
库,提供了一个良好且清晰的解决方案。使用 Pydantic 包装模型验证器,您可以在开始验证子项之前设置上下文变量,然后在验证后清理上下文变量。示例:
from pydantic import BaseModel, field_validator, model_validator
from contextvars import ContextVar
context_multiplier = ContextVar("context_multiplier")
class Item(BaseModel):
value: int
@field_validator("value")
@classmethod
def validate_value(cls, value):
multiplier = context_multiplier.get()
if value % multiplier != 0:
raise ValueError(f"not a multiple of {multiplier}")
return value
class Container(BaseModel):
multiplier: int
field_1: Item
field_2: Item
@model_validator(mode="wrap")
@classmethod
def validate_model(cls, v, handler):
try:
multiplier = int(v["multiplier"])
except KeyError:
raise ValueError("multiplier required")
token = context_multiplier.set(v["multiplier"])
try:
return handler(v)
finally:
context_multiplier.reset(token)
使用正确的输入进行验证成功:
>>> Container.model_validate(
... {"multiplier": 3, "field_1": {"value": 9}, "field_2": {"value": 12}}
... )
Container(multiplier=3, field_1=Item(value=9), field_2=Item(value=12))
验证会检查输入与乘数的关系:
>>> Container.model_validate(
... {"multiplier": 3, "field_1": {"value": 9}, "field_2": {"value": 11}}
... )
pydantic_core._pydantic_core.ValidationError: 1 validation error for Container
field_2.value
Value error, not a multiple of 3 [type=value_error, input_value=11, input_type=int]
For further information visit https://errors.pydantic.dev/2.1.2/v/value_error
使用 ContextVar 意味着这个解决方案也应该适用于异步代码。
英文:
Pydantic 2, combined with Python's contextvars
library, provides a good and clean solution. Using a Pydantic wrap model validator, you can set a context variable before starting validation of the children, then clean up the context variable after validation. Example:
from pydantic import BaseModel, field_validator, model_validator
from contextvars import ContextVar
context_multiplier = ContextVar("context_multiplier")
class Item(BaseModel):
value: int
@field_validator("value")
@classmethod
def validate_value(cls, value):
multiplier = context_multiplier.get()
if value % multiplier != 0:
raise ValueError(f"not a multiple of {multiplier}")
return value
class Container(BaseModel):
multiplier: int
field_1: Item
field_2: Item
@model_validator(mode="wrap")
@classmethod
def validate_model(cls, v, handler):
try:
multiplier = int(v["multiplier"])
except KeyError:
raise ValueError("multiplier required")
token = context_multiplier.set(v["multiplier"])
try:
return handler(v)
finally:
context_multiplier.reset(token)
Validation succeeds with correct inputs:
>>> Container.model_validate(
... {"multiplier": 3, "field_1": {"value": 9}, "field_2": {"value": 12}}
... )
Container(multiplier=3, field_1=Item(value=9), field_2=Item(value=12))
Validation checks the inputs against the multiplier:
>>> Container.model_validate(
... {"multiplier": 3, "field_1": {"value": 9}, "field_2": {"value": 11}}
... )
pydantic_core._pydantic_core.ValidationError: 1 validation error for Container
field_2.value
Value error, not a multiple of 3 [type=value_error, input_value=11, input_type=int]
For further information visit https://errors.pydantic.dev/2.1.2/v/value_error
Using ContextVar means this solution should also work with async code.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论