fix(critical): 恢复 bzzoiro events 采集管线,消除数据源注册表静默失效
C2 数据丢失修复。重构提交6a49940删除了 src/data/bzzoiro.py 中的 fetch_bzzoiro_events 与 BzzoiroSource 后未做替换,同时 sources.py 用 `try/except Exception: pass` 吞掉了 ImportError,导致: - _SOURCES 注册表恒为空 - get_source("bzzoiro") 恒抛 ValueError("未知数据源: bzzoiro") - src/api/routes/ingest.py 与 schedules.py 运行时全线失效, 且日志中无任何导入错误痕迹 修复内容: 1. 从f05dc1a恢复并移植 events 管线: - fetch_bzzoiro_events():分页抓取 /events/(纯异步,复用现有 _fetch_json_async) - @register class BzzoiroSource + ingest():保留原签名 (db, *, leagues, date_from, date_to, status),保持「不 commit, 事务由调用方 UnitOfWork 控制」的契约,并保留单联赛抓取失败的 错误隔离(记录 error 后 continue) - 复用现有 _to_date / _to_int_or_none / _match_key,未重复定义 - 血缘字段继续使用与 nm 配对的 raw(避免 P0-3 回归) 2. sources.py:导入失败改为 logger.exception 记录,新增 _loaded 标记 保证 _load_sources() 至多执行一次;失败时不置位以便后续重试。 get_source() 对真正未知的名字仍抛 ValueError。 验证: - 新增 tests/test_bzzoiro_source_registry.py(8 项):注册表非空、 get_source/list_sources 可用、ingest 签名契约、不再静默吞异常 - test_regressions.py:3 failed -> 2 failed - 全量:14 failed -> 13 failed,通过数 190 -> 199 注意事项:未改动任何 caller(routes/ingest.py、routes/schedules.py 原样 可用,证明签名保持正确)。
This commit is contained in:
+27
-11
@@ -8,10 +8,13 @@
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Protocol
|
||||
|
||||
from src.db.base import AsyncSession
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DataSource(Protocol):
|
||||
"""比赛数据源契约:抓取 → 规范化 → 入库。"""
|
||||
@@ -29,6 +32,9 @@ class DataSource(Protocol):
|
||||
# ── 注册表 ──
|
||||
_SOURCES: dict[str, DataSource] = {}
|
||||
|
||||
# 延迟加载标记:保证 _load_sources() 最多执行一次,重复调用廉价
|
||||
_loaded = False
|
||||
|
||||
|
||||
def register(source):
|
||||
"""装饰器:将数据源注册到全局注册表。
|
||||
@@ -42,8 +48,7 @@ def register(source):
|
||||
|
||||
def get_source(name: str) -> DataSource:
|
||||
"""按名获取数据源。"""
|
||||
if not _SOURCES:
|
||||
_load_sources()
|
||||
_load_sources()
|
||||
if name not in _SOURCES:
|
||||
raise ValueError(f"未知数据源: {name}")
|
||||
return _SOURCES[name]
|
||||
@@ -51,18 +56,29 @@ def get_source(name: str) -> DataSource:
|
||||
|
||||
def list_sources() -> list[str]:
|
||||
"""列出所有已注册数据源名。"""
|
||||
if not _SOURCES:
|
||||
_load_sources()
|
||||
_load_sources()
|
||||
return list(_SOURCES.keys())
|
||||
|
||||
|
||||
def _load_sources() -> None:
|
||||
"""延迟导入数据源触发 @register(避免循环导入)。"""
|
||||
from src.data.bzzoiro import BzzoiroSource # noqa: F811
|
||||
"""延迟导入数据源触发 @register(避免循环导入)。
|
||||
|
||||
导入失败必须留下痕迹:静默吞掉 ImportError 会让注册表恒为空,
|
||||
导致 get_source() 对所有数据源都报「未知数据源」,把导入错误
|
||||
伪装成「不存在的名字」—— 这类静默失效极难定位,故在此显式记日志。
|
||||
"""
|
||||
global _loaded
|
||||
if _loaded:
|
||||
return
|
||||
try:
|
||||
from src.data.bzzoiro import BzzoiroSource # noqa: F401
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"数据源模块导入失败,注册表将为空 —— get_source() 会对所有名字报「未知数据源」"
|
||||
)
|
||||
return # 保持 _loaded=False,下次调用可重试
|
||||
_loaded = True
|
||||
|
||||
|
||||
# 保持向后兼容:模块加载时尝试加载(但不再强制)
|
||||
try:
|
||||
_load_sources()
|
||||
except Exception:
|
||||
pass
|
||||
# 模块加载时预热(失败会记录日志,不再静默)
|
||||
_load_sources()
|
||||
|
||||
Reference in New Issue
Block a user