diff --git a/scripts/architecture/baseline.py b/scripts/architecture/baseline.py index 5b4f41ab1..53471c17f 100644 --- a/scripts/architecture/baseline.py +++ b/scripts/architecture/baseline.py @@ -250,8 +250,11 @@ def collect_dependency_baseline() -> dict[str, Any]: } -def collect_run_module_contracts() -> dict[str, Any]: - """收集字符串模块调度方法及其同步、异步调用位置。""" +def _collect_run_module_locations() -> tuple[ + dict[str, list[dict[str, Any]]], + list[dict[str, Any]], +]: + """扫描模块调度调用及其当前源码位置。""" calls: dict[str, list[dict[str, Any]]] = defaultdict(list) dynamic_calls: list[dict[str, Any]] = [] for module_name, path in discover_modules().items(): @@ -274,18 +277,53 @@ def collect_run_module_contracts() -> dict[str, Any]: calls[node.args[0].value].append(location) else: dynamic_calls.append(location) + return calls, dynamic_calls + + +def _aggregate_locations( + locations: list[dict[str, Any]], + fields: tuple[str, ...], +) -> list[dict[str, Any]]: + """按稳定字段聚合调用位置,排除行号但保留调用次数。""" + counts: dict[tuple[Any, ...], int] = defaultdict(int) + for location in locations: + counts[tuple(location[field] for field in fields)] += 1 + return [ + { + **dict(zip(fields, values)), + "count": count, + } + for values, count in sorted(counts.items()) + ] + + +def collect_run_module_contracts() -> dict[str, Any]: + """收集不受源码行号变化影响的模块调度语义契约。""" + calls, dynamic_calls = _collect_run_module_locations() stable_calls = { - method: sorted( - locations, - key=lambda item: (item["caller"], item["line"], item["mode"]), - ) + method: _aggregate_locations(locations, ("caller", "mode")) for method, locations in sorted(calls.items()) } return { "method_count": len(stable_calls), - "call_count": sum(len(locations) for locations in stable_calls.values()), + "call_count": sum(len(locations) for locations in calls.values()), "dynamic_call_count": len(dynamic_calls), "methods": stable_calls, + "dynamic_calls": _aggregate_locations(dynamic_calls, ("caller", "mode")), + } + + +def collect_run_module_diagnostics() -> dict[str, Any]: + """收集模块调度调用的当前源码位置,仅用于人工诊断。""" + calls, dynamic_calls = _collect_run_module_locations() + return { + "methods": { + method: sorted( + locations, + key=lambda item: (item["caller"], item["line"], item["mode"]), + ) + for method, locations in sorted(calls.items()) + }, "dynamic_calls": sorted( dynamic_calls, key=lambda item: (item["caller"], item["line"], item["mode"]), @@ -330,8 +368,14 @@ def _event_enum_members(enum_name: str) -> tuple[str, ...]: ) -def collect_event_contracts() -> dict[str, Any]: - """收集宿主事件枚举的生产者、消费者和动态调用位置。""" +def _collect_event_locations() -> tuple[ + list[str], + dict[str, list[dict[str, Any]]], + dict[str, list[dict[str, Any]]], + list[dict[str, Any]], + list[dict[str, Any]], +]: + """扫描事件枚举及其生产、消费位置,供语义和诊断视图复用。""" event_members = _event_enum_members("EventType") chain_event_members = _event_enum_members("ChainEventType") @@ -390,28 +434,70 @@ def collect_event_contracts() -> dict[str, Any]: *(f"EventType.{member}" for member in event_members), *(f"ChainEventType.{member}" for member in chain_event_members), ] + return ( + enum_names, + producers, + consumers, + dynamic_producers, + dynamic_consumers, + ) + + +def collect_event_contracts() -> dict[str, Any]: + """收集不受源码行号变化影响的宿主事件语义契约。""" + ( + enum_names, + producers, + consumers, + dynamic_producers, + dynamic_consumers, + ) = _collect_event_locations() contracts = { name: { - "producers": sorted( + "producers": _aggregate_locations( producers.get(name, []), - key=lambda item: (item["caller"], item["line"]), + ("caller",), ), - "consumers": sorted( + "consumers": _aggregate_locations( consumers.get(name, []), - key=lambda item: (item["caller"], item["line"]), + ("caller",), ), } for name in sorted(enum_names) } return { "event_count": len(contracts), - "producer_count": sum( - len(item["producers"]) for item in contracts.values() - ), - "consumer_count": sum( - len(item["consumers"]) for item in contracts.values() - ), + "producer_count": sum(len(items) for items in producers.values()), + "consumer_count": sum(len(items) for items in consumers.values()), "events": contracts, + "dynamic_producers": _aggregate_locations(dynamic_producers, ("caller",)), + "dynamic_consumers": _aggregate_locations(dynamic_consumers, ("caller",)), + } + + +def collect_event_diagnostics() -> dict[str, Any]: + """收集事件生产与消费的当前源码位置,仅用于人工诊断。""" + ( + enum_names, + producers, + consumers, + dynamic_producers, + dynamic_consumers, + ) = _collect_event_locations() + return { + "events": { + name: { + "producers": sorted( + producers.get(name, []), + key=lambda item: (item["caller"], item["line"]), + ), + "consumers": sorted( + consumers.get(name, []), + key=lambda item: (item["caller"], item["line"]), + ), + } + for name in sorted(enum_names) + }, "dynamic_producers": sorted( dynamic_producers, key=lambda item: (item["caller"], item["line"]), @@ -503,7 +589,7 @@ def collect_compat_manifest() -> dict[str, Any]: def collect_runtime_baseline() -> dict[str, Any]: """生成模块调度、SDK 和兼容层公开契约基线。""" return { - "schema_version": 1, + "schema_version": 2, "run_module": collect_run_module_contracts(), "events": collect_event_contracts(), "sdk_exports": collect_sdk_exports(), @@ -511,6 +597,14 @@ def collect_runtime_baseline() -> dict[str, Any]: } +def collect_runtime_diagnostics() -> dict[str, Any]: + """生成带当前源码行号的运行契约诊断视图,不写入语义 fixture。""" + return { + "run_module": collect_run_module_diagnostics(), + "events": collect_event_diagnostics(), + } + + def git_head(repository: Path) -> str: """读取外部插件仓当前提交,失败时返回可诊断占位值。""" result = subprocess.run( @@ -633,11 +727,13 @@ def collect_official_plugin_baseline(plugin_repo: Path) -> dict[str, Any]: if hook in defined_names: hook_files[hook].add(relative) return { - "schema_version": 2, - "source": { + "schema_version": 3, + "scope": { "repository": "MoviePilot-Plugins", - "head": git_head(plugin_repo), "roots": [root.name for root in roots], + }, + "provenance": { + "head": git_head(plugin_repo), "python_file_count": len(paths), "source_sha256": digest.hexdigest(), }, @@ -669,12 +765,103 @@ def write_json(path: Path, value: dict[str, Any]) -> None: def _display_path(path: Path) -> Path: + """优先返回仓库相对路径,便于 CLI 输出稳定可读。""" try: return path.relative_to(PROJECT_ROOT) except ValueError: return path +def _migrate_runtime_baseline(value: dict[str, Any]) -> dict[str, Any]: + """把包含源码行号的 v1 运行契约转换为稳定语义结构。""" + if value.get("schema_version") != 1: + return value + run_module = value["run_module"] + events = value["events"] + return { + "schema_version": 2, + "run_module": { + **{ + key: run_module[key] + for key in ("method_count", "call_count", "dynamic_call_count") + }, + "methods": { + method: _aggregate_locations(locations, ("caller", "mode")) + for method, locations in run_module["methods"].items() + }, + "dynamic_calls": _aggregate_locations( + run_module["dynamic_calls"], + ("caller", "mode"), + ), + }, + "events": { + **{ + key: events[key] + for key in ("event_count", "producer_count", "consumer_count") + }, + "events": { + event_name: { + "producers": _aggregate_locations( + contract["producers"], + ("caller",), + ), + "consumers": _aggregate_locations( + contract["consumers"], + ("caller",), + ), + } + for event_name, contract in events["events"].items() + }, + "dynamic_producers": _aggregate_locations( + events["dynamic_producers"], + ("caller",), + ), + "dynamic_consumers": _aggregate_locations( + events["dynamic_consumers"], + ("caller",), + ), + }, + "sdk_exports": value["sdk_exports"], + "compat_manifest": value["compat_manifest"], + } + + +def _migrate_plugin_baseline(value: dict[str, Any]) -> dict[str, Any]: + """把来源信息混排的 v2 插件基线转换为 scope/provenance 结构。""" + if value.get("schema_version") != 2: + return value + source = value["source"] + return { + "schema_version": 3, + "scope": { + "repository": source["repository"], + "roots": source["roots"], + }, + "provenance": { + "head": source["head"], + "python_file_count": source["python_file_count"], + "source_sha256": source["source_sha256"], + }, + "imports": value["imports"], + "hooks": value["hooks"], + "api_routes": value["api_routes"], + } + + +def semantic_baseline(path: Path, value: dict[str, Any]) -> dict[str, Any]: + """返回参与门禁比较的稳定语义视图,并兼容读取旧 fixture。""" + if path.name == RUNTIME_BASELINE_PATH.name: + return _migrate_runtime_baseline(value) + if path.name == PLUGIN_BASELINE_PATH.name: + migrated = _migrate_plugin_baseline(value) + return { + key: item + for key, item in migrated.items() + if key != "provenance" + } + return value + + def check_json( path: Path, actual: dict[str, Any], @@ -683,7 +870,14 @@ def check_json( ) -> bool: """比较当前扫描结果和已提交基线并输出限定范围的更新提示。""" expected = json.loads(path.read_text(encoding="utf-8")) - if expected == actual: + expected_semantic = semantic_baseline(path, expected) + actual_semantic = semantic_baseline(path, actual) + if expected_semantic == actual_semantic: + if expected != actual: + print( + f"基线语义未变化,但 schema/provenance 已变化:{_display_path(path)}", + file=sys.stderr, + ) return True print( f"架构基线已变化:{_display_path(path)};" @@ -713,6 +907,11 @@ def parse_args(argv: Optional[list[str]] = None) -> argparse.Namespace: type=Path, help="官方插件操作所需的独立 MoviePilot-Plugins 仓路径", ) + parser.add_argument( + "--diagnostics", + action="store_true", + help="随宿主检查打印当前运行契约源码位置", + ) args = parser.parse_args(argv) if args.check or args.write: if not args.scope: @@ -724,11 +923,15 @@ def parse_args(argv: Optional[list[str]] = None) -> argparse.Namespace: file=sys.stderr, ) setattr(args, f"{'check' if args.check else 'write'}_{args.scope}", True) + elif args.scope: + parser.error("--scope 只能与旧 --check/--write 一起使用") plugin_action = args.check_plugins or args.write_plugins if plugin_action and not args.plugin_repo: parser.error("插件基线操作必须指定 --plugin-repo") if not plugin_action and args.plugin_repo: parser.error("--plugin-repo 只能用于插件基线操作") + if args.diagnostics and not args.check_host: + parser.error("--diagnostics 只能与 --check-host 一起使用") return args @@ -763,6 +966,8 @@ def main(argv: Optional[list[str]] = None) -> int: check_json(path, baseline, write_hint=write_hint) for path, baseline in baselines ] + if args.diagnostics: + print(json.dumps(collect_runtime_diagnostics(), ensure_ascii=False, indent=2)) return 0 if all(checks) else 1 diff --git a/scripts/startup/performance.py b/scripts/startup/performance.py index 7ea39aa63..006c37b40 100644 --- a/scripts/startup/performance.py +++ b/scripts/startup/performance.py @@ -322,6 +322,7 @@ def parse_args(argv: Optional[list[str]] = None) -> argparse.Namespace: def _display_path(path: Path) -> Path: + """优先返回仓库相对路径,便于 CLI 输出稳定可读。""" try: return path.relative_to(PROJECT_ROOT) except ValueError: diff --git a/tests/fixtures/architecture/official-plugin-baseline.json b/tests/fixtures/architecture/official-plugin-baseline.json index 7da6dd43b..739ed77a7 100644 --- a/tests/fixtures/architecture/official-plugin-baseline.json +++ b/tests/fixtures/architecture/official-plugin-baseline.json @@ -1111,48 +1111,59 @@ "response_model": null } ], - "plugins.v2/chatgpt/__init__.py": [ + "plugins.v2/courseorganizer/__init__.py": [ { "auth": "'bear'", - "endpoint": "get_cache_stats", - "endpoint_return": "dict", + "endpoint": "get_review_route", + "endpoint_return": "Any", "methods": [ "GET" ], - "path": "/cache_stats", + "path": "/review", "response_class": null, "response_model": null }, { "auth": "'bear'", - "endpoint": "clear_cache", - "endpoint_return": "dict", + "endpoint": "save_review_route", + "endpoint_return": "Any", "methods": [ - "GET" + "POST" ], - "path": "/clear_cache", + "path": "/review", "response_class": null, "response_model": null }, { "auth": "'bear'", - "endpoint": "clear_usage_stats", - "endpoint_return": "dict", + "endpoint": "refresh_review_route", + "endpoint_return": "Any", "methods": [ - "GET" + "POST" ], - "path": "/clear_usage_stats", + "path": "/review/refresh", "response_class": null, "response_model": null }, { "auth": "'bear'", - "endpoint": "get_usage_stats", - "endpoint_return": "dict", + "endpoint": "associate_tmdb_route", + "endpoint_return": "Any", "methods": [ - "GET" + "POST" ], - "path": "/usage_stats", + "path": "/review/tmdb/associate", + "response_class": null, + "response_model": null + }, + { + "auth": "'bear'", + "endpoint": "search_tmdb_route", + "endpoint_return": "Any", + "methods": [ + "POST" + ], + "path": "/review/tmdb/search", "response_class": null, "response_model": null } @@ -2501,6 +2512,52 @@ "response_model": null } ], + "plugins.v3/chatgpt/__init__.py": [ + { + "auth": "'bear'", + "endpoint": "get_cache_stats", + "endpoint_return": "dict", + "methods": [ + "GET" + ], + "path": "/cache_stats", + "response_class": null, + "response_model": null + }, + { + "auth": "'bear'", + "endpoint": "clear_cache", + "endpoint_return": "dict", + "methods": [ + "GET" + ], + "path": "/clear_cache", + "response_class": null, + "response_model": null + }, + { + "auth": "'bear'", + "endpoint": "clear_usage_stats", + "endpoint_return": "dict", + "methods": [ + "GET" + ], + "path": "/clear_usage_stats", + "response_class": null, + "response_model": null + }, + { + "auth": "'bear'", + "endpoint": "get_usage_stats", + "endpoint_return": "dict", + "methods": [ + "GET" + ], + "path": "/usage_stats", + "response_class": null, + "response_model": null + } + ], "plugins.v3/doubanrank/__init__.py": [ { "auth": null, @@ -2638,7 +2695,7 @@ ] }, "get_api": { - "file_count": 81, + "file_count": 83, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agenttokens/__init__.py", @@ -2656,6 +2713,7 @@ "plugins.v2/clashruleprovider/__init__.py", "plugins.v2/cleaninvalidseed/__init__.py", "plugins.v2/contractcheck/__init__.py", + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/crossseed/__init__.py", "plugins.v2/dailysummary/__init__.py", "plugins.v2/doubanrank/__init__.py", @@ -2708,6 +2766,7 @@ "plugins.v3/autoclean/__init__.py", "plugins.v3/bangumicoll/__init__.py", "plugins.v3/brushflow/__init__.py", + "plugins.v3/chatgpt/__init__.py", "plugins.v3/doubanrank/__init__.py", "plugins.v3/doubansync/__init__.py", "plugins.v3/downloadsitetag/__init__.py", @@ -2728,7 +2787,7 @@ "files": [] }, "get_command": { - "file_count": 79, + "file_count": 81, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agenttokens/__init__.py", @@ -2745,6 +2804,7 @@ "plugins.v2/clashruleprovider/__init__.py", "plugins.v2/cleaninvalidseed/__init__.py", "plugins.v2/contractcheck/__init__.py", + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/crossseed/__init__.py", "plugins.v2/dailysummary/__init__.py", "plugins.v2/doubanrank/__init__.py", @@ -2796,6 +2856,7 @@ "plugins.v3/autoclean/__init__.py", "plugins.v3/bangumicoll/__init__.py", "plugins.v3/brushflow/__init__.py", + "plugins.v3/chatgpt/__init__.py", "plugins.v3/doubanrank/__init__.py", "plugins.v3/doubansync/__init__.py", "plugins.v3/downloadsitetag/__init__.py", @@ -2827,7 +2888,7 @@ ] }, "get_form": { - "file_count": 81, + "file_count": 83, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agenttokens/__init__.py", @@ -2845,6 +2906,7 @@ "plugins.v2/clashruleprovider/__init__.py", "plugins.v2/cleaninvalidseed/__init__.py", "plugins.v2/contractcheck/__init__.py", + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/crossseed/__init__.py", "plugins.v2/dailysummary/__init__.py", "plugins.v2/doubanrank/__init__.py", @@ -2897,6 +2959,7 @@ "plugins.v3/autoclean/__init__.py", "plugins.v3/bangumicoll/__init__.py", "plugins.v3/brushflow/__init__.py", + "plugins.v3/chatgpt/__init__.py", "plugins.v3/doubanrank/__init__.py", "plugins.v3/doubansync/__init__.py", "plugins.v3/downloadsitetag/__init__.py", @@ -2923,7 +2986,7 @@ ] }, "get_page": { - "file_count": 81, + "file_count": 83, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agenttokens/__init__.py", @@ -2941,6 +3004,7 @@ "plugins.v2/clashruleprovider/__init__.py", "plugins.v2/cleaninvalidseed/__init__.py", "plugins.v2/contractcheck/__init__.py", + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/crossseed/__init__.py", "plugins.v2/dailysummary/__init__.py", "plugins.v2/doubanrank/__init__.py", @@ -2993,6 +3057,7 @@ "plugins.v3/autoclean/__init__.py", "plugins.v3/bangumicoll/__init__.py", "plugins.v3/brushflow/__init__.py", + "plugins.v3/chatgpt/__init__.py", "plugins.v3/doubanrank/__init__.py", "plugins.v3/doubansync/__init__.py", "plugins.v3/downloadsitetag/__init__.py", @@ -3009,13 +3074,14 @@ ] }, "get_render_mode": { - "file_count": 11, + "file_count": 12, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agenttokens/__init__.py", "plugins.v2/airecognizerenhancer/__init__.py", "plugins.v2/brushflow/__init__.py", "plugins.v2/clashruleprovider/__init__.py", + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/fullscreenposterwall/__init__.py", "plugins.v2/hrblocker/__init__.py", "plugins.v2/oidcauth/__init__.py", @@ -3025,7 +3091,7 @@ ] }, "get_service": { - "file_count": 47, + "file_count": 48, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/autoauction/__init__.py", @@ -3035,6 +3101,7 @@ "plugins.v2/clashruleprovider/__init__.py", "plugins.v2/cleaninvalidseed/__init__.py", "plugins.v2/contractcheck/__init__.py", + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/crossseed/__init__.py", "plugins.v2/dailysummary/__init__.py", "plugins.v2/doubanrank/__init__.py", @@ -3081,7 +3148,7 @@ "files": [] }, "get_state": { - "file_count": 82, + "file_count": 85, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agenttokens/__init__.py", @@ -3100,6 +3167,7 @@ "plugins.v2/clashruleprovider/__init__.py", "plugins.v2/cleaninvalidseed/__init__.py", "plugins.v2/contractcheck/__init__.py", + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/crossseed/__init__.py", "plugins.v2/dailysummary/__init__.py", "plugins.v2/doubanrank/__init__.py", @@ -3152,6 +3220,8 @@ "plugins.v3/autoclean/__init__.py", "plugins.v3/bangumicoll/__init__.py", "plugins.v3/brushflow/__init__.py", + "plugins.v3/chatgpt/__init__.py", + "plugins.v3/chatgpt/openai.py", "plugins.v3/doubanrank/__init__.py", "plugins.v3/doubansync/__init__.py", "plugins.v3/downloadsitetag/__init__.py", @@ -3168,7 +3238,7 @@ ] }, "init_plugin": { - "file_count": 81, + "file_count": 83, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agenttokens/__init__.py", @@ -3186,6 +3256,7 @@ "plugins.v2/clashruleprovider/__init__.py", "plugins.v2/cleaninvalidseed/__init__.py", "plugins.v2/contractcheck/__init__.py", + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/crossseed/__init__.py", "plugins.v2/dailysummary/__init__.py", "plugins.v2/doubanrank/__init__.py", @@ -3238,6 +3309,7 @@ "plugins.v3/autoclean/__init__.py", "plugins.v3/bangumicoll/__init__.py", "plugins.v3/brushflow/__init__.py", + "plugins.v3/chatgpt/__init__.py", "plugins.v3/doubanrank/__init__.py", "plugins.v3/doubansync/__init__.py", "plugins.v3/downloadsitetag/__init__.py", @@ -3254,7 +3326,7 @@ ] }, "stop_service": { - "file_count": 81, + "file_count": 83, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agenttokens/__init__.py", @@ -3272,6 +3344,7 @@ "plugins.v2/clashruleprovider/__init__.py", "plugins.v2/cleaninvalidseed/__init__.py", "plugins.v2/contractcheck/__init__.py", + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/crossseed/__init__.py", "plugins.v2/dailysummary/__init__.py", "plugins.v2/doubanrank/__init__.py", @@ -3324,6 +3397,7 @@ "plugins.v3/autoclean/__init__.py", "plugins.v3/bangumicoll/__init__.py", "plugins.v3/brushflow/__init__.py", + "plugins.v3/chatgpt/__init__.py", "plugins.v3/doubanrank/__init__.py", "plugins.v3/doubansync/__init__.py", "plugins.v3/downloadsitetag/__init__.py", @@ -3348,11 +3422,12 @@ ] }, "app.agent.llm": { - "file_count": 3, + "file_count": 4, "files": [ "plugins.v2/airecognizerenhancer/__init__.py", - "plugins.v2/chatgpt/openai.py", - "plugins.v2/qbuploadlimiter/__init__.py" + "plugins.v2/courseorganizer/providers.py", + "plugins.v2/qbuploadlimiter/__init__.py", + "plugins.v3/chatgpt/openai.py" ] }, "app.agent.llm.helper": { @@ -3415,10 +3490,11 @@ ] }, "app.chain.media": { - "file_count": 9, + "file_count": 10, "files": [ "plugins.v2/agentresourceofficer/services/hdhive_openapi.py", "plugins.v2/airecognizerenhancer/__init__.py", + "plugins.v2/courseorganizer/providers.py", "plugins.v2/doubanrank/__init__.py", "plugins.v2/doubansync/__init__.py", "plugins.v2/lexiannot/__init__.py", @@ -3548,7 +3624,7 @@ ] }, "app.core.config": { - "file_count": 70, + "file_count": 73, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agentresourceofficer/services/hdhive_openapi.py", @@ -3584,6 +3660,7 @@ "plugins.v2/cleaninvalidseed/__init__.py", "plugins.v2/contractcheck/__init__.py", "plugins.v2/contractcheck/siteuserinfo/__init__.py", + "plugins.v2/courseorganizer/providers.py", "plugins.v2/crossseed/__init__.py", "plugins.v2/dailysummary/__init__.py", "plugins.v2/doubanrank/__init__.py", @@ -3594,6 +3671,7 @@ "plugins.v2/ffmpegthumb/__init__.py", "plugins.v2/fullscreenposterwall/__init__.py", "plugins.v2/imdbsource/__init__.py", + "plugins.v2/imdbsource/imdbapi.py", "plugins.v2/imdbsource/imdbhelper.py", "plugins.v2/invitessignin/__init__.py", "plugins.v2/iyuuautoseed/__init__.py", @@ -3619,7 +3697,8 @@ "plugins.v2/tvdbdiscover/__init__.py", "plugins.v2/tvfirstwatch/__init__.py", "plugins.v2/updatewechatip/__init__.py", - "plugins.v2/wechatclawbot/__init__.py" + "plugins.v2/wechatclawbot/__init__.py", + "plugins.v3/chatgpt/__init__.py" ] }, "app.core.context": { @@ -3645,7 +3724,7 @@ ] }, "app.core.event": { - "file_count": 40, + "file_count": 41, "files": [ "plugins.v2/agenttokens/__init__.py", "plugins.v2/airecognizerenhancer/__init__.py", @@ -3686,7 +3765,8 @@ "plugins.v2/tvfirstwatch/__init__.py", "plugins.v2/updatewechatip/__init__.py", "plugins.v2/wechatclawbot/__init__.py", - "plugins.v2/wxpusher/__init__.py" + "plugins.v2/wxpusher/__init__.py", + "plugins.v3/chatgpt/__init__.py" ] }, "app.core.meta": { @@ -3880,8 +3960,9 @@ ] }, "app.db.user_oper": { - "file_count": 2, + "file_count": 3, "files": [ + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/doubansync/__init__.py", "plugins.v2/oidcauth/__init__.py" ] @@ -4036,7 +4117,7 @@ ] }, "app.log": { - "file_count": 97, + "file_count": 99, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agentresourceofficer/feishu_channel.py", @@ -4082,6 +4163,7 @@ "plugins.v2/contractcheck/__init__.py", "plugins.v2/contractcheck/siteuserinfo/__init__.py", "plugins.v2/contractcheck/siteuserinfo/nexus_php.py", + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/crossseed/__init__.py", "plugins.v2/dailysummary/__init__.py", "plugins.v2/doubanrank/__init__.py", @@ -4134,7 +4216,8 @@ "plugins.v2/updatewechatip/__init__.py", "plugins.v2/wechatclawbot/__init__.py", "plugins.v2/wechatclawbot/ilink/client.py", - "plugins.v2/wxpusher/__init__.py" + "plugins.v2/wxpusher/__init__.py", + "plugins.v3/chatgpt/__init__.py" ] }, "app.modules.bangumi": { @@ -4210,7 +4293,7 @@ ] }, "app.plugins": { - "file_count": 81, + "file_count": 83, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agenttokens/__init__.py", @@ -4228,6 +4311,7 @@ "plugins.v2/clashruleprovider/__init__.py", "plugins.v2/cleaninvalidseed/__init__.py", "plugins.v2/contractcheck/__init__.py", + "plugins.v2/courseorganizer/__init__.py", "plugins.v2/crossseed/__init__.py", "plugins.v2/dailysummary/__init__.py", "plugins.v2/doubanrank/__init__.py", @@ -4280,6 +4364,7 @@ "plugins.v3/autoclean/__init__.py", "plugins.v3/bangumicoll/__init__.py", "plugins.v3/brushflow/__init__.py", + "plugins.v3/chatgpt/__init__.py", "plugins.v3/doubanrank/__init__.py", "plugins.v3/doubansync/__init__.py", "plugins.v3/downloadsitetag/__init__.py", @@ -4322,9 +4407,10 @@ ] }, "app.plugins.chatgpt.openai": { - "file_count": 1, + "file_count": 2, "files": [ - "plugins.v2/chatgpt/__init__.py" + "plugins.v2/chatgpt/__init__.py", + "plugins.v3/chatgpt/__init__.py" ] }, "app.plugins.ffmpegthumb.ffmpeg_helper": { @@ -4334,24 +4420,21 @@ ] }, "app.plugins.imdbsource.imdbhelper": { - "file_count": 2, + "file_count": 1, "files": [ - "plugins.v2/imdbsource/__init__.py", - "plugins.v3/imdbsource/__init__.py" + "plugins.v2/imdbsource/__init__.py" ] }, "app.plugins.imdbsource.officialapi": { - "file_count": 2, + "file_count": 1, "files": [ - "plugins.v2/imdbsource/__init__.py", - "plugins.v3/imdbsource/__init__.py" + "plugins.v2/imdbsource/__init__.py" ] }, "app.plugins.imdbsource.schema": { - "file_count": 2, + "file_count": 1, "files": [ - "plugins.v2/imdbsource/__init__.py", - "plugins.v3/imdbsource/__init__.py" + "plugins.v2/imdbsource/__init__.py" ] }, "app.plugins.iyuuautoseed.iyuu_helper": { @@ -4385,7 +4468,7 @@ ] }, "app.schemas": { - "file_count": 48, + "file_count": 50, "files": [ "plugins.v2/autoclean/__init__.py", "plugins.v2/blurayremux/__init__.py", @@ -4426,9 +4509,11 @@ "plugins.v2/wechatclawbot/__init__.py", "plugins.v3/autoclean/__init__.py", "plugins.v3/brushflow/__init__.py", + "plugins.v3/chatgpt/__init__.py", "plugins.v3/doubanrank/__init__.py", "plugins.v3/downloadsitetag/__init__.py", "plugins.v3/imdbsource/__init__.py", + "plugins.v3/imdbsource/schema/imdbapi.py", "plugins.v3/libraryscraper/__init__.py", "plugins.v3/maoyanrank/__init__.py", "plugins.v3/mediaservermsg/__init__.py", @@ -4437,14 +4522,26 @@ "plugins.v3/tvdbdiscover/__init__.py" ] }, + "app.schemas.context": { + "file_count": 1, + "files": [ + "plugins.v3/imdbsource/schema/imdbapi.py" + ] + }, "app.schemas.event": { "file_count": 1, "files": [ "plugins.v2/hrblocker/__init__.py" ] }, + "app.schemas.exception": { + "file_count": 1, + "files": [ + "plugins.v3/imdbsource/officialapi.py" + ] + }, "app.schemas.types": { - "file_count": 65, + "file_count": 66, "files": [ "plugins.v2/agentresourceofficer/__init__.py", "plugins.v2/agenttokens/__init__.py", @@ -4500,6 +4597,7 @@ "plugins.v3/autoclean/__init__.py", "plugins.v3/bangumicoll/__init__.py", "plugins.v3/brushflow/__init__.py", + "plugins.v3/chatgpt/__init__.py", "plugins.v3/doubansync/__init__.py", "plugins.v3/downloadsitetag/__init__.py", "plugins.v3/episodegroupmeta/__init__.py", @@ -4522,9 +4620,8 @@ ] }, "app.sdk.cache": { - "file_count": 5, + "file_count": 4, "files": [ - "plugins.v3/imdbsource/__init__.py", "plugins.v3/imdbsource/imdbapi.py", "plugins.v3/imdbsource/imdbhelper.py", "plugins.v3/imdbsource/officialapi.py", @@ -4532,7 +4629,7 @@ ] }, "app.sdk.config": { - "file_count": 20, + "file_count": 21, "files": [ "plugins.v3/agentresourceofficer/__init__.py", "plugins.v3/agentresourceofficer/services/hdhive_openapi.py", @@ -4546,6 +4643,7 @@ "plugins.v3/downloadsitetag/__init__.py", "plugins.v3/episodegroupmeta/__init__.py", "plugins.v3/imdbsource/__init__.py", + "plugins.v3/imdbsource/imdbapi.py", "plugins.v3/imdbsource/imdbhelper.py", "plugins.v3/libraryscraper/__init__.py", "plugins.v3/maoyanrank/__init__.py", @@ -4573,7 +4671,7 @@ ] }, "app.sdk.logging": { - "file_count": 24, + "file_count": 23, "files": [ "plugins.v3/agentresourceofficer/__init__.py", "plugins.v3/agentresourceofficer/feishu_channel.py", @@ -4588,7 +4686,6 @@ "plugins.v3/downloadsitetag/__init__.py", "plugins.v3/episodegroupmeta/__init__.py", "plugins.v3/historytov2/__init__.py", - "plugins.v3/imdbsource/__init__.py", "plugins.v3/imdbsource/imdbapi.py", "plugins.v3/imdbsource/imdbhelper.py", "plugins.v3/imdbsource/officialapi.py", @@ -4622,7 +4719,7 @@ ] }, "app.sdk.network": { - "file_count": 17, + "file_count": 16, "files": [ "plugins.v3/animeupscale/__init__.py", "plugins.v3/bangumicoll/__init__.py", @@ -4632,7 +4729,6 @@ "plugins.v3/downloadsitetag/__init__.py", "plugins.v3/episodegroupmeta/__init__.py", "plugins.v3/historytov2/__init__.py", - "plugins.v3/imdbsource/__init__.py", "plugins.v3/imdbsource/imdbapi.py", "plugins.v3/imdbsource/imdbhelper.py", "plugins.v3/imdbsource/officialapi.py", @@ -4839,15 +4935,17 @@ ] } }, - "schema_version": 2, - "source": { - "head": "ddb41dbcbbea21196154a7f6d5fdba3aa34a5e4a", - "python_file_count": 231, + "provenance": { + "head": "217c8d25ffe6ff0b3f6352c4278fd6896def442e", + "python_file_count": 237, + "source_sha256": "c3796565ff3fd49a8ff3f1e1373d9afab44fd7b98371e44f662c32739fe53d15" + }, + "schema_version": 3, + "scope": { "repository": "MoviePilot-Plugins", "roots": [ "plugins.v2", "plugins.v3" - ], - "source_sha256": "63663538bd7f5b7c6a9f318ab58ad35d8c5ab68bd7d798d07a5dcdf600f062ad" + ] } } diff --git a/tests/fixtures/architecture/runtime-contract-baseline.json b/tests/fixtures/architecture/runtime-contract-baseline.json index a0823de67..865ce667a 100644 --- a/tests/fixtures/architecture/runtime-contract-baseline.json +++ b/tests/fixtures/architecture/runtime-contract-baseline.json @@ -1164,117 +1164,77 @@ "dynamic_consumers": [ { "caller": "app.adapters.system.fsproxy", - "line": 392 + "count": 1 }, { "caller": "app.agent.llm.provider", - "line": 2253 + "count": 1 }, { "caller": "app.chain.transfer", - "line": 616 + "count": 1 }, { "caller": "app.db.oper.transferpending", - "line": 23 + "count": 1 }, { "caller": "app.runtime.events", - "line": 138 - }, - { - "caller": "app.runtime.events", - "line": 461 - }, - { - "caller": "app.runtime.events", - "line": 465 + "count": 3 }, { "caller": "app.testing.bootstrap", - "line": 111 + "count": 1 }, { "caller": "app.workflow", - "line": 311 + "count": 1 } ], "dynamic_producers": [ { "caller": "app.agent.tools.impl._filter_rule_utils", - "line": 433 + "count": 1 }, { "caller": "app.agent.tools.impl.update_system_settings", - "line": 276 + "count": 1 }, { "caller": "app.api.endpoints.system", - "line": 860 - }, - { - "caller": "app.api.endpoints.system", - "line": 974 - }, - { - "caller": "app.api.endpoints.system", - "line": 1028 - }, - { - "caller": "app.api.endpoints.system", - "line": 1042 + "count": 4 }, { "caller": "app.chain._messaging", - "line": 217 - }, - { - "caller": "app.chain._messaging", - "line": 227 - }, - { - "caller": "app.chain._messaging", - "line": 333 - }, - { - "caller": "app.chain._messaging", - "line": 343 + "count": 4 }, { "caller": "app.chain._recognition", - "line": 460 - }, - { - "caller": "app.chain._recognition", - "line": 500 + "count": 2 }, { "caller": "app.chain.user", - "line": 160 - }, - { - "caller": "app.chain.user", - "line": 223 + "count": 2 }, { "caller": "app.command", - "line": 440 + "count": 1 }, { "caller": "app.modules._base.mediaserver", - "line": 56 + "count": 1 }, { "caller": "app.modules.navidrome", - "line": 91 + "count": 1 }, { "caller": "app.modules.plex", - "line": 99 + "count": 1 }, { "caller": "app.runtime.extensions.module_manager", - "line": 238 + "count": 1 } ], "event_count": 53, @@ -1284,7 +1244,7 @@ "producers": [ { "caller": "app.agent.orchestrator", - "line": 1277 + "count": 1 } ] }, @@ -1301,11 +1261,11 @@ "producers": [ { "caller": "app.command", - "line": 267 + "count": 1 }, { "caller": "app.modules._base.notification", - "line": 92 + "count": 1 } ] }, @@ -1314,7 +1274,7 @@ "producers": [ { "caller": "app.api.endpoints.discover", - "line": 31 + "count": 1 } ] }, @@ -1327,11 +1287,7 @@ "producers": [ { "caller": "app.chain.media", - "line": 1502 - }, - { - "caller": "app.chain.media", - "line": 2048 + "count": 2 } ] }, @@ -1344,11 +1300,7 @@ "producers": [ { "caller": "app.chain.media", - "line": 815 - }, - { - "caller": "app.chain.media", - "line": 1753 + "count": 2 } ] }, @@ -1357,11 +1309,7 @@ "producers": [ { "caller": "app.chain.media", - "line": 753 - }, - { - "caller": "app.chain.media", - "line": 1691 + "count": 2 } ] }, @@ -1370,7 +1318,7 @@ "producers": [ { "caller": "app.api.deps", - "line": 384 + "count": 1 } ] }, @@ -1379,11 +1327,11 @@ "producers": [ { "caller": "app.api.endpoints.recommend", - "line": 42 + "count": 1 }, { "caller": "app.workflow.actions.fetch_medias", - "line": 111 + "count": 1 } ] }, @@ -1392,7 +1340,7 @@ "producers": [ { "caller": "app.chain.download", - "line": 1056 + "count": 1 } ] }, @@ -1401,7 +1349,7 @@ "producers": [ { "caller": "app.chain.download", - "line": 1423 + "count": 1 } ] }, @@ -1410,11 +1358,7 @@ "producers": [ { "caller": "app.chain.transfer", - "line": 1102 - }, - { - "caller": "app.chain.transfer", - "line": 1118 + "count": 2 } ] }, @@ -1423,7 +1367,7 @@ "producers": [ { "caller": "app.chain.subscribe", - "line": 2824 + "count": 1 } ] }, @@ -1432,11 +1376,7 @@ "producers": [ { "caller": "app.chain.subscribe", - "line": 3567 - }, - { - "caller": "app.chain.subscribe", - "line": 3589 + "count": 2 } ] }, @@ -1445,11 +1385,7 @@ "producers": [ { "caller": "app.modules.filemanager.transhandler", - "line": 983 - }, - { - "caller": "app.modules.filemanager.transhandler", - "line": 1105 + "count": 2 } ] }, @@ -1458,7 +1394,7 @@ "producers": [ { "caller": "app.modules.filemanager.transhandler", - "line": 509 + "count": 1 } ] }, @@ -1467,7 +1403,7 @@ "producers": [ { "caller": "app.modules.filemanager.transhandler", - "line": 1329 + "count": 1 } ] }, @@ -1476,7 +1412,7 @@ "producers": [ { "caller": "app.modules.filemanager.transhandler", - "line": 1308 + "count": 1 } ] }, @@ -1485,7 +1421,7 @@ "producers": [ { "caller": "app.workflow.actions.send_event", - "line": 46 + "count": 1 } ] }, @@ -1494,7 +1430,7 @@ "producers": [ { "caller": "app.agent.orchestrator", - "line": 870 + "count": 1 } ] }, @@ -1503,7 +1439,7 @@ "producers": [ { "caller": "app.chain.transfer", - "line": 396 + "count": 1 } ] }, @@ -1512,7 +1448,7 @@ "producers": [ { "caller": "app.chain.transfer", - "line": 275 + "count": 1 } ] }, @@ -1520,17 +1456,17 @@ "consumers": [ { "caller": "app.command", - "line": 442 + "count": 1 } ], "producers": [ { "caller": "app.agent.tools.impl.run_slash_command", - "line": 90 + "count": 1 }, { "caller": "app.chain.message", - "line": 443 + "count": 1 } ] }, @@ -1538,15 +1474,15 @@ "consumers": [ { "caller": "app.runtime.extensions.module_manager", - "line": 69 + "count": 1 }, { "caller": "app.runtime.reload", - "line": 81 + "count": 1 }, { "caller": "app.startup.agent_initializer", - "line": 107 + "count": 1 } ], "producers": [] @@ -1556,7 +1492,7 @@ "producers": [ { "caller": "app.chain.download", - "line": 1244 + "count": 1 } ] }, @@ -1565,7 +1501,7 @@ "producers": [ { "caller": "app.chain.download", - "line": 2089 + "count": 1 } ] }, @@ -1573,13 +1509,13 @@ "consumers": [ { "caller": "app.chain.download", - "line": 2073 + "count": 1 } ], "producers": [ { "caller": "app.api.deps", - "line": 370 + "count": 1 } ] }, @@ -1592,19 +1528,11 @@ "producers": [ { "caller": "app.application.messaging.plugin", - "line": 414 - }, - { - "caller": "app.application.messaging.plugin", - "line": 445 - }, - { - "caller": "app.application.messaging.plugin", - "line": 475 + "count": 3 }, { "caller": "app.chain.message", - "line": 687 + "count": 1 } ] }, @@ -1612,17 +1540,13 @@ "consumers": [ { "caller": "app.chain.scraping", - "line": 597 + "count": 1 } ], "producers": [ { "caller": "app.chain._transfer", - "line": 464 - }, - { - "caller": "app.chain._transfer", - "line": 639 + "count": 2 } ] }, @@ -1630,7 +1554,7 @@ "consumers": [ { "caller": "app.command", - "line": 476 + "count": 1 } ], "producers": [] @@ -1639,7 +1563,7 @@ "consumers": [ { "caller": "app.api.endpoints.agent", - "line": 1337 + "count": 1 } ], "producers": [] @@ -1652,7 +1576,7 @@ "consumers": [ { "caller": "app.scheduler", - "line": 1148 + "count": 1 } ], "producers": [] @@ -1665,17 +1589,17 @@ "consumers": [ { "caller": "app.chain.search", - "line": 2864 + "count": 1 }, { "caller": "app.chain.subscribe", - "line": 3044 + "count": 1 } ], "producers": [ { "caller": "app.api.deps", - "line": 216 + "count": 1 } ] }, @@ -1684,11 +1608,7 @@ "producers": [ { "caller": "app.chain.site", - "line": 75 - }, - { - "caller": "app.chain.site", - "line": 164 + "count": 2 } ] }, @@ -1696,29 +1616,21 @@ "consumers": [ { "caller": "app.chain.site", - "line": 587 - }, - { - "caller": "app.chain.site", - "line": 629 - }, - { - "caller": "app.chain.site", - "line": 652 + "count": 3 } ], "producers": [ { "caller": "app.agent.tools.impl.update_site", - "line": 200 + "count": 1 }, { "caller": "app.api.deps", - "line": 211 + "count": 1 }, { "caller": "app.chain.site", - "line": 567 + "count": 1 } ] }, @@ -1727,11 +1639,7 @@ "producers": [ { "caller": "app.chain.subscribe", - "line": 1034 - }, - { - "caller": "app.chain.subscribe", - "line": 1238 + "count": 2 } ] }, @@ -1740,7 +1648,7 @@ "producers": [ { "caller": "app.chain.subscribe", - "line": 2862 + "count": 1 } ] }, @@ -1749,11 +1657,11 @@ "producers": [ { "caller": "app.agent.tools.impl.delete_subscribe", - "line": 70 + "count": 1 }, { "caller": "app.api.deps", - "line": 87 + "count": 1 } ] }, @@ -1762,19 +1670,11 @@ "producers": [ { "caller": "app.agent.tools.impl.update_subscribe", - "line": 316 + "count": 1 }, { "caller": "app.api.endpoints.subscribe", - "line": 296 - }, - { - "caller": "app.api.endpoints.subscribe", - "line": 329 - }, - { - "caller": "app.api.endpoints.subscribe", - "line": 389 + "count": 3 } ] }, @@ -1783,7 +1683,7 @@ "producers": [ { "caller": "app.chain.transfer", - "line": 382 + "count": 1 } ] }, @@ -1792,7 +1692,7 @@ "producers": [ { "caller": "app.chain.transfer", - "line": 261 + "count": 1 } ] }, @@ -1801,19 +1701,15 @@ "producers": [ { "caller": "app.chain", - "line": 137 - }, - { - "caller": "app.chain", - "line": 163 + "count": 2 }, { "caller": "app.runtime.events", - "line": 114 + "count": 1 }, { "caller": "app.scheduler", - "line": 768 + "count": 1 } ] }, @@ -1822,7 +1718,7 @@ "producers": [ { "caller": "app.chain.transfer", - "line": 368 + "count": 1 } ] }, @@ -1831,7 +1727,7 @@ "producers": [ { "caller": "app.chain.transfer", - "line": 247 + "count": 1 } ] }, @@ -1840,7 +1736,7 @@ "producers": [ { "caller": "app.chain.message", - "line": 505 + "count": 1 } ] }, @@ -1849,7 +1745,7 @@ "producers": [ { "caller": "app.chain.webhook", - "line": 21 + "count": 1 } ] }, @@ -1857,13 +1753,13 @@ "consumers": [ { "caller": "app.chain.workflow", - "line": 1161 + "count": 1 } ], "producers": [ { "caller": "app.workflow", - "line": 379 + "count": 1 } ] } @@ -1879,1728 +1775,1633 @@ "anilist_credits": [ { "caller": "app.chain.anilist", - "line": 95, + "count": 1, "mode": "sync" } ], "anilist_discover": [ { "caller": "app.chain.anilist", - "line": 77, + "count": 1, "mode": "sync" } ], "anilist_info": [ { "caller": "app.chain.anilist", - "line": 20, + "count": 1, "mode": "sync" } ], "anilist_person_credits": [ { "caller": "app.chain.anilist", - "line": 164, + "count": 1, "mode": "sync" } ], "anilist_person_detail": [ { "caller": "app.chain.anilist", - "line": 144, + "count": 1, "mode": "sync" } ], "anilist_popular_this_season": [ { "caller": "app.chain.anilist", - "line": 55, + "count": 1, "mode": "sync" } ], "anilist_recommendations": [ { "caller": "app.chain.anilist", - "line": 119, + "count": 1, "mode": "sync" } ], "anilist_trending": [ { "caller": "app.chain.anilist", - "line": 37, + "count": 1, "mode": "sync" } ], "any_files": [ { "caller": "app.chain.storage", - "line": 41, + "count": 1, "mode": "sync" } ], "async_anilist_credits": [ { "caller": "app.chain.anilist", - "line": 107, + "count": 1, "mode": "async" } ], "async_anilist_discover": [ { "caller": "app.chain.anilist", - "line": 85, + "count": 1, "mode": "async" } ], "async_anilist_info": [ { "caller": "app.chain.anilist", - "line": 29, + "count": 1, "mode": "async" } ], "async_anilist_person_credits": [ { "caller": "app.chain.anilist", - "line": 176, + "count": 1, "mode": "async" } ], "async_anilist_person_detail": [ { "caller": "app.chain.anilist", - "line": 152, + "count": 1, "mode": "async" } ], "async_anilist_popular_this_season": [ { "caller": "app.chain.anilist", - "line": 67, + "count": 1, "mode": "async" } ], "async_anilist_recommendations": [ { "caller": "app.chain.anilist", - "line": 131, + "count": 1, "mode": "async" } ], "async_anilist_trending": [ { "caller": "app.chain.anilist", - "line": 45, + "count": 1, "mode": "async" } ], "async_bangumi_calendar": [ { "caller": "app.chain.bangumi", - "line": 65, + "count": 1, "mode": "async" }, { "caller": "app.chain.recommend", - "line": 560, + "count": 1, "mode": "async" } ], "async_bangumi_credits": [ { "caller": "app.chain.bangumi", - "line": 86, + "count": 1, "mode": "async" } ], "async_bangumi_discover": [ { "caller": "app.chain.bangumi", - "line": 71, + "count": 1, "mode": "async" } ], "async_bangumi_info": [ { "caller": "app.chain", - "line": 456, + "count": 1, "mode": "async" }, { "caller": "app.chain.bangumi", - "line": 79, + "count": 1, "mode": "async" } ], "async_bangumi_person_credits": [ { "caller": "app.chain.bangumi", - "line": 107, + "count": 1, "mode": "async" } ], "async_bangumi_person_detail": [ { "caller": "app.chain.bangumi", - "line": 100, + "count": 1, "mode": "async" } ], "async_bangumi_recommend": [ { "caller": "app.chain.bangumi", - "line": 93, + "count": 1, "mode": "async" } ], "async_douban_discover": [ { "caller": "app.chain.douban", - "line": 382, + "count": 1, "mode": "async" }, { "caller": "app.chain.recommend", - "line": 606, - "mode": "async" - }, - { - "caller": "app.chain.recommend", - "line": 617, + "count": 2, "mode": "async" } ], "async_douban_info": [ { "caller": "app.chain", - "line": 393, + "count": 1, "mode": "async" } ], "async_douban_movie_credits": [ { "caller": "app.chain.douban", - "line": 411, + "count": 1, "mode": "async" } ], "async_douban_movie_recommend": [ { "caller": "app.chain.douban", - "line": 425, + "count": 1, "mode": "async" } ], "async_douban_person_credits": [ { "caller": "app.chain.douban", - "line": 339, + "count": 1, "mode": "async" } ], "async_douban_person_detail": [ { "caller": "app.chain.douban", - "line": 331, + "count": 1, "mode": "async" } ], "async_douban_tv_credits": [ { "caller": "app.chain.douban", - "line": 418, + "count": 1, "mode": "async" } ], "async_douban_tv_recommend": [ { "caller": "app.chain.douban", - "line": 432, + "count": 1, "mode": "async" } ], "async_identify_music_by_fingerprint": [ { "caller": "app.chain.acoustid", - "line": 26, + "count": 1, "mode": "async" } ], "async_match_doubaninfo": [ { "caller": "app.chain", - "line": 266, + "count": 1, "mode": "async" } ], "async_match_music_album": [ { "caller": "app.chain.musicbrainz", - "line": 283, + "count": 1, "mode": "async" } ], "async_match_tmdbinfo": [ { "caller": "app.chain", - "line": 308, + "count": 1, "mode": "async" } ], "async_movie_hot": [ { "caller": "app.chain.douban", - "line": 397, + "count": 1, "mode": "async" }, { "caller": "app.chain.recommend", - "line": 663, + "count": 1, "mode": "async" } ], "async_movie_showing": [ { "caller": "app.chain.douban", - "line": 355, + "count": 1, "mode": "async" }, { "caller": "app.chain.recommend", - "line": 569, + "count": 1, "mode": "async" } ], "async_movie_top250": [ { "caller": "app.chain.douban", - "line": 348, + "count": 1, "mode": "async" }, { "caller": "app.chain.recommend", - "line": 627, + "count": 1, "mode": "async" } ], "async_obtain_images": [ { "caller": "app.chain", - "line": 330, + "count": 1, "mode": "async" } ], "async_recognize_media": [ { "caller": "app.chain._recognition", - "line": 108, + "count": 1, "mode": "async" }, { "caller": "app.chain.douban", - "line": 70, + "count": 1, "mode": "async" }, { "caller": "app.chain.musicbrainz", - "line": 63, + "count": 1, "mode": "async" } ], "async_refresh_torrents": [ { "caller": "app.chain", - "line": 688, + "count": 1, "mode": "async" } ], "async_search_collections": [ { "caller": "app.chain", - "line": 562, + "count": 1, "mode": "async" } ], "async_search_medias": [ { "caller": "app.chain", - "line": 510, + "count": 1, "mode": "async" } ], "async_search_persons": [ { "caller": "app.chain", - "line": 536, + "count": 1, "mode": "async" } ], "async_search_subtitles": [ { "caller": "app.chain", - "line": 646, + "count": 1, "mode": "async" } ], "async_search_torrents": [ { "caller": "app.chain", - "line": 629, + "count": 1, "mode": "async" } ], "async_tmdb_collection": [ { "caller": "app.chain.tmdb", - "line": 218, + "count": 1, "mode": "async" } ], "async_tmdb_discover": [ { "caller": "app.chain.recommend", - "line": 498, - "mode": "async" - }, - { - "caller": "app.chain.recommend", - "line": 526, + "count": 2, "mode": "async" }, { "caller": "app.chain.tmdb", - "line": 194, + "count": 1, "mode": "async" } ], "async_tmdb_episodes": [ { "caller": "app.chain.tmdb", - "line": 242, + "count": 1, "mode": "async" } ], "async_tmdb_group_seasons": [ { "caller": "app.chain.tmdb", - "line": 232, + "count": 1, "mode": "async" } ], "async_tmdb_info": [ { "caller": "app.chain", - "line": 438, + "count": 1, "mode": "async" } ], "async_tmdb_movie_credits": [ { "caller": "app.chain.tmdb", - "line": 279, + "count": 1, "mode": "async" } ], "async_tmdb_movie_recommend": [ { "caller": "app.chain.tmdb", - "line": 264, + "count": 1, "mode": "async" } ], "async_tmdb_movie_similar": [ { "caller": "app.chain.tmdb", - "line": 250, + "count": 1, "mode": "async" } ], "async_tmdb_person_credits": [ { "caller": "app.chain.tmdb", - "line": 302, + "count": 1, "mode": "async" } ], "async_tmdb_person_detail": [ { "caller": "app.chain.tmdb", - "line": 294, + "count": 1, "mode": "async" } ], "async_tmdb_seasons": [ { "caller": "app.chain.tmdb", - "line": 225, + "count": 1, "mode": "async" } ], "async_tmdb_trending": [ { "caller": "app.chain.recommend", - "line": 547, + "count": 1, "mode": "async" }, { "caller": "app.chain.tmdb", - "line": 211, + "count": 1, "mode": "async" } ], "async_tmdb_tv_credits": [ { "caller": "app.chain.tmdb", - "line": 287, + "count": 1, "mode": "async" } ], "async_tmdb_tv_recommend": [ { "caller": "app.chain.tmdb", - "line": 271, + "count": 1, "mode": "async" } ], "async_tmdb_tv_similar": [ { "caller": "app.chain.tmdb", - "line": 257, + "count": 1, "mode": "async" } ], "async_tv_animation": [ { "caller": "app.chain.douban", - "line": 390, + "count": 1, "mode": "async" }, { "caller": "app.chain.recommend", - "line": 654, + "count": 1, "mode": "async" } ], "async_tv_hot": [ { "caller": "app.chain.douban", - "line": 404, + "count": 1, "mode": "async" }, { "caller": "app.chain.recommend", - "line": 672, + "count": 1, "mode": "async" } ], "async_tv_weekly_chinese": [ { "caller": "app.chain.douban", - "line": 362, + "count": 1, "mode": "async" }, { "caller": "app.chain.recommend", - "line": 636, + "count": 1, "mode": "async" } ], "async_tv_weekly_global": [ { "caller": "app.chain.douban", - "line": 369, + "count": 1, "mode": "async" }, { "caller": "app.chain.recommend", - "line": 645, + "count": 1, "mode": "async" } ], "async_update_recognize_cache": [ { "caller": "app.chain._recognition", - "line": 78, + "count": 1, "mode": "async" } ], "bangumi_calendar": [ { "caller": "app.chain.bangumi", - "line": 17, + "count": 1, "mode": "sync" } ], "bangumi_credits": [ { "caller": "app.chain.bangumi", - "line": 38, + "count": 1, "mode": "sync" } ], "bangumi_discover": [ { "caller": "app.chain.bangumi", - "line": 23, + "count": 1, "mode": "sync" } ], "bangumi_info": [ { "caller": "app.chain", - "line": 448, + "count": 1, "mode": "sync" }, { "caller": "app.chain.bangumi", - "line": 31, + "count": 1, "mode": "sync" } ], "bangumi_person_credits": [ { "caller": "app.chain.bangumi", - "line": 59, + "count": 1, "mode": "sync" } ], "bangumi_person_detail": [ { "caller": "app.chain.bangumi", - "line": 52, + "count": 1, "mode": "sync" } ], "bangumi_recommend": [ { "caller": "app.chain.bangumi", - "line": 45, + "count": 1, "mode": "sync" } ], "channel_manage": [ { "caller": "app.chain.notification", - "line": 28, + "count": 1, "mode": "sync" } ], "clear_cache": [ { "caller": "app.chain", - "line": 1044, + "count": 1, "mode": "sync" } ], "create_folder": [ { "caller": "app.chain.storage", - "line": 47, + "count": 1, "mode": "sync" } ], "delete_file": [ { "caller": "app.chain.storage", - "line": 77, + "count": 1, "mode": "sync" } ], "delete_message": [ { "caller": "app.chain._messaging", - "line": 410, + "count": 1, "mode": "sync" } ], "douban_discover": [ { "caller": "app.chain.douban", - "line": 277, + "count": 1, "mode": "sync" } ], "douban_info": [ { "caller": "app.chain", - "line": 373, + "count": 1, "mode": "sync" } ], "douban_movie_credits": [ { "caller": "app.chain.douban", - "line": 303, + "count": 1, "mode": "sync" } ], "douban_movie_recommend": [ { "caller": "app.chain.douban", - "line": 317, + "count": 1, "mode": "sync" } ], "douban_person_credits": [ { "caller": "app.chain.douban", - "line": 238, + "count": 1, "mode": "sync" } ], "douban_person_detail": [ { "caller": "app.chain.douban", - "line": 230, + "count": 1, "mode": "sync" } ], "douban_tv_credits": [ { "caller": "app.chain.douban", - "line": 310, + "count": 1, "mode": "sync" } ], "douban_tv_recommend": [ { "caller": "app.chain.douban", - "line": 324, + "count": 1, "mode": "sync" } ], "download": [ { "caller": "app.chain", - "line": 733, + "count": 1, "mode": "sync" } ], "download_added": [ { "caller": "app.chain", - "line": 757, + "count": 1, "mode": "sync" } ], "download_discord_file_bytes": [ { "caller": "app.chain.message", - "line": 1412, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1742, + "count": 2, "mode": "sync" } ], "download_feishu_file_bytes": [ { "caller": "app.chain.message", - "line": 1446, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1734, + "count": 2, "mode": "sync" } ], "download_feishu_image_to_data_url": [ { "caller": "app.chain.message", - "line": 1553, + "count": 1, "mode": "sync" } ], "download_file": [ { "caller": "app.chain.storage", - "line": 61, + "count": 1, "mode": "sync" } ], "download_qq_file_bytes": [ { "caller": "app.chain.message", - "line": 1419, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1746, + "count": 2, "mode": "sync" } ], "download_slack_file_bytes": [ { "caller": "app.chain.message", - "line": 1405, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1738, + "count": 2, "mode": "sync" } ], "download_slack_file_to_data_url": [ { "caller": "app.chain.message", - "line": 1561, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1759, + "count": 2, "mode": "sync" } ], "download_synologychat_file_bytes": [ { "caller": "app.chain.message", - "line": 1435, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1754, + "count": 2, "mode": "sync" } ], "download_telegram_file_bytes": [ { "caller": "app.chain.message", - "line": 1378, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1384, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1699, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1704, + "count": 4, "mode": "sync" } ], "download_telegram_file_to_base64": [ { "caller": "app.chain.message", - "line": 1531, + "count": 1, "mode": "sync" } ], "download_vocechat_file_bytes": [ { "caller": "app.chain.message", - "line": 1426, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1750, + "count": 2, "mode": "sync" } ], "download_vocechat_image_to_data_url": [ { "caller": "app.chain.message", - "line": 1569, + "count": 1, "mode": "sync" } ], "download_wechat_image_to_data_url": [ { "caller": "app.chain.message", - "line": 1545, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1716, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1721, + "count": 3, "mode": "sync" } ], "download_wechat_media_bytes": [ { "caller": "app.chain.message", - "line": 1389, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1396, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1708, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1712, - "mode": "sync" - }, - { - "caller": "app.chain.message", - "line": 1730, + "count": 5, "mode": "sync" } ], "downloader_info": [ { "caller": "app.chain.dashboard", - "line": 22, + "count": 1, "mode": "sync" } ], "edit_message": [ { "caller": "app.chain._messaging", - "line": 456, + "count": 1, "mode": "sync" } ], "filter_torrents": [ { "caller": "app.chain", - "line": 705, + "count": 1, "mode": "sync" } ], "finalize_message": [ { "caller": "app.chain._messaging", - "line": 488, + "count": 1, "mode": "sync" } ], "get_file_item": [ { "caller": "app.chain.storage", - "line": 101, + "count": 1, "mode": "sync" } ], "get_folder": [ { "caller": "app.chain.storage", - "line": 53, + "count": 1, "mode": "sync" } ], "get_parent_item": [ { "caller": "app.chain.storage", - "line": 107, + "count": 1, "mode": "sync" } ], "get_search_page_size": [ { "caller": "app.chain", - "line": 574, + "count": 1, "mode": "sync" } ], "get_torrent_trackers": [ { "caller": "app.chain", - "line": 951, + "count": 1, "mode": "sync" } ], "identify_music_by_fingerprint": [ { "caller": "app.chain.acoustid", - "line": 15, + "count": 1, "mode": "sync" } ], "list_files": [ { "caller": "app.chain.storage", - "line": 35, + "count": 1, "mode": "sync" } ], "list_torrents": [ { "caller": "app.chain", - "line": 779, + "count": 1, "mode": "sync" } ], "load_category_config": [ { "caller": "app.chain", - "line": 1020, + "count": 1, "mode": "sync" } ], "mark_message_processing_finished": [ { "caller": "app.chain._messaging", - "line": 85, + "count": 1, "mode": "sync" } ], "mark_message_processing_started": [ { "caller": "app.chain._messaging", - "line": 47, + "count": 1, "mode": "sync" } ], "match_doubaninfo": [ { "caller": "app.chain", - "line": 238, + "count": 1, "mode": "sync" } ], "match_music_album": [ { "caller": "app.chain.musicbrainz", - "line": 268, + "count": 1, "mode": "sync" } ], "match_tmdbinfo": [ { "caller": "app.chain", - "line": 290, + "count": 1, "mode": "sync" } ], "media_category": [ { "caller": "app.chain", - "line": 1014, + "count": 1, "mode": "sync" } ], "media_exists": [ { "caller": "app.chain", - "line": 981, + "count": 1, "mode": "sync" } ], "media_files": [ { "caller": "app.chain", - "line": 991, + "count": 1, "mode": "sync" } ], "media_statistic": [ { "caller": "app.chain.dashboard", - "line": 16, + "count": 1, "mode": "sync" }, { "caller": "app.chain.mediaserver", - "line": 163, + "count": 1, "mode": "sync" } ], "mediaserver_image_cookies": [ { "caller": "app.chain.mediaserver", - "line": 269, + "count": 1, "mode": "sync" } ], "mediaserver_iteminfo": [ { "caller": "app.chain.mediaserver", - "line": 177, + "count": 1, "mode": "sync" } ], "mediaserver_items": [ { "caller": "app.chain.mediaserver", - "line": 139, + "count": 1, "mode": "sync" } ], "mediaserver_items_count": [ { "caller": "app.chain.mediaserver", - "line": 150, + "count": 1, "mode": "sync" } ], "mediaserver_latest": [ { "caller": "app.chain.mediaserver", - "line": 205, + "count": 1, "mode": "sync" } ], "mediaserver_latest_images": [ { "caller": "app.chain.mediaserver", - "line": 218, + "count": 1, "mode": "sync" } ], "mediaserver_librarys": [ { "caller": "app.chain.mediaserver", - "line": 92, + "count": 1, "mode": "sync" } ], "mediaserver_play_url": [ { "caller": "app.chain.mediaserver", - "line": 243, + "count": 1, "mode": "sync" } ], "mediaserver_playing": [ { "caller": "app.chain.mediaserver", - "line": 191, + "count": 1, "mode": "sync" } ], "mediaserver_season_episode_ids": [ { "caller": "app.chain.mediaserver", - "line": 255, + "count": 1, "mode": "sync" } ], "mediaserver_tv_episodes": [ { "caller": "app.chain.mediaserver", - "line": 183, + "count": 1, "mode": "sync" } ], "message_parser": [ { "caller": "app.chain", - "line": 472, + "count": 1, "mode": "sync" } ], "metadata_img": [ { "caller": "app.chain", - "line": 1005, + "count": 1, "mode": "sync" }, { "caller": "app.chain.scraping", - "line": 573, + "count": 1, "mode": "sync" } ], "metadata_nfo": [ { "caller": "app.chain.scraping", - "line": 552, + "count": 1, "mode": "sync" } ], "movie_hot": [ { "caller": "app.chain.douban", - "line": 290, + "count": 1, "mode": "sync" } ], "movie_showing": [ { "caller": "app.chain.douban", - "line": 252, + "count": 1, "mode": "sync" } ], "movie_top250": [ { "caller": "app.chain.douban", - "line": 246, + "count": 1, "mode": "sync" } ], "music_album": [ { "caller": "app.chain.douban", - "line": 86, - "mode": "sync" + "count": 1, + "mode": "async" }, { "caller": "app.chain.douban", - "line": 98, - "mode": "async" - }, - { - "caller": "app.chain.musicbrainz", - "line": 79, + "count": 1, "mode": "sync" }, { "caller": "app.chain.musicbrainz", - "line": 91, + "count": 1, "mode": "async" + }, + { + "caller": "app.chain.musicbrainz", + "count": 1, + "mode": "sync" } ], "music_album_related": [ { "caller": "app.chain.douban", - "line": 114, + "count": 1, "mode": "async" }, { "caller": "app.chain.musicbrainz", - "line": 107, + "count": 1, "mode": "async" } ], "music_artist": [ { "caller": "app.chain.musicbrainz", - "line": 120, + "count": 1, "mode": "async" } ], "music_artist_albums": [ { "caller": "app.chain.musicbrainz", - "line": 138, + "count": 1, "mode": "async" } ], "music_artist_related": [ { "caller": "app.chain.musicbrainz", - "line": 157, + "count": 1, "mode": "async" } ], "music_cache_clear": [ { "caller": "app.chain.musicbrainz", - "line": 303, + "count": 1, "mode": "sync" } ], "music_cache_delete": [ { "caller": "app.chain.musicbrainz", - "line": 298, + "count": 1, "mode": "sync" } ], "music_cache_items": [ { "caller": "app.chain.musicbrainz", - "line": 293, + "count": 1, "mode": "sync" } ], "music_chart": [ { "caller": "app.chain.listenbrainz", - "line": 34, - "mode": "sync" + "count": 1, + "mode": "async" }, { "caller": "app.chain.listenbrainz", - "line": 51, - "mode": "async" + "count": 1, + "mode": "sync" } ], "music_discover": [ { "caller": "app.chain.douban", - "line": 132, - "mode": "sync" + "count": 1, + "mode": "async" }, { "caller": "app.chain.douban", - "line": 154, - "mode": "async" + "count": 1, + "mode": "sync" } ], "music_fresh_releases": [ { "caller": "app.chain.listenbrainz", - "line": 70, + "count": 1, "mode": "async" } ], "music_lyrics": [ { "caller": "app.chain.lrclib", - "line": 16, - "mode": "sync" + "count": 1, + "mode": "async" }, { "caller": "app.chain.lrclib", - "line": 26, - "mode": "async" + "count": 1, + "mode": "sync" } ], "obtain_images": [ { "caller": "app.chain", - "line": 320, + "count": 1, "mode": "sync" } ], "obtain_specific_image": [ { "caller": "app.chain", - "line": 350, + "count": 1, "mode": "sync" } ], "recognize_media": [ { "caller": "app.chain._recognition", - "line": 99, + "count": 1, "mode": "sync" }, { "caller": "app.chain.douban", - "line": 50, + "count": 1, "mode": "sync" }, { "caller": "app.chain.musicbrainz", - "line": 43, + "count": 1, "mode": "sync" } ], "recommend_name": [ { "caller": "app.chain._transfer", - "line": 672, - "mode": "sync" - }, - { - "caller": "app.chain._transfer", - "line": 679, + "count": 2, "mode": "sync" } ], "refresh_torrents": [ { "caller": "app.chain", - "line": 667, + "count": 1, "mode": "sync" } ], "refresh_userdata": [ { "caller": "app.chain.site", - "line": 69, + "count": 1, "mode": "sync" } ], "register_commands": [ { "caller": "app.chain", - "line": 1032, + "count": 1, "mode": "sync" } ], "remove_torrents": [ { "caller": "app.chain", - "line": 861, + "count": 1, "mode": "sync" } ], "rename_file": [ { "caller": "app.chain.storage", - "line": 83, + "count": 1, "mode": "sync" } ], "save_category_config": [ { "caller": "app.chain", - "line": 1026, + "count": 1, "mode": "sync" } ], "scheduler_job": [ { "caller": "app.chain", - "line": 1038, + "count": 1, "mode": "sync" } ], "search_collections": [ { "caller": "app.chain", - "line": 549, + "count": 1, "mode": "sync" } ], "search_medias": [ { "caller": "app.chain", - "line": 497, + "count": 1, "mode": "sync" } ], "search_music": [ { "caller": "app.chain.douban", - "line": 19, - "mode": "sync" + "count": 1, + "mode": "async" }, { "caller": "app.chain.douban", - "line": 33, - "mode": "async" - }, - { - "caller": "app.chain.musicbrainz", - "line": 16, + "count": 1, "mode": "sync" }, { "caller": "app.chain.musicbrainz", - "line": 26, + "count": 1, "mode": "async" + }, + { + "caller": "app.chain.musicbrainz", + "count": 1, + "mode": "sync" } ], "search_persons": [ { "caller": "app.chain", - "line": 523, + "count": 1, "mode": "sync" } ], "search_subtitles": [ { "caller": "app.chain", - "line": 610, + "count": 1, "mode": "sync" } ], "search_torrents": [ { "caller": "app.chain", - "line": 593, + "count": 1, "mode": "sync" } ], "search_tvdb": [ { "caller": "app.chain.tvdb", - "line": 12, + "count": 1, "mode": "sync" } ], "send_direct_message": [ { "caller": "app.chain._messaging", - "line": 475, + "count": 1, "mode": "sync" } ], "set_torrents_tag": [ { "caller": "app.chain", - "line": 900, + "count": 1, "mode": "sync" } ], "site_subtitle_links": [ { "caller": "app.chain.download", - "line": 571, + "count": 1, "mode": "sync" } ], "snapshot_storage": [ { "caller": "app.chain.storage", - "line": 120, + "count": 1, "mode": "sync" } ], "start_torrents": [ { "caller": "app.chain", - "line": 877, + "count": 1, "mode": "sync" } ], "stop_torrents": [ { "caller": "app.chain", - "line": 888, + "count": 1, "mode": "sync" } ], "storage_manage": [ { "caller": "app.chain.storage", - "line": 28, + "count": 1, "mode": "sync" } ], "tmdb_cache_clear": [ { "caller": "app.chain.tmdb", - "line": 344, + "count": 1, "mode": "sync" } ], "tmdb_cache_delete": [ { "caller": "app.chain.tmdb", - "line": 337, + "count": 1, "mode": "sync" } ], "tmdb_cache_items": [ { "caller": "app.chain.tmdb", - "line": 330, + "count": 1, "mode": "sync" } ], "tmdb_collection": [ { "caller": "app.chain.tmdb", - "line": 64, + "count": 1, "mode": "sync" } ], "tmdb_discover": [ { "caller": "app.chain.tmdb", - "line": 40, + "count": 1, "mode": "sync" } ], "tmdb_episodes": [ { "caller": "app.chain._transfer", - "line": 665, + "count": 1, "mode": "sync" }, { "caller": "app.chain.tmdb", - "line": 87, + "count": 1, "mode": "sync" } ], "tmdb_group_seasons": [ { "caller": "app.chain.tmdb", - "line": 78, + "count": 1, "mode": "sync" } ], "tmdb_info": [ { "caller": "app.chain", - "line": 426, + "count": 1, "mode": "sync" } ], "tmdb_movie_credits": [ { "caller": "app.chain.tmdb", - "line": 123, + "count": 1, "mode": "sync" } ], "tmdb_movie_recommend": [ { "caller": "app.chain.tmdb", - "line": 108, + "count": 1, "mode": "sync" } ], "tmdb_movie_similar": [ { "caller": "app.chain.tmdb", - "line": 94, + "count": 1, "mode": "sync" } ], "tmdb_person_credits": [ { "caller": "app.chain.tmdb", - "line": 146, + "count": 1, "mode": "sync" } ], "tmdb_person_detail": [ { "caller": "app.chain.tmdb", - "line": 138, + "count": 1, "mode": "sync" } ], "tmdb_seasons": [ { "caller": "app.chain.tmdb", - "line": 71, + "count": 1, "mode": "sync" } ], "tmdb_trending": [ { "caller": "app.chain.tmdb", - "line": 57, + "count": 1, "mode": "sync" } ], "tmdb_tv_credits": [ { "caller": "app.chain.tmdb", - "line": 131, + "count": 1, "mode": "sync" } ], "tmdb_tv_recommend": [ { "caller": "app.chain.tmdb", - "line": 115, + "count": 1, "mode": "sync" } ], "tmdb_tv_similar": [ { "caller": "app.chain.tmdb", - "line": 101, + "count": 1, "mode": "sync" } ], "torrent_files": [ { "caller": "app.chain", - "line": 966, + "count": 1, "mode": "sync" } ], "transfer": [ { "caller": "app.chain", - "line": 822, + "count": 1, "mode": "sync" } ], "transfer_completed": [ { "caller": "app.chain", - "line": 846, + "count": 1, "mode": "sync" } ], "tv_animation": [ { "caller": "app.chain.douban", - "line": 284, + "count": 1, "mode": "sync" } ], "tv_hot": [ { "caller": "app.chain.douban", - "line": 296, + "count": 1, "mode": "sync" } ], "tv_weekly_chinese": [ { "caller": "app.chain.douban", - "line": 258, + "count": 1, "mode": "sync" } ], "tv_weekly_global": [ { "caller": "app.chain.douban", - "line": 264, + "count": 1, "mode": "sync" } ], "tvdb_info": [ { "caller": "app.chain", - "line": 406, + "count": 1, "mode": "sync" } ], "tvdb_slug": [ { "caller": "app.chain", - "line": 414, + "count": 1, "mode": "sync" } ], "update_recognize_cache": [ { "caller": "app.chain._recognition", - "line": 62, + "count": 1, "mode": "sync" } ], "update_torrent": [ { "caller": "app.chain", - "line": 927, + "count": 1, "mode": "sync" } ], "upload_file": [ { "caller": "app.chain.storage", - "line": 71, + "count": 1, "mode": "sync" } ], "user_authenticate": [ { "caller": "app.chain.user", - "line": 156, + "count": 1, "mode": "sync" } ], "webhook_parser": [ { "caller": "app.chain", - "line": 486, + "count": 1, "mode": "sync" } ] } }, - "schema_version": 1, + "schema_version": 2, "sdk_exports": { "app.sdk": [], "app.sdk.browser": [ diff --git a/tests/test_architecture_baseline_cli.py b/tests/test_architecture_baseline_cli.py index fe29e0e25..e6741e462 100644 --- a/tests/test_architecture_baseline_cli.py +++ b/tests/test_architecture_baseline_cli.py @@ -71,6 +71,133 @@ def test_architecture_plugin_action_requires_repository(capsys): assert "必须指定 --plugin-repo" in capsys.readouterr().err +def test_architecture_diagnostics_only_support_host_check(capsys): + """源码位置诊断不得与写操作或插件扫描混用。""" + with pytest.raises(SystemExit) as error: + architecture_baseline.parse_args(["--write-host", "--diagnostics"]) + + assert error.value.code == 2 + assert "只能与 --check-host" in capsys.readouterr().err + + +def test_runtime_semantics_ignore_line_changes_but_keep_call_count(tmp_path: Path): + """旧 fixture 的行号变化不影响门禁,重复调用次数仍属于语义。""" + baseline_path = tmp_path / "runtime-contract-baseline.json" + old_value = { + "schema_version": 1, + "run_module": { + "method_count": 1, + "call_count": 2, + "dynamic_call_count": 0, + "methods": { + "search": [ + {"caller": "app.chain.search", "line": 10, "mode": "sync"}, + {"caller": "app.chain.search", "line": 11, "mode": "sync"}, + ] + }, + "dynamic_calls": [], + }, + "events": { + "event_count": 1, + "producer_count": 1, + "consumer_count": 0, + "events": { + "EventType.NoticeMessage": { + "producers": [{"caller": "app.chain.message", "line": 20}], + "consumers": [], + } + }, + "dynamic_producers": [], + "dynamic_consumers": [], + }, + "sdk_exports": {}, + "compat_manifest": {}, + } + moved_value = json.loads(json.dumps(old_value)) + moved_value["run_module"]["methods"]["search"][0]["line"] = 1000 + moved_value["run_module"]["methods"]["search"][1]["line"] = 1001 + moved_value["events"]["events"]["EventType.NoticeMessage"]["producers"][0][ + "line" + ] = 2000 + + old_semantic = architecture_baseline.semantic_baseline(baseline_path, old_value) + moved_semantic = architecture_baseline.semantic_baseline( + baseline_path, + moved_value, + ) + + assert old_semantic == moved_semantic + assert old_semantic["run_module"]["methods"]["search"] == [ + {"caller": "app.chain.search", "mode": "sync", "count": 2} + ] + + +def test_plugin_provenance_does_not_participate_in_semantic_gate(tmp_path: Path): + """插件仓 revision 和源码摘要变化不应伪装成 ABI 变化。""" + baseline_path = tmp_path / "official-plugin-baseline.json" + first = { + "schema_version": 3, + "scope": {"repository": "MoviePilot-Plugins", "roots": ["plugins.v3"]}, + "provenance": { + "head": "a" * 40, + "python_file_count": 1, + "source_sha256": "a" * 64, + }, + "imports": {}, + "hooks": {}, + "api_routes": {}, + } + second = json.loads(json.dumps(first)) + second["provenance"] = { + "head": "b" * 40, + "python_file_count": 2, + "source_sha256": "b" * 64, + } + + assert architecture_baseline.semantic_baseline( + baseline_path, + first, + ) == architecture_baseline.semantic_baseline(baseline_path, second) + + +def test_plugin_v2_fixture_migrates_before_semantic_comparison(tmp_path: Path): + """旧插件 fixture 应在内存中迁移,不能因 schema 排列变化误报 ABI。""" + baseline_path = tmp_path / "official-plugin-baseline.json" + old_value = { + "schema_version": 2, + "source": { + "repository": "MoviePilot-Plugins", + "head": "a" * 40, + "roots": ["plugins.v2", "plugins.v3"], + "python_file_count": 1, + "source_sha256": "a" * 64, + }, + "imports": {"app.sdk.logging": {"file_count": 1, "files": ["x.py"]}}, + "hooks": {}, + "api_routes": {}, + } + new_value = { + "schema_version": 3, + "scope": { + "repository": "MoviePilot-Plugins", + "roots": ["plugins.v2", "plugins.v3"], + }, + "provenance": { + "head": "b" * 40, + "python_file_count": 2, + "source_sha256": "b" * 64, + }, + "imports": old_value["imports"], + "hooks": {}, + "api_routes": {}, + } + + assert architecture_baseline.semantic_baseline( + baseline_path, + old_value, + ) == architecture_baseline.semantic_baseline(baseline_path, new_value) + + def test_architecture_write_host_only_updates_host_files( tmp_path: Path, monkeypatch, diff --git a/tests/test_architecture_contract_baseline.py b/tests/test_architecture_contract_baseline.py index aa3970c9f..d8304bd23 100644 --- a/tests/test_architecture_contract_baseline.py +++ b/tests/test_architecture_contract_baseline.py @@ -41,9 +41,10 @@ def test_official_plugin_baseline_records_external_source(): baseline_path = BASELINE_ROOT / "official-plugin-baseline.json" baseline = json.loads(baseline_path.read_text(encoding="utf-8")) - assert baseline["source"]["repository"] == "MoviePilot-Plugins" - assert len(baseline["source"]["head"]) == 40 - assert baseline["source"]["roots"] == ["plugins.v2", "plugins.v3"] + assert baseline["schema_version"] == 3 + assert baseline["scope"]["repository"] == "MoviePilot-Plugins" + assert baseline["scope"]["roots"] == ["plugins.v2", "plugins.v3"] + assert len(baseline["provenance"]["head"]) == 40 assert all( not path.startswith("app/plugins/") for contract in (*baseline["imports"].values(), *baseline["hooks"].values()) @@ -95,6 +96,15 @@ def test_startup_performance_baseline_records_all_cold_import_targets(): assert contract["loaded_module_count"] > 0 +def test_runtime_contract_baseline_excludes_diagnostic_line_numbers(): + """运行契约 fixture 只保存稳定语义,源码位置必须按需诊断。""" + baseline_path = BASELINE_ROOT / "runtime-contract-baseline.json" + baseline = json.loads(baseline_path.read_text(encoding="utf-8")) + + assert baseline["schema_version"] == 2 + assert '"line"' not in json.dumps(baseline) + + def test_startup_performance_baseline_records_normal_and_safe_lifecycle_resources(): """非功能基线必须同时记录正常/安全模式和隔离资源增量。""" baseline_path = BASELINE_ROOT / "startup-performance-baseline.json"