# Decorators

One of the best ways to create a consistent and DRY web API is to make use of decorators to remove functionality from the handlers, and make it repeatable across your views.

Therefore, it is very common to see a Sanic view handler with several decorators on it.

@app.get("/orders")
@authorized("view_order")
@validate_list_params()
@inject_user()
async def get_order_details(request, params, user):
    ...

# Example

Here is a starter template to help you create decorators.

In this example, let’s say you want to check that a user is authorized to access a particular endpoint. You can create a decorator that wraps a handler function, checks a request if the client is authorized to access a resource, and sends the appropriate response.

from functools import wraps
from sanic.response import json
def authorized():
    def decorator(f):
        @wraps(f)
        async def decorated_function(request, *args, **kwargs):
            # run some method that checks the request
            # for the client's authorization status
            is_authorized = await check_request_for_authorization_status(request)
            if is_authorized:
                # the user is authorized.
                # run the handler method and return the response
                response = await f(request, *args, **kwargs)
                return response
            else:
                # the user is not authorized.
                return json({"status": "not_authorized"}, 403)
        return decorated_function
    return decorator
@app.route("/")
@authorized()
async def test(request):
    return json({"status": "authorized"})

# Templates

Decorators are fundamental to building applications with Sanic. They increase the portability and maintainablity of your code.

In paraphrasing the Zen of Python: "[decorators] are one honking great idea -- let's do more of those!"

To make it easier to implement them, here are three examples of copy/pastable code to get you started.

Don't forget to add these import statements. Although it is not necessary, using @wraps helps keep some of the metadata of your function in tact. See docs (opens new window). Also, we use the isawaitable pattern here to allow the route handlers to by regular or asynchronous functions.

from inspect import isawaitable
from functools import wraps

# With args

Often, you will want a decorator that will always need arguments. Therefore, when it is implemented you will always be calling it.

@app.get("/")
@foobar(1, 2)
async def handler(request: Request):
    return text("hi")
def foobar(arg1, arg2):
    def decorator(f):
        @wraps(f)
        async def decorated_function(request, *args, **kwargs):
            response = f(request, *args, **kwargs)
            if isawaitable(response):
                response = await response
            return response
        return decorated_function
    return decorator

# Without args

Sometimes you want a decorator that will not take arguments. When this is the case, it is a nice convenience not to have to call it

@app.get("/")
@foobar
async def handler(request: Request):
    return text("hi")
def foobar(func):
    def decorator(f):
        @wraps(f)
        async def decorated_function(request, *args, **kwargs):
            response = f(request, *args, **kwargs)
            if isawaitable(response):
                response = await response
            return response
        return decorated_function
    return decorator(func)

# With or Without args

If you want a decorator with the ability to be called or not, you can follow this pattern. Using keyword only arguments is not necessary, but might make implementation simpler.

@app.get("/")
@foobar(arg1=1, arg2=2)
async def handler(request: Request):
    return text("hi")
@app.get("/")
@foobar
async def handler(request: Request):
    return text("hi")
def foobar(maybe_func=None, *, arg1=None, arg2=None):
    def decorator(f):
        @wraps(f)
        async def decorated_function(request, *args, **kwargs):
            response = f(request, *args, **kwargs)
            if isawaitable(response):
                response = await response
            return response
        return decorated_function
    return decorator(maybe_func) if maybe_func else decorator
MIT Licensed
Copyright © 2018-present Sanic Community Organization

~ Made with ❤️ and ☕️ ~