Search code examples
pythonpython-3.xcsvflaskwerkzeug

werkzeug.exceptions.BadRequestKeyError While trying to get .csv file from a GET request using Flask


I'm trying to get a .csv file from a form, get it on my server, open it and treat the nunmers to return the valid ones, but when I try to open the file from the request I get "werkzeug.exceptions.BadRequestKeyError"

My html is this:

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>Flask Tutorial</title>
  </head>
  <body>
    <form action="/api/listtels" method="get">
      <label for="tels">file:</label>
      <input name="tels" type="file" id="tels"><br><br>
      <input type="submit" value="send">
    </form>
  </body>
</html>

And my python code is this:

import flask
import csv
from flask import Flask, render_template, request, jsonify

app = flask.Flask(__name__)
app.config["DEBUG"] = True

@app.route('/', methods=['GET'])
def home():
    return render_template("home.html")

@app.route('/api/listtels', methods=['GET'])
def ltels():

    tels = "ERROR" 
    ret = "ERROR"
    if 'tels' in request.args:
        tels = request.files['tels']
        ret = "tel\n"
    else:
        return("\'tels\' arg is needed")
    with open(tels, encoding='UTF-8') as f:
        rows = csv.reader(f,delimiter=",",lineterminator="\n")
        next(rows, None)
        for row in rows:
            tel = row[0]
            tel = str(''.join(filter(str.isnumeric, tel)))
            if len(tel) == 11:
                ret = ret + tel + "\n"
            elif len(tel) == 13:
                ret = ret + "+" + tel + "\n"
    return (ret)


app.run()

It's been 3 days since I started to use Python and Flask, so plz, no judgment :D


Solution

  • The flask request.files machinery assumes that the request is going to be a POST, because it isn't possible to upload a file via a GET request (you can GET the file's data, but that's not quite the same thing*).

    Therefore, the form's method attribute should be POST, and it should also have the attribute enctype set to "multipart/form-data" for absolute correctness.

    <form action="/api/listtels" method="POST" enctype="multipart/form-data">
    

    On the server side

    • The route should accept POST requests only.
    • The names of file inputs don't form part of request.args, so the membership test should be changed to test request.files.
    • The uploaded file needs to be decoded and loaded into a buffer so that it can be handled by csv.reader (or it could be written to disk and then read back)
    import io
    
    @app.route('/api/listtels', methods=['POST'])
    def ltels():
    
        tels = "ERROR" 
        ret = "ERROR"
        # Check if the file is loaded
        if 'tels' in request.files:
            tels = request.files['tels']
            ret = "tel\n"
        else:
            return("\'tels\' arg is needed")
        # Load decoded file data into a text buffer
        buf = io.StringIO(tels.read().decode('utf-8'))
        rows = csv.reader(buf,delimiter=",",lineterminator="\n")
        next(rows, None)
        for row in rows:
            tel = row[0]
            tel = str(''.join(filter(str.isnumeric, tel)))
            if len(tel) == 11:
                ret = ret + tel + "\n"
            elif len(tel) == 13:
                ret = ret + "+" + tel + "\n"
    return (ret)
    

    * You could get the content via GET either by pasting the file's contents into an <input type="textarea"...> or manipulating the form data with javascript before sending it to the server.