unrealon 1.1.6__py3-none-any.whl → 2.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. {unrealon-1.1.6.dist-info/licenses → unrealon-2.0.5.dist-info}/LICENSE +1 -1
  2. unrealon-2.0.5.dist-info/METADATA +491 -0
  3. unrealon-2.0.5.dist-info/RECORD +128 -0
  4. {unrealon-1.1.6.dist-info → unrealon-2.0.5.dist-info}/WHEEL +2 -1
  5. unrealon-2.0.5.dist-info/entry_points.txt +3 -0
  6. unrealon-2.0.5.dist-info/top_level.txt +3 -0
  7. unrealon_browser/__init__.py +5 -6
  8. unrealon_browser/cli/browser_cli.py +18 -9
  9. unrealon_browser/cli/interactive_mode.py +13 -4
  10. unrealon_browser/core/browser_manager.py +29 -16
  11. unrealon_browser/dto/__init__.py +21 -0
  12. unrealon_browser/dto/bot_detection.py +175 -0
  13. unrealon_browser/dto/models/config.py +9 -3
  14. unrealon_browser/managers/__init__.py +1 -1
  15. unrealon_browser/managers/logger_bridge.py +1 -4
  16. unrealon_browser/stealth/__init__.py +27 -0
  17. unrealon_browser/stealth/bypass_techniques.pyc +0 -0
  18. unrealon_browser/stealth/manager.pyc +0 -0
  19. unrealon_browser/stealth/nodriver_stealth.pyc +0 -0
  20. unrealon_browser/stealth/playwright_stealth.pyc +0 -0
  21. unrealon_browser/stealth/scanner_tester.pyc +0 -0
  22. unrealon_browser/stealth/undetected_chrome.pyc +0 -0
  23. unrealon_core/__init__.py +172 -0
  24. unrealon_core/config/__init__.py +16 -0
  25. unrealon_core/config/environment.py +151 -0
  26. unrealon_core/config/urls.py +94 -0
  27. unrealon_core/enums/__init__.py +24 -0
  28. unrealon_core/enums/status.py +216 -0
  29. unrealon_core/enums/types.py +240 -0
  30. unrealon_core/error_handling/__init__.py +45 -0
  31. unrealon_core/error_handling/circuit_breaker.py +292 -0
  32. unrealon_core/error_handling/error_context.py +324 -0
  33. unrealon_core/error_handling/recovery.py +371 -0
  34. unrealon_core/error_handling/retry.py +268 -0
  35. unrealon_core/exceptions/__init__.py +46 -0
  36. unrealon_core/exceptions/base.py +292 -0
  37. unrealon_core/exceptions/communication.py +22 -0
  38. unrealon_core/exceptions/driver.py +11 -0
  39. unrealon_core/exceptions/proxy.py +11 -0
  40. unrealon_core/exceptions/task.py +12 -0
  41. unrealon_core/exceptions/validation.py +17 -0
  42. unrealon_core/models/__init__.py +79 -0
  43. unrealon_core/models/arq_context.py +252 -0
  44. unrealon_core/models/arq_responses.py +125 -0
  45. unrealon_core/models/base.py +291 -0
  46. unrealon_core/models/bridge_stats.py +58 -0
  47. unrealon_core/models/communication.py +39 -0
  48. unrealon_core/models/connection_stats.py +47 -0
  49. unrealon_core/models/driver.py +30 -0
  50. unrealon_core/models/driver_details.py +98 -0
  51. unrealon_core/models/logging.py +28 -0
  52. unrealon_core/models/task.py +21 -0
  53. unrealon_core/models/typed_responses.py +210 -0
  54. unrealon_core/models/websocket/__init__.py +91 -0
  55. unrealon_core/models/websocket/base.py +49 -0
  56. unrealon_core/models/websocket/config.py +200 -0
  57. unrealon_core/models/websocket/driver.py +215 -0
  58. unrealon_core/models/websocket/errors.py +138 -0
  59. unrealon_core/models/websocket/heartbeat.py +100 -0
  60. unrealon_core/models/websocket/logging.py +261 -0
  61. unrealon_core/models/websocket/proxy.py +496 -0
  62. unrealon_core/models/websocket/tasks.py +275 -0
  63. unrealon_core/models/websocket/utils.py +153 -0
  64. unrealon_core/models/websocket_session.py +144 -0
  65. unrealon_core/monitoring/__init__.py +43 -0
  66. unrealon_core/monitoring/alerts.py +398 -0
  67. unrealon_core/monitoring/dashboard.py +307 -0
  68. unrealon_core/monitoring/health_check.py +354 -0
  69. unrealon_core/monitoring/metrics.py +352 -0
  70. unrealon_core/utils/__init__.py +11 -0
  71. unrealon_core/utils/time.py +61 -0
  72. unrealon_core/version.py +219 -0
  73. unrealon_driver/__init__.py +90 -51
  74. unrealon_driver/core_module/__init__.py +34 -0
  75. unrealon_driver/core_module/base.py +184 -0
  76. unrealon_driver/core_module/config.py +30 -0
  77. unrealon_driver/core_module/event_manager.py +127 -0
  78. unrealon_driver/core_module/protocols.py +98 -0
  79. unrealon_driver/core_module/registry.py +146 -0
  80. unrealon_driver/decorators/__init__.py +15 -0
  81. unrealon_driver/decorators/retry.py +117 -0
  82. unrealon_driver/decorators/schedule.py +137 -0
  83. unrealon_driver/decorators/task.py +61 -0
  84. unrealon_driver/decorators/timing.py +132 -0
  85. unrealon_driver/driver/__init__.py +20 -0
  86. unrealon_driver/driver/communication/__init__.py +10 -0
  87. unrealon_driver/driver/communication/session.py +203 -0
  88. unrealon_driver/driver/communication/websocket_client.py +205 -0
  89. unrealon_driver/driver/core/__init__.py +10 -0
  90. unrealon_driver/driver/core/config.py +175 -0
  91. unrealon_driver/driver/core/driver.py +221 -0
  92. unrealon_driver/driver/factory/__init__.py +9 -0
  93. unrealon_driver/driver/factory/manager_factory.py +130 -0
  94. unrealon_driver/driver/lifecycle/__init__.py +11 -0
  95. unrealon_driver/driver/lifecycle/daemon.py +76 -0
  96. unrealon_driver/driver/lifecycle/initialization.py +97 -0
  97. unrealon_driver/driver/lifecycle/shutdown.py +48 -0
  98. unrealon_driver/driver/monitoring/__init__.py +9 -0
  99. unrealon_driver/driver/monitoring/health.py +63 -0
  100. unrealon_driver/driver/utilities/__init__.py +10 -0
  101. unrealon_driver/driver/utilities/logging.py +51 -0
  102. unrealon_driver/driver/utilities/serialization.py +61 -0
  103. unrealon_driver/managers/__init__.py +32 -0
  104. unrealon_driver/managers/base.py +174 -0
  105. unrealon_driver/managers/browser.py +98 -0
  106. unrealon_driver/managers/cache.py +116 -0
  107. unrealon_driver/managers/http.py +107 -0
  108. unrealon_driver/managers/logger.py +286 -0
  109. unrealon_driver/managers/proxy.py +99 -0
  110. unrealon_driver/managers/registry.py +87 -0
  111. unrealon_driver/managers/threading.py +54 -0
  112. unrealon_driver/managers/update.py +107 -0
  113. unrealon_driver/utils/__init__.py +9 -0
  114. unrealon_driver/utils/time.py +10 -0
  115. unrealon-1.1.6.dist-info/METADATA +0 -625
  116. unrealon-1.1.6.dist-info/RECORD +0 -55
  117. unrealon-1.1.6.dist-info/entry_points.txt +0 -9
  118. unrealon_browser/managers/stealth.py +0 -388
  119. unrealon_driver/README.md +0 -0
  120. unrealon_driver/exceptions.py +0 -33
  121. unrealon_driver/html_analyzer/__init__.py +0 -32
  122. unrealon_driver/html_analyzer/cleaner.py +0 -657
  123. unrealon_driver/html_analyzer/config.py +0 -64
  124. unrealon_driver/html_analyzer/manager.py +0 -247
  125. unrealon_driver/html_analyzer/models.py +0 -115
  126. unrealon_driver/html_analyzer/websocket_analyzer.py +0 -157
  127. unrealon_driver/models/__init__.py +0 -31
  128. unrealon_driver/models/websocket.py +0 -98
  129. unrealon_driver/parser/__init__.py +0 -36
  130. unrealon_driver/parser/cli_manager.py +0 -142
  131. unrealon_driver/parser/daemon_manager.py +0 -403
  132. unrealon_driver/parser/managers/__init__.py +0 -25
  133. unrealon_driver/parser/managers/config.py +0 -293
  134. unrealon_driver/parser/managers/error.py +0 -412
  135. unrealon_driver/parser/managers/result.py +0 -321
  136. unrealon_driver/parser/parser_manager.py +0 -458
  137. unrealon_driver/smart_logging/__init__.py +0 -24
  138. unrealon_driver/smart_logging/models.py +0 -44
  139. unrealon_driver/smart_logging/smart_logger.py +0 -406
  140. unrealon_driver/smart_logging/unified_logger.py +0 -525
  141. unrealon_driver/websocket/__init__.py +0 -31
  142. unrealon_driver/websocket/client.py +0 -249
  143. unrealon_driver/websocket/config.py +0 -188
  144. unrealon_driver/websocket/manager.py +0 -90
