Files
MoviePilot/app/startup/composition/database.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

305 lines
9.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
数据库治理能力的宿主组合根。
"""
from __future__ import annotations
from dataclasses import dataclass
from app.adapters.system.backup.database import (
PostgreSQLBackupBackend,
SQLiteBackupBackend,
)
from app.adapters.system.backup.files import BackupFiles
from app.application.backup import (
BackupPolicy,
DatabaseBackupBackend,
DatabaseBackupService,
)
from app.application.database import (
DatabaseGovernance,
DatabaseHealthService,
configure_database_governance,
reset_database_governance,
)
from app.application.maintenance import (
DataCleanupService,
read_cleanup_policy,
)
from app.application.plugin.runtime import get_existing_plugin_manager
from app.application.plugin.transaction import (
PluginPersistenceService,
configure_plugin_persistence,
reset_plugin_persistence,
)
from app.application.query import (
DataQueryService,
configure_data_query_service,
reset_data_query_service,
)
from app.application.workflow import (
WorkflowQueryService,
configure_workflow_execution,
configure_workflow_query,
reset_workflow_execution,
reset_workflow_query,
)
from app.db.adapters.pluginidentity import TransactionalPluginIdentityStore
from app.db.adapters.plugininstallation import TransactionalPluginInstallationStore
from app.db.adapters.query import SqlAlchemyDataQueryAdapter
from app.db.adapters.transaction import TransactionalWriteRunner
from app.db.adapters.user import TransactionalUserRepository
from app.db.adapters.workflow import (
TransactionalWorkflowExecutionService,
TransactionalWorkflowQueryRepository,
)
from app.db.engine import get_engine
from app.db.health import probe_database
from app.db.maintenance import DatabaseCleanupRepository
from app.db.oper.systemconfig import SystemConfigOper
from app.db.plugin.registry import database_handles
from app.db.session import (
SessionFactory,
async_session_scope,
)
from app.db.uow import (
configure_transaction_runners,
reset_transaction_runners,
)
from app.db.worker import DatabaseWorker
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
@dataclass(frozen=True, slots=True)
class DatabaseRuntime:
"""
保存数据库 worker 与兼容写入口共享的事务执行器。
:param worker: 在专属线程执行同步数据库短事务的进程级 worker
:param transaction: 提供单次独占会话的写事务执行器
"""
worker: DatabaseWorker
transaction: TransactionalWriteRunner
@dataclass(frozen=True, slots=True)
class DatabaseComposition:
"""
保存初始化器后续装配所需的数据库查询能力。
:param data_query: 已配置数据库访问端口的数据查询服务
:param plugin_persistence: 插件安装和身份持久化应用服务
:param workflow_query: 工作流定义与状态查询服务
"""
data_query: DataQueryService
plugin_persistence: PluginPersistenceService
workflow_query: WorkflowQueryService
_database_runtime: DatabaseRuntime | None = None
_database_runtime_starting = False
async def start_database_runtime() -> DatabaseRuntime:
"""
启动进程唯一数据库 worker,并登记兼容写事务 runner。
"""
global _database_runtime, _database_runtime_starting
if _database_runtime is not None or _database_runtime_starting:
raise RuntimeError("数据库 runtime 已由当前进程持有")
_database_runtime_starting = True
try:
transaction = TransactionalWriteRunner(
sync_session=SessionFactory,
async_session=async_session_scope,
)
worker = DatabaseWorker()
await worker.start()
configure_transaction_runners(
sync=transaction.sync,
async_=transaction.async_,
)
runtime = DatabaseRuntime(worker=worker, transaction=transaction)
_database_runtime = runtime
return runtime
finally:
_database_runtime_starting = False
async def stop_database_runtime() -> None:
"""
关闭数据库 worker;失败时保留 owner 供诊断和重试。
"""
global _database_runtime
runtime = _database_runtime
if runtime is not None:
await runtime.worker.shutdown()
_database_runtime = None
def database_runtime_active() -> bool:
"""
返回数据库 worker 是否仍由当前进程持有。
"""
return _database_runtime is not None
def build_transactional_user_repository() -> TransactionalUserRepository:
"""
构造供 Chain、Agent 与进程级认证共享的短会话用户仓储。
"""
return TransactionalUserRepository(
sync_session=SessionFactory,
async_session=async_session_scope,
)
def compose_database_services(
*,
runtime: DatabaseRuntime,
system_config: SystemConfigOper,
) -> DatabaseComposition:
"""
组合查询、插件持久化与工作流查询所需实现。
"""
data_query_adapter = SqlAlchemyDataQueryAdapter(SessionFactory)
data_query = DataQueryService(
subscriptions=data_query_adapter,
histories=data_query_adapter,
async_executor=runtime.worker,
)
plugin_persistence = PluginPersistenceService(
executor=runtime.worker,
identities=TransactionalPluginIdentityStore(SessionFactory),
installations=TransactionalPluginInstallationStore(
SessionFactory,
system_config.update_atomically,
),
)
workflow_query = WorkflowQueryService(
repository=TransactionalWorkflowQueryRepository(
sync_session=SessionFactory,
async_session=async_session_scope,
)
)
return DatabaseComposition(
data_query=data_query,
plugin_persistence=plugin_persistence,
workflow_query=workflow_query,
)
def publish_database_services(composition: DatabaseComposition) -> None:
"""
一次性发布当前 lifespan 的查询与插件持久化服务。
"""
configure_data_query_service(composition.data_query)
configure_plugin_persistence(composition.plugin_persistence)
configure_workflow_query(composition.workflow_query)
def configure_workflow_execution_composition() -> None:
"""
构造并发布使用短事务会话的工作流执行服务。
"""
configure_workflow_execution(TransactionalWorkflowExecutionService(SessionFactory))
def reset_workflow_execution_composition() -> None:
"""
撤销当前 lifespan 发布的工作流执行服务。
"""
reset_workflow_execution()
def reset_database_services() -> None:
"""
撤销当前 lifespan 发布的全部数据库派生服务。
"""
reset_database_governance()
reset_transaction_runners()
reset_workflow_query()
reset_plugin_persistence()
reset_data_query_service()
def build_database_governance() -> DatabaseGovernance:
"""
以缓存同步引擎为事实源构造一个完整数据库治理门面。
"""
engine = get_engine()
dialect = engine.dialect.name
backup_backend: DatabaseBackupBackend
if dialect == "sqlite":
backup_backend = SQLiteBackupBackend(engine)
elif dialect == "postgresql":
backup_backend = PostgreSQLBackupBackend(engine)
else:
raise RuntimeError(f"不支持的数据库类型:{dialect}")
def backup_plugin_databases() -> None:
"""
为当前已建立的 SQLite 插件库创建独立制品。
"""
if dialect != "sqlite":
return
manager = get_existing_plugin_manager()
if manager is None:
return
for handle in database_handles():
if handle.db_path is None:
continue
version = manager.get_local_plugin_version(handle.plugin_id)
if not version:
logger.warning(
"插件 %s 未找到版本,将使用无版本文件名继续备份数据库",
handle.plugin_id,
)
plugin_service = DatabaseBackupService(
backend=SQLiteBackupBackend(handle.engine),
artifact_store_factory=BackupFiles,
policy_reader=lambda: BackupPolicy(
root=get_runtime_setting("DATABASE_BACKUP_PATH") / "plugins" / handle.plugin_id,
retention_days=get_runtime_setting("DB_BACKUP_RETENTION_DAYS"),
max_count=get_runtime_setting("DB_BACKUP_MAX_COUNT"),
),
target=handle.plugin_id,
version=version,
)
plugin_service.create()
return DatabaseGovernance(
health=DatabaseHealthService(probe_database),
cleanup=DataCleanupService(
repository=DatabaseCleanupRepository(session_factory=SessionFactory),
policy_reader=read_cleanup_policy,
),
backup=DatabaseBackupService(
backend=backup_backend,
artifact_store_factory=BackupFiles,
policy_reader=read_backup_policy,
),
plugin_backup=backup_plugin_databases,
)
def configure_database() -> None:
"""
构造并登记宿主进程唯一的数据库治理门面。
"""
configure_database_governance(build_database_governance())
def read_backup_policy() -> BackupPolicy:
"""
读取一次可热更新的数据库备份目录与保留策略。
"""
return BackupPolicy(
root=get_runtime_setting("DATABASE_BACKUP_PATH"),
retention_days=get_runtime_setting("DB_BACKUP_RETENTION_DAYS"),
max_count=get_runtime_setting("DB_BACKUP_MAX_COUNT"),
)