Skip to content

Event Driven Architecture

levi edited this page Nov 6, 2025 · 2 revisions

Event-Driven Architecture

Table of Contents

  1. Introduction
  2. EventBus Implementation
  3. EventType Enumeration
  4. Event Subscription and Publishing
  5. Thread-Safe Design and Concurrency
  6. Practical Usage Examples
  7. Best Practices and Common Issues
  8. Conclusion

Introduction

The EventBus system in PyTradingView implements a robust event-driven architecture that enables decoupled communication between different components of the application. This documentation provides a comprehensive analysis of the EventBus implementation, focusing on its singleton pattern, thread-safe design, and the mechanisms for event subscription and publishing. The system supports both synchronous and asynchronous operations, allowing components to react to various lifecycle events across widgets, charts, indicators, and bridge connections. By leveraging this event-driven approach, the application achieves better modularity, maintainability, and responsiveness.

EventBus Implementation

The EventBus class implements the singleton pattern to ensure that only one instance exists throughout the application's lifecycle, providing a centralized hub for event management. This design prevents multiple instances from creating conflicting event states and ensures consistent event delivery across all components.

classDiagram
class EventBus {
-_instance : Optional[EventBus]
-_subscribers : Dict[EventType, List[Callable]]
-_event_queue : asyncio.Queue
-_running : bool
+get_instance() : EventBus
+__init__()
+subscribe(event_type : EventType, callback : Callable[[Event], Awaitable[None]]) : None
+unsubscribe(event_type : EventType, callback : Callable[[Event], Awaitable[None]]) : None
+publish(event_type : EventType, data : Optional[Dict[str, Any]], source : Optional[str]) : None
+publish_sync(event_type : EventType, data : Optional[Dict[str, Any]], source : Optional[str]) : None
+clear_subscribers(event_type : Optional[EventType]) : None
+get_subscriber_count(event_type : EventType) : int
+__repr__() : str
}
class Event {
+type : EventType
+data : Dict[str, Any]
+source : Optional[str]
+__repr__() : str
}
class EventType {
+WIDGET_CREATED : str
+WIDGET_READY : str
+WIDGET_DESTROYED : str
+CHART_READY : str
+CHART_DATA_LOADED : str
+CHART_DATA_EXPORTED : str
+INDICATOR_LOADED : str
+INDICATOR_ACTIVATED : str
+INDICATOR_DEACTIVATED : str
+INDICATOR_CALCULATED : str
+BRIDGE_STARTED : str
+BRIDGE_CONNECTED : str
+BRIDGE_DISCONNECTED : str
}
EventBus --> Event : "creates"
EventBus --> EventType : "uses"
Loading

EventType Enumeration

The EventType enumeration defines all available event types across the application, categorized by their functional domains. This type-safe approach ensures that events are properly named and organized, reducing errors and improving code readability. The enumeration includes events for widget lifecycle management, chart operations, indicator states, and bridge connectivity.

Widget Lifecycle Events

  • WIDGET_CREATED: Triggered when a widget is instantiated
  • WIDGET_READY: Fired when a widget has completed initialization and is ready for interaction
  • WIDGET_DESTROYED: Published when a widget is being removed or destroyed

Chart Events

  • CHART_READY: Indicates that a chart has been fully loaded and rendered
  • CHART_DATA_LOADED: Triggered when new data has been loaded into a chart
  • CHART_DATA_EXPORTED: Published when chart data has been exported

Indicator Events

  • INDICATOR_LOADED: Fired when an indicator has been successfully loaded
  • INDICATOR_ACTIVATED: Triggered when an indicator becomes active
  • INDICATOR_DEACTIVATED: Published when an indicator is deactivated
  • INDICATOR_CALCULATED: Indicates that indicator calculations have been completed

Bridge Events

  • BRIDGE_STARTED: Triggered when the bridge connection process begins
  • BRIDGE_CONNECTED: Fired when a successful connection to the bridge is established
  • BRIDGE_DISCONNECTED: Published when the bridge connection is terminated

Event Subscription and Publishing

The EventBus system provides two primary mechanisms for event communication: subscription and publishing. Components can subscribe to specific event types and provide callback functions that will be executed when those events occur. The system supports both asynchronous and synchronous publishing, accommodating different usage scenarios.

Asynchronous Publishing

The publish method handles asynchronous event publishing, which is the primary mode of operation in the event-driven architecture. When an event is published, it creates an Event object with the specified type, data payload, and source identifier. The system then retrieves all subscribers for that event type and executes their callback functions concurrently using asyncio tasks.

