Created
February 27, 2016 13:25
-
-
Save danijar/827bee07350a96bbf35b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
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
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
@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
it always fall back to Flask-RESTful's error handling. Any idea?
Cheers,