15

I want to define custom error handling for a Flask-restful API.

The suggested approach in the documentation here is to do the following:

errors = {
    'UserAlreadyExistsError': {
        'message': "A user with that username already exists.",
        'status': 409,
    },
    'ResourceDoesNotExist': {
        'message': "A resource with that ID no longer exists.",
        'status': 410,
        'extra': "Any extra information you want.",
    },
}
app = Flask(__name__)
api = flask_restful.Api(app, errors=errors)

Now I find this format pretty attractive but I need to specify more parameters when some exception happens. For example, when encountering ResourceDoesNotExist, I want to specify what id does not exist.

Currently, I'm doing the following:

app = Flask(__name__)
api = flask_restful.Api(app)


class APIException(Exception):
    def __init__(self, code, message):
        self._code = code
        self._message = message

    @property
    def code(self):
        return self._code

    @property
    def message(self):
        return self._message

    def __str__(self):
        return self.__class__.__name__ + ': ' + self.message


class ResourceDoesNotExist(APIException):
    """Custom exception when resource is not found."""
    def __init__(self, model_name, id):
        message = 'Resource {} {} not found'.format(model_name.title(), id)
        super(ResourceNotFound, self).__init__(404, message)


class MyResource(Resource):
    def get(self, id):
        try:
            model = MyModel.get(id)
            if not model:
               raise ResourceNotFound(MyModel.__name__, id)
        except APIException as e:
            abort(e.code, str(e))

When called with an id that doesn't exist MyResource will return the following JSON:

{'message': 'ResourceDoesNotExist: Resource MyModel 5 not found'}

This works fine but I'd like to use to Flask-restful error handling instead.

1

4 Answers 4

11

Instead of attaching errors dict to Api, I am overriding handle_error method of Api class to handle exceptions of my application.

# File: app.py
# ------------

from flask import Blueprint, jsonify
from flask_restful import Api
from werkzeug.http import HTTP_STATUS_CODES
from werkzeug.exceptions import HTTPException

from view import SomeView

class ExtendedAPI(Api):
    """This class overrides 'handle_error' method of 'Api' class ,
    to extend global exception handing functionality of 'flask-restful'.
    """
    def handle_error(self, err):
        """It helps preventing writing unnecessary
        try/except block though out the application
        """
        print(err) # log every exception raised in the application
        # Handle HTTPExceptions
        if isinstance(err, HTTPException):
            return jsonify({
                    'message': getattr(
                        err, 'description', HTTP_STATUS_CODES.get(err.code, '')
                    )
                }), err.code
        # If msg attribute is not set,
        # consider it as Python core exception and
        # hide sensitive error info from end user
        if not getattr(err, 'message', None):
            return jsonify({
                'message': 'Server has encountered some error'
                }), 500
        # Handle application specific custom exceptions
        return jsonify(**err.kwargs), err.http_status_code


api_bp = Blueprint('api', __name__)
api = ExtendedAPI(api_bp)

# Routes
api.add_resource(SomeView, '/some_list')

Custom exceptions can be kept in separate file, like:

# File: errors.py
# ---------------


class Error(Exception):
    """Base class for other exceptions"""
    def __init__(self, http_status_code:int, *args, **kwargs):
        # If the key `msg` is provided, provide the msg string
        # to Exception class in order to display
        # the msg while raising the exception
        self.http_status_code = http_status_code
        self.kwargs = kwargs
        msg = kwargs.get('msg', kwargs.get('message'))
        if msg:
            args = (msg,)
            super().__init__(args)
        self.args = list(args)
        for key in kwargs.keys():
            setattr(self, key, kwargs[key])


class ValidationError(Error):
    """Should be raised in case of custom validations"""

And in the views exceptions can be raised like:

# File: view.py
# -------------

from flask_restful import Resource
from errors import ValidationError as VE


class SomeView(Resource):
    def get(self):
        raise VE(
            400, # Http Status code
            msg='some error message', code=SomeCode
        )

Like in view, exceptions can actually be raised from any file in the app which will be handled by the ExtendedAPI handle_error method.

Sign up to request clarification or add additional context in comments.

4 Comments

In case of Python core exception- if you want to keep the error stack trace, just use logging.critical(err, exc_info=True) from logging lib
@Karolius yeah, inside handle_error function, in place of print statement you actually need to log that error.
Best answer by far. Thank you much @AYUSHSENAPATI!!
@AYUSHSENAPATI You saved my hours of research time. Much appreciated. Thank you
10

According to the docs

Flask-RESTful will call the handle_error() function on any 400 or 500 error that happens on a Flask-RESTful route, and leave other routes alone.

You can leverage this to implement the required functionality. The only downside is having to create a custom Api.

class CustomApi(flask_restful.Api):

    def handle_error(self, e):
        flask_restful.abort(e.code, str(e))

If you keep your defined exceptions, when an exception occurs, you'll get the same behaviour as

class MyResource(Resource):
    def get(self, id):
        try:
            model = MyModel.get(id)
            if not model:
               raise ResourceNotFound(MyModel.__name__, id)
        except APIException as e:
            abort(e.code, str(e))

1 Comment

If you want to return a json syntaxed error in the handle_error method, use return jsonify({"message":"your error message"}), e.code
5

I've used the Blueprint to work with the flask-restful, and I've found that the solution @billmccord and @cedmt provided on the issue not worked for this case, because the Blueprint don't have the handle_exception and handle_user_exception functions.

My workaround is that enhance the function handle_error of the Api, if the "error handler" of the "Exception" have been registered, just raise it, the "error handler" registered on app will deal with that Exception, or the Exception will be passed to the "flask-restful" controlled "custom error handler".

class ImprovedApi(Api):
    def handle_error(self, e):
        for val in current_app.error_handler_spec.values():
            for handler in val.values():
                registered_error_handlers = list(filter(lambda x: isinstance(e, x), handler.keys()))
                if len(registered_error_handlers) > 0:
                    raise e
        return super().handle_error(e)


api_entry = ImprovedApi(api_entry_bp)

BTW, seems the flask-restful had been deprecated...

4 Comments

Why do you think seems flask-restful had been deprecated?
@Chris2048 Maybe I used the wrong words, the update frequency of the repository is very low, and here, this is an issue that opened at 2014 for a version 1.0.0, but no any activities for a long time.
@Stark deprecated is for sure the wrong word to use, it may not be maintained like it used to be, but does it really have to be? Tons of repositories don't update frequently but are widely used.
@Stark exactly! If a repo updates constantly it can be both a good or bad thing, bad meaning that the repo had a lot of issues that need to be worked on, but good in that the maintainer is actively improving it. If it's not being updated, it either means that the repo is near perfect as is or the maintainer stopped taking care of it, just depends on the package
2

I faced the same issue too and after extending flask-restful.Api I realized that you really don't need to extend the flask-restful.Api

you can easily do this by inheriting from werkzeug.exceptions.HTTPException and solve this issue

app = Flask(__name__)
api = flask_restful.Api(app)

from werkzeug.exceptions import HTTPException

class APIException(HTTPException):
    def __init__(self, code, message):
        super().__init__()
        self.code = code
        self.description = message



class ResourceDoesNotExist(APIException):
        """Custom exception when resource is not found."""
    def __init__(self, model_name, id):
        message = 'Resource {} {} not found'.format(model_name.title(), id)
        super().__init__(404, message)


class MyResource(Resource):
    def get(self, id):
        try:
            model = MyModel.get(id)
            if not model:
               raise ResourceNotFound(MyModel.__name__, id)
        except APIException as e:
            abort(e.code, str(e))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.