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,97 @@
1
+ """
2
+ Driver initialization logic.
3
+
4
+ Handles the setup and initialization of all driver components.
5
+ """
6
+
7
+ import logging
8
+ from typing import List, TYPE_CHECKING
9
+
10
+ from ..core.config import DriverMode
11
+ from ..communication.websocket_client import WebSocketClient
12
+ from ..communication.session import DriverSession
13
+
14
+ if TYPE_CHECKING:
15
+ from ..core.driver import UniversalDriver
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class DriverInitializer:
21
+ """Handles driver initialization process."""
22
+
23
+ @staticmethod
24
+ async def initialize_driver(driver: 'UniversalDriver', capabilities: List[str] = []) -> bool:
25
+ """
26
+ Initialize driver with all components.
27
+
28
+ Args:
29
+ driver: UniversalDriver instance
30
+ capabilities: List of driver capabilities
31
+
32
+ Returns:
33
+ True if initialization successful
34
+ """
35
+ # Store capabilities
36
+ driver.capabilities = capabilities
37
+
38
+ try:
39
+ logger.info(f"Initializing UniversalDriver: {driver.driver_id}")
40
+
41
+ # Initialize manager system
42
+ if not await driver.manager_registry.initialize_all():
43
+ logger.error("Manager initialization failed")
44
+ return False
45
+
46
+ # Initialize module system
47
+ await driver.module_registry.initialize_all()
48
+
49
+ # Setup WebSocket and session
50
+ await DriverInitializer._setup_communication(driver, capabilities)
51
+
52
+ logger.info(f"UniversalDriver {driver.driver_id} initialized successfully")
53
+ driver.is_initialized = True
54
+ return True
55
+
56
+ except Exception as e:
57
+ logger.error(f"Driver initialization failed: {e}")
58
+ return False
59
+
60
+ @staticmethod
61
+ async def _setup_communication(driver: 'UniversalDriver', capabilities: List[str]):
62
+ """Setup WebSocket client and session based on driver mode."""
63
+ # Get WebSocket URL from config (auto-detected)
64
+ websocket_url = driver.config.effective_websocket_url
65
+
66
+ # Setup WebSocket client if URL available
67
+ if websocket_url:
68
+ driver.websocket_client = WebSocketClient(
69
+ websocket_url,
70
+ driver.driver_id
71
+ )
72
+
73
+ # Set WebSocket client for logger (always for logging)
74
+ if driver.logger_manager:
75
+ driver.logger_manager.websocket_client = driver.websocket_client
76
+
77
+ # Only create RPC session in DAEMON mode
78
+ if driver.config.mode == DriverMode.DAEMON:
79
+ # Create session
80
+ driver.session = DriverSession(
81
+ driver.driver_id,
82
+ driver.websocket_client
83
+ )
84
+
85
+ # Start session with capabilities
86
+ if capabilities is not None:
87
+ if not await driver.session.start_session(capabilities):
88
+ logger.error("Session start failed")
89
+ raise RuntimeError("Session start failed")
90
+ else:
91
+ # In STANDALONE mode - try to connect WebSocket for logging (optional)
92
+ try:
93
+ await driver.websocket_client.connect()
94
+ logger.info("WebSocket connected for logging only (standalone mode)")
95
+ except Exception as e:
96
+ logger.warning(f"WebSocket connection failed in standalone mode (will use local logging): {e}")
97
+ # Continue without WebSocket - not critical in standalone mode
@@ -0,0 +1,48 @@
1
+ """
2
+ Driver shutdown logic.
3
+
4
+ Handles clean shutdown of all driver components.
5
+ """
6
+
7
+ import logging
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from ..core.driver import UniversalDriver
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class DriverShutdown:
17
+ """Handles driver shutdown process."""
18
+
19
+ @staticmethod
20
+ async def shutdown_driver(driver: 'UniversalDriver'):
21
+ """Shutdown driver cleanly."""
22
+ try:
23
+ logger.info(f"Shutting down UniversalDriver: {driver.driver_id}")
24
+
25
+ # Stop session
26
+ if driver.session:
27
+ await driver.session.stop_session()
28
+
29
+ # Disconnect WebSocket client
30
+ if driver.websocket_client:
31
+ await driver.websocket_client.disconnect()
32
+
33
+ # Stop event manager
34
+ await driver.event_manager.stop()
35
+
36
+ # Stop module system
37
+ await driver.module_registry.stop_all()
38
+
39
+ # Shutdown managers
40
+ await driver.manager_registry.shutdown_all()
41
+
42
+ logger.info(f"UniversalDriver {driver.driver_id} shutdown complete")
43
+ driver.is_initialized = False
44
+
45
+ except Exception as e:
46
+ logger.error(f"Driver shutdown error: {e}")
47
+ # Still mark as not initialized even if shutdown failed
48
+ driver.is_initialized = False
@@ -0,0 +1,9 @@
1
+ """
2
+ Driver monitoring and health checks.
3
+
4
+ Provides health status and monitoring capabilities.
5
+ """
6
+
7
+ from .health import HealthMonitor
8
+
9
+ __all__ = ["HealthMonitor"]
@@ -0,0 +1,63 @@
1
+ """
2
+ Health monitoring for driver components.
3
+
4
+ Provides health checks and status monitoring for the driver.
5
+ """
6
+
7
+ import logging
8
+ from typing import Dict, Any, TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from ..core.driver import UniversalDriver
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class HealthMonitor:
17
+ """Monitors driver health and provides status information."""
18
+
19
+ @staticmethod
20
+ async def get_health_status(driver: 'UniversalDriver') -> Dict[str, Any]:
21
+ """Get comprehensive health status."""
22
+ try:
23
+ # Get manager health
24
+ manager_health = await driver.manager_registry.health_check_all()
25
+
26
+ # Get module health
27
+ module_health = await driver.module_registry.health_check_all()
28
+
29
+ # Get session status
30
+ session_status = None
31
+ if driver.session:
32
+ session_status = driver.session.get_status()
33
+
34
+ return {
35
+ "driver_id": driver.driver_id,
36
+ "config": {
37
+ "mode": driver.config.mode.value,
38
+ "websocket_enabled": driver.config.websocket_url is not None
39
+ },
40
+ "managers": manager_health,
41
+ "modules": module_health,
42
+ "session": session_status
43
+ }
44
+
45
+ except Exception as e:
46
+ logger.error(f"Health check failed: {e}")
47
+ return {
48
+ "driver_id": driver.driver_id,
49
+ "status": "error",
50
+ "error": str(e)
51
+ }
52
+
53
+ @staticmethod
54
+ def get_basic_status(driver: 'UniversalDriver') -> Dict[str, Any]:
55
+ """Get basic driver status (synchronous)."""
56
+ return {
57
+ "driver_id": driver.driver_id,
58
+ "is_initialized": driver.is_initialized,
59
+ "is_daemon_mode": driver.session is not None,
60
+ "is_connected": driver.websocket_client is not None and driver.websocket_client.is_connected,
61
+ "manager_count": len(driver.manager_registry.managers) if driver.manager_registry else 0,
62
+ "capabilities": driver.capabilities
63
+ }
@@ -0,0 +1,10 @@
1
+ """
2
+ Driver utilities.
3
+
4
+ Common utilities for logging, serialization, and result handling.
5
+ """
6
+
7
+ from .logging import LoggingUtility
8
+ from .serialization import SerializationUtility
9
+
10
+ __all__ = ["LoggingUtility", "SerializationUtility"]
@@ -0,0 +1,51 @@
1
+ """
2
+ Logging utilities for driver components.
3
+
4
+ Provides convenient logging methods and utilities.
5
+ """
6
+
7
+ import logging
8
+ from typing import Dict, Any, Optional, TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from unrealon_driver.managers.logger import LoggerManager
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class LoggingUtility:
17
+ """Utility class for driver logging operations."""
18
+
19
+ def __init__(self, driver_id: str):
20
+ """Initialize logging utility."""
21
+ self.driver_id = driver_id
22
+ self.logger_manager: Optional['LoggerManager'] = None
23
+
24
+ def log(self, level: str, message: str, context: Optional[Dict[str, Any]] = None):
25
+ """Log message through logger manager."""
26
+ if self.logger_manager:
27
+ self.logger_manager.log(level, message, context)
28
+ else:
29
+ # Fallback to standard logging
30
+ log_func = getattr(logger, level.lower(), logger.info)
31
+ log_func(f"{message} | Context: {context}" if context else message)
32
+
33
+ def debug(self, message: str, context: Optional[Dict[str, Any]] = None):
34
+ """Log debug message."""
35
+ self.log("DEBUG", message, context)
36
+
37
+ def info(self, message: str, context: Optional[Dict[str, Any]] = None):
38
+ """Log info message."""
39
+ self.log("INFO", message, context)
40
+
41
+ def warning(self, message: str, context: Optional[Dict[str, Any]] = None):
42
+ """Log warning message."""
43
+ self.log("WARNING", message, context)
44
+
45
+ def error(self, message: str, context: Optional[Dict[str, Any]] = None):
46
+ """Log error message."""
47
+ self.log("ERROR", message, context)
48
+
49
+ def critical(self, message: str, context: Optional[Dict[str, Any]] = None):
50
+ """Log critical message."""
51
+ self.log("CRITICAL", message, context)
@@ -0,0 +1,61 @@
1
+ """
2
+ Serialization utilities for driver components.
3
+
4
+ Handles result saving and JSON serialization with Pydantic support.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+ from typing import Any, Optional
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class SerializationUtility:
17
+ """Utility class for serialization and result saving."""
18
+
19
+ @staticmethod
20
+ def save_results_to_file(data: dict, filename: str, results_dir: Optional[str] = None) -> Path:
21
+ """
22
+ Save parsing results to JSON file with automatic serialization.
23
+
24
+ Args:
25
+ data: Data to save (can contain Pydantic models)
26
+ filename: Base filename (without extension)
27
+ results_dir: Directory to save to (default: ./data/results)
28
+
29
+ Returns:
30
+ Path to saved file
31
+ """
32
+ if results_dir is None:
33
+ results_dir = "./data/results"
34
+
35
+ results_path = Path(results_dir)
36
+ results_path.mkdir(parents=True, exist_ok=True)
37
+
38
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
39
+ filepath = results_path / f"{filename}_{timestamp}.json"
40
+
41
+ # Convert Pydantic models to dict for JSON serialization
42
+ serializable_data = SerializationUtility._serialize_for_json(data)
43
+
44
+ with open(filepath, 'w', encoding='utf-8') as f:
45
+ json.dump(serializable_data, f, indent=2, ensure_ascii=False, default=str)
46
+
47
+ logger.info(f"Results saved to: {filepath}")
48
+ return filepath
49
+
50
+ @staticmethod
51
+ def _serialize_for_json(data: Any) -> Any:
52
+ """Recursively serialize data for JSON, handling Pydantic models."""
53
+ if hasattr(data, "model_dump"):
54
+ # Pydantic v2 model
55
+ return data.model_dump()
56
+ elif isinstance(data, dict):
57
+ return {key: SerializationUtility._serialize_for_json(value) for key, value in data.items()}
58
+ elif isinstance(data, list):
59
+ return [SerializationUtility._serialize_for_json(item) for item in data]
60
+ else:
61
+ return data
@@ -0,0 +1,32 @@
1
+ """
2
+ Clean manager system for UnrealOn Driver.
3
+ """
4
+
5
+ from .base import BaseManager, ManagerConfig, ManagerStatus
6
+ from .logger import LoggerManager, LoggerManagerConfig
7
+ from .http import HttpManager, HttpManagerConfig
8
+ from .browser import BrowserManager, BrowserManagerConfig
9
+ from .cache import CacheManager, CacheManagerConfig
10
+ from .proxy import ProxyManager, ProxyManagerConfig
11
+ from .threading import ThreadManager, ThreadManagerConfig
12
+ from .update import UpdateManager, UpdateManagerConfig
13
+ from .registry import ManagerRegistry
14
+
15
+ __all__ = [
16
+ # Base
17
+ "BaseManager",
18
+ "ManagerConfig",
19
+ "ManagerStatus",
20
+
21
+ # Managers
22
+ "LoggerManager", "LoggerManagerConfig",
23
+ "HttpManager", "HttpManagerConfig",
24
+ "BrowserManager", "BrowserManagerConfig",
25
+ "CacheManager", "CacheManagerConfig",
26
+ "ProxyManager", "ProxyManagerConfig",
27
+ "ThreadManager", "ThreadManagerConfig",
28
+ "UpdateManager", "UpdateManagerConfig",
29
+
30
+ # Registry
31
+ "ManagerRegistry",
32
+ ]
@@ -0,0 +1,174 @@
1
+ """
2
+ Clean base manager system.
3
+ """
4
+
5
+ import asyncio
6
+ import logging
7
+ from abc import ABC, abstractmethod
8
+ from datetime import datetime
9
+ from typing import Optional, Dict, Any
10
+ from enum import Enum
11
+
12
+ from pydantic import BaseModel, Field
13
+
14
+ from ..utils.time import utc_now
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ManagerStatus(str, Enum):
20
+ """Manager lifecycle status."""
21
+ UNINITIALIZED = "uninitialized"
22
+ INITIALIZING = "initializing"
23
+ READY = "ready"
24
+ ERROR = "error"
25
+ SHUTTING_DOWN = "shutting_down"
26
+ SHUTDOWN = "shutdown"
27
+
28
+
29
+ class ManagerConfig(BaseModel):
30
+ """Base configuration for all managers."""
31
+ enabled: bool = Field(default=True, description="Whether manager is enabled")
32
+ timeout: int = Field(default=30, description="Operation timeout seconds")
33
+ max_retries: int = Field(default=3, description="Max retry attempts")
34
+ log_level: str = Field(default="INFO", description="Logging level")
35
+
36
+ model_config = {"extra": "forbid"}
37
+
38
+
39
+ class ManagerStats(BaseModel):
40
+ """Manager operation statistics."""
41
+ operations_total: int = 0
42
+ operations_successful: int = 0
43
+ operations_failed: int = 0
44
+ last_operation: Optional[datetime] = None
45
+ average_duration: float = 0.0
46
+
47
+ def record_operation(self, success: bool, duration: float):
48
+ """Record operation result."""
49
+ self.operations_total += 1
50
+ if success:
51
+ self.operations_successful += 1
52
+ else:
53
+ self.operations_failed += 1
54
+
55
+ self.last_operation = utc_now()
56
+
57
+ # Simple moving average
58
+ if self.operations_total == 1:
59
+ self.average_duration = duration
60
+ else:
61
+ self.average_duration = (
62
+ (self.average_duration * (self.operations_total - 1) + duration)
63
+ / self.operations_total
64
+ )
65
+
66
+ def get_success_rate(self) -> float:
67
+ """Get success rate percentage."""
68
+ if self.operations_total == 0:
69
+ return 0.0
70
+ return (self.operations_successful / self.operations_total) * 100.0
71
+
72
+
73
+ class BaseManager(ABC):
74
+ """
75
+ Clean base manager class.
76
+
77
+ Provides common functionality for all managers:
78
+ - Lifecycle management
79
+ - Statistics tracking
80
+ - Error handling
81
+ - Health checks
82
+ """
83
+
84
+ def __init__(self, config: ManagerConfig, name: str):
85
+ self.config = config
86
+ self.name = name
87
+ self.status = ManagerStatus.UNINITIALIZED
88
+ self.stats = ManagerStats()
89
+ self.logger = logging.getLogger(f"{__name__}.{name}")
90
+
91
+ # Set log level
92
+ if hasattr(logging, config.log_level):
93
+ self.logger.setLevel(getattr(logging, config.log_level))
94
+
95
+ async def initialize(self) -> bool:
96
+ """Initialize manager."""
97
+ if not self.config.enabled:
98
+ self.logger.info(f"Manager {self.name} is disabled")
99
+ self.status = ManagerStatus.SHUTDOWN
100
+ return True
101
+
102
+ try:
103
+ self.status = ManagerStatus.INITIALIZING
104
+ self.logger.info(f"Initializing manager: {self.name}")
105
+
106
+ success = await self._initialize()
107
+
108
+ if success:
109
+ self.status = ManagerStatus.READY
110
+ self.logger.info(f"Manager {self.name} initialized successfully")
111
+ else:
112
+ self.status = ManagerStatus.ERROR
113
+ self.logger.error(f"Manager {self.name} initialization failed")
114
+
115
+ return success
116
+
117
+ except Exception as e:
118
+ self.status = ManagerStatus.ERROR
119
+ self.logger.error(f"Manager {self.name} initialization error: {e}")
120
+ return False
121
+
122
+ async def shutdown(self):
123
+ """Shutdown manager."""
124
+ try:
125
+ self.status = ManagerStatus.SHUTTING_DOWN
126
+ self.logger.info(f"Shutting down manager: {self.name}")
127
+
128
+ await self._shutdown()
129
+
130
+ self.status = ManagerStatus.SHUTDOWN
131
+ self.logger.info(f"Manager {self.name} shutdown complete")
132
+
133
+ except Exception as e:
134
+ self.logger.error(f"Manager {self.name} shutdown error: {e}")
135
+ self.status = ManagerStatus.ERROR
136
+
137
+ @abstractmethod
138
+ async def _initialize(self) -> bool:
139
+ """Manager-specific initialization."""
140
+ pass
141
+
142
+ @abstractmethod
143
+ async def _shutdown(self):
144
+ """Manager-specific shutdown."""
145
+ pass
146
+
147
+ async def health_check(self) -> Dict[str, Any]:
148
+ """Perform health check."""
149
+ try:
150
+ health_data = await self._health_check()
151
+
152
+ return {
153
+ "name": self.name,
154
+ "status": self.status.value,
155
+ "enabled": self.config.enabled,
156
+ "stats": self.stats.model_dump(),
157
+ "health": health_data
158
+ }
159
+
160
+ except Exception as e:
161
+ self.logger.error(f"Health check failed for {self.name}: {e}")
162
+ return {
163
+ "name": self.name,
164
+ "status": "error",
165
+ "error": str(e)
166
+ }
167
+
168
+ async def _health_check(self) -> Dict[str, Any]:
169
+ """Manager-specific health check."""
170
+ return {"status": "ok"}
171
+
172
+ def is_ready(self) -> bool:
173
+ """Check if manager is ready."""
174
+ return self.status == ManagerStatus.READY
@@ -0,0 +1,98 @@
1
+ """
2
+ Clean browser manager using unrealon_browser.
3
+ """
4
+
5
+ from typing import Optional
6
+ from pydantic import Field
7
+
8
+ from unrealon_browser import BrowserManager as CoreBrowserManager, BrowserConfig
9
+ from .base import BaseManager, ManagerConfig
10
+
11
+
12
+ class BrowserManagerConfig(ManagerConfig):
13
+ """Browser manager configuration."""
14
+
15
+ headless: bool = Field(default=True, description="Run headless")
16
+ parser_name: str = Field(..., description="Parser name for browser")
17
+
18
+
19
+ class BrowserManager(BaseManager):
20
+ """Clean browser manager wrapper."""
21
+
22
+ def __init__(self, config: BrowserManagerConfig):
23
+ super().__init__(config, "browser")
24
+ self.config: BrowserManagerConfig = config
25
+ self.browser: Optional[CoreBrowserManager] = None
26
+
27
+ async def _initialize(self) -> bool:
28
+ """Initialize browser manager (but not the actual browser yet)."""
29
+ try:
30
+ # Don't initialize the actual browser here - do it lazily on first use
31
+ # This prevents browser from starting in daemon mode until needed
32
+ self.logger.info("Browser manager ready (browser will start on first use)")
33
+ return True
34
+
35
+ except Exception as e:
36
+ self.logger.error(f"Browser manager initialization failed: {e}")
37
+ return False
38
+
39
+ async def _shutdown(self):
40
+ """Shutdown browser."""
41
+ if self.browser:
42
+ await self.browser.close_async()
43
+ self.browser = None
44
+
45
+ async def _ensure_browser_initialized(self) -> bool:
46
+ """Ensure browser is initialized (lazy initialization)."""
47
+ if self.browser is not None:
48
+ return True
49
+
50
+ try:
51
+ self.logger.info("🚀 Starting browser (lazy initialization)...")
52
+
53
+ # Create browser config
54
+ browser_config = BrowserConfig(parser_name=self.config.parser_name)
55
+
56
+ # Create browser manager
57
+ self.browser = CoreBrowserManager(browser_config)
58
+
59
+ # Initialize browser
60
+ await self.browser.initialize_async()
61
+
62
+ self.logger.info("✅ Browser started successfully")
63
+ return True
64
+
65
+ except Exception as e:
66
+ self.logger.error(f"❌ Browser initialization failed: {e}")
67
+ self.browser = None
68
+ return False
69
+
70
+ async def navigate(self, url: str) -> bool:
71
+ """Navigate to URL (with lazy browser initialization)."""
72
+ # Ensure browser is initialized
73
+ if not await self._ensure_browser_initialized():
74
+ raise RuntimeError("Failed to initialize browser")
75
+
76
+ try:
77
+ await self.browser.navigate_async(url)
78
+ self.stats.record_operation(True, 0.0)
79
+ return True
80
+ except Exception as e:
81
+ self.logger.error(f"Navigation failed: {e}")
82
+ self.stats.record_operation(False, 0.0)
83
+ return False
84
+
85
+ async def get_html(self) -> Optional[str]:
86
+ """Get page HTML (with lazy browser initialization)."""
87
+ # Ensure browser is initialized
88
+ if not await self._ensure_browser_initialized():
89
+ raise RuntimeError("Failed to initialize browser")
90
+
91
+ try:
92
+ html = await self.browser.get_page_content_async()
93
+ self.stats.record_operation(True, 0.0)
94
+ return html
95
+ except Exception as e:
96
+ self.logger.error(f"Get HTML failed: {e}")
97
+ self.stats.record_operation(False, 0.0)
98
+ return None