@@ -0,0 +1,132 @@
1
+ """
2
+ Clean timing decorator for performance monitoring.
3
+ """
4
+
5
+ import asyncio
6
+ import logging
7
+ import time
8
+ from functools import wraps
9
+ from typing import Callable, Optional, Dict, Any
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def timing(
15
+ log_result: bool = True,
16
+ log_level: str = "INFO",
17
+ include_args: bool = False,
18
+ threshold: Optional[float] = None
19
+ ):
20
+ """
21
+ Clean timing decorator.
22
+
23
+ Measures and logs function execution time.
24
+
25
+ Args:
26
+ log_result: Whether to log the timing result
27
+ log_level: Log level for timing messages
28
+ include_args: Include function arguments in log
29
+ threshold: Only log if execution time exceeds threshold (seconds)
30
+ """
31
+
32
+ def decorator(func: Callable) -> Callable:
33
+ @wraps(func)
34
+ async def async_wrapper(*args, **kwargs):
35
+ """Async timing wrapper."""
36
+
37
+ start_time = asyncio.get_event_loop().time()
38
+
39
+ try:
40
+ result = await func(*args, **kwargs)
41
+ success = True
42
+ error = None
43
+ except Exception as e:
44
+ result = None
45
+ success = False
46
+ error = str(e)
47
+ raise
48
+ finally:
49
+ end_time = asyncio.get_event_loop().time()
50
+ duration = end_time - start_time
51
+
52
+ # Log timing if enabled and threshold met
53
+ if log_result and (threshold is None or duration >= threshold):
54
+ _log_timing(func, duration, success, error, args, kwargs, include_args, log_level)
55
+
56
+ return result
57
+
58
+ @wraps(func)
59
+ def sync_wrapper(*args, **kwargs):
60
+ """Sync timing wrapper."""
61
+
62
+ start_time = time.perf_counter()
63
+
64
+ try:
65
+ result = func(*args, **kwargs)
66
+ success = True
67
+ error = None
68
+ except Exception as e:
69
+ result = None
70
+ success = False
71
+ error = str(e)
72
+ raise
73
+ finally:
74
+ end_time = time.perf_counter()
75
+ duration = end_time - start_time
76
+
77
+ # Log timing if enabled and threshold met
78
+ if log_result and (threshold is None or duration >= threshold):
79
+ _log_timing(func, duration, success, error, args, kwargs, include_args, log_level)
80
+
81
+ return result
82
+
83
+ # Return appropriate wrapper based on function type
84
+ if asyncio.iscoroutinefunction(func):
85
+ return async_wrapper
86
+ else:
87
+ return sync_wrapper
88
+
89
+ return decorator
90
+
91
+
92
+ def _log_timing(
93
+ func: Callable,
94
+ duration: float,
95
+ success: bool,
96
+ error: Optional[str],
97
+ args: tuple,
98
+ kwargs: dict,
99
+ include_args: bool,
100
+ log_level: str
101
+ ):
102
+ """Log timing information."""
103
+
104
+ # Format duration
105
+ if duration < 0.001:
106
+ duration_str = f"{duration * 1000000:.0f}μs"
107
+ elif duration < 1.0:
108
+ duration_str = f"{duration * 1000:.1f}ms"
109
+ else:
110
+ duration_str = f"{duration:.2f}s"
111
+
112
+ # Build log message
113
+ status = "✓" if success else "✗"
114
+ message = f"{status} {func.__name__} took {duration_str}"
115
+
116
+ if error:
117
+ message += f" (failed: {error})"
118
+
119
+ if include_args:
120
+ args_str = ", ".join([repr(arg) for arg in args])
121
+ kwargs_str = ", ".join([f"{k}={repr(v)}" for k, v in kwargs.items()])
122
+
123
+ if args_str and kwargs_str:
124
+ message += f" | Args: ({args_str}, {kwargs_str})"
125
+ elif args_str:
126
+ message += f" | Args: ({args_str})"
127
+ elif kwargs_str:
128
+ message += f" | Args: ({kwargs_str})"
129
+
130
+ # Log at specified level
131
+ log_func = getattr(logger, log_level.lower(), logger.info)
132
+ log_func(message)
@@ -0,0 +1,20 @@
1
+ """
2
+ UniversalDriver - Modular driver system.
3
+
4
+ Clean, organized driver architecture with:
5
+ - Core driver functionality
6
+ - Lifecycle management
7
+ - Communication layer
8
+ - Factory pattern for managers
9
+ - Health monitoring
10
+ - Utilities for common operations
11
+ """
12
+
13
+ from .core.driver import UniversalDriver
14
+ from .core.config import DriverConfig, DriverMode
15
+
16
+ __all__ = [
17
+ "UniversalDriver",
18
+ "DriverConfig",
19
+ "DriverMode"
20
+ ]
@@ -0,0 +1,10 @@
1
+ """
2
+ Communication layer.
3
+
4
+ WebSocket client and RPC session management.
5
+ """
6
+
7
+ from .websocket_client import WebSocketClient
8
+ from .session import DriverSession
9
+
10
+ __all__ = ["WebSocketClient", "DriverSession"]
@@ -0,0 +1,203 @@
1
+ """
2
+ Clean driver session management.
3
+ """
4
+
5
+ import asyncio
6
+ import logging
7
+ from datetime import datetime
8
+ from typing import Dict, Any, Optional, List, Callable
9
+ from enum import Enum
10
+
11
+ from pydantic import BaseModel, Field
12
+
13
+ from unrealon_core.models.websocket import (
14
+ TaskAssignmentData,
15
+ TaskResultData,
16
+ WebSocketMessage
17
+ )
18
+ from unrealon_core.enums import DriverStatus, TaskStatus, MessageType
19
+ from unrealon_driver.utils.time import utc_now
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class SessionStatus(str, Enum):
25
+ """Clean session status."""
26
+ DISCONNECTED = "disconnected"
27
+ CONNECTING = "connecting"
28
+ CONNECTED = "connected"
29
+ REGISTERED = "registered"
30
+ ACTIVE = "active"
31
+ ERROR = "error"
32
+
33
+
34
+ class SessionStats(BaseModel):
35
+ """Session statistics."""
36
+ connected_at: Optional[datetime] = None
37
+ registered_at: Optional[datetime] = None
38
+ tasks_completed: int = 0
39
+ tasks_failed: int = 0
40
+ last_activity: Optional[datetime] = None
41
+
42
+
43
+ class DriverSession:
44
+ """
45
+ Clean session manager.
46
+
47
+ Handles connection, registration, and task processing
48
+ with simple, clear interface.
49
+ """
50
+
51
+ def __init__(self, driver_id: str, websocket_client=None):
52
+ self.driver_id = driver_id
53
+ self.websocket_client = websocket_client
54
+ self.status = SessionStatus.DISCONNECTED
55
+ self.stats = SessionStats()
56
+
57
+ # Task handling
58
+ self.task_handlers: Dict[str, Callable] = {}
59
+
60
+ # Setup WebSocket message handler
61
+ if self.websocket_client:
62
+ self.websocket_client.on_message = self._handle_websocket_message
63
+
64
+ async def start_session(self, capabilities: List[str] = None) -> bool:
65
+ """Start session with registration."""
66
+ if not self.websocket_client:
67
+ logger.error("No WebSocket client configured")
68
+ return False
69
+
70
+ try:
71
+ # Connect WebSocket
72
+ self.status = SessionStatus.CONNECTING
73
+ if not await self.websocket_client.connect():
74
+ self.status = SessionStatus.ERROR
75
+ return False
76
+
77
+ self.status = SessionStatus.CONNECTED
78
+ self.stats.connected_at = utc_now()
79
+
80
+ # Register driver
81
+ if await self.register(capabilities or []):
82
+ self.status = SessionStatus.REGISTERED
83
+ self.stats.registered_at = utc_now()
84
+ logger.info(f"Session started for driver: {self.driver_id}")
85
+ return True
86
+ else:
87
+ self.status = SessionStatus.ERROR
88
+ return False
89
+
90
+ except Exception as e:
91
+ logger.error(f"Session start failed: {e}")
92
+ self.status = SessionStatus.ERROR
93
+ return False
94
+
95
+ async def stop_session(self):
96
+ """Stop session cleanly."""
97
+ try:
98
+ if self.websocket_client:
99
+ await self.websocket_client.disconnect()
100
+
101
+ self.status = SessionStatus.DISCONNECTED
102
+ logger.info(f"Session stopped for driver: {self.driver_id}")
103
+
104
+ except Exception as e:
105
+ logger.error(f"Session stop error: {e}")
106
+
107
+ async def register(self, capabilities: List[str]) -> bool:
108
+ """Register driver with capabilities."""
109
+ if not self.websocket_client:
110
+ return False
111
+
112
+ try:
113
+ success = await self.websocket_client.register_driver(capabilities)
114
+ if success:
115
+ logger.info(f"Driver registered: {self.driver_id} with capabilities: {capabilities}")
116
+ return success
117
+ except Exception as e:
118
+ logger.error(f"Registration failed: {e}")
119
+ return False
120
+
121
+ def register_task_handler(self, task_type: str, handler: Callable):
122
+ """Register handler for task type."""
123
+ self.task_handlers[task_type] = handler
124
+ logger.debug(f"Registered handler for task type: {task_type}")
125
+
126
+ def _handle_websocket_message(self, message: WebSocketMessage):
127
+ """Handle incoming WebSocket messages."""
128
+ try:
129
+ self.stats.last_activity = utc_now()
130
+
131
+ if message.type == MessageType.TASK_ASSIGN:
132
+ asyncio.create_task(self._handle_task_assignment(message))
133
+ else:
134
+ logger.debug(f"Received message type: {message.type}")
135
+
136
+ except Exception as e:
137
+ logger.error(f"Error handling WebSocket message: {e}")
138
+
139
+ async def _handle_task_assignment(self, message: WebSocketMessage):
140
+ """Handle task assignment."""
141
+ try:
142
+ # Validate task data
143
+ task_data = TaskAssignmentData.model_validate(message.data)
144
+
145
+ # Find handler
146
+ handler = self.task_handlers.get(task_data.task_type)
147
+ if not handler:
148
+ logger.warning(f"No handler for task type: {task_data.task_type}")
149
+ await self._send_task_result(
150
+ task_data.task_id,
151
+ TaskStatus.FAILED,
152
+ error="No handler for task type"
153
+ )
154
+ return
155
+
156
+ # Execute task
157
+ try:
158
+ result = await handler(task_data)
159
+ await self._send_task_result(
160
+ task_data.task_id,
161
+ TaskStatus.COMPLETED,
162
+ result=result
163
+ )
164
+ self.stats.tasks_completed += 1
165
+
166
+ except Exception as e:
167
+ logger.error(f"Task execution failed: {e}")
168
+ await self._send_task_result(
169
+ task_data.task_id,
170
+ TaskStatus.FAILED,
171
+ error=str(e)
172
+ )
173
+ self.stats.tasks_failed += 1
174
+
175
+ except Exception as e:
176
+ logger.error(f"Task assignment handling failed: {e}")
177
+
178
+ async def _send_task_result(self, task_id: str, status: TaskStatus, result: Any = None, error: str = None):
179
+ """Send task result back."""
180
+ try:
181
+ result_data = TaskResultData(
182
+ task_id=task_id,
183
+ driver_id=self.driver_id,
184
+ status=status,
185
+ result=result,
186
+ error_message=error,
187
+ completed_at=utc_now()
188
+ )
189
+
190
+ if self.websocket_client:
191
+ self.websocket_client.send(result_data)
192
+
193
+ except Exception as e:
194
+ logger.error(f"Failed to send task result: {e}")
195
+
196
+ def get_status(self) -> Dict[str, Any]:
197
+ """Get session status."""
198
+ return {
199
+ "driver_id": self.driver_id,
200
+ "status": self.status.value,
201
+ "stats": self.stats.model_dump(),
202
+ "handlers": list(self.task_handlers.keys())
203
+ }
@@ -0,0 +1,205 @@
1
+ """
2
+ Clean WebSocket client with auto-reconnection and message queuing.
3
+ """
4
+
5
+ import asyncio
6
+ import json
7
+ import logging
8
+ from typing import Optional, Callable, List
9
+ from collections import deque
10
+
11
+ import websockets
12
+ try:
13
+ from websockets.asyncio.client import ClientConnection
14
+ except ImportError:
15
+ # Fallback for older websockets versions
16
+ try:
17
+ from websockets.client import WebSocketClientProtocol as ClientConnection
18
+ except ImportError:
19
+ # Ultimate fallback
20
+ ClientConnection = None
21
+
22
+ from unrealon_core.models.websocket import (
23
+ DriverRegistrationMessage,
24
+ DriverRegistrationData,
25
+ WebSocketMessage
26
+ )
27
+ from unrealon_core.enums.types import MessageType
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class WebSocketClient:
33
+ """
34
+ Clean WebSocket client with robust connection management.
35
+
36
+ Features:
37
+ - Auto-reconnection with exponential backoff
38
+ - Message queuing during disconnections
39
+ - Background message processing
40
+ - Clean error handling
41
+ """
42
+
43
+ def __init__(self, websocket_url: str, driver_id: str):
44
+ self.websocket_url = websocket_url
45
+ self.driver_id = driver_id
46
+ self.websocket: Optional[ClientConnection] = None
47
+ self.connected = False
48
+ self.running = False
49
+
50
+ # Message handling
51
+ self.on_message: Optional[Callable[[WebSocketMessage], None]] = None
52
+ self.message_queue: deque = deque()
53
+
54
+ # Background tasks
55
+ self._sender_task: Optional[asyncio.Task] = None
56
+ self._receiver_task: Optional[asyncio.Task] = None
57
+ self._monitor_task: Optional[asyncio.Task] = None
58
+
59
+ # Reconnection settings
60
+ self.reconnect_delay = 1.0
61
+ self.max_reconnect_delay = 60.0
62
+ self.reconnect_attempts = 0
63
+
64
+ async def connect(self) -> bool:
65
+ """Connect and start background tasks."""
66
+ if self.running:
67
+ return True
68
+
69
+ self.running = True
70
+
71
+ # Start background tasks
72
+ self._sender_task = asyncio.create_task(self._message_sender())
73
+ self._receiver_task = asyncio.create_task(self._message_receiver())
74
+ self._monitor_task = asyncio.create_task(self._connection_monitor())
75
+
76
+ return await self._establish_connection()
77
+
78
+ async def disconnect(self):
79
+ """Clean disconnect and stop all tasks."""
80
+ self.running = False
81
+
82
+ # Cancel background tasks
83
+ for task in [self._sender_task, self._receiver_task, self._monitor_task]:
84
+ if task and not task.done():
85
+ task.cancel()
86
+ try:
87
+ await task
88
+ except asyncio.CancelledError:
89
+ pass
90
+
91
+ # Close WebSocket
92
+ if self.websocket:
93
+ await self.websocket.close()
94
+ self.websocket = None
95
+
96
+ self.connected = False
97
+ logger.info("WebSocket client stopped")
98
+
99
+ async def _establish_connection(self) -> bool:
100
+ """Establish WebSocket connection."""
101
+ try:
102
+ logger.info(f"Connecting to WebSocket: {self.websocket_url}")
103
+ self.websocket = await websockets.connect(self.websocket_url)
104
+ self.connected = True
105
+ self.reconnect_attempts = 0
106
+ self.reconnect_delay = 1.0
107
+ logger.info("WebSocket connected successfully")
108
+ return True
109
+ except Exception as e:
110
+ logger.error(f"WebSocket connection failed: {e}")
111
+ self.connected = False
112
+ return False
113
+
114
+ async def _connection_monitor(self):
115
+ """Monitor connection and handle reconnection."""
116
+ while self.running:
117
+ if not self.connected and self.running:
118
+ logger.info(f"Attempting reconnection (attempt {self.reconnect_attempts + 1})")
119
+
120
+ if await self._establish_connection():
121
+ logger.info("Reconnection successful")
122
+ else:
123
+ self.reconnect_attempts += 1
124
+ # Exponential backoff
125
+ self.reconnect_delay = min(
126
+ self.reconnect_delay * 2,
127
+ self.max_reconnect_delay
128
+ )
129
+ await asyncio.sleep(self.reconnect_delay)
130
+ else:
131
+ await asyncio.sleep(5.0) # Check every 5 seconds
132
+
133
+ async def _message_sender(self):
134
+ """Background task to send queued messages."""
135
+ while self.running:
136
+ if self.connected and self.websocket and self.message_queue:
137
+ try:
138
+ message = self.message_queue.popleft()
139
+ await self.websocket.send(message)
140
+ except (websockets.exceptions.ConnectionClosed, ConnectionResetError):
141
+ self.connected = False
142
+ # Put message back in queue
143
+ self.message_queue.appendleft(message)
144
+ except Exception as e:
145
+ logger.error(f"Error sending message: {e}")
146
+ else:
147
+ await asyncio.sleep(0.1)
148
+
149
+ async def _message_receiver(self):
150
+ """Background task to receive messages."""
151
+ while self.running:
152
+ if self.connected and self.websocket:
153
+ try:
154
+ message_str = await self.websocket.recv()
155
+
156
+ # Parse and validate message
157
+ message_data = json.loads(message_str)
158
+ message = WebSocketMessage.model_validate(message_data)
159
+
160
+ # Handle message
161
+ if self.on_message:
162
+ self.on_message(message)
163
+
164
+ except (websockets.exceptions.ConnectionClosed, ConnectionResetError):
165
+ self.connected = False
166
+ logger.warning("WebSocket connection lost")
167
+ except Exception as e:
168
+ logger.error(f"Error receiving message: {e}")
169
+ else:
170
+ await asyncio.sleep(0.1)
171
+
172
+ def send(self, message_data) -> None:
173
+ """Queue message for sending."""
174
+ if hasattr(message_data, 'model_dump_json'):
175
+ # Pydantic model
176
+ message_json = message_data.model_dump_json()
177
+ else:
178
+ # Raw data
179
+ message_json = json.dumps(message_data)
180
+
181
+ self.message_queue.append(message_json)
182
+
183
+ async def register_driver(self, capabilities: List[str]) -> bool:
184
+ """Register driver with capabilities."""
185
+ try:
186
+ # Create registration data
187
+ registration_data = DriverRegistrationData(
188
+ driver_id=self.driver_id,
189
+ driver_name=self.driver_id,
190
+ driver_type="universal",
191
+ capabilities=capabilities
192
+ )
193
+
194
+ # Create registration message with correct type
195
+ registration_message = DriverRegistrationMessage(data=registration_data)
196
+
197
+ # Queue for sending
198
+ self.send(registration_message)
199
+
200
+ logger.info(f"Driver registration queued: {self.driver_id}")
201
+ return True
202
+
203
+ except Exception as e:
204
+ logger.error(f"Driver registration failed: {e}")
205
+ return False
@@ -0,0 +1,10 @@
1
+ """
2
+ Core driver components.
3
+
4
+ Contains the main UniversalDriver class and configuration.
5
+ """
6
+
7
+ from .driver import UniversalDriver
8
+ from .config import DriverConfig, DriverMode
9
+
10
+ __all__ = ["UniversalDriver", "DriverConfig", "DriverMode"]