- Add base API client and cache manager classes - Implement FMP and YFinance specific clients and cache managers - Add API factory for managing multiple data providers - Add test suite for API configuration and caching - Add logging configuration for API operations
148 lines
5.0 KiB
Python
148 lines
5.0 KiB
Python
from typing import Optional, Dict, Any
|
|
import logging
|
|
import os
|
|
from .base import BaseAPIClient
|
|
from .fmp.client import FMPClient
|
|
from .yfinance.client import YFinanceClient
|
|
from ..cache.fmp_cache import FMPCacheManager
|
|
from ..cache.yfinance_cache import YFinanceCacheManager
|
|
|
|
class APIFactory:
|
|
"""Factory for creating and managing API clients."""
|
|
|
|
def __init__(self, fmp_api_key: Optional[str] = None):
|
|
"""Initialize API factory.
|
|
|
|
Args:
|
|
fmp_api_key: Optional FMP API key. If not provided, will try to get from environment variable.
|
|
"""
|
|
# Try to get API key from environment variable if not provided
|
|
self.fmp_api_key = fmp_api_key or os.environ.get('FMP_API_KEY')
|
|
if not self.fmp_api_key:
|
|
logging.warning("No FMP API key found in environment. Some features may be limited.")
|
|
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
self._clients: Dict[str, BaseAPIClient] = {}
|
|
|
|
def get_client(self, provider: str = 'fmp') -> BaseAPIClient:
|
|
"""Get an API client instance.
|
|
|
|
Args:
|
|
provider: API provider ('fmp' or 'yfinance')
|
|
|
|
Returns:
|
|
API client instance
|
|
|
|
Raises:
|
|
ValueError: If provider is invalid or FMP API key is missing
|
|
"""
|
|
provider = provider.lower()
|
|
|
|
if provider not in ['fmp', 'yfinance']:
|
|
raise ValueError(f"Invalid provider: {provider}")
|
|
|
|
if provider in self._clients:
|
|
return self._clients[provider]
|
|
|
|
if provider == 'fmp':
|
|
if not self.fmp_api_key:
|
|
raise ValueError("FMP API key is required")
|
|
client = FMPClient(self.fmp_api_key)
|
|
else: # yfinance
|
|
client = YFinanceClient()
|
|
|
|
self._clients[provider] = client
|
|
return client
|
|
|
|
def get_data(self, symbol: str, data_type: str, provider: str = 'fmp', fallback: bool = True) -> Any:
|
|
"""Get data from API with fallback support.
|
|
|
|
Args:
|
|
symbol: ETF ticker symbol
|
|
data_type: Type of data to retrieve
|
|
provider: Primary API provider
|
|
fallback: Whether to fall back to yfinance if primary fails
|
|
|
|
Returns:
|
|
Requested data or error information
|
|
"""
|
|
try:
|
|
# Try primary provider
|
|
client = self.get_client(provider)
|
|
data = getattr(client, f"get_{data_type}")(symbol)
|
|
|
|
# Check if data is valid
|
|
if isinstance(data, dict) and data.get('error'):
|
|
if fallback and provider == 'fmp':
|
|
self.logger.info(f"Falling back to yfinance for {symbol}")
|
|
return self.get_data(symbol, data_type, 'yfinance', False)
|
|
return data
|
|
|
|
return data
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error getting {data_type} for {symbol}: {str(e)}")
|
|
|
|
if fallback and provider == 'fmp':
|
|
self.logger.info(f"Falling back to yfinance for {symbol}")
|
|
return self.get_data(symbol, data_type, 'yfinance', False)
|
|
|
|
return {
|
|
'error': True,
|
|
'message': str(e),
|
|
'provider': provider,
|
|
'data_type': data_type,
|
|
'symbol': symbol
|
|
}
|
|
|
|
def clear_cache(self, provider: Optional[str] = None) -> Dict[str, int]:
|
|
"""Clear cache for specified provider or all providers.
|
|
|
|
Args:
|
|
provider: Optional provider to clear cache for
|
|
|
|
Returns:
|
|
Dictionary with number of files cleared per provider
|
|
"""
|
|
results = {}
|
|
|
|
if provider:
|
|
providers = [provider]
|
|
else:
|
|
providers = ['fmp', 'yfinance']
|
|
|
|
for prov in providers:
|
|
try:
|
|
client = self.get_client(prov)
|
|
results[prov] = client.clear_cache()
|
|
except Exception as e:
|
|
self.logger.error(f"Error clearing cache for {prov}: {str(e)}")
|
|
results[prov] = 0
|
|
|
|
return results
|
|
|
|
def get_cache_stats(self, provider: Optional[str] = None) -> Dict[str, Dict]:
|
|
"""Get cache statistics for specified provider or all providers.
|
|
|
|
Args:
|
|
provider: Optional provider to get stats for
|
|
|
|
Returns:
|
|
Dictionary with cache statistics per provider
|
|
"""
|
|
results = {}
|
|
|
|
if provider:
|
|
providers = [provider]
|
|
else:
|
|
providers = ['fmp', 'yfinance']
|
|
|
|
for prov in providers:
|
|
try:
|
|
client = self.get_client(prov)
|
|
results[prov] = client.get_cache_stats()
|
|
except Exception as e:
|
|
self.logger.error(f"Error getting cache stats for {prov}: {str(e)}")
|
|
results[prov] = {}
|
|
|
|
return results |