50 lines
1.8 KiB
Python
50 lines
1.8 KiB
Python
"""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
|