- Extracted callback management logic into a new `CallbackDispatcher` class, promoting separation of concerns and enhancing modularity. - Updated `BaseDataCollector` to utilize the `CallbackDispatcher` for adding, removing, and notifying data callbacks, improving code clarity and maintainability. - Refactored related methods to ensure consistent error handling and logging practices. - Added unit tests for the `CallbackDispatcher` to validate its functionality and ensure robust error handling. These changes streamline the callback management architecture, aligning with project standards for maintainability and performance.
85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
"""
|
|
Module for managing data callbacks and notifications for data collectors.
|
|
|
|
This module encapsulates the logic for registering, removing, and notifying
|
|
callback functions when new market data points are received, promoting a
|
|
clean separation of concerns within the data collector architecture.
|
|
"""
|
|
|
|
import asyncio
|
|
from typing import Dict, List, Optional, Any, Callable
|
|
|
|
from data.common.data_types import DataType, MarketDataPoint
|
|
|
|
|
|
class CallbackDispatcher:
|
|
"""
|
|
Manages the dispatching of market data points to registered callbacks.
|
|
"""
|
|
|
|
def __init__(self, component_name: str, logger=None):
|
|
self.component_name = component_name
|
|
self.logger = logger
|
|
self._data_callbacks: Dict[DataType, List[Callable]] = {
|
|
data_type: [] for data_type in DataType
|
|
}
|
|
|
|
def _log_debug(self, message: str) -> None:
|
|
if self.logger:
|
|
self.logger.debug(f"{self.component_name}: {message}")
|
|
|
|
def _log_info(self, message: str) -> None:
|
|
if self.logger:
|
|
self.logger.info(f"{self.component_name}: {message}")
|
|
|
|
def _log_warning(self, message: str) -> None:
|
|
if self.logger:
|
|
self.logger.warning(f"{self.component_name}: {message}")
|
|
|
|
def _log_error(self, message: str, exc_info: bool = False) -> None:
|
|
if self.logger:
|
|
self.logger.error(f"{self.component_name}: {message}", exc_info=exc_info)
|
|
|
|
def add_data_callback(self, data_type: DataType, callback: Callable[[MarketDataPoint], None]) -> None:
|
|
"""
|
|
Add a callback function for specific data type.
|
|
|
|
Args:
|
|
data_type: Type of data to monitor
|
|
callback: Function to call when data is received
|
|
"""
|
|
if callback not in self._data_callbacks[data_type]:
|
|
self._data_callbacks[data_type].append(callback)
|
|
self._log_debug(f"Added callback for {data_type.value} data")
|
|
|
|
def remove_data_callback(self, data_type: DataType, callback: Callable[[MarketDataPoint], None]) -> None:
|
|
"""
|
|
Remove a callback function for specific data type.
|
|
|
|
Args:
|
|
data_type: Type of data to stop monitoring
|
|
callback: Function to remove
|
|
"""
|
|
if callback in self._data_callbacks[data_type]:
|
|
self._data_callbacks[data_type].remove(callback)
|
|
self._log_debug(f"Removed callback for {data_type.value} data")
|
|
|
|
async def notify_callbacks(self, data_point: MarketDataPoint) -> None:
|
|
"""
|
|
Notify all registered callbacks for a data point.
|
|
|
|
Args:
|
|
data_point: Market data to distribute
|
|
"""
|
|
callbacks = self._data_callbacks.get(data_point.data_type, [])
|
|
|
|
for callback in callbacks:
|
|
try:
|
|
# Handle both sync and async callbacks
|
|
if asyncio.iscoroutinefunction(callback):
|
|
await callback(data_point)
|
|
else:
|
|
callback(data_point)
|
|
|
|
except Exception as e:
|
|
self._log_error(f"Error in data callback for {data_point.data_type.value} {data_point.symbol}: {e}", exc_info=True) |