From e9313c664466b0f34d5ba8b8934914e4df601982 Mon Sep 17 00:00:00 2001 From: InfinityPacer <160988576+InfinityPacer@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:00:27 +0800 Subject: [PATCH] feat(sdk): expose custom separator configuration --- app/domain/meta/customization.py | 9 +++++++++ app/sdk/media.py | 2 ++ docs/architecture-overview.md | 2 +- docs/architecture/optimization-checklist.md | 2 +- .../architecture/dependency-baseline.json | 5 +++-- .../architecture/runtime-contract-baseline.json | 5 +++++ tests/test_customization_matcher.py | 17 +++++++++++++++++ tests/test_plugin_sdk.py | 13 ++++++++++++- 8 files changed, 50 insertions(+), 5 deletions(-) diff --git a/app/domain/meta/customization.py b/app/domain/meta/customization.py index 91732d393..325d33fbe 100644 --- a/app/domain/meta/customization.py +++ b/app/domain/meta/customization.py @@ -18,6 +18,15 @@ def get_customization() -> object: return _customization_provider() +def set_custom_separator(separator: str | None) -> None: + """设置自定义占位符输出分隔符;传入空值恢复宿主默认行为。""" + if separator == "": + separator = None + if separator is not None and not isinstance(separator, str): + raise TypeError("custom separator must be a string or None") + CustomizationMatcher().custom_separator = separator + + class CustomizationMatcher(metaclass=Singleton): """ 识别自定义占位符 diff --git a/app/sdk/media.py b/app/sdk/media.py index 4036c3880..3c0d83033 100644 --- a/app/sdk/media.py +++ b/app/sdk/media.py @@ -1,6 +1,7 @@ """插件使用的媒体上下文、标题解析、识别类型和媒体身份规则。""" from app.domain.context import Context, MediaInfo, TorrentInfo +from app.domain.meta.customization import set_custom_separator from app.domain.meta.metaanime import MetaAnime from app.domain.meta.metabase import MetaBase from app.domain.meta.metamusic import ( @@ -72,4 +73,5 @@ __all__ = [ "parse_media_key", "parse_media_source_selection", "resolve_media_identity", + "set_custom_separator", ] diff --git a/docs/architecture-overview.md b/docs/architecture-overview.md index fc56ce482..9a13638f3 100644 --- a/docs/architecture-overview.md +++ b/docs/architecture-overview.md @@ -755,7 +755,7 @@ flowchart LR | 指标 | 当前值 | |---|---:| | Python 模块 | 917 | -| 内部导入边 | 7,671 | +| 内部导入边 | 7,672 | | 非平凡 SCC | 1(精确 containment 的 TMDB 移植包环) | | Application / Chain 具体 Adapter 直连 | 0 / 0 | | Direct egress | 53(债务已清零,53 条精确 containment) | diff --git a/docs/architecture/optimization-checklist.md b/docs/architecture/optimization-checklist.md index e26c2b7f5..468a79785 100644 --- a/docs/architecture/optimization-checklist.md +++ b/docs/architecture/optimization-checklist.md @@ -93,7 +93,7 @@ ARCH-201 至 ARCH-204 均达到实现、验证、提交、推送和远端门禁 | 指标 | 当前值 | 解释 | |---|---:|---| -| 宿主 Python 模块 / 内部依赖边 | 917 / 7,671 | `dependency-baseline.json` 当前快照 | +| 宿主 Python 模块 / 内部依赖边 | 917 / 7,672 | `dependency-baseline.json` 当前快照 | | 非平凡 SCC | 1 | 仅保留精确 containment 的 29 模块 TMDB 移植包环 | | 跨层 DB 边界债务 | 0 | Application、Chain、API、Agent、Runtime、Workflow 到 DB 的受控债务均为零 | | Model/Oper 事务债务 | 0 | 自建 Session、自动事务装饰器、直接 commit/rollback 等基线均为零 | diff --git a/tests/fixtures/architecture/dependency-baseline.json b/tests/fixtures/architecture/dependency-baseline.json index c67fb3503..77297d3d5 100644 --- a/tests/fixtures/architecture/dependency-baseline.json +++ b/tests/fixtures/architecture/dependency-baseline.json @@ -1089,8 +1089,8 @@ "runtime_only": true } }, - "edge_count": 7671, - "edge_sha256": "04c20dbde6f746e2467064f2999efed4b25b9e6ccaa7042e94e96b39662522e7", + "edge_count": 7672, + "edge_sha256": "0d21d2307e4cc2d5d9f3c3373786d33a68bf816ec5d28b18e8d4b8519314cd14", "edges": [ "app -> app.foundation", "app -> app.foundation.environment", @@ -7838,6 +7838,7 @@ "app.sdk.media -> app.domain.context", "app.sdk.media -> app.domain.media", "app.sdk.media -> app.domain.meta", + "app.sdk.media -> app.domain.meta.customization", "app.sdk.media -> app.domain.meta.metaanime", "app.sdk.media -> app.domain.meta.metabase", "app.sdk.media -> app.domain.meta.metamusic", diff --git a/tests/fixtures/architecture/runtime-contract-baseline.json b/tests/fixtures/architecture/runtime-contract-baseline.json index 9a702ec03..aef4a58eb 100644 --- a/tests/fixtures/architecture/runtime-contract-baseline.json +++ b/tests/fixtures/architecture/runtime-contract-baseline.json @@ -10112,6 +10112,11 @@ "kind": "import", "name": "resolve_media_identity", "target": "app.schemas.media.resolve_media_identity" + }, + { + "kind": "import", + "name": "set_custom_separator", + "target": "app.domain.meta.customization.set_custom_separator" } ], "app.sdk.network": [ diff --git a/tests/test_customization_matcher.py b/tests/test_customization_matcher.py index 0269f429e..82ab55dd0 100644 --- a/tests/test_customization_matcher.py +++ b/tests/test_customization_matcher.py @@ -1,5 +1,6 @@ from app.domain.meta import customization as customization_module from app.domain.meta.customization import CustomizationMatcher +from app.sdk.media import set_custom_separator def test_match_uses_latest_customization_setting(monkeypatch): @@ -15,3 +16,19 @@ def test_match_uses_latest_customization_setting(monkeypatch): assert matcher.match("[GROUP][TEAM] Movie") == "GROUP" values[0] = ["TEAM"] assert matcher.match("[GROUP][TEAM] Movie") == "TEAM" + + +def test_sdk_set_custom_separator_updates_matcher_and_restores_default(monkeypatch): + """插件只能通过 SDK 设置分隔符,清空后恢复宿主默认值。""" + matcher = CustomizationMatcher() + monkeypatch.setattr( + customization_module, + "_customization_provider", + lambda: ["GROUP", "TEAM"], + ) + + set_custom_separator("#") + assert matcher.match("[GROUP][TEAM] Movie") == "GROUP#TEAM" + + set_custom_separator(None) + assert matcher.match("[GROUP][TEAM] Movie") == "GROUP@TEAM" diff --git a/tests/test_plugin_sdk.py b/tests/test_plugin_sdk.py index 47e36c4b0..1df779a4d 100644 --- a/tests/test_plugin_sdk.py +++ b/tests/test_plugin_sdk.py @@ -8,7 +8,14 @@ from app.sdk.cache import Cache, cached from app.sdk.config import settings from app.sdk.events import Event, eventmanager from app.sdk.logging import logger -from app.sdk.media import MediaInfo, MetaBase, MetaInfo, MetaMusic, NfoReader +from app.sdk.media import ( + MediaInfo, + MetaBase, + MetaInfo, + MetaMusic, + NfoReader, + set_custom_separator, +) from app.sdk.network import RequestUtils, RssHelper, SitesHelper from app.sdk.plugins import ModuleManager, PluginManager from app.sdk.services import NotificationHelper @@ -22,6 +29,9 @@ PROJECT_ROOT = Path(__file__).parents[1] def test_sdk_exports_canonical_plugin_interfaces(): """SDK 应复用 canonical 对象,不复制实现或制造第二套单例。""" from app.domain.context import MediaInfo as CanonicalMediaInfo + from app.domain.meta.customization import ( + set_custom_separator as canonical_set_custom_separator, + ) from app.domain.meta.metabase import MetaBase as CanonicalMetaBase from app.domain.meta.metamusic import MetaMusic as CanonicalMetaMusic from app.domain.metainfo import MetaInfo as CanonicalMetaInfo @@ -51,6 +61,7 @@ def test_sdk_exports_canonical_plugin_interfaces(): assert eventmanager is canonical_eventmanager assert logger is canonical_logger assert MediaInfo is CanonicalMediaInfo + assert set_custom_separator is canonical_set_custom_separator assert MetaBase is CanonicalMetaBase assert MetaInfo is CanonicalMetaInfo assert MetaMusic is CanonicalMetaMusic