Uploading and Downloading Files with Flask

Each Answer to this Q is separated by one/two green lines.

I’m trying to write a really simply webapp with PythonAnywhere and Flask that has lets the user upload a text file, generates a csv file, then lets the user download the csv file. It doesn’t have to be fancy, it only has to work. I have already written the program for generating the csv from a txt file on the drive.

Right now, my function opens the file on the drive with:

with open(INPUTFILE, "r") as fname:

and writes the csv with:

with open(OUTPUTFILE, 'w') as fname:

with INPUTFILE and OUTPUTFILE being filename strings.

Would it be better for me to handle the files as objects, returned by the flask/html somehow?

I don’t know how to do this. How should I structure this program? How many HTML Templates do I need? I would prefer to work on the files wihthout saving them anywhere but if I have to save them to the PythonAnywhere directory, I could. How can I do that?

PythonAnywhere dev here. This is a good question about Flask and web development in general rather than specific to our system, so I’ll try to give a generic answer without anything specific to us 🙂

There are a few things that I’d need to know to give a definitive answer to your question, so I’ll start by listing the assumptions I’m making — leave me a comment if I’m wrong with any of them and I’ll update the answer appropriately.

  • I’m assuming that the files you’re uploading aren’t huge and can fit into a reasonable amount of memory — let’s say, smaller than a megabyte.
  • I’m assuming that the program that you’ve already written to generate the CSV from the text file is in Python, and that it has (or, perhaps more likely, could be easily changed to have) a function that takes a string containing the contents of the text file, and returns the contents that need to be written into the CSV.

If both of those are the case, then the best way to structure your Flask app would be to handle everything inside Flask. A code sample is worth a thousand words, so here’s a simple one I put together that allows the user to upload a text file, runs it through a function called transform (which is where the function from your conversion program would slot in — mine just replaces = with , throughout the file), and sends the results back to the browser. There’s a live version of this app on PythonAnywhere here.

from flask import Flask, make_response, request

app = Flask(__name__)

def transform(text_file_contents):
    return text_file_contents.replace("=", ",")

def form():
    return """
                <h1>Transform a file demo</h1>

                <form action="/transform" method="post" enctype="multipart/form-data">
                    <input type="file" name="data_file" />
                    <input type="submit" />

@app.route('/transform', methods=["POST"])
def transform_view():
    request_file = request.files['data_file']
    if not request_file:
        return "No file"

    file_contents = request_file.stream.read().decode("utf-8")

    result = transform(file_contents)

    response = make_response(result)
    response.headers["Content-Disposition"] = "attachment; filename=result.csv"
    return response

Regarding your other questions:

  • Templates: I didn’t use a template for this example, because I wanted it all to fit into a single piece of code. If I were doing it properly then I’d put the stuff that’s generated by the form view into a template, but that’s all.
  • Can you do it by writing to files — yes you can, and the uploaded file can be saved by using the save(filename) method on the file object that I’m using the stream property of. But if your files are pretty small (as per my assumption above) then it probably makes more sense to process them in-memory like the code above does.

I hope that all helps, and if you have any questions then just leave a comment.

Better to add

response.headers["Cache-Control"] = "must-revalidate"
response.headers["Pragma"] = "must-revalidate"
response.headers["Content-type"] = "application/csv"

If you don’t add the content type, FF 48.0 reported it as html and opened Save dialog once for HTML and then for CSV. If you don’t add Cache-Control your result may get cached, and if you serve active content this is not what you want. If you use must-revalidate with no age, it will effectively serve as no-cache – see here and here for an explanation.

The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 .