How do parse a curl PUT request in Flask-RESTful? - python

How do I save the data uploaded using a curl command like curl localhost:5000/upload/test.bin --upload-file tmp/test.bin in a Flask-RESTful PUT handler method?
The code in Ron Harlev's answer to Flask-RESTful - Upload image works with the POST request from curl -F "file=#tmp/test.bin" localhost:5000/upload/test.bin (slightly modified below):
def post(self, filepath):
parse = reqparse.RequestParser()
parse.add_argument('file', type=werkzeug.datastructures.FileStorage, location='files')
args = parse.parse_args()
upload_file = args['file']
upload_file.save("/usr/tmp/{}".format(filepath))
return ({'filepath': filepath}, 200)
But if I try to use the code to handle the PUT request from curl --upload-file (changing post to put above, of course) I get: "'NoneType' object has no attribute 'save'". This refers to the second-last line in the above code.
How do I get a handle to the file data uploaded using curl --upload-file, so I can save it to a local file?
Update: This works around the problem: curl --request PUT -F "file=#tmp/test.bin" localhost:5000/upload/test.bin, but I still don't have an answer to my question.

curls docs define --upload-file as a PUT http request https://curl.haxx.se/docs/manpage.html#-T.
I'm not sure of the need to handle this through a restful API and I'm pretty sure that's where curl is causing problems, perhaps the assumption that this must be done through flask-restful is holding you back?
maybe try building this as a vanilla flask endpoint instead, this code should work for you.
from flask import Flask, request, jsonify
...
#app.route('/simpleupload/<string:filepath>', methods=['POST','PUT'])
def flask_upload(filepath):
with open("/tmp/{}".format(filepath), 'wb') as file:
file.write(request.stream.read())
return (jsonify({'filepath': filepath}), 200)

Related

Postman, Python and passing images and metadata to a web service

