๐น 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()
Output:
Before function runs
Hello, World!
After function runs
Here:
-
@my_decorator
wraps thehello()
function. - When you call
hello()
, actuallywrapper()
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")
Output:
Hello Atif!
Hello Atif!
Hello Atif!
๐น 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):
...
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):
...
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)
๐น 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"}
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()
Output:
๐ Before the function
Hello Atif!
๐ After the function
๐ Here:
-
@simple_decorator
is applied tosay_hello
. - When you call
say_hello()
, Python actually runswrapper()
.
๐ 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))
Output:
Function add called with args=(3, 5), kwargs={}
8
๐ 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")
Output:
Run 1 of 3
Hello Atif
Run 2 of 3
Hello Atif
Run 3 of 3
Hello Atif
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
Output:
โ User not logged in!
Welcome Atif!
๐ 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
Output:
โ
Registered Payment with admin class PaymentAdmin
๐ 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 forfunction = 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!"}
When you visit /hello
:
๐ Calling endpoint: say_hello
โ
Finished endpoint: say_hello
๐ 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!"}
๐ If you call /secure
without X-API-Key: secret123
, youโll get:
{"detail": "Invalid API Key"}
๐ 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!"}
- 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"
โ 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)