Home > Enterprise >  Flask Error handling not working properly
Flask Error handling not working properly

Time:08-30

I have a custom exception shown below where functions are raising these exceptions.

class UnauthorizedToSendException(HTTPException):
    code = 400
    description = 'Unauthorized to Send.'

They are then defined in Flask's create_app() as follows:

def handle_custom_exceptions(e):
    response = {"error": e.description, "message": ""}
    if len(e.args) > 0:
        response["message"] = e.args[0]
    # Add some logging so that we can monitor different types of errors
    app.logger.error(f"{e.description}: {response['message']}")
    return jsonify(response), e.code

 app.register_error_handler(UnauthorizedToSendException, handle_custom_exceptions)

When this exception is raised below:

class LaptopStatus(Resource):
    @staticmethod
    def get():
        raise UnauthorizedToSendException('you are unauthorized to send')

However, the output is always this way:

{
    "message": "you are unauthorized to send"
}

Is there something missing here?

CodePudding user response:

flask_restful.Api has its own error handling implementation that mostly replaces Flask's errorhandler functionality, so using the Flask.errorhandler decorator or Flask.register_error_handler won't work.

There are a couple solutions to this.

Don't Inherit from HTTPException and set PROPAGATE_EXCEPTIONS

The flask-restful error routing has short circuits for handling exceptions that don't inherit from HTTPException, and if you set your Flask app to propagate exceptions, it will send the exception to be handled by normal Flask's handlers.

import flask
from flask import Flask
from flask_restful import Resource, Api


app = Flask(__name__)

# Note that this doesn't inherit from HTTPException
class UnauthorizedToSendException(Exception):
    code = 400
    description = 'Unauthorized to Send'


@app.errorhandler(UnauthorizedToSendException)
def handle_unauth(e: Exception):
    rsp = {"error": e.description, "message": ""}
    if len(e.args) > 0:
        rsp["message"] = e.args[0]

    app.logger.error(f"{e.description}: {rsp['message']}")
    return flask.jsonify(rsp), e.code


class LaptopStatus(Resource):
    @staticmethod
    def get():
        raise UnauthorizedToSendException("Not authorized")


api = Api(app)
api.add_resource(LaptopStatus, '/status')


if __name__ == "__main__":
    # Setting this is important otherwise your raised
    # exception will just generate a regular exception
    app.config['PROPAGATE_EXCEPTIONS'] = True
    app.run()

Running this I get...

#> python flask-test-propagate.py
# ... blah blah ...


#> curl http://localhost:5000/status
{"error":"Unauthorized to Send","message":"Not authorized"}

Replace flask_restul.Api.error_router

This will override the behavior of the Api class's error_router method, and just use the original handler that Flask would use.

You can see it's just a monkey-patch of the class's method with...

Api.error_router = lambda self, hnd, e: hnd(e)

This will allow you to subclass HTTPException and override flask-restful's behavior.

import flask
from flask import Flask
from flask_restful import Resource, Api
from werkzeug.exceptions import HTTPException

# patch the Api class with a custom error router
# that just use's flask's handler (which is passed in as hnd)
Api.error_router = lambda self, hnd, e: hnd(e)

app = Flask(__name__)


class UnauthorizedToSendException(HTTPException):
    code = 400
    description = 'Unauthorized to Send'


@app.errorhandler(UnauthorizedToSendException)
def handle_unauth(e: Exception):
    print("custom!")
    rsp = {"error": e.description, "message": ""}
    if len(e.args) > 0:
        rsp["message"] = e.args[0]

    app.logger.error(f"{e.description}: {rsp['message']}")
    return flask.jsonify(rsp), e.code


class LaptopStatus(Resource):
    @staticmethod
    def get():
        raise UnauthorizedToSendException("Not authorized")


api = Api(app)
api.add_resource(LaptopStatus, '/status')


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

Switch to using flask.views.MethodView

Documentation:

Just thought of this, and it's a more drastic change to your code, but flask has facilities for making building REST APIs easier. flask-restful isn't exactly abandoned, but the pace of changes have slowed down the last several years, and various components like the error handling system have become too inflexible.

If you're using flask-restful specifically for the Resource implementation, you can switch to the MethodView, and then you don't need to do any kind of workarounds.

import flask
from flask import Flask
from flask.views import MethodView
from werkzeug.exceptions import HTTPException


app = Flask(__name__)


class UnauthorizedToSendException(HTTPException):
    code = 400
    description = 'Unauthorized to Send'


@app.errorhandler(UnauthorizedToSendException)
def handle_unauth(e: Exception):
    rsp = {"error": e.description, "message": ""}
    if len(e.args) > 0:
        rsp["message"] = e.args[0]

    app.logger.error(f"{e.description}: {rsp['message']}")
    return flask.jsonify(rsp), e.code


class LaptopStatusApi(MethodView):
    def get(self):
        raise UnauthorizedToSendException("Not authorized")


app.add_url_rule("/status", view_func=LaptopStatusApi.as_view("laptopstatus"))

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

CodePudding user response:

read the https://flask.palletsprojects.com/en/2.1.x/errorhandling/

but: See werkzeug.exception for default HTTPException classes. For HTTP400 (werkzeug.exception.BadRequest)

  • Related