sequenceDiagram
participant Publisher
participant EventBus
participant Subscriber1
participant Subscriber2
participant SubscriberN
Publisher->>EventBus : publish(event_type, data, source)
EventBus->>EventBus : Create Event object
EventBus->>EventBus : Get subscribers for event_type
EventBus->>Subscriber1 : callback(event)
EventBus->>Subscriber2 : callback(event)
EventBus->>SubscriberN : callback(event)
Subscriber1-->>EventBus : Task completion
Subscriber2-->>EventBus : Task completion
SubscriberN-->>EventBus : Task completion
EventBus-->>Publisher : All tasks completed
Loading

Synchronous Publishing

The publish_sync method provides a synchronous interface for publishing events, which is useful in non-async contexts. This method intelligently handles different event loop states: if an event loop is already running, it creates a new task; otherwise, it runs the async publish method to completion. This flexibility allows components to publish events regardless of their execution context.

Thread-Safe Design and Concurrency

The EventBus implementation is designed to be thread-safe and handle concurrent operations efficiently. The system uses asyncio primitives to manage asynchronous operations and ensure that event processing does not block the main execution thread.

Internal Queue Management

The EventBus maintains an internal asyncio.Queue for event processing, allowing events to be queued and processed in order. This queue-based approach prevents event loss during high-load periods and ensures that events are processed in a controlled manner.

Concurrent Callback Execution

When an event is published, all subscribed callbacks are executed concurrently using asyncio.gather(). This parallel execution improves performance by allowing multiple components to respond to an event simultaneously. The system also implements proper error handling by capturing exceptions from individual callbacks and logging them without affecting other subscribers.

Error Handling

The EventBus includes comprehensive error handling mechanisms:

  • Callback creation errors are caught and logged
  • Individual callback execution errors are captured using return_exceptions=True in asyncio.gather()
  • Detailed error logging includes the callback name and exception information
  • The system continues processing even if individual callbacks fail

Practical Usage Examples

Subscribing to Events

Components can subscribe to events by obtaining the EventBus instance and registering their callback functions. The following example demonstrates how to subscribe to the CHART_READY event:

event_bus = EventBus.get_instance()
async def on_chart_ready(event: Event):
    # Handle chart ready event
    chart_data = event.data.get('chart')
    # Process chart data
    pass

event_bus.subscribe(EventType.CHART_READY, on_chart_ready)

Publishing Custom Events

Components can publish custom events with specific data payloads. The following example shows how to publish an INDICATOR_ACTIVATED event:

event_bus = EventBus.get_instance()
event_data = {
    'indicator_id': 'moving_average_20',
    'parameters': {'period': 20, 'type': 'simple'}
}
event_bus.publish(EventType.INDICATOR_ACTIVATED, event_data, source='indicator_manager')

Synchronous Event Publishing

In synchronous contexts, components can use the publish_sync method:

event_bus = EventBus.get_instance()
event_bus.publish_sync(EventType.WIDGET_READY, {'widget_id': 'chart1'}, source='widget_manager')

Best Practices and Common Issues

Memory Management

A common issue in event-driven systems is memory leaks due to unsubscribed callbacks. To prevent this:

  • Always call unsubscribe when a component is destroyed
  • Use weak references for callbacks when appropriate
  • Implement proper cleanup in component lifecycle methods

Event Naming Conventions

Follow consistent naming conventions for custom events:

  • Use lowercase with dots as separators (e.g., "indicator.custom_event")
  • Include the component domain in the event name
  • Be descriptive but concise

Data Payload Structure

When publishing events with data, follow these guidelines:

  • Use dictionaries for structured data
  • Include only necessary information
  • Document the expected data structure
  • Use consistent key names across similar events

Error Handling

Implement robust error handling in event callbacks:

  • Wrap callback logic in try-except blocks
  • Log errors with sufficient context
  • Provide fallback behavior when possible
  • Avoid letting exceptions propagate to the EventBus

Conclusion

The EventBus system in PyTradingView provides a comprehensive event-driven architecture that enables flexible and decoupled communication between application components. Through its singleton pattern, thread-safe design, and support for both synchronous and asynchronous operations, the system offers a robust foundation for building responsive and maintainable applications. The well-defined EventType enumeration ensures type safety and consistency across event handling, while the concurrent callback execution model maximizes performance. By following the best practices outlined in this documentation, developers can effectively leverage the EventBus system to create modular, scalable, and reliable applications.

Clone this wiki locally