Merge pull request #1431 from untitaker/flask-errorhandling-amend

Some cleanup for errorhandling refactor
This commit is contained in:
Markus Unterwaditzer 2015-04-12 00:06:57 +02:00
commit aaebe9fe25
2 changed files with 105 additions and 97 deletions

View file

@ -14,7 +14,7 @@ from threading import Lock
from datetime import timedelta from datetime import timedelta
from itertools import chain from itertools import chain
from functools import update_wrapper from functools import update_wrapper
from collections import Mapping from collections import Mapping, deque
from werkzeug.datastructures import ImmutableDict from werkzeug.datastructures import ImmutableDict
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
@ -1412,26 +1412,36 @@ class Flask(_PackageBoundObject):
""" """
exc_class, code = self._get_exc_class_and_code(type(e)) exc_class, code = self._get_exc_class_and_code(type(e))
def find_superclass(handler_map): def find_handler(handler_map):
if not handler_map: if not handler_map:
return None return
for superclass in exc_class.__mro__: queue = deque(exc_class.__mro__)
if superclass is BaseException: # Protect from geniuses who might create circular references in
return None # __mro__
handler = handler_map.get(superclass) done = set()
while True:
cls = queue.popleft()
if cls in done:
continue
done.add(cls)
handler = handler_map.get(cls)
if handler is not None: if handler is not None:
handler_map[exc_class] = handler # cache for next time exc_class is raised # cache for next time exc_class is raised
handler_map[exc_class] = handler
return handler return handler
return None
queue.extend(cls.__mro__)
# try blueprint handlers # try blueprint handlers
handler = find_superclass(self.error_handler_spec.get(request.blueprint, {}).get(code)) handler = find_handler(self.error_handler_spec
.get(request.blueprint, {})
.get(code))
if handler is not None: if handler is not None:
return handler return handler
# fall back to app handlers # fall back to app handlers
return find_superclass(self.error_handler_spec[None].get(code)) return find_handler(self.error_handler_spec[None].get(code))
def handle_http_exception(self, e): def handle_http_exception(self, e):
"""Handles an HTTP exception. By default this will invoke the """Handles an HTTP exception. By default this will invoke the

View file

@ -4,110 +4,108 @@ import flask
def test_error_handler_subclass(): def test_error_handler_subclass():
app = flask.Flask(__name__) app = flask.Flask(__name__)
class ParentException(Exception): class ParentException(Exception):
pass pass
class ChildExceptionUnregistered(ParentException): class ChildExceptionUnregistered(ParentException):
pass pass
class ChildExceptionRegistered(ParentException): class ChildExceptionRegistered(ParentException):
pass pass
@app.errorhandler(ParentException) @app.errorhandler(ParentException)
def parent_exception_handler(e): def parent_exception_handler(e):
assert isinstance(e, ParentException) assert isinstance(e, ParentException)
return 'parent' return 'parent'
@app.errorhandler(ChildExceptionRegistered) @app.errorhandler(ChildExceptionRegistered)
def child_exception_handler(e): def child_exception_handler(e):
assert isinstance(e, ChildExceptionRegistered) assert isinstance(e, ChildExceptionRegistered)
return 'child-registered' return 'child-registered'
@app.route('/parent') @app.route('/parent')
def parent_test(): def parent_test():
raise ParentException() raise ParentException()
@app.route('/child-unregistered') @app.route('/child-unregistered')
def unregistered_test(): def unregistered_test():
raise ChildExceptionUnregistered() raise ChildExceptionUnregistered()
@app.route('/child-registered') @app.route('/child-registered')
def registered_test(): def registered_test():
raise ChildExceptionRegistered() raise ChildExceptionRegistered()
c = app.test_client()
c = app.test_client() assert c.get('/parent').data == b'parent'
assert c.get('/child-unregistered').data == b'parent'
assert c.get('/parent').data == b'parent' assert c.get('/child-registered').data == b'child-registered'
assert c.get('/child-unregistered').data == b'parent'
assert c.get('/child-registered').data == b'child-registered'
def test_error_handler_http_subclass(): def test_error_handler_http_subclass():
app = flask.Flask(__name__) app = flask.Flask(__name__)
class ForbiddenSubclassRegistered(Forbidden): class ForbiddenSubclassRegistered(Forbidden):
pass pass
class ForbiddenSubclassUnregistered(Forbidden): class ForbiddenSubclassUnregistered(Forbidden):
pass pass
@app.errorhandler(403) @app.errorhandler(403)
def code_exception_handler(e): def code_exception_handler(e):
assert isinstance(e, Forbidden) assert isinstance(e, Forbidden)
return 'forbidden' return 'forbidden'
@app.errorhandler(ForbiddenSubclassRegistered) @app.errorhandler(ForbiddenSubclassRegistered)
def subclass_exception_handler(e): def subclass_exception_handler(e):
assert isinstance(e, ForbiddenSubclassRegistered) assert isinstance(e, ForbiddenSubclassRegistered)
return 'forbidden-registered' return 'forbidden-registered'
@app.route('/forbidden') @app.route('/forbidden')
def forbidden_test(): def forbidden_test():
raise Forbidden() raise Forbidden()
@app.route('/forbidden-registered') @app.route('/forbidden-registered')
def registered_test(): def registered_test():
raise ForbiddenSubclassRegistered() raise ForbiddenSubclassRegistered()
@app.route('/forbidden-unregistered') @app.route('/forbidden-unregistered')
def unregistered_test(): def unregistered_test():
raise ForbiddenSubclassUnregistered() raise ForbiddenSubclassUnregistered()
c = app.test_client()
c = app.test_client() assert c.get('/forbidden').data == b'forbidden'
assert c.get('/forbidden-unregistered').data == b'forbidden'
assert c.get('/forbidden').data == b'forbidden' assert c.get('/forbidden-registered').data == b'forbidden-registered'
assert c.get('/forbidden-unregistered').data == b'forbidden'
assert c.get('/forbidden-registered').data == b'forbidden-registered'
def test_error_handler_blueprint(): def test_error_handler_blueprint():
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.errorhandler(500) @bp.errorhandler(500)
def bp_exception_handler(e): def bp_exception_handler(e):
return 'bp-error' return 'bp-error'
@bp.route('/error') @bp.route('/error')
def bp_test(): def bp_test():
raise InternalServerError() raise InternalServerError()
app = flask.Flask(__name__) app = flask.Flask(__name__)
@app.errorhandler(500) @app.errorhandler(500)
def app_exception_handler(e): def app_exception_handler(e):
return 'app-error' return 'app-error'
@app.route('/error') @app.route('/error')
def app_test(): def app_test():
raise InternalServerError() raise InternalServerError()
app.register_blueprint(bp, url_prefix='/bp') app.register_blueprint(bp, url_prefix='/bp')
c = app.test_client() c = app.test_client()
assert c.get('/error').data == b'app-error' assert c.get('/error').data == b'app-error'
assert c.get('/bp/error').data == b'bp-error' assert c.get('/bp/error').data == b'bp-error'