完善部分平台治理功能,及修改看板缺陷

This commit is contained in:
wangjiming
2026-08-10 11:41:16 +08:00
parent 75cc105ebc
commit f809825a7d
18 changed files with 1873 additions and 362 deletions

View File

@@ -1,4 +1,4 @@
from __future__ import annotations
from __future__ import annotations
import json
import uuid
@@ -442,16 +442,14 @@ async def dashboard_stats() -> dict[str, Any]:
running_statuses = {"syncing", "queued", "running"}
running_ft = [t for t in tasks if t.get("status") in running_statuses]
failed_ft = [t for t in tasks if t.get("status") == "failed"]
all_ft = tasks # 全部训练任务(含已完成/异常)
online_nodes = [n for n in nodes if n.get("scheduler_status") == "online"]
# 评测中运行的任务
running_eval = [e for e in eval_tasks if e.get("status") in running_statuses]
# 数据处理中运行的任务
# 评测中运行的任务
eval_running = 0
try:
dp_running = int(dp_store.list_tasks(page=1, page_size=1, status="running").get("total", 0))
eval_tasks = store.eval_tasks()
eval_running = len([e for e in eval_tasks if e.get("status") in running_statuses])
except Exception:
dp_running = 0
eval_running = 0
# 近 7 天训练统计(按创建日期分桶)
now = datetime.now(timezone.utc)
@@ -472,45 +470,30 @@ async def dashboard_stats() -> dict[str, Any]:
}
)
# 服务状态 —— 通过对应接口连通性判断是否正常
# 服务状态 —— 每个服务的"实例数"含义:
# 模型训练 → 训练任务总数
# 模型评测 → 评测任务总数
# 模型推理 → 推理/对比任务实例数
# 模型管理 → 基座模型注册总数
# 数据集管理 → 数据集总数
# 数据处理 → 数据处理任务总数
# 数据类型转换 → 数据转换任务总数
service_checks = [
("模型训练", "/fine-tune", "模型训练"),
("模型评测", "/model-eval", "模型评测"),
("模型推理", "/model-inference", "模型推理"),
("模型管理", "/model-manage", "模型管理"),
("数据集管理", "/dataset-manage", "数据集管理"),
("数据处理", "/data-process", "数据处理"),
("数据类型转换", "/data-convert", "数据类型转换"),
("模型训练", "fine-tune", len(tasks)),
("模型评测", "model-eval", len(eval_tasks)),
("模型推理", "model-inference", len(store.compare_tasks())),
("模型管理", "model-manage", len(store.models())),
("数据集管理", "dataset-manage", len(datasets)),
("数据处理", "data-process", dp_count),
("数据类型转换", "data-convert", dp_count),
]
service_status = []
for svc_type, _path, _label in service_checks:
try:
svc_count = 0
if svc_type == "模型训练":
svc_count = len(tasks)
elif svc_type == "模型评测":
svc_count = len(eval_tasks)
elif svc_type == "模型推理":
svc_count = len(online_nodes)
elif svc_type == "模型管理":
svc_count = len(store.models())
elif svc_type == "数据集管理":
svc_count = len(datasets)
elif svc_type == "数据处理":
svc_count = dp_count
elif svc_type == "数据类型转换":
svc_count = dp_count
service_status.append({
"type": svc_type,
"status": "normal",
"count": svc_count,
})
except Exception:
service_status.append({
"type": svc_type,
"status": "error",
"count": 0,
})
for svc_type, _path, svc_count in service_checks:
service_status.append({
"type": svc_type,
"status": "normal",
"count": svc_count,
})
# 训练任务状态归一化
status_map = {
@@ -590,12 +573,16 @@ async def dashboard_stats() -> dict[str, Any]:
]
# 登录时长排行(本月),只取 top 5
login_duration_rank = store.login_duration_rank(limit=5)
login_duration_rank = []
try:
login_duration_rank = store.login_duration_rank(limit=5)
except Exception:
pass
return ok(
{
"online_services": sum(s["count"] for s in service_status),
"running_tasks": len(running_ft) + len(running_eval) + dp_running,
"running_tasks": len(running_ft) + eval_running,
"pending_alerts": 0,
"training_7d": training_7d,
"service_status": service_status,
@@ -656,6 +643,29 @@ async def reset_user_password(
raise fail(400, str(exc))
@router.post("/users/me/password")
async def change_my_password(
payload: dict[str, Any] = Body(...),
current_user: dict = Depends(get_current_user),
) -> dict[str, Any]:
"""用户自行修改密码:验证旧密码后设置新密码。"""
old_password = payload.get("old_password") or ""
new_password = payload.get("new_password") or ""
if not old_password or not new_password:
raise fail(400, "old_password and new_password are required")
if len(new_password) < 6:
raise fail(400, "new password must be at least 6 characters")
try:
success = get_platform_store().change_password(
current_user["id"], old_password, new_password
)
except KeyError:
raise fail(404, "user not found")
if not success:
raise fail(400, "old password is incorrect")
return ok({"changed": True})
@router.get("/model-manage/local-models")
async def local_models() -> dict[str, Any]:
store = get_platform_store()
@@ -685,8 +695,15 @@ async def local_models() -> dict[str, Any]:
@router.get("/model-manage/trained-models")
async def trained_models() -> dict[str, Any]:
return ok({"models": get_platform_store().trained_models()})
async def trained_models(current_user: dict = Depends(get_current_user)) -> dict[str, Any]:
all_models = get_platform_store().trained_models()
if is_admin(current_user):
return ok({"models": all_models})
# 普通用户只能看到自己创建的 + ACL 授权的
user_id = current_user.get("id")
accessible = set(filter_accessible_resource_ids("trained_model", [m["id"] for m in all_models], current_user))
result = [m for m in all_models if m.get("created_by") == user_id or m["id"] in accessible]
return ok({"models": result})
@router.delete("/model-manage/trained-models/{model_id}")
@@ -720,16 +737,13 @@ async def model_by_name(name: str) -> dict[str, Any]:
@router.get("/model-manage")
async def model_list(current_user: dict = Depends(get_current_user)) -> dict[str, Any]:
models = get_platform_store().models()
if current_user.get("role") == "admin" or current_user.get("protected"):
return ok(models)
# 普通用户只返回有 ACL 授权的模型
accessible = set(filter_accessible_resource_ids("model", [m["id"] for m in models], current_user))
return ok([m for m in models if m["id"] in accessible])
# 基座模型是平台共享资源,所有登录用户均可查看
return ok(get_platform_store().models())
@router.post("/model-manage")
async def create_model(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
async def create_model(payload: dict[str, Any] = Body(...), current_user: dict = Depends(get_current_user)) -> dict[str, Any]:
payload.setdefault("created_by", current_user.get("id"))
try:
return ok(get_platform_store().create_model(payload))
except KeyError as exc:
@@ -1047,14 +1061,18 @@ async def download_dataset_file(dataset_id: str, file_id: str, version_id: str |
@router.get("/dataset-manage")
async def dataset_list(current_user: dict = Depends(get_current_user)) -> dict[str, Any]:
datasets = get_platform_store().datasets()
if current_user.get("role") == "admin" or current_user.get("protected"):
if is_admin(current_user):
return ok(datasets)
# 普通用户可见:自己创建的 + ACL 授权的
user_id = current_user.get("id")
accessible = set(filter_accessible_resource_ids("dataset", [d["id"] for d in datasets], current_user))
return ok([d for d in datasets if d["id"] in accessible])
result = [d for d in datasets if d.get("created_by") == user_id or d["id"] in accessible]
return ok(result)
@router.post("/dataset-manage")
async def create_dataset(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
async def create_dataset(payload: dict[str, Any] = Body(...), current_user: dict = Depends(get_current_user)) -> dict[str, Any]:
payload.setdefault("created_by", current_user.get("id"))
dataset = get_platform_store().create_dataset(payload)
return ok({"id": dataset["id"]})
@@ -1127,8 +1145,20 @@ async def create_fine_tune(payload: dict[str, Any] = Body(...)) -> dict[str, Any
@router.post("/fine-tune/start")
async def start_fine_tune(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
async def start_fine_tune(
payload: dict[str, Any] = Body(...),
current_user: dict = Depends(get_current_user),
) -> dict[str, Any]:
store = get_platform_store()
# GPU 权限校验:普通用户只能使用被分配的 GPU
if not is_admin(current_user):
node_id = payload.get("compute_node_id") or payload.get("node_id")
gpu_indices = payload.get("gpus") or []
if node_id and gpu_indices:
if not store.check_gpu_access(current_user["id"], node_id, gpu_indices):
raise fail(403, "无权使用所选 GPU请联系管理员分配")
# 记录创建者
payload.setdefault("created_by", current_user.get("id"))
try:
return ok(await _submit_fine_tune_task(store, payload))
except KeyError:
@@ -1267,10 +1297,14 @@ async def update_fine_tune(task_id: str, payload: dict[str, Any] = Body(...)) ->
@router.post("/fine-tune/stop/{task_id}")
async def stop_fine_tune(task_id: str) -> dict[str, Any]:
async def stop_fine_tune(task_id: str, current_user: dict = Depends(get_current_user)) -> dict[str, Any]:
store = get_platform_store()
try:
task = store.task(task_id)
# 审批拦截:非 admin 停止他人任务需审批
pending = _require_approval_or_admin("fine_tune_task", task_id, current_user, f"停止训练任务 {task_id}")
if pending:
return pending
node = _node_for_task(task)
if task.get("compute_job_id") and node and get_settings().compute_mode != "simulator":
job = await ComputeNodeClient(node["api_base_url"]).stop_job(task["compute_job_id"])
@@ -1281,8 +1315,8 @@ async def stop_fine_tune(task_id: str) -> dict[str, Any]:
@router.post("/fine-tune/{task_id}/stop")
async def stop_fine_tune_alt(task_id: str) -> dict[str, Any]:
return await stop_fine_tune(task_id)
async def stop_fine_tune_alt(task_id: str, current_user: dict = Depends(get_current_user)) -> dict[str, Any]:
return await stop_fine_tune(task_id, current_user)
@router.post("/fine-tune/{task_id}/retry")

View File

@@ -1,4 +1,4 @@
from fastapi import APIRouter
from fastapi import APIRouter
from app.api.v1.endpoints.data_process import router as data_process_router
from app.api.v1.endpoints.platform import router as platform_router
@@ -9,6 +9,7 @@ from app.modules.approval.router import router as approval_router
from app.modules.system.router import router as system_router
from app.modules.retention.router import router as retention_router
from app.modules.resource.router import router as resource_router
from app.modules.gpu.router import router as gpu_router
from app.modules.data_convert.router import router as data_convert_router
api_router = APIRouter()
@@ -21,4 +22,5 @@ api_router.include_router(project_router, tags=["project"])
api_router.include_router(approval_router, tags=["approval"])
api_router.include_router(retention_router, tags=["retention"])
api_router.include_router(resource_router, tags=["resource"])
api_router.include_router(gpu_router, tags=["gpu-assignment"])
api_router.include_router(data_convert_router, tags=["data-convert"])

View File

@@ -525,7 +525,7 @@ class PlatformStore:
},
)
schema_dir = Path(__file__).with_name("sql")
for extra in ("002_governance.sql", "003_tenant_quota.sql"):
for extra in ("002_governance.sql", "003_tenant_quota.sql", "004_permissions.sql"):
extra_path = schema_dir / extra
if extra_path.exists():
conn.executescript(extra_path.read_text(encoding="utf-8"))
@@ -667,8 +667,8 @@ class PlatformStore:
conn.execute(
"""
INSERT INTO trained_models
(id, name, train_methods, base_model_path, create_time, merged, merging, merged_path, artifact_dir, compute_node_id, compute_node_name)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(id, name, train_methods, base_model_path, create_time, merged, merging, merged_path, artifact_dir, compute_node_id, compute_node_name, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
trained_model_id,
@@ -682,6 +682,7 @@ class PlatformStore:
output_dir,
task.get("compute_node_id"),
task.get("compute_node_code") or task.get("compute_node_name"),
task.get("created_by"),
),
)
# Use real artifact data from compute node when available
@@ -1284,10 +1285,22 @@ class PlatformStore:
row = conn.execute("SELECT * FROM users WHERE id=?", (user_id,)).fetchone()
if not row:
raise KeyError(user_id)
is_admin = row["role"] == "admin" or bool(row["protected"])
if "permissions" in payload:
perms = payload["permissions"]
if is_admin:
# 管理员权限不可更改,必须是全部
perms = ALL_PERMISSIONS
else:
# 非 admin 用户不能拥有 user-settings 权限
perms = [p for p in (perms or []) if p != "user-settings"]
payload = {**payload, "permissions": perms}
values = {
"role": payload.get("role", row["role"]),
"status": payload.get("status", row["status"]),
"permissions": json_dumps(payload.get("permissions", json_loads(row["permissions"], []))),
"permissions": json_dumps(
payload.get("permissions", json_loads(row["permissions"], []))
),
}
conn.execute(
"UPDATE users SET role=?, status=?, permissions=? WHERE id=?",
@@ -1302,6 +1315,34 @@ class PlatformStore:
raise KeyError(user_id)
if row["protected"]:
raise ValueError("protected user cannot be deleted")
# 级联删除该用户关联的数据
tables_to_clean = [
# ACL 授权
("acls", "principal_type='user' AND principal_id=?", [user_id]),
# 审批实例(申请人)
("approval_instances", "applicant_id=?", [user_id]),
# 审计日志
("audit_logs", "actor_id=?", [user_id]),
# 项目成员
("project_members", "user_id=?", [user_id]),
# GPU 分配
("gpu_assignments", "user_id=?", [user_id]),
# 数据集
("datasets", "created_by=?", [user_id]),
# 基座模型
("models", "created_by=?", [user_id]),
# 微调产物
("trained_models", "created_by=?", [user_id]),
# 评测任务
("eval_tasks", "created_by=?", [user_id]),
# 对比/推理任务payload 中 creator
# 训练任务:仅标记为已删除或保留(有 compute_job_id 关联),不清物理数据
]
for table_name, where_clause, params in tables_to_clean:
try:
conn.execute(f"DELETE FROM {table_name} WHERE {where_clause}", params)
except Exception:
pass # 表可能不存在或字段不存在,跳过
conn.execute("DELETE FROM users WHERE id=?", (user_id,))
def reset_password(self, user_id: str, new_password: str) -> None:
@@ -1357,8 +1398,8 @@ class PlatformStore:
conn.execute(
"""
INSERT INTO models
(id, name, type, purpose, model_source, description, path, api_url, api_key, online_model_name, can_train, create_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(id, name, type, purpose, model_source, description, path, api_url, api_key, online_model_name, can_train, create_time, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
model_id,
@@ -1373,6 +1414,7 @@ class PlatformStore:
payload.get("online_model_name"),
can_train,
utcnow(),
payload.get("created_by"),
),
)
return dict(conn.execute("SELECT * FROM models WHERE id=?", (model_id,)).fetchone())
@@ -1540,8 +1582,8 @@ class PlatformStore:
conn.execute(
"""
INSERT INTO datasets
(id, name, type, storage_type, source, task_id, size, count, description, create_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(id, name, type, storage_type, source, task_id, size, count, description, create_time, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
dataset_id,
@@ -1554,6 +1596,7 @@ class PlatformStore:
payload.get("count", 0),
payload.get("description"),
utcnow(),
payload.get("created_by"),
),
)
return self._dataset(conn, conn.execute("SELECT * FROM datasets WHERE id=?", (dataset_id,)).fetchone())
@@ -4074,6 +4117,130 @@ class PlatformStore:
"DELETE FROM retention_policies WHERE id=?", (policy_id,)
)
# ===================== 平台治理GPU 算力分配 =====================
def gpu_assignments(self) -> list[dict[str, Any]]:
"""查询全部分配关系。"""
with self.connect() as conn:
rows = conn.execute(
"""
SELECT ga.*, u.username, u.display_name,
n.code AS node_code, n.name AS node_name, g.name AS gpu_name
FROM gpu_assignments ga
LEFT JOIN users u ON u.id = ga.user_id
LEFT JOIN compute_nodes n ON n.id = ga.node_id
LEFT JOIN gpus g ON g.node_id = ga.node_id AND g.gpu_index = ga.gpu_index
ORDER BY ga.assigned_at DESC
"""
).fetchall()
return [dict(r) for r in rows]
def gpu_assignments_for_user(self, user_id: str) -> list[dict[str, Any]]:
"""查询某用户被分配的 GPU 列表。"""
with self.connect() as conn:
rows = conn.execute(
"""
SELECT ga.node_id, ga.gpu_index,
n.code AS node_code, n.name AS node_name,
g.name AS gpu_name, g.uuid, g.memory_total_gb
FROM gpu_assignments ga
JOIN compute_nodes n ON n.id = ga.node_id
LEFT JOIN gpus g ON g.node_id = ga.node_id AND g.gpu_index = ga.gpu_index
WHERE ga.user_id = ?
ORDER BY n.code, ga.gpu_index
""",
(user_id,),
).fetchall()
return [dict(r) for r in rows]
def assign_gpus(self, assignments: list[dict[str, Any]], assigned_by: str | None = None) -> list[dict[str, Any]]:
"""批量分配 GPU幂等已存在的分配跳过"""
now = utcnow()
with self.connect() as conn:
for a in assignments:
node_id = a["node_id"]
gpu_index = a["gpu_index"]
user_id = a["user_id"]
existing = conn.execute(
"SELECT id FROM gpu_assignments WHERE node_id=? AND gpu_index=? AND user_id=?",
(node_id, gpu_index, user_id),
).fetchone()
if existing:
continue
aid = new_id("ga")
conn.execute(
"""
INSERT INTO gpu_assignments (id, node_id, gpu_index, user_id, assigned_by, assigned_at)
VALUES (?, ?, ?, ?, ?, ?)
""",
(aid, node_id, gpu_index, user_id, assigned_by, now),
)
return self.gpu_assignments()
def unassign_gpu(self, assignment_id: str) -> None:
with self.connect() as conn:
conn.execute("DELETE FROM gpu_assignments WHERE id=?", (assignment_id,))
def check_gpu_access(self, user_id: str, node_id: str, gpu_indices: list[int]) -> bool:
"""检查用户是否被分配了指定节点的指定 GPU 卡。"""
if not gpu_indices:
return True
with self.connect() as conn:
rows = conn.execute(
"""
SELECT gpu_index FROM gpu_assignments
WHERE user_id=? AND node_id=?
""",
(user_id, node_id),
).fetchall()
assigned = {r["gpu_index"] for r in rows}
return all(idx in assigned for idx in gpu_indices)
# ===================== 平台治理:资源可见性过滤 =====================
def _filter_accessible_ids(
self, resource_type: str, all_ids: list[str], user: dict[str, Any]
) -> list[str]:
"""从全部资源 ID 中过滤出当前用户可访问的 ID 列表。
- admin 直接返回全部。
- 资源所有者可见(需调用方在 all_ids 中提供 owned ids
- ACL 授权的用户/角色可见。
"""
if user.get("role") == "admin" or user.get("protected"):
return all_ids
if not all_ids:
return []
user_id = user.get("id")
user_role = user.get("role")
with self.connect() as conn:
rows = conn.execute(
"""
SELECT DISTINCT resource_id FROM acls
WHERE resource_type=? AND (
(principal_type='user' AND principal_id=?)
OR (principal_type='role' AND principal_id=?)
)
""",
(resource_type, user_id, user_role),
).fetchall()
accessible = {r["resource_id"] for r in rows}
return [rid for rid in all_ids if rid in accessible]
def change_password(self, user_id: str, old_password: str, new_password: str) -> bool:
"""用户自行修改密码:验证旧密码后设置新密码。"""
with self.connect() as conn:
row = conn.execute("SELECT * FROM users WHERE id=?", (user_id,)).fetchone()
if not row:
raise KeyError(user_id)
matched, _ = verify_password(old_password, row["password_hash"])
if not matched:
return False
conn.execute(
"UPDATE users SET password_hash=? WHERE id=?",
(hash_password(new_password), user_id),
)
return True
_store: PlatformStore | None = None

View File

@@ -0,0 +1,22 @@
-- ============================================================
-- 权限体系扩展GPU 分配表 + 资源所有权字段
-- ============================================================
-- GPU 分配表:管理员指定哪些用户可以使用哪些 GPU 卡
CREATE TABLE IF NOT EXISTS gpu_assignments (
id TEXT PRIMARY KEY,
node_id TEXT NOT NULL REFERENCES compute_nodes(id) ON DELETE CASCADE,
gpu_index INTEGER NOT NULL,
user_id TEXT NOT NULL,
assigned_by TEXT,
assigned_at TEXT NOT NULL,
UNIQUE (node_id, gpu_index, user_id)
);
CREATE INDEX IF NOT EXISTS idx_gpu_assignments_user ON gpu_assignments(user_id);
CREATE INDEX IF NOT EXISTS idx_gpu_assignments_gpu ON gpu_assignments(node_id, gpu_index);
-- 资源所有权字段:用户创建的数据集/模型/训练产物/评测任务
ALTER TABLE datasets ADD COLUMN IF NOT EXISTS created_by TEXT;
ALTER TABLE models ADD COLUMN IF NOT EXISTS created_by TEXT;
ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS created_by TEXT;
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;

View File

@@ -0,0 +1 @@
"""GPU assignment management module."""

View File

@@ -0,0 +1,77 @@
"""GPU 算力分配管理路由。"""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Body, Depends, Request
from app.api.v1.endpoints.platform import ok, fail
from app.core.auth import get_current_user, is_admin
from app.db.platform_store import get_platform_store
router = APIRouter(prefix="/compute", tags=["gpu-assignment"])
def _actor_id(request: Request) -> str | None:
auth = request.headers.get("Authorization", "")
token = auth.replace("Bearer ", "").strip()
if token.startswith("platform-token-"):
return token[len("platform-token-"):]
return None
@router.get("/gpu-assignments")
def list_assignments(current_user: dict = Depends(get_current_user)) -> dict[str, Any]:
"""查看全部分配关系(仅 admin"""
if not is_admin(current_user):
raise fail(403, "admin permission required")
return ok(get_platform_store().gpu_assignments())
@router.post("/gpu-assignments")
def assign_gpus(
payload: dict[str, Any] = Body(...),
request: Request = None,
current_user: dict = Depends(get_current_user),
) -> dict[str, Any]:
"""批量分配 GPU仅 admin。body: { assignments: [{ node_id, gpu_index, user_id }] }"""
if not is_admin(current_user):
raise fail(403, "admin permission required")
assignments = payload.get("assignments") or []
if not assignments:
raise fail(400, "assignments 不能为空")
actor = _actor_id(request) if request else None
result = get_platform_store().assign_gpus(assignments, assigned_by=actor)
get_platform_store().record_audit(
action="gpu.assign",
actor_id=actor,
target_type="gpu",
detail=f"count={len(assignments)}",
)
return ok(result)
@router.delete("/gpu-assignments/{assignment_id}")
def unassign_gpu(
assignment_id: str,
request: Request = None,
current_user: dict = Depends(get_current_user),
) -> dict[str, Any]:
"""撤销 GPU 分配(仅 admin"""
if not is_admin(current_user):
raise fail(403, "admin permission required")
get_platform_store().unassign_gpu(assignment_id)
actor = _actor_id(request) if request else None
get_platform_store().record_audit(
action="gpu.unassign",
actor_id=actor,
target_type="gpu",
target_id=assignment_id,
)
return ok({"deleted": assignment_id})
@router.get("/my-gpus")
def my_gpus(current_user: dict = Depends(get_current_user)) -> dict[str, Any]:
"""查看当前用户可用的 GPU 列表。"""
return ok(get_platform_store().gpu_assignments_for_user(current_user["id"]))

View File

@@ -138,6 +138,14 @@ class FakePlatformStore:
return dict(u)
return None
def create_session(self, user_id: str) -> dict[str, Any]:
import secrets
sid = secrets.token_hex(16)
return {"session_id": sid, "user_id": user_id}
def finish_session(self, session_id: str) -> None:
pass
def users(self) -> list[dict[str, Any]]:
return [dict(u) for u in self._users]
@@ -392,12 +400,21 @@ class FakePlatformStore:
def tasks(self) -> list[dict[str, Any]]:
return self._tasks
def eval_tasks(self) -> list[dict[str, Any]]:
return []
def compute_nodes(self) -> list[dict[str, Any]]:
return self._compute_nodes
def gpus(self) -> list[dict[str, Any]]:
return self._gpus
def compare_tasks(self) -> list[dict[str, Any]]:
return []
def trained_models(self) -> list[dict[str, Any]]:
return []
def system_info(self) -> dict[str, Any]:
return {"cpu": {}, "memory": {}}