"""数据源协议 + 注册表。 定义 DataSource 契约,并提供全局注册表供路由层分发。 每个比赛数据源实现该协议,注册后即可通过统一入口调度。 当前只有 bzzoiro 一个数据源(Understat / injuries 已移除), 保留协议与注册表是为了统一 ingest 调度入口的结构。 """ from __future__ import annotations from typing import Protocol from src.db.base import AsyncSession class DataSource(Protocol): """比赛数据源契约:抓取 → 规范化 → 入库。""" @property def name(self) -> str: """数据源标识名(用于路由/日志)。""" ... async def ingest(self, db: AsyncSession, **kwargs) -> dict: """执行完整采集流程,返回统计。""" ... # ── 注册表 ── _SOURCES: dict[str, DataSource] = {} def register(source): """装饰器:将数据源注册到全局注册表。 兼容类注册与实例注册:类会被实例化后存入(保证 get_source 返回实例)。 """ obj = source() if isinstance(source, type) else source _SOURCES[obj.name] = obj return source def get_source(name: str) -> DataSource: """按名获取数据源。""" if not _SOURCES: _load_sources() if name not in _SOURCES: raise ValueError(f"未知数据源: {name}") return _SOURCES[name] def list_sources() -> list[str]: """列出所有已注册数据源名。""" if not _SOURCES: _load_sources() return list(_SOURCES.keys()) def _load_sources() -> None: """延迟导入数据源触发 @register(避免循环导入)。""" from src.data.bzzoiro import BzzoiroSource # noqa: F811 # 保持向后兼容:模块加载时尝试加载(但不再强制) try: _load_sources() except Exception: pass