Browse Source

Merge pull request #2350 from davidism/errorhandler

show nice message when registering error handler for unknown code
pull/1452/merge
David Lord 8 years ago committed by GitHub
parent
commit
c7f433c49b
  1. 71
      docs/errorhandling.rst
  2. 31
      flask/app.py
  3. 7
      tests/test_basic.py

71
docs/errorhandling.rst

@ -76,49 +76,72 @@ Error handlers
You might want to show custom error pages to the user when an error occurs. You might want to show custom error pages to the user when an error occurs.
This can be done by registering error handlers. This can be done by registering error handlers.
Error handlers are normal :ref:`views` but instead of being registered for An error handler is a normal view function that return a response, but instead
routes, they are registered for exceptions that are raised while trying to of being registered for a route, it is registered for an exception or HTTP
do something else. status code that would is raised while trying to handle a request.
Registering Registering
``````````` ```````````
Register error handlers using :meth:`~flask.Flask.errorhandler` or Register handlers by decorating a function with
:meth:`~flask.Flask.register_error_handler`:: :meth:`~flask.Flask.errorhandler`. Or use
:meth:`~flask.Flask.register_error_handler` to register the function later.
Remember to set the error code when returning the response. ::
@app.errorhandler(werkzeug.exceptions.BadRequest) @app.errorhandler(werkzeug.exceptions.BadRequest)
def handle_bad_request(e): def handle_bad_request(e):
return 'bad request!' return 'bad request!', 400
app.register_error_handler(400, lambda e: 'bad request!') # or, without the decorator
app.register_error_handler(400, handle_bad_request)
Those two ways are equivalent, but the first one is more clear and leaves
you with a function to call on your whim (and in tests). Note that
:exc:`werkzeug.exceptions.HTTPException` subclasses like :exc:`werkzeug.exceptions.HTTPException` subclasses like
:exc:`~werkzeug.exceptions.BadRequest` from the example and their HTTP codes :exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable
are interchangeable when handed to the registration methods or decorator when registering handlers. (``BadRequest.code == 400``)
(``BadRequest.code == 400``).
You are however not limited to :exc:`~werkzeug.exceptions.HTTPException` Non-standard HTTP codes cannot be registered by code because they are not known
or HTTP status codes but can register a handler for every exception class you by Werkzeug. Instead, define a subclass of
like. :class:`~werkzeug.exceptions.HTTPException` with the appropriate code and
register and raise that exception class. ::
.. versionchanged:: 0.11 class InsufficientStorage(werkzeug.exceptions.HTTPException):
code = 507
description = 'Not enough storage space.'
app.register_error_handler(InsuffcientStorage, handle_507)
Errorhandlers are now prioritized by specificity of the exception classes raise InsufficientStorage()
they are registered for instead of the order they are registered in.
Handlers can be registered for any exception class, not just
:exc:`~werkzeug.exceptions.HTTPException` subclasses or HTTP status
codes. Handlers can be registered for a specific class, or for all subclasses
of a parent class.
Handling Handling
```````` ````````
Once an exception instance is raised, its class hierarchy is traversed, When an exception is caught by Flask while handling a request, it is first
and searched for in the exception classes for which handlers are registered. looked up by code. If no handler is registered for the code, it is looked up
The most specific handler is selected. by its class hierarchy; the most specific handler is chosen. If no handler is
registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a
generic message about their code, while other exceptions are converted to a
generic 500 Internal Server Error.
E.g. if an instance of :exc:`ConnectionRefusedError` is raised, and a handler For example, if an instance of :exc:`ConnectionRefusedError` is raised, and a handler
is registered for :exc:`ConnectionError` and :exc:`ConnectionRefusedError`, is registered for :exc:`ConnectionError` and :exc:`ConnectionRefusedError`,
the more specific :exc:`ConnectionRefusedError` handler is called on the the more specific :exc:`ConnectionRefusedError` handler is called with the
exception instance, and its response is shown to the user. exception instance to generate the response.
Handlers registered on the blueprint take precedence over those registered
globally on the application, assuming a blueprint is handling the request that
raises the exception. However, the blueprint cannot handle 404 routing errors
because the 404 occurs at the routing level before the blueprint can be
determined.
.. versionchanged:: 0.11
Handlers are prioritized by specificity of the exception classes they are
registered for instead of the order they are registered in.
Error Mails Error Mails
----------- -----------

