mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
refactor: type downloader action contracts
This commit is contained in:
@@ -120,6 +120,12 @@ _METHOD_CONTRACTS = {
|
||||
"list_torrents": ModuleMethodContract(family="downloader", input_contract="TorrentListRequest", result_contract="list[DownloaderTorrent]", result_shape=ModuleResultShape.LIST, aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE, required_parameters=("status", "hashs", "downloader", "include_all_tags")),
|
||||
"torrent_files": ModuleMethodContract(family="downloader", input_contract="TorrentFilesRequest", result_contract="DownloaderFileCollection | None", required_parameters=("tid", "downloader")),
|
||||
"get_torrent_trackers": ModuleMethodContract(family="downloader", input_contract="TorrentTrackersRequest", result_contract="dict[str, list[str]] | None", result_shape=ModuleResultShape.MAPPING, aggregation=ModuleResultAggregation.ORDERED_MAPPING_MERGE, required_parameters=("hash_string", "downloader")),
|
||||
"download": ModuleMethodContract(family="downloader", input_contract="DownloadTaskRequest", result_contract="DownloadTaskResult | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("content", "download_dir", "cookie", "episodes", "category", "label", "downloader")),
|
||||
"remove_torrents": ModuleMethodContract(family="downloader", input_contract="TorrentRemoveRequest", result_contract="bool | None", result_shape=ModuleResultShape.BOOLEAN, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("hashs", "delete_file", "downloader")),
|
||||
"set_torrents_tag": ModuleMethodContract(family="downloader", input_contract="TorrentTagRequest", result_contract="bool | None", result_shape=ModuleResultShape.BOOLEAN, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("hashs", "tags", "downloader")),
|
||||
"start_torrents": ModuleMethodContract(family="downloader", input_contract="TorrentControlRequest", result_contract="bool | None", result_shape=ModuleResultShape.BOOLEAN, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("hashs", "downloader")),
|
||||
"stop_torrents": ModuleMethodContract(family="downloader", input_contract="TorrentControlRequest", result_contract="bool | None", result_shape=ModuleResultShape.BOOLEAN, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("hashs", "downloader")),
|
||||
"update_torrent": ModuleMethodContract(family="downloader", input_contract="TorrentUpdateRequest", result_contract="dict[str, bool] | None", result_shape=ModuleResultShape.MAPPING, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("hash_string", "downloader", "download_limit", "upload_limit", "tracker_list", "save_path", "category", "ratio_limit", "seeding_time_limit")),
|
||||
}
|
||||
|
||||
_PREFIX_CONTRACTS = (
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
`shield` 不再让网络请求逃逸生命周期预算,仓库级并发合并、缓存键和 V1/V2/V3 返回兼容保持不变。
|
||||
请求作用域的结构化并发不进入全局登记器:传统 WebAgent SSE 的 collection 子任务改由生成器
|
||||
`finally` 取消并等待清理,断线和 ASGI 取消均不会留下请求级 task。
|
||||
2. **动态模块契约仍以 legacy 聚合语义为主。** 当前登记 `212` 个模块方法,其中 `178` 个仍使用 `legacy` aggregation,`27` 个使用 `first_non_empty`、`6` 个使用 `ordered_list_merge`、`1` 个使用 `ordered_mapping_merge`。`app/runtime/extensions/module/contracts.py` 已能登记 family、输入/结果标签和基础签名诊断,调度器也已按这 34 个显式聚合声明执行首个非空、有序列表或有序映射合并;但 `176` 个方法没有 required parameters,其余方法仍主要依赖运行时反射、返回值形状和旧短路规则。未知第三方方法保留 legacy fallback 是兼容要求,不应删除;宿主高频能力则应逐族补齐可执行的输入校验、结果校验、超时和错误语义。
|
||||
2. **动态模块契约仍以 legacy 聚合语义为主。** 当前登记 `212` 个模块方法,其中 `172` 个仍使用 `legacy` aggregation,`33` 个使用 `first_non_empty`、`6` 个使用 `ordered_list_merge`、`1` 个使用 `ordered_mapping_merge`。`app/runtime/extensions/module/contracts.py` 已能登记 family、输入/结果标签和基础签名诊断,调度器也已按这 40 个显式聚合声明执行首个非空、有序列表或有序映射合并;但 `170` 个方法没有 required parameters,其余方法仍主要依赖运行时反射、返回值形状和旧短路规则。未知第三方方法保留 legacy fallback 是兼容要求,不应删除;宿主高频能力则应逐族补齐可执行的输入校验、结果校验、超时和错误语义。
|
||||
3. **Model/Base 的数据库装饰器和隐式会话 ABI 已全部清零。** 查询、写事务和 `legacy_*` 装饰器均为 `0`;所有 Model `db` 参数要求显式 Session,Base CRUD 仅在调用方事务内查询或 stage。可无会话构造的入口统一留在 Oper,经组合根事务执行器运行;插件 SDK 不再导出宿主 Model。后续重点转为减少 ORM 对象跨层流转,并保持 Model 隐式事务零回退。
|
||||
|
||||
Oper 内部的执行入口也已统一:最后一处 `AgentTaskOper` 直接 transaction runner 调用已迁入
|
||||
@@ -801,6 +801,9 @@ ModuleMethodSpec(
|
||||
- `get_torrent_trackers` 新增有序映射聚合:未指定下载器时按宿主优先级合并 qBittorrent、Transmission、
|
||||
rTorrent 的名称到 Tracker 列表映射,不再由首个非空 dict 隐式截断;插件 provider 返回映射后仍按旧 ABI
|
||||
优先短路宿主,未知方法和其他 legacy 映射不受该策略影响。
|
||||
- qBittorrent、Transmission、rTorrent 共享的 `download`、删除、启停、标签与更新 6 个目标选择动作已冻结
|
||||
一致参数和首个非空结果语义;bool/dict 结果启用基础形状诊断,tuple 下载结果保持业务合同标签而不强制
|
||||
Python 形状。广播型 `download_added` / `transfer_completed` 和管道型 `filter_torrents` 继续保留 legacy。
|
||||
|
||||
#### ARCH-241:Event Contract Registry
|
||||
|
||||
|
||||
+61
-29
@@ -3740,15 +3740,23 @@
|
||||
"version": 1
|
||||
},
|
||||
"download": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"input_contract": "DownloadTaskRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"required_parameters": [
|
||||
"category",
|
||||
"content",
|
||||
"cookie",
|
||||
"download_dir",
|
||||
"downloader",
|
||||
"episodes",
|
||||
"label"
|
||||
],
|
||||
"result_contract": "DownloadTaskResult | None",
|
||||
"result_shape": "any",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
@@ -5014,16 +5022,20 @@
|
||||
"version": 1
|
||||
},
|
||||
"remove_torrents": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"input_contract": "TorrentRemoveRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"delete_file",
|
||||
"downloader",
|
||||
"hashs"
|
||||
],
|
||||
"result_contract": "bool | None",
|
||||
"result_shape": "boolean",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -5228,16 +5240,20 @@
|
||||
"version": 1
|
||||
},
|
||||
"set_torrents_tag": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"input_contract": "TorrentTagRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"downloader",
|
||||
"hashs",
|
||||
"tags"
|
||||
],
|
||||
"result_contract": "bool | None",
|
||||
"result_shape": "boolean",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -5282,32 +5298,38 @@
|
||||
"version": 1
|
||||
},
|
||||
"start_torrents": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"input_contract": "TorrentControlRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"downloader",
|
||||
"hashs"
|
||||
],
|
||||
"result_contract": "bool | None",
|
||||
"result_shape": "boolean",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"stop_torrents": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"input_contract": "TorrentControlRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"downloader",
|
||||
"hashs"
|
||||
],
|
||||
"result_contract": "bool | None",
|
||||
"result_shape": "boolean",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -5784,16 +5806,26 @@
|
||||
"version": 1
|
||||
},
|
||||
"update_torrent": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"input_contract": "TorrentUpdateRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"category",
|
||||
"download_limit",
|
||||
"downloader",
|
||||
"hash_string",
|
||||
"ratio_limit",
|
||||
"save_path",
|
||||
"seeding_time_limit",
|
||||
"tracker_list",
|
||||
"upload_limit"
|
||||
],
|
||||
"result_contract": "dict[str, bool] | None",
|
||||
"result_shape": "mapping",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
|
||||
@@ -178,6 +178,42 @@ def test_torrent_tracker_contract_merges_downloader_mappings() -> None:
|
||||
assert contract.result_shape is ModuleResultShape.MAPPING
|
||||
|
||||
|
||||
def test_downloader_action_contracts_freeze_shared_provider_signatures() -> None:
|
||||
"""三种宿主下载器的目标选择动作应使用一致的首个非空契约。"""
|
||||
expected_parameters = {
|
||||
"download": (
|
||||
"content",
|
||||
"download_dir",
|
||||
"cookie",
|
||||
"episodes",
|
||||
"category",
|
||||
"label",
|
||||
"downloader",
|
||||
),
|
||||
"remove_torrents": ("hashs", "delete_file", "downloader"),
|
||||
"set_torrents_tag": ("hashs", "tags", "downloader"),
|
||||
"start_torrents": ("hashs", "downloader"),
|
||||
"stop_torrents": ("hashs", "downloader"),
|
||||
"update_torrent": (
|
||||
"hash_string",
|
||||
"downloader",
|
||||
"download_limit",
|
||||
"upload_limit",
|
||||
"tracker_list",
|
||||
"save_path",
|
||||
"category",
|
||||
"ratio_limit",
|
||||
"seeding_time_limit",
|
||||
),
|
||||
}
|
||||
|
||||
for method, parameters in expected_parameters.items():
|
||||
contract = get_module_method_contract(method)
|
||||
assert contract.family == "downloader"
|
||||
assert contract.aggregation is ModuleResultAggregation.FIRST_NON_EMPTY
|
||||
assert contract.required_parameters == parameters
|
||||
|
||||
|
||||
def test_attachment_result_diagnostics_distinguish_bytes_and_strings() -> None:
|
||||
"""附件契约应区分二进制内容和可展示字符串,偏差仍仅供诊断。"""
|
||||
assert diagnose_module_result("download_qq_file_bytes", b"content") == ()
|
||||
|
||||
Reference in New Issue
Block a user