Add admin panel, WebSocket support, and API versioning
Introduce an admin portal (React + Nginx), WebSocket routing, and API versioning middleware with `/api/v1/` prefix deprecation. Add master API key authentication, new Prometheus metrics for AI token consumption and active WebSockets, and extend S3 config with a public endpoint URL. Update test paths and fixtures to align with the new routing structure.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Resource modules for the Imago SDK."""
|
||||
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
AI endpoints interaction.
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
from httpx import AsyncClient
|
||||
|
||||
from ..utils import with_retry
|
||||
|
||||
class AIResource:
|
||||
"""Resource defining AI generative operations."""
|
||||
def __init__(self, client: AsyncClient):
|
||||
self._client = client
|
||||
|
||||
async def _handle_request(self, method: str, path: str, **kwargs) -> Any:
|
||||
async def _req():
|
||||
resp = await self._client.request(method, path, **kwargs)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
return await with_retry(_req)
|
||||
|
||||
async def summarize_url(self, url: str, language: str = "français") -> Dict[str, Any]:
|
||||
return await self._handle_request("POST", "/api/v1/ai/summarize", json={"url": url, "language": language})
|
||||
|
||||
async def draft_task(self, description: str, context: str = "") -> Dict[str, Any]:
|
||||
return await self._handle_request("POST", "/api/v1/ai/draft-task", json={"description": description, "context": context})
|
||||
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Client API keys management (requires admin scope)
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
from httpx import AsyncClient
|
||||
|
||||
from ..utils import with_retry
|
||||
|
||||
class AuthResource:
|
||||
"""Resource for administering client accounts (admin only)."""
|
||||
def __init__(self, client: AsyncClient):
|
||||
self._client = client
|
||||
|
||||
async def _handle_request(self, method: str, path: str, **kwargs) -> Any:
|
||||
async def _req():
|
||||
resp = await self._client.request(method, path, **kwargs)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
return await with_retry(_req)
|
||||
|
||||
async def create_client(self, name: str, is_premium: bool = False, scopes: list[str] = None) -> Dict[str, Any]:
|
||||
data = {"name": name, "is_premium": is_premium}
|
||||
if scopes:
|
||||
data["scopes"] = scopes
|
||||
return await self._handle_request("POST", "/api/v1/auth/clients", json=data)
|
||||
|
||||
async def list_clients(self) -> list[Dict[str, Any]]:
|
||||
return await self._handle_request("GET", "/api/v1/auth/clients")
|
||||
|
||||
async def rotate_key(self, client_id: str) -> Dict[str, str]:
|
||||
return await self._handle_request("POST", f"/api/v1/auth/clients/{client_id}/rotate-key")
|
||||
|
||||
async def delete_client(self, client_id: str) -> None:
|
||||
await self._handle_request("DELETE", f"/api/v1/auth/clients/{client_id}")
|
||||
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Client module for interacting with Imago API resources.
|
||||
"""
|
||||
from typing import Any, Dict, Optional, List
|
||||
import io
|
||||
|
||||
from httpx import AsyncClient, Response
|
||||
|
||||
from ..models import HubImage
|
||||
from ..websocket import PipelineStream
|
||||
from ..utils import with_retry
|
||||
|
||||
class ImagesResource:
|
||||
"""Resource defining image operations in the Imago API."""
|
||||
def __init__(self, client: AsyncClient):
|
||||
self._client = client
|
||||
self.ws_url = f"{self._client.base_url}"
|
||||
|
||||
async def _handle_request(self, method: str, path: str, **kwargs) -> Any:
|
||||
async def _req():
|
||||
resp = await self._client.request(method, path, **kwargs)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
return await with_retry(_req)
|
||||
|
||||
async def list(self, page: int = 1, size: int = 50, tags: Optional[str] = None) -> Dict[str, Any]:
|
||||
params: dict = {"page": page, "size": size}
|
||||
if tags is not None:
|
||||
params["tags"] = tags
|
||||
return await self._handle_request("GET", "/api/v1/images", params=params)
|
||||
|
||||
async def get(self, image_id: int) -> HubImage:
|
||||
data = await self._handle_request("GET", f"/api/v1/images/{image_id}")
|
||||
return HubImage.from_dict(data)
|
||||
|
||||
async def get_status(self, image_id: int) -> Dict[str, Any]:
|
||||
return await self._handle_request("GET", f"/api/v1/images/{image_id}/status")
|
||||
|
||||
async def upload(self, file_content: bytes, filename: str, content_type: str = "image/jpeg") -> HubImage:
|
||||
files = {"file": (filename, file_content, content_type)}
|
||||
data = await self._handle_request("POST", "/api/v1/images/upload", files=files)
|
||||
return HubImage.from_dict(data)
|
||||
|
||||
async def delete(self, image_id: int) -> Dict[str, Any]:
|
||||
return await self._handle_request("DELETE", f"/api/v1/images/{image_id}")
|
||||
|
||||
async def get_all_tags(self) -> List[str]:
|
||||
return await self._handle_request("GET", "/api/v1/images/tags/all")
|
||||
|
||||
async def reprocess(self, image_id: int) -> Dict[str, Any]:
|
||||
return await self._handle_request("POST", f"/api/v1/images/{image_id}/reprocess")
|
||||
|
||||
async def get_download_url(self, image_id: int) -> str:
|
||||
data = await self._handle_request("GET", f"/api/v1/images/{image_id}/download-url")
|
||||
return data.get("download_url")
|
||||
|
||||
async def get_thumbnail_url(self, image_id: int) -> str:
|
||||
data = await self._handle_request("GET", f"/api/v1/images/{image_id}/thumbnail-url")
|
||||
return data.get("thumbnail_url")
|
||||
|
||||
def stream_pipeline(self, image_id: int, api_key: str) -> PipelineStream:
|
||||
"""Returns a PipelineStream object for tracking the pipeline status in real-time."""
|
||||
return PipelineStream(self.ws_url, api_key, image_id)
|
||||
Reference in New Issue
Block a user