The working example below is derived from the answers here, here, as well as here, here and here, at which I would suggest you have a look for more details and explanation.
Sample data
data.csv
Id,name,age,height,weight
1,Alice,20,62,120.6
2,Freddie,21,74,190.6
3,Bob,17,68,120.0
Option 1 - Return modified data in a new CSV file
app.py
from fastapi import FastAPI, File, UploadFile, Request, Response, HTTPException
from fastapi.templating import Jinja2Templates
from io import BytesIO
import pandas as pd
app = FastAPI()
templates = Jinja2Templates(directory='templates')
@app.post('/upload')
def upload(file: UploadFile = File(...)):
try:
contents = file.file.read()
buffer = BytesIO(contents)
df = pd.read_csv(buffer)
except:
raise HTTPException(status_code=500, detail='Something went wrong')
finally:
buffer.close()
file.file.close()
# remove a column from the DataFrame
df.drop('age', axis=1, inplace=True)
headers = {'Content-Disposition': 'attachment; filename="modified_data.csv"'}
return Response(df.to_csv(), headers=headers, media_type='text/csv')
@app.get('/')
def main(request: Request):
return templates.TemplateResponse('index.html', {'request': request})
templates/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<form method="post" action="/upload" enctype="multipart/form-data">
<label for="csvFile">Choose a CSV file</label>
<input type="file" id="csvFile" name="file" onchange="enableSubmitBtn();"><br><br>
<input type="submit" id="submitBtn" value="submit" disabled>
</form>
<script>
function enableSubmitBtn() {
document.getElementById('submitBtn').removeAttribute("disabled");
}
</script>
</body>
</html>
Option 2 - Return modified data in a new Jinja2 Template
If you would rather like to return a new Jinja2 template with the modified data instead of a csv file as demonstrated above, you could use the below.
Method 1
Use pandas.DataFrame.to_html()
to render the DataFrame as an HTML table. You could optionally use the classes
parameter in to_html()
function to pass a class
name, or a list of names, that will be used in a style sheet in your frontend to style the table. Additionally, you could remove the border
by specifying border=0
in to_html()
.
app.py
# ... (rest of code is same as in Option 1)
@app.post('/upload')
def upload(request: Request, file: UploadFile = File(...)):
# ... (rest of code is same as in Option 1)
context = {'request': request, 'table': df.to_html()}
return templates.TemplateResponse('results.html', context)
templates/results.html
<!DOCTYPE html>
<html>
<body>{{ table | safe }}</body>
</html>
Method 2
Use pandas.DataFrame.to_dict()
to convert the DataFrame to a dictionary and return it.
app.py
# ... (rest of code is same as in Option 1)
@app.post('/upload')
def upload(request: Request, file: UploadFile = File(...)):
# ... (rest of code is same as in Option 1)
context = {'request': request, 'data': df.to_dict(orient='records'), 'columns': df.columns.values}
return templates.TemplateResponse('results.html', context)
templates/results.html
<!DOCTYPE html>
<html>
<body>
<table style="width:50%">
<tr>
{% for c in columns %}<td>{{ c }}</td>{% endfor %}
</tr>
{% for d in data %}
<tr>
{% for v in d.values() %}
<td>{{ v }}</td>
{% endfor %}
<br>
</tr>
{% endfor %}
</table>
</body>
</html>