Refactor BaseDataCollector to integrate CallbackDispatcher for improved callback management

- 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.
This commit is contained in:
Vasily.onl
2025-06-09 17:47:26 +08:00
parent 41f0e8e6b6
commit 3db8fb1c41
4 changed files with 214 additions and 30 deletions

View File

@@ -16,6 +16,7 @@ from enum import Enum
from utils.logger import get_logger
from .collector.collector_state_telemetry import CollectorStatus, CollectorStateAndTelemetry
from .collector.collector_connection_manager import ConnectionManager
from .collector.collector_callback_dispatcher import CallbackDispatcher
from .common.data_types import DataType, MarketDataPoint
@@ -137,14 +138,15 @@ class BaseDataCollector(ABC):
state_telemetry=self._state_telemetry
)
# Initialize callback dispatcher
self._callback_dispatcher = CallbackDispatcher(
component_name=component,
logger=self.logger
)
# Collector state (now managed by _state_telemetry)
self._tasks: Set[asyncio.Task] = set()
# Data callbacks
self._data_callbacks: Dict[DataType, List[Callable]] = {
data_type: [] for data_type in DataType
}
# Log initialization if logger is available
if self._state_telemetry.logger:
if not self._state_telemetry.log_errors_only:
@@ -457,9 +459,7 @@ class BaseDataCollector(ABC):
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")
self._callback_dispatcher.add_data_callback(data_type, callback)
def remove_data_callback(self, data_type: DataType, callback: Callable[[MarketDataPoint], None]) -> None:
"""
@@ -469,9 +469,7 @@ class BaseDataCollector(ABC):
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")
self._callback_dispatcher.remove_data_callback(data_type, callback)
async def _notify_callbacks(self, data_point: MarketDataPoint) -> None:
"""
@@ -480,18 +478,7 @@ class BaseDataCollector(ABC):
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: {e}")
await self._callback_dispatcher.notify_callbacks(data_point)
# Update statistics
self._state_telemetry.increment_messages_processed()

View File

@@ -0,0 +1,85 @@
"""
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)