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

sve*_*del 6 python validation nested pydantic fastapi

我需要一个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'},
                }
        }
Run Code Online (Sandbox Code Playgroud)

我已成功创建三种不同的条目类型作为三个单独的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] = {}
Run Code Online (Sandbox Code Playgroud)

IntVariable请注意和之间的数据类型差异ContVariable

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

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

Chr*_*ris 6

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

由于IntVariableContVariable模型具有完全相同数量的属性和键名称,因此当将float数字传递给min和 时max,它们会转换为int,因为 pydantic 无法区分这两个模型。除此之外,minandmax是 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
Run Code Online (Sandbox Code Playgroud)

输入示例如下所示。确保[]在键入 时使用方括号options Set,否则如果{}使用大括号,FastAPI 会发出错误消息。

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
Run Code Online (Sandbox Code Playgroud)

更新

由于上述情况,当ValidationError为其中一个模型引发 a 时,会引发所有三个模型的错误(而不是仅针对该特定模型引发错误),因此可以使用Discriminated Unions,如此答案中所述。对于受歧视的工会,“在失败的情况下只会出现一个明确的错误”。下面的例子:

应用程序.py

{
   "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
   }
}
Run Code Online (Sandbox Code Playgroud)

测试数据

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
Run Code Online (Sandbox Code Playgroud)

另一种解决方案是使用依赖函数,在该函数中迭代字典并尝试使用块中的三个模型解析字典中的每个项目/条目try-catch,类似于此答案中描述的内容。但是,这需要循环遍历所有模型,或者在条目中包含鉴别器(例如"model_type"上面的),指示您应该尝试解析哪个模型。