Applying Decorators to Class-Based Views in Sanic

In the Sanic web framework, class-based views are constructed by inheriting from HTTPMethodView. This structure allows developers to define handling logic for different HTTP methods (GET, POST, PUT, etc.) as separate methods within the same class. To attach decorators to these views, Sanic provides a dedicated class attribute named decorators.

Defining a Class-Based View

Below is a standard implementation of a Sanic class view. It handles GET and POST requests independently.

from sanic import Sanic
from sanic.response import json
from sanic.views import HTTPMethodView

app = Sanic("demo_app")

class UserView(HTTPMethodView):
    async def get(self, request):
        return json({"message": "Handling GET request"})

    async def post(self, request):
        return json({"message": "Handling POST request"})

app.add_route(UserView.as_view(), "/users")

Implementing a Flexible Decorator

Decorators act as middleware for specific views. A common use case is user authentication. To support flexible usage—allowing the decorator to be used with or without arguments—a specific structure is required. The decorator factory must detect whether it was called with arguments or directly with the function.

The following example demonstrates an authentication decorator that checks a hypothetical authorization flag.

def require_auth(arg=None):
    def decorator(f):
        async def wrapper(request, *args, **kwargs):
            # Simulate authorization check
            is_authenticated = False
            
            if is_authenticated:
                # Proceed to the view method if authorized
                response = await f(request, *args, **kwargs)
                return response
            else:
                return json({"status": "access_denied", "message": "Authentication required"}, status=401)
        return wrapper

    # Handle the case where the decorator is used with or without parentheses
    if callable(arg):
        return decorator(arg)
    return decorator

Attaching Decorators to the View

To apply this decorator to every method in the class view, add it to the decorators list. This list accepts multiple callables, which are executed in order.

class SecureView(HTTPMethodView):
    decorators = [require_auth]

    async def get(self, request):
        return json({"data": "Secure data retrieved"})

In this configuration, accessing the endpoint will trigger the require_auth logic before the get method executes. Since is_authenticated is set to False in the example, the request will be rejected immediately.

Stacking Multiple Decorators

You can chain multiple decorators by adding them to the list. They execute from left to right. If the first decorator fails (e.g., returns an error response), subsequent decorators and the view method itself are skipped.

Let's define a secondary decorator for premium user checks.

def require_pro(arg=None):
    def decorator(f):
        async def wrapper(request, *args, **kwargs):
            is_pro_user = False
            
            if is_pro_user:
                return await f(request, *args, **kwargs)
            else:
                return json({"status": "upgrade_required"}, status=403)
        return wrapper

    if callable(arg):
        return decorator(arg)
    return decorator

Now, apply both decorators to the view.

class PremiumView(HTTPMethodView):
    decorators = [require_auth, require_pro]

    async def get(self, request):
        return json({"content": "Premium content"})

When a request hits this endpoint, require_auth runs first. If it passes, require_pro runs. Since the logic in both decorators currently denies access by default, the request will fail at the first hurdle. To test success flow, toggle the boolean flags within the decorators to True.

Tags: python Sanic web development Class-Based Views decorators

Posted on Thu, 16 Jul 2026 16:18:31 +0000 by ktsirig