From 5c12acefbb90d4328f896633b636c8e05d87a0dd Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 5 Jun 2017 06:14:13 -0700 Subject: [PATCH 1/2] failing test --- tests/test_basic.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/test_basic.py b/tests/test_basic.py index 108c1409..d24678c2 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -980,6 +980,39 @@ def test_http_error_subclass_handling(app, client): assert client.get('/3').data == b'apple' +def test_errorhandler_precedence(app, client): + class E1(Exception): + pass + + class E2(Exception): + pass + + class E3(E1, E2): + pass + + @app.errorhandler(E2) + def handle_e2(e): + return 'E2' + + @app.errorhandler(Exception) + def handle_exception(e): + return 'Exception' + + @app.route('/E1') + def raise_e1(): + raise E1 + + @app.route('/E3') + def raise_e3(): + raise E3 + + rv = client.get('/E1') + assert rv.data == b'Exception' + + rv = client.get('/E3') + assert rv.data == b'E2' + + def test_trapping_of_bad_request_key_errors(app, client): @app.route('/fail') def fail(): From b5f4c5215022a01823675038bc246dd1d771a543 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 5 Jun 2017 06:24:08 -0700 Subject: [PATCH 2/2] don't cache error handlers for exception mro closes #2267, closes #1433 --- CHANGES | 4 ++++ flask/app.py | 26 +++++++++++--------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index 2c4ecdde..59e8c4c1 100644 --- a/CHANGES +++ b/CHANGES @@ -76,6 +76,9 @@ Major release, unreleased - Extract JSON handling to a mixin applied to both the request and response classes used by Flask. This adds the ``is_json`` and ``get_json`` methods to the response to make testing JSON response much easier. (`#2358`_) +- Removed error handler caching because it caused unexpected results for some + exception inheritance hierarchies. Register handlers explicitly for each + exception if you don't want to traverse the MRO. (`#2362`_) .. _#1489: https://github.com/pallets/flask/pull/1489 .. _#1621: https://github.com/pallets/flask/pull/1621 @@ -98,6 +101,7 @@ Major release, unreleased .. _#2352: https://github.com/pallets/flask/pull/2352 .. _#2354: https://github.com/pallets/flask/pull/2354 .. _#2358: https://github.com/pallets/flask/pull/2358 +.. _#2362: https://github.com/pallets/flask/pull/2362 Version 0.12.2 -------------- diff --git a/flask/app.py b/flask/app.py index 342dde86..7034b755 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1484,32 +1484,28 @@ class Flask(_PackageBoundObject): return f def _find_error_handler(self, e): - """Find a registered error handler for a request in this order: + """Return a registered error handler for an exception 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. + blueprint handler for an exception class, app handler for an exception + class, or ``None`` if a suitable handler is not found. """ exc_class, code = self._get_exc_class_and_code(type(e)) - def find_handler(handler_map): + for name, c in ( + (request.blueprint, code), (None, code), + (request.blueprint, None), (None, None) + ): + handler_map = self.error_handler_spec.setdefault(name, {}).get(c) + if not handler_map: - return + continue for cls in exc_class.__mro__: handler = handler_map.get(cls) + if handler is not None: - # cache for next time exc_class is raised - handler_map[exc_class] = handler return handler - # 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)) - - if handler: - return handler - def handle_http_exception(self, e): """Handles an HTTP exception. By default this will invoke the registered error handlers and fall back to returning the