Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flask-restful - Custom error handling

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.

like image 765
JahMyst Avatar asked Dec 14 '16 18:12

JahMyst


People also ask

How does Flask handle errors in API?

Flask gives you the ability to raise any HTTP exception registered by Werkzeug. However, the default HTTP exceptions return simple exception pages. You might want to show custom error pages to the user when an error occurs. This can be done by registering error handlers.

Is flask RESTful deprecated?

The whole request parser part of Flask-RESTful is slated for removal and will be replaced by documentation on how to integrate with other packages that do the input/output stuff better (such as marshmallow). This means that it will be maintained until 2.0 but consider it deprecated.

Is Flask GOOD FOR REST API?

Flask does not have a good Browseable API option. The creator of Django REST Framework created a similar library for Flask called Flask-API, but it is not under active development nor is it ready for production use.

What is the difference between Flask and flask RESTful?

Flask is a highly flexible Python web framework built with a small core and easy-to-extend philosophy. Flask-Restful is an extension of Flask that offers extension to enable writing a clean object-oriented code for fast API development.


3 Answers

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))
like image 168
Anfernee Avatar answered Oct 30 '22 09:10

Anfernee


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.

like image 21
AYUSH SENAPATI Avatar answered Oct 30 '22 10:10

AYUSH SENAPATI


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...

like image 44
Stark Avatar answered Oct 30 '22 11:10

Stark