3

I'm trying to create a serverless file upload API using FastAPI/Mangum and am running into a strange JSON decoding issue when attempting to follow the example in the docs.

Here is my code:

# main.py
import os

from typing import List
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
from mangum import Mangum

app = FastAPI()

@app.get("/")
async def main():
    content = """
    <body>
     <form action="/registration/create" enctype="multipart/form-data" method="post">
      <input name="files" type="file" multiple>
      <input type="submit">
     </form>
    </body>
    """
    return HTMLResponse(content=content)

@app.post("/registration/create")
async def create_registration(files: List[UploadFile]):
    return {"file_len": len(files)}

handler = Mangum(app)
# test_main.py
from urllib import response
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_registration():
    files = [('files', ('example.txt', open('example.txt', 'rb'), 'text/plain'))]
    response = client.post("/registration/create", files=files)
    assert response.status_code == 200

When I run the test or attempt to POST files using the web page example, I receive a JSON decoding error and the request fails with 422 status code error:

{
 "detail":
   [{"loc":["body",0],
     "msg":"Expecting value: line 1 column 1 (char 0)",
     "type":"value_error.jsondecode",
     "ctx": {
       "msg": "Expecting value",
       "doc": "\nContent-Disposition: form-data; name=\\"files\\"; filename=\\"example.txt\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nexample text in the file\n",
     "pos":0,
     "lineno":1,
     "colno":1
  }
 }]
}

Here is the docs page I am referencing.

Chris
  • 18,724
  • 6
  • 46
  • 80
gstranger
  • 53
  • 2

1 Answers1

0

The error you get simply shows that your FastAPI endpoint, in the way it is defined, is expecting JSON data, and this is because you didn't specify the type of UploadFile, i.e., using = File(...). Hence, FastAPI interprets and expects List[UploadFile] as JSON body instead.

Your endpoint should instead look like this:

from fastapi import File

@app.post("/registration/create")
async def create_registration(files: List[UploadFile] = File(...)):
    return {"file_len": len(files)}

Please have a look at this answer, as well as this and this answer for more details.

Update

In the latest version of FastAPI, you can define a file parameter with a type of UploadFile without having to use = File() or = File(...) in the default value of the parameter. Hence, the below should also be working as expected:

@app.post("/registration/create")
async def create_registration(files: List[UploadFile]):
    return {"file_len": len(files)}

Working Example

This example is based on FastAPI's documentation on Multiple File Uploads.

from fastapi import FastAPI, UploadFile
from fastapi.responses import HTMLResponse
from typing import List

app = FastAPI()


@app.post("/files/")
async def create_files(files: List[UploadFile]):
    return {"filenames": [file.filename for file in files]}


@app.get("/")
async def main():
    content = """
    <body>
    <form action="/files/" enctype="multipart/form-data" method="post">
    <input name="files" type="file" multiple>
    <input type="submit">
    </form>
    </body>
    """
    return HTMLResponse(content=content)
Chris
  • 18,724
  • 6
  • 46
  • 80