DEV Community

Muhammad Atif Iqbal
Muhammad Atif Iqbal

Posted on

What is a Decorator in Python, Django and FastAPI?

๐Ÿ”น Decorators in Python, Django and FastAPI in details with examples

In Python, a decorator is a function that wraps another function or class to modify or extend its behavior without changing its code directly.

Think of it like:

โ€œA decorator takes a function/class as input โ†’ adds some extra functionality โ†’ and returns a new function/class.โ€


๐Ÿ”น Example 1 โ€“ Simple function decorator

def my_decorator(func):
    def wrapper():
        print("Before function runs")
        func()
        print("After function runs")
    return wrapper

@my_decorator   # same as: hello = my_decorator(hello)
def hello():
    print("Hello, World!")

hello()
Enter fullscreen mode Exit fullscreen mode

Output:

Before function runs
Hello, World!
After function runs
Enter fullscreen mode Exit fullscreen mode

Here:

  • @my_decorator wraps the hello() function.
  • When you call hello(), actually wrapper() runs.

๐Ÿ”น Example 2 โ€“ Decorator with arguments

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)   # repeat hello 3 times
def hello(name):
    print(f"Hello {name}!")

hello("Atif")
Enter fullscreen mode Exit fullscreen mode

Output:

Hello Atif!
Hello Atif!
Hello Atif!
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”น Example 3 โ€“ Django decorator

In Django, youโ€™ve seen things like:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    ...
Enter fullscreen mode Exit fullscreen mode

Here @login_required ensures that only logged-in users can access that view.


๐Ÿ”น Example 4 โ€“ Class decorator (Django admin)

In your admin file, you saw:

@admin.register(Payment)
class PaymentAdmin(admin.ModelAdmin):
    ...
Enter fullscreen mode Exit fullscreen mode

This is a class decorator.
It tells Django: โ€œRegister the Payment model in the admin, using this custom admin class.โ€

Equivalent without decorator:

class PaymentAdmin(admin.ModelAdmin):
    ...

admin.site.register(Payment, PaymentAdmin)
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”น Example 5 โ€“ FastAPI decorator

In FastAPI, routes are defined with decorators:

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
def say_hello():
    return {"msg": "Hello World"}
Enter fullscreen mode Exit fullscreen mode

Here @app.get("/hello") is a decorator that says: โ€œWhen someone sends a GET request to /hello, call this function.โ€


โœ… Summary for decorators:

  • Decorators = wrappers that extend/modify behavior of functions/classes.
  • Theyโ€™re widely used in Django (admin, views, permissions) and FastAPI (routes, middlewares).

how to write your own decorator step by step

Letโ€™s build your own decorators step by step.
Weโ€™ll start from very basic โ†’ then add arguments โ†’ then apply in Django-like use cases.


๐Ÿ›  Step 1: Basic decorator

def simple_decorator(func):
    def wrapper():
        print("๐Ÿ‘‰ Before the function")
        func()
        print("๐Ÿ‘‰ After the function")
    return wrapper

@simple_decorator
def say_hello():
    print("Hello Atif!")

say_hello()
Enter fullscreen mode Exit fullscreen mode

Output:

๐Ÿ‘‰ Before the function
Hello Atif!
๐Ÿ‘‰ After the function
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“Œ Here:

  • @simple_decorator is applied to say_hello.
  • When you call say_hello(), Python actually runs wrapper().

๐Ÿ›  Step 2: Decorator for any function with arguments

def log_args(func):
    def wrapper(*args, **kwargs):
        print(f"Function {func.__name__} called with args={args}, kwargs={kwargs}")
        return func(*args, **kwargs)   # run the real function
    return wrapper

@log_args
def add(a, b):
    return a + b

print(add(3, 5))
Enter fullscreen mode Exit fullscreen mode

Output:

Function add called with args=(3, 5), kwargs={}
8
Enter fullscreen mode Exit fullscreen mode

๐Ÿ›  Step 3: Decorator with arguments

Sometimes you want to pass options to your decorator itself.

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(n):
                print(f"Run {i+1} of {n}")
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)   # repeat the function 3 times
def greet(name):
    print(f"Hello {name}")

greet("Atif")
Enter fullscreen mode Exit fullscreen mode

Output:

Run 1 of 3
Hello Atif
Run 2 of 3
Hello Atif
Run 3 of 3
Hello Atif
Enter fullscreen mode Exit fullscreen mode

Decorator in Django

๐Ÿ›  A Django-like decorator

