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

我需要一个变量 covars 包含未知数量的条目,其中每个条目是三个不同的自定义 Pydantic 模型之一。在这种情况下,每个条目都为我的应用程序描述了一个变量。

具体来说,我希望 covars 具有以下形式。这里显示了三个条目,即 variable1variable2variable3 ,代表三种不同类型的条目。但是,在部署时,应用程序必须允许接收三个以上的条目,并且并非所有条目类型都需要出现在请求中。

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] = {}

注意 IntVariableContVariable 之间的数据类型差异。

我的问题: 如何制作一个 Pydantic 模型,允许组合任意数量的 IntVariableContVariableCatVariable 类型的条目以获得我正在寻找的输出?

计划是使用此模型在数据发布到 API 时验证数据,然后将序列化版本存储到应用程序数据库(使用 ormar )。

stack overflow How to generate Pydantic model for multiple different objects
原文答案

答案:

作者头像

首先,由于您似乎没有使用预定义的键,您可以使用 custom root type ,它允许您在 pydantic 模型中拥有任意键名,如 here 所讨论的。接下来,您可以使用 Union ,它允许模型属性接受不同的类型(并且在定义时也忽略顺序)。因此,无论顺序如何,您都可以传递三个模型的多个条目。

由于 IntVariableContVariable 模型具有完全相同数量的属性和键名,当将 float 数字传递给 minmax 时,它们将转换为 int ,如pydantic 无法区分这两种模型。最重要的是, minmax 是 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 时,会引发所有三个模型的错误(而不是仅针对该特定模型引发错误),因此可以使用 Discriminated Unions ,如 this answer 。对于有区别的联合, "only one explicit error is raised in case of failure" 。下面的例子:

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 块中的三个模型解析字典中的每个项目/条目,类似于 this answer (Update 1) 中描述的内容.但是,这将需要遍历所有模型,或者在条目中有一个鉴别器(例如上面的 "model_type" ),指示您应该尝试解析哪个模型。

作者头像

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

我使用了其他几个功能来完成这项工作。首先,我将这三种类型设置为 Enum 来约束选项。其次,如果第一个选项出现在例如 StrictIntStrictFloat ,即如果我使用 StrictStr 。第三,我删除输入 python (其类型为 int )并使用通过 float 的自定义替换将其替换为 guess 类型的另一个字段 float

guess: Union[float,int,str]