unrealon 1.1.5__py3-none-any.whl → 2.0.4__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 (146) hide show
  1. {unrealon-1.1.5.dist-info/licenses → unrealon-2.0.4.dist-info}/LICENSE +1 -1
  2. unrealon-2.0.4.dist-info/METADATA +491 -0
  3. unrealon-2.0.4.dist-info/RECORD +129 -0
  4. {unrealon-1.1.5.dist-info → unrealon-2.0.4.dist-info}/WHEEL +2 -1
  5. unrealon-2.0.4.dist-info/entry_points.txt +3 -0
  6. unrealon-2.0.4.dist-info/top_level.txt +3 -0
  7. unrealon_browser/__init__.py +5 -2
  8. unrealon_browser/cli/browser_cli.py +18 -9
  9. unrealon_browser/cli/interactive_mode.py +18 -7
  10. unrealon_browser/core/browser_manager.py +76 -13
  11. unrealon_browser/dto/__init__.py +21 -0
  12. unrealon_browser/dto/bot_detection.py +175 -0
  13. unrealon_browser/dto/models/config.py +14 -1
  14. unrealon_browser/managers/__init__.py +4 -1
  15. unrealon_browser/managers/logger_bridge.py +3 -6
  16. unrealon_browser/managers/page_wait_manager.py +198 -0
  17. unrealon_browser/stealth/__init__.py +27 -0
  18. unrealon_browser/stealth/bypass_techniques.pyc +0 -0
  19. unrealon_browser/stealth/manager.pyc +0 -0
  20. unrealon_browser/stealth/nodriver_stealth.pyc +0 -0
  21. unrealon_browser/stealth/playwright_stealth.pyc +0 -0
  22. unrealon_browser/stealth/scanner_tester.pyc +0 -0
  23. unrealon_browser/stealth/undetected_chrome.pyc +0 -0
  24. unrealon_core/__init__.py +160 -0
  25. unrealon_core/config/__init__.py +16 -0
  26. unrealon_core/config/environment.py +98 -0
  27. unrealon_core/config/urls.py +93 -0
  28. unrealon_core/enums/__init__.py +24 -0
  29. unrealon_core/enums/status.py +216 -0
  30. unrealon_core/enums/types.py +240 -0
  31. unrealon_core/error_handling/__init__.py +45 -0
  32. unrealon_core/error_handling/circuit_breaker.py +292 -0
  33. unrealon_core/error_handling/error_context.py +324 -0
  34. unrealon_core/error_handling/recovery.py +371 -0
  35. unrealon_core/error_handling/retry.py +268 -0
  36. unrealon_core/exceptions/__init__.py +46 -0
  37. unrealon_core/exceptions/base.py +292 -0
  38. unrealon_core/exceptions/communication.py +22 -0
  39. unrealon_core/exceptions/driver.py +11 -0
  40. unrealon_core/exceptions/proxy.py +11 -0
  41. unrealon_core/exceptions/task.py +12 -0
  42. unrealon_core/exceptions/validation.py +17 -0
  43. unrealon_core/models/__init__.py +98 -0
  44. unrealon_core/models/arq_context.py +252 -0
  45. unrealon_core/models/arq_responses.py +125 -0
  46. unrealon_core/models/base.py +291 -0
  47. unrealon_core/models/bridge_stats.py +58 -0
  48. unrealon_core/models/communication.py +39 -0
  49. unrealon_core/models/config.py +47 -0
  50. unrealon_core/models/connection_stats.py +47 -0
  51. unrealon_core/models/driver.py +30 -0
  52. unrealon_core/models/driver_details.py +98 -0
  53. unrealon_core/models/logging.py +28 -0
  54. unrealon_core/models/task.py +21 -0
  55. unrealon_core/models/typed_responses.py +210 -0
  56. unrealon_core/models/websocket/__init__.py +91 -0
  57. unrealon_core/models/websocket/base.py +49 -0
  58. unrealon_core/models/websocket/config.py +200 -0
  59. unrealon_core/models/websocket/driver.py +215 -0
  60. unrealon_core/models/websocket/errors.py +138 -0
  61. unrealon_core/models/websocket/heartbeat.py +100 -0
  62. unrealon_core/models/websocket/logging.py +261 -0
  63. unrealon_core/models/websocket/proxy.py +496 -0
  64. unrealon_core/models/websocket/tasks.py +275 -0
  65. unrealon_core/models/websocket/utils.py +153 -0
  66. unrealon_core/models/websocket_session.py +144 -0
  67. unrealon_core/monitoring/__init__.py +43 -0
  68. unrealon_core/monitoring/alerts.py +398 -0
  69. unrealon_core/monitoring/dashboard.py +307 -0
  70. unrealon_core/monitoring/health_check.py +354 -0
  71. unrealon_core/monitoring/metrics.py +352 -0
  72. unrealon_core/utils/__init__.py +11 -0
  73. unrealon_core/utils/time.py +61 -0
  74. unrealon_core/version.py +219 -0
  75. unrealon_driver/__init__.py +88 -50
  76. unrealon_driver/core_module/__init__.py +34 -0
  77. unrealon_driver/core_module/base.py +184 -0
  78. unrealon_driver/core_module/config.py +30 -0
  79. unrealon_driver/core_module/event_manager.py +127 -0
  80. unrealon_driver/core_module/protocols.py +98 -0
  81. unrealon_driver/core_module/registry.py +146 -0
  82. unrealon_driver/decorators/__init__.py +15 -0
  83. unrealon_driver/decorators/retry.py +117 -0
  84. unrealon_driver/decorators/schedule.py +137 -0
  85. unrealon_driver/decorators/task.py +61 -0
  86. unrealon_driver/decorators/timing.py +132 -0
  87. unrealon_driver/driver/__init__.py +20 -0
  88. unrealon_driver/driver/communication/__init__.py +10 -0
  89. unrealon_driver/driver/communication/session.py +203 -0
  90. unrealon_driver/driver/communication/websocket_client.py +197 -0
  91. unrealon_driver/driver/core/__init__.py +10 -0
  92. unrealon_driver/driver/core/config.py +85 -0
  93. unrealon_driver/driver/core/driver.py +221 -0
  94. unrealon_driver/driver/factory/__init__.py +9 -0
  95. unrealon_driver/driver/factory/manager_factory.py +130 -0
  96. unrealon_driver/driver/lifecycle/__init__.py +11 -0
  97. unrealon_driver/driver/lifecycle/daemon.py +76 -0
  98. unrealon_driver/driver/lifecycle/initialization.py +97 -0
  99. unrealon_driver/driver/lifecycle/shutdown.py +48 -0
  100. unrealon_driver/driver/monitoring/__init__.py +9 -0
  101. unrealon_driver/driver/monitoring/health.py +63 -0
  102. unrealon_driver/driver/utilities/__init__.py +10 -0
  103. unrealon_driver/driver/utilities/logging.py +51 -0
  104. unrealon_driver/driver/utilities/serialization.py +61 -0
  105. unrealon_driver/managers/__init__.py +32 -0
  106. unrealon_driver/managers/base.py +174 -0
  107. unrealon_driver/managers/browser.py +98 -0
  108. unrealon_driver/managers/cache.py +116 -0
  109. unrealon_driver/managers/http.py +107 -0
  110. unrealon_driver/managers/logger.py +286 -0
  111. unrealon_driver/managers/proxy.py +99 -0
  112. unrealon_driver/managers/registry.py +87 -0
  113. unrealon_driver/managers/threading.py +54 -0
  114. unrealon_driver/managers/update.py +107 -0
  115. unrealon_driver/utils/__init__.py +9 -0
  116. unrealon_driver/utils/time.py +10 -0
  117. unrealon/__init__.py +0 -40
  118. unrealon-1.1.5.dist-info/METADATA +0 -621
  119. unrealon-1.1.5.dist-info/RECORD +0 -54
  120. unrealon-1.1.5.dist-info/entry_points.txt +0 -9
  121. unrealon_browser/managers/stealth.py +0 -388
  122. unrealon_driver/exceptions.py +0 -33
  123. unrealon_driver/html_analyzer/__init__.py +0 -32
  124. unrealon_driver/html_analyzer/cleaner.py +0 -657
  125. unrealon_driver/html_analyzer/config.py +0 -64
  126. unrealon_driver/html_analyzer/manager.py +0 -247
  127. unrealon_driver/html_analyzer/models.py +0 -115
  128. unrealon_driver/html_analyzer/websocket_analyzer.py +0 -157
  129. unrealon_driver/models/__init__.py +0 -31
  130. unrealon_driver/models/websocket.py +0 -98
  131. unrealon_driver/parser/__init__.py +0 -36
  132. unrealon_driver/parser/cli_manager.py +0 -142
  133. unrealon_driver/parser/daemon_manager.py +0 -403
  134. unrealon_driver/parser/managers/__init__.py +0 -25
  135. unrealon_driver/parser/managers/config.py +0 -293
  136. unrealon_driver/parser/managers/error.py +0 -412
  137. unrealon_driver/parser/managers/result.py +0 -321
  138. unrealon_driver/parser/parser_manager.py +0 -458
  139. unrealon_driver/smart_logging/__init__.py +0 -24
  140. unrealon_driver/smart_logging/models.py +0 -44
  141. unrealon_driver/smart_logging/smart_logger.py +0 -406
  142. unrealon_driver/smart_logging/unified_logger.py +0 -525
  143. unrealon_driver/websocket/__init__.py +0 -31
  144. unrealon_driver/websocket/client.py +0 -249
  145. unrealon_driver/websocket/config.py +0 -188
  146. 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,197 @@
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
+ from websockets.asyncio.client import ClientConnection
13
+
14
+ from unrealon_core.models.websocket import (
15
+ DriverRegistrationMessage,
16
+ DriverRegistrationData,
17
+ WebSocketMessage
18
+ )
19
+ from unrealon_core.enums.types import MessageType
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class WebSocketClient:
25
+ """
26
+ Clean WebSocket client with robust connection management.
27
+
28
+ Features:
29
+ - Auto-reconnection with exponential backoff
30
+ - Message queuing during disconnections
31
+ - Background message processing
32
+ - Clean error handling
33
+ """
34
+
35
+ def __init__(self, websocket_url: str, driver_id: str):
36
+ self.websocket_url = websocket_url
37
+ self.driver_id = driver_id
38
+ self.websocket: Optional[ClientConnection] = None
39
+ self.connected = False
40
+ self.running = False
41
+
42
+ # Message handling
43
+ self.on_message: Optional[Callable[[WebSocketMessage], None]] = None
44
+ self.message_queue: deque = deque()
45
+
46
+ # Background tasks
47
+ self._sender_task: Optional[asyncio.Task] = None
48
+ self._receiver_task: Optional[asyncio.Task] = None
49
+ self._monitor_task: Optional[asyncio.Task] = None
50
+
51
+ # Reconnection settings
52
+ self.reconnect_delay = 1.0
53
+ self.max_reconnect_delay = 60.0
54
+ self.reconnect_attempts = 0
55
+
56
+ async def connect(self) -> bool:
57
+ """Connect and start background tasks."""
58
+ if self.running:
59
+ return True
60
+
61
+ self.running = True
62
+
63
+ # Start background tasks
64
+ self._sender_task = asyncio.create_task(self._message_sender())
65
+ self._receiver_task = asyncio.create_task(self._message_receiver())
66
+ self._monitor_task = asyncio.create_task(self._connection_monitor())
67
+
68
+ return await self._establish_connection()
69
+
70
+ async def disconnect(self):
71
+ """Clean disconnect and stop all tasks."""
72
+ self.running = False
73
+
74
+ # Cancel background tasks
75
+ for task in [self._sender_task, self._receiver_task, self._monitor_task]:
76
+ if task and not task.done():
77
+ task.cancel()
78
+ try:
79
+ await task
80
+ except asyncio.CancelledError:
81
+ pass
82
+
83
+ # Close WebSocket
84
+ if self.websocket:
85
+ await self.websocket.close()
86
+ self.websocket = None
87
+
88
+ self.connected = False
89
+ logger.info("WebSocket client stopped")
90
+
91
+ async def _establish_connection(self) -> bool:
92
+ """Establish WebSocket connection."""
93
+ try:
94
+ logger.info(f"Connecting to WebSocket: {self.websocket_url}")
95
+ self.websocket = await websockets.connect(self.websocket_url)
96
+ self.connected = True
97
+ self.reconnect_attempts = 0
98
+ self.reconnect_delay = 1.0
99
+ logger.info("WebSocket connected successfully")
100
+ return True
101
+ except Exception as e:
102
+ logger.error(f"WebSocket connection failed: {e}")
103
+ self.connected = False
104
+ return False
105
+
106
+ async def _connection_monitor(self):
107
+ """Monitor connection and handle reconnection."""
108
+ while self.running:
109
+ if not self.connected and self.running:
110
+ logger.info(f"Attempting reconnection (attempt {self.reconnect_attempts + 1})")
111
+
112
+ if await self._establish_connection():
113
+ logger.info("Reconnection successful")
114
+ else:
115
+ self.reconnect_attempts += 1
116
+ # Exponential backoff
117
+ self.reconnect_delay = min(
118
+ self.reconnect_delay * 2,
119
+ self.max_reconnect_delay
120
+ )
121
+ await asyncio.sleep(self.reconnect_delay)
122
+ else:
123
+ await asyncio.sleep(5.0) # Check every 5 seconds
124
+
125
+ async def _message_sender(self):
126
+ """Background task to send queued messages."""
127
+ while self.running:
128
+ if self.connected and self.websocket and self.message_queue:
129
+ try:
130
+ message = self.message_queue.popleft()
131
+ await self.websocket.send(message)
132
+ except (websockets.exceptions.ConnectionClosed, ConnectionResetError):
133
+ self.connected = False
134
+ # Put message back in queue
135
+ self.message_queue.appendleft(message)
136
+ except Exception as e:
137
+ logger.error(f"Error sending message: {e}")
138
+ else:
139
+ await asyncio.sleep(0.1)
140
+
141
+ async def _message_receiver(self):
142
+ """Background task to receive messages."""
143
+ while self.running:
144
+ if self.connected and self.websocket:
145
+ try:
146
+ message_str = await self.websocket.recv()
147
+
148
+ # Parse and validate message
149
+ message_data = json.loads(message_str)
150
+ message = WebSocketMessage.model_validate(message_data)
151
+
152
+ # Handle message
153
+ if self.on_message:
154
+ self.on_message(message)
155
+
156
+ except (websockets.exceptions.ConnectionClosed, ConnectionResetError):
157
+ self.connected = False
158
+ logger.warning("WebSocket connection lost")
159
+ except Exception as e:
160
+ logger.error(f"Error receiving message: {e}")
161
+ else:
162
+ await asyncio.sleep(0.1)
163
+
164
+ def send(self, message_data) -> None:
165
+ """Queue message for sending."""
166
+ if hasattr(message_data, 'model_dump_json'):
167
+ # Pydantic model
168
+ message_json = message_data.model_dump_json()
169
+ else:
170
+ # Raw data
171
+ message_json = json.dumps(message_data)
172
+
173
+ self.message_queue.append(message_json)
174
+
175
+ async def register_driver(self, capabilities: List[str]) -> bool:
176
+ """Register driver with capabilities."""
177
+ try:
178
+ # Create registration data
179
+ registration_data = DriverRegistrationData(
180
+ driver_id=self.driver_id,
181
+ driver_name=self.driver_id,
182
+ driver_type="universal",
183
+ capabilities=capabilities
184
+ )
185
+
186
+ # Create registration message with correct type
187
+ registration_message = DriverRegistrationMessage(data=registration_data)
188
+
189
+ # Queue for sending
190
+ self.send(registration_message)
191
+
192
+ logger.info(f"Driver registration queued: {self.driver_id}")
193
+ return True
194
+
195
+ except Exception as e:
196
+ logger.error(f"Driver registration failed: {e}")
197
+ 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"]
@@ -0,0 +1,85 @@
1
+ """
2
+ Clean driver configuration without hardcoded values.
3
+ """
4
+
5
+ import os
6
+ from enum import Enum
7
+ from typing import Optional
8
+ from pydantic import BaseModel, Field, computed_field
9
+
10
+
11
+ class DriverMode(str, Enum):
12
+ """Driver operation modes."""
13
+ STANDALONE = "standalone"
14
+ DAEMON = "daemon"
15
+
16
+
17
+ class DriverConfig(BaseModel):
18
+ """
19
+ Clean driver configuration.
20
+ No hardcoded presets - user configures everything explicitly.
21
+ """
22
+
23
+ # Basic settings
24
+ name: str = Field(..., description="Driver name")
25
+ mode: DriverMode = Field(default=DriverMode.STANDALONE, description="Operation mode")
26
+
27
+ # WebSocket connection (auto-detected)
28
+ websocket_url: Optional[str] = Field(default=None, description="Manual WebSocket URL override")
29
+ websocket_timeout: int = Field(default=30, description="WebSocket timeout seconds")
30
+
31
+ @computed_field
32
+ @property
33
+ def effective_websocket_url(self) -> Optional[str]:
34
+ """
35
+ Auto-detect WebSocket URL from multiple sources.
36
+
37
+ Priority:
38
+ 1. Explicit websocket_url field
39
+ 2. Environment variable UNREALON_WEBSOCKET_URL
40
+ 3. Environment variable UNREALON_WS_URL
41
+ 4. Default localhost for development
42
+ """
43
+ # 1. Explicit override
44
+ if self.websocket_url:
45
+ return self.websocket_url
46
+
47
+ # 2. Environment variables
48
+ env_url = (
49
+ os.getenv('UNREALON_WEBSOCKET_URL') or
50
+ os.getenv('UNREALON_WS_URL') or
51
+ os.getenv('WS_URL')
52
+ )
53
+ if env_url:
54
+ return env_url
55
+
56
+ # 3. No default - return None if nothing is configured
57
+ return None
58
+
59
+ # Logging
60
+ log_level: str = Field(default="INFO", description="Logging level")
61
+ log_file: Optional[str] = Field(default=None, description="Log file path")
62
+
63
+ # HTTP settings
64
+ http_timeout: int = Field(default=30, description="HTTP timeout seconds")
65
+ max_retries: int = Field(default=3, description="Max HTTP retries")
66
+
67
+ # Browser settings
68
+ browser_headless: bool = Field(default=True, description="Run browser headless")
69
+ browser_timeout: int = Field(default=30, description="Browser timeout seconds")
70
+
71
+ # Proxy settings
72
+ proxy_enabled: bool = Field(default=False, description="Enable proxy rotation")
73
+ proxy_rotation_interval: int = Field(default=300, description="Proxy rotation seconds")
74
+
75
+ # Cache settings
76
+ cache_enabled: bool = Field(default=True, description="Enable response caching")
77
+ cache_ttl: int = Field(default=3600, description="Cache TTL seconds")
78
+
79
+ # Threading
80
+ max_workers: int = Field(default=4, description="Max thread workers")
81
+
82
+ # Performance
83
+ batch_size: int = Field(default=10, description="Batch processing size")
84
+
85
+ model_config = {"extra": "forbid"}