mirror of
https://github.com/furyhawk/ai_agent.git
synced 2026-07-21 10:15:44 +00:00
- 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`.
220 lines
6.9 KiB
Python
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",
|
|
}
|
|
},
|
|
)
|
|
```
|
|
"""
|