Files
Seg_Data_Server_Net/backend/app/db.py

173 lines
5.1 KiB
Python

from __future__ import annotations
import json
import sqlite3
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from .config import settings
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat()
def connect() -> sqlite3.Connection:
settings.db_path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(settings.db_path)
conn.row_factory = sqlite3.Row
return conn
def init_db() -> None:
with connect() as conn:
conn.execute(
"""
create table if not exists jobs (
id text primary key,
type text not null,
status text not null,
params_json text not null,
command_json text not null,
cwd text not null,
description text not null default '',
pid integer,
exit_code integer,
created_at text not null,
started_at text,
finished_at text,
log_path text not null,
error text
)
"""
)
conn.execute(
"""
create table if not exists profiles (
id integer primary key autoincrement,
name text not null,
kind text not null,
data_json text not null,
updated_at text not null,
unique(name, kind)
)
"""
)
def _job_from_row(row: sqlite3.Row) -> dict[str, Any]:
data = dict(row)
data["params"] = json.loads(data.pop("params_json"))
data["command"] = json.loads(data.pop("command_json"))
return data
def insert_job(job: dict[str, Any]) -> None:
with connect() as conn:
conn.execute(
"""
insert into jobs (
id, type, status, params_json, command_json, cwd, description,
pid, exit_code, created_at, started_at, finished_at, log_path, error
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
job["id"],
job["type"],
job["status"],
json.dumps(job["params"], ensure_ascii=False),
json.dumps(job["command"], ensure_ascii=False),
job["cwd"],
job.get("description", ""),
job.get("pid"),
job.get("exit_code"),
job["created_at"],
job.get("started_at"),
job.get("finished_at"),
job["log_path"],
job.get("error"),
),
)
def update_job(job_id: str, **fields: Any) -> None:
if not fields:
return
allowed = {
"status",
"pid",
"exit_code",
"started_at",
"finished_at",
"error",
}
updates = {key: value for key, value in fields.items() if key in allowed}
if not updates:
return
assignments = ", ".join(f"{key}=?" for key in updates)
with connect() as conn:
conn.execute(f"update jobs set {assignments} where id=?", [*updates.values(), job_id])
def get_job(job_id: str) -> dict[str, Any] | None:
with connect() as conn:
row = conn.execute("select * from jobs where id=?", (job_id,)).fetchone()
return _job_from_row(row) if row else None
def list_jobs(limit: int = 100) -> list[dict[str, Any]]:
with connect() as conn:
rows = conn.execute(
"select * from jobs order by created_at desc limit ?", (limit,)
).fetchall()
return [_job_from_row(row) for row in rows]
def upsert_profile(name: str, kind: str, data: dict[str, Any]) -> dict[str, Any]:
updated_at = utc_now()
with connect() as conn:
conn.execute(
"""
insert into profiles (name, kind, data_json, updated_at)
values (?, ?, ?, ?)
on conflict(name, kind) do update set
data_json=excluded.data_json,
updated_at=excluded.updated_at
""",
(name, kind, json.dumps(data, ensure_ascii=False), updated_at),
)
return {"name": name, "kind": kind, "data": data, "updated_at": updated_at}
def list_profiles(kind: str | None = None) -> list[dict[str, Any]]:
sql = "select name, kind, data_json, updated_at from profiles"
params: tuple = ()
if kind:
sql += " where kind=?"
params = (kind,)
sql += " order by kind, name"
with connect() as conn:
rows = conn.execute(sql, params).fetchall()
return [
{
"name": row["name"],
"kind": row["kind"],
"data": json.loads(row["data_json"]),
"updated_at": row["updated_at"],
}
for row in rows
]
def log_tail(log_path: str | Path, max_bytes: int = 8192) -> str:
path = Path(log_path)
if not path.exists():
return ""
size = path.stat().st_size
with path.open("rb") as handle:
if size > max_bytes:
handle.seek(size - max_bytes)
return handle.read().decode("utf-8", errors="replace")