"""Rotating (fallback) wrapper over multiple fundamental data providers.""" from __future__ import annotations import logging from shared.fundamentals.base import FundamentalsProvider, FundamentalsSnapshot logger = logging.getLogger(__name__) class RotatingProvider(FundamentalsProvider): """Tries each provider in order and returns the first successful result. If a provider raises an exception it is caught, logged, and the next provider is attempted. Returns ``None`` only when every provider either returned ``None`` or raised. """ def __init__(self, providers: list[FundamentalsProvider]) -> None: if not providers: raise ValueError("RotatingProvider requires at least one provider") self._providers = providers async def fetch(self, ticker: str) -> FundamentalsSnapshot | None: """Try each provider in sequence; return the first non-None result.""" for provider in self._providers: provider_name = type(provider).__name__ try: result = await provider.fetch(ticker) if result is not None: logger.info( "Provider %s succeeded for %s", provider_name, ticker, ) return result logger.debug( "Provider %s returned None for %s, trying next", provider_name, ticker, ) except Exception: logger.exception( "Provider %s raised for %s, trying next", provider_name, ticker, ) logger.warning("All providers exhausted for %s", ticker) return None