Letโ€™s make our own login_required style decorator:

def my_login_required(func):
    def wrapper(request, *args, **kwargs):
        if not getattr(request, "user", None):   # check if request has a user
            return "โŒ User not logged in!"
        return func(request, *args, **kwargs)
    return wrapper

# fake request objects
class Request:
    def __init__(self, user=None):
        self.user = user

@my_login_required
def dashboard(request):
    return f"Welcome {request.user}!"

print(dashboard(Request()))        # no user
print(dashboard(Request("Atif")))  # with user
Enter fullscreen mode Exit fullscreen mode

Output:

โŒ User not logged in!
Welcome Atif!
Enter fullscreen mode Exit fullscreen mode

๐Ÿ›  Using class decorator (like Django Admin)

def register_model(model_name):
    def decorator(admin_class):
        print(f"โœ… Registered {model_name} with admin class {admin_class.__name__}")
        return admin_class
    return decorator

@register_model("Payment")
class PaymentAdmin:
    pass
Enter fullscreen mode Exit fullscreen mode

Output:

โœ… Registered Payment with admin class PaymentAdmin
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“Œ This is exactly how @admin.register(Model) works internally.


โœ… Summary for Django Decorators:

  • A decorator is a function that wraps another function/class.
  • @decorator_name is just shorthand for function = decorator_name(function).
  • Theyโ€™re useful for authentication checks, logging, caching, registering routes/admins, etc.

decorators in FastAPI.

They work the same as Python decorators, but in FastAPI theyโ€™re often used for middleware-like behavior (before/after running your endpoint).


๐Ÿ›  Example 1: Simple logging decorator

from fastapi import FastAPI

app = FastAPI()

# Custom decorator
def log_request(func):
    async def wrapper(*args, **kwargs):
        print(f"๐Ÿ‘‰ Calling endpoint: {func.__name__}")
        result = await func(*args, **kwargs)
        print(f"โœ… Finished endpoint: {func.__name__}")
        return result
    return wrapper

@app.get("/hello")
@log_request
async def say_hello():
    return {"message": "Hello Atif!"}
Enter fullscreen mode Exit fullscreen mode

When you visit /hello:

๐Ÿ‘‰ Calling endpoint: say_hello
โœ… Finished endpoint: say_hello
Enter fullscreen mode Exit fullscreen mode

๐Ÿ›  Example 2: Decorator to check API Key

from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

def require_api_key(func):
    async def wrapper(request: Request, *args, **kwargs):
        api_key = request.headers.get("X-API-Key")
        if api_key != "secret123":
            raise HTTPException(status_code=403, detail="Invalid API Key")
        return await func(request, *args, **kwargs)
    return wrapper

@app.get("/secure")
@require_api_key
async def secure_endpoint(request: Request):
    return {"message": "You are authorized!"}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”‘ If you call /secure without X-API-Key: secret123, youโ€™ll get:

{"detail": "Invalid API Key"}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ›  Example 3: Decorator with arguments (rate limiter style)

import time
from fastapi import FastAPI, HTTPException

app = FastAPI()

def rate_limit(seconds: int):
    last_called = {}

    def decorator(func):
        async def wrapper(*args, **kwargs):
            now = time.time()
            if func.__name__ in last_called and now - last_called[func.__name__] < seconds:
                raise HTTPException(status_code=429, detail="Too many requests")
            last_called[func.__name__] = now
            return await func(*args, **kwargs)
        return wrapper
    return decorator

@app.get("/ping")
@rate_limit(5)   # limit calls to every 5 seconds
async def ping():
    return {"message": "pong!"}
Enter fullscreen mode Exit fullscreen mode
  • First request works โœ…
  • Second request within 5s โ†’ 429 Too Many Requests

๐Ÿ›  Example 4: Class decorator for routes (like Djangoโ€™s @admin.register)

def tag_routes(tag: str):
    def decorator(func):
        func._tag = tag  # attach metadata
        return func
    return decorator

app = FastAPI()

@app.get("/items")
@tag_routes("inventory")
async def get_items():
    return {"items": ["apple", "banana"]}

# Later you could inspect `get_items._tag` == "inventory"
Enter fullscreen mode Exit fullscreen mode

โœ… Summary for FastAPI decorators

  • Work same as Python decorators
  • Useful for:

    • Logging
    • Auth / API keys
    • Rate limiting
    • Attaching metadata
  • You can mix them with FastAPIโ€™s built-in dependencies, but decorators give more fine-grained control.

Top comments (0)