FastAPIで画像とモデルを同時にpostする

fastAPIでfileのアップロードとBaseModelを継承したクラスのポストを同時にやろうとすると422エラーになってつまずいたので、その解決策の備忘録。

ファイルとモデルを同時にポストする (失敗)

コード

from fastapi import FastAPI, File, UploadFile
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
    name: str = "Toshi"

@app.post("/uploadfile/")
async def upload_file(user: User, file: UploadFile = File(...)):
    return {"filename": file.filename, 'user': user}

実行結果

/docs から実行すると、422 Error: Unprocessable Entity 。Response bodyは以下。

{
  "detail": [
    {
      "loc": [
        "body",
        "user"
      ],
      "msg": "value is not a valid dict",
      "type": "type_error.dict"
    }
  ]
}

Response headers

content-length: 95 
 content-type: application/json 
 date: Tue,31 Aug 2021 14:11:22 GMT 
 server: uvicorn 

ファイルとモデルを同時にポストする (成功)

コード

from fastapi import FastAPI, File, UploadFile, Body
from pydantic import BaseModel
import json

app = FastAPI()

class User(BaseModel):
    name: str = "Toshi"

    @classmethod
    def __get_validators__(cls):
        yield cls.validate_to_json

    @classmethod
    def validate_to_json(cls, value):
        if isinstance(value, str):
            return cls(**json.loads(value))
        return value

@app.post("/uploadfile/")
async def upload_file(user: User = Body(...), file: UploadFile = File(...)):
    return {"filename": file.filename, 'user': user}

ポイント

BaseModelを継承したクラスをポストするときは application/x-www-form-urlencoded (curl の -d オプション)になる。ファイルをアップロードするときは multipart/form-data (curl の -f オプション)になる。User = Body(...)とすることで -f にそろえる。

Userモデルでstringの入力をjson形式に変換する処理を記述する。

参考

Comments

タイトルとURLをコピーしました