我定义了一个标准的API响应类型,如下所示:

class ApiResponse(BaseModel):
    success: bool
    data: Optional[List[Any]] = []
    message: Optional[str] = None
    meta: Optional[dict] = {}

但是,根据终结点的不同,data列表将是预定义的Pydatics类型的列表.例如,如果终结点是/users,我希望Pydtic强制data列表必须是包含User个对象的列表.我try 修改ApiResponse类,如下所示:

class User(BaseModel):
    name: str
    favorite_sandwich: str

class ApiResponse(SubType):
    success: bool
    data: Optional[List[SubType]] = []
    message: Optional[str] = None
    meta: Optional[dict] = {}

然后,我希望能够使用以下命令定义端点:

@router.get("/users", response_model=ApiResponse[User])
async def get_groups() -> ApiResponse[User]:
    users = models.User.all()
    count = models.User.count()
    return {"success" : True, "data": users, "meta" : {"count": count}}

有什么办法可以做到这一点吗?

推荐答案

是的,有.这就是一般的generics和特别的generic models的目的.

例如,您创建了一个type variable M,并将其上限设置为BaseModel,然后定义一个由该类型变量参数化的GenericModel类,并用List[M]注释其data字段

示例:

from typing import Any, Dict, Generic, List, Optional, TypeVar

from pydantic import BaseModel, Field
from pydantic.generics import GenericModel

M = TypeVar("M", bound=BaseModel)


class ApiResponse(GenericModel, Generic[M]):
    success: bool
    data: List[M] = Field(default_factory=list)
    message: Optional[str] = None
    meta: Dict[str, Any] = Field(default_factory=dict)


class User(BaseModel):
    name: str
    favorite_sandwich: str


class Point2D(BaseModel):
    x: float
    y: float

然后,您可以这样定义您的路由,例如:

from fastapi import FastAPI

# ... import ApiResponse, Point2D, User


app = FastAPI()


@app.get("/users", response_model=ApiResponse[User])
async def get_groups() -> Dict[str, Any]:
    users = [
        User(name="Alice", favorite_sandwich="BLT"),
        User(name="Bob", favorite_sandwich="Ham&Cheese"),
    ]
    count = len(users)
    return {
        "success": True,
        "data": users,
        "meta": {"count": count},
    }


@app.get("/points")
async def get_points() -> ApiResponse[Point2D]:
    points = [Point2D(x=3.14, y=0), Point2D(x=-100.1, y=420.69)]
    return ApiResponse(success=True, data=points)

向这些端点发送GET个请求会产生以下响应正文:

{
  "success": true,
  "data": [
    {
      "name": "Alice",
      "favorite_sandwich": "BLT"
    },
    {
      "name": "Bob",
      "favorite_sandwich": "Ham&Cheese"
    }
  ],
  "message": null,
  "meta": {
    "count": 2
  }
}
{
  "success": true,
  "data": [
    {
      "x": 3.14,
      "y": 0
    },
    {
      "x": -100.1,
      "y": 420.69
    }
  ],
  "message": null,
  "meta": null
}

如果判断为这些端点生成的JSON架构,您将看到类型参数被正确解析,并且这data个列表元素架构对于这两个端点是不同的.


Side notes:

  • 最初的ApiResponse模型将datameta都定义为Optional,但您的缺省值对应于"实际"字段类型(即分别为listdict).Optional[T]相当于Union[T, None].因此,除非您希望允许None作为这些字段的值,否则我建议go 掉那里的Optional.设置默认值已使这些字段不再是必填项.(请参阅我的示例)

  • 此外,在处理像listdict这样的可变类型时,使用default_factory模式而不是赋值缺省值是probably的好主意(或者至少是好的形式).

  • 当您的路由处理程序函数的返回类型实际上与您想要的响应模型匹配时(例如,在我的points路由中),您可以省略路由修饰符的response_model参数,而只用它注释函数本身.当它doesn't匹配时(如在users中),您仍然应该正确地注释返回类型(在该示例Dict[str, Any]中),但在修饰符中提供正确的response_model.(详情见here)

Python相关问答推荐

Django:如何将一个模型的唯一实例创建为另一个模型中的字段

单击cookie按钮,但结果不一致

定义同侪组并计算同侪组分析

Pandas 群内滚动总和

使用Ubuntu、Python和Weasyprint的Docker文件-venv的问题

如何在Python中使用时区夏令时获取任何给定本地时间的纪元值?

jit JAX函数中的迭代器

Pystata:从Python并行运行stata实例

连接两个具有不同标题的收件箱

如何让剧作家等待Python中出现特定cookie(然后返回它)?

追溯(最近最后一次调用):文件C:\Users\Diplom/PycharmProject\Yolo01\Roboflow-4.py,第4行,在模块导入roboflow中

使用@ guardlasses. guardlass和注释的Python继承

Streamlit应用程序中的Plotly条形图中未正确显示Y轴刻度

在np数组上实现无重叠的二维滑动窗口

如何在UserSerializer中添加显式字段?

Pandas GroupBy可以分成两个盒子吗?

isinstance()在使用dill.dump和dill.load后,对列表中包含的对象失败

用渐近模计算含符号的矩阵乘法

如何在FastAPI中为我上传的json文件提供索引ID?

如何在海上配对图中使某些标记周围的黑色边框