What is a Decorator in Python, Django and FastAPI?
Muhammad Atif Iqbal

Muhammad Atif Iqbal @atifwattoo

About: AI Engineer with a software engineering background, skilled in Python, TensorFlow, PyTorch, FastAPI, Flask, Django ReactJS, and NextJS. Expert in building scalable AI models and applications.

Location:
Lahore, Pakistan
Joined:
Mar 23, 2024

What is a Decorator in Python, Django and FastAPI?

Publish Date: Sep 4 '25
0 0

🔹 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.

Comments 0 total

    Add comment