我需要一个变量
covars
,其中包含未知数量的条目,其中每个条目都是三个不同的自定义Pydantic
模型之一。在本例中,每个条目都描述了我的应用程序的一个变量。
具体来说,我希望
covars
具有以下形式。这里显示了三个条目,即variable1
、variable2
和variable3
,代表三种不同类型的条目。不过,在部署时,应用程序必须允许接收三个以上的条目,并且并非所有条目类型都需要出现在请求中。
covars = {
'variable1': # type: integer
{
'guess': 1,
'min': 0,
'max': 2,
},
'variable2': # type: continuous
{
'guess': 12.2,
'min': -3.4,
'max': 30.8,
},
'variable3': # type: categorical
{
'guess': 'red',
'options': {'red', 'blue', 'green'},
}
}
我已经成功创建了三种不同的条目类型作为三个独立的
Pydantic
模型
import pydantic
from typing import Set, Dict, Union
class IntVariable(pydantic.BaseModel):
guess: int
min: int
max: int
class ContVariable(pydantic.BaseModel):
guess: float
min: float
max: float
class CatVariable(pydantic.BaseModel):
guess: str
options: Set[str] = {}
注意
IntVariable
和 ContVariable
之间的数据类型差异。
我的问题: 如何制作一个
Pydantic
模型,允许组合任意数量的 IntVariable
、ContVariable
和 CatVariable
类型的条目来获得我正在寻找的输出?
计划是使用此模型在数据发布到 API 时验证数据,然后将序列化版本存储到应用程序数据库(使用
ormar
)。
首先,由于您似乎没有使用预定义的键,因此您可以使用自定义根类型,它允许您在 pydantic 模型中拥有任意键名称,如此处所述。接下来,您可以使用
Union
,它允许模型属性接受不同的类型(并且在定义时也忽略顺序)。因此,您可以传递三个模型的多个条目,无论顺序如何。
由于
IntVariable
和 ContVariable
模型具有完全相同数量的属性和键名称,因此当将 float
数字传递给 min
和 max
时,它们会转换为 int
,因为没有办法pydantic 来区分这两种模型。最重要的是,min
和max
是Python中的保留关键字;因此,最好更改它们,如下所示。
from typing import Dict, Set, Union
from pydantic import BaseModel
app = FastAPI()
class IntVariable(BaseModel):
guess: int
i_min: int
i_max: int
class ContVariable(BaseModel):
guess: float
f_min: float
f_max: float
class CatVariable(BaseModel):
guess: str
options: Set[str]
class Item(BaseModel):
__root__: Union [IntVariable, ContVariable, CatVariable]
@app.post("/upload")
async def upload(covars: Dict[str, Item]):
return covars
输入示例如下所示。确保在输入
[]
options
时使用方括号 Set
,否则如果使用大括号 {}
,FastAPI 会发出警告。
{
"variable1":{
"guess":1,
"i_min":0,
"i_max":2
},
"variable2":{
"guess":"orange",
"options":["orange", "yellow", "brown"]
},
"variable3":{
"guess":12.2,
"f_min":-3.4,
"f_max":30.8
},
"variable4":{
"guess":"red",
"options":["red", "blue", "green"]
},
"variable5":{
"guess":2.15,
"f_min":-1.75,
"f_max":11.8
}
}
鉴于上述情况,当为其中一个模型引发
ValidationError
时,所有三个模型都会引发错误(而不是仅针对该特定模型引发错误),可以使用 Discrimination Unions,如 中所述这个答案。对于受歧视联合,“在失败的情况下只会引发一个明确的错误”。下面的例子:
app.py
from fastapi import FastAPI
from typing import Dict, Set, Union
from pydantic import BaseModel, Field
from typing import Literal
app = FastAPI()
class IntVariable(BaseModel):
model_type: Literal['int']
guess: int
i_min: int
i_max: int
class ContVariable(BaseModel):
model_type: Literal['cont']
guess: float
f_min: float
f_max: float
class CatVariable(BaseModel):
model_type: Literal['cat']
guess: str
options: Set[str]
class Item(BaseModel):
__root__: Union[IntVariable, ContVariable, CatVariable] = Field(..., discriminator='model_type')
@app.post("/upload")
async def upload(covars: Dict[str, Item]):
return covars
测试数据
{
"variable1":{
"model_type": "int",
"guess":1,
"i_min":0,
"i_max":2
},
"variable2":{
"model_type": "cat",
"guess":"orange",
"options":["orange", "yellow", "brown"]
},
"variable3":{
"model_type": "cont",
"guess":12.2,
"f_min":-3.4,
"f_max":30.8
},
"variable4":{
"model_type": "cat",
"guess":"red",
"options":["red", "blue", "green"]
},
"variable5":{
"model_type": "cont",
"guess":2.15,
"f_min":-1.75,
"f_max":11.8
}
}
另一种解决方案是使用 dependency 函数,您可以在其中迭代字典并尝试使用
try-catch
块中的三个模型解析字典中的每个项目/条目,类似于 这个答案 中描述的内容。但是,这需要循环遍历所有模型,或者在条目中包含鉴别器(例如上面的"model_type"
),指示您应该尝试解析哪个模型。
我最终使用自定义验证器解决了问题。将其添加到此处以补充@Chris 的解决方案。
我使用了一些其他功能来完成这项工作。首先,我将三种类型设置为
Enum
来约束选项。其次,我使用 StrictInt
、StrictFloat
和 StrictStr
来规避以下挑战:如果第一个选项出现在例如python
是 int
,即如果我要使用 float
。第三,我删除输入 guess
(类型为 float
),并通过 guess: Union[float,int,str]
使用自定义替换将其替换为 vtype
类型的另一个字段 VarType
。type