show nice message when registering error handler for unknown code
clean up error handler docs closes #1837
This commit is contained in:
parent
9049755e3f
commit
859d9a9d5c
3 changed files with 79 additions and 54 deletions
|
|
@ -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.'
|
||||
|
||||
Errorhandlers are now prioritized by specificity of the exception classes
|
||||
they are registered for instead of the order they are registered in.
|
||||
app.register_error_handler(InsuffcientStorage, handle_507)
|
||||
|
||||
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
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.
|
||||
|
|
|
|||
|
|
@ -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…
Add table
Add a link
Reference in a new issue