mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-06 07:09:09 +08:00
Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
"""插件市场候选事实与来源选择策略。"""
|
||||
"""
|
||||
插件市场候选事实与来源选择策略。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -25,7 +27,9 @@ LOCAL_PLUGIN_SOURCE_PREFIX = "local://"
|
||||
|
||||
|
||||
def _build_public_local_source(plugin_id: str, package_generation: str) -> str:
|
||||
"""生成不携带宿主路径的本地候选标识。"""
|
||||
"""
|
||||
生成不携带宿主路径的本地候选标识。
|
||||
"""
|
||||
source = f"{LOCAL_PLUGIN_SOURCE_PREFIX}{quote(plugin_id, safe='')}"
|
||||
if package_generation != "v1":
|
||||
source += f"?version={quote(package_generation, safe='')}"
|
||||
@@ -33,29 +37,53 @@ def _build_public_local_source(plugin_id: str, package_generation: str) -> str:
|
||||
|
||||
|
||||
class MarketReadStatus(StrEnum):
|
||||
"""一次市场索引读取的最终状态。"""
|
||||
"""
|
||||
一次市场索引读取的最终状态。
|
||||
"""
|
||||
|
||||
# 索引读取成功并存在,可返回空候选集合
|
||||
PRESENT = "present"
|
||||
# 已确认该市场没有请求代际的索引
|
||||
ABSENT = "absent"
|
||||
# 索引读取失败,不能当作索引不存在
|
||||
FAILED = "failed"
|
||||
|
||||
|
||||
class PluginSelectionStatus(StrEnum):
|
||||
"""插件候选选择的可观察结果。"""
|
||||
"""
|
||||
插件候选选择的可观察结果。
|
||||
"""
|
||||
|
||||
# 已选定唯一符合来源策略的候选
|
||||
SELECTED = "selected"
|
||||
# 没有符合条件的可用候选
|
||||
UNAVAILABLE = "unavailable"
|
||||
# 多个来源存在歧义或违反既有来源绑定
|
||||
CONFLICT = "conflict"
|
||||
# 市场或本地读取不完整,无法安全选择
|
||||
INCOMPLETE = "incomplete"
|
||||
|
||||
|
||||
class PluginSourceSelectionError(RuntimeError):
|
||||
"""插件来源选择的策略错误。"""
|
||||
"""
|
||||
插件来源选择的策略错误。
|
||||
"""
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PluginMarketCandidate:
|
||||
"""一个在线市场条目的原始候选事实。"""
|
||||
"""
|
||||
一个在线市场条目的原始候选事实。
|
||||
|
||||
:param plugin_id: 物理插件标识,不使用分身实例标识
|
||||
:param source_key: 经规范化的在线仓库身份键,格式为 github:所有者/仓库
|
||||
:param source_type: 可信在线来源类型,区分官方与第三方
|
||||
:param repo_url: 插件候选所在仓库的访问地址
|
||||
:param package_generation: 插件包代际目录,例如 v1、v2 或 v3
|
||||
:param plugin_version: 插件声明的业务版本;未提供时为 None
|
||||
:param dto: 市场或本地扫描得到的原始元数据;未提供时为 None
|
||||
:ivar normalized_plugin_id: 按小写规范化的物理插件身份键,由初始化自动计算,不接受构造参数
|
||||
"""
|
||||
|
||||
plugin_id: str
|
||||
source_key: str
|
||||
@@ -67,7 +95,9 @@ class PluginMarketCandidate:
|
||||
normalized_plugin_id: str = field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""校验候选身份,并把外部来源键归一为持久化合同使用的形式。"""
|
||||
"""
|
||||
校验候选身份,并把外部来源键归一为持久化合同使用的形式。
|
||||
"""
|
||||
normalized_id = normalize_physical_plugin_id(self.plugin_id)
|
||||
source_type = _coerce_online_source_type(self.source_type)
|
||||
source_key = validate_online_source_key(self.source_key)
|
||||
@@ -85,11 +115,15 @@ class PluginMarketCandidate:
|
||||
|
||||
@property
|
||||
def payload_source_type(self) -> PluginPayloadSourceType:
|
||||
"""返回用于载荷审计的在线来源类型。"""
|
||||
"""
|
||||
返回用于载荷审计的在线来源类型。
|
||||
"""
|
||||
return PluginPayloadSourceType(self.source_type.value)
|
||||
|
||||
def public_dict(self) -> dict[str, Any]:
|
||||
"""生成不携带原始元数据的公共候选投影。"""
|
||||
"""
|
||||
生成不携带原始元数据的公共候选投影。
|
||||
"""
|
||||
return {
|
||||
"plugin_id": self.plugin_id,
|
||||
"source_key": self.source_key,
|
||||
@@ -102,7 +136,16 @@ class PluginMarketCandidate:
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PluginLocalCandidate:
|
||||
"""一个本地插件载荷候选,与在线来源身份保持独立。"""
|
||||
"""
|
||||
一个本地插件载荷候选,与在线来源身份保持独立。
|
||||
|
||||
:param plugin_id: 物理插件标识,不使用分身实例标识
|
||||
:param repo_url: 本地载荷仓库引用,仅在宿主内部使用,不直接对外公开
|
||||
:param package_generation: 插件包代际目录,例如 v1、v2 或 v3
|
||||
:param plugin_version: 插件声明的业务版本;未提供时为 None
|
||||
:param dto: 市场或本地扫描得到的原始元数据;未提供时为 None
|
||||
:ivar normalized_plugin_id: 按小写规范化的物理插件身份键,由初始化自动计算,不接受构造参数
|
||||
"""
|
||||
|
||||
plugin_id: str
|
||||
repo_url: str
|
||||
@@ -112,7 +155,9 @@ class PluginLocalCandidate:
|
||||
normalized_plugin_id: str = field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""校验本地载荷的插件与版本事实。"""
|
||||
"""
|
||||
校验本地载荷的插件与版本事实。
|
||||
"""
|
||||
normalized_id = normalize_physical_plugin_id(self.plugin_id)
|
||||
repo_url = _normalize_repo_url(self.repo_url)
|
||||
package_generation = normalize_package_generation(self.package_generation)
|
||||
@@ -124,21 +169,29 @@ class PluginLocalCandidate:
|
||||
|
||||
@property
|
||||
def payload_source_type(self) -> PluginPayloadSourceType:
|
||||
"""返回本地载荷类型,不把本地路径伪装成在线来源。"""
|
||||
"""
|
||||
返回本地载荷类型,不把本地路径伪装成在线来源。
|
||||
"""
|
||||
return PluginPayloadSourceType.LOCAL
|
||||
|
||||
@property
|
||||
def source_type(self) -> PluginPayloadSourceType:
|
||||
"""返回独立的本地载荷类型,不伪造在线可信来源。"""
|
||||
"""
|
||||
返回独立的本地载荷类型,不伪造在线可信来源。
|
||||
"""
|
||||
return PluginPayloadSourceType.LOCAL
|
||||
|
||||
@property
|
||||
def source_key(self) -> None:
|
||||
"""本地载荷没有可绑定的在线来源键。"""
|
||||
"""
|
||||
本地载荷没有可绑定的在线来源键。
|
||||
"""
|
||||
return None
|
||||
|
||||
def public_dict(self) -> dict[str, Any]:
|
||||
"""生成不包含本地仓库路径的公共候选投影。"""
|
||||
"""
|
||||
生成不包含本地仓库路径的公共候选投影。
|
||||
"""
|
||||
public_repo_url = _build_public_local_source(
|
||||
self.plugin_id,
|
||||
self.package_generation,
|
||||
@@ -154,7 +207,15 @@ class PluginLocalCandidate:
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class MarketRead:
|
||||
"""记录一个配置市场的读取状态及其全部在线候选。"""
|
||||
"""
|
||||
记录一个配置市场的读取状态及其全部在线候选。
|
||||
|
||||
:param market: 本次读取的配置市场地址
|
||||
:param status: 当前市场与包代际索引的读取状态,区分存在、不存在和失败
|
||||
:param candidates: 本次读取获得的插件候选集合
|
||||
:param error: 读取失败的可读原因;无错误时为 None
|
||||
:param package_generation: 插件包代际目录,例如 v1、v2 或 v3
|
||||
"""
|
||||
|
||||
market: str
|
||||
status: MarketReadStatus
|
||||
@@ -163,7 +224,9 @@ class MarketRead:
|
||||
package_generation: str = "v1"
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""拒绝把失败读取伪装成空成功结果。"""
|
||||
"""
|
||||
拒绝把失败读取伪装成空成功结果。
|
||||
"""
|
||||
market = _normalize_market(self.market)
|
||||
status = MarketReadStatus(self.status)
|
||||
candidates = tuple(self.candidates)
|
||||
@@ -194,7 +257,9 @@ class MarketRead:
|
||||
*,
|
||||
package_generation: str = "v1",
|
||||
) -> "MarketRead":
|
||||
"""构造存在的索引读取;真实空索引可以没有候选。"""
|
||||
"""
|
||||
构造存在的索引读取;真实空索引可以没有候选。
|
||||
"""
|
||||
return cls(
|
||||
market=market,
|
||||
status=MarketReadStatus.PRESENT,
|
||||
@@ -209,7 +274,9 @@ class MarketRead:
|
||||
*,
|
||||
package_generation: str = "v1",
|
||||
) -> "MarketRead":
|
||||
"""构造已确认不存在的代际索引。"""
|
||||
"""
|
||||
构造已确认不存在的代际索引。
|
||||
"""
|
||||
return cls(
|
||||
market=market,
|
||||
status=MarketReadStatus.ABSENT,
|
||||
@@ -224,7 +291,9 @@ class MarketRead:
|
||||
*,
|
||||
package_generation: str = "v1",
|
||||
) -> "MarketRead":
|
||||
"""构造失败读取,并保留可诊断但不用于选择的错误说明。"""
|
||||
"""
|
||||
构造失败读取,并保留可诊断但不用于选择的错误说明。
|
||||
"""
|
||||
return cls(
|
||||
market=market,
|
||||
status=MarketReadStatus.FAILED,
|
||||
@@ -234,16 +303,22 @@ class MarketRead:
|
||||
|
||||
@property
|
||||
def succeeded(self) -> bool:
|
||||
"""判断该索引是否得到存在或不存在的确定结论。"""
|
||||
"""
|
||||
判断该索引是否得到存在或不存在的确定结论。
|
||||
"""
|
||||
return self.status is not MarketReadStatus.FAILED
|
||||
|
||||
@property
|
||||
def present_index(self) -> bool:
|
||||
"""判断该代际索引是否真实存在。"""
|
||||
"""
|
||||
判断该代际索引是否真实存在。
|
||||
"""
|
||||
return self.status is MarketReadStatus.PRESENT
|
||||
|
||||
def public_dict(self) -> dict[str, Any]:
|
||||
"""生成市场读取的脱敏投影,保留状态、代际和候选事实。"""
|
||||
"""
|
||||
生成市场读取的脱敏投影,保留状态、代际和候选事实。
|
||||
"""
|
||||
return {
|
||||
"market": self.market,
|
||||
"package_generation": self.package_generation,
|
||||
@@ -254,23 +329,36 @@ class MarketRead:
|
||||
|
||||
|
||||
class LocalCandidateReadStatus(StrEnum):
|
||||
"""一次本地插件仓库扫描的可观察终态。"""
|
||||
"""
|
||||
一次本地插件仓库扫描的可观察终态。
|
||||
"""
|
||||
|
||||
# 本地扫描成功并存在仓库,可返回空候选集合
|
||||
PRESENT = "present"
|
||||
# 已确认不存在本地插件仓库
|
||||
ABSENT = "absent"
|
||||
# 本地扫描失败,候选完整性无法保证
|
||||
FAILED = "failed"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LocalCandidateRead:
|
||||
"""记录本地候选扫描状态,避免扫描失败伪装成空仓库。"""
|
||||
"""
|
||||
记录本地候选扫描状态,避免扫描失败伪装成空仓库。
|
||||
|
||||
:param status: 本地仓库扫描结果,区分存在、不存在和失败
|
||||
:param candidates: 本次读取获得的插件候选集合
|
||||
:param error: 读取失败的可读原因;无错误时为 None
|
||||
"""
|
||||
|
||||
status: LocalCandidateReadStatus
|
||||
candidates: tuple[PluginLocalCandidate, ...] = ()
|
||||
error: str | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""保证本地扫描状态、候选和错误说明相互一致。"""
|
||||
"""
|
||||
保证本地扫描状态、候选和错误说明相互一致。
|
||||
"""
|
||||
status = LocalCandidateReadStatus(self.status)
|
||||
candidates = tuple(self.candidates)
|
||||
if any(not isinstance(candidate, PluginLocalCandidate) for candidate in candidates):
|
||||
@@ -294,7 +382,9 @@ class LocalCandidateRead:
|
||||
cls,
|
||||
candidates: Iterable[PluginLocalCandidate] = (),
|
||||
) -> "LocalCandidateRead":
|
||||
"""构造扫描成功的本地候选快照,空结果仍表示扫描成功。"""
|
||||
"""
|
||||
构造扫描成功的本地候选快照,空结果仍表示扫描成功。
|
||||
"""
|
||||
return cls(
|
||||
status=LocalCandidateReadStatus.PRESENT,
|
||||
candidates=tuple(candidates),
|
||||
@@ -302,16 +392,22 @@ class LocalCandidateRead:
|
||||
|
||||
@classmethod
|
||||
def absent(cls) -> "LocalCandidateRead":
|
||||
"""构造没有配置本地仓库的结果。"""
|
||||
"""
|
||||
构造没有配置本地仓库的结果。
|
||||
"""
|
||||
return cls(status=LocalCandidateReadStatus.ABSENT)
|
||||
|
||||
@classmethod
|
||||
def failure(cls, error: str) -> "LocalCandidateRead":
|
||||
"""构造无法完成本地扫描的结果。"""
|
||||
"""
|
||||
构造无法完成本地扫描的结果。
|
||||
"""
|
||||
return cls(status=LocalCandidateReadStatus.FAILED, error=error)
|
||||
|
||||
def public_dict(self) -> dict[str, Any]:
|
||||
"""生成不泄漏本地路径的扫描投影。"""
|
||||
"""
|
||||
生成不泄漏本地路径的扫描投影。
|
||||
"""
|
||||
return {
|
||||
"status": self.status.value,
|
||||
"error": self.error,
|
||||
@@ -321,7 +417,15 @@ class LocalCandidateRead:
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CandidateInventory:
|
||||
"""一次短生命周期市场快照,保留配置市场状态和全部候选。"""
|
||||
"""
|
||||
一次短生命周期市场快照,保留配置市场状态和全部候选。
|
||||
|
||||
:param market_reads: 各配置市场与包代际的读取事实
|
||||
:param local_candidates: 兼容入口提供的本地候选集合
|
||||
:param expected_markets: 必须完成读取的市场集合;None 从读取事实推导
|
||||
:param expected_generations: 必须完成读取的包代际集合;与 expected_markets 同时为 None 时只要求已有读取全部成功
|
||||
:param local_read: 本地扫描的明确结果;None 兼容使用 local_candidates
|
||||
"""
|
||||
|
||||
market_reads: tuple[MarketRead, ...]
|
||||
local_candidates: tuple[PluginLocalCandidate, ...] = ()
|
||||
@@ -330,7 +434,9 @@ class CandidateInventory:
|
||||
local_read: LocalCandidateRead | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""冻结快照输入,避免后续市场刷新改变选择依据。"""
|
||||
"""
|
||||
冻结快照输入,避免后续市场刷新改变选择依据。
|
||||
"""
|
||||
market_reads = tuple(self.market_reads)
|
||||
local_candidates = tuple(self.local_candidates)
|
||||
expected_markets = (
|
||||
@@ -374,18 +480,24 @@ class CandidateInventory:
|
||||
|
||||
@property
|
||||
def configured_markets(self) -> tuple[str, ...]:
|
||||
"""按配置顺序返回本轮预期读取的市场。"""
|
||||
"""
|
||||
按配置顺序返回本轮预期读取的市场。
|
||||
"""
|
||||
if self.expected_markets is not None:
|
||||
return self.expected_markets
|
||||
return tuple(dict.fromkeys(read.market for read in self.market_reads))
|
||||
|
||||
def reads_for(self, market: str) -> tuple[MarketRead, ...]:
|
||||
"""返回一个市场的全部代际读取事实。"""
|
||||
"""
|
||||
返回一个市场的全部代际读取事实。
|
||||
"""
|
||||
normalized_market = _normalize_market(market)
|
||||
return tuple(read for read in self.market_reads if read.market == normalized_market)
|
||||
|
||||
def read_for(self, market: str, package_generation: str) -> MarketRead | None:
|
||||
"""返回一个市场和代际的读取事实。"""
|
||||
"""
|
||||
返回一个市场和代际的读取事实。
|
||||
"""
|
||||
normalized_generation = normalize_package_generation(package_generation)
|
||||
return next(
|
||||
(
|
||||
@@ -398,7 +510,9 @@ class CandidateInventory:
|
||||
|
||||
@property
|
||||
def complete(self) -> bool:
|
||||
"""只有预期市场与代际均可证明已成功读取时才算完整。"""
|
||||
"""
|
||||
只有预期市场与代际均可证明已成功读取时才算完整。
|
||||
"""
|
||||
if not self.market_reads or not all(read.succeeded for read in self.market_reads):
|
||||
return False
|
||||
expected_markets = self.expected_markets
|
||||
@@ -417,7 +531,9 @@ class CandidateInventory:
|
||||
|
||||
@property
|
||||
def can_use_for_tofu(self) -> bool:
|
||||
"""判断快照是否足以证明唯一第三方来源。"""
|
||||
"""
|
||||
判断快照是否足以证明唯一第三方来源。
|
||||
"""
|
||||
local_read = self.local_read
|
||||
return (
|
||||
self.complete
|
||||
@@ -427,7 +543,9 @@ class CandidateInventory:
|
||||
|
||||
@property
|
||||
def online_candidates(self) -> tuple[PluginMarketCandidate, ...]:
|
||||
"""按配置市场顺序返回全部在线候选,不按 ID 或版本去重。"""
|
||||
"""
|
||||
按配置市场顺序返回全部在线候选,不按 ID 或版本去重。
|
||||
"""
|
||||
return tuple(
|
||||
candidate
|
||||
for read in self.market_reads
|
||||
@@ -436,7 +554,9 @@ class CandidateInventory:
|
||||
)
|
||||
|
||||
def candidates_for(self, plugin_id: str) -> tuple[PluginMarketCandidate, ...]:
|
||||
"""读取一个插件 ID 的全部在线候选。"""
|
||||
"""
|
||||
读取一个插件 ID 的全部在线候选。
|
||||
"""
|
||||
normalized_id = normalize_physical_plugin_id(plugin_id)
|
||||
return tuple(
|
||||
candidate
|
||||
@@ -445,7 +565,9 @@ class CandidateInventory:
|
||||
)
|
||||
|
||||
def local_candidates_for(self, plugin_id: str) -> tuple[PluginLocalCandidate, ...]:
|
||||
"""读取一个插件 ID 的全部本地候选。"""
|
||||
"""
|
||||
读取一个插件 ID 的全部本地候选。
|
||||
"""
|
||||
normalized_id = normalize_physical_plugin_id(plugin_id)
|
||||
return tuple(
|
||||
candidate
|
||||
@@ -454,7 +576,9 @@ class CandidateInventory:
|
||||
)
|
||||
|
||||
def public_dict(self) -> dict[str, Any]:
|
||||
"""生成完整库存的脱敏投影,不泄漏本地路径或原始 DTO。"""
|
||||
"""
|
||||
生成完整库存的脱敏投影,不泄漏本地路径或原始 DTO。
|
||||
"""
|
||||
local_read = self.local_read
|
||||
if local_read is None:
|
||||
raise RuntimeError("候选清单缺少本地读取终态")
|
||||
@@ -468,7 +592,14 @@ class CandidateInventory:
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PluginSelection:
|
||||
"""候选选择结果,冲突和不完整状态均不降级为静默空值。"""
|
||||
"""
|
||||
候选选择结果,冲突和不完整状态均不降级为静默空值。
|
||||
|
||||
:param status: 候选选择状态,区分选定、不可用、冲突和信息不完整
|
||||
:param candidate: 唯一被选定的候选;未选定时为 None
|
||||
:param conflict_source_keys: 产生同名来源冲突的仓库键
|
||||
:param reason: 未能选定候选的可读原因,默认空字符串
|
||||
"""
|
||||
|
||||
status: PluginSelectionStatus
|
||||
candidate: PluginMarketCandidate | PluginLocalCandidate | None = None
|
||||
@@ -476,7 +607,9 @@ class PluginSelection:
|
||||
reason: str = ""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""保证选择状态与载荷及冲突信息相互一致。"""
|
||||
"""
|
||||
保证选择状态与载荷及冲突信息相互一致。
|
||||
"""
|
||||
status = PluginSelectionStatus(self.status)
|
||||
conflict_source_keys = tuple(sorted(set(self.conflict_source_keys)))
|
||||
if status is PluginSelectionStatus.SELECTED and self.candidate is None:
|
||||
@@ -490,11 +623,15 @@ class PluginSelection:
|
||||
|
||||
@property
|
||||
def selected(self) -> bool:
|
||||
"""判断是否已经选择出一个载荷候选。"""
|
||||
"""
|
||||
判断是否已经选择出一个载荷候选。
|
||||
"""
|
||||
return self.status is PluginSelectionStatus.SELECTED
|
||||
|
||||
def public_dict(self) -> dict[str, Any]:
|
||||
"""生成安全的选择结果投影,不透传本地路径或原始 DTO。"""
|
||||
"""
|
||||
生成安全的选择结果投影,不透传本地路径或原始 DTO。
|
||||
"""
|
||||
result: dict[str, Any] = {
|
||||
"status": self.status.value,
|
||||
"reason": self.reason,
|
||||
@@ -510,7 +647,9 @@ Candidate: TypeAlias = PluginMarketCandidate | PluginLocalCandidate
|
||||
|
||||
|
||||
def normalize_package_generation(package_generation: str) -> str:
|
||||
"""校验并归一插件包代际。"""
|
||||
"""
|
||||
校验并归一插件包代际。
|
||||
"""
|
||||
value = str(package_generation).strip().lower()
|
||||
if value not in PLUGIN_GENERATIONS:
|
||||
raise ValueError("插件包代际必须为 v1、v2 或 v3")
|
||||
@@ -525,7 +664,9 @@ def _select_local_candidate(
|
||||
generation_order: tuple[str, ...],
|
||||
local_candidates: Iterable[PluginLocalCandidate] | None,
|
||||
) -> PluginSelection | None:
|
||||
"""优先选择本地载荷;读取失败时阻止自动降级到在线来源。"""
|
||||
"""
|
||||
优先选择本地载荷;读取失败时阻止自动降级到在线来源。
|
||||
"""
|
||||
local = (
|
||||
tuple(local_candidates)
|
||||
if local_candidates is not None
|
||||
@@ -581,7 +722,7 @@ def select_plugin_candidate(
|
||||
:param local_candidates: 可选的本地载荷候选;已有在线绑定时参与代际和版本比较
|
||||
:param requested_source_key: 调用方提供的规范在线来源;非显式调用不能绕过本地载荷
|
||||
:param explicit_source: 本次调用是否代表管理员明确选源
|
||||
:param allow_source_change: 是否是带 revision 的显式换源命令
|
||||
:param allow_source_change: 是否是带版本号的显式换源命令
|
||||
:return: 带明确冲突或不完整状态的选择结果
|
||||
"""
|
||||
normalized_id = normalize_physical_plugin_id(plugin_id)
|
||||
@@ -712,7 +853,9 @@ def _select_bound_source_candidate(
|
||||
identity: PluginIdentity | None,
|
||||
generation_order: tuple[str, ...],
|
||||
) -> PluginSelection:
|
||||
"""只在已绑定仓库范围内选取在线候选,并与可用本地载荷协调。"""
|
||||
"""
|
||||
只在已绑定仓库范围内选取在线候选,并与可用本地载荷协调。
|
||||
"""
|
||||
if identity is None:
|
||||
raise PluginSourceSelectionError("已绑定来源选择缺少插件身份")
|
||||
source_type, source_key = allowed_source
|
||||
@@ -757,7 +900,9 @@ def _select_bound_or_local_candidate(
|
||||
identity: PluginIdentity,
|
||||
generation_order: tuple[str, ...],
|
||||
) -> PluginSelection:
|
||||
"""在已绑定在线候选和本地候选之间选择代际、版本更高的载荷。"""
|
||||
"""
|
||||
在已绑定在线候选和本地候选之间选择代际、版本更高的载荷。
|
||||
"""
|
||||
if (
|
||||
local_selection is None
|
||||
or local_selection.status is not PluginSelectionStatus.SELECTED
|
||||
@@ -806,7 +951,9 @@ def list_effective_online_candidates(
|
||||
plugin_id: str,
|
||||
generations: Sequence[str],
|
||||
) -> tuple[PluginMarketCandidate, ...]:
|
||||
"""按来源列出当前运行代际实际可安装的最高版本候选,官方来源始终置顶。"""
|
||||
"""
|
||||
按来源列出当前运行代际实际可安装的最高版本候选,官方来源始终置顶。
|
||||
"""
|
||||
generation_order = _normalize_generation_order(generations)
|
||||
grouped: dict[
|
||||
tuple[TrustedPluginSourceType, str],
|
||||
@@ -836,7 +983,9 @@ def get_effective_local_candidate(
|
||||
plugin_id: str,
|
||||
generations: Sequence[str],
|
||||
) -> PluginLocalCandidate | None:
|
||||
"""返回本地插件目录中当前运行代际优先级最高的安全候选。"""
|
||||
"""
|
||||
返回本地插件目录中当前运行代际优先级最高的安全候选。
|
||||
"""
|
||||
candidate = _select_best(
|
||||
inventory.local_candidates_for(plugin_id),
|
||||
_normalize_generation_order(generations),
|
||||
@@ -845,7 +994,9 @@ def get_effective_local_candidate(
|
||||
|
||||
|
||||
def _coerce_online_source_type(source_type: TrustedPluginSourceType) -> TrustedPluginSourceType:
|
||||
"""把外部字符串来源类型转换为可信在线来源枚举。"""
|
||||
"""
|
||||
把外部字符串来源类型转换为可信在线来源枚举。
|
||||
"""
|
||||
value = TrustedPluginSourceType(source_type)
|
||||
if value is TrustedPluginSourceType.UNKNOWN:
|
||||
raise ValueError("未知来源不能作为在线市场候选")
|
||||
@@ -853,7 +1004,9 @@ def _coerce_online_source_type(source_type: TrustedPluginSourceType) -> TrustedP
|
||||
|
||||
|
||||
def _normalize_market(market: str) -> str:
|
||||
"""校验市场标识,保留其作为本轮快照的显示值。"""
|
||||
"""
|
||||
校验市场标识,保留其作为本轮快照的显示值。
|
||||
"""
|
||||
value = str(market).strip()
|
||||
if not value:
|
||||
raise ValueError("插件市场标识不能为空")
|
||||
@@ -861,7 +1014,9 @@ def _normalize_market(market: str) -> str:
|
||||
|
||||
|
||||
def _normalize_repo_url(repo_url: str) -> str:
|
||||
"""保留仓库地址作为安装事实,但移除无意义的外围空白。"""
|
||||
"""
|
||||
保留仓库地址作为安装事实,但移除无意义的外围空白。
|
||||
"""
|
||||
value = str(repo_url).strip()
|
||||
if not value:
|
||||
raise ValueError("插件仓库地址不能为空")
|
||||
@@ -869,7 +1024,9 @@ def _normalize_repo_url(repo_url: str) -> str:
|
||||
|
||||
|
||||
def _normalize_plugin_version(plugin_version: str | None) -> str | None:
|
||||
"""标准化可选插件声明版本,并保持缺失版本可观察。"""
|
||||
"""
|
||||
标准化可选插件声明版本,并保持缺失版本可观察。
|
||||
"""
|
||||
if plugin_version is None:
|
||||
return None
|
||||
value = str(plugin_version).strip()
|
||||
@@ -881,7 +1038,9 @@ def _normalize_plugin_version(plugin_version: str | None) -> str | None:
|
||||
|
||||
|
||||
def _normalize_generation_order(generations: Sequence[str]) -> tuple[str, ...]:
|
||||
"""校验调用方提供的代际优先序,并拒绝重复项。"""
|
||||
"""
|
||||
校验调用方提供的代际优先序,并拒绝重复项。
|
||||
"""
|
||||
normalized = tuple(normalize_package_generation(generation) for generation in generations)
|
||||
if not normalized:
|
||||
raise ValueError("至少需要一个当前运行代际")
|
||||
@@ -894,7 +1053,9 @@ def _allowed_source(
|
||||
identity: PluginIdentity | None,
|
||||
normalized_plugin_id: str,
|
||||
) -> tuple[TrustedPluginSourceType, str] | None:
|
||||
"""从已安装身份读取不可变的允许在线来源。"""
|
||||
"""
|
||||
从已安装身份读取不可变的允许在线来源。
|
||||
"""
|
||||
if identity is None:
|
||||
return None
|
||||
if identity.normalized_plugin_id != normalized_plugin_id:
|
||||
@@ -910,7 +1071,9 @@ def _select_best(
|
||||
candidates: Sequence[Candidate],
|
||||
generation_order: Sequence[str],
|
||||
) -> Candidate | None:
|
||||
"""在已完成来源过滤后按代际和同源版本选择最高候选。"""
|
||||
"""
|
||||
在已完成来源过滤后按代际和同源版本选择最高候选。
|
||||
"""
|
||||
for generation in generation_order:
|
||||
generation_candidates = tuple(
|
||||
candidate
|
||||
@@ -923,7 +1086,9 @@ def _select_best(
|
||||
|
||||
|
||||
def _select_highest_version(candidates: Sequence[Candidate]) -> Candidate:
|
||||
"""使用宿主既有版本比较语义选择同源最高版本,平级保留先读候选。"""
|
||||
"""
|
||||
使用宿主既有版本比较语义选择同源最高版本,平级保留先读候选。
|
||||
"""
|
||||
selected = candidates[0]
|
||||
for candidate in candidates[1:]:
|
||||
selected_version = selected.plugin_version or "0"
|
||||
|
||||
Reference in New Issue
Block a user