Skip to content

Instantly share code, notes, and snippets.

@danijar
Created February 27, 2016 13:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save danijar/827bee07350a96bbf35b to your computer and use it in GitHub Desktop.
Save danijar/827bee07350a96bbf35b to your computer and use it in GitHub Desktop.
import flask_restful
class Api(flask_restful.Api):
"""
Patch Flask-style custom error handling into the Flask-RESTful api class.
"""
def __init__(self, *args, **kwargs):
super(Api, self).__init__(*args, **kwargs)
self._errorhandlers = []
def errorhandler(self, exception_type):
"""
Defined handlers for exceptions. Example:
@api.errorhandler(ServerError):
def handle_server_error(error):
response = flask.jsonify({'message': error.message})
response.status_code = error.status_code
return response
"""
def wrapper(func):
self._errorhandlers.append((exception_type, func))
# Sort error handlers to have sub exceptions first, so that those
# take preference over base exceptions.
self._errorhandlers = sorted(
self._errorhandlers,
key=lambda x: x[0],
cmp=self._inheritance_comparator)
print(self._errorhandlers)
return func
return wrapper
def handle_error(self, error, previous_errors=None):
# Keep track of previous errors in the current chain of exception
# handling in order to prevent infinite cycles that would occur if two
# error handler raise the exception handled by the other.
previous_errors = previous_errors or []
previous_errors.append(type(error))
# Try to find the first custom handler for the occured exception.
for exception_type, handler in self._errorhandlers:
if not isinstance(error, exception_type):
continue
try:
return handler(error)
except Exception as new_error:
if type(new_error) not in previous_errors:
return self.handle_error(new_error, previous_errors)
break
# If no matching handler was found or an infinite cycle is detected,
# fall back to Flask-RESTful's error handling.
return super(Api, self).handle_error(error)
@staticmethod
def _inheritance_comparator(lhs, rhs):
lhs_sub = issubclass(lhs, rhs)
rhs_sub = issubclass(lhs, rhs)
if lhs_sub and not rhs_sub:
return -1
if rhs_sub and not lhs_sub:
return 1
return 0
@magnuschill
Copy link

Hello, this is very helpful! I found this gist from following you comment on this issue:
flask-restful/flask-restful#274
If you are willing to share this code freely could you please add a license to the header?
Also, I created a fork which addresses an issue with python3.5 compatibility if you are interested.

@danijar
Copy link
Author

danijar commented May 16, 2016

Hi and sorry for the late reply. You can freely use this code under the MIT license.

@erwinyusrizal
Copy link

erwinyusrizal commented May 25, 2017

@danijar

Thanks for the patch since RF pull request seems given up :(

I tried your patch above, but my custom exception handler is not executed, it seems this conditions (Line 43) never True

for exception_type, handler in self._errorhandlers:
            if not isinstance(error, exception_type):
                continue

it always fall back to Flask-RESTful's error handling. Any idea?

Cheers,

@rhythmize
Copy link

rhythmize commented Apr 26, 2018

Hi
Your code patch is really good. But I can't figure out the significance of previous_errors list.
From what I understand, every exception type is mapped with a method handler, and it doesn't handle its Base or Child exceptions. So how can possibly infinite cycle of exceptions may occur. Moreover, handle_error method of flask_restful.Api has no such implementation.
Can you just clarify the confusion?
Thanks

@minelminel
Copy link

For anyone getting the error 'cmp' is an invalid keyword argument for this function, replace lines 27-30 with this snippet

import functools
...
self._errorhandlers = sorted(
    self._errorhandlers,
    key=functools.cmp_to_key(self._inheritance_comparator)) 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment