Files
MoviePilot/app/runtime/extensions/module/quality.py
T

164 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""宿主 Module 的渐进式集成质量清单。"""
from __future__ import annotations
from dataclasses import dataclass
from enum import StrEnum
class ModuleQualityLevel(StrEnum):
"""描述模块当前接受的质量门禁等级。"""
LEGACY = "legacy"
ASSESSED = "assessed"
@dataclass(frozen=True, slots=True)
class ModuleQualityProfile:
"""记录一个模块已验证规则、维护者和精确豁免原因。"""
module: str
level: ModuleQualityLevel
owner: str
verified_rules: frozenset[str] = frozenset()
exemption_reason: str | None = None
QUALITY_RULES = frozenset(
{
"fake-client-or-fixture",
"zero-real-network-tests",
"sync-async-boundary",
"no-blocking-io-in-event-loop",
"auth-rate-timeout-offline-semantics",
"bounded-concurrency-or-polling",
"reload-stop-idempotent",
"module-contract-v2",
"sensitive-log-redaction",
"owner-declared",
}
)
COMMON_ASSESSED_RULES = frozenset(
{
"zero-real-network-tests",
"no-blocking-io-in-event-loop",
"module-contract-v2",
"owner-declared",
}
)
# 这些模块均已纳入全局真实网络守卫、async 阻塞扫描和 V2 方法契约门禁。
# 模块专属的鉴权、限流、并发和生命周期规则仍按 profile 中的精确豁免逐项补强,
# 但不再使用无法区分“未审查”和“已审查有缺口”的 legacy 状态。
BASELINE_ASSESSED_MODULES = frozenset(
{
"acoustid",
"anilist",
"discord",
"douban",
"emby",
"fanart",
"feishu",
"filemanager",
"filter",
"imdb",
"indexer",
"jellyfin",
"listenbrainz",
"lrclib",
"musicbrainz",
"navidrome",
"plex",
"postgresql",
"qbittorrent",
"qqbot",
"redis",
"rtorrent",
"slack",
"subtitle",
"synologychat",
"telegram",
"theaudiodb",
"themoviedb",
"thetvdb",
"transmission",
"trimemedia",
"ugreen",
"vocechat",
"webpush",
"wechat",
"wechatclawbot",
"zspace",
}
)
MODULE_QUALITY_PROFILES = {
module: ModuleQualityProfile(
module=module,
level=ModuleQualityLevel.ASSESSED,
owner="MoviePilot core",
verified_rules=COMMON_ASSESSED_RULES,
exemption_reason=(
"已完成宿主通用网络、异步阻塞、V2 方法契约与维护责任门禁;"
"模块专属鉴权、限流、并发、敏感日志及 reload/stop 语义只在有对应能力时适用,"
"继续由各模块专项测试证明"
),
)
for module in BASELINE_ASSESSED_MODULES
}
MODULE_QUALITY_PROFILES.update({
"bangumi": ModuleQualityProfile(
module="bangumi",
level=ModuleQualityLevel.ASSESSED,
owner="MoviePilot core",
verified_rules=frozenset(
{
"fake-client-or-fixture",
"zero-real-network-tests",
"sync-async-boundary",
"reload-stop-idempotent",
"module-contract-v2",
"sensitive-log-redaction",
"owner-declared",
}
),
exemption_reason=(
"外部 Bangumi API 的限流与并发策略仍沿用通用 HTTP adapter"
"本轮仅对配置快照改动面启用 assessed 门禁"
),
),
"dingtalk": ModuleQualityProfile(
module="dingtalk",
level=ModuleQualityLevel.ASSESSED,
owner="MoviePilot core",
verified_rules=frozenset(
{
"fake-client-or-fixture",
"zero-real-network-tests",
"reload-stop-idempotent",
"module-contract-v2",
"sensitive-log-redaction",
"owner-declared",
}
),
exemption_reason=(
"钉钉自定义机器人仅提供同步出站 Webhook,不包含长连接、轮询或入站回调"
),
),
})
def get_module_quality_profile(module: str) -> ModuleQualityProfile:
"""返回显式 profile;未迁移模块以带原因的 legacy 视图呈现。"""
return MODULE_QUALITY_PROFILES.get(
module,
ModuleQualityProfile(
module=module,
level=ModuleQualityLevel.LEGACY,
owner="MoviePilot core",
exemption_reason="存量模块尚未在二阶段任务中修改,按渐进策略暂不提升门禁",
),
)