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)