如何在 FastAPI 中使用 Pydantic 模型定义查询参数?

3

我正在尝试创建一个类似于 /services?status=New 的端点。

status 将是 New 或者 Old

以下是我的代码:

from fastapi import APIRouter, Depends
from pydantic import BaseModel
from enum import Enum

router = APIRouter()

class ServiceStatusEnum(str, Enum):
    new = "New"
    old = "Old"


class ServiceStatusQueryParam(BaseModel):
    status: ServiceStatusEnum


@router.get("/services")
def get_services(
  status: ServiceStatusQueryParam = Query(..., title="Services", description="my desc"),
):
    pass #my code for handling this route.....

结果是我收到了一个错误,似乎与这个问题这里有关。

错误信息显示:AssertionError:Param:状态只能是请求正文,使用Body()


然后我找到了另一个解决方案,在这里有详细的说明。

因此,我的代码将是这样的:

from fastapi import APIRouter, Depends
from pydantic import BaseModel
from enum import Enum

router = APIRouter()

class ServiceStatusEnum(str, Enum):
    new = "New"
    old = "Old"


class ServicesQueryParam(BaseModel):
    status: ServiceStatusEnum


@router.get("/services")
def get_services(
  q: ServicesQueryParam = Depends(),
):
    pass #my code for handling this route.....

它正在工作(我不明白为什么)- 但问题是如何在哪里添加描述和标题?


1
我不理解 ServiceStatusQueryParam 的目的。为什么不直接使用 ServiceStatusEnum 注释您路由的 status 参数呢?那样就可以工作了。你真的需要在 URL 查询参数中使用整个 JSON 对象吗?对我来说似乎非常尴尬。 - Daniil Fajnberg
我不太清楚fastapi的区别,因为我是新手,但你似乎是对的。我测试了一下,它可以工作。 - Amin Ba
2
在你的第一个代码片段中,URL查询参数理论上应该是?status={"status":"New"}或类似的内容,因为你将status查询参数的类型设置为ServiceStatusQueryParam模型,该模型反序列化为JSON对象。而你只想让查询为?status=New,所以本质上是字符串类型,但受到枚举成员的限制。 - Daniil Fajnberg
如果我想对字符串进行一些验证,我该怎么做? - Amin Ba
1个回答

7

要创建一个Pydantic模型并使用它来定义查询参数,您需要在端点的参数中使用Depends()。要为查询参数添加descriptiontitle等,可以将Query()包装在Field()中。

我还想提到,可以使用Literal类型代替Enum,如此处此处所述。此外,如果想在Pydantic模型中定义一个List字段并将其用作查询参数,则需要在单独的依赖类中实现此功能,如此处此处所示,或者再次将Query()包装在Field()中,如下所示。

此外,为了在 Pydantic 模型内部对查询参数执行验证,可以像通常使用 Pydantic 的 @validator 一样进行操作,如 此处 所示,以及 这里这里。请注意,在这种情况下,如果将 BaseModel 用于查询参数,则引发 ValueError 将导致 Internal Server Error。因此,当验证失败时,应该抛出一个 HTTPException,或者使用自定义异常处理程序来处理 ValueError 异常,如 此答案 中的选项 2 所示。除了使用 @validator,还可以对 Query 参数进行其他验证,如 FastAPI 文档 中所述(请参见 Query 类实现)。

顺便提一下,关于定义可选参数,下面的示例使用了Optional类型提示(伴随着Query中的None作为默认值)来自typing模块;然而,您也可以参考this answerthis answer,这两个答案描述了所有可用的方法。

工作示例

from fastapi import FastAPI, Depends, Query, HTTPException
from pydantic import BaseModel, Field, validator
from typing import List, Optional, Literal
from enum import Enum

app = FastAPI()

class Status(str, Enum):
    new = 'New'
    old = 'Old'


class ServiceStatus(BaseModel):
    status: Optional[Status] = Field (Query(..., description='Select service status'))
    msg: Optional[str] = Field (Query(None, description='Type something'))
    choice: Literal['a', 'b', 'c', 'd'] = Field (Query(..., description='Choose something'))
    comments: List[str] = Field (Query(..., description='Add some comments'))
    
    @validator('choice')
    def check_choice(cls, v):
        if v == 'b': 
             raise HTTPException(status_code=422, detail='Wrong choice')
        return v

@app.get('/status')
def main(status: ServiceStatus = Depends()):
    return status

我能在这里包括路径并在字段之间运行复杂验证吗? - Amin Ba
谢谢。你说得对。实际上,我已经在Udemy上学习了两门关于FastAPI的课程,但有时候我仍然不知道开发人员应该做什么,不应该做什么。我可以创建端点,但是我不知道这是否是专业人士正在做的事情,以及这是否是FastAPI的创建者想象中开发人员应该做的事情。 - Amin Ba
既然您提供了這個例子,我在想是否應該在ServiceStatus類中也包括PathBody - Amin Ba
如果我们有services/{service_id},你会在class ServiceStatus(BaseModel)下面添加service_id: str = Field(Path(..., description=''))吗? - Amin Ba
请查看这个答案,以及这个答案这个答案(您也可能会发现这个有帮助)。请确保彻底阅读上面链接的答案,以及包含在内的任何参考资料。 - Chris
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,