设置页(/settings):
- 新增 Setting DB 模型(键值存储,持久化运行时覆盖)
- config.py 加 _apply_overrides:get_settings() 合并 DB 覆盖值
(_applying_overrides 标志防递归:DB 初始化回调 get_settings 时不重入)
- save_setting() 写 DB + 清 lru_cache,后续任务读到新值
- settings_router.py: GET/PUT /api/settings
- settings_html.py: 设置页表单(batch_size/beam_size/sort_by_length)
+ 设备只读信息 + 保存后 reload 确认
- 验证:改 asr_batch_size=16 beam_size=1 -> 任务 ASR 日志确认生效
- 验证:重启容器后设置从 DB 恢复(持久化)
删除任务:
- task_router.py: DELETE /api/tasks/{id},仅 done/failed 可删
删字幕/音频/视频产物 + UploadSession + Task DB 记录
- home_html.py + history_html.py: done/failed 任务显示删除按钮
+ confirm 确认 + 调用 DELETE API + 刷新列表
- 验证:删除 task 15 成功,删除进行中 task 返回 409
/docs 去认证:
- 移除 require_docs_auth 依赖,/docs /redoc /openapi.json 直接公开
离线模式:
- Dockerfile dev/final 加 HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1
(模型已缓存在 ./models volume,无需联网验证)
- 验证:离线模式完整跑通 ASR+翻译(task 15 done)
94 lines
2.8 KiB
Python
94 lines
2.8 KiB
Python
"""SQLite 引擎、Session、Base、get_db 依赖。
|
||
|
||
自包含:无需外部 MySQL,容器内单文件 SQLite 即可。对齐 server/database.py 的接口形态,
|
||
但用 SQLite(本项目独立运行、无并发写入压力)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
from typing import Generator
|
||
|
||
from sqlalchemy import create_engine
|
||
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
||
|
||
from .config import get_settings
|
||
|
||
_engine = None
|
||
_SessionLocal = None
|
||
|
||
|
||
def _db_path() -> Path:
|
||
"""SQLite 文件落在 work_dir 下,跟数据一起走 volume。"""
|
||
s = get_settings()
|
||
p = s.work_dir() / "audio2text.db"
|
||
p.parent.mkdir(parents=True, exist_ok=True)
|
||
return p
|
||
|
||
|
||
def get_engine():
|
||
global _engine, _SessionLocal
|
||
if _engine is None:
|
||
url = f"sqlite:///{_db_path()}"
|
||
_engine = create_engine(
|
||
url,
|
||
# 后台 reaper / 缓存清理线程与请求线程并发写同一库;busy_timeout 让等待方
|
||
# 在拿锁时阻塞 5s 而非立即报 database is locked。
|
||
connect_args={"check_same_thread": False, "timeout": 30},
|
||
future=True,
|
||
)
|
||
_SessionLocal = sessionmaker(bind=_engine, autoflush=False, expire_on_commit=False)
|
||
return _engine
|
||
|
||
|
||
def get_session_local():
|
||
get_engine()
|
||
return _SessionLocal
|
||
|
||
|
||
class Base(DeclarativeBase):
|
||
pass
|
||
|
||
|
||
def init_db_schema() -> None:
|
||
"""建表(幂等)+ 旧库迁移(给 task 表补新字段)。
|
||
|
||
SQLAlchemy 的 create_all 只建新表不改旧表。对已存在的 task 表,
|
||
需手动 ALTER TABLE ADD COLUMN 补 wav_path / segments_json(nullable)。
|
||
"""
|
||
from .models.task import Task # noqa: F401
|
||
from .models.upload_session import UploadSession # noqa: F401
|
||
from .models.setting import Setting # noqa: F401
|
||
|
||
engine = get_engine()
|
||
Base.metadata.create_all(engine)
|
||
_migrate_task_columns(engine)
|
||
|
||
|
||
def _migrate_task_columns(engine) -> None:
|
||
"""检测 task 表缺失的列并 ALTER TABLE 补上(nullable,向后兼容)。"""
|
||
from sqlalchemy import inspect, text
|
||
|
||
insp = inspect(engine)
|
||
if "task" not in insp.get_table_names():
|
||
return # 新库,create_all 已建好完整表
|
||
existing = {c["name"] for c in insp.get_columns("task")}
|
||
# 新增字段:(列名, 列定义)
|
||
additions = [
|
||
("wav_path", "VARCHAR(1024)"),
|
||
("segments_json", "TEXT"),
|
||
]
|
||
with engine.begin() as conn:
|
||
for col, coltype in additions:
|
||
if col not in existing:
|
||
conn.execute(text(f"ALTER TABLE task ADD COLUMN {col} {coltype}"))
|
||
|
||
|
||
def get_db() -> Generator[Session, None, None]:
|
||
"""FastAPI 依赖:每请求一个 Session,结束自动关闭。"""
|
||
db = get_session_local()()
|
||
try:
|
||
yield db
|
||
finally:
|
||
db.close()
|