Issue
Here's my intent: I need to use curl to connect to my Python application's Flask API, and upload the contents of a text file to be used by the Python application. However, I cannot seem to both complete the connection and upload the data.
Based upon lots of otherwise-fruitless searching, I am using the following command-line:
curl -X POST -i -F 'input_txt=@single_file.txt' http://myhost:5000/api_tst01/
However, that gives me a key error on the server side:
File "U:\home\testapp\app.py", line 303, in api_tst01
print(request.form["input_txt"])
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\users\<me>\AppData\Roaming\Python\Python311\site-packages\werkzeug\datastructures\structures.py", line 192, in __getitem__
raise exceptions.BadRequestKeyError(key)
werkzeug.exceptions.BadRequestKeyError: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
My server-side code is the following:
@app.route('/api_tst01/', methods=['POST'])
def api_tst01():
print(request.form["input_txt"])
At this point, I figure that if I can print it, I can otherwise access/use it to parse through for the relevant data. Clearly something is getting through, but how can I get to it? The file itself contains some XML data, nothing fancy.
Solution
When you submit a simple form value, like this:
curl http://localhost:5000/api_tst01 -F input_txt='hello world'
The request looks like (you can see this using the --trace-ascii <filename>
option to curl
):
POST /api_tst01 HTTP/1.1
Host: localhost:5000
User-Agent: curl/8.0.1
Accept: */*
Content-Length: 147
Content-Type: multipart/form-data; boundary=--------------------
----f74b8d8f0b3df63b
--------------------------f74b8d8f0b3df63b
Content-Disposition: form-data; name="input_txt"
foo
--------------------------f74b8d8f0b3df63b--
On the other hand, when reading data from a file, like this:
echo hello world > input.txt
curl http://localhost:5000/api_tst01 -F [email protected]
We get:
POST /api_tst01 HTTP/1.1
Host: localhost:5000
User-Agent: curl/8.0.1
Accept: */*
Content-Length: 204
Content-Type: multipart/form-data; boundary=--------------------
----cb4e6a25eda23951
--------------------------cb4e6a25eda23951
Content-Disposition: form-data; name="input_txt"; filename="input.txt"
Content-Type: text/plain
hello world.
--------------------------cb4e6a25eda23951--
Note in particular the Content-disposition
line, which includes a filename=
attribute in the second example.
This causes Flask to place the received data into your application's files
attribute, rather than the form
attribute. We can see the different if we write a simple server like this:
from flask import Flask, request
app = Flask(__name__)
@app.route('/api_tst01', methods=['POST'])
def api_tst01():
print('files:', request.files)
print('form:', request.form)
return "Thanks"
When we make the first request (with -F input_txt='hello world'
), we see:
files: ImmutableMultiDict([])
form: ImmutableMultiDict([('input_txt', 'hello world')])
But when we make the second request (with -F [email protected]
), we see:
files: ImmutableMultiDict([('input_txt', <FileStorage: 'input.txt' ('text/plain')>)])
form: ImmutableMultiDict([])
So you probably want to write your code to handle both situations:
from flask import Flask, request
app = Flask(__name__)
@app.route('/api_tst01', methods=['POST'])
def api_tst01():
if 'input_txt' in request.form:
data = request.form['input_txt']
elif 'input_txt' in request.files:
data = request.files['input_txt'].read()
print('received:', data)
return "Thanks"
The above works, but note that the received data is slightly different for request.forms
vs request.files
(in the first case it's a unicode string; in the second case it's a byte string and probably includes a trailing newline) .
Answered By - larsks Answer Checked By - Katrina (WPSolving Volunteer)