"""provider 注册表:按 `provider_code` 分发。 Java 侧按任务行冻结的 `provider_code_snapshot` 找适配器,**不按当前目录**——目录 可能被改指向或禁用,而在途任务必须仍由当初提交它的适配器轮询。用当前目录路由会 拿着别人的任务 ID 去问错的供应商。 """ from __future__ import annotations from app.providers.base import GenerationProvider class ProviderRegistry: """`provider_code` → 适配器。""" def __init__(self, providers: list[GenerationProvider] | None = None): self._by_code: dict[str, GenerationProvider] = {} for provider in providers or []: self.register(provider) def register(self, provider: GenerationProvider) -> None: code = provider.provider_code if code in self._by_code: # 重复注册意味着装配出错。静默覆盖会让「到底哪个实例在处理任务」 # 取决于注册顺序,是最难查的一类问题。 raise ValueError(f"provider_code 重复注册:{code}") self._by_code[code] = provider def find(self, provider_code: str) -> GenerationProvider | None: """未知 code 返回 None。 对一个已持久化的任务而言,未知 code 意味着当初提交它的适配器已被移除。 调用方应当作**不可重试**的失败,而不是循环重试到预算耗尽。 """ return self._by_code.get(provider_code) def all(self) -> list[GenerationProvider]: """全部已装配的适配器。 供「按 media_kind 找适配器」这类查询用——按能力而非按 code 检索时用它。 """ return list(self._by_code.values()) @property def codes(self) -> frozenset[str]: return frozenset(self._by_code) def __len__(self) -> int: return len(self._by_code)