Filters and Error Handlers

Filters

Filters are like middlewares but applied to a specific resource. They have an API similar to what Django offers.

Here’s a basic prototype of a filter:

def basic_filter(get_response, params):
    # get_response is the view function or the next filter on the chain
    # params are the resource parameters

    # This part is called during the resource initialization.
    # You can configure, for instance, things based on params values.

    async def decorated_filter(request, *args, **kwargs):
        # Pre-Response code. You can change request attributes,
        # raise exceptions, call another response function...

        # Processing resource (view)
        result = await get_response(request, *args, **kwargs)

        # Post-Response code. You can change the response attributes,
        # raise exceptions, log things...

    # Don't forget this!
    return decorated_filter

A filter is a decorator function. It must return an asynchronous callable that will handle the request and will return a response or the result of the get_response function.

On Rafter’ side, you can pass the `` filters`` or the validators parameter (both lists) to rafter.app.Rafter.resource().

Each filter will then be chained to the other, in their order of declaration.

Important

The Rafter class has one default validator: filter_transform_response that transforms the response when possible.

If you pass the filters argument to your resource, you’ll override the default filters. If that’s not what you want, you can pass the validators argument instead. These filters will then be chained to the default filters.

Example

The following example demonstrates two filters. The first one changes the response according to the value of ?action in the query string. The second serializes data to plist format when applicable.

examples/filters.py
# -*- coding: utf-8 -*-
import plistlib

from sanic.exceptions import abort
from sanic.response import text, HTTPResponse

from rafter import Rafter

# Our primary Rafter App
app = Rafter()


# Input filter
def basic_filter(get_response, params):
    # This filter changes the response according to the
    # request's GET parameter "action".
    async def decorated_filter(request, *args, **kwargs):
        # When ?action=abort
        if request.args.get('action') == 'abort':
            abort(500, 'Abort!')

        # When ?action=text
        if request.args.get('action') == 'text':
            return text('test response')

        # Go on with the request
        return await get_response(request, *args, **kwargs)

    return decorated_filter


# Output filter
def output_filter(get_response, params):
    # This filter is going to serialize our data into a plist value
    # and send the result as a application/plist+xml response
    # if the request's Accept header is correctly set.
    async def decorated_filter(request, *args, **kwargs):
        response = await get_response(request, *args, **kwargs)

        # Don't do it like that please!
        accept = request.headers.get('accept', '*/*')
        if accept != 'application/plist+xml':
            return response

        # Actually, here you should check if you have a Response instance
        # In this example we don't really need to.

        # You accept plist? Here you go!
        return HTTPResponse(plistlib.dumps(response.data).decode('utf-8'),
                            content_type='application/plist+xml')

    return decorated_filter


@app.resource('/input', validators=[basic_filter])
async def filtered_in(request):
    # See what happens when we add a filter in "validators"
    return request.args


@app.resource('/output', methods=['POST'], validators=[output_filter])
async def filtered_out(request):
    # Return what we received in json
    return request.json


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)

Error Handlers

Rafter provides 3 default error handlers and one exception class rafter.exceptions.ApiError.

All 3 handlers return a structured JSON response containing at least a message and a status. If the Rafter app is in debuging mode, they also return a structured stack trace.

Here are the exception classes handled by the default error handlers:

rafter.exceptions.ApiError:
This exception’s message, status and extra argument are returned.
sanic.exceptions.SanicException:
This exception’s message and status are returned
Exception:
For any other exception, a fixed error with status 500 and An error occured. message.

Example

examples/errors.py
# -*- coding: utf-8 -*-
from sanic.exceptions import abort

from rafter import Rafter, ApiError

app = Rafter()


@app.resource('/')
async def main_view(request):
    # Raising any type of exception
    raise ValueError('Something is wrong!')


@app.resource('/api')
async def api_error(request):
    # Raising an ApiError with custom code, a message
    # and extra arguments
    raise ApiError('Something went very wrong.', 599, xtra=12,
                   explanation='http://example.net/')


@app.resource('/sanic')
async def sanic_error(request):
    # Using Sanic's abort function
    abort(599, 'A bad error.')


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)