Flask-restful: app_errorhandler has problem with gunicorn

Created on 20 Dec 2018  路  2Comments  路  Source: flask-restful/flask-restful

version:

Flask               0.11.1
Flask-RESTful       0.3.7
gunicorn            19.9.0

DEBUG config:

class DevelopmentConfig(Config):
    DEBUG = True
    SQLALCHEMY_ECHO = False

Approximate code:

from flask import Blueprint
from flask_restful import Api
api_bp = Blueprint('api', __name__)
api = Api(api_bp)

@api_bp.app_errorhandler(Exception)
def internal_error(e):
    error = str(e)
    if request.accept_mimetypes.accept_json:
        response = jsonify(
            {'error': 'exception  error', 'message': error})
        response.status_code = 500
        logger = current_app.logger
        logger.warning(traceback.format_exc())
        db.session.rollback()
        return response
    return render_template('500.html'), 500

@api_bp.route('/hello')
def hello():
    raise Exception

class world(Resource):
    def get(self):
        raise Exception

api.add_resource(world, '/world')

when DEBUG is True, app_errorhandler can work with gunicorn.

/hello and /world response is
{
    "error": "exception error",
    "message": ""
}

when DEBUG is False, app_errorhandler has problem with gunicorn,

/hello response is 
{
    "error": "exception error",
    "message": ""
}
/world response is
{
    "message": "Internal Server Error"
}

Most helpful comment

@woshihaoren When call api = Api(api_bp), Api will replace the origin Flask App exception handler.

See flask_restful/__init__.py#L193

    def _init_app(self, app):
        """Perform initialization actions with the given :class:`flask.Flask`
        object.
        :param app: The flask application object
        :type app: flask.Flask
        """
        app.handle_exception = partial(self.error_router, app.handle_exception)
        app.handle_user_exception = partial(self.error_router, app.handle_user_exception)

        if len(self.resources) > 0:
            for resource, urls, kwargs in self.resources:
                self._register_view(app, resource, *urls, **kwargs)

In error_router, check whether the exception is raised from a flask-restful endpoint handler.

    def error_router(self, original_handler, e):
        """This function decides whether the error occured in a flask-restful
        endpoint or not. If it happened in a flask-restful endpoint, our
        handler will be dispatched. If it happened in an unrelated view, the
        app's original error handler will be dispatched.
        In the event that the error occurred in a flask-restful endpoint but
        the local handler can't resolve the situation, the router will fall
        back onto the original_handler as last resort.
        :param original_handler: the original Flask error handler for the app
        :type original_handler: function
        :param e: the exception raised while handling the request
        :type e: Exception
        """
        if self._has_fr_route():
            try:
                return self.handle_error(e)
            except Exception:
                pass  # Fall through to original handler
            return original_handler(e)

handle_error check the exception type and the value of PROPAGATE_EXCEPTIONS flask_restful/__init__.py#L281

    def handle_error(self, e):
        """Error handler for the API transforms a raised exception into a Flask
        response, with the appropriate HTTP status code and body.
        :param e: the raised Exception object
        :type e: Exception
        """
        got_request_exception.send(current_app._get_current_object(), exception=e)

        if not isinstance(e, HTTPException) and current_app.propagate_exceptions:
            exc_type, exc_value, tb = sys.exc_info()
            if exc_value is e:
                raise
            else:
                raise e

In DEBUG mode, PROPAGATE_EXCEPTIONS will be True. See also Flask doc.

Exceptions are re-raised rather than being handled by the app鈥檚 error handlers. If not set, this is implicitly true if TESTING or DEBUG is enabled.

So that is the reason of why it can work in DEBUG mode. There are some solution to solve the problem:

  • PROPAGATE_EXCEPTIONS = True
  • inherit Api class and override the handle_error method
  • use flask-restul style exception handling, more detail please see custom-error-handlers

All 2 comments

@woshihaoren When call api = Api(api_bp), Api will replace the origin Flask App exception handler.

See flask_restful/__init__.py#L193

    def _init_app(self, app):
        """Perform initialization actions with the given :class:`flask.Flask`
        object.
        :param app: The flask application object
        :type app: flask.Flask
        """
        app.handle_exception = partial(self.error_router, app.handle_exception)
        app.handle_user_exception = partial(self.error_router, app.handle_user_exception)

        if len(self.resources) > 0:
            for resource, urls, kwargs in self.resources:
                self._register_view(app, resource, *urls, **kwargs)

In error_router, check whether the exception is raised from a flask-restful endpoint handler.

    def error_router(self, original_handler, e):
        """This function decides whether the error occured in a flask-restful
        endpoint or not. If it happened in a flask-restful endpoint, our
        handler will be dispatched. If it happened in an unrelated view, the
        app's original error handler will be dispatched.
        In the event that the error occurred in a flask-restful endpoint but
        the local handler can't resolve the situation, the router will fall
        back onto the original_handler as last resort.
        :param original_handler: the original Flask error handler for the app
        :type original_handler: function
        :param e: the exception raised while handling the request
        :type e: Exception
        """
        if self._has_fr_route():
            try:
                return self.handle_error(e)
            except Exception:
                pass  # Fall through to original handler
            return original_handler(e)

handle_error check the exception type and the value of PROPAGATE_EXCEPTIONS flask_restful/__init__.py#L281

    def handle_error(self, e):
        """Error handler for the API transforms a raised exception into a Flask
        response, with the appropriate HTTP status code and body.
        :param e: the raised Exception object
        :type e: Exception
        """
        got_request_exception.send(current_app._get_current_object(), exception=e)

        if not isinstance(e, HTTPException) and current_app.propagate_exceptions:
            exc_type, exc_value, tb = sys.exc_info()
            if exc_value is e:
                raise
            else:
                raise e

In DEBUG mode, PROPAGATE_EXCEPTIONS will be True. See also Flask doc.

Exceptions are re-raised rather than being handled by the app鈥檚 error handlers. If not set, this is implicitly true if TESTING or DEBUG is enabled.

So that is the reason of why it can work in DEBUG mode. There are some solution to solve the problem:

  • PROPAGATE_EXCEPTIONS = True
  • inherit Api class and override the handle_error method
  • use flask-restul style exception handling, more detail please see custom-error-handlers

@Hanaasagi Perfect answer!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ueg1990 picture ueg1990  路  4Comments

ParthGandhi picture ParthGandhi  路  6Comments

codebykyle picture codebykyle  路  3Comments

lipis picture lipis  路  3Comments

makmanalp picture makmanalp  路  8Comments