mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 20:17:13 +08:00
refactor: type high-value module contracts
This commit is contained in:
@@ -25,6 +25,7 @@ class ModuleResultShape(StrEnum):
|
||||
STRING = "string"
|
||||
MAPPING = "mapping"
|
||||
BOOLEAN = "boolean"
|
||||
BYTES = "bytes"
|
||||
|
||||
|
||||
class ModuleExecutionMode(StrEnum):
|
||||
@@ -101,6 +102,22 @@ _METHOD_CONTRACTS = {
|
||||
"register_commands": ModuleMethodContract(family="messaging", input_contract="CommandRegistrationRequest", result_contract="None", required_parameters=("commands",)),
|
||||
"scheduler_job": ModuleMethodContract(family="scheduling", input_contract="SchedulerJobRequest", result_contract="None"),
|
||||
"webhook_parser": ModuleMethodContract(family="integration", input_contract="WebhookRequest", result_contract="WebhookEventInfo | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("body", "form", "args")),
|
||||
"download_discord_file_bytes": ModuleMethodContract(family="messaging", input_contract="MessageFileDownloadRequest", result_contract="bytes | None", result_shape=ModuleResultShape.BYTES, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("file_ref", "source")),
|
||||
"download_feishu_file_bytes": ModuleMethodContract(family="messaging", input_contract="MessageFileDownloadRequest", result_contract="bytes | None", result_shape=ModuleResultShape.BYTES, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("file_ref", "source")),
|
||||
"download_feishu_image_to_data_url": ModuleMethodContract(family="messaging", input_contract="MessageImageDownloadRequest", result_contract="str | None", result_shape=ModuleResultShape.STRING, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("image_ref", "source")),
|
||||
"download_qq_file_bytes": ModuleMethodContract(family="messaging", input_contract="MessageFileDownloadRequest", result_contract="bytes | None", result_shape=ModuleResultShape.BYTES, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("file_ref", "source")),
|
||||
"download_slack_file_bytes": ModuleMethodContract(family="messaging", input_contract="MessageFileDownloadRequest", result_contract="bytes | None", result_shape=ModuleResultShape.BYTES, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("file_ref", "source")),
|
||||
"download_slack_file_to_data_url": ModuleMethodContract(family="messaging", input_contract="MessageFileDownloadRequest", result_contract="str | None", result_shape=ModuleResultShape.STRING, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("file_url", "source")),
|
||||
"download_synologychat_file_bytes": ModuleMethodContract(family="messaging", input_contract="MessageFileDownloadRequest", result_contract="bytes | None", result_shape=ModuleResultShape.BYTES, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("file_ref", "source")),
|
||||
"download_telegram_file_bytes": ModuleMethodContract(family="messaging", input_contract="MessageFileDownloadRequest", result_contract="bytes | None", result_shape=ModuleResultShape.BYTES, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("file_id", "source")),
|
||||
"download_telegram_file_to_base64": ModuleMethodContract(family="messaging", input_contract="MessageFileDownloadRequest", result_contract="str | None", result_shape=ModuleResultShape.STRING, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("file_id", "source")),
|
||||
"download_vocechat_file_bytes": ModuleMethodContract(family="messaging", input_contract="MessageFileDownloadRequest", result_contract="bytes | None", result_shape=ModuleResultShape.BYTES, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("file_ref", "source")),
|
||||
"download_vocechat_image_to_data_url": ModuleMethodContract(family="messaging", input_contract="MessageImageDownloadRequest", result_contract="str | None", result_shape=ModuleResultShape.STRING, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("image_ref", "source")),
|
||||
"download_wechat_image_to_data_url": ModuleMethodContract(family="messaging", input_contract="MessageImageDownloadRequest", result_contract="str | None", result_shape=ModuleResultShape.STRING, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("image_ref", "source")),
|
||||
"download_wechat_media_bytes": ModuleMethodContract(family="messaging", input_contract="MessageMediaDownloadRequest", result_contract="bytes | None", result_shape=ModuleResultShape.BYTES, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("media_ref", "source")),
|
||||
"downloader_info": ModuleMethodContract(family="downloader", input_contract="DownloaderInfoRequest", result_contract="list[DownloaderInfo]", result_shape=ModuleResultShape.LIST, aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE, required_parameters=("downloader",)),
|
||||
"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")),
|
||||
}
|
||||
|
||||
_PREFIX_CONTRACTS = (
|
||||
@@ -476,6 +493,7 @@ def diagnose_module_result(method: str, result: Any) -> tuple[str, ...]:
|
||||
ModuleResultShape.STRING: isinstance(result, str),
|
||||
ModuleResultShape.MAPPING: isinstance(result, dict),
|
||||
ModuleResultShape.BOOLEAN: isinstance(result, bool),
|
||||
ModuleResultShape.BYTES: isinstance(result, bytes),
|
||||
}
|
||||
if matches.get(shape, True):
|
||||
return ()
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
`shield` 不再让网络请求逃逸生命周期预算,仓库级并发合并、缓存键和 V1/V2/V3 返回兼容保持不变。
|
||||
请求作用域的结构化并发不进入全局登记器:传统 WebAgent SSE 的 collection 子任务改由生成器
|
||||
`finally` 取消并等待清理,断线和 ASGI 取消均不会留下请求级 task。
|
||||
2. **动态模块契约仍以 legacy 聚合语义为主。** 当前登记 `212` 个模块方法,其中 `194` 个仍使用 `legacy` aggregation,只有 `14` 个 `first_non_empty`、`4` 个 `ordered_list_merge`。`app/runtime/extensions/module/contracts.py` 已能登记 family、输入/结果标签和基础签名诊断,调度器也已按这 18 个显式聚合声明执行首个非空或有序列表合并;但 `193` 个方法没有 required parameters,其余方法仍主要依赖运行时反射、返回值形状和旧短路规则。未知第三方方法保留 legacy fallback 是兼容要求,不应删除;宿主高频能力则应逐族补齐可执行的输入校验、结果校验、超时和错误语义。
|
||||
2. **动态模块契约仍以 legacy 聚合语义为主。** 当前登记 `212` 个模块方法,其中 `179` 个仍使用 `legacy` aggregation,`27` 个使用 `first_non_empty`、`6` 个使用 `ordered_list_merge`。`app/runtime/extensions/module/contracts.py` 已能登记 family、输入/结果标签和基础签名诊断,调度器也已按这 33 个显式聚合声明执行首个非空或有序列表合并;但 `177` 个方法没有 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 调用已迁入
|
||||
@@ -794,6 +794,10 @@ ModuleMethodSpec(
|
||||
provider 决策函数,避免契约字段只存在于快照而运行时仍执行另一套隐式算法。未登记方法和仍声明
|
||||
`legacy` 的宿主/插件能力继续保留原签名接力、列表合并、异常隔离和短路行为;旧 provider 的签名或
|
||||
结果偏差仍只诊断,不拒绝插件加载和执行。
|
||||
- 消息附件下载的 13 个宿主能力不再因 `download_*` 名称误归到下载器族,已按真实 `file/image/media`
|
||||
参数、bytes/string 结果和首个非空语义登记;`list_torrents`、`downloader_info` 按三个下载器宿主实现
|
||||
冻结为有序列表合并。`torrent_files` 因 qBittorrent 的 `TorrentFilesList` 与其他下载器普通列表并存,
|
||||
只补真实参数与异构结果合同,继续显式保留 legacy 聚合,不能用错误的列表标签掩盖待归一化边界。
|
||||
|
||||
#### ARCH-241:Event Contract Registry
|
||||
|
||||
|
||||
+140
-91
@@ -3772,48 +3772,57 @@
|
||||
"version": 1
|
||||
},
|
||||
"download_discord_file_bytes": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageFileDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"file_ref",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "bytes | None",
|
||||
"result_shape": "bytes",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"download_feishu_file_bytes": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageFileDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"file_ref",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "bytes | None",
|
||||
"result_shape": "bytes",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"download_feishu_image_to_data_url": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageImageDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"image_ref",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "str | None",
|
||||
"result_shape": "string",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -3839,176 +3848,208 @@
|
||||
"version": 1
|
||||
},
|
||||
"download_qq_file_bytes": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageFileDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"file_ref",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "bytes | None",
|
||||
"result_shape": "bytes",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"download_slack_file_bytes": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageFileDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"file_ref",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "bytes | None",
|
||||
"result_shape": "bytes",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"download_slack_file_to_data_url": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageFileDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"file_url",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "str | None",
|
||||
"result_shape": "string",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"download_synologychat_file_bytes": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageFileDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"file_ref",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "bytes | None",
|
||||
"result_shape": "bytes",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"download_telegram_file_bytes": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageFileDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"file_id",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "bytes | None",
|
||||
"result_shape": "bytes",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"download_telegram_file_to_base64": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageFileDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"file_id",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "str | None",
|
||||
"result_shape": "string",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"download_vocechat_file_bytes": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageFileDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"file_ref",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "bytes | None",
|
||||
"result_shape": "bytes",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"download_vocechat_image_to_data_url": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageImageDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"image_ref",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "str | None",
|
||||
"result_shape": "string",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"download_wechat_image_to_data_url": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageImageDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"image_ref",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "str | None",
|
||||
"result_shape": "string",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"download_wechat_media_bytes": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessageMediaDownloadRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"media_ref",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "bytes | None",
|
||||
"result_shape": "bytes",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"downloader_info": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "ordered_list_merge",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"input_contract": "DownloaderInfoRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"downloader"
|
||||
],
|
||||
"result_contract": "list[DownloaderInfo]",
|
||||
"result_shape": "list",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -4188,16 +4229,21 @@
|
||||
"version": 1
|
||||
},
|
||||
"list_torrents": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "ordered_list_merge",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"input_contract": "TorrentListRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"downloader",
|
||||
"hashs",
|
||||
"include_all_tags",
|
||||
"status"
|
||||
],
|
||||
"result_contract": "list[DownloaderTorrent]",
|
||||
"result_shape": "list",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -5576,11 +5622,14 @@
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"input_contract": "TorrentFilesRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"required_parameters": [
|
||||
"downloader",
|
||||
"tid"
|
||||
],
|
||||
"result_contract": "DownloaderFileCollection | None",
|
||||
"result_shape": "any",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
|
||||
@@ -134,6 +134,53 @@ def test_result_diagnostics_check_only_enabled_basic_shapes() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_message_attachment_contracts_use_messaging_family() -> None:
|
||||
"""消息附件下载能力不得因 download 前缀误归入下载器能力族。"""
|
||||
expected_parameters = {
|
||||
"download_telegram_file_bytes": ("file_id", "source"),
|
||||
"download_wechat_media_bytes": ("media_ref", "source"),
|
||||
"download_slack_file_to_data_url": ("file_url", "source"),
|
||||
"download_feishu_image_to_data_url": ("image_ref", "source"),
|
||||
}
|
||||
|
||||
for method, parameters in expected_parameters.items():
|
||||
contract = get_module_method_contract(method)
|
||||
assert contract.family == "messaging"
|
||||
assert contract.aggregation is ModuleResultAggregation.FIRST_NON_EMPTY
|
||||
assert contract.required_parameters == parameters
|
||||
|
||||
|
||||
def test_downloader_query_contracts_merge_provider_lists() -> None:
|
||||
"""下载器查询能力应显式合并各 provider 的有序列表结果。"""
|
||||
for method in ("list_torrents", "downloader_info"):
|
||||
contract = get_module_method_contract(method)
|
||||
assert contract.family == "downloader"
|
||||
assert contract.aggregation is ModuleResultAggregation.ORDERED_LIST_MERGE
|
||||
assert contract.result_shape is ModuleResultShape.LIST
|
||||
|
||||
|
||||
def test_heterogeneous_torrent_files_result_remains_legacy_compatible() -> None:
|
||||
"""下载器文件集合尚未归一前不得声明虚假的列表聚合语义。"""
|
||||
contract = get_module_method_contract("torrent_files")
|
||||
|
||||
assert contract.required_parameters == ("tid", "downloader")
|
||||
assert contract.result_contract == "DownloaderFileCollection | None"
|
||||
assert contract.aggregation is ModuleResultAggregation.LEGACY
|
||||
assert contract.result_shape is ModuleResultShape.ANY
|
||||
|
||||
|
||||
def test_attachment_result_diagnostics_distinguish_bytes_and_strings() -> None:
|
||||
"""附件契约应区分二进制内容和可展示字符串,偏差仍仅供诊断。"""
|
||||
assert diagnose_module_result("download_qq_file_bytes", b"content") == ()
|
||||
assert diagnose_module_result("download_qq_file_bytes", "content") == (
|
||||
"unexpected-result:bytes:str",
|
||||
)
|
||||
assert diagnose_module_result(
|
||||
"download_wechat_image_to_data_url",
|
||||
"data:image/png;base64,AA==",
|
||||
) == ()
|
||||
|
||||
|
||||
def test_unknown_plugin_result_keeps_unchecked_legacy_compatibility() -> None:
|
||||
"""未知第三方方法的任意返回值继续不做结果形状诊断。"""
|
||||
assert diagnose_module_result("third_party_custom_method", object()) == ()
|
||||
|
||||
Reference in New Issue
Block a user