You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI中如何基于grant_type实现多类型Form Data接口?

问题

我想要实现一个FastAPI接口,该接口需要根据grant_type参数使用不同的FormData字段,定义了两个数据类:

from dataclasses import dataclass
from typing import Literal
from fastapi import Form

@dataclass
class DeviceTokenGrant:
    grant_type: Literal["urn:ietf:params:oauth:grant-type:device_code"] = Form(const=True)
    client_id: str = Form()
    device_code: str = Form()


@dataclass
class RefreshTokenGrant:
    grant_type: Literal["refresh_token"] = Form(const=True)
    refresh_token: str = Form()

要求保持application/x-www-form-urlencoded媒体类型而非application/json,但尝试直接用联合类型结合Depends时遇到问题:

  • 使用DeviceTokenGrant | RefreshTokenGrant会报错:
async def token(grant: DeviceTokenGrant | RefreshTokenGrant = Depends()) -> TokenResult:

错误信息:

TypeError: auth.api.oauth.DeviceTokenGrant | auth.api.oauth.RefreshTokenGrant is not a callable object
  • 使用Union时,接口Schema异常,被替换为args和kwargs参数:
async def token(grant: Union[DeviceTokenGrant, RefreshTokenGrant] = Depends()) -> TokenResult:
解决方案

FastAPI不支持直接对表单数据使用联合类型自动解析,但可以通过自定义依赖函数实现根据grant_type匹配对应表单字段的需求,同时保持application/x-www-form-urlencoded格式。

具体实现

先编写一个依赖函数,先获取grant_type参数,再根据其值校验并返回对应的数据类实例:

from dataclasses import dataclass
from typing import Literal, Union, Optional
from fastapi import Form, Depends, HTTPException, status

@dataclass
class DeviceTokenGrant:
    grant_type: Literal["urn:ietf:params:oauth:grant-type:device_code"] = Form(const=True)
    client_id: str = Form()
    device_code: str = Form()


@dataclass
class RefreshTokenGrant:
    grant_type: Literal["refresh_token"] = Form(const=True)
    refresh_token: str = Form()

async def get_grant(
    grant_type: str = Form(...),
    client_id: Optional[str] = Form(None),
    device_code: Optional[str] = Form(None),
    refresh_token: Optional[str] = Form(None)
) -> Union[DeviceTokenGrant, RefreshTokenGrant]:
    match grant_type:
        case "urn:ietf:params:oauth:grant-type:device_code":
            if not client_id or not device_code:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail="client_id和device_code为必填字段"
                )
            return DeviceTokenGrant(grant_type=grant_type, client_id=client_id, device_code=device_code)
        case "refresh_token":
            if not refresh_token:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail="refresh_token为必填字段"
                )
            return RefreshTokenGrant(grant_type=grant_type, refresh_token=refresh_token)
        case _:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"不支持的grant_type: {grant_type}"
            )

# 接口使用自定义依赖
async def token(grant: Union[DeviceTokenGrant, RefreshTokenGrant] = Depends(get_grant)) -> TokenResult:
    if isinstance(grant, DeviceTokenGrant):
        # 处理设备令牌逻辑
        return TokenResult(...)
    elif isinstance(grant, RefreshTokenGrant):
        # 处理刷新令牌逻辑
        return TokenResult(...)

说明

  • 这种方式能让Swagger文档正确展示所有表单字段,同时通过校验逻辑保证不同grant_type下的必填项不缺失。
  • 若使用Pydantic模型替代dataclass,还能利用Pydantic的内置校验能力进一步简化逻辑,但dataclass的写法完全满足需求。

内容的提问来源于stack exchange,提问作者Nerlin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 13:07:38