Files
MoviePilot/app/db/models/__init__.py
Aqr-K 4dbb8b8204 feat(plugin): 插件配置与实例描述符迁入独立表
插件配置此前寄存在 systemconfig 的 plugin.<实例ID> 单键下,实例描述符则整份挤在
PluginInstances 这一个 JSON 键里,两者都不成立:

- plugin.<ID> 是裸字符串 key,而仓库规则本身禁止用裸字符串做 SystemConfig key,
  只允许先定义 SystemConfigKey 枚举项;插件 ID 由用户安装决定,永远枚举不出常量。
  条目数随安装量增长,混在系统设置表里会把主程序自己的设置项淹掉。
- 那个键存的其实是实例 ID,表里没有任何一列说得出它属于哪个插件,想列出某个插件
  的全部实例配置只能靠字符串前缀去猜。
- 实例描述符整份读出再整份写回,改一个分身要重写全部分身。

改为 plugininstance 一实例一行:instance_id 与 source_plugin_id 构成身份,本体与
分身由两者是否相等派生而不设模式列——模式列会是这个等式的冗余副本,两者一旦失步
同一行就会在不同读取口被判成不同角色。展示信息与业务参数同存一行,属于同一个
生命周期,分表只会让建分身、删分身退化成两张表之间的协调问题。

读写路由落在 SystemConfigOper.get/set/delete,而不是在各个调用方各改一处:第三方
插件可能直接用 self.systemconfig.get("plugin.xxx") 读写自己的配置,只改
PluginConfigStore 与 _PluginBase.get_config/update_config 必然漏掉它们。路由只做
前缀识别,插入、更新与空本体行回收都委托 PluginInstanceOper,不在配置层重抄一遍。

PluginInstances 旧键迁移后不删,留作回滚依据,并以
SystemConfigKey.PluginInstancesImported 标志防止重复导入;判据不能是「表当前为空」,
否则用户把分身全部删光后,下次启动会把它们整批导回来。

迁移链:b2d4f6a8c1e3 -> 281965691a20(3.0.34 建表并搬描述符)
-> c4e1a7b9d2f6(3.0.35 加 config_data 并搬 plugin.* 配置)。

依赖基线与启动模块数随两个新模块重算,架构文档与数据库技能表目录同步登记新表。
2026-09-12 17:39:36 -04:00

94 lines
3.4 KiB
Python

"""ORM 模型的惰性兼容导出与显式注册入口。"""
from importlib import import_module
from typing import Any
from . import _identity # noqa: F401 注册全局媒体身份写入不变量
_MODEL_EXPORTS = {
"AgentChat": ("app.db.models.agentchat", "AgentChat"),
"AgentInvocation": ("app.db.models.agentinvocation", "AgentInvocation"),
"AgentTask": ("app.db.models.agenttask", "AgentTask"),
"AgentTaskRun": ("app.db.models.agenttaskrun", "AgentTaskRun"),
"DownloadFailure": ("app.db.models.downloadfailure", "DownloadFailure"),
"DownloadFiles": ("app.db.models.downloadhistory", "DownloadFiles"),
"DownloadHistory": ("app.db.models.downloadhistory", "DownloadHistory"),
"MediaServerItem": ("app.db.models.mediaserver", "MediaServerItem"),
"Message": ("app.db.models.message", "Message"),
"OutboxMessage": ("app.db.models.outbox", "OutboxMessage"),
"PassKey": ("app.db.models.passkey", "PassKey"),
"PluginData": ("app.db.models.plugindata", "PluginData"),
"PluginInstallation": (
"app.db.models.plugininstallation",
"PluginInstallation",
),
"PluginInstance": (
"app.db.models.plugininstance",
"PluginInstance",
),
"PluginIdentity": (
"app.db.models.pluginidentity",
"PluginIdentity",
),
"Site": ("app.db.models.site", "Site"),
"SiteIcon": ("app.db.models.siteicon", "SiteIcon"),
"SiteStatistic": ("app.db.models.sitestatistic", "SiteStatistic"),
"SiteUserData": ("app.db.models.siteuserdata", "SiteUserData"),
"Subscribe": ("app.db.models.subscribe", "Subscribe"),
"SubscribeHistory": (
"app.db.models.subscribehistory",
"SubscribeHistory",
),
"SubscriptionSearchBatch": (
"app.db.models.subscriptionsearch",
"SubscriptionSearchBatch",
),
"SubscriptionSearchTask": (
"app.db.models.subscriptionsearch",
"SubscriptionSearchTask",
),
"SubscriptionSiteBudget": (
"app.db.models.subscriptionsearch",
"SubscriptionSiteBudget",
),
"SystemConfig": ("app.db.models.systemconfig", "SystemConfig"),
"TransferHistory": ("app.db.models.transferhistory", "TransferHistory"),
"TransferExecutionStep": (
"app.db.models.transferexecutionstep",
"TransferExecutionStep",
),
"TransferSettlementReceipt": (
"app.db.models.transfersettlementreceipt",
"TransferSettlementReceipt",
),
"TransferPending": ("app.db.models.transferpending", "TransferPending"),
"User": ("app.db.models.user", "User"),
"UserConfig": ("app.db.models.userconfig", "UserConfig"),
"Workflow": ("app.db.models.workflow", "Workflow"),
}
def load_all_models() -> None:
"""显式导入全部 ORM 模型,供建表和 Alembic 元数据收集使用。"""
for module_name, _ in dict.fromkeys(_MODEL_EXPORTS.values()):
import_module(module_name)
def __getattr__(name: str) -> Any:
"""按需解析旧模型包级导出,并缓存模型类。"""
contract = _MODEL_EXPORTS.get(name)
if contract is None:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
module_name, symbol_name = contract
value = getattr(import_module(module_name), symbol_name)
globals()[name] = value
return value
def __dir__() -> list[str]:
"""返回模型包的兼容公开面。"""
return sorted({*globals(), *_MODEL_EXPORTS, "load_all_models"})
__all__ = [*_MODEL_EXPORTS, "load_all_models"]