How to upload a csv file using Jinja2 Templates and FastAPI , and return it after modifications?

Question:

I am using FastAPI to upload a csv file, perform some modifications on it and then return it to the HTML page. I am using Jinja2 as the template engine and HTML in frontend.

How can I upload the csv file using Jinja2 template, modify it and then return it to the client?

Python code

from fastapi.templating import Jinja2Templates
from fastapi import FastAPI, File, UploadFile, Request
from io import BytesIO
import pandas as pd
import uvicorn

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/")
def form_post(request: Request):
result = "upload file"
return templates.TemplateResponse('home.html', context={'request': request, 'result': result})

@app.post("/")
def upload(request: Request, file: UploadFile = File(...)):

    contents1 = file.file.read()
    buffer1 = BytesIO(contents1)
    test1 = pd.read_csv(buffer1)
    buffer1.close()
    file.file.close()
    test1 = dict(test1.values)
    
    return templates.TemplateResponse('home.html', context={'request': request, 'result': test1})

if __name__ == "__main__":
    uvicorn.run(app)

HTML code

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>RUL_PREDICTION</title>
</head>
<body>
<h1>RUL PREDICTION</h1>
<form method="post">
<input type="file" name="file" id="file"/>
<button type="submit">upload</button>
</form>
<p>{{ result }}</p>
</body>
</html>
Asked By: Biswajeet Padhi

||

Answers:

This might work:

@app.post("/")
def upload(file: UploadFile):

    with open("temp.csv", "wb") as f:
        for row in file.file:
            f.write(row)
    
    with open("temp.csv", "r", encoding="utf-8") as csv:
        # modifications
    

    return FileResponse(path="temp.csv", filename="new.csv", media_type="application/octet-stream")
Answered By: rtoth

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>
Answered By: Chris
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.