From 637eae548935ad1434459c1afac286c8686abeed Mon Sep 17 00:00:00 2001 From: cerickson Date: Mon, 22 May 2017 15:57:31 -0700 Subject: [PATCH] Added support for generic HTTPException handlers on app and blueprints Error handlers are now returned in order of blueprint:code, app:code, blueprint:HTTPException, app:HTTPException, None Corresponding tests also added. Ref issue #941, pr #1383, #2082, #2144 --- flask/app.py | 33 ++++++------- tests/test_user_error_handler.py | 80 ++++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 44 deletions(-) diff --git a/flask/app.py b/flask/app.py index 1251d2fb..f4c73978 100644 --- a/flask/app.py +++ b/flask/app.py @@ -133,6 +133,8 @@ class Flask(_PackageBoundObject): :param static_folder: the folder with static files that should be served at `static_url_path`. Defaults to the ``'static'`` folder in the root path of the application. + folder in the root path of the application. Defaults + to None. :param host_matching: sets the app's ``url_map.host_matching`` to the given given value. Defaults to False. :param static_host: the host to use when adding the static route. Defaults @@ -1460,15 +1462,17 @@ class Flask(_PackageBoundObject): return f def _find_error_handler(self, e): - """Finds a registered error handler for the request’s blueprint. - Otherwise falls back to the app, returns None if not a suitable - handler is found. + """Find a registered error handler for a request in this order: + blueprint handler for a specific code, app handler for a specific code, + blueprint generic HTTPException handler, app generic HTTPException handler, + and returns None if a suitable handler is not found. """ exc_class, code = self._get_exc_class_and_code(type(e)) def find_handler(handler_map): if not handler_map: return + for cls in exc_class.__mro__: handler = handler_map.get(cls) if handler is not None: @@ -1476,24 +1480,13 @@ class Flask(_PackageBoundObject): handler_map[exc_class] = handler return handler - # try blueprint handlers - handler = find_handler(self.error_handler_spec - .get(request.blueprint, {}) - .get(code)) - if handler is not None: - return handler - - # fall back to app handlers - handler = find_handler(self.error_handler_spec[None].get(code)) - if handler is not None: - return handler - - try: - handler = find_handler(self.error_handler_spec[None][None]) - except KeyError: - handler = None + # check for any in blueprint or app + for name, c in ((request.blueprint, code), (None, code), + (request.blueprint, None), (None, None)): + handler = find_handler(self.error_handler_spec.get(name, {}).get(c)) - return handler + if handler: + return handler def handle_http_exception(self, e): """Handles an HTTP exception. By default this will invoke the diff --git a/tests/test_user_error_handler.py b/tests/test_user_error_handler.py index b6d4ca34..24ffe73f 100644 --- a/tests/test_user_error_handler.py +++ b/tests/test_user_error_handler.py @@ -1,5 +1,10 @@ # -*- coding: utf-8 -*- -from werkzeug.exceptions import Forbidden, InternalServerError, HTTPException, NotFound +from werkzeug.exceptions import ( + Forbidden, + InternalServerError, + HTTPException, + NotFound + ) import flask @@ -32,29 +37,6 @@ def test_error_handler_no_match(): assert c.get('/keyerror').data == b'KeyError' -def test_default_error_handler(): - app = flask.Flask(__name__) - - @app.errorhandler(HTTPException) - def catchall_errorhandler(e): - assert isinstance(e, HTTPException) - assert isinstance(e, NotFound) - return 'default' - - @app.errorhandler(Forbidden) - def catchall_errorhandler(e): - assert isinstance(e, Forbidden) - return 'forbidden' - - @app.route('/forbidden') - def forbidden(): - raise Forbidden() - - c = app.test_client() - assert c.get('/undefined').data == b'default' - assert c.get('/forbidden').data == b'forbidden' - - def test_error_handler_subclass(): app = flask.Flask(__name__) @@ -161,3 +143,53 @@ def test_error_handler_blueprint(): assert c.get('/error').data == b'app-error' assert c.get('/bp/error').data == b'bp-error' + + +def test_default_error_handler(): + bp = flask.Blueprint('bp', __name__) + + @bp.errorhandler(HTTPException) + def bp_exception_handler(e): + assert isinstance(e, HTTPException) + assert isinstance(e, NotFound) + return 'bp-default' + + @bp.errorhandler(Forbidden) + def bp_exception_handler(e): + assert isinstance(e, Forbidden) + return 'bp-forbidden' + + @bp.route('/undefined') + def bp_registered_test(): + raise NotFound() + + @bp.route('/forbidden') + def bp_forbidden_test(): + raise Forbidden() + + app = flask.Flask(__name__) + + @app.errorhandler(HTTPException) + def catchall_errorhandler(e): + assert isinstance(e, HTTPException) + assert isinstance(e, NotFound) + return 'default' + + @app.errorhandler(Forbidden) + def catchall_errorhandler(e): + assert isinstance(e, Forbidden) + return 'forbidden' + + @app.route('/forbidden') + def forbidden(): + raise Forbidden() + + app.register_blueprint(bp, url_prefix='/bp') + + c = app.test_client() + assert c.get('/bp/undefined').data == b'bp-default' + assert c.get('/bp/forbidden').data == b'bp-forbidden' + assert c.get('/undefined').data == b'default' + assert c.get('/forbidden').data == b'forbidden' + +