2

I have a simple Flask app hosted on Python Anywhere. The app provides an interface where a user can upload a CSV file, and the app inserts a new column into the uploaded CSV file by overwriting the first column (row[0]) and putting the original row[0] at the end of the file and downloads the transformed file for the user. So, the CSV input looks like this (first row is a header row that should be unmodified save for the addition of a 4th column name):

Pet1,Pet2,Pet3
Cats,Dogs,Mice

And the output should look like:

Pet1,Pet2,Pet3,Pet4
Birds,Dogs,Mice,Cats

Here is my code:

from flask import Flask, make_response, request
import operator
import io
import csv

app = Flask(__name__)

def transform(text_file_contents):
  reader = csv.reader(text_file_contents)
  all = []
  row = next(reader)
  row.append('Pet4')
  all.append(row)
  for row in reader:
    pet4 = row[0]
    row[0] = "Birds"
    row.append(pet4)
    all.append(row)
  sortedlist = sorted(all, key=operator.itemgetter(0))
  return sortedlist


@app.route('/')
def form():
  return """
    <html>
        <body>
            <h1>Upload a CSV File</h1>

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

@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

This is the error I am getting:

Traceback (most recent call last):
File "/home/user/.virtualenvs/myvirtualenv/lib/python3.6/site-packages/flask/app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "/home/user/.virtualenvs/myvirtualenv/lib/python3.6/site-packages/flask/app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/user/.virtualenvs/myvirtualenv/lib/python3.6/site-packages/flask/app.py", line 1718, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/home/user/.virtualenvs/myvirtualenv/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
raise value
File "/home/user/.virtualenvs/myvirtualenv/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/home/user/.virtualenvs/myvirtualenv/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/user/mysite/callnumbers.py", line 62, in transform_view
result = transform(file_contents)
File "/home/user/mysite/callnumbers.py", line 18, in transform
row[0] = row[1]
IndexError: list index out of range

I can tell that my script isn't reading the uploaded input file correctly (hence not being able to find the data in the columns I am trying to reference and the IndexError) but I haven't been able to find any examples of Flask using the Python CSV library to parse an uploaded file and manipulate columns in the way I need to. How can I resolve this? Any pointers in the right direction are much appreciated!

Joel
  • 1,564
  • 7
  • 12
  • 20
lpm
  • 45
  • 9
  • 1
    According to [this](https://stackoverflow.com/questions/20015550/read-file-data-without-saving-it-in-flask) question, the solution is to do `request_file.read()` instead of `request_file.stream.read()`. I've never done it that way myself but it seemed to work for that question's OP. – noslenkwah Nov 14 '18 at 22:44

2 Answers2

0

The csv.reader() function takes a file object instead of text. One way to do it is to save the file then reopen it in the transform function.

from flask import Flask, make_response, request
import operator
import io
import csv    

app = Flask(__name__)


def transform():
  with open('myfile.csv', 'rb') as f:
    reader = csv.reader(f)  
    all = []
    row = next(reader)
    row.append('Pet4')
    all.append(row)
    for row in reader:
      pet4 = row[0]
      row[0] = "Birds"
      row.append(pet4)
      all.append(row)
    sortedlist = sorted(all, key=operator.itemgetter(0))  
  return sortedlist

@app.route('/')
def form():
  return """
    <html>
        <body>
            <h1>Upload a CSV File</h1>

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

@app.route('/transform', methods=["POST"])
def transform_view():
  request_file = request.files['data_file']
  request_file.save("myfile.csv")
  if not request_file:
    return "No file"
  #file_contents = io.StringIO(request_file.stream.read().decode("UTF8"), newline=None)
  #file_contents = request_file.stream.read().decode("utf-8")

  result = transform()
  print result

  response = make_response(result)
  response.headers["Content-Disposition"] = "attachment; filename=result.csv"
  return response
if __name__ == '__main__':
    app.run()
Mike C.
  • 1,761
  • 2
  • 22
  • 46
0

As Mike C. said the problem is that csv.reader() function takes a file object (or equivalent) instead of text. But instead of saving a file on the disk, you can decode on the stream :

stream = codecs.iterdecode(request_file.stream, 'utf-8')
result = transform(stream)

which makes for the full code :

import codecs
import operator
from flask import Flask, make_response, request
import csv

app = Flask(__name__)

def transform(text_file_contents):
  reader = csv.reader(text_file_contents)
  list_all = []

  row = next(reader)
  row.append('Pet4')
  list_all.append(row)

  for row in reader:
    pet4 = row[0]
    row[0] = "Birds"
    row.append(pet4)
    list_all.append(row)
  sortedlist = sorted(list_all, key=operator.itemgetter(0))
  return sortedlist


@app.route('/')
def form():
  return """
    <html>
        <body>
            <h1>Upload a CSV File</h1>

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

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


    stream = codecs.iterdecode(request_file.stream, 'utf-8')
    result = transform(stream)

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

if __name__ == '__main__':
    app.run()
JBLaf
  • 776
  • 5
  • 10