Browse Source

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
pull/2314/head
cerickson 8 years ago
parent
commit
4f815015b8
  1. 33
      flask/app.py
  2. 80
      tests/test_user_error_handler.py

33
flask/app.py

@ -133,6 +133,8 @@ class Flask(_PackageBoundObject):
:param static_folder: the folder with static files that should be served :param static_folder: the folder with static files that should be served
at `static_url_path`. Defaults to the ``'static'`` at `static_url_path`. Defaults to the ``'static'``
folder in the root path of the application. 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 :param host_matching: sets the app's ``url_map.host_matching`` to the given
given value. Defaults to False. given value. Defaults to False.
:param static_host: the host to use when adding the static route. Defaults :param static_host: the host to use when adding the static route. Defaults
@ -1460,15 +1462,17 @@ class Flask(_PackageBoundObject):
return f return f
def _find_error_handler(self, e): def _find_error_handler(self, e):
"""Finds a registered error handler for the request’s blueprint. """Find a registered error handler for a request in this order:
Otherwise falls back to the app, returns None if not a suitable blueprint handler for a specific code, app handler for a specific code,
handler is found. 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)) exc_class, code = self._get_exc_class_and_code(type(e))
def find_handler(handler_map): def find_handler(handler_map):
if not handler_map: if not handler_map:
return return
for cls in exc_class.__mro__: for cls in exc_class.__mro__:
handler = handler_map.get(cls) handler = handler_map.get(cls)
if handler is not None: if handler is not None:
@ -1476,24 +1480,13 @@ class Flask(_PackageBoundObject):
handler_map[exc_class] = handler handler_map[exc_class] = handler
return handler return handler
# try blueprint handlers # check for any in blueprint or app
handler = find_handler(self.error_handler_spec for name, c in ((request.blueprint, code), (None, code),
.get(request.blueprint, {}) (request.blueprint, None), (None, None)):
.get(code)) handler = find_handler(self.error_handler_spec.get(name, {}).get(c))
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
return handler if handler:
return handler
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

80
tests/test_user_error_handler.py

@ -1,5 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from werkzeug.exceptions import Forbidden, InternalServerError, HTTPException, NotFound from werkzeug.exceptions import (
Forbidden,
InternalServerError,
HTTPException,
NotFound
)
import flask import flask
@ -32,29 +37,6 @@ def test_error_handler_no_match():
assert c.get('/keyerror').data == b'KeyError' 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(): def test_error_handler_subclass():
app = flask.Flask(__name__) app = flask.Flask(__name__)
@ -161,3 +143,53 @@ def test_error_handler_blueprint():
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'
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'

Loading…
Cancel
Save