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. 55
      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.
This can be done by registering error handlers.
Error handlers are normal :ref:`views` but instead of being registered for
routes, they are registered for exceptions that are raised while trying to
do something else.
An error handler is a normal view function that return a response, but instead
of being registered for a route, it is registered for an exception or HTTP
status code that would is raised while trying to handle a request.
Registering
```````````
Register error handlers using :meth:`~flask.Flask.errorhandler` or
:meth:`~flask.Flask.register_error_handler`::
Register handlers by decorating a function with
: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)
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.BadRequest` from the example and their HTTP codes
are interchangeable when handed to the registration methods or decorator
(``BadRequest.code == 400``).
:exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable
when registering handlers. (``BadRequest.code == 400``)
You are however not limited to :exc:`~werkzeug.exceptions.HTTPException`
or HTTP status codes but can register a handler for every exception class you
like.
Non-standard HTTP codes cannot be registered by code because they are not known
by Werkzeug. Instead, define a subclass of
: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
they are registered for instead of the order they are registered in.
raise InsufficientStorage()
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
````````
Once an exception instance is raised, its class hierarchy is traversed,
and searched for in the exception classes for which handlers are registered.
The most specific handler is selected.
When an exception is caught by Flask while handling a request, it is first
looked up by code. If no handler is registered for the code, it is looked up
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`,
the more specific :exc:`ConnectionRefusedError` handler is called on the
exception instance, and its response is shown to the user.
the more specific :exc:`ConnectionRefusedError` handler is called with the
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
-----------

55
flask/app.py

@ -1166,7 +1166,9 @@ class Flask(_PackageBoundObject):
@setupmethod
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::
@app.errorhandler(404)
@ -1179,21 +1181,6 @@ class Flask(_PackageBoundObject):
def special_exception_handler(error):
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
Use :meth:`register_error_handler` instead of modifying
:attr:`error_handler_spec` directly, for application wide error
@ -1212,6 +1199,7 @@ class Flask(_PackageBoundObject):
return f
return decorator
@setupmethod
def register_error_handler(self, code_or_exception, f):
"""Alternative error attach function to the :meth:`errorhandler`
decorator that is more straightforward to use for non decorator
@ -1230,11 +1218,18 @@ class Flask(_PackageBoundObject):
"""
if isinstance(code_or_exception, HTTPException): # old broken behavior
raise ValueError(
'Tried to register a handler for an exception instance {0!r}. '
'Handlers can only be registered for exception classes or HTTP error codes.'
.format(code_or_exception))
'Tried to register a handler for an exception instance {0!r}.'
' Handlers can only be registered for exception classes or'
' HTTP error codes.'.format(code_or_exception)
)
exc_class, code = self._get_exc_class_and_code(code_or_exception)
try:
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[exc_class] = f
@ -1339,7 +1334,7 @@ class Flask(_PackageBoundObject):
@setupmethod
def before_request(self, f):
"""Registers a function to run before each request.
For example, this can be used to open a database connection, or to load
the logged in user from the session.
@ -1467,12 +1462,12 @@ class Flask(_PackageBoundObject):
"""Register a URL value preprocessor function for all view
functions in the application. These functions will be called before the
:meth:`before_request` functions.
The function can modify the values captured from the matched url before
they are passed to the view. For example, this can be used to pop a
common language code value and place it in ``g`` rather than pass it to
every view.
The function is passed the endpoint name and values dict. The return
value is ignored.
"""
@ -1543,7 +1538,7 @@ class Flask(_PackageBoundObject):
exception is not called and it shows up as regular exception in the
traceback. This is helpful for debugging implicitly raised HTTP
exceptions.
.. versionchanged:: 1.0
Bad request errors are not trapped by default in debug mode.
@ -1783,10 +1778,10 @@ class Flask(_PackageBoundObject):
``str`` (``unicode`` in Python 2)
A response object is created with the string encoded to UTF-8
as the body.
``bytes`` (``str`` in Python 2)
A response object is created with the bytes as the body.
``tuple``
Either ``(body, status, headers)``, ``(body, status)``, or
``(body, headers)``, where ``body`` is any of the other types
@ -1795,13 +1790,13 @@ class Flask(_PackageBoundObject):
tuples. If ``body`` is a :attr:`response_class` instance,
``status`` overwrites the exiting value and ``headers`` are
extended.
:attr:`response_class`
The object is returned unchanged.
other :class:`~werkzeug.wrappers.Response` class
The object is coerced to :attr:`response_class`.
:func:`callable`
The function is called as a WSGI application. The result is
used to create a response object.
@ -1938,7 +1933,7 @@ class Flask(_PackageBoundObject):
:attr:`url_value_preprocessors` registered with the app and the
current blueprint (if any). Then calls :attr:`before_request_funcs`
registered with the app and the blueprint.
If any :meth:`before_request` handler returns a non-None value, the
value is handled as if it was the return value from the view, and
further request handling is stopped.

7
tests/test_basic.py

@ -870,6 +870,13 @@ def test_error_handling(app, client):
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):
app.config['LOGGER_HANDLER_POLICY'] = 'never'
app.testing = False

Loading…
Cancel
Save