mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-14 01:39:45 +08:00
插件配置此前寄存在 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.* 配置)。
依赖基线与启动模块数随两个新模块重算,架构文档与数据库技能表目录同步登记新表。
94 lines
3.4 KiB
Python
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"]
|