From 59b0b85835fa925e67f9a3f5173f7d6b6e67a411 Mon Sep 17 00:00:00 2001 From: jackwardell Date: Sat, 2 May 2020 12:34:31 +0100 Subject: [PATCH] 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. --- docs/blueprints.rst | 2 +- docs/errorhandling.rst | 588 ++++++++++++++++++++++++++++------- docs/patterns/apierrors.rst | 63 ---- docs/patterns/errorpages.rst | 123 -------- docs/patterns/index.rst | 2 - 5 files changed, 475 insertions(+), 303 deletions(-) delete mode 100644 docs/patterns/apierrors.rst delete mode 100644 docs/patterns/errorpages.rst diff --git a/docs/blueprints.rst b/docs/blueprints.rst index 1f10a301..ce88c0f5 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -273,4 +273,4 @@ at the application level using the ``request`` proxy object:: else: return ex -More information on error handling see :doc:`/patterns/errorpages`. +See :doc:`/errorhandling`. diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index b7b2edf1..121c8a05 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -1,5 +1,5 @@ -Application Errors -================== +Handling Application Errors +=========================== .. 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 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 -exception to the :attr:`~flask.Flask.logger`. +production mode, and an exception is raised Flask will display a very simple +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 -with errors. - -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 -`_ for dealing with application errors. It's -available as an Open Source project `on GitHub -`_ and is also available as a `hosted version -`_ 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 - `_ for more information. -* `Getting started with Sentry `_ -* `Flask-specific documentation `_. +with errors including custom exceptions and 3rd party tools. -Error handlers --------------- +.. _common-error-codes: -You might want to show custom error pages to the user when an error occurs. -This can be done by registering error handlers. +Common Error Codes +`````````````````` -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. +The following error codes are some that are often displayed to the user, +even if the application behaves correctly: -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 -: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. :: +*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. - @app.errorhandler(werkzeug.exceptions.BadRequest) - def handle_bad_request(e): - return 'bad request!', 400 +*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. - # or, without the decorator - app.register_error_handler(400, handle_bad_request) +*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. -:exc:`werkzeug.exceptions.HTTPException` subclasses like -:exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable -when registering handlers. (``BadRequest.code == 400``) +*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. -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 -: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 -```````` +Flask gives you to the ability to raise any HTTP exception registered by +werkzeug. However, as the default HTTP exceptions return simple exception +pages, Flask also offers the opportunity to customise these HTTP exceptions via +custom error handlers as well as to add exception handlers for builtin and +custom exceptions. 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. +generic "500 Internal Server Error". 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 with the -exception instance to generate the response. +:exc:`ConnectionRefusedError`, 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 @@ -137,6 +107,348 @@ because the 404 occurs at the routing level before the blueprint can be 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 %} +

Internal Server Error

+

Oops... we seem to have made a mistake, sorry!

+

Go somewhere nice instead + {% 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 `````````````````````````` @@ -169,6 +481,11 @@ so you don't lose information about the HTTP error. response.content_type = "application/json" 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 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 because it the ``HTTPException`` handler is more specific. -Unhandled Exceptions -```````````````````` -When there is no error handler registered for an exception, a 500 -Internal Server Error will be returned instead. See -:meth:`flask.Flask.handle_exception` for information about this -behavior. +Generic Error Pages +------------------- -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. +If we pass in the exception into a template as below: .. code-block:: python - @app.errorhandler(InternalServerError) - def handle_500(e): - original = getattr(e, "original_exception", None) + from werkzeug.exceptions import HTTPException - if original is None: - # direct 500 error, such as abort(500) - return render_template("500.html"), 500 + @app.errorhandler(HTTPException) + def handle_exception(e): + 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 %} +

{{ e.code }} {{ e.name }}

+

{{ e.description }}

+

Go home + {% 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 ------- @@ -232,14 +556,6 @@ See :doc:`/logging` for information on how to log exceptions, such as by 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 --------------------------- @@ -292,3 +608,47 @@ you could have something like:: use_debugger = app.debug and not(app.config.get('DEBUG_WITH_APTANA')) app.run(use_debugger=use_debugger, debug=app.debug, 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 +`_ for dealing with application errors. It's +available as an Open Source project `on GitHub +`_ and is also available as a `hosted version +`_ 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 + `_ for more information. +* `Getting started with Sentry `_ +* `Flask-specific documentation `_. diff --git a/docs/patterns/apierrors.rst b/docs/patterns/apierrors.rst deleted file mode 100644 index 90e8c13d..00000000 --- a/docs/patterns/apierrors.rst +++ /dev/null @@ -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) diff --git a/docs/patterns/errorpages.rst b/docs/patterns/errorpages.rst deleted file mode 100644 index cf7462af..00000000 --- a/docs/patterns/errorpages.rst +++ /dev/null @@ -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 %} -

Page Not Found

-

What you were looking for is just not there. -

go somewhere nice - {% 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) diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst index a9727e3d..f765cd8a 100644 --- a/docs/patterns/index.rst +++ b/docs/patterns/index.rst @@ -18,7 +18,6 @@ collected in the following pages. packages appfactories appdispatch - apierrors urlprocessors distribute fabric @@ -31,7 +30,6 @@ collected in the following pages. templateinheritance flashing jquery - errorpages lazyloading mongoengine favicon