Complete Integration
This guide shows how to combine authentication, rate limiting, and metrics in a complete endpoint. These features work together to provide secure, protected, and observable APIs.
Feature Integration Order
When combining features, they execute in a specific order:
graph LR
A[Request] --> B[Authentication]
B --> C[Rate Limiting]
C --> D[Metrics]
D --> E[Handler]
- Authentication - Validates the API key or user token
- Rate Limiting - Checks if the project has exceeded its quota
- Metrics - Records the request for billing
- Handler - Your business logic
Framework Examples
In FastAPI, combine features using the dependencies parameter:
"""Complete FastAPI integration example.
This example shows how to combine authentication, rate limiting, and metrics
in a FastAPI application. Based on real-world usage patterns.
"""
from functools import partial
from typing import Any
from fastapi import APIRouter, Depends, Request, Response
from application_kit.authenticator.types import Products
from application_kit.fastapi.metrics import AddJobToMetrics
from application_kit.fastapi.ratelimit import ProjectRateLimiter
from application_kit.fastapi.security import AuthenticateKey
# Create router for your API endpoints
router = APIRouter(prefix="/api/v1", tags=["locations"])
# Metrics: create a partial function for your service to simplify usage
add_job_to_metrics = partial(AddJobToMetrics, "LOCATIONS")
@router.get(
"/search",
response_class=Response,
dependencies=[
# Authentication validates the API key
Depends(AuthenticateKey(product=Products.STORES)),
# Rate limiting: max 100 requests per period
Depends(ProjectRateLimiter(max_requests=100)),
# Metrics: track this endpoint
Depends(add_job_to_metrics("search")),
],
)
async def search_locations(request: Request, query: str) -> dict[str, Any]:
"""Search for locations.
This endpoint demonstrates the complete integration:
- Authentication validates the API key
- Rate limiting prevents abuse
- Metrics track usage for billing
"""
# Access project reference from the validated token
project_token = request.state.project_token
project_ref = project_token.reference
# Your business logic here
return {
"query": query,
"project_id": project_ref.project_id if project_ref else None,
"results": [],
}
@router.post(
"/locations",
dependencies=[
# Write permission required for POST
Depends(AuthenticateKey(product=Products.STORES, write_permission_needed=True)),
Depends(ProjectRateLimiter(max_requests=50)),
Depends(add_job_to_metrics("create")),
],
)
async def create_location(request: Request) -> dict[str, Any]:
"""Create a new location.
This endpoint requires write permission.
"""
project_token = request.state.project_token
project_ref = project_token.reference
return {
"status": "created",
"project_id": project_ref.project_id if project_ref else None,
}
Key Points:
- Use
dependencies=[]to apply middleware without requiring a return value - Create reusable dependency instances (like
add_job_to_metrics) withfunctools.partial - Access the authenticated project via
request.state.project_token
In Django, stack decorators in the correct order (bottom decorator executes first):
"""Complete Django integration example.
This example shows how to combine authentication, rate limiting, and metrics
in a Django application using decorators.
"""
from typing import Any
from django.http import HttpRequest, JsonResponse
from application_kit.authenticator.types import Products
from application_kit.django.decorators import authenticate_key, count_request
from application_kit.django.ratelimit import rate_limit
from application_kit.django.request import get_project_reference
@authenticate_key(product=Products.STORES)
@rate_limit(max_requests=100)
@count_request("LOCATIONS", "search")
def search_locations(request: HttpRequest) -> JsonResponse:
"""Search for locations.
This endpoint demonstrates the complete integration:
- @authenticate_key validates the API key
- @rate_limit prevents abuse (100 requests per period)
- @count_request tracks usage for billing
Decorators are applied bottom-to-top, so the order is:
1. count_request (always counts, even if rate limited)
2. rate_limit (checks rate limit)
3. authenticate_key (validates API key first)
"""
# Access the project reference using the helper
project_ref = get_project_reference(request)
# Your business logic here
query = request.GET.get("query", "")
results: list[Any] = []
return JsonResponse(
{
"query": query,
"project_id": project_ref.project_id,
"results": results,
}
)
@authenticate_key(product=Products.STORES, write_permission_needed=True)
@rate_limit(max_requests=50)
@count_request("LOCATIONS", "create")
def create_location(request: HttpRequest) -> JsonResponse:
"""Create a new location.
This endpoint requires write permission.
"""
project_ref = get_project_reference(request)
return JsonResponse(
{
"status": "created",
"project_id": project_ref.project_id,
}
)
Key Points:
- Decorators are applied bottom-to-top, so
@authenticate_keyruns first (outermost) - Use
get_project_reference(request)helper to access project IDs safely - The helper raises
PermissionDeniedif called without a valid token
Legacy
Django Ninja support is maintained for existing projects. For new projects, use FastAPI instead.
Django Ninja uses the @chain() and @terminate() pattern with Django decorators:
"""Complete Django Ninja integration example.
This example shows how to combine authentication, rate limiting, and metrics
in a Django Ninja application using the chain/terminate pattern.
"""
from django.http import HttpRequest
from application_kit.django.decorators import authenticate_key, count_request
from application_kit.django.ratelimit import rate_limit
from application_kit.django.request import get_project_reference
from application_kit.shinobi.api import WoosmapApi
from application_kit.shinobi.authentication import PrivateKeyAuth, PublicKeyAuth
from application_kit.shinobi.decorators import chain, terminate
api = WoosmapApi(description="Locations API")
@api.get("/search", auth=[PublicKeyAuth(), PrivateKeyAuth()])
@chain(authenticate_key(), rate_limit(max_requests=100), count_request("LOCATIONS", "search"))
@terminate()
def search_locations(request: HttpRequest, query: str) -> dict[str, object]:
"""Search for locations.
This endpoint demonstrates the complete integration:
- auth=[...] defines accepted authentication schemes
- @chain() applies decorators: authenticate_key -> rate_limit -> count_request
- @terminate() finalizes the decorator chain
"""
project_ref = get_project_reference(request)
return {
"query": query,
"project_id": project_ref.project_id,
"results": [],
}
@api.post("/locations", auth=[PrivateKeyAuth()])
@chain(
authenticate_key(write_permission_needed=True),
rate_limit(max_requests=50),
count_request("LOCATIONS", "create"),
)
@terminate()
def create_location(request: HttpRequest) -> dict[str, object]:
"""Create a new location.
This endpoint requires write permission (private key only).
"""
project_ref = get_project_reference(request)
return {
"status": "created",
"project_id": project_ref.project_id,
}
Key Points:
- Use
auth=[...]to define accepted authentication schemes - Use
@chain()to apply Django decorators in sequence - Always end with
@terminate()to finalize the decorator chain - Access project info via
get_project_reference(request)
Common Patterns
Different Rate Limits for Read vs Write
Apply stricter rate limits to write operations:
"""FastAPI rate limit patterns example.
Shows different rate limits for read vs write operations.
"""
from fastapi import APIRouter, Depends
from application_kit.authenticator.types import Products
from application_kit.fastapi.ratelimit import ProjectRateLimiter
from application_kit.fastapi.security import AuthenticateKey
# Use standard APIRouter - rate limit docs are auto-injected when included in app
router = APIRouter()
# Read endpoint: 100 requests/period
@router.get(
"/items",
dependencies=[
Depends(AuthenticateKey(product=Products.STORES)),
Depends(ProjectRateLimiter(max_requests=100)),
],
)
async def list_items() -> list[str]:
"""List all items."""
return []
# Write endpoint: 10 requests/period with write permission
@router.post(
"/items",
dependencies=[
Depends(AuthenticateKey(product=Products.STORES, write_permission_needed=True)),
Depends(ProjectRateLimiter(max_requests=10)),
],
)
async def create_item() -> dict[str, str]:
"""Create a new item."""
return {"status": "created"}
"""Django rate limit patterns example.
Shows different rate limits for read vs write operations.
"""
from django.http import HttpRequest, JsonResponse
from application_kit.authenticator.types import Products
from application_kit.django.decorators import authenticate_key
from application_kit.django.ratelimit import rate_limit
@authenticate_key(product=Products.STORES)
@rate_limit(max_requests=100)
def list_items(request: HttpRequest) -> JsonResponse:
return JsonResponse({"items": []})
@authenticate_key(product=Products.STORES, write_permission_needed=True)
@rate_limit(max_requests=10)
def create_item(request: HttpRequest) -> JsonResponse:
return JsonResponse({"status": "created"})
"""Django Ninja rate limit patterns example.
Shows different rate limits for read vs write operations.
"""
from django.http import HttpRequest
from application_kit.django.decorators import authenticate_key
from application_kit.django.ratelimit import rate_limit
from application_kit.shinobi.api import WoosmapApi
from application_kit.shinobi.authentication import PrivateKeyAuth, PublicKeyAuth
from application_kit.shinobi.decorators import chain, terminate
api = WoosmapApi(description="Items API")
# Read endpoint: 100 requests/period
@api.get("/items", auth=[PublicKeyAuth(), PrivateKeyAuth()])
@chain(authenticate_key(), rate_limit(max_requests=100))
@terminate()
def list_items(request: HttpRequest) -> list[str]:
return []
# Write endpoint: 10 requests/period with write permission
@api.post("/items", auth=[PrivateKeyAuth()])
@chain(authenticate_key(write_permission_needed=True), rate_limit(max_requests=10))
@terminate()
def create_item(request: HttpRequest) -> dict[str, str]:
return {"status": "created"}
Overriding Configuration in Tests
FastAPI uses the Settings object. Use SettingsOverride to override settings in tests:
"""FastAPI: Disabling rate limiting example.
Shows how to disable rate limiting via settings override in tests.
"""
from application_kit.fastapi.settings import SettingsOverride
# In tests, disable rate limiting:
with SettingsOverride(DISABLE_RATE_LIMIT=True):
# Rate limiting is bypassed for all endpoints
pass
Django uses get_configuration(). Use the test mixin or context manager:
"""Django: Disabling rate limiting in tests.
For Django, use the BenderSettingsOverrideTest mixin in test classes,
or OverrideContextManager for context manager usage.
"""
import unittest
from typing import Any
from application_kit import ApplicationKitConfig, configuration
from application_kit.configuration import OverrideContextManager
from application_kit.test_case import BenderSettingsOverrideTest
# Option 1: Test class mixin
class MyTestCase(BenderSettingsOverrideTest, unittest.TestCase):
"""Test case with configuration overrides."""
def get_overrides(self) -> dict[ApplicationKitConfig, Any]:
"""Return configuration overrides for tests."""
return {
# Add your overrides here
}
def test_something(self) -> None:
# Tests run with overrides applied
pass
# Option 2: Context manager for granular control
def test_with_context_manager() -> None:
"""Use OverrideContextManager for specific test sections."""
with OverrideContextManager(configuration):
# Configuration overrides applied here
pass
Next Steps
- FastAPI Settings - Configure and override settings
- Testing - Test endpoints with mocked authentication
- Troubleshooting - Debug common issues