Add configurable data providers and localize frontend UI
This commit is contained in:
@@ -1,29 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Centralized Data Source Configuration
|
||||
"""Centralized data source configuration and fallback ordering."""
|
||||
|
||||
Auto-detects and manages data source based on available API keys.
|
||||
Priority: FINNHUB_API_KEY > FINANCIAL_DATASETS_API_KEY
|
||||
"""
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Optional
|
||||
|
||||
DataSource = Literal["finnhub", "financial_datasets"]
|
||||
DataSource = Literal["finnhub", "financial_datasets", "yfinance", "local_csv"]
|
||||
_KNOWN_SOURCES: tuple[DataSource, ...] = (
|
||||
"finnhub",
|
||||
"financial_datasets",
|
||||
"yfinance",
|
||||
"local_csv",
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DataSourceConfig:
|
||||
"""Immutable data source configuration"""
|
||||
"""Resolved data source configuration."""
|
||||
|
||||
source: DataSource
|
||||
api_key: str
|
||||
sources: list[DataSource]
|
||||
|
||||
|
||||
# Module-level cache for the resolved configuration
|
||||
_config_cache: Optional[DataSourceConfig] = None
|
||||
|
||||
|
||||
def _parse_enabled_sources() -> list[DataSource]:
|
||||
"""Parse optional enabled source allowlist from the environment."""
|
||||
raw_value = os.getenv("ENABLED_DATA_SOURCES", "").strip().lower()
|
||||
if not raw_value:
|
||||
return []
|
||||
|
||||
enabled: list[DataSource] = []
|
||||
for item in raw_value.split(","):
|
||||
candidate = item.strip()
|
||||
if not candidate or candidate not in _KNOWN_SOURCES:
|
||||
continue
|
||||
if candidate not in enabled:
|
||||
enabled.append(candidate)
|
||||
return enabled
|
||||
|
||||
|
||||
def _ordered_sources() -> list[DataSource]:
|
||||
"""Resolve source preference and available fallbacks."""
|
||||
preferred = os.getenv("FIN_DATA_SOURCE", "").strip().lower()
|
||||
finnhub_key = os.getenv("FINNHUB_API_KEY", "").strip()
|
||||
fd_key = os.getenv("FINANCIAL_DATASETS_API_KEY", "").strip()
|
||||
enabled_sources = _parse_enabled_sources()
|
||||
wants_yfinance = preferred == "yfinance" or "yfinance" in enabled_sources
|
||||
|
||||
available: list[DataSource] = []
|
||||
if finnhub_key:
|
||||
available.append("finnhub")
|
||||
if fd_key:
|
||||
available.append("financial_datasets")
|
||||
if wants_yfinance:
|
||||
available.append("yfinance")
|
||||
available.append("local_csv")
|
||||
|
||||
if enabled_sources:
|
||||
filtered = [source for source in enabled_sources if source in available]
|
||||
if filtered:
|
||||
available = filtered
|
||||
|
||||
if preferred in available:
|
||||
ordered = [preferred]
|
||||
ordered.extend(source for source in available if source != preferred)
|
||||
return ordered
|
||||
return available
|
||||
|
||||
|
||||
def _resolve_config() -> DataSourceConfig:
|
||||
"""
|
||||
Resolve data source configuration based on available API keys.
|
||||
@@ -33,21 +81,22 @@ def _resolve_config() -> DataSourceConfig:
|
||||
2. FINANCIAL_DATASETS_API_KEY (if set)
|
||||
3. Raises error if neither is available
|
||||
"""
|
||||
# Check for Finnhub API key first (higher priority)
|
||||
finnhub_key = os.getenv("FINNHUB_API_KEY")
|
||||
if finnhub_key:
|
||||
return DataSourceConfig(source="finnhub", api_key=finnhub_key)
|
||||
|
||||
# Fallback to Financial Datasets API
|
||||
fd_key = os.getenv("FINANCIAL_DATASETS_API_KEY")
|
||||
if fd_key:
|
||||
return DataSourceConfig(source="financial_datasets", api_key=fd_key)
|
||||
|
||||
# No API key available
|
||||
raise ValueError(
|
||||
"No API key found. Please set either FINNHUB_API_KEY or "
|
||||
"FINANCIAL_DATASETS_API_KEY in your .env file.",
|
||||
)
|
||||
sources = _ordered_sources()
|
||||
if "finnhub" in sources:
|
||||
return DataSourceConfig(
|
||||
source="finnhub",
|
||||
api_key=os.getenv("FINNHUB_API_KEY", "").strip(),
|
||||
sources=sources,
|
||||
)
|
||||
if "financial_datasets" in sources:
|
||||
return DataSourceConfig(
|
||||
source="financial_datasets",
|
||||
api_key=os.getenv("FINANCIAL_DATASETS_API_KEY", "").strip(),
|
||||
sources=sources,
|
||||
)
|
||||
if "yfinance" in sources:
|
||||
return DataSourceConfig(source="yfinance", api_key="", sources=sources)
|
||||
return DataSourceConfig(source="local_csv", api_key="", sources=sources)
|
||||
|
||||
|
||||
def get_config() -> DataSourceConfig:
|
||||
@@ -71,6 +120,11 @@ def get_data_source() -> DataSource:
|
||||
return get_config().source
|
||||
|
||||
|
||||
def get_data_sources() -> list[DataSource]:
|
||||
"""Get preferred source ordering including fallbacks."""
|
||||
return get_config().sources
|
||||
|
||||
|
||||
def get_api_key() -> str:
|
||||
"""Get the API key for the configured data source."""
|
||||
return get_config().api_key
|
||||
|
||||
Reference in New Issue
Block a user