Flask-restful API not accepting json - python

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.

Related

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/] >

Get POST request body as JSON of array as root node in flask-restful

Using flask-restful I'm trying to capture a JSON array passed into the body of a POST request, but can't seem to find it using the RequestParser, any ideas?
Currently my code looks like this:
# api.py
from flask import Flask
from flask_restful import Api
from resources import CustomRange
app = Flask(__name__)
api = Api(app)
api.add_resource(CustomRange, '/customrange/<start>/<end>')
app.run()
# resources.py
from flask_restful import Resource, reqparse
parser = reqparse.RequestParser()
parser.add_argument('data', action="append")
#parser.add_argument('data', type=int, location="json")
#parser.add_argument('data', type=int, location="form")
#parser.add_argument('data', location="json")
#parser.add_argument('data', location="json", action="append")
#parser.add_argument('data', type=list)
#parser.add_argument('data', type=list, location="json")
#parser.add_argument('data', type=list, location="form")
## None of the above variants seem to capture it
class CustomRange(Resource):
def post(self, start: int, end: int):
args = parser.parse_args()
return args # Always {'data': None}
I tried a simple example of passing the following into the POST body:
[1, 2, 3]
But no dice. Even tried without parser.add_argument.
Here's the full test request that I'm using:
curl --location --request POST '127.0.0.1:5000/customrange/1/100' \
--header 'Content-Type: application/json' \
--data-raw '[1, 2, 3]'
Currently reqparse will handle only JSON objects as body (source: will fail on any other object except dict and MultiDict), and not any other type. You'll have to grab the request object directly for your needs.
from flask import request
from flask_restful import Resource
class CustomRange(Resource):
def post(self, start: int, end: int):
args = request.json
# rest of your code

Flask app request.get_json() return (Ellipsis, Ellipsis) instead of json

I am building a simple Flask API and I Am testing a post request from Postman,
like this {"name": "Frosty"} . This is my class that handles the requests to the endpoint where the POST request goes:
from http import HTTPStatus
from flask.views import MethodView
from flask import Blueprint
from injector import singleton, inject
from flask import jsonify, abort, request
#singleton
class PetsController(MethodView):
#inject
def __init__(self) -> None:
super().__init__()
self.pets = [
{"id": 1, "name": "Snoopy"},
{"id": 2, "name": "Furball"},
{"id": 3, "name": "Alaska"},
]
def configure(self):
self.pets_view = Blueprint("pets_view", __name__)
self.pets_view.add_url_rule("/pets/", view_func=PetsController.as_view("pets"))
def get(self):
return jsonify({"pets": self.pets})
def post(self):
data = request.get_json()
if not data or not "name" in data:
return jsonify(
message=f"Data missing from POST request {data}",
status=HTTPStatus.BAD_REQUEST.value,
)
new_pet = {"id": len(self.pets) + 1, "name": data["name"]}
self.pets.append(new_pet)
return jsonify(
message=f"new pet added: {new_pet}",
status=HTTPStatus.CREATED.value,
)
I am getting a bad request response because the request.get_json() ad request.json both return this tuple (Ellipsis, Ellipsis).
Any ideas why?
Ok it seems that was a Postman glitch, even though I had define in the Headers the Content-Type: application/json it was not sending that information.
I deleted and started a new POST request and first manually added the header and then added the raw body data, selected type JSON and it worked.
Try this. It will surely work; it's worked for me.
request.get_json(force=True, silent=True, cache=False)
P.S.: Only setting cache=false also worked for me.

Consume JSON body, not form data

This is my Python. The input to it is
curl http://35.173.47.24:8090/put -d {"id":102} -X PUT
My code:
from flask import Flask, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
todos = {}
class TodoSimple(Resource):
def put(self):
i=request.form['id']
print(i)
api.add_resource(TodoSimple, '/put')
if __name__ == '__main__':
app.run(host='0.0.0.0',port=8090,debug=True)
I need to pass json parameter. What do I have to change?
change the request to
curl -X PUT http://25.173.47.24:8080/put -d '{"id":102}' -H "Content-Type:application/json"
issue resolved issue was with code . request.get_json should be used

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