How to return 400 (Bad Request) on Flask?

58,404

Solution 1

You have a variety of options:

The most basic:

@app.route('/')
def index():
    return "Record not found", 400

If you want to access the headers, you can grab the response object:

@app.route('/')
def index():
    resp = make_response("Record not found", 400)
    resp.headers['X-Something'] = 'A value'
    return resp

Or you can make it more explicit, and not just return a number, but return a status code object

from flask_api import status

@app.route('/')
def index():
    return "Record not found", status.HTTP_400_BAD_REQUEST

Further reading:

You can read more about the first two here: About Responses (Flask quickstart)
And the third here: Status codes (Flask API Guide)

Solution 2

I like to use the flask.Response class:

from flask import Response


@app.route("/")
def index():
    return Response(
        "The response body goes here",
        status=400,
    )

flask.abort is a wrapper around werkzeug.exceptions.abort which is really just a helper method to make it easier to raise HTTP exceptions. That's fine in most cases, but for restful APIs, I think it may be better to be explicit with return responses.

Solution 3

Here's some snippets from a Flask app I wrote years ago. It has an example of a 400 response

import werkzeug
from flask import Flask, Response, json
from flask_restplus import reqparse, Api, Resource, abort
from flask_restful import request
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

api = Api(app)

parser = reqparse.RequestParser()
parser.add_argument('address_to_score', type=werkzeug.datastructures.FileStorage, location='files')

class MissingColumnException(Exception):
    pass

class InvalidDateFormatException(Exception):
    pass

@api.route('/project')
class Project(Resource):

    @api.expect(parser)
    @api.response(200, 'Success')
    @api.response(400, 'Validation Error')
    def post(self):
        """
        Takes in an excel file of addresses and outputs a JSON with scores and rankings.
        """
        try:
            df, input_trees, needed_zones = data.parse_incoming_file(request)

        except MissingColumnException as e:
            abort(400, 'Excel File Missing Mandatory Column(s):', columns=str(e))

        except Exception as e:
            abort(400, str(e))

        project_trees = data.load_needed_trees(needed_zones, settings['directories']['current_tree_folder'])

        df = data.multiprocess_query(df, input_trees, project_trees)
        df = data.score_locations(df)
        df = data.rank_locations(df)
        df = data.replace_null(df)
        output_file = df.to_dict('index')
        resp = Response(json.dumps(output_file), mimetype='application/json')
        resp.status_code = 200

    return resp

@api.route('/project/health')
class ProjectHealth(Resource):

    @api.response(200, 'Success')
    def get(self):
        """
        Returns the status of the server if it's still running.
        """
        resp = Response(json.dumps('OK'), mimetype='application/json')
        resp.status_code = 200

    return resp

Solution 4

You can return a tuple with the second element being the status (either 400 or 500).

from flask import Flask
app = Flask(__name__)


@app.route('/')
def hello():
    return "Record not found", 400

if __name__ == '__main__':
    app.run()

Example of calling the API from python:

import requests

response = requests.get('http://127.0.0.1:5000/')

response.text
# 'This is a bad request!'

response.status_code
# 400

Solution 5

I think you're using the abort() function correctly. I suspect the issue here is that an error handler is that is catching the 400 error and then erroring out which causes the 500 error. See here for more info on flask error handling.

As an example, the following would change a 400 into a 500 error:

@app.errorhandler(400)
def handle_400_error(e):
    raise Exception("Unhandled Exception")

If you're not doing any error handling, it could be coming from the connexion framework, although I'm not familiar with this framework.

Share:
58,404
Luis Ramon Ramirez Rodriguez
Author by

Luis Ramon Ramirez Rodriguez

Updated on July 09, 2022

Comments

  • Luis Ramon Ramirez Rodriguez
    Luis Ramon Ramirez Rodriguez almost 2 years

    I have created a simple flask app that and I'm reading the response from python as:

    response = requests.post(url,data=json.dumps(data), headers=headers ) 
    data = json.loads(response.text)
    

    Now my issue is that under certain conditions I want to return a 400 or 500 message response. So far I'm doing it like this:

    abort(400, 'Record not found') 
    #or 
    abort(500, 'Some error...') 
    

    This does print the message on the terminal:

    enter image description here

    But in the API response I kept getting a 500 error response:

    enter image description here

    The structure of the code is as follows:

    |--my_app
       |--server.py
       |--main.py
       |--swagger.yml
    

    Where server.py has this code:

    from flask import render_template
    import connexion
    # Create the application instance
    app = connexion.App(__name__, specification_dir="./")
    # read the swagger.yml file to configure the endpoints
    app.add_api("swagger.yml")
    # Create a URL route in our application for "/"
    @app.route("/")
    def home():
        """
        This function just responds to the browser URL
        localhost:5000/
    
        :return:        the rendered template "home.html"
        """
        return render_template("home.html")
    if __name__ == "__main__":
        app.run(host="0.0.0.0", port="33")
    

    And main.py has all the function I'm using for the API endpoints.

    E.G:

    def my_funct():
       abort(400, 'Record not found') 
    

    When my_funct is called, I get the Record not found printed on the terminal, but not in the response from the API itself, where I always get the 500 message error.

  • Adam Hughes
    Adam Hughes about 4 years
    Can anyone elaborate on what the Response class adds over reutrning just a tuple of str/code?
  • frogg
    frogg over 2 years
    @AdamHughes in this instance they are identical, as in Flask is calling Response when you pass it the tuple. Response also allows you to set content type and mime type but I believe you can do this without importing response. Miguel Grinberg has this to say.