mirror of https://github.com/pallets/flask.git
consolidate error handling docs
Remove apierrors.rst and errorpages.rst from patterns and integrate the content into errorhandling.rst, along with other changes and extra content.
This commit is contained in:
parent
2fa8eb3cfe
commit
59b0b85835
|
@ -273,4 +273,4 @@ at the application level using the ``request`` proxy object::
|
||||||
else:
|
else:
|
||||||
return ex
|
return ex
|
||||||
|
|
||||||
More information on error handling see :doc:`/patterns/errorpages`.
|
See :doc:`/errorhandling`.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
Application Errors
|
Handling Application Errors
|
||||||
==================
|
===========================
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
|
|
||||||
|
@ -20,115 +20,85 @@ errors:
|
||||||
|
|
||||||
And that's just a small sample of issues you could be facing. So how do we
|
And that's just a small sample of issues you could be facing. So how do we
|
||||||
deal with that sort of problem? By default if your application runs in
|
deal with that sort of problem? By default if your application runs in
|
||||||
production mode, Flask will display a very simple page for you and log the
|
production mode, and an exception is raised Flask will display a very simple
|
||||||
exception to the :attr:`~flask.Flask.logger`.
|
page for you and log the exception to the :attr:`~flask.Flask.logger`.
|
||||||
|
|
||||||
But there is more you can do, and we will cover some better setups to deal
|
But there is more you can do, and we will cover some better setups to deal
|
||||||
with errors.
|
with errors including custom exceptions and 3rd party tools.
|
||||||
|
|
||||||
Error Logging Tools
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Sending error mails, even if just for critical ones, can become
|
|
||||||
overwhelming if enough users are hitting the error and log files are
|
|
||||||
typically never looked at. This is why we recommend using `Sentry
|
|
||||||
<https://sentry.io/>`_ for dealing with application errors. It's
|
|
||||||
available as an Open Source project `on GitHub
|
|
||||||
<https://github.com/getsentry/sentry>`_ and is also available as a `hosted version
|
|
||||||
<https://sentry.io/signup/>`_ which you can try for free. Sentry
|
|
||||||
aggregates duplicate errors, captures the full stack trace and local
|
|
||||||
variables for debugging, and sends you mails based on new errors or
|
|
||||||
frequency thresholds.
|
|
||||||
|
|
||||||
To use Sentry you need to install the `sentry-sdk` client with extra `flask` dependencies::
|
|
||||||
|
|
||||||
$ pip install sentry-sdk[flask]
|
|
||||||
|
|
||||||
And then add this to your Flask app::
|
|
||||||
|
|
||||||
import sentry_sdk
|
|
||||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
|
||||||
|
|
||||||
sentry_sdk.init('YOUR_DSN_HERE',integrations=[FlaskIntegration()])
|
|
||||||
|
|
||||||
The `YOUR_DSN_HERE` value needs to be replaced with the DSN value you get
|
|
||||||
from your Sentry installation.
|
|
||||||
|
|
||||||
After installation, failures leading to an Internal Server Error
|
|
||||||
are automatically reported to Sentry and from there you can
|
|
||||||
receive error notifications.
|
|
||||||
|
|
||||||
Follow-up reads:
|
|
||||||
|
|
||||||
* Sentry also supports catching errors from your worker queue (RQ, Celery) in a
|
|
||||||
similar fashion. See the `Python SDK docs
|
|
||||||
<https://docs.sentry.io/platforms/python/>`_ for more information.
|
|
||||||
* `Getting started with Sentry <https://docs.sentry.io/quickstart/?platform=python>`_
|
|
||||||
* `Flask-specific documentation <https://docs.sentry.io/platforms/python/flask/>`_.
|
|
||||||
|
|
||||||
|
|
||||||
Error handlers
|
.. _common-error-codes:
|
||||||
--------------
|
|
||||||
|
|
||||||
You might want to show custom error pages to the user when an error occurs.
|
Common Error Codes
|
||||||
This can be done by registering error handlers.
|
``````````````````
|
||||||
|
|
||||||
An error handler is a normal view function that returns a response, but instead
|
The following error codes are some that are often displayed to the user,
|
||||||
of being registered for a route, it is registered for an exception or HTTP
|
even if the application behaves correctly:
|
||||||
status code that would be raised while trying to handle a request.
|
|
||||||
|
|
||||||
Registering
|
*400 Bad Request*
|
||||||
```````````
|
When the server will not process the request due to something that
|
||||||
|
the server perceives to be a client error. Such as malformed request
|
||||||
|
syntax, missing query parameters, etc.
|
||||||
|
|
||||||
Register handlers by decorating a function with
|
*403 Forbidden*
|
||||||
:meth:`~flask.Flask.errorhandler`. Or use
|
If you have some kind of access control on your website, you will have
|
||||||
:meth:`~flask.Flask.register_error_handler` to register the function later.
|
to send a 403 code for disallowed resources. So make sure the user
|
||||||
Remember to set the error code when returning the response. ::
|
is not lost when they try to access a forbidden resource.
|
||||||
|
|
||||||
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
*404 Not Found*
|
||||||
def handle_bad_request(e):
|
The good old "chap, you made a mistake typing that URL" message. So
|
||||||
return 'bad request!', 400
|
common that even novices to the internet know that 404 means: damn,
|
||||||
|
the thing I was looking for is not there. It's a very good idea to
|
||||||
|
make sure there is actually something useful on a 404 page, at least a
|
||||||
|
link back to the index.
|
||||||
|
|
||||||
# or, without the decorator
|
*410 Gone*
|
||||||
app.register_error_handler(400, handle_bad_request)
|
Did you know that there the "404 Not Found" has a brother named "410
|
||||||
|
Gone"? Few people actually implement that, but the idea is that
|
||||||
|
resources that previously existed and got deleted answer with 410
|
||||||
|
instead of 404. If you are not deleting documents permanently from
|
||||||
|
the database but just mark them as deleted, do the user a favour and
|
||||||
|
use the 410 code instead and display a message that what they were
|
||||||
|
looking for was deleted for all eternity.
|
||||||
|
|
||||||
:exc:`werkzeug.exceptions.HTTPException` subclasses like
|
*500 Internal Server Error*
|
||||||
:exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable
|
Usually happens on programming errors or if the server is overloaded.
|
||||||
when registering handlers. (``BadRequest.code == 400``)
|
A terribly good idea is to have a nice page there, because your
|
||||||
|
application *will* fail sooner or later.
|
||||||
|
|
||||||
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. ::
|
|
||||||
|
|
||||||
class InsufficientStorage(werkzeug.exceptions.HTTPException):
|
|
||||||
code = 507
|
|
||||||
description = 'Not enough storage space.'
|
|
||||||
|
|
||||||
app.register_error_handler(InsufficientStorage, handle_507)
|
Default Error Handling
|
||||||
|
``````````````````````
|
||||||
|
|
||||||
raise InsufficientStorage()
|
When building a Flask application you *will* run into exceptions. If some part
|
||||||
|
of your code breaks while handling a request (and you have no error handlers
|
||||||
|
registered) an "500 Internal Server Error"
|
||||||
|
(:exc:`~werkzeug.exceptions.InternalServerError`) will be returned by default.
|
||||||
|
Similarly, if a request is sent to an unregistered route a "404 Not Found"
|
||||||
|
(:exc:`~werkzeug.exceptions.NotFound`) error will occur. If a route receives an
|
||||||
|
unallowed request method a "405 Method Not Allowed"
|
||||||
|
(:exc:`~werkzeug.exceptions.MethodNotAllowed`) will be raised. These are all
|
||||||
|
subclasses of :class:`~werkzeug.exceptions.HTTPException` and are provided by
|
||||||
|
default in Flask.
|
||||||
|
|
||||||
Handlers can be registered for any exception class, not just
|
Flask gives you to the ability to raise any HTTP exception registered by
|
||||||
:exc:`~werkzeug.exceptions.HTTPException` subclasses or HTTP status
|
werkzeug. However, as the default HTTP exceptions return simple exception
|
||||||
codes. Handlers can be registered for a specific class, or for all subclasses
|
pages, Flask also offers the opportunity to customise these HTTP exceptions via
|
||||||
of a parent class.
|
custom error handlers as well as to add exception handlers for builtin and
|
||||||
|
custom exceptions.
|
||||||
Handling
|
|
||||||
````````
|
|
||||||
|
|
||||||
When an exception is caught by Flask while handling a request, it is first
|
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
|
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
|
by its class hierarchy; the most specific handler is chosen. If no handler is
|
||||||
registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a
|
registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a
|
||||||
generic message about their code, while other exceptions are converted to a
|
generic message about their code, while other exceptions are converted to a
|
||||||
generic 500 Internal Server Error.
|
generic "500 Internal Server Error".
|
||||||
|
|
||||||
For example, if an instance of :exc:`ConnectionRefusedError` is raised,
|
For example, if an instance of :exc:`ConnectionRefusedError` is raised,
|
||||||
and a handler is registered for :exc:`ConnectionError` and
|
and a handler is registered for :exc:`ConnectionError` and
|
||||||
:exc:`ConnectionRefusedError`,
|
:exc:`ConnectionRefusedError`, the more specific :exc:`ConnectionRefusedError`
|
||||||
the more specific :exc:`ConnectionRefusedError` handler is called with the
|
handler is called with the exception instance to generate the response.
|
||||||
exception instance to generate the response.
|
|
||||||
|
|
||||||
Handlers registered on the blueprint take precedence over those registered
|
Handlers registered on the blueprint take precedence over those registered
|
||||||
globally on the application, assuming a blueprint is handling the request that
|
globally on the application, assuming a blueprint is handling the request that
|
||||||
|
@ -137,6 +107,348 @@ because the 404 occurs at the routing level before the blueprint can be
|
||||||
determined.
|
determined.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _handling-errors:
|
||||||
|
|
||||||
|
Handling Errors
|
||||||
|
```````````````
|
||||||
|
|
||||||
|
Sometimes when building a Flask application, you might want to raise a
|
||||||
|
:exc:`~werkzeug.exceptions.HTTPException` to signal to the user that
|
||||||
|
something is wrong with the request. Fortunately, Flask comes with a handy
|
||||||
|
:func:`~flask.abort` function that aborts a request with a HTTP error from
|
||||||
|
werkzeug as desired.
|
||||||
|
|
||||||
|
Consider the code below, we might have a user profile route, but if the user
|
||||||
|
fails to pass a username we raise a "400 Bad Request" and if the user passes a
|
||||||
|
username but we can't find it, we raise a "404 Not Found".
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from flask import abort, render_template, request
|
||||||
|
|
||||||
|
# a username needs to be supplied in the query args
|
||||||
|
# a successful request would be like /profile?username=jack
|
||||||
|
@app.route("/profile")
|
||||||
|
def user_profile():
|
||||||
|
username = request.arg.get("username")
|
||||||
|
# if a username isn't supplied in the request, return a 400 bad request
|
||||||
|
if username is None:
|
||||||
|
abort(400)
|
||||||
|
|
||||||
|
user = get_user(username=username)
|
||||||
|
# if a user can't be found by their username, return 404 not found
|
||||||
|
if user is None:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
return render_template("profile.html", user=user)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _custom-error-handlers:
|
||||||
|
|
||||||
|
Custom error handlers
|
||||||
|
`````````````````````
|
||||||
|
|
||||||
|
The default :exc:`~werkzeug.exceptions.HTTPException` returns a black and white
|
||||||
|
error page with a basic description, but nothing fancy. Considering
|
||||||
|
these errors *will* be thrown during the lifetime of your application, it is
|
||||||
|
highly advisable to customise these exceptions to improve the user experience
|
||||||
|
of your site. This can be done by registering error handlers.
|
||||||
|
|
||||||
|
An error handler is a normal view function that returns a response, but instead
|
||||||
|
of being registered for a route, it is registered for an exception or HTTP
|
||||||
|
status code that would be raised while trying to handle a request.
|
||||||
|
|
||||||
|
It is passed the instance of the error being handled, which is most
|
||||||
|
likely an integer that represents a :exc:`~werkzeug.exceptions.HTTPException`
|
||||||
|
status code. For example 500 (an "Internal Server Error") which maps to
|
||||||
|
:exc:`~werkzeug.exceptions.InternalServerError`.
|
||||||
|
|
||||||
|
It is registered with the :meth:`~flask.Flask.errorhandler`
|
||||||
|
decorator or the :meth:`~flask.Flask.register_error_handler` to register
|
||||||
|
the function later. A handler can be registered for a status code,
|
||||||
|
like 404 or 500, or for an built-in exception class, like KeyError,
|
||||||
|
or a custom exception class that inherits from Exception or its subclasses.
|
||||||
|
|
||||||
|
The status code of the response will not be set to the handler's code. Make
|
||||||
|
sure to provide the appropriate HTTP status code when returning a response from
|
||||||
|
a handler or a 200 OK HTTP code will be sent instead.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from werkzeug.exceptions import InternalServerError
|
||||||
|
|
||||||
|
# as a decorator with an int as the exception code
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def handle_internal_server_error(e):
|
||||||
|
# returning 500 with the text sets the error handler's code
|
||||||
|
# make sure to provide the appropriate HTTP status code
|
||||||
|
# otherwise 200 will be returned as default
|
||||||
|
return 'Internal Server Error!', 500
|
||||||
|
|
||||||
|
# or, as a decorator with the werkzeug exception for internal server error
|
||||||
|
@app.errorhandler(InternalServerError)
|
||||||
|
def handle_internal_server_error(e):
|
||||||
|
# werkzeug exceptions have a code attribute
|
||||||
|
return 'Internal Server Error!', e.code
|
||||||
|
|
||||||
|
# or, without the decorator
|
||||||
|
app.register_error_handler(500, handle_internal_server_error)
|
||||||
|
|
||||||
|
# similarly with a werkzeug exception
|
||||||
|
app.register_error_handler(InternalServerError, handle_internal_server_error)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
A handler for "500 Internal Server Error" will not be used when running in
|
||||||
|
debug mode. Instead, the interactive debugger will be shown.
|
||||||
|
|
||||||
|
If there is an error handler registered for ``InternalServerError``,
|
||||||
|
this will be invoked. As of Flask 1.1.0, this error handler will always
|
||||||
|
be passed an instance of ``InternalServerError``, not the original
|
||||||
|
unhandled error. The original error is available as ``e.original_exception``.
|
||||||
|
Until Werkzeug 1.0.0, this attribute will only exist during unhandled
|
||||||
|
errors, use ``getattr`` to get access it for compatibility.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.errorhandler(InternalServerError)
|
||||||
|
def handle_500(e):
|
||||||
|
original = getattr(e, "original_exception", None)
|
||||||
|
|
||||||
|
if original is None:
|
||||||
|
# direct 500 error, such as abort(500)
|
||||||
|
return render_template("500.html"), 500
|
||||||
|
|
||||||
|
# wrapped unhandled error
|
||||||
|
return render_template("500_unhandled.html", e=original), 500
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Registering Custom Exceptions
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
You can create your own custom exceptions by subclassing
|
||||||
|
:exc:`werkzeug.exceptions.HTTPException`. As shown above, integer HTTP codes
|
||||||
|
are interchangable when registering handlers. (``BadRequest.code == 400``)
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class InsufficientStorage(werkzeug.exceptions.HTTPException):
|
||||||
|
code = 507
|
||||||
|
description = 'Not enough storage space.'
|
||||||
|
|
||||||
|
def handle_507(e):
|
||||||
|
return 'Not enough storage space!', 507
|
||||||
|
|
||||||
|
app.register_error_handler(InsufficientStorage, handle_507)
|
||||||
|
|
||||||
|
# during an request
|
||||||
|
raise InsufficientStorage()
|
||||||
|
|
||||||
|
Handlers can be registered for any exception class that inherits from Exception.
|
||||||
|
|
||||||
|
|
||||||
|
Unhandled Exceptions
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
If an exception is raised in the code while Flask is handling a request and
|
||||||
|
there is no error handler registered for that exception, a "500 Internal Server
|
||||||
|
Error" will be returned instead. See :meth:`flask.Flask.handle_exception` for
|
||||||
|
information about this behavior.
|
||||||
|
|
||||||
|
Custom error pages
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The above examples wouldn't actually be an improvement on the default
|
||||||
|
exception pages. We can create a custom 500.html template like this:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}Internal Server Error{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h1>Internal Server Error</h1>
|
||||||
|
<p>Oops... we seem to have made a mistake, sorry!</p>
|
||||||
|
<p><a href="{{ url_for('index') }}">Go somewhere nice instead</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
It can be implemented by rendering the template on "500 Internal Server Error":
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from flask import render_template
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def internal_server_error(e):
|
||||||
|
# note that we set the 500 status explicitly
|
||||||
|
return render_template('500.html'), 500
|
||||||
|
|
||||||
|
|
||||||
|
When using the :doc:`/patterns/appfactories`:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
|
||||||
|
from flask import Flask, render_template
|
||||||
|
|
||||||
|
def internal_server_error(e):
|
||||||
|
return render_template('500.html'), 500
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.register_error_handler(500, internal_server_error)
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
When using :doc:`/blueprints`:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
blog = Blueprint('blog', __name__)
|
||||||
|
|
||||||
|
# as a decorator
|
||||||
|
@blog.errorhandler(500)
|
||||||
|
def internal_server_error(e):
|
||||||
|
return render_template('500.html'), 500
|
||||||
|
|
||||||
|
# or with register_error_handler
|
||||||
|
blog.register_error_handler(500, internal_server_error)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
In blueprints errorhandlers will simply work as expected; however, there is a caveat
|
||||||
|
concerning handlers for 404 and 405 exceptions. These errorhandlers are only
|
||||||
|
invoked from an appropriate ``raise`` statement or a call to ``abort`` in another
|
||||||
|
of the blueprint's view functions; they are not invoked by, e.g., an invalid URL
|
||||||
|
access. This is because the blueprint does not "own" a certain URL space, so
|
||||||
|
the application instance has no way of knowing which blueprint error handler it
|
||||||
|
should run if given an invalid URL. If you would like to execute different
|
||||||
|
handling strategies for these errors based on URL prefixes, they may be defined
|
||||||
|
at the application level using the ``request`` proxy object:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from flask import jsonify, render_template
|
||||||
|
|
||||||
|
# at the application level
|
||||||
|
# not the blueprint level
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def page_not_found(e):
|
||||||
|
# if a request is in our blog URL space
|
||||||
|
if request.path.startswith('/blog/'):
|
||||||
|
# we return a custom blog 404 page
|
||||||
|
return render_template("blog/404.html"), 404
|
||||||
|
else:
|
||||||
|
# otherwise we return our generic site-wide 404 page
|
||||||
|
return render_template("404.html"), 404
|
||||||
|
|
||||||
|
|
||||||
|
@app.errorhandler(405)
|
||||||
|
def method_not_allowed(e):
|
||||||
|
# if a request has the wrong method to our API
|
||||||
|
if request.path.startswith('/api/'):
|
||||||
|
# we return a json saying so
|
||||||
|
return jsonify(message="Method Not Allowed"), 405
|
||||||
|
else:
|
||||||
|
# otherwise we return a generic site-wide 405 page
|
||||||
|
return render_template("405.html"), 405
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
More information on error handling with blueprint can be found in
|
||||||
|
:doc:`/blueprints`.
|
||||||
|
|
||||||
|
|
||||||
|
Returning API errors as JSON
|
||||||
|
````````````````````````````
|
||||||
|
|
||||||
|
When building APIs in Flask, some developers realise that the builtin
|
||||||
|
exceptions are not expressive enough for APIs and that the content type of
|
||||||
|
:mimetype:`text/html` they are emitting is not very useful for API consumers.
|
||||||
|
|
||||||
|
Using the same techniques as above and :func:`~flask.json.jsonify` we can return JSON
|
||||||
|
responses to API errors. :func:`~flask.abort` is called
|
||||||
|
with a ``description`` parameter. The errorhandler will
|
||||||
|
use that as the JSON error message, and set the status code to 404.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from flask import abort, jsonify
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def resource_not_found(e):
|
||||||
|
return jsonify(error=str(e)), 404
|
||||||
|
|
||||||
|
@app.route("/cheese")
|
||||||
|
def get_one_cheese():
|
||||||
|
resource = get_resource()
|
||||||
|
|
||||||
|
if resource is None:
|
||||||
|
abort(404, description="Resource not found")
|
||||||
|
|
||||||
|
return jsonify(resource)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
We can also create custom exception classes; for instance, for an API we can
|
||||||
|
introduce a new custom exception that can take a proper human readable message,
|
||||||
|
a status code for the error and some optional payload to give more context
|
||||||
|
for the error.
|
||||||
|
|
||||||
|
This is a simple example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from flask import jsonify, request
|
||||||
|
|
||||||
|
class InvalidAPIUsage(Exception):
|
||||||
|
status_code = 400
|
||||||
|
|
||||||
|
def __init__(self, message, status_code=None, payload=None):
|
||||||
|
super().__init__()
|
||||||
|
self.message = message
|
||||||
|
if status_code is not None:
|
||||||
|
self.status_code = status_code
|
||||||
|
self.payload = payload
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
rv = dict(self.payload or ())
|
||||||
|
rv['message'] = self.message
|
||||||
|
return rv
|
||||||
|
|
||||||
|
@app.errorhandler(InvalidAPIUsage)
|
||||||
|
def invalid_api_usage(e):
|
||||||
|
return jsonify(e.to_dict())
|
||||||
|
|
||||||
|
# an API app route for getting user information
|
||||||
|
# a correct request might be /api/user?user_id=420
|
||||||
|
@app.route("/api/user")
|
||||||
|
def user_api(user_id):
|
||||||
|
user_id = request.arg.get("user_id")
|
||||||
|
if not user_id:
|
||||||
|
raise InvalidAPIUsage("No user id provided!")
|
||||||
|
|
||||||
|
user = get_user(user_id=user_id)
|
||||||
|
if not user:
|
||||||
|
raise InvalidAPIUsage("No such user!", status_code=404)
|
||||||
|
|
||||||
|
return jsonify(user.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
A view can now raise that exception with an error message. Additionally
|
||||||
|
some extra payload can be provided as a dictionary through the `payload`
|
||||||
|
parameter.
|
||||||
|
|
||||||
|
|
||||||
Generic Exception Handlers
|
Generic Exception Handlers
|
||||||
``````````````````````````
|
``````````````````````````
|
||||||
|
|
||||||
|
@ -169,6 +481,11 @@ so you don't lose information about the HTTP error.
|
||||||
response.content_type = "application/json"
|
response.content_type = "application/json"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
# or using jsonify
|
||||||
|
@app.errorhandler(HTTPException)
|
||||||
|
def handle_exception(e):
|
||||||
|
return jsonify("code": e.code, "name": e.name, "description": e.description), e.code
|
||||||
|
|
||||||
|
|
||||||
An error handler for ``Exception`` might seem useful for changing how
|
An error handler for ``Exception`` might seem useful for changing how
|
||||||
all errors, even unhandled ones, are presented to the user. However,
|
all errors, even unhandled ones, are presented to the user. However,
|
||||||
|
@ -196,35 +513,42 @@ register handlers for both ``HTTPException`` and ``Exception``, the
|
||||||
``Exception`` handler will not handle ``HTTPException`` subclasses
|
``Exception`` handler will not handle ``HTTPException`` subclasses
|
||||||
because it the ``HTTPException`` handler is more specific.
|
because it the ``HTTPException`` handler is more specific.
|
||||||
|
|
||||||
Unhandled Exceptions
|
|
||||||
````````````````````
|
|
||||||
|
|
||||||
When there is no error handler registered for an exception, a 500
|
Generic Error Pages
|
||||||
Internal Server Error will be returned instead. See
|
-------------------
|
||||||
:meth:`flask.Flask.handle_exception` for information about this
|
|
||||||
behavior.
|
|
||||||
|
|
||||||
If there is an error handler registered for ``InternalServerError``,
|
If we pass in the exception into a template as below:
|
||||||
this will be invoked. As of Flask 1.1.0, this error handler will always
|
|
||||||
be passed an instance of ``InternalServerError``, not the original
|
|
||||||
unhandled error. The original error is available as ``e.original_exception``.
|
|
||||||
Until Werkzeug 1.0.0, this attribute will only exist during unhandled
|
|
||||||
errors, use ``getattr`` to get access it for compatibility.
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@app.errorhandler(InternalServerError)
|
from werkzeug.exceptions import HTTPException
|
||||||
def handle_500(e):
|
|
||||||
original = getattr(e, "original_exception", None)
|
|
||||||
|
|
||||||
if original is None:
|
@app.errorhandler(HTTPException)
|
||||||
# direct 500 error, such as abort(500)
|
def handle_exception(e):
|
||||||
return render_template("500.html"), 500
|
return render_template("exception.html", e=e), e.code
|
||||||
|
|
||||||
# wrapped unhandled error
|
|
||||||
return render_template("500_unhandled.html", e=original), 500
|
|
||||||
|
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}{{ e.name }}{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h1>{{ e.code }} {{ e.name }}</h1>
|
||||||
|
<p>{{ e.description }}</p>
|
||||||
|
<p><a href="{{ url_for('index') }}">Go home</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Debugging Application Errors
|
||||||
|
````````````````````````````
|
||||||
|
|
||||||
|
For production applications, configure your application with logging and
|
||||||
|
notifications as described in :doc:`/logging`. This section provides
|
||||||
|
pointers when debugging deployment configuration and digging deeper with a
|
||||||
|
full-featured Python debugger.
|
||||||
|
|
||||||
Logging
|
Logging
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -232,14 +556,6 @@ See :doc:`/logging` for information on how to log exceptions, such as by
|
||||||
emailing them to admins.
|
emailing them to admins.
|
||||||
|
|
||||||
|
|
||||||
Debugging Application Errors
|
|
||||||
============================
|
|
||||||
|
|
||||||
For production applications, configure your application with logging and
|
|
||||||
notifications as described in :doc:`/logging`. This section provides
|
|
||||||
pointers when debugging deployment configuration and digging deeper with a
|
|
||||||
full-featured Python debugger.
|
|
||||||
|
|
||||||
|
|
||||||
When in Doubt, Run Manually
|
When in Doubt, Run Manually
|
||||||
---------------------------
|
---------------------------
|
||||||
|
@ -292,3 +608,47 @@ you could have something like::
|
||||||
use_debugger = app.debug and not(app.config.get('DEBUG_WITH_APTANA'))
|
use_debugger = app.debug and not(app.config.get('DEBUG_WITH_APTANA'))
|
||||||
app.run(use_debugger=use_debugger, debug=app.debug,
|
app.run(use_debugger=use_debugger, debug=app.debug,
|
||||||
use_reloader=use_debugger, host='0.0.0.0')
|
use_reloader=use_debugger, host='0.0.0.0')
|
||||||
|
|
||||||
|
|
||||||
|
.. _error-logging-tools:
|
||||||
|
|
||||||
|
|
||||||
|
Error Logging Tools
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Sending error mails, even if just for critical ones, can become
|
||||||
|
overwhelming if enough users are hitting the error and log files are
|
||||||
|
typically never looked at. This is why we recommend using `Sentry
|
||||||
|
<https://sentry.io/>`_ for dealing with application errors. It's
|
||||||
|
available as an Open Source project `on GitHub
|
||||||
|
<https://github.com/getsentry/sentry>`_ and is also available as a `hosted version
|
||||||
|
<https://sentry.io/signup/>`_ which you can try for free. Sentry
|
||||||
|
aggregates duplicate errors, captures the full stack trace and local
|
||||||
|
variables for debugging, and sends you mails based on new errors or
|
||||||
|
frequency thresholds.
|
||||||
|
|
||||||
|
To use Sentry you need to install the `sentry-sdk` client with extra `flask` dependencies::
|
||||||
|
|
||||||
|
$ pip install sentry-sdk[flask]
|
||||||
|
|
||||||
|
And then add this to your Flask app::
|
||||||
|
|
||||||
|
import sentry_sdk
|
||||||
|
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||||
|
|
||||||
|
sentry_sdk.init('YOUR_DSN_HERE',integrations=[FlaskIntegration()])
|
||||||
|
|
||||||
|
The `YOUR_DSN_HERE` value needs to be replaced with the DSN value you get
|
||||||
|
from your Sentry installation.
|
||||||
|
|
||||||
|
After installation, failures leading to an Internal Server Error
|
||||||
|
are automatically reported to Sentry and from there you can
|
||||||
|
receive error notifications.
|
||||||
|
|
||||||
|
Follow-up reads:
|
||||||
|
|
||||||
|
* Sentry also supports catching errors from your worker queue (RQ, Celery) in a
|
||||||
|
similar fashion. See the `Python SDK docs
|
||||||
|
<https://docs.sentry.io/platforms/python/>`_ for more information.
|
||||||
|
* `Getting started with Sentry <https://docs.sentry.io/quickstart/?platform=python>`_
|
||||||
|
* `Flask-specific documentation <https://docs.sentry.io/platforms/python/flask/>`_.
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
Implementing API Exceptions
|
|
||||||
===========================
|
|
||||||
|
|
||||||
It's very common to implement RESTful APIs on top of Flask. One of the
|
|
||||||
first things that developers run into is the realization that the builtin
|
|
||||||
exceptions are not expressive enough for APIs and that the content type of
|
|
||||||
:mimetype:`text/html` they are emitting is not very useful for API consumers.
|
|
||||||
|
|
||||||
The better solution than using ``abort`` to signal errors for invalid API
|
|
||||||
usage is to implement your own exception type and install an error handler
|
|
||||||
for it that produces the errors in the format the user is expecting.
|
|
||||||
|
|
||||||
Simple Exception Class
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
The basic idea is to introduce a new exception that can take a proper
|
|
||||||
human readable message, a status code for the error and some optional
|
|
||||||
payload to give more context for the error.
|
|
||||||
|
|
||||||
This is a simple example::
|
|
||||||
|
|
||||||
from flask import jsonify
|
|
||||||
|
|
||||||
class InvalidUsage(Exception):
|
|
||||||
status_code = 400
|
|
||||||
|
|
||||||
def __init__(self, message, status_code=None, payload=None):
|
|
||||||
Exception.__init__(self)
|
|
||||||
self.message = message
|
|
||||||
if status_code is not None:
|
|
||||||
self.status_code = status_code
|
|
||||||
self.payload = payload
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
rv = dict(self.payload or ())
|
|
||||||
rv['message'] = self.message
|
|
||||||
return rv
|
|
||||||
|
|
||||||
A view can now raise that exception with an error message. Additionally
|
|
||||||
some extra payload can be provided as a dictionary through the `payload`
|
|
||||||
parameter.
|
|
||||||
|
|
||||||
Registering an Error Handler
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
At that point views can raise that error, but it would immediately result
|
|
||||||
in an internal server error. The reason for this is that there is no
|
|
||||||
handler registered for this error class. That however is easy to add::
|
|
||||||
|
|
||||||
@app.errorhandler(InvalidUsage)
|
|
||||||
def handle_invalid_usage(error):
|
|
||||||
response = jsonify(error.to_dict())
|
|
||||||
response.status_code = error.status_code
|
|
||||||
return response
|
|
||||||
|
|
||||||
Usage in Views
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Here is how a view can use that functionality::
|
|
||||||
|
|
||||||
@app.route('/foo')
|
|
||||||
def get_foo():
|
|
||||||
raise InvalidUsage('This view is gone', status_code=410)
|
|
|
@ -1,123 +0,0 @@
|
||||||
Custom Error Pages
|
|
||||||
==================
|
|
||||||
|
|
||||||
Flask comes with a handy :func:`~flask.abort` function that aborts a
|
|
||||||
request with an HTTP error code early. It will also provide a plain black
|
|
||||||
and white error page for you with a basic description, but nothing fancy.
|
|
||||||
|
|
||||||
Depending on the error code it is less or more likely for the user to
|
|
||||||
actually see such an error.
|
|
||||||
|
|
||||||
Common Error Codes
|
|
||||||
------------------
|
|
||||||
|
|
||||||
The following error codes are some that are often displayed to the user,
|
|
||||||
even if the application behaves correctly:
|
|
||||||
|
|
||||||
*404 Not Found*
|
|
||||||
The good old "chap, you made a mistake typing that URL" message. So
|
|
||||||
common that even novices to the internet know that 404 means: damn,
|
|
||||||
the thing I was looking for is not there. It's a very good idea to
|
|
||||||
make sure there is actually something useful on a 404 page, at least a
|
|
||||||
link back to the index.
|
|
||||||
|
|
||||||
*403 Forbidden*
|
|
||||||
If you have some kind of access control on your website, you will have
|
|
||||||
to send a 403 code for disallowed resources. So make sure the user
|
|
||||||
is not lost when they try to access a forbidden resource.
|
|
||||||
|
|
||||||
*410 Gone*
|
|
||||||
Did you know that there the "404 Not Found" has a brother named "410
|
|
||||||
Gone"? Few people actually implement that, but the idea is that
|
|
||||||
resources that previously existed and got deleted answer with 410
|
|
||||||
instead of 404. If you are not deleting documents permanently from
|
|
||||||
the database but just mark them as deleted, do the user a favour and
|
|
||||||
use the 410 code instead and display a message that what they were
|
|
||||||
looking for was deleted for all eternity.
|
|
||||||
|
|
||||||
*500 Internal Server Error*
|
|
||||||
Usually happens on programming errors or if the server is overloaded.
|
|
||||||
A terribly good idea is to have a nice page there, because your
|
|
||||||
application *will* fail sooner or later (see also:
|
|
||||||
:doc:`/errorhandling`).
|
|
||||||
|
|
||||||
|
|
||||||
Error Handlers
|
|
||||||
--------------
|
|
||||||
|
|
||||||
An error handler is a function that returns a response when a type of error is
|
|
||||||
raised, similar to how a view is a function that returns a response when a
|
|
||||||
request URL is matched. It is passed the instance of the error being handled,
|
|
||||||
which is most likely a :exc:`~werkzeug.exceptions.HTTPException`. An error
|
|
||||||
handler for "500 Internal Server Error" will be passed uncaught exceptions in
|
|
||||||
addition to explicit 500 errors.
|
|
||||||
|
|
||||||
An error handler is registered with the :meth:`~flask.Flask.errorhandler`
|
|
||||||
decorator or the :meth:`~flask.Flask.register_error_handler` method. A handler
|
|
||||||
can be registered for a status code, like 404, or for an exception class.
|
|
||||||
|
|
||||||
The status code of the response will not be set to the handler's code. Make
|
|
||||||
sure to provide the appropriate HTTP status code when returning a response from
|
|
||||||
a handler.
|
|
||||||
|
|
||||||
A handler for "500 Internal Server Error" will not be used when running in
|
|
||||||
debug mode. Instead, the interactive debugger will be shown.
|
|
||||||
|
|
||||||
Here is an example implementation for a "404 Page Not Found" exception::
|
|
||||||
|
|
||||||
from flask import render_template
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
|
||||||
def page_not_found(e):
|
|
||||||
# note that we set the 404 status explicitly
|
|
||||||
return render_template('404.html'), 404
|
|
||||||
|
|
||||||
When using the :doc:`appfactories`::
|
|
||||||
|
|
||||||
from flask import Flask, render_template
|
|
||||||
|
|
||||||
def page_not_found(e):
|
|
||||||
return render_template('404.html'), 404
|
|
||||||
|
|
||||||
def create_app(config_filename):
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.register_error_handler(404, page_not_found)
|
|
||||||
return app
|
|
||||||
|
|
||||||
An example template might be this:
|
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
|
||||||
|
|
||||||
{% extends "layout.html" %}
|
|
||||||
{% block title %}Page Not Found{% endblock %}
|
|
||||||
{% block body %}
|
|
||||||
<h1>Page Not Found</h1>
|
|
||||||
<p>What you were looking for is just not there.
|
|
||||||
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
Returning API errors as JSON
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
When using Flask for web APIs, you can use the same techniques as above
|
|
||||||
to return JSON responses to API errors. :func:`~flask.abort` is called
|
|
||||||
with a ``description`` parameter. The :meth:`~flask.errorhandler` will
|
|
||||||
use that as the JSON error message, and set the status code to 404.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from flask import abort, jsonify
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
|
||||||
def resource_not_found(e):
|
|
||||||
return jsonify(error=str(e)), 404
|
|
||||||
|
|
||||||
@app.route("/cheese")
|
|
||||||
def get_one_cheese():
|
|
||||||
resource = get_resource()
|
|
||||||
|
|
||||||
if resource is None:
|
|
||||||
abort(404, description="Resource not found")
|
|
||||||
|
|
||||||
return jsonify(resource)
|
|
|
@ -18,7 +18,6 @@ collected in the following pages.
|
||||||
packages
|
packages
|
||||||
appfactories
|
appfactories
|
||||||
appdispatch
|
appdispatch
|
||||||
apierrors
|
|
||||||
urlprocessors
|
urlprocessors
|
||||||
distribute
|
distribute
|
||||||
fabric
|
fabric
|
||||||
|
@ -31,7 +30,6 @@ collected in the following pages.
|
||||||
templateinheritance
|
templateinheritance
|
||||||
flashing
|
flashing
|
||||||
jquery
|
jquery
|
||||||
errorpages
|
|
||||||
lazyloading
|
lazyloading
|
||||||
mongoengine
|
mongoengine
|
||||||
favicon
|
favicon
|
||||||
|
|
Loading…
Reference in New Issue