diff --git a/flask/app.py b/flask/app.py index aa64f4c6e8..40090ab0d1 100644 --- a/flask/app.py +++ b/flask/app.py @@ -19,7 +19,7 @@ from functools import update_wrapper from werkzeug.datastructures import ImmutableDict -from werkzeug.routing import Map, Rule, RequestRedirect +from werkzeug.routing import Map, Rule, RequestRedirect, BuildError from werkzeug.exceptions import HTTPException, InternalServerError, \ MethodNotAllowed, BadRequest @@ -341,16 +341,14 @@ def __init__(self, import_name, static_path=None, static_url_path=None, #: decorator. self.error_handler_spec = {None: self._error_handlers} - #: If not `None`, this function is called when :meth:`url_for` raises - #: :exc:`~werkzeug.routing.BuildError`, with the call signature:: - #: - #: self.build_error_handler(error, endpoint, **values) - #: - #: Here, `error` is the instance of `BuildError`, and `endpoint` and - #: `**values` are the arguments passed into :meth:`url_for`. + #: A list of functions that are called when :meth:`url_for` raises a + #: :exc:`~werkzeug.routing.BuildError`. Each function registered here + #: is called with `error`, `endpoint` and `values`. If a function + #: returns `None` or raises a `BuildError` the next function is + #: tried. #: #: .. versionadded:: 0.9 - self.build_error_handler = None + self.url_build_error_handlers = [] #: A dictionary with lists of functions that should be called at the #: beginning of the request. The key of the dictionary is the name of @@ -1490,19 +1488,24 @@ def inject_url_defaults(self, endpoint, values): for func in funcs: func(endpoint, values) - def handle_build_error(self, error, endpoint, **values): + def handle_url_build_error(self, error, endpoint, values): """Handle :class:`~werkzeug.routing.BuildError` on :meth:`url_for`. - - Calls :attr:`build_error_handler` if it is not `None`. """ - if self.build_error_handler is None: - exc_type, exc_value, tb = sys.exc_info() - if exc_value is error: - # exception is current, raise in context of original traceback. - raise exc_type, exc_value, tb - else: - raise error - return self.build_error_handler(error, endpoint, **values) + exc_type, exc_value, tb = sys.exc_info() + for handler in self.url_build_error_handlers: + try: + rv = handler(error, endpoint, values) + if rv is not None: + return rv + except BuildError, error: + pass + + # At this point we want to reraise the exception. If the error is + # still the same one we can reraise it with the original traceback, + # otherwise we raise it from here. + if error is exc_value: + raise exc_type, exc_value, tb + raise error def preprocess_request(self): """Called before the actual request dispatching and will diff --git a/flask/helpers.py b/flask/helpers.py index 05f84ef72b..e633e1b929 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -11,7 +11,6 @@ from __future__ import with_statement -import imp import os import sys import pkgutil @@ -303,10 +302,12 @@ def external_url_handler(error, endpoint, **values): rv = url_adapter.build(endpoint, values, method=method, force_external=external) except BuildError, error: + # We need to inject the values again so that the app callback can + # deal with that sort of stuff. values['_external'] = external values['_anchor'] = anchor values['_method'] = method - return appctx.app.handle_build_error(error, endpoint, **values) + return appctx.app.handle_url_build_error(error, endpoint, values) rv = url_adapter.build(endpoint, values, method=method, force_external=external) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index cf7590cbfe..55b66f785e 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -712,13 +712,13 @@ def test_build_error_handler(self): try: raise RuntimeError('Test case where BuildError is not current.') except RuntimeError: - self.assertRaises(BuildError, app.handle_build_error, error, 'spam') + self.assertRaises(BuildError, app.handle_url_build_error, error, 'spam', {}) # Test a custom handler. - def handler(error, endpoint, **values): + def handler(error, endpoint, values): # Just a test. return '/test_handler/' - app.build_error_handler = handler + app.url_build_error_handlers.append(handler) with app.test_request_context(): self.assert_equal(flask.url_for('spam'), '/test_handler/')