-
Notifications
You must be signed in to change notification settings - Fork 1
Asynchronous Programming Model
- Introduction
- Core Asynchronous Components
- Event Bus and Event Handling
- TVBridge and WebSocket Communication
- Async Method Chains and Data Flow
- Performance Considerations
- Error Handling in Asynchronous Code
- Testing Async Components
- Best Practices
The asynchronous programming model in the PyTradingView system is designed to handle non-blocking operations for WebSocket communication, HTTP requests, and event processing. The system leverages Python's asyncio library to manage concurrent operations efficiently. Key components such as TVEngineRuntime and TVBridge utilize async/await patterns to ensure responsive and scalable performance. This document details the implementation of these patterns, focusing on how asynchronous methods handle various operations and integrate with the asyncio event loop.
The system's asynchronous architecture is built around several core components that manage different aspects of the application's functionality. The TVEngineRuntime class serves as the main entry point for the indicator engine, handling initialization, setup, and runtime operations. It integrates with the TVBridge to manage communication between the Python backend and the frontend JavaScript components. The TVEventBus provides a publish-subscribe mechanism for event handling, allowing components to communicate without tight coupling.
classDiagram
class TVEngineRuntime {
+setup(indicators_dir : str, auto_activate : bool, config : Dict[str, Any]) TVEngineRuntime
+run(widget_config : Dict[str, Any], indicators_dir : str, on_port : int) None
-_on_chart_ready_event(event : Event) None
-_on_chart_data_ready(widget : TVWidget) None
-_subscribe_layout_changed_event(widget : TVWidget) None
-_handle_layout_changed(widget : TVWidget) None
-_initialize_all_charts(widget : TVWidget) None
-_activate_default_indicators_for_chart(chart_id : str) None
-_cleanup_all_charts() None
-_setup_chart_data_listener(widget : TVWidget, chart : TVChart, chart_id : str, chart_index : int) None
}
class TVBridge {
+get_instance() TVBridge
+register_config_provider(config : TVWidgetConfig) None
+register_chart_ready_callback(callback : Callable[[TVWidget], Awaitable[None]]) None
+setup_routes() None
+_register_connection_routes() None
+_register_rpc_routes() None
+_handle_web_to_python_call(data : Dict[str, Any]) TVMethodResponse
+_handle_widget_call(params : TVMethodCall) Dict[str, Any]
+_handle_object_pool_call(params : TVMethodCall) Dict[str, Any]
+_handle_bridge_call(params : TVMethodCall) Dict[str, Any]
+_handle_generic_object_call(params : TVMethodCall) Dict[str, Any]
+_validate_method_call(params : TVMethodCall) Optional[str]
+_handle_chart_data_ready(call_params : TVMethodCall) None
+_is_port_available(port : int) bool
+_find_available_port(start_port : int, max_attempts : int) int
+_get_python_executable() str
+start_connect_to_node_server() None
+start_http_server(on_port : int) None
+is_listening() bool
+connect_to_node_server(max_retries : int, base_delay : float) bool
+call_node_server(params : TVMethodCall) TVMethodResponse
+_call_node_http_request(url : str, method : str, json_data : Optional[dict], timeout : int) Optional[dict]
+run(on_port : int) None
}
TVEngineRuntime --> TVBridge : "uses"
The TVEventBus class implements a publish-subscribe pattern for event handling, allowing components to communicate without direct dependencies. Events are published asynchronously using the publish method, which creates tasks for all subscribed callbacks. For synchronous contexts, the publish_sync method is provided, which either creates a task if the event loop is running or runs the coroutine directly otherwise. This ensures that events can be published from both async and sync contexts seamlessly.
classDiagram
class EventBus {
+get_instance() EventBus
+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
}
class Event {
+type : EventType
+data : Dict[str, Any]
+source : Optional[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 : "publishes"
EventBus --> EventType : "uses"
The TVBridge class manages communication between the Python backend and the frontend JavaScript components via WebSocket and HTTP requests. It uses FastAPI to create an HTTP server that handles RPC calls from the frontend. The _handle_web_to_python_call method processes incoming requests, dispatching them to appropriate handlers based on the class name and method name. For WebSocket communication, the bridge establishes a connection to the Node server, handling retries and port conflicts automatically.
sequenceDiagram
participant Frontend
participant TVBridge
participant NodeServer
Frontend->>TVBridge : POST /web/call/py
TVBridge->>TVBridge : _handle_web_to_python_call(data)
TVBridge->>TVBridge : dispatch to handler
TVBridge->>NodeServer : HTTP request to Node server
NodeServer-->>TVBridge : Response
TVBridge-->>Frontend : Response
loop Connection Retry
TVBridge->>NodeServer : connect/from/py
alt Success
NodeServer-->>TVBridge : Success
TVBridge->>TVBridge : set is_connected_to_node = True
else Failure
TVBridge->>TVBridge : await asyncio.sleep(delay)
TVBridge->>NodeServer : retry connection
end
end
The system's async method chains begin with chart initialization and proceed through data export callbacks. When a chart is ready, the _on_chart_data_ready method is called, which subscribes to layout change events and initializes all charts in the current layout. Each chart's data loading is handled by the _setup_chart_data_listener method, which sets up a callback for when data is loaded. This callback exports the data and runs indicators on the chart, producing signals and drawables.
flowchart TD
A[Chart Ready] --> B[_on_chart_data_ready]
B --> C[Subscribe to Layout Changes]
C --> D[Initialize All Charts]
D --> E[Setup Data Loading Listener]
E --> F[Data Loaded Callback]
F --> G[Export Data]
G --> H[Run Indicators]
H --> I[Produce Signals and Drawables]
Performance in the asynchronous programming model is optimized through the use of asyncio.create_task for fire-and-forget operations, ensuring that non-blocking calls do not hold up the event loop. Blocking calls within async functions are avoided to prevent stalling the entire system. The TVBridge's HTTP server is designed to handle multiple requests concurrently, leveraging FastAPI's asynchronous capabilities. Additionally, the event bus ensures that event processing is efficient by running all subscribed callbacks concurrently.
Error handling in asynchronous code is critical to maintaining system stability. The TVEventBus captures exceptions in event callbacks and logs them without stopping the entire event processing pipeline. Similarly, the TVBridge logs errors in HTTP requests and connection attempts, allowing the system to continue operating even when individual requests fail. Proper error handling ensures that transient issues do not lead to system-wide failures.
Testing asynchronous components requires the use of the pytest-asyncio plugin to run async test functions. Tests should cover both successful and error scenarios, ensuring that the system behaves correctly under various conditions. Mocking external dependencies such as the Node server is essential for isolating the components being tested.
Best practices for asynchronous programming in this system include:
- Using
asyncio.create_taskfor fire-and-forget operations - Avoiding blocking calls in async functions
- Properly handling exceptions in event callbacks
- Ensuring that all async methods are properly awaited
- Using type hints and docstrings to improve code readability and maintainability