Files
ai_agent/backend/app/api/versioning.py
T
furyhawk 8351e73d39 feat: add Zustand stores for conversation, file preview, sidebar, theme, and knowledge base selection
- Implemented `conversation-store` for managing conversations and messages.
- Created `file-preview-store` to handle file preview state.
- Added `sidebar-store` for sidebar visibility management.
- Developed `theme-store` for theme persistence and management.
- Introduced `kb-selection-store` for managing active knowledge base selections with persistence.

chore: define API and chat types

- Added types for API responses, authentication, chat messages, conversations, and projects.
- Defined interfaces for various entities including users, sessions, and message ratings.

build: configure TypeScript and testing setup

- Set up `tsconfig.json` for TypeScript configuration.
- Created `vitest.config.ts` for testing configuration with Vitest.
- Added `vitest.setup.ts` for global test setup including mocks for Next.js router and media queries.
- Configured Vercel deployment settings in `vercel.json`.
2026-06-11 16:54:43 +08:00

220 lines
6.9 KiB
Python

"""API versioning utilities and deprecation handling.
This module provides tools for managing API version deprecation:
- Deprecation middleware for entire API versions
- Deprecation decorator for individual endpoints
- RFC 8594 compliant deprecation headers
"""
import logging
from collections.abc import Callable
from datetime import datetime
from functools import wraps
from typing import Any
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.types import ASGIApp
logger = logging.getLogger(__name__)
class VersionDeprecationMiddleware(BaseHTTPMiddleware):
"""Middleware to add deprecation headers for deprecated API versions.
Adds RFC 8594 compliant headers:
- Deprecation: Indicates the version is deprecated
- Sunset: Indicates when the version will be removed
- Link: Points to migration documentation
Usage in main.py:
app.add_middleware(
VersionDeprecationMiddleware,
deprecated_versions={"v1": {"sunset": "2025-06-01", "link": "/docs/migration/v2"}},
)
"""
def __init__(
self,
app: ASGIApp,
deprecated_versions: dict[str, dict[str, str]] | None = None,
) -> None:
"""Initialize the middleware.
Args:
app: The ASGI application.
deprecated_versions: Dict mapping version prefixes to deprecation info.
Each entry should have:
- sunset: ISO date string when version will be removed (optional)
- link: URL to migration documentation (optional)
- message: Custom deprecation message (optional)
Example:
{
"v1": {
"sunset": "2025-06-01",
"link": "https://api.example.com/docs/migration/v2",
"message": "Please migrate to API v2",
}
}
"""
super().__init__(app)
self.deprecated_versions = deprecated_versions or {}
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
"""Process the request and add deprecation headers if needed."""
response = await call_next(request)
# Check if request path matches a deprecated version
path = request.url.path
for version, info in self.deprecated_versions.items():
if f"/api/{version}/" in path or path.endswith(f"/api/{version}"):
self._add_deprecation_headers(response, version, info)
self._log_deprecated_usage(request, version)
break
return response
def _add_deprecation_headers(
self, response: Response, version: str, info: dict[str, str]
) -> None:
"""Add RFC 8594 deprecation headers to the response."""
# Deprecation header - indicates the API is deprecated
response.headers["Deprecation"] = "true"
# Sunset header - when the API will be removed
if sunset := info.get("sunset"):
# Convert to HTTP date format
sunset_date = datetime.fromisoformat(sunset)
response.headers["Sunset"] = sunset_date.strftime("%a, %d %b %Y %H:%M:%S GMT")
# Link header - documentation for migration
if link := info.get("link"):
response.headers["Link"] = f'<{link}>; rel="deprecation"'
# Custom warning header
message = info.get("message", f"API {version} is deprecated")
response.headers["X-API-Deprecation-Warning"] = message
def _log_deprecated_usage(self, request: Request, version: str) -> None:
"""Log usage of deprecated API version for monitoring."""
logger.warning(
"Deprecated API version accessed: %s %s %s",
version,
request.method,
request.url.path,
)
def deprecated(
sunset: str | None = None,
message: str | None = None,
link: str | None = None,
) -> Callable[..., Any]:
"""Decorator to mark an endpoint as deprecated.
Adds deprecation headers to responses from the decorated endpoint.
Use this for deprecating individual endpoints within an active API version.
Args:
sunset: ISO date string when endpoint will be removed.
message: Custom deprecation message.
link: URL to migration documentation.
Usage:
@router.get("/old-endpoint")
@deprecated(
sunset="2025-06-01",
message="Use /new-endpoint instead",
link="/docs/migration",
)
async def old_endpoint():
...
"""
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
# Get the response from the endpoint
result = await func(*args, **kwargs)
# Find Response object in args (FastAPI injects it)
response = None
for arg in args:
if isinstance(arg, Response):
response = arg
break
for value in kwargs.values():
if isinstance(value, Response):
response = value
break
# If we have a Response object, add headers
if response:
response.headers["Deprecation"] = "true"
if sunset:
sunset_date = datetime.fromisoformat(sunset)
response.headers["Sunset"] = sunset_date.strftime("%a, %d %b %Y %H:%M:%S GMT")
if link:
response.headers["Link"] = f'<{link}>; rel="deprecation"'
if message:
response.headers["X-API-Deprecation-Warning"] = message
return result
# Add deprecation info to OpenAPI schema
wrapper.__doc__ = (
f"{func.__doc__ or ''}\n\n"
f"**DEPRECATED**"
f"{f': {message}' if message else ''}"
f"{f' (Sunset: {sunset})' if sunset else ''}"
)
return wrapper
return decorator
# Example usage documentation
"""
## Adding a New API Version
1. Create a new version folder:
```
app/api/routes/v2/
├── __init__.py
├── health.py
├── auth.py
└── ...
```
2. Create the v2 router in `v2/__init__.py`:
```python
from fastapi import APIRouter
v2_router = APIRouter()
# Include routes...
```
3. Add the v2 router in `app/api/router.py`:
```python
from app.api.routes.v2 import v2_router
api_router.include_router(v1_router, prefix="/v1")
api_router.include_router(v2_router, prefix="/v2")
```
4. Mark v1 as deprecated in `main.py`:
```python
app.add_middleware(
VersionDeprecationMiddleware,
deprecated_versions={
"v1": {
"sunset": "2025-12-31",
"link": "/docs/migration/v2",
"message": "Please migrate to API v2",
}
},
)
```
"""