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?
The proper approach would be to define File
/UploadFile
and Form
type parameters in your endpoint, as demonstrated in Method 1 of this answer, as well as here, here and here (for a faster file-upload approach, see this and this as well). For instance:
@app.post("/submit")
async def register(name: str = Form(...), files: List[UploadFile] = File(...)):
pass
However, since you are looking for an approach using the Request
object, 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 or Option 2 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.
app.py
from fastapi import FastAPI, Request, Depends, HTTPException
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from starlette.datastructures import FormData, UploadFile
app = FastAPI()
templates = Jinja2Templates(directory='templates')
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!')
# Use this approach, if keys (names) of Form/File data are unknown to the backend
@app.post('/submit')
async def main(body = Depends(get_body)):
if isinstance(body, FormData): # if Form/File data received
for k in body:
entries = body.getlist(k)
if isinstance(body.getlist(k)[0], UploadFile): # check if it is an UploadFile object
for file in entries:
print(f'Filename: {file.filename}. Content (first 15 bytes): {await file.read(15)}')
else:
data = entries if len(entries) > 1 else entries[0]
print(f"{k}={data}")
return 'OK'
# Use this approach, if keys (names) of Form/File data are known to the backend beforehand
@app.post('/other')
async def main(body = Depends(get_body)):
if isinstance(body, FormData): # if Form/File data received
items = body.getlist('items')
print(f"items={items}")
msg = body.get('msg')
print(f"msg={msg}")
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'
@app.get('/', response_class=HTMLResponse)
async def main(request: Request):
return templates.TemplateResponse('index.html', {'request': request})
templates/index.html
<!DOCTYPE html>
<html>
<body>
<form id="myForm" >
msg : <input type="text" name="msg" value="test"><br>
item 1 : <input type="text" name="items" value="1"><br>
item 2 : <input type="text" name="items" value="2"><br>
</form>
<label for="fileInput">Choose file(s) to upload</label>
<input type="file" id="fileInput" multiple><br>
<input type="button" value="Submit" onclick="submitUsingFetch()">
<p id="resp"></p>
<script>
function submitUsingFetch() {
const resp = document.getElementById("resp");
const fileInput = document.getElementById('fileInput');
const myForm = document.getElementById('myForm');
var formData = new FormData(myForm);
for (const file of fileInput.files)
formData.append('files', file);
fetch('/submit', {
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(data => {
resp.innerHTML = JSON.stringify(data); // data is a JSON object
})
.catch(error => {
console.error(error);
});
}
</script>
</body>
</html>
requests
test.py
import requests
url = 'http://127.0.0.1:8000/submit'
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)