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.
- {unrealon-1.1.6.dist-info/licenses → unrealon-2.0.5.dist-info}/LICENSE +1 -1
- unrealon-2.0.5.dist-info/METADATA +491 -0
- unrealon-2.0.5.dist-info/RECORD +128 -0
- {unrealon-1.1.6.dist-info → unrealon-2.0.5.dist-info}/WHEEL +2 -1
- unrealon-2.0.5.dist-info/entry_points.txt +3 -0
- unrealon-2.0.5.dist-info/top_level.txt +3 -0
- unrealon_browser/__init__.py +5 -6
- unrealon_browser/cli/browser_cli.py +18 -9
- unrealon_browser/cli/interactive_mode.py +13 -4
- unrealon_browser/core/browser_manager.py +29 -16
- unrealon_browser/dto/__init__.py +21 -0
- unrealon_browser/dto/bot_detection.py +175 -0
- unrealon_browser/dto/models/config.py +9 -3
- unrealon_browser/managers/__init__.py +1 -1
- unrealon_browser/managers/logger_bridge.py +1 -4
- unrealon_browser/stealth/__init__.py +27 -0
- unrealon_browser/stealth/bypass_techniques.pyc +0 -0
- unrealon_browser/stealth/manager.pyc +0 -0
- unrealon_browser/stealth/nodriver_stealth.pyc +0 -0
- unrealon_browser/stealth/playwright_stealth.pyc +0 -0
- unrealon_browser/stealth/scanner_tester.pyc +0 -0
- unrealon_browser/stealth/undetected_chrome.pyc +0 -0
- unrealon_core/__init__.py +172 -0
- unrealon_core/config/__init__.py +16 -0
- unrealon_core/config/environment.py +151 -0
- unrealon_core/config/urls.py +94 -0
- unrealon_core/enums/__init__.py +24 -0
- unrealon_core/enums/status.py +216 -0
- unrealon_core/enums/types.py +240 -0
- unrealon_core/error_handling/__init__.py +45 -0
- unrealon_core/error_handling/circuit_breaker.py +292 -0
- unrealon_core/error_handling/error_context.py +324 -0
- unrealon_core/error_handling/recovery.py +371 -0
- unrealon_core/error_handling/retry.py +268 -0
- unrealon_core/exceptions/__init__.py +46 -0
- unrealon_core/exceptions/base.py +292 -0
- unrealon_core/exceptions/communication.py +22 -0
- unrealon_core/exceptions/driver.py +11 -0
- unrealon_core/exceptions/proxy.py +11 -0
- unrealon_core/exceptions/task.py +12 -0
- unrealon_core/exceptions/validation.py +17 -0
- unrealon_core/models/__init__.py +79 -0
- unrealon_core/models/arq_context.py +252 -0
- unrealon_core/models/arq_responses.py +125 -0
- unrealon_core/models/base.py +291 -0
- unrealon_core/models/bridge_stats.py +58 -0
- unrealon_core/models/communication.py +39 -0
- unrealon_core/models/connection_stats.py +47 -0
- unrealon_core/models/driver.py +30 -0
- unrealon_core/models/driver_details.py +98 -0
- unrealon_core/models/logging.py +28 -0
- unrealon_core/models/task.py +21 -0
- unrealon_core/models/typed_responses.py +210 -0
- unrealon_core/models/websocket/__init__.py +91 -0
- unrealon_core/models/websocket/base.py +49 -0
- unrealon_core/models/websocket/config.py +200 -0
- unrealon_core/models/websocket/driver.py +215 -0
- unrealon_core/models/websocket/errors.py +138 -0
- unrealon_core/models/websocket/heartbeat.py +100 -0
- unrealon_core/models/websocket/logging.py +261 -0
- unrealon_core/models/websocket/proxy.py +496 -0
- unrealon_core/models/websocket/tasks.py +275 -0
- unrealon_core/models/websocket/utils.py +153 -0
- unrealon_core/models/websocket_session.py +144 -0
- unrealon_core/monitoring/__init__.py +43 -0
- unrealon_core/monitoring/alerts.py +398 -0
- unrealon_core/monitoring/dashboard.py +307 -0
- unrealon_core/monitoring/health_check.py +354 -0
- unrealon_core/monitoring/metrics.py +352 -0
- unrealon_core/utils/__init__.py +11 -0
- unrealon_core/utils/time.py +61 -0
- unrealon_core/version.py +219 -0
- unrealon_driver/__init__.py +90 -51
- unrealon_driver/core_module/__init__.py +34 -0
- unrealon_driver/core_module/base.py +184 -0
- unrealon_driver/core_module/config.py +30 -0
- unrealon_driver/core_module/event_manager.py +127 -0
- unrealon_driver/core_module/protocols.py +98 -0
- unrealon_driver/core_module/registry.py +146 -0
- unrealon_driver/decorators/__init__.py +15 -0
- unrealon_driver/decorators/retry.py +117 -0
- unrealon_driver/decorators/schedule.py +137 -0
- unrealon_driver/decorators/task.py +61 -0
- unrealon_driver/decorators/timing.py +132 -0
- unrealon_driver/driver/__init__.py +20 -0
- unrealon_driver/driver/communication/__init__.py +10 -0
- unrealon_driver/driver/communication/session.py +203 -0
- unrealon_driver/driver/communication/websocket_client.py +205 -0
- unrealon_driver/driver/core/__init__.py +10 -0
- unrealon_driver/driver/core/config.py +175 -0
- unrealon_driver/driver/core/driver.py +221 -0
- unrealon_driver/driver/factory/__init__.py +9 -0
- unrealon_driver/driver/factory/manager_factory.py +130 -0
- unrealon_driver/driver/lifecycle/__init__.py +11 -0
- unrealon_driver/driver/lifecycle/daemon.py +76 -0
- unrealon_driver/driver/lifecycle/initialization.py +97 -0
- unrealon_driver/driver/lifecycle/shutdown.py +48 -0
- unrealon_driver/driver/monitoring/__init__.py +9 -0
- unrealon_driver/driver/monitoring/health.py +63 -0
- unrealon_driver/driver/utilities/__init__.py +10 -0
- unrealon_driver/driver/utilities/logging.py +51 -0
- unrealon_driver/driver/utilities/serialization.py +61 -0
- unrealon_driver/managers/__init__.py +32 -0
- unrealon_driver/managers/base.py +174 -0
- unrealon_driver/managers/browser.py +98 -0
- unrealon_driver/managers/cache.py +116 -0
- unrealon_driver/managers/http.py +107 -0
- unrealon_driver/managers/logger.py +286 -0
- unrealon_driver/managers/proxy.py +99 -0
- unrealon_driver/managers/registry.py +87 -0
- unrealon_driver/managers/threading.py +54 -0
- unrealon_driver/managers/update.py +107 -0
- unrealon_driver/utils/__init__.py +9 -0
- unrealon_driver/utils/time.py +10 -0
- unrealon-1.1.6.dist-info/METADATA +0 -625
- unrealon-1.1.6.dist-info/RECORD +0 -55
- unrealon-1.1.6.dist-info/entry_points.txt +0 -9
- unrealon_browser/managers/stealth.py +0 -388
- unrealon_driver/README.md +0 -0
- unrealon_driver/exceptions.py +0 -33
- unrealon_driver/html_analyzer/__init__.py +0 -32
- unrealon_driver/html_analyzer/cleaner.py +0 -657
- unrealon_driver/html_analyzer/config.py +0 -64
- unrealon_driver/html_analyzer/manager.py +0 -247
- unrealon_driver/html_analyzer/models.py +0 -115
- unrealon_driver/html_analyzer/websocket_analyzer.py +0 -157
- unrealon_driver/models/__init__.py +0 -31
- unrealon_driver/models/websocket.py +0 -98
- unrealon_driver/parser/__init__.py +0 -36
- unrealon_driver/parser/cli_manager.py +0 -142
- unrealon_driver/parser/daemon_manager.py +0 -403
- unrealon_driver/parser/managers/__init__.py +0 -25
- unrealon_driver/parser/managers/config.py +0 -293
- unrealon_driver/parser/managers/error.py +0 -412
- unrealon_driver/parser/managers/result.py +0 -321
- unrealon_driver/parser/parser_manager.py +0 -458
- unrealon_driver/smart_logging/__init__.py +0 -24
- unrealon_driver/smart_logging/models.py +0 -44
- unrealon_driver/smart_logging/smart_logger.py +0 -406
- unrealon_driver/smart_logging/unified_logger.py +0 -525
- unrealon_driver/websocket/__init__.py +0 -31
- unrealon_driver/websocket/client.py +0 -249
- unrealon_driver/websocket/config.py +0 -188
- 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,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,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
|