From 4b1d4dd5b3fc11fdc1dd9d5aff7425547c6bf18e Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sat, 29 Aug 2026 22:07:38 +0800 Subject: [PATCH] test(architecture): lower parity type debt --- app/chain/search/facade.py | 54 +++++++++++++++++-- app/chain/subscribe/facade.py | 11 ++-- docs/architecture-optimization-checklist.md | 2 +- .../architecture/dependency-baseline.json | 10 +++- .../fixtures/architecture/mypy-baseline.json | 10 ++-- 5 files changed, 70 insertions(+), 17 deletions(-) diff --git a/app/chain/search/facade.py b/app/chain/search/facade.py index 836af4d85..d9ecac87e 100644 --- a/app/chain/search/facade.py +++ b/app/chain/search/facade.py @@ -1,7 +1,7 @@ """搜索处理链稳定 Facade。""" from collections.abc import Callable -from typing import Any, TypeVar, cast +from typing import Any, Optional, TypeVar, cast from app.chain.base import ChainBase from app.chain.search.cache import SearchCacheOwner @@ -15,8 +15,10 @@ from app.chain.search.result import SearchResultOwner from app.chain.search.site import SearchSiteOwner from app.chain.search.subtitle import SearchSubtitleOwner from app.chain.search.title import SearchTitleOwner +from app.domain.context import Context, MediaInfo from app.runtime.events import Event, eventmanager -from app.schemas.types import EventType +from app.schemas.mediaserver import NotExistMediaInfo +from app.schemas.types import EventType, MediaType _PUBLIC_MODULE = "app.chain.search" _Handler = TypeVar("_Handler", bound=Callable[..., Any]) @@ -78,7 +80,27 @@ class SearchChain(ChainBase): _invoke_recommend_llm = staticmethod(SearchRecommendOwner._invoke_recommend_llm) start_recommend_task = SearchRecommendOwner.start_recommend_task search_by_id = SearchMediaOwner.search_by_id - search_by_title = SearchTitleOwner.search_by_title + + def search_by_title( + self, + title: str, + page: Optional[int] = 0, + sites: Optional[list[int]] = None, + cache_local: Optional[bool] = False, + mtype: Optional[MediaType] = None, + rule_groups: Optional[list[str]] = None, + ) -> list[Context]: + """通过稳定 Facade 调用标题搜索 owner,保留精确的公开类型合同。""" + return SearchTitleOwner.search_by_title( + cast(SearchTitleOwner, self), + title=title, + page=page, + sites=sites, + cache_local=cache_local, + mtype=mtype, + rule_groups=rule_groups, + ) + last_search_results = SearchCacheOwner.last_search_results async_last_search_results = SearchCacheOwner.async_last_search_results async_last_subtitle_search_results = SearchCacheOwner.async_last_subtitle_search_results @@ -101,7 +123,31 @@ class SearchChain(ChainBase): _process_music = SearchMusicOwner._process_music _async_process_music = SearchMusicOwner._async_process_music _async_process_music_stream = SearchMusicOwner._async_process_music_stream - process = SearchMediaOwner.process + + def process( + self, + mediainfo: MediaInfo, + keyword: Optional[str] = None, + no_exists: Optional[dict[str, dict[int, NotExistMediaInfo]]] = None, + sites: Optional[list[int]] = None, + rule_groups: Optional[list[str]] = None, + area: Optional[str] = "title", + custom_words: Optional[list[str]] = None, + filter_params: Optional[dict[str, str]] = None, + ) -> list[Context]: + """通过稳定 Facade 调用精确媒体搜索 owner,保留公开类型合同。""" + return SearchMediaOwner.process( + cast(SearchMediaOwner, self), + mediainfo=mediainfo, + keyword=keyword, + no_exists=no_exists, + sites=sites, + rule_groups=rule_groups, + area=area, + custom_words=custom_words, + filter_params=filter_params, + ) + async_process = SearchMediaOwner.async_process async_process_stream = SearchMediaOwner.async_process_stream _build_subtitle_season_episodes = staticmethod(SearchSubtitleOwner._build_subtitle_season_episodes) diff --git a/app/chain/subscribe/facade.py b/app/chain/subscribe/facade.py index 2accef7a8..f74fab3e6 100644 --- a/app/chain/subscribe/facade.py +++ b/app/chain/subscribe/facade.py @@ -9,7 +9,7 @@ from app.chain._music import MusicSubscribeMixin from app.chain.base import ChainBase from app.chain.download import DownloadChain from app.chain.media import MediaChain -from app.chain.search import SearchChain +from app.chain.search.facade import SearchChain from app.chain.subscribe.completion import SubscribeCompletionOwner from app.chain.subscribe.create import SubscribeCreateOwner from app.chain.subscribe.interaction import SubscribeInteractionOwner @@ -56,19 +56,22 @@ class SubscribeChain( """为音乐订阅 owner 提供可替换的媒体识别构造点。""" from app.chain import _music as _music_mixin - return (_music_mixin.MediaChain or MediaChain)() + factory = getattr(_music_mixin, "MediaChain", None) or MediaChain + return factory() def _music_download_chain(self) -> DownloadChain: """为音乐订阅 owner 提供可替换的下载构造点。""" from app.chain import _music as _music_mixin - return (_music_mixin.DownloadChain or DownloadChain)() + factory = getattr(_music_mixin, "DownloadChain", None) or DownloadChain + return factory() def _music_search_chain(self) -> SearchChain: """为音乐订阅 owner 提供可替换的搜索构造点。""" from app.chain import _music as _music_mixin - return (_music_mixin.SearchChain or SearchChain)() + factory = getattr(_music_mixin, "SearchChain", None) or SearchChain + return factory() def _music_site_keywords(self, mediainfo: MediaInfo) -> list[str]: """返回音乐订阅目标在各站点使用的检索关键字。""" diff --git a/docs/architecture-optimization-checklist.md b/docs/architecture-optimization-checklist.md index f547b91b4..60fb5cc9c 100644 --- a/docs/architecture-optimization-checklist.md +++ b/docs/architecture-optimization-checklist.md @@ -78,7 +78,7 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain` | Event Contract | 53 | 均已有 payload model,但当前全部是 diagnostic enforcement | | Python 源码量 | 约 298,700 行 | 60 个文件超过 1,000 行,10 个超过 2,000 行 | | 长方法 | 299 个超过 80 行 | 70 个超过 150 行,23 个超过 250 行;大量是私有方法 | -| 全量 mypy 历史债务 | 10,086 / 592 文件 | 识别链 typed plan/result、TMDB API、消息、Indexer、目录源与 Recommend 双 ABI 共用计划继续清除参数、返回值和泛型注解债;strict frontier 当前覆盖 41 个文件 | +| 全量 mypy 历史债务 | 10,063 / 592 文件 | 识别链 typed plan/result、TMDB API、消息、Indexer、目录源与 Recommend 双 ABI 共用计划继续清除参数、返回值和泛型注解债;strict frontier 当前覆盖 41 个文件 | | Ruff 历史诊断 | 637 | 低水位门禁通过,但规则集只覆盖 `E4/E7/E9/F/I` | | 覆盖率低水位 | Application 81.45%,Domain 81.01% | Chain、Runtime、Agent、Adapter、Startup 未进入包级覆盖率门禁 | diff --git a/tests/fixtures/architecture/dependency-baseline.json b/tests/fixtures/architecture/dependency-baseline.json index 551c5880a..5f2a5d97e 100644 --- a/tests/fixtures/architecture/dependency-baseline.json +++ b/tests/fixtures/architecture/dependency-baseline.json @@ -1089,8 +1089,8 @@ "runtime_only": true } }, - "edge_count": 8330, - "edge_sha256": "1a084d7f7953fed5f141f7bc6082442fffd69c0b71ad65a65b64e0454602c617", + "edge_count": 8336, + "edge_sha256": "7219becfe5cbd053de6d1779a5db3323218b0ca1893e354ef83d3dd3c77fa10a", "edges": [ "app -> app.foundation", "app -> app.foundation.environment", @@ -4317,6 +4317,7 @@ "app.chain._music -> app.chain.download", "app.chain._music -> app.chain.media", "app.chain._music -> app.chain.search", + "app.chain._music -> app.chain.search.facade", "app.chain._music -> app.domain", "app.chain._music -> app.domain.context", "app.chain._music -> app.domain.media", @@ -4578,6 +4579,7 @@ "app.chain.interaction -> app.chain.download", "app.chain.interaction -> app.chain.media", "app.chain.interaction -> app.chain.search", + "app.chain.interaction -> app.chain.search.facade", "app.chain.interaction -> app.chain.subscribe", "app.chain.interaction -> app.chain.subscribe.facade", "app.chain.interaction -> app.domain", @@ -4891,9 +4893,12 @@ "app.chain.search.facade -> app.chain.search.site", "app.chain.search.facade -> app.chain.search.subtitle", "app.chain.search.facade -> app.chain.search.title", + "app.chain.search.facade -> app.domain", + "app.chain.search.facade -> app.domain.context", "app.chain.search.facade -> app.runtime", "app.chain.search.facade -> app.runtime.events", "app.chain.search.facade -> app.schemas", + "app.chain.search.facade -> app.schemas.mediaserver", "app.chain.search.facade -> app.schemas.types", "app.chain.search.media -> app.chain", "app.chain.search.media -> app.chain.media", @@ -5127,6 +5132,7 @@ "app.chain.subscribe.facade -> app.chain.download", "app.chain.subscribe.facade -> app.chain.media", "app.chain.subscribe.facade -> app.chain.search", + "app.chain.subscribe.facade -> app.chain.search.facade", "app.chain.subscribe.facade -> app.chain.subscribe", "app.chain.subscribe.facade -> app.chain.subscribe.completion", "app.chain.subscribe.facade -> app.chain.subscribe.create", diff --git a/tests/fixtures/architecture/mypy-baseline.json b/tests/fixtures/architecture/mypy-baseline.json index 7b0714d00..10c869144 100644 --- a/tests/fixtures/architecture/mypy-baseline.json +++ b/tests/fixtures/architecture/mypy-baseline.json @@ -1366,9 +1366,7 @@ "union-attr": 11 }, "app/chain/subscribe/facade.py": { - "attr-defined": 3, - "misc": 4, - "truthy-function": 1 + "misc": 4 }, "app/chain/subscribe/interaction.py": { "arg-type": 2 @@ -1913,9 +1911,9 @@ "assignment": 27, "empty-body": 1, "misc": 2, - "no-any-return": 8, - "no-untyped-call": 12, - "no-untyped-def": 11, + "no-any-return": 1, + "no-untyped-call": 4, + "no-untyped-def": 7, "return-value": 2, "type-arg": 13, "union-attr": 2