1

I am developing a webhook in which a third-party service will hit my URL and will provide some files, now I can not use FastAPI's UploadFile = File (...) because it throws an error of the required field File I want to read the payload and files from the request object as we can do in Flask by simply doing this

from flask import request
files = request.files

How can I achieve the same in FastAPI?

Chris
  • 18,724
  • 6
  • 46
  • 80
The knight
  • 87
  • 1
  • 10
  • What is the error you're facing and why exactly can't you use `UploadFile`? – lord_haffi Aug 24 '23 at 14:23
  • because I will have to assign a variable inside the function like file; UploadFIle = File(..) and the third party hitting my endpoint is not sending the data like this. they are sending the files in request. so I want to read the files from the request object – The knight Aug 24 '23 at 14:32
  • That's what `UploadFile` does; can you show the code and the request that doesn't work as you expect it to? – MatsLindh Aug 24 '23 at 15:22

1 Answers1

1

You could use Starlette's request.form() method to parse the body, which would return a FormData object containing both file uploads and text input. For more details and references, please have a look at this answer, on which the following example is based. Another solution would be to use the approach demonstrated in Option 1 of this answer.

Also, if, for any reason, you would like to use a def instead of async def endpoint, please have a look at this answer on how to read the file contents inside a def endpoint. You might find this answer helpful as well.

Working Example

app.py

from fastapi import FastAPI, Depends, Request, HTTPException
from starlette.datastructures import FormData

app = FastAPI()

async def get_body(request: Request):
    content_type = request.headers.get('Content-Type')
    if content_type is None:
        raise HTTPException(status_code=400, detail='No Content-Type provided!')
    elif (content_type == 'application/x-www-form-urlencoded' or
          content_type.startswith('multipart/form-data')):
        try:
            return await request.form()
        except Exception:
            raise HTTPException(status_code=400, detail='Invalid Form data')
    else:
        raise HTTPException(status_code=400, detail='Content-Type not supported!')


@app.post('/')
async def main(body = Depends(get_body)):
    if isinstance(body, FormData):  # if Form/File data received
        msg = body.get('msg')
        items = body.getlist('items')
        files = body.getlist('files')  # returns a list of UploadFile objects
        if files:
            for file in files:
                print(f'Filename: {file.filename}. Content (first 15 bytes): {await file.read(15)}')
        return 'OK'

test.py

import requests

url = 'http://127.0.0.1:8000/'
data = {'items': ['foo', 'bar'], 'msg': 'Hello!'}
files = [('files', open('a.txt', 'rb')), ('files', open('b.txt', 'rb'))]
 
# Send Form data and files
r = requests.post(url, data=data, files=files)  
print(r.text)

# Send Form data only
r = requests.post(url, data=data)              
print(r.text)
Chris
  • 18,724
  • 6
  • 46
  • 80
  • the code is working but I am getting an empty list of files. – The knight Aug 24 '23 at 17:22
  • Please make sure that you are using the **same** name for the `files` parameter as the one used by the third-party service that is calling the API endpoint. In the example above, that name is `files`. Have a look at related posts [here](https://stackoverflow.com/a/71176578/17865804), [here](https://stackoverflow.com/a/73739720/17865804) and [here](https://stackoverflow.com/a/70797993/17865804). If that is unknown to you, you could use/print `await request.body()` to get the raw `multipart/form-data` body and extract the name from the `Content-Disposition` header of the file part. – Chris Aug 25 '23 at 04:29