英文:
How to set pydantic model minimum size
问题
我有一个以下的Pydantic模型:
class model(BaseModel):
name: Optional[str]
age: Optional[int]
gender: Optional[str]
并且使用这个模型验证请求体。但是想要将Pydantic模型的最小大小设置为1,以便端点不会处理空输入。
@app.post("my_url")
def test(req: dict=model):
一些代码
可以在请求体中使用长度函数,但尝试找到一些Pydantic的方法。请帮忙!
英文:
I have a pydantic model below:
class model(BaseModel):
name: Optional[str]
age: Optional[int]
gender: Optional[str]
and validating the request body using this model. but want to set minimum size of pydantic model to be 1 so endpoint should not process empty input
@app.post("my_url")
def test(req: dict=model):
some code
can use length function inside body but trying to find some pydantic way.
Please help !
答案1
得分: 3
以下是您要翻译的代码部分:
最直接的解决方案是一个 [root 验证器][1],如 M.O. 在上面的评论中提到的:
```python
from typing import Any, Optional
from pydantic import BaseModel, root_validator
class Model(BaseModel):
foo: Optional[str]
bar: Optional[int]
baz: Optional[float]
@root_validator
def ensure_one_field_is_set(cls, data: dict[str, Any]) -> dict[str, Any]:
if all(value is None for value in data.values()):
raise ValueError("至少必须提供一个值")
return data
演示:
from pydantic import ValidationError
print(Model(bar=69).json(indent=4))
try:
Model()
except ValidationError as err:
print(err.json(indent=4))
输出:
{
"foo": null,
"bar": 69,
"baz": null
}
[
{
"loc": [
"__root__"
],
"msg": "至少必须提供一个值",
"type": "value_error"
}
]
然而,这种方法存在一个潜在的陷阱。
根验证器按照它们在模型中注册的顺序被调用。这意味着,如果您子类化 Model
并添加一个新的 root 验证器,该验证器将设置一个字段为非-None
值,那么之前的根验证器仍然会首先被调用,新的验证器在介入之前将引发错误:
class SubModel(Model):
@root_validator
def set_baz_if_all_are_none(cls, data: dict[str, Any]) -> dict[str, Any]:
if all(value is None for value in data.values()):
data["baz"] = 3.14
return data
SubModel() # 将引发错误,`baz` 不会被设置
当然,这是一个假设的示例,但仍然值得注意。您可以通过明确 覆盖 先前的验证器方法来解决此问题:
class SubModel(Model):
@root_validator
def set_baz_if_all_are_none(cls, data: dict[str, Any]) -> dict[str, Any]:
if all(value is None for value in data.values()):
data["baz"] = 3.14
return data
@root_validator
def ensure_one_field_is_set(cls, data: dict[str, Any]) -> dict[str, Any]:
return data # 什么也不做
print(SubModel()) # foo=None bar=None baz=3.14
另一种方法是在自己的 __init__
方法中执行检查并手动构建一个 ValidationError
:
from typing import Any, Optional
from pydantic import BaseModel, ValidationError
from pydantic.error_wrappers import ErrorWrapper
class Model(BaseModel):
foo: Optional[str]
bar: Optional[int]
baz: Optional[float]
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
if all(value is None for _field, value in self):
raise ValidationError(
[
ErrorWrapper(
ValueError("至少必须提供一个值"),
"__root__",
)
],
self.__class__,
)
现在,使用 Model()
将产生与上面根验证器相同的错误。
<details>
<summary>英文:</summary>
The most straightforward solution is a [root validator][1] as mentionend by M.O. in the comments above:
```python
from typing import Any, Optional
from pydantic import BaseModel, root_validator
class Model(BaseModel):
foo: Optional[str]
bar: Optional[int]
baz: Optional[float]
@root_validator
def ensure_one_field_is_set(cls, data: dict[str, Any]) -> dict[str, Any]:
if all(value is None for value in data.values()):
raise ValueError("At least one value must be provided")
return data
Demo:
from pydantic import ValidationError
print(Model(bar=69).json(indent=4))
try:
Model()
except ValidationError as err:
print(err.json(indent=4))
Output:
{
"foo": null,
"bar": 69,
"baz": null
}
[
{
"loc": [
"__root__"
],
"msg": "At least one value must be provided",
"type": "value_error"
}
]
However there is one potential pitfall with this approach.
Root validators are called in the order they were registered with model. This means, if you subclass Model
and add a new root validator that sets one of the fields to not-None
, the previous root validator will still be called first and it will raise an error before the new validator gets a chance to intervene:
class SubModel(Model):
@root_validator
def set_baz_if_all_are_none(cls, data: dict[str, Any]) -> dict[str, Any]:
if all(value is None for value in data.values()):
data["baz"] = 3.14
return data
SubModel() # will cause an error, `baz` will not be set
Granted, this is a contrived example, but still something to keep in mind. You can work around this by explicitly overriding the previous validator method:
class SubModel(Model):
@root_validator
def set_baz_if_all_are_none(cls, data: dict[str, Any]) -> dict[str, Any]:
if all(value is None for value in data.values()):
data["baz"] = 3.14
return data
@root_validator
def ensure_one_field_is_set(cls, data: dict[str, Any]) -> dict[str, Any]:
return data # do nothing
print(SubModel()) # foo=None bar=None baz=3.14
An alternative would be to simply do the check in your own __init__
method and manually construct a ValidationError
:
from typing import Any, Optional
from pydantic import BaseModel, ValidationError
from pydantic.error_wrappers import ErrorWrapper
class Model(BaseModel):
foo: Optional[str]
bar: Optional[int]
baz: Optional[float]
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
if all(value is None for _field, value in self):
raise ValidationError(
[
ErrorWrapper(
ValueError("At least one value must be provided"),
"__root__",
)
],
self.__class__,
)
Doing Model()
would now produce the same error as with the root validator above.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论