this is a two-part question: I have seen individual pieces discussed, but can't seem to get the recommended suggestions to work together. I want to create a web service to store images and their metadata passed from a caller and run a test call from Postman to make sure it is working. So to pass an image (Drew16.jpg) to the web service via Postman, it appears I need something like this:
For the web service, I have some python/flask code to read the request (one of many variations I have tried):
from flask import Flask, jsonify, request, render_template
from flask_restful import Resource, Api, reqparse
...
def post(self, name):
request_data = request.get_json()
userId = request_data['UserId']
type = request_data['ImageType']
image = request.files['Image']
Had no problem with the data portion and straight JSON but adding the image has been a bugger. Where am I going wrong on my Postman config? What is the actual set of Python commands for reading the metadata and the file from the post? TIA
Pardon the almost blog post. I am posting this because while you can find partial answers in various places, I haven't run across a complete post anywhere, which would have saved me a ton of time. The problem is you need both sides to the story in order to verify either.
So I want to send a request using Postman to a Python/Flask web service. It has to have an image along with some metadata.
Here are the settings for Postman (URL, Headers):
And Body:
Now on to the web service. Here is a bare bones service which will take the request, print the metadata and save the file:
from flask import Flask, request
app = Flask(__name__)
# POST - just get the image and metadata
#app.route('/RequestImageWithMetadata', methods=['POST'])
def post():
request_data = request.form['some_text']
print(request_data)
imagefile = request.files.get('imagefile', '')
imagefile.save('D:/temp/test_image.jpg')
return "OK", 200
app.run(port=5000)
Enjoy!
Make sure `request.files['Image'] contains the image you are sending and follow http://flask.pocoo.org/docs/1.0/patterns/fileuploads/ to save the file to your file system. Something like
file = request.files['Image']
file.save('./test_image.jpg')
might do what you want, while you will have to work out the details of how the file should be named and where it should be placed.

Django service on gunicorn POST request is recieved as GET?

I have a Django rest service running on virutal environment on gunicorn server with the following .wsgi file:
import os, sys import site
site.addsitedir('/opt/valuation/env/lib/python2.7/site-packages')
sys.stdout = sys.stderr
os.environ['DJANGO_SETTINGS_MODULE'] = 'valuation.valuationcont.valuation.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
When I do curl POST call the service works perfectly:
curl -H "Content-Type: application/json" -X POST -d '{...}' -u username:password http://localhost:8000/valuation/predict/
But when I do the same request on API gateway using axios, Django service responds my custom GET response ("GET not supported, try POST").
axios({
method: 'post',
url:'http://localhost:8000/valuation/predict',
headers:{
"Content-Type":"application/json",
"Authorization":"Basic [BASE64 ENCODING]"
},
data:{
...
}
}).then(response=>{
console.log(response.data)
}).catch(err=>{
console.log(err.toString())
})
The request is transformed from GET to POST.
This only happens with the django/gunicorn service.
Since I am new to django/gunicorn I think there is something wrong with the .wsgi file. But how come the curl call then works?
Any help appreciated, been struggling with this for a week now.
Edit:
Managed to recreate the same problem in my local machine. axios POST requests using its API are translated into GET.
Using the axios.post(...) method I managed to get 403 and 201. All while POSTMAN works fine.
I have a suspicion that since the POST fails axios API has a default fallback to GET which then doesn't fail and service responds normally ("GET not supported" as is should).
New step to debug this would be to ask, how do I recreate POSTMAN POST call as close as possible in javascript since POSTMAN is working and it is obviously axios that is causing the problems.
You're not using the same URL. In the curl snippet you request http://localhost:8000/valuation/predict/ but in the second you request http://localhost:8000/valuation/predict - without the final slash.
Django by default redirects URLs that don't end in a slash to one that does, and a redirect is always a GET.

Changing GET to POST in Python (Flask)

I am trying to create a simple app where an array of integers is generated on the server and sent to the client. Here is some sample (working) code in app.py:
from flask import Flask, render_template, request, url_for
import random
app = Flask(__name__)
#app.route('/')
def form():
s_abc = [random.random() for _ in range(40)]
return render_template('abc.html', s_abc=s_abc)
if __name__ == '__main__':
app.run(debug=True)
And here is a (working) snippet of abc.html:
<div>
{{s_abc}}
</div>
The problem is, this code uses the GET HTTP method (which Flask uses by default when none is specified). I would like to instead use the POST HTTP method, since it is apparently more secure for sending data. (Source: http://blog.teamtreehouse.com/the-definitive-guide-to-get-vs-post)
To do this, I tried to change:
#app.route('/')
to:
#app.route('/', methods=['POST'])
Unfortunately, I got an error: "Method Not Allowed: The method is not allowed for the requested URL."
Question: How should I fix this?
[Note: From what I currently understand, I would need to create a form to use the POST method, but I don't think my website would need a form, since the client isn't sending data to the server. (In my website, the server is sending data to the client.)]
[Note: This is similar to a question I asked previously, but not the same, because here I am asking about a specific error message, Method not Allowed.]
You should not be using the POST method in this instance. The POST method is meant to be used when you are changing data on the server. In this case you are only retrieving data, so GET is the more appropriate method to use here.
In any case, if you do decide to use POST, your code is actually working. You can see this if you use a POST request to access the endpoint:
$ curl -X POST http://127.0.0.1:5000/
<div>
[0.03464541692036849, 0.5588700799957625, 0.4702806873145451, 0.7525198710149907, 0.0674801622743858, 0.28229897849445273, 0.17400190415782735, 0.48911931330821357, 0.8033543541248421, 0.16335301905982258, 0.3307436416488905, 0.11670066397858725, 0.552907551276049, 0.6699689958218984, 0.7444295210533091, 0.8258885497774587, 0.8656500198078877, 0.6826827672886756, 0.27219907080455874, 0.9053285546116574, 0.8328655798090894, 0.2323223157770763, 0.9775485685217323, 0.34887389370958166, 0.17092511319368353, 0.20875570398480459, 0.6744092445507751, 0.6176283706166301, 0.05070680888843082, 0.3441890248079591, 0.17701427714228501, 0.115329649057473, 0.325114272097177, 0.19386610624431766, 0.18892384889766745, 0.511139418514318, 0.019931284111035397, 0.5240369332606203, 0.8936272011794374, 0.9665936114223397]
</div>
If you just access the URL in your browser, this will actually send a GET request, and you have modified your code to specifically only allow the POST method - which is why you get the Method Not Allowed response.
$ curl -X GET http://127.0.0.1:5000/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
The best solution is for you to use GET in this instance.
When you use the browser to visit a page, the browser always send 'GET' request to server... so when you've changed the methods to 'POST', flask can't find any GET route for '/' and return "Method Not Allowed" error, as the '/' doesn't allows GET anymore when browsers asks for that page.
You shouldn't use POST for this. POST only used when submitting data from forms or ajax.

How can I get uwsgi to accept POST request without data?

I have a web application that serves post requests, and handles cases when no data or empty data are given. When running the application manually, it behaves properly. But when serving it through uwsgi, a specific case fails, POST requests with no data (empty data works):
$ curl -X POST http://www.example.com/search
curl: (52) Empty reply from server
$ curl -d "" http://www.example.com/search
[{...}]
Is there any uwsgi option I missed that could fix this behavior? How can I get uwsgi to properly forward those requests to my web application?
As #roberto stated, it was a problem in my code. I had this piece of code:
When a user does a post query without data, wheezy will try building self.request.form using environ variables like CONTENT_LENGTH, which here doesn't exist and raises an error. Somehow, the try/except did not work with uwsgi, so I changed:
try:
self.request.form
except:
self.request.form = {}
self.request.files = {}
As follow:
if not self.request.environ.get('CONTENT_LENGTH', False):
self.request.form = {}
self.request.files = {}
And it works fine now

Flask - Getting request parameters in Google App Engine?

I'm running Flask on App Engine and I'm having trouble getting the request parameters of an issued request.
Here's my function being called:
#APP.route('/some_model/new', methods = ['PUT'])
def new_some_model():
prop1 = request.args.get('prop1')
prop2 = request.args.get('prop2')
import logging
logging.error(prop1)
logging.error(prop2)
And then I'm running the following command with curl:
curl -X PUT -d "prop1=prop1&prop2=prop2" http://myapp.appspot.com/some_function/new
I've tried several variations, but with no success. The curl command returns a "500 server error" and in the app-log I see that both prop1 and prop2 are None at the point of logging. The server error came from the properties being required later on. So the problem is that the request.args.get() is returning nothing. Any suggestions on what I might be doing wrong? Thank you much!
Data sent by POST or PUT will be in request.form, request.args is for parsed query string data.

Categories