Home > database >  How to return a 400 Bad Request response using Flask-RESTful.RequestParser?
How to return a 400 Bad Request response using Flask-RESTful.RequestParser?

Time:04-06

I am creating an API using Flask and Flask-RESTful. I wish to parse the body of an incoming request using flask_restful.reqparse.RequestParser(). Upon receiving an incorrect JSON, I would like to return a 400 Bad Request response. However, my application is instead returning a 500 Internal Server Error response. I thought the RequestParser() was supposed to handle these responses automatically? Can anyone explain what is going wrong?

Below is the code for the API Resource

from flask_restful import Resource, reqparse

class Foo(Resource):

    parser = reqparse.RequestParser()
    parser.add_argument("foo",
        type=int,
        required=True,
        action='append',
        help="Request body must contain a 'foo' key which comprises a list of IDs, e.g. {'foo': [44, 3213, 532, 4312]}"
    )

    def get(self):
        data = self.parser.parse_args(strict=True)
        return {'bar': data['foo']}

When I send a GET request to the API with the body {"err": [3, 4, 1]} I receive the following 500 Internal Server Error response:

{"message": "Internal Server Error"}

and not the message I specified in the help parameter. In my logs I also get the following error message:

KeyError: "'foo'"

I know I could wrap the data = self.parser.parse_args(strict=True) in a try/except KeyError clause and handle the incorrect JSON myself, but I thought that Flask-RESTful would do that for me? What else could I try?

CodePudding user response:

By defining an APIArgument class that will be passed to the RequestParser constructor you can define your own customized response. You also need to pass the bundle_errors = True to the constructor and configure flask by setting the application configuration key "BUNDLE_ERRORS" to True

See error handling of Request Parsing.

import json

from flask import Flask, Response, abort
from flask_restful import Api, Resource, reqparse
from flask_restful.reqparse import Argument

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

api = Api(app)


class APIArgument(Argument):
    def __init__(self, *args, **kwargs):
        super(APIArgument, self).__init__(*args, **kwargs)

    def handle_validation_error(self, error, bundle_errors):
        help_str = "(%s) " % self.help if self.help else ""
        msg = "[%s]: %s%s" % (self.name, help_str, str(error))
        res = Response(
            json.dumps({"message": msg, "code": 400, "status": "FAIL"}),
            mimetype="application/json",
            status=400,
        )
        return abort(res)


class Foo(Resource):
    parser = reqparse.RequestParser(argument_class=APIArgument, bundle_errors=True)
    parser.add_argument(
        "foo",
        type=int,
        action="append",
        required=True,
        help="Request body must contain a 'foo' key which comprises a list of IDs, e.g. {'foo': [44, 3213, 532, 4312]}",
    )

    def get(self):
        data = self.parser.parse_args(strict=True)
        return {'bar': data['foo']}


api.add_resource(Foo, '/')

if __name__ == "__main__":
    app.run(port=9000, debug=True)

CodePudding user response:

For those who do not want to deal with the boilerplate code, this also works:

By moving the parser code into the get() route, the default handle_validation_error function provided by flask-restful will be used (I guess):

class Foo(Resource):
    def get(self):
        parser = reqparse.RequestParser()
        parser.add_argument('foo', required=True, action="append", help="Request body must contain a 'foo' key which comprises a list of IDs, e.g. {'foo': [44, 3213, 532, 4312]}",
        data = parser.parse_args()
        print(data.get("foo")

        # this code won't be reached, because the parser aborts the request early if foo is missing
        return {'bar': data['foo']}

this means that you have to provide a separate parser for every method, but I think this is usually a good idea anyway, rather than defining this in the route itself, for all methods. flask-restful probably didn't intend to do that this way, that's why it doesn't work out of the box.

Also: Keep in mind that GET requests cannot have a json body if the request comes from a browser, this is only possible if no browser is involved! I generally advise against this, because it's not standard-compatible.

  • Related