为什么 FastAPI 不能正确处理从 int 和 Enum 派生的类型?

Joh*_* K. 5 validation pydantic fastapi

使用以下FastAPI后端:

from enum import Enum
from fastapi import FastAPI

class MyNumber(int, Enum):
    ONE = 1
    TWO = 2
    THREE = 3

app = FastAPI()

@app.get("/add/{a}/{b}")
async def get_model(a: MyNumber, b: MyNumber):
    return {"sum": a + b}
Run Code Online (Sandbox Code Playgroud)

GET操作完成时:

curl -X 'GET' \
  'http://127.0.0.1:8000/add/2/3' \
  -H 'accept: application/json'
Run Code Online (Sandbox Code Playgroud)

返回以下内容:

{
  "detail": [
    {
      "loc": [
        "path",
        "a"
      ],
      "msg": "value is not a valid enumeration member; permitted: 1, 2, 3",
      "type": "type_error.enum",
      "ctx": {
        "enum_values": [
          1,
          2,
          3
        ]
      }
    },
    {
      "loc": [
        "path",
        "b"
      ],
      "msg": "value is not a valid enumeration member; permitted: 1, 2, 3",
      "type": "type_error.enum",
      "ctx": {
        "enum_values": [
          1,
          2,
          3
        ]
      }
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

为什么会这样呢?甚至 Swagger UI 也将可能的值识别为整数:

在此输入图像描述

IntEnum我已经尝试过使用替代(source )的解决方案,并且我可以确认它有效,但仍然 - 为什么它必须是这种方式?

enum.py源代码定义IntEnum为:

curl -X 'GET' \
  'http://127.0.0.1:8000/add/2/3' \
  -H 'accept: application/json'
Run Code Online (Sandbox Code Playgroud)

Dje*_*eth 6

根据OP下面的评论做了一些挖掘。首先解释一下为什么这没有按预期工作:这就是 Starlette 处理路径参数的方式。当 Starlette 处理请求时(或者更准确地说,当Router调用APIRouter继承的对象时),它会将 确定path_params为一个字典,其中键是参数名称,值是其值。但是,当发生这种情况并且您没有在路径 string中指定类型时,它会自动将路径参数视为字符串。然后它将其添加到 中Request,基本上作为字典{"a":"1", "b":"2"}。然后,在调用堆栈的更上方,FastAPI 尝试查找与“1”对应的枚举值,这会抛出验证错误,因为"1" is not 1. 请注意,您的MyNumber类在这里还没有发挥任何作用。

此行为是设计使然,可能会受到如下影响:

@app.get("/add/{a:int}/{b:int}")
Run Code Online (Sandbox Code Playgroud)

这将确保 Starlette 将创建一个类似于 的 path_params 字典{"a":1, "b":2}。请注意,12现在是整数,并且将由 FastAPI 进行处理。

至于为什么MyNumber(IntEnum)开箱即用而MyNumber(int, Enum)不能,这是 Pydantic 的实现。我似乎无法准确指出具体情况,但是当ModelField为两个参数创建 时(在应用程序启动时发生utils.py -> create_response_field()),列表中validators包含int使用IntEnum.

但因为ModelField是使用创建的,functools.partial()所以我无法跟踪 Pydantic 的调用堆栈。所以,我不确定为什么会这样。

因此,简而言之,有两个选项可以解决此问题:要么强制 Starlette 解析路径参数abas int(使用),要么从您自己的枚举类{a:int}继承。IntEnum