mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 20:17:13 +08:00
refactor: type routed module contracts
This commit is contained in:
@@ -190,6 +190,13 @@ _METHOD_CONTRACTS = {
|
||||
"music_fresh_releases": ModuleMethodContract(family="music", input_contract="MusicFreshReleasesRequest", result_contract="list[MusicInfo]", result_shape=ModuleResultShape.LIST, aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE, required_parameters=("days", "sort", "past", "future", "offset", "count")),
|
||||
"music_lyrics": ModuleMethodContract(family="music", input_contract="MusicLyricsRequest", result_contract="MusicLyrics | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("music",)),
|
||||
"search_music": ModuleMethodContract(family="music", input_contract="MusicSearchRequest", result_contract="list[MusicInfo]", result_shape=ModuleResultShape.LIST, aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE, required_parameters=("meta", "limit", "media_source")),
|
||||
"channel_manage": ModuleMethodContract(family="messaging", input_contract="ChannelManageRequest", result_contract="dict[str, Any] | None", result_shape=ModuleResultShape.MAPPING, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("channel", "action")),
|
||||
"delete_message": ModuleMethodContract(family="messaging", input_contract="MessageDeleteRequest", result_contract="bool | None", result_shape=ModuleResultShape.BOOLEAN, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("channel", "source", "message_id", "chat_id")),
|
||||
"edit_message": ModuleMethodContract(family="messaging", input_contract="MessageEditRequest", result_contract="bool | None", result_shape=ModuleResultShape.BOOLEAN, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("channel", "source", "message_id", "chat_id", "text", "title", "buttons", "metadata")),
|
||||
"mark_message_processing_started": ModuleMethodContract(family="messaging", input_contract="MessageProcessingStartRequest", result_contract="dict[str, Any] | None", result_shape=ModuleResultShape.MAPPING, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("channel", "source", "userid", "message_id", "chat_id", "text")),
|
||||
"mark_message_processing_finished": ModuleMethodContract(family="messaging", input_contract="MessageProcessingFinishRequest", result_contract="bool | None", result_shape=ModuleResultShape.BOOLEAN, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("channel", "source", "userid", "message_id", "chat_id", "status")),
|
||||
"message_parser": ModuleMethodContract(family="messaging", input_contract="MessageParseRequest", result_contract="IncomingMessage | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("source", "body", "form", "args")),
|
||||
"send_direct_message": ModuleMethodContract(family="messaging", input_contract="DirectMessageSendRequest", result_contract="MessageResponse | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("message",)),
|
||||
"send_message": ModuleMethodContract(family="messaging", input_contract="MessageSendRequest", result_contract="Message | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY),
|
||||
"finalize_message": ModuleMethodContract(family="messaging", input_contract="MessageFinalizeRequest", result_contract="Message | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("response",)),
|
||||
"register_commands": ModuleMethodContract(family="messaging", input_contract="CommandRegistrationRequest", result_contract="None", required_parameters=("commands",)),
|
||||
@@ -210,6 +217,7 @@ _METHOD_CONTRACTS = {
|
||||
"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")),
|
||||
"refresh_torrents": ModuleMethodContract(family="downloader", input_contract="TorrentRefreshRequest", result_contract="list[TorrentInfo]", result_shape=ModuleResultShape.LIST, aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE, required_parameters=("site", "keyword", "cat", "page", "mtype")),
|
||||
"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")),
|
||||
@@ -218,6 +226,8 @@ _METHOD_CONTRACTS = {
|
||||
"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")),
|
||||
"tmdb_cache_items": ModuleMethodContract(family="tmdb", input_contract="TmdbCacheListRequest", result_contract="list[dict[str, Any]]", result_shape=ModuleResultShape.LIST, aggregation=ModuleResultAggregation.ORDERED_LIST_MERGE),
|
||||
"tmdb_cache_delete": ModuleMethodContract(family="tmdb", input_contract="TmdbCacheDeleteRequest", result_contract="dict[str, Any] | None", result_shape=ModuleResultShape.MAPPING, aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("cache_key",)),
|
||||
}
|
||||
|
||||
# 同一能力的同步/异步入口共享不可变契约对象,避免参数和聚合语义各自漂移。
|
||||
@@ -276,6 +286,7 @@ _METHOD_CONTRACTS.update({
|
||||
"async_tmdb_tv_similar": _METHOD_CONTRACTS["tmdb_tv_similar"],
|
||||
"async_identify_music_by_fingerprint": _METHOD_CONTRACTS["identify_music_by_fingerprint"],
|
||||
"async_match_music_album": _METHOD_CONTRACTS["match_music_album"],
|
||||
"async_refresh_torrents": _METHOD_CONTRACTS["refresh_torrents"],
|
||||
})
|
||||
|
||||
_PREFIX_CONTRACTS = (
|
||||
|
||||
+105
-53
@@ -3108,16 +3108,22 @@
|
||||
"version": 1
|
||||
},
|
||||
"async_refresh_torrents": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "ordered_list_merge",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "integration",
|
||||
"input_contract": "IntegrationKeywordArguments",
|
||||
"family": "downloader",
|
||||
"input_contract": "TorrentRefreshRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "IntegrationProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"cat",
|
||||
"keyword",
|
||||
"mtype",
|
||||
"page",
|
||||
"site"
|
||||
],
|
||||
"result_contract": "list[TorrentInfo]",
|
||||
"result_shape": "list",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -3722,16 +3728,19 @@
|
||||
"version": 1
|
||||
},
|
||||
"channel_manage": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessagingKeywordArguments",
|
||||
"input_contract": "ChannelManageRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "MessagingProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"action",
|
||||
"channel"
|
||||
],
|
||||
"result_contract": "dict[str, Any] | None",
|
||||
"result_shape": "mapping",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -3791,16 +3800,21 @@
|
||||
"version": 1
|
||||
},
|
||||
"delete_message": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessagingKeywordArguments",
|
||||
"input_contract": "MessageDeleteRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "MessagingProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"channel",
|
||||
"chat_id",
|
||||
"message_id",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "bool | None",
|
||||
"result_shape": "boolean",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -4282,16 +4296,25 @@
|
||||
"version": 1
|
||||
},
|
||||
"edit_message": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessagingKeywordArguments",
|
||||
"input_contract": "MessageEditRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "MessagingProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"buttons",
|
||||
"channel",
|
||||
"chat_id",
|
||||
"message_id",
|
||||
"metadata",
|
||||
"source",
|
||||
"text",
|
||||
"title"
|
||||
],
|
||||
"result_contract": "bool | None",
|
||||
"result_shape": "boolean",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -4500,32 +4523,46 @@
|
||||
"version": 1
|
||||
},
|
||||
"mark_message_processing_finished": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessagingKeywordArguments",
|
||||
"input_contract": "MessageProcessingFinishRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "MessagingProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"channel",
|
||||
"chat_id",
|
||||
"message_id",
|
||||
"source",
|
||||
"status",
|
||||
"userid"
|
||||
],
|
||||
"result_contract": "bool | None",
|
||||
"result_shape": "boolean",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"mark_message_processing_started": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessagingKeywordArguments",
|
||||
"input_contract": "MessageProcessingStartRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "MessagingProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"channel",
|
||||
"chat_id",
|
||||
"message_id",
|
||||
"source",
|
||||
"text",
|
||||
"userid"
|
||||
],
|
||||
"result_contract": "dict[str, Any] | None",
|
||||
"result_shape": "mapping",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -4873,15 +4910,20 @@
|
||||
"version": 1
|
||||
},
|
||||
"message_parser": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessagingKeywordArguments",
|
||||
"input_contract": "MessageParseRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "MessagingProviderResult",
|
||||
"required_parameters": [
|
||||
"args",
|
||||
"body",
|
||||
"form",
|
||||
"source"
|
||||
],
|
||||
"result_contract": "IncomingMessage | None",
|
||||
"result_shape": "any",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
@@ -5307,16 +5349,22 @@
|
||||
"version": 1
|
||||
},
|
||||
"refresh_torrents": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "ordered_list_merge",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "downloader",
|
||||
"input_contract": "DownloaderKeywordArguments",
|
||||
"input_contract": "TorrentRefreshRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "DownloaderProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"cat",
|
||||
"keyword",
|
||||
"mtype",
|
||||
"page",
|
||||
"site"
|
||||
],
|
||||
"result_contract": "list[TorrentInfo]",
|
||||
"result_shape": "list",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
@@ -5568,15 +5616,17 @@
|
||||
"version": 1
|
||||
},
|
||||
"send_direct_message": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "messaging",
|
||||
"input_contract": "MessagingKeywordArguments",
|
||||
"input_contract": "DirectMessageSendRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "MessagingProviderResult",
|
||||
"required_parameters": [
|
||||
"message"
|
||||
],
|
||||
"result_contract": "MessageResponse | None",
|
||||
"result_shape": "any",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
@@ -5733,32 +5783,34 @@
|
||||
"version": 1
|
||||
},
|
||||
"tmdb_cache_delete": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "first_non_empty",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "tmdb",
|
||||
"input_contract": "TmdbKeywordArguments",
|
||||
"input_contract": "TmdbCacheDeleteRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "TmdbProviderResult",
|
||||
"result_shape": "any",
|
||||
"required_parameters": [
|
||||
"cache_key"
|
||||
],
|
||||
"result_contract": "dict[str, Any] | None",
|
||||
"result_shape": "mapping",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
"version": 1
|
||||
},
|
||||
"tmdb_cache_items": {
|
||||
"aggregation": "legacy",
|
||||
"aggregation": "ordered_list_merge",
|
||||
"error_policy": "isolate_provider",
|
||||
"execution": "sync_or_async",
|
||||
"family": "tmdb",
|
||||
"input_contract": "TmdbKeywordArguments",
|
||||
"input_contract": "TmdbCacheListRequest",
|
||||
"plugin_short_circuit": true,
|
||||
"public_to_plugins": true,
|
||||
"required_parameters": [],
|
||||
"result_contract": "TmdbProviderResult",
|
||||
"result_shape": "any",
|
||||
"result_contract": "list[dict[str, Any]]",
|
||||
"result_shape": "list",
|
||||
"supports_async": true,
|
||||
"supports_sync": true,
|
||||
"timeout_policy": "caller_budget",
|
||||
|
||||
@@ -150,6 +150,43 @@ def test_message_attachment_contracts_use_messaging_family() -> None:
|
||||
assert contract.required_parameters == parameters
|
||||
|
||||
|
||||
def test_message_route_contracts_use_first_matching_provider() -> None:
|
||||
"""消息路由能力应保留渠道筛选语义,并停止依赖 legacy 结果接力。"""
|
||||
expected_shapes = {
|
||||
"channel_manage": ModuleResultShape.MAPPING,
|
||||
"delete_message": ModuleResultShape.BOOLEAN,
|
||||
"edit_message": ModuleResultShape.BOOLEAN,
|
||||
"mark_message_processing_started": ModuleResultShape.MAPPING,
|
||||
"mark_message_processing_finished": ModuleResultShape.BOOLEAN,
|
||||
"message_parser": ModuleResultShape.ANY,
|
||||
"send_direct_message": ModuleResultShape.ANY,
|
||||
}
|
||||
|
||||
for method, result_shape in expected_shapes.items():
|
||||
contract = get_module_method_contract(method)
|
||||
assert contract.family == "messaging"
|
||||
assert contract.aggregation is ModuleResultAggregation.FIRST_NON_EMPTY
|
||||
assert contract.result_shape is result_shape
|
||||
assert contract.required_parameters
|
||||
|
||||
|
||||
def test_refresh_and_tmdb_cache_contracts_match_host_result_shapes() -> None:
|
||||
"""刷新种子与 TMDB 缓存入口应声明列表合并或目标值路由语义。"""
|
||||
refresh = get_module_method_contract("refresh_torrents")
|
||||
async_refresh = get_module_method_contract("async_refresh_torrents")
|
||||
cache_items = get_module_method_contract("tmdb_cache_items")
|
||||
cache_delete = get_module_method_contract("tmdb_cache_delete")
|
||||
|
||||
assert refresh is async_refresh
|
||||
assert refresh.family == "downloader"
|
||||
assert refresh.aggregation is ModuleResultAggregation.ORDERED_LIST_MERGE
|
||||
assert refresh.result_shape is ModuleResultShape.LIST
|
||||
assert cache_items.aggregation is ModuleResultAggregation.ORDERED_LIST_MERGE
|
||||
assert cache_items.result_shape is ModuleResultShape.LIST
|
||||
assert cache_delete.aggregation is ModuleResultAggregation.FIRST_NON_EMPTY
|
||||
assert cache_delete.result_shape is ModuleResultShape.MAPPING
|
||||
|
||||
|
||||
def test_downloader_query_contracts_merge_provider_lists() -> None:
|
||||
"""下载器查询能力应显式合并各 provider 的有序列表结果。"""
|
||||
for method in ("list_torrents", "downloader_info"):
|
||||
|
||||
Reference in New Issue
Block a user