Flask- Not able to get PUT method working? - python

Having trouble trying to get PUT method to work. Every time I try
curl http://127.0.0.1:5000/videos/video3 -d "title=YES" -X PUT in the terminal, I end up with an error that says: {"message": "The browser (or proxy) sent a request that this server could not understand."}
I've tried the code below and was able to get the GET method working and thought I was getting close with PUT method but now got stuck.
from flask import Flask
from flask_restful import Resource, Api, reqparse, abort
app = Flask("VideoAPI")
api = Api(app)
parser = reqparse.RequestParser()
parser.add_argument('title',required=True)
videos = {
'video1': {'title': 'Hello World in Python'},
'video2': {'title': 'Why Matlab is the Best Language Ever'}
}
class Video(Resource):
def get(self, video_id):
if video_id == "all":
return videos
if video_id not in videos:
abort(404, message=f"Video {video_id} not found")
return videos[video_id]
def put(self, video_id):
args = parser.parse_args()
new_video = {'title': args['title']}
videos[video_id] = new_video
return {video_id: videos[video_id]}, 201
api.add_resource(Video, '/videos/<video_id>')
if __name__ == '__main__':
app.run()
Expected Output: {"video1": {"title": "Hello World In Python"}, "video2": {"title": Why Matlab is the best language ever"}, "video3": {"title": "YES"}}

Since you don't specify the Content-Type header in the request, curl sends it as application/x-www-form-urlencoded by default. Therefore, if you add location='form' to the argument definition it should work correctly:
parser.add_argument('title', required=True, location='form')

Your put method is expecting a JSON format {"title": "YES"} but the data you sent was not that format.
Curl request should be
curl -H "Content-Type: application/json" -X PUT -d '{"title": "YES"}' http://127.0.0.1:5000/videos/video3
Response
{
"video3": {
"title": "YES"
}
}
To get your expected output
def put(self, video_id):
args = parser.parse_args()
new_video = {'title': args['title']}
videos[video_id] = new_video
# return {video_id: videos[video_id]}, 201
return videos, 201

Related

Flask-Restful Error: request Content-Type was not 'application/json'."}

I was following this tutorial and it was going pretty well. He then introduced reqparse and I followed along. I tried to test my code and I get this error
{'message': "Did not attempt to load JSON data because the request Content-Type was not 'application/json'."}
I don't know if I'm missing something super obvious but I'm pretty sure I copied his code exactly. here's the code:
main.py
from flask import Flask, request
from flask_restful import Api, Resource, reqparse
app = Flask(__name__)
api = Api(app)
#basic get and post
names = {"sai": {"age": 19, "gender": "male"},
"bill": {"age": 23, "gender": "male"}}
class HelloWorld(Resource):
def get(self, name, numb):
return names[name]
def post(self):
return {"data": "Posted"}
api.add_resource(HelloWorld, "/helloworld/<string:name>/<int:numb>")
# getting larger data
pictures = {}
class picture(Resource):
def get(self, picture_id):
return pictures[picture_id]
def put(self, picture_id):
print(request.form['likes'])
pass
api.add_resource(picture, "/picture/<int:picture_id>")
# reqparse
video_put_args = reqparse.RequestParser() # make new request parser object to make sure it fits the correct guidelines
video_put_args.add_argument("name", type=str, help="Name of the video")
video_put_args.add_argument("views", type=int, help="Views on the video")
video_put_args.add_argument("likes", type=int, help="Likes on the video")
videos = {}
class Video(Resource):
def get(self, video_id):
return videos[video_id]
def post(self, video_id):
args = video_put_args.parse_args()
print(request.form['likes'])
return {video_id: args}
api.add_resource(Video, "/video/<int:video_id>")
if __name__ == "__main__":
app.run(debug=True)
test_rest.py
import requests
BASE = "http://127.0.0.1:5000/"
response = requests.post(BASE + 'video/1', {"likes": 10})
print(response.json())
I don't know why you have an issue as far as I can tell you did copy him exactly how he did it. Here's a fix that'll work although I can't explain why his code works and yours doesn't. His video is two years old so it could be deprecated behaviour.
import requests
import json
BASE = "http://127.0.0.1:5000/"
payload = {"likes": 10}
headers = {'accept': 'application/json'}
response = requests.post(BASE + 'video/1', json=payload)
print(response.json())
Currently following the same tutorial and faced the same issue.
Solved mine by adding the keyword argument json for the data
response = requests.post(BASE + 'video/1', json={"likes": 10})
You can set the header like the error message says.
import requests, json
BASE = "http://127.0.0.1:5000/"
# Set request's header.
headers = {"Content-Type": "application/json; charset=utf-8"}
# Set data.
data = {"likes": 10}
#
response = requests.post(BASE + 'video/1', headers=headers, json=data)
print("Status Code: ", response.status_code)
print("JSON Response: ", response.json())
If you are able to change the code:
I manage to solve the issue by adding location=<target> to parser.add_argument() function.
parser.add_argument("email", type=str, required=True)
+ parser.add_argument("email", type=str, required=True, location='form')
You need to add the correct location of your input data. Some possible values are json, args, and form. Learn more at:
https://flask-restful.readthedocs.io/en/latest/api.html#reqparse.RequestParser.parse_args
In my case, it is form. because I use multipart/form-data as input.
If you are unable to change the code:
Downgrade the werkzeug to the version before this commit
Credit: Flask-restx request parser returns 400 Bad Request
You can use the Response.get_json() method instead of the json property, which lets you specify a force parameter:
force (bool) — Ignore the mimetype and always try to parse JSON
Usage would then be:
import requests
response = requests.post('http://127.0.0.1:5000/video/1', {'likes': 10})
response_data_forced_json = response.get_json(force=True)
This is actually what is called when getting the Response.json property, only with the default arguments.

Flask: The browser (or proxy) sent a request that this server could not understand

I created a small Flask service. However each time I tried to use the say-hi endpoint, I get the following message:
{
"message": "The browser (or proxy) sent a request that this server could not understand."
}
My Flask service looks like this:
from flask import Flask, request
from flask_restful import Resource, Api, abort, reqparse
app = Flask(__name__)
api = Api(app)
class HelloResource(Resource):
def get(self):
return { 'message': 'Hello' }
class SayHiResource(Resource):
def get(self):
parser = reqparse.RequestParser()
parser.add_argument('name', required=True, help='Name cannot be blank')
args = parser.parse_args()
return { 'message': 'Hello ' + args['name'] }
api.add_resource(HelloResource, '/hello')
api.add_resource(SayHiResource, '/say-hi')
if __name__ == '__main__':
app.run(debug=True, load_dotenv=True)
However, there is not much information about why is failing.
The way I'm running is by using gunicorn and the serviceEntrypoint.py file,
which only has this content:
from src.api.service import app
if __name__ == '__main__':
app.run()
Here is my folder structure
.
├── requirements.txt
├── serviceEntrypoint.py
└── src
├── __init__.py
└── api
├── __init__.py
└── service.py
Why the /hello ending works, but the say-hi doesn't when I call to http://localhost:8000/say-hi?name=John?
The solution to this is to add location='args', so that reqparse uses only the query string value:
parser.add_argument('name', required=True, help='Name cannot be blank', location='args')
The reason for the issue is that Argument has 'json' as the default location and recent versions of Werkzeug (used by flask) will raise an exception when reqparse tries to read json data from the (non-json) request. This should probably be considered a bug in reqparse, but it's deprecated, so don't count on an update.
if you run the flask debug server and issue your HTTP request http://localhost:8000/say-hi?name=John, you will see that the actual error is:
message "Did not attempt to load JSON data because the request Content-Type was not 'application/json'."
There is documentation and examples here, but it boils down to choosing if the request should be a GET or a POST. The way you structured your request - you are passing 1 field only, the username - it looks like a GET, in this case you should have:
api.add_resource(SayHiResource, '/say-hi/<username>')
and class:
class SayHiResource(Resource):
def get(self, username):
return { 'message': 'Hello ' + username }
if you want to implement a POST request, please trace in the documentation the example that is triggered by the call: curl http://localhost:5000/todos -d "task=something new" -X POST -v
Update:
for using query parameters, you can use the request.args:
api.add_resource(SayHiResource, '/say-hi')
class:
class SayHiResource(Resource):
def get(self):
username = request.args.get("username")
status = request.args.get("status")
# print(query_args)
return { 'message': 'Hello {}, your status is: {}'.format(username, status) }
example:
[http_offline#greenhat-35 /tmp/] > curl 'http://localhost:8000/say-hi?username=lala&status=enabled'
{
"message": "Hello lala, your status is: enabled"
}
[http_offline#greenhat-35 /tmp/] >

Flask gettin 'NoneType' object is not subscriptable

Im making an Api that get two datas, "id" and "name", but i'm getting 'NoneType' object is not subscriptable" erro in name = request.json['Name']
from flask import Flask, jsonify, request, Response #import flask library
from checking import checker
app = Flask(__name__)
#app.route("/v1/products", methods=["GET", "POST"])
def getData():
user_id = request.json['id']
name = request.json['Name']
data = {'id' : user_id, 'Name' : name}
flag = checker(data)
if flag == True:
return 200, 'OK'
else:
return 403, 'Forbidden'
if __name__ == '__main__':
app.run(host='localhost', debug=True)
To send the data for API, i run the follow code:
curl -X POST -H "Content-Type: v1/products" -d '{'id' : '123', 'Name' : 'mesa'}' http://localhost:5000/v1/products
What i'm doing wrong ?
The issue isn't with your flask code but with your curl request. You aren't setting the content type and body properly, so there is no json for your API endpoint to process.
You'll want to change the first part of your command to: curl -X POST -H "Content-Type: application/json"
You may also have issues with your quotes in the request body, you'll either need to escape or modify the quotes so they aren't ambiguous.
This seem to work for me. Hope it does for you:
from flask import Flask, jsonify, request, Response #import flask library
app = Flask(__name__)
#app.route("/v1/products", methods=["GET", "POST"])
def getData():
user_id = request.args['id']
name = request.args['Name']
data = {'id' : user_id, 'Name' : name}
print(data)
if request.args != None:
print('OK')
else:
print('Forbidden')
return (data)
if __name__ == '__main__':
app.run(host='localhost', debug=True)

Flask-restful API not accepting json

I am trying to learn how to do apis. I copied everything from the book exactly but i am unable to post to the api. I tried posting {'name':'holy'} as raw data in postman( an json posting tool) to api and I get the vladation help message error"No Name provided":but when i try name=holy it works fine. I thought it was not suppose to work like that, How do i get it to work with {'name':'holy'}
from flask import Flask, request,render_template, jsonify
from flask_restful import Resource, Api,marshal_with, fields, reqparse
app = Flask(__name__)
api = Api(app)
class UserApi(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument(
'name',
required=True,
help='No name provided',
location=['form', 'json']
)
def get(self):
return jsonify ({"first_name":"Holy","last_name": "Johnson"})
def post(self):
args = self.reqparse.parse_args()
return jsonify ({"first_name":"Holy","last_name": "Johnson"})
api.add_resource(UserApi, '/users')
if __name__ == '__main__':
app.run(debug=True)
Your code should work - you have to specify the request header Content-Type: application/json. The reason why is because flask-restful's reqparse module tries to parse its data from flask.request.json, which is only set if Content-Type: application/json is set.
If you have access to curl (or wget), you can do the following to test:
$shell> curl -X POST -H "Content-Type: application/json" -d '{"name": "holly"}' http://localhost:5000/users
{
"first_name": "Holy",
"last_name": "Johnson"
}
In Postman, you can set a header, like shown in the screenshot below.

Is it possible to make POST request in Flask?

There is a need to make POST request from server side in Flask.
Let's imagine that we have:
#app.route("/test", methods=["POST"])
def test():
test = request.form["test"]
return "TEST: %s" % test
#app.route("/index")
def index():
# Is there something_like_this method in Flask to perform the POST request?
return something_like_this("/test", { "test" : "My Test Data" })
I haven't found anything specific in Flask documentation. Some say urllib2.urlopen is the issue but I failed to combine Flask and urlopen. Is it really possible?
For the record, here's general code to make a POST request from Python:
#make a POST request
import requests
dictToSend = {'question':'what is the answer?'}
res = requests.post('http://localhost:5000/tests/endpoint', json=dictToSend)
print 'response from server:',res.text
dictFromServer = res.json()
Notice that we are passing in a Python dict using the json= option. This conveniently tells the requests library to do two things:
serialize the dict to JSON
write the correct MIME type ('application/json') in the HTTP header
And here's a Flask application that will receive and respond to that POST request:
#handle a POST request
from flask import Flask, render_template, request, url_for, jsonify
app = Flask(__name__)
#app.route('/tests/endpoint', methods=['POST'])
def my_test_endpoint():
input_json = request.get_json(force=True)
# force=True, above, is necessary if another developer
# forgot to set the MIME type to 'application/json'
print 'data from client:', input_json
dictToReturn = {'answer':42}
return jsonify(dictToReturn)
if __name__ == '__main__':
app.run(debug=True)
Yes, to make a POST request you can use urllib, see the documentation.
I would however recommend to use the requests module instead.
EDIT:
I suggest you refactor your code to extract the common functionality:
#app.route("/test", methods=["POST"])
def test():
return _test(request.form["test"])
#app.route("/index")
def index():
return _test("My Test Data")
def _test(argument):
return "TEST: %s" % argument

Categories