Day 18: Custom Decorators
Learning Objectives
- Understand decorator patterns in Nexios
- Create custom route decorators
- Implement middleware decorators
- Build utility decorators
Basic Decorator Pattern
Creating simple decorators in Nexios:
python
from nexios import NexiosApp
from nexios.http import Request, Response
from functools import wraps
from typing import Callable, Any
import time
app = NexiosApp()
def timing_decorator(func: Callable) -> Callable:
@wraps(func)
async def wrapper(*args, **kwargs) -> Any:
start_time = time.time()
result = await func(*args, **kwargs)
duration = time.time() - start_time
# Add timing header to response
if isinstance(result, Response):
result.headers["X-Response-Time"] = f"{duration:.4f}s"
return result
return wrapper
@app.get("/timed")
@timing_decorator
async def timed_endpoint(request: Request):
return {"message": "This response is timed"}
Route Decorators
Creating decorators for route handling:
python
from nexios.http import Request, Response
from nexios.exceptions import HTTPException
import json
def validate_json(*required_fields: str):
def decorator(func: Callable) -> Callable:
@wraps(func)
async def wrapper(request: Request, *args, **kwargs) -> Any:
try:
data = await request.json
# Check required fields
missing = [field for field in required_fields
if field not in data]
if missing:
return Response(
content={"error": f"Missing fields: {missing}"},
status_code=400
)
# Add validated data to request
request.state.validated_data = data
return await func(request, *args, **kwargs)
except json.JSONDecodeError:
return Response(
content={"error": "Invalid JSON"},
status_code=400
)
return wrapper
return decorator
@app.post("/users")
@validate_json("username", "email")
async def create_user(request: Request):
data = request.state.validated_data
return {"message": "User created", "data": data}
Middleware Decorators
Creating reusable middleware decorators:
python
from nexios.http import Request, Response
from nexios.middleware import Middleware
from typing import Optional
def rate_limit(requests: int, window: int = 60):
def decorator(func: Callable) -> Callable:
# Store request counts
counters = {}
@wraps(func)
async def wrapper(request: Request, *args, **kwargs) -> Any:
# Get client IP
client_ip = request.client.host
now = time.time()
# Clean old entries
counters[client_ip] = [
ts for ts in counters.get(client_ip, [])
if now - ts < window
]
# Check rate limit
if len(counters[client_ip]) >= requests:
return Response(
content={"error": "Rate limit exceeded"},
status_code=429
)
# Add new request timestamp
counters[client_ip].append(now)
return await func(request, *args, **kwargs)
return wrapper
return decorator
@app.get("/limited")
@rate_limit(requests=5, window=60)
async def limited_endpoint(request: Request):
return {"message": "Rate limited endpoint"}
Advanced Patterns
Combining multiple decorators and middleware:
python
from nexios.auth import auth
from nexios.cache import cache
from typing import List
def roles_required(roles: List[str]):
def decorator(func: Callable) -> Callable:
@wraps(func)
async def wrapper(request: Request, *args, **kwargs) -> Any:
user = request.user
if not any(role in user.roles for role in roles):
return Response(
content={"error": "Insufficient permissions"},
status_code=403
)
return await func(request, *args, **kwargs)
return wrapper
return decorator
def cache_response(ttl: int = 300):
def decorator(func: Callable) -> Callable:
@wraps(func)
async def wrapper(request: Request, *args, **kwargs) -> Any:
# Generate cache key
cache_key = f"{request.method}:{request.url.path}"
# Try to get from cache
if cached := await cache.get(cache_key):
return Response(
content=cached,
headers={"X-Cache": "HIT"}
)
# Get fresh response
response = await func(request, *args, **kwargs)
# Cache the response
if isinstance(response, Response):
await cache.set(
cache_key,
response.body,
ttl=ttl
)
return response
return wrapper
return decorator
@app.get("/admin/stats")
@auth(["jwt"])
@roles_required(["admin"])
@cache_response(ttl=60)
async def admin_stats(request: Request):
stats = await generate_stats()
return {"stats": stats}
📝 Practice Exercise
Create utility decorators:
- Request validation
- Response transformation
- Error handling
- Performance monitoring
Build authentication decorators:
- Role-based access control
- API key validation
- Rate limiting
- Token refresh
Implement caching decorators:
- Response caching
- Query result caching
- Cache invalidation
- Cache dependencies
📚 Additional Resources
🎯 Next Steps
Tomorrow in Day 19: Dependency Injection, we'll explore:
- Service containers
- Dependency management
- Lifecycle hooks
- Testing with DI