Browse Source

Check error handlers for specific classes first

This allows adding error handlers like this:

    @app.errorhandler(werkzeug.exceptions.Forbidden)

And subclassing HTTPExceptions:

    class ForbiddenBecauseReason(Forbidden): pass

    @app.errorhandler(ForbiddenBecauseReason)
    def error1(): return "Forbidden because reason", 403
    @app.errorhandler(403)
    def error2(): return "Forbidden", 403

... the idea being, that a flask extension might want to raise an
exception, with the default behaviour of creating a HTTP error page,
but still allowing the user to add a view/handler specific to that
exception (e.g., "Forbidden because you are not in the right group").
pull/839/head
Daniel Richman 11 years ago
parent
commit
3d67736e09
  1. 6
      CHANGES
  2. 5
      flask/app.py
  3. 41
      flask/testsuite/basic.py

6
CHANGES

@ -14,6 +14,12 @@ Version 1.0
`False` it will only be modified if the session actually modifies. `False` it will only be modified if the session actually modifies.
Non permanent sessions are not affected by this and will always Non permanent sessions are not affected by this and will always
expire if the browser window closes. expire if the browser window closes.
- Error handlers that match specific classes are now checked first,
thereby allowing catching exceptions that are subclasses of HTTP
exceptions (in ``werkzeug.execptions``). This makes it possible
for an extension author to create exceptions that will by default
result in the HTTP error of their choosing, but may be caught with
a custom error handler if desired.
Version 0.10.2 Version 0.10.2
-------------- --------------

5
flask/app.py

@ -1365,8 +1365,6 @@ class Flask(_PackageBoundObject):
# wants the traceback preserved in handle_http_exception. Of course # wants the traceback preserved in handle_http_exception. Of course
# we cannot prevent users from trashing it themselves in a custom # we cannot prevent users from trashing it themselves in a custom
# trap_http_exception method so that's their fault then. # trap_http_exception method so that's their fault then.
if isinstance(e, HTTPException) and not self.trap_http_exception(e):
return self.handle_http_exception(e)
blueprint_handlers = () blueprint_handlers = ()
handlers = self.error_handler_spec.get(request.blueprint) handlers = self.error_handler_spec.get(request.blueprint)
@ -1377,6 +1375,9 @@ class Flask(_PackageBoundObject):
if isinstance(e, typecheck): if isinstance(e, typecheck):
return handler(e) return handler(e)
if isinstance(e, HTTPException) and not self.trap_http_exception(e):
return self.handle_http_exception(e)
reraise(exc_type, exc_value, tb) reraise(exc_type, exc_value, tb)
def handle_exception(self, e): def handle_exception(self, e):

41
flask/testsuite/basic.py

@ -18,7 +18,7 @@ from datetime import datetime
from threading import Thread from threading import Thread
from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning
from flask._compat import text_type from flask._compat import text_type
from werkzeug.exceptions import BadRequest, NotFound from werkzeug.exceptions import BadRequest, NotFound, Forbidden
from werkzeug.http import parse_date from werkzeug.http import parse_date
from werkzeug.routing import BuildError from werkzeug.routing import BuildError
@ -626,12 +626,18 @@ class BasicFunctionalityTestCase(FlaskTestCase):
@app.errorhandler(500) @app.errorhandler(500)
def internal_server_error(e): def internal_server_error(e):
return 'internal server error', 500 return 'internal server error', 500
@app.errorhandler(Forbidden)
def forbidden(e):
return 'forbidden', 403
@app.route('/') @app.route('/')
def index(): def index():
flask.abort(404) flask.abort(404)
@app.route('/error') @app.route('/error')
def error(): def error():
1 // 0 1 // 0
@app.route('/forbidden')
def error2():
flask.abort(403)
c = app.test_client() c = app.test_client()
rv = c.get('/') rv = c.get('/')
self.assert_equal(rv.status_code, 404) self.assert_equal(rv.status_code, 404)
@ -639,6 +645,9 @@ class BasicFunctionalityTestCase(FlaskTestCase):
rv = c.get('/error') rv = c.get('/error')
self.assert_equal(rv.status_code, 500) self.assert_equal(rv.status_code, 500)
self.assert_equal(b'internal server error', rv.data) self.assert_equal(b'internal server error', rv.data)
rv = c.get('/forbidden')
self.assert_equal(rv.status_code, 403)
self.assert_equal(b'forbidden', rv.data)
def test_before_request_and_routing_errors(self): def test_before_request_and_routing_errors(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
@ -668,6 +677,36 @@ class BasicFunctionalityTestCase(FlaskTestCase):
c = app.test_client() c = app.test_client()
self.assert_equal(c.get('/').data, b'42') self.assert_equal(c.get('/').data, b'42')
def test_http_error_subclass_handling(self):
class ForbiddenSubclass(Forbidden):
pass
app = flask.Flask(__name__)
@app.errorhandler(ForbiddenSubclass)
def handle_forbidden_subclass(e):
self.assert_true(isinstance(e, ForbiddenSubclass))
return 'banana'
@app.errorhandler(403)
def handle_forbidden_subclass(e):
self.assert_false(isinstance(e, ForbiddenSubclass))
self.assert_true(isinstance(e, Forbidden))
return 'apple'
@app.route('/1')
def index1():
raise ForbiddenSubclass()
@app.route('/2')
def index2():
flask.abort(403)
@app.route('/3')
def index3():
raise Forbidden()
c = app.test_client()
self.assert_equal(c.get('/1').data, b'banana')
self.assert_equal(c.get('/2').data, b'apple')
self.assert_equal(c.get('/3').data, b'apple')
def test_trapping_of_bad_request_key_errors(self): def test_trapping_of_bad_request_key_errors(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.testing = True app.testing = True

Loading…
Cancel
Save