存档
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
"""data_analytics 插件 API:凭证 CRUD + 分析任务提交/查询/事件流。
|
||||
|
||||
挂载后路径前缀为 /api/v1/plugin/data_analytics/...,跟核心 API 完全独立。
|
||||
所有数据库读写都走 organization actor 的代理方法(确保分布式模式下不跨 actor
|
||||
共享 SQLAlchemy session)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from kilostar.utils.access import Accessor, TokenData
|
||||
from kilostar.utils.ray_hook import ray_actor_hook
|
||||
|
||||
router = APIRouter(tags=["data_analytics"])
|
||||
|
||||
|
||||
# ─── Schemas ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class CredentialCreate(BaseModel):
|
||||
display_name: str = Field(..., max_length=100)
|
||||
endpoint_url: Optional[str] = None
|
||||
region: str = "us-east-1"
|
||||
access_key: str = Field(..., min_length=1)
|
||||
secret_key: str = Field(..., min_length=1)
|
||||
|
||||
|
||||
class JobCreate(BaseModel):
|
||||
cred_id: str
|
||||
description: str = Field(..., min_length=1, max_length=2000)
|
||||
|
||||
|
||||
# ─── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _get_org():
|
||||
try:
|
||||
return ray_actor_hook("org_data_analytics").org_data_analytics
|
||||
except Exception as e:
|
||||
raise HTTPException(503, f"data_analytics 插件未就绪:{e}")
|
||||
|
||||
|
||||
# ─── Credentials ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/credentials")
|
||||
async def list_credentials(
|
||||
token_data: TokenData = Depends(Accessor.get_current_user),
|
||||
):
|
||||
org = _get_org()
|
||||
rows = await org.cred_list.remote(token_data.username)
|
||||
return {"credentials": rows}
|
||||
|
||||
|
||||
@router.post("/credentials")
|
||||
async def create_credential(
|
||||
payload: CredentialCreate,
|
||||
token_data: TokenData = Depends(Accessor.get_current_user),
|
||||
):
|
||||
org = _get_org()
|
||||
row = await org.cred_create.remote(
|
||||
user_id=token_data.username,
|
||||
display_name=payload.display_name,
|
||||
access_key=payload.access_key,
|
||||
secret_key=payload.secret_key,
|
||||
endpoint_url=payload.endpoint_url,
|
||||
region=payload.region,
|
||||
)
|
||||
return row
|
||||
|
||||
|
||||
@router.delete("/credentials/{cred_id}")
|
||||
async def delete_credential(
|
||||
cred_id: str,
|
||||
token_data: TokenData = Depends(Accessor.get_current_user),
|
||||
):
|
||||
org = _get_org()
|
||||
ok = await org.cred_delete.remote(cred_id, token_data.username)
|
||||
if not ok:
|
||||
raise HTTPException(404, "凭证不存在或不属于当前用户")
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
# ─── Jobs ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.post("/jobs")
|
||||
async def create_job(
|
||||
payload: JobCreate,
|
||||
token_data: TokenData = Depends(Accessor.get_current_user),
|
||||
):
|
||||
org = _get_org()
|
||||
try:
|
||||
return await org.job_create.remote(
|
||||
user_id=token_data.username,
|
||||
cred_id=payload.cred_id,
|
||||
description=payload.description,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(400, str(e))
|
||||
|
||||
|
||||
@router.get("/jobs")
|
||||
async def list_jobs(
|
||||
token_data: TokenData = Depends(Accessor.get_current_user),
|
||||
):
|
||||
org = _get_org()
|
||||
rows = await org.job_list.remote(token_data.username)
|
||||
return {"jobs": rows}
|
||||
|
||||
|
||||
@router.get("/jobs/{job_id}")
|
||||
async def get_job(
|
||||
job_id: str,
|
||||
token_data: TokenData = Depends(Accessor.get_current_user),
|
||||
):
|
||||
org = _get_org()
|
||||
row = await org.job_get.remote(job_id, token_data.username)
|
||||
if row is None:
|
||||
raise HTTPException(404, "任务不存在")
|
||||
return row
|
||||
|
||||
|
||||
@router.get("/jobs/{job_id}/stream")
|
||||
async def stream_job(
|
||||
job_id: str,
|
||||
token_data: TokenData = Depends(Accessor.get_current_user),
|
||||
):
|
||||
"""转发 organization 事件流为 SSE。"""
|
||||
import json
|
||||
|
||||
org = _get_org()
|
||||
row = await org.job_get.remote(job_id, token_data.username)
|
||||
if row is None:
|
||||
raise HTTPException(404, "任务不存在")
|
||||
org_task_id = row.get("org_task_id")
|
||||
if not org_task_id:
|
||||
raise HTTPException(409, "任务尚未投递到 organization")
|
||||
|
||||
async def _generate():
|
||||
async for event in await org.stream.remote(org_task_id):
|
||||
payload = event if isinstance(event, str) else json.dumps(event, ensure_ascii=False)
|
||||
yield f"data: {payload}\n\n"
|
||||
|
||||
return StreamingResponse(_generate(), media_type="text/event-stream")
|
||||
Reference in New Issue
Block a user