Browse Source

Merge pull request #2350 from davidism/errorhandler

show nice message when registering error handler for unknown code
pull/1452/merge
David Lord 7 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. 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
----------- -----------

55
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
@ -1230,11 +1218,18 @@ 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)
)
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 = self.error_handler_spec.setdefault(key, {}).setdefault(code, {})
handlers[exc_class] = f handlers[exc_class] = f
@ -1339,7 +1334,7 @@ class Flask(_PackageBoundObject):
@setupmethod @setupmethod
def before_request(self, f): def before_request(self, f):
"""Registers a function to run before each request. """Registers a function to run before each request.
For example, this can be used to open a database connection, or to load For example, this can be used to open a database connection, or to load
the logged in user from the session. the logged in user from the session.
@ -1467,12 +1462,12 @@ class Flask(_PackageBoundObject):
"""Register a URL value preprocessor function for all view """Register a URL value preprocessor function for all view
functions in the application. These functions will be called before the functions in the application. These functions will be called before the
:meth:`before_request` functions. :meth:`before_request` functions.
The function can modify the values captured from the matched url before 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 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 common language code value and place it in ``g`` rather than pass it to
every view. every view.
The function is passed the endpoint name and values dict. The return The function is passed the endpoint name and values dict. The return
value is ignored. value is ignored.
""" """
@ -1543,7 +1538,7 @@ class Flask(_PackageBoundObject):
exception is not called and it shows up as regular exception in the exception is not called and it shows up as regular exception in the
traceback. This is helpful for debugging implicitly raised HTTP traceback. This is helpful for debugging implicitly raised HTTP
exceptions. exceptions.
.. versionchanged:: 1.0 .. versionchanged:: 1.0
Bad request errors are not trapped by default in debug mode. Bad request errors are not trapped by default in debug mode.
@ -1783,10 +1778,10 @@ class Flask(_PackageBoundObject):
``str`` (``unicode`` in Python 2) ``str`` (``unicode`` in Python 2)
A response object is created with the string encoded to UTF-8 A response object is created with the string encoded to UTF-8
as the body. as the body.
``bytes`` (``str`` in Python 2) ``bytes`` (``str`` in Python 2)
A response object is created with the bytes as the body. A response object is created with the bytes as the body.
``tuple`` ``tuple``
Either ``(body, status, headers)``, ``(body, status)``, or Either ``(body, status, headers)``, ``(body, status)``, or
``(body, headers)``, where ``body`` is any of the other types ``(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, tuples. If ``body`` is a :attr:`response_class` instance,
``status`` overwrites the exiting value and ``headers`` are ``status`` overwrites the exiting value and ``headers`` are
extended. extended.
:attr:`response_class` :attr:`response_class`
The object is returned unchanged. The object is returned unchanged.
other :class:`~werkzeug.wrappers.Response` class other :class:`~werkzeug.wrappers.Response` class
The object is coerced to :attr:`response_class`. The object is coerced to :attr:`response_class`.
:func:`callable` :func:`callable`
The function is called as a WSGI application. The result is The function is called as a WSGI application. The result is
used to create a response object. used to create a response object.
@ -1938,7 +1933,7 @@ class Flask(_PackageBoundObject):
:attr:`url_value_preprocessors` registered with the app and the :attr:`url_value_preprocessors` registered with the app and the
current blueprint (if any). Then calls :attr:`before_request_funcs` current blueprint (if any). Then calls :attr:`before_request_funcs`
registered with the app and the blueprint. registered with the app and the blueprint.
If any :meth:`before_request` handler returns a non-None value, the 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 value is handled as if it was the return value from the view, and
further request handling is stopped. 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 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