如何为多个不同对象生成 Pydantic 模型

问题描述 投票:0回答:2

我需要一个变量

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
)。

python validation nested fastapi pydantic
2个回答
6
投票

首先,由于您似乎没有使用预定义的键,因此您可以使用自定义根类型,它允许您在 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"
),指示您应该尝试解析哪个模型。


2
投票

我最终使用自定义验证器解决了问题。将其添加到此处以补充@Chris 的解决方案。

我使用了一些其他功能来完成这项工作。首先,我将三种类型设置为

Enum
来约束选项。其次,我使用
StrictInt
StrictFloat
StrictStr
来规避以下挑战:如果第一个选项出现在例如
python
int
,即如果我要使用
float
。第三,我删除输入
guess
(类型为
float
),并通过
guess: Union[float,int,str]
使用自定义替换将其替换为
vtype
类型的另一个字段
VarType
type
    

© www.soinside.com 2019 - 2024. All rights reserved.