Skip to content

Instantly share code, notes, and snippets.

@ernstki
Last active May 28, 2021 00:36
Show Gist options
  • Save ernstki/fbdde2f57665e299e02852beeae18dfe to your computer and use it in GitHub Desktop.
Save ernstki/fbdde2f57665e299e02852beeae18dfe to your computer and use it in GitHub Desktop.
Minimum-viable Flask app that processes uploaded file input (and offers the result for download)
##
## Take a file uploaded by the user, process it, and offer the result for
## download
##
## For a more complete example (with better input checking), see:
## https://flask.palletsprojects.com/en/1.1.x/patterns/fileuploads/
##
## Run like this:
## $ mkdir uploads
## $ FLASK_APP=flask-upload-download.py flask run
##
import os
import mimetypes
import subprocess
from flask import Flask, abort, redirect, url_for, request, render_template, \
send_from_directory
from jinja2 import Template
from werkzeug.utils import secure_filename
# Flask configuration
MAX_CONTENT_LENGTH = 1024*1024 # 1 MB
inputpage = """<html>
<body>
<h1>Select a file to upload:</h1>
<form action="/wordcount" method="POST", enctype="multipart/form-data">
<input type="file" name="inputfile">
<br><br>
<input type="submit" value="Upload">
</form>
</html>"""
outputpage = Template("""<html>
<body>
<h1>Your file, <tt>{{ filename }}</tt>, has {{ linecount }} lines.</h1>
<a href="/download/{{ filename }}">Download results for {{ filename }}</a>
</body>
</html>""")
errorpage = Template("""<html>
<body>
<h1>{{ error }}</h1>
<p>{{ detail }}</p>
</body>
</html>""")
app = Flask(__name__)
app.config.from_object(__name__)
@app.errorhandler(400)
def bad_request(detail):
return render_template(errorpage, error='Input Error', detail=detail)
@app.errorhandler(500)
def internal_error(detail):
return render_template(errorpage, error='Internal Error', detail=detail)
@app.route('/')
def index():
return inputpage
@app.route('/wordcount', methods=['POST'])
def process_upload():
infile = request.files['inputfile']
# 'uploads' subdirectory must already exist
infilename = secure_filename(infile.filename)
inpath = os.path.join('uploads', infilename)
infile.save(inpath)
mimetype = mimetypes.guess_type(inpath)
if not mimetype[0]:
abort(400, f"Unrecognized MIME type; input file must be plain text.")
# quick sanity check to make sure it's a plain (uncompressed) text file
if not mimetype[0].startswith('text/') and mimetype[1] is None:
abort(400, f"Input file must be plain text (got {mimetype[0]}).")
try:
p = subprocess.run(['wc', '-l', inpath], check=True,
encoding='utf8', capture_output=True)
except subprocess.SubprocessError as e:
abort(500, f"Problem processing input file (<tt>{e.stderr}</tt>).")
# overwrite the input file with the output of 'wc -l'
with open(inpath, 'w') as outf:
outf.writelines(p.stdout)
return redirect(url_for('process_upload') + '/' + infilename)
@app.route('/wordcount/<filename>', methods=['GET'])
def display_result(filename):
filename = secure_filename(filename)
wcfile = os.path.join('uploads', filename)
if not os.path.isfile(wcfile):
abort(400, f"No such file <tt>{filename}</tt>.")
# read the linecount out of the file in 'uploads/' subdirectory
with open(wcfile, 'r') as f:
count = f.readline().strip().split(' ')[0]
return render_template(outputpage, filename=filename, linecount=count)
@app.route('/download/<filename>')
def download_result(filename):
filename = secure_filename(filename)
wcfile = os.path.join('uploads', filename)
if not os.path.isfile(wcfile):
abort(400, f"No such file <tt>{filename}</tt>.")
return send_from_directory('uploads', filename, as_attachment=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment