- Introduced a new `exceptions.py` file containing custom exceptions for the exchanges module, improving error specificity and handling. - Updated the `factory.py` and `registry.py` files to utilize the new exceptions, enhancing robustness in error reporting and logging. - Implemented validation logic in `ExchangeCollectorConfig` to ensure proper configuration, raising appropriate exceptions when validation fails. - Enhanced logging throughout the factory methods to provide better insights into the collector creation process and error scenarios. - Added comprehensive documentation for the exchanges module, detailing the architecture, error handling, and usage examples. These changes significantly improve the error handling and maintainability of the exchanges module, aligning with project standards and enhancing developer experience.
237 lines
8.3 KiB
Python
237 lines
8.3 KiB
Python
"""
|
|
Exchange Factory for creating data collectors.
|
|
|
|
This module provides a factory pattern for creating data collectors
|
|
from different exchanges based on configuration.
|
|
"""
|
|
|
|
import importlib
|
|
from typing import Dict, List, Optional, Any, Type, Tuple
|
|
from dataclasses import dataclass
|
|
|
|
from utils.logger import get_logger
|
|
from ..base_collector import BaseDataCollector, DataType
|
|
from .registry import EXCHANGE_REGISTRY, get_supported_exchanges, get_exchange_info
|
|
from .exceptions import (
|
|
ExchangeError,
|
|
ExchangeNotSupportedError,
|
|
InvalidConfigurationError,
|
|
CollectorCreationError,
|
|
ValidationError
|
|
)
|
|
|
|
# Initialize logger
|
|
logger = get_logger('exchanges')
|
|
|
|
@dataclass
|
|
class ExchangeCollectorConfig:
|
|
"""Configuration for creating an exchange collector."""
|
|
exchange: str
|
|
symbol: str
|
|
data_types: List[DataType]
|
|
auto_restart: bool = True
|
|
health_check_interval: float = 30.0
|
|
store_raw_data: bool = True
|
|
custom_params: Optional[Dict[str, Any]] = None
|
|
|
|
def __post_init__(self):
|
|
"""Validate configuration after initialization."""
|
|
if not self.exchange:
|
|
raise InvalidConfigurationError("Exchange name cannot be empty")
|
|
if not self.symbol:
|
|
raise InvalidConfigurationError("Symbol cannot be empty")
|
|
if not self.data_types:
|
|
raise InvalidConfigurationError("At least one data type must be specified")
|
|
logger.debug(f"Created collector config for {self.exchange} {self.symbol}")
|
|
|
|
|
|
class ExchangeFactory:
|
|
"""Factory for creating exchange-specific data collectors."""
|
|
|
|
@staticmethod
|
|
def create_collector(config: ExchangeCollectorConfig) -> BaseDataCollector:
|
|
"""
|
|
Create a data collector for the specified exchange.
|
|
|
|
Args:
|
|
config: Configuration for the collector
|
|
|
|
Returns:
|
|
Instance of the appropriate collector class
|
|
|
|
Raises:
|
|
ExchangeNotSupportedError: If exchange is not supported
|
|
CollectorCreationError: If collector creation fails
|
|
"""
|
|
exchange_name = config.exchange.lower()
|
|
logger.info(f"Creating collector for {exchange_name} {config.symbol}")
|
|
|
|
if exchange_name not in EXCHANGE_REGISTRY:
|
|
supported = get_supported_exchanges()
|
|
error_msg = f"Exchange '{config.exchange}' not supported. Supported exchanges: {supported}"
|
|
logger.error(error_msg)
|
|
raise ExchangeNotSupportedError(error_msg)
|
|
|
|
exchange_info = get_exchange_info(exchange_name)
|
|
collector_class_path = exchange_info['collector']
|
|
|
|
# Parse module and class name
|
|
module_path, class_name = collector_class_path.rsplit('.', 1)
|
|
|
|
try:
|
|
# Import the module
|
|
logger.debug(f"Importing collector module {module_path}")
|
|
module = importlib.import_module(module_path)
|
|
|
|
# Get the collector class
|
|
collector_class = getattr(module, class_name)
|
|
|
|
# Prepare collector arguments
|
|
collector_args = {
|
|
'symbol': config.symbol,
|
|
'data_types': config.data_types,
|
|
'auto_restart': config.auto_restart,
|
|
'health_check_interval': config.health_check_interval,
|
|
'store_raw_data': config.store_raw_data
|
|
}
|
|
|
|
# Add any custom parameters
|
|
if config.custom_params:
|
|
collector_args.update(config.custom_params)
|
|
|
|
# Create and return the collector instance
|
|
logger.info(f"Successfully created collector for {exchange_name} {config.symbol}")
|
|
return collector_class(**collector_args)
|
|
|
|
except ImportError as e:
|
|
error_msg = f"Failed to import collector class '{collector_class_path}': {e}"
|
|
logger.error(error_msg)
|
|
raise CollectorCreationError(error_msg) from e
|
|
except Exception as e:
|
|
error_msg = f"Failed to create collector for '{config.exchange}': {e}"
|
|
logger.error(error_msg)
|
|
raise CollectorCreationError(error_msg) from e
|
|
|
|
@staticmethod
|
|
def create_multiple_collectors(configs: List[ExchangeCollectorConfig]) -> List[BaseDataCollector]:
|
|
"""
|
|
Create multiple collectors from a list of configurations.
|
|
|
|
Args:
|
|
configs: List of collector configurations
|
|
|
|
Returns:
|
|
List of collector instances
|
|
"""
|
|
collectors = []
|
|
logger.info(f"Creating {len(configs)} collectors")
|
|
|
|
for config in configs:
|
|
try:
|
|
collector = ExchangeFactory.create_collector(config)
|
|
collectors.append(collector)
|
|
logger.debug(f"Successfully created collector for {config.exchange} {config.symbol}")
|
|
except ExchangeError as e:
|
|
logger.error(f"Failed to create collector for {config.exchange} {config.symbol}: {e}")
|
|
|
|
logger.info(f"Successfully created {len(collectors)} out of {len(configs)} collectors")
|
|
return collectors
|
|
|
|
@staticmethod
|
|
def get_supported_pairs(exchange: str) -> List[str]:
|
|
"""
|
|
Get supported trading pairs for an exchange.
|
|
|
|
Args:
|
|
exchange: Exchange name
|
|
|
|
Returns:
|
|
List of supported trading pairs
|
|
"""
|
|
exchange_info = get_exchange_info(exchange)
|
|
if exchange_info:
|
|
return exchange_info.get('supported_pairs', [])
|
|
return []
|
|
|
|
@staticmethod
|
|
def get_supported_data_types(exchange: str) -> List[str]:
|
|
"""
|
|
Get supported data types for an exchange.
|
|
|
|
Args:
|
|
exchange: Exchange name
|
|
|
|
Returns:
|
|
List of supported data types
|
|
"""
|
|
exchange_info = get_exchange_info(exchange)
|
|
if exchange_info:
|
|
return exchange_info.get('supported_data_types', [])
|
|
return []
|
|
|
|
@staticmethod
|
|
def validate_config(config: ExchangeCollectorConfig) -> Tuple[bool, List[str]]:
|
|
"""
|
|
Validate collector configuration.
|
|
|
|
Args:
|
|
config: Configuration to validate
|
|
|
|
Returns:
|
|
Tuple of (is_valid, list_of_errors)
|
|
"""
|
|
logger.debug(f"Validating configuration for {config.exchange} {config.symbol}")
|
|
errors = []
|
|
|
|
# Check if exchange is supported
|
|
if config.exchange.lower() not in EXCHANGE_REGISTRY:
|
|
errors.append(f"Exchange '{config.exchange}' not supported")
|
|
|
|
# Check if symbol is supported
|
|
supported_pairs = ExchangeFactory.get_supported_pairs(config.exchange)
|
|
if supported_pairs and config.symbol not in supported_pairs:
|
|
errors.append(f"Symbol '{config.symbol}' not supported for {config.exchange}")
|
|
|
|
# Check if data types are supported
|
|
supported_data_types = ExchangeFactory.get_supported_data_types(config.exchange)
|
|
if supported_data_types:
|
|
for data_type in config.data_types:
|
|
if data_type.value not in supported_data_types:
|
|
errors.append(f"Data type '{data_type.value}' not supported for {config.exchange}")
|
|
|
|
is_valid = len(errors) == 0
|
|
if not is_valid:
|
|
logger.warning(f"Configuration validation failed for {config.exchange}: {errors}")
|
|
else:
|
|
logger.debug(f"Configuration validation passed for {config.exchange}")
|
|
|
|
return is_valid, errors
|
|
|
|
|
|
def create_okx_collector(symbol: str,
|
|
data_types: Optional[List[DataType]] = None,
|
|
**kwargs) -> BaseDataCollector:
|
|
"""
|
|
Convenience function to create an OKX collector.
|
|
|
|
Args:
|
|
symbol: Trading pair symbol (e.g., 'BTC-USDT')
|
|
data_types: List of data types to collect
|
|
**kwargs: Additional collector parameters
|
|
|
|
Returns:
|
|
OKX collector instance
|
|
"""
|
|
if data_types is None:
|
|
data_types = [DataType.TRADE, DataType.ORDERBOOK]
|
|
|
|
logger.debug(f"Creating OKX collector for {symbol}")
|
|
|
|
config = ExchangeCollectorConfig(
|
|
exchange='okx',
|
|
symbol=symbol,
|
|
data_types=data_types,
|
|
**kwargs
|
|
)
|
|
|
|
return ExchangeFactory.create_collector(config) |