diff --git a/flask/app.py b/flask/app.py index 4c00c36b..c0a9dac3 100644 --- a/flask/app.py +++ b/flask/app.py @@ -329,6 +329,17 @@ class Flask(_PackageBoundObject): #: 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`. + #: + #: .. versionadded:: 0.9 + self.build_error_handler = None + #: 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 #: the blueprint this function is active for, `None` for all requests. @@ -1473,6 +1484,15 @@ class Flask(_PackageBoundObject): for func in funcs: func(endpoint, values) + def handle_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: + raise error + return self.build_error_handler(error, endpoint, **values) + def preprocess_request(self): """Called before the actual request dispatching and will call every as :meth:`before_request` decorated function. diff --git a/flask/helpers.py b/flask/helpers.py index 238d7df7..21b010b4 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -20,6 +20,7 @@ import mimetypes from time import time from zlib import adler32 from threading import RLock +from werkzeug.routing import BuildError from werkzeug.urls import url_quote # try to load the best simplejson implementation available. If JSON @@ -214,6 +215,10 @@ def url_for(endpoint, **values): .. versionadded:: 0.9 The `_anchor` and `_method` parameters were added. + .. versionadded:: 0.9 + Calls :meth:`Flask.handle_build_error` on + :exc:`~werkzeug.routing.BuildError`. + :param endpoint: the endpoint of the URL (name of the function) :param values: the variable arguments of the URL rule :param _external: if set to `True`, an absolute URL is generated. @@ -260,6 +265,15 @@ def url_for(endpoint, **values): anchor = values.pop('_anchor', None) method = values.pop('_method', None) appctx.app.inject_url_defaults(endpoint, values) + try: + rv = url_adapter.build(endpoint, values, method=method, + force_external=external) + except BuildError, error: + values['_external'] = external + values['_anchor'] = anchor + values['_method'] = method + return appctx.app.handle_build_error(error, endpoint, **values) + rv = url_adapter.build(endpoint, values, method=method, force_external=external) if anchor is not None: diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 0a4b1d9c..d138c45e 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -19,6 +19,7 @@ from threading import Thread from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning from werkzeug.exceptions import BadRequest, NotFound from werkzeug.http import parse_date +from werkzeug.routing import BuildError class BasicFunctionalityTestCase(FlaskTestCase): @@ -695,6 +696,17 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(flask.url_for('hello', name='test x', _external=True), 'http://localhost/hello/test%20x') + def test_build_error_handler(self): + app = flask.Flask(__name__) + with app.test_request_context(): + self.assertRaises(BuildError, flask.url_for, 'spam') + def handler(error, endpoint, **values): + # Just a test. + return '/test_handler/' + app.build_error_handler = handler + with app.test_request_context(): + self.assert_equal(flask.url_for('spam'), '/test_handler/') + def test_custom_converters(self): from werkzeug.routing import BaseConverter class ListConverter(BaseConverter):