๐น 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)