Skip to content

Decorators

Use decorators to add functionality to your routes without modifying handler logic.

Rate Limiting

Control request frequency per endpoint.

from reroute import RouteBase
from reroute.decorators import rate_limit

class MyRoutes(RouteBase):
    # Global rate limit
    @rate_limit("5/min")
    def post(self):
        return {"created": True}

    # Per-IP rate limit (v0.1.6+)
    @rate_limit("10/hour", per_ip=True)
    def get(self):
        return {"data": "..."}

    # Custom key function
    @rate_limit("100/day", key_func=lambda self: f"user_{self.user_id}")
    def delete(self):
        return {"deleted": True}

Supported periods: sec, min, hour, day

New in v0.1.6: Per-IP rate limiting with automatic IP extraction from request headers.

Caching

Cache responses to improve performance.

from reroute import RouteBase
from reroute.decorators import cache

class MyRoutes(RouteBase):
    @cache(duration=60)  # Cache for 60 seconds
    def get(self):
        return {"data": "expensive_operation_result"}

    # Custom cache key
    @cache(duration=300, key_func=lambda self: f"user_{self.user_id}")
    def get(self):
        return {"user_data": "..."}

Note: Cache is in-memory. For production, consider using Redis or a distributed cache system.

Request Validation

Option 1: @validate Decorator (New in v0.1.6)

Simple validation for basic use cases.

from reroute import RouteBase
from reroute.decorators import validate

class MyRoutes(RouteBase):
    # Type validation
    @validate(schema={"name": str, "age": int, "email": str})
    def post(self, data):
        return {"created": True, "data": data}

    # Required fields
    @validate(required_fields=["email", "password"])
    def post(self, data):
        return {"authenticated": True}

    # Custom validator
    @validate(validator_func=lambda data: (
        ("@" in data.get("email", ""), "Invalid email format")
    ))
    def post(self, data):
        return {"validated": True}

    # Combined validation
    @validate(
        schema={"name": str, "age": int},
        required_fields=["email"],
        validator_func=lambda data: (data["age"] >= 18, "Must be 18+")
    )
    def post(self, data):
        return {"account_created": True}

When to use: - Simple type checking - Required field validation - Quick custom validators

For complex validation with better type safety and IDE support.

from reroute import RouteBase
from reroute.params import Body
from pydantic import BaseModel, field_validator, EmailStr

class UserCreate(BaseModel):
    name: str
    age: int
    email: EmailStr

    @field_validator('age')
    def validate_age(cls, v):
        if v < 18:
            raise ValueError('User must be 18 or older')
        return v

class MyRoutes(RouteBase):
    def post(self, user: UserCreate = Body()):
        """Automatically validates request body"""
        return {"validated": True, "user": user.model_dump()}

Advantages: - Automatic validation and detailed error messages - Type hints and IDE autocomplete - OpenAPI schema generation - Advanced field validators - Nested model support

Request Timeout

Prevent long-running requests from blocking your application.

from reroute import RouteBase
from reroute.decorators import timeout
import asyncio
import time

class MyRoutes(RouteBase):
    # Async handler (recommended - works everywhere)
    @timeout(5)
    async def get(self):
        await asyncio.sleep(10)  # Will timeout
        return {"data": "..."}

    # Sync handler (Unix: true timeout, Windows: best effort)
    @timeout(30)
    def post(self):
        time.sleep(60)  # Will timeout
        return {"processed": True}

Platform Support: - Async handlers: Full timeout support on all platforms (recommended) - Sync handlers on Unix/Linux/Mac: True timeout using signals - Sync handlers on Windows: Returns timeout response but function continues in background

Best Practice

Use async handlers (async def) for guaranteed timeout behavior across all platforms.

Authentication & Authorization

Protect routes with role-based access control.

from reroute import RouteBase
from reroute.decorators import requires

class MyRoutes(RouteBase):
    # Role-based authorization
    @requires("admin")
    def delete(self):
        return {"deleted": True}

    # Multiple roles (OR logic)
    @requires("admin", "moderator")
    def put(self):
        return {"updated": True}

    # Custom authentication check
    @requires(check_func=lambda self: self.is_authenticated())
    def get(self):
        return {"data": "protected"}

Returns: - 401 Unauthorized if authentication fails - 403 Forbidden if user lacks required role

Note: Requires integration with your authentication system.

Request Logging

Automatically log request details.

from reroute import RouteBase
from reroute.decorators import log_requests
import logging

logger = logging.getLogger(__name__)

class MyRoutes(RouteBase):
    # Default logging (prints to console)
    @log_requests()
    def get(self):
        return {"data": "..."}

    # Custom logger
    @log_requests(logger_func=logger.info)
    def post(self):
        return {"created": True}

Combining Decorators

Stack multiple decorators for comprehensive route protection.

from reroute import RouteBase
from reroute.decorators import rate_limit, cache, validate, timeout, requires
from reroute.params import Body
from pydantic import BaseModel

class UserCreate(BaseModel):
    email: str
    name: str
    age: int

class MyRoutes(RouteBase):
    # Multiple decorators on read endpoint
    @rate_limit("10/min", per_ip=True)
    @cache(duration=30)
    @timeout(5)
    async def get(self):
        """Rate-limited, cached, with timeout"""
        return {"data": "..."}

    # Protected write endpoint with validation
    @requires("admin")
    @rate_limit("5/min")
    @validate(required_fields=["email", "name"])
    @timeout(10)
    def post(self, data):
        """Admin-only, rate-limited, validated, with timeout"""
        return {"created": True, "data": data}

    # Best practice: Pydantic validation + decorators
    @rate_limit("10/min", per_ip=True)
    @requires("user")
    @timeout(15)
    async def put(self, user: UserCreate = Body()):
        """Authenticated, rate-limited, fully validated, with timeout"""
        return {"updated": True, "user": user.model_dump()}

Decorator Execution Order:

Decorators are applied from bottom to top (closest to function first):

@cache(60)        # 3. Response cached
@rate_limit("5/min")  # 2. Check rate limit
@timeout(10)      # 1. Start timeout timer
def get(self):
    return {"data": "..."}

Decorator Order Matters

Place @timeout closest to the function for accurate timeout measurement. Place @cache outermost to cache the final response (after rate limits pass).

Best Practices

1. Choose the Right Validation Approach

  • @validate decorator: Quick type checks and required fields
  • Pydantic models: Complex validation, nested data, OpenAPI docs

2. Use Async Handlers When Possible

# ✅ Good: Async with timeout
@timeout(5)
async def get(self):
    await asyncio.sleep(1)
    return {"data": "..."}

# ⚠️ OK on Unix, limited on Windows
@timeout(5)
def get(self):
    time.sleep(1)
    return {"data": "..."}

3. Layer Security Decorators

@requires("admin")        # Authentication first
@rate_limit("5/min")      # Then rate limiting
@validate(schema={...})   # Then validation
@timeout(10)              # Finally timeout
def sensitive_operation(self, data):
    return {"status": "success"}

4. Per-IP Rate Limiting for Public APIs

# ✅ Good: Prevent abuse per client
@rate_limit("100/hour", per_ip=True)
def public_api(self):
    return {"data": "..."}

# ❌ Bad: Single shared limit for all users
@rate_limit("100/hour")
def public_api(self):
    return {"data": "..."}

5. Cache Expensive Operations

@cache(duration=300)  # 5 minutes
async def expensive_query(self):
    # Database query, API call, etc.
    return await self.fetch_from_database()

Learn More