Skip to content

Commit

Permalink
Added support for generic HTTPException handlers on app and blueprints
Browse files Browse the repository at this point in the history
Error handlers are now returned in order of blueprint:code, app:code,
blueprint:HTTPException, app:HTTPException, None

Corresponding tests also added.

Ref issue pallets#941, pr pallets#1383, pallets#2082, pallets#2144
  • Loading branch information
cerickson committed May 23, 2017
1 parent 668061a commit 4f81501
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 44 deletions.
33 changes: 13 additions & 20 deletions flask/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1460,40 +1462,31 @@ def url_defaults(self, f):
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:
# cache for next time exc_class is raised
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
Expand Down
80 changes: 56 additions & 24 deletions tests/test_user_error_handler.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -32,29 +37,6 @@ def key_error():
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__)

Expand Down Expand Up @@ -161,3 +143,53 @@ def app_test():

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'


0 comments on commit 4f81501

Please sign in to comment.