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 22, 2017
1 parent b11f735 commit fe041b7
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 10 deletions.
20 changes: 11 additions & 9 deletions flask/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1462,28 +1462,30 @@ 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.
"""Finds a registered error handlers 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
# 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

# fall back to app handlers
return find_handler(self.error_handler_spec[None].get(code))
Expand Down
56 changes: 55 additions & 1 deletion 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
from werkzeug.exceptions import (
Forbidden,
InternalServerError,
HTTPException,
NotFound
)
import flask


Expand Down Expand Up @@ -138,3 +143,52 @@ 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 fe041b7

Please sign in to comment.