FastAPI上传文件返回422 Unprocessable Entity错误如何解决?
问题原因
- HTTP协议限制:当接口包含
UploadFile类型的文件参数时,请求的Content-Type必须为multipart/form-data。你当前代码中定义的request: schemas.CategoryIn/request: schemas.ProductIn属于JSON请求体参数,FastAPI会默认从application/json类型的请求体中解析这类参数,两种Content-Type无法共存,因此FastAPI找不到对应的request参数,返回422校验错误。 - 入参Schema定义不合理:你当前的
CategoryIn和ProductIn中image字段为必填项,但前端调用上传接口时只会传业务字段和文件,不会提前传入image的URL(这个URL是后端上传到云存储后才生成的),因此Pydantic对request参数做校验时会因为缺少必填的image字段报错,也会触发422错误。
解决方案
第一步:调整入参Schema,移除不需要前端传入的image字段
修改你的Schema定义,将入参类和基础类拆分,入参类不要包含image字段:
from typing import Optional, List from pydantic import BaseModel # 保留原有基础类,用于出参/ORM映射 class CategoryBase(BaseModel): name: str slug: str image: str class Category(CategoryBase): id: int class Config: orm_mode = True # 修改入参类,仅保留需要前端传入的字段 class CategoryIn(BaseModel): name: str slug: str # Product部分同理修改 class ProductBase(BaseModel): name: str description: Optional[str] = None price: float image: str slug: str class Product(ProductBase): id: int category_id: int category: Category class Config: orm_mode = True # 修改Product入参类,移除image字段 class ProductIn(BaseModel): name: str description: Optional[str] = None price: float slug: str # 出参类保持不变即可 class CategoryOut(CategoryBase): products: List[Product] = [] id: int class Config: orm_mode = True
第二步:调整路由参数,兼容文件上传的请求格式
这里提供两种可选实现:
方案1:直接拆分参数为Form类型(简单直观,适合字段少的场景)
修改路由函数的参数定义,将原来的request对象拆分为单独的Form参数,手动构造入参实例:
from fastapi import Form @router.post('/category/create', response_model=schemas.CategoryOut) def create_category( *, name: str = Form(...), slug: str = Form(...), file: UploadFile = File(...), db: Session = Depends(get_db), current_user: schemas.UserOut = Depends(get_current_active_user) ): category = storeview.get_category_by_slug(db, slug) if category: raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=f"Category with slug {slug} already exists!") # 手动构造CategoryIn实例,传入后端生成的image URL request = schemas.CategoryIn( name=name, slug=slug, image=storeview.save_file(file) ) return storeview.create_category(db, request) @router.post('/product/create', response_model=schemas.Product) async def create_product( *, file: UploadFile = File(...), name: str = Form(...), description: Optional[str] = Form(None), price: float = Form(...), slug: str = Form(...), category_id: int = Form(...), db: Session = Depends(get_db), current_user: schemas.UserOut = Depends(get_current_active_user) ): product = storeview.get_category_by_slug(db, slug) if product: raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=f"Product with slug {slug} already exists!") # 手动构造ProductIn实例 request = schemas.ProductIn( name=name, description=description, price=price, slug=slug, image=storeview.save_file(file) ) return storeview.create_product(db, request, category_id)
方案2:用Depends封装Pydantic解析(适合字段多不想拆分参数的场景)
你可以自定义一个依赖函数,从Form参数中解析出Pydantic模型实例,避免拆分大量参数:
import inspect from fastapi import Form, Depends from pydantic import BaseModel def form_body(cls: BaseModel): async def as_form(**data): return cls(**data) sig = inspect.signature(as_form) parameters = [] for k, field in cls.__fields__.items(): if field.required: param = inspect.Parameter(k, inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Form(...)) else: param = inspect.Parameter(k, inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Form(field.default)) parameters.append(param) as_form.__signature__ = sig.replace(parameters=parameters) return as_form # 给入参类加上form_body装饰器 @form_body class CategoryIn(BaseModel): name: str slug: str @form_body class ProductIn(BaseModel): name: str description: Optional[str] = None price: float slug: str
之后路由就可以直接用Depends获取入参实例了:
@router.post('/category/create', response_model=schemas.CategoryOut) def create_category( *, request: schemas.CategoryIn = Depends(), file: UploadFile = File(...), db: Session = Depends(get_db), current_user: schemas.UserOut = Depends(get_current_active_user) ): category = storeview.get_category_by_slug(db, request.slug) if category: raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=f"Category with slug {request.slug} already exists!") request.image = storeview.save_file(file) return storeview.create_category(db, request)
内容的提问来源于stack exchange,提问作者Seyi Oyefeso
相关产品推荐
相关产品推荐