31
flask/app.py

@ -1166,7 +1166,9 @@ class Flask(_PackageBoundObject):
@setupmethod @setupmethod
def errorhandler(self, code_or_exception): def errorhandler(self, code_or_exception):
"""A decorator that is used to register a function given an """Register a function to handle errors by code or exception class.
A decorator that is used to register a function given an
error code. Example:: error code. Example::
@app.errorhandler(404) @app.errorhandler(404)
@ -1179,21 +1181,6 @@ class Flask(_PackageBoundObject):
def special_exception_handler(error): def special_exception_handler(error):
return 'Database connection failed', 500 return 'Database connection failed', 500
You can also register a function as error handler without using
the :meth:`errorhandler` decorator. The following example is
equivalent to the one above::
def page_not_found(error):
return 'This page does not exist', 404
app.error_handler_spec[None][404] = page_not_found
Setting error handlers via assignments to :attr:`error_handler_spec`
however is discouraged as it requires fiddling with nested dictionaries
and the special case for arbitrary exception types.
The first ``None`` refers to the active blueprint. If the error
handler should be application wide ``None`` shall be used.
.. versionadded:: 0.7 .. versionadded:: 0.7
Use :meth:`register_error_handler` instead of modifying Use :meth:`register_error_handler` instead of modifying
:attr:`error_handler_spec` directly, for application wide error :attr:`error_handler_spec` directly, for application wide error
@ -1212,6 +1199,7 @@ class Flask(_PackageBoundObject):
return f return f
return decorator return decorator
@setupmethod
def register_error_handler(self, code_or_exception, f): def register_error_handler(self, code_or_exception, f):
"""Alternative error attach function to the :meth:`errorhandler` """Alternative error attach function to the :meth:`errorhandler`
decorator that is more straightforward to use for non decorator decorator that is more straightforward to use for non decorator
@ -1231,10 +1219,17 @@ class Flask(_PackageBoundObject):
if isinstance(code_or_exception, HTTPException): # old broken behavior if isinstance(code_or_exception, HTTPException): # old broken behavior
raise ValueError( raise ValueError(
'Tried to register a handler for an exception instance {0!r}.' 'Tried to register a handler for an exception instance {0!r}.'
'Handlers can only be registered for exception classes or HTTP error codes.' ' Handlers can only be registered for exception classes or'
.format(code_or_exception)) ' HTTP error codes.'.format(code_or_exception)
)
try:
exc_class, code = self._get_exc_class_and_code(code_or_exception) exc_class, code = self._get_exc_class_and_code(code_or_exception)
except KeyError:
raise KeyError(
"'{0}' is not a recognized HTTP error code. Use a subclass of"
" HTTPException with that code instead.".format(code_or_exception)
)
handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {}) handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {})
handlers[exc_class] = f handlers[exc_class] = f

7
tests/test_basic.py

@ -870,6 +870,13 @@ def test_error_handling(app, client):
assert b'forbidden' == rv.data assert b'forbidden' == rv.data
def test_error_handler_unknown_code(app):
with pytest.raises(KeyError) as exc_info:
app.register_error_handler(999, lambda e: ('999', 999))
assert 'Use a subclass' in exc_info.value.args[0]
def test_error_handling_processing(app, client): def test_error_handling_processing(app, client):
app.config['LOGGER_HANDLER_POLICY'] = 'never' app.config['LOGGER_HANDLER_POLICY'] = 'never'
app.testing = False app.testing = False

Loading…
Cancel
Save