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
相关产品推荐
相关产品推荐

