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,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
@@ -0,0 +1,116 @@
1
+ """
2
+ Clean cache manager.
3
+ """
4
+
5
+ import asyncio
6
+ from typing import Any, Optional, Dict
7
+ from datetime import datetime, timedelta
8
+ from pydantic import Field
9
+
10
+ from ..utils.time import utc_now
11
+ from .base import BaseManager, ManagerConfig
12
+
13
+
14
+ class CacheManagerConfig(ManagerConfig):
15
+ """Cache manager configuration."""
16
+ default_ttl: int = Field(default=3600, description="Default TTL seconds")
17
+ max_size: int = Field(default=1000, description="Max cache entries")
18
+
19
+
20
+ class CacheEntry:
21
+ """Cache entry with TTL."""
22
+
23
+ def __init__(self, value: Any, ttl: int):
24
+ self.value = value
25
+ self.expires_at = utc_now() + timedelta(seconds=ttl)
26
+
27
+ def is_expired(self) -> bool:
28
+ """Check if entry is expired."""
29
+ return utc_now() > self.expires_at
30
+
31
+
32
+ class CacheManager(BaseManager):
33
+ """Simple in-memory cache manager."""
34
+
35
+ def __init__(self, config: CacheManagerConfig):
36
+ super().__init__(config, "cache")
37
+ self.config: CacheManagerConfig = config
38
+ self._cache: Dict[str, CacheEntry] = {}
39
+ self._cleanup_task: Optional[asyncio.Task] = None
40
+
41
+ async def _initialize(self) -> bool:
42
+ """Initialize cache."""
43
+ # Start cleanup task
44
+ self._cleanup_task = asyncio.create_task(self._cleanup_expired())
45
+ return True
46
+
47
+ async def _shutdown(self):
48
+ """Shutdown cache."""
49
+ if self._cleanup_task:
50
+ self._cleanup_task.cancel()
51
+ try:
52
+ await self._cleanup_task
53
+ except asyncio.CancelledError:
54
+ pass
55
+
56
+ self._cache.clear()
57
+
58
+ async def _cleanup_expired(self):
59
+ """Background task to clean expired entries."""
60
+ while True:
61
+ try:
62
+ await asyncio.sleep(60) # Cleanup every minute
63
+
64
+ expired_keys = []
65
+ for key, entry in self._cache.items():
66
+ if entry.is_expired():
67
+ expired_keys.append(key)
68
+
69
+ for key in expired_keys:
70
+ del self._cache[key]
71
+
72
+ except asyncio.CancelledError:
73
+ break
74
+ except Exception as e:
75
+ self.logger.error(f"Cache cleanup error: {e}")
76
+
77
+ def get(self, key: str) -> Optional[Any]:
78
+ """Get value from cache."""
79
+ entry = self._cache.get(key)
80
+ if not entry:
81
+ return None
82
+
83
+ if entry.is_expired():
84
+ del self._cache[key]
85
+ return None
86
+
87
+ return entry.value
88
+
89
+ def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
90
+ """Set value in cache."""
91
+ if len(self._cache) >= self.config.max_size:
92
+ # Remove oldest entry
93
+ oldest_key = next(iter(self._cache))
94
+ del self._cache[oldest_key]
95
+
96
+ ttl = ttl or self.config.default_ttl
97
+ self._cache[key] = CacheEntry(value, ttl)
98
+
99
+ def delete(self, key: str) -> bool:
100
+ """Delete key from cache."""
101
+ if key in self._cache:
102
+ del self._cache[key]
103
+ return True
104
+ return False
105
+
106
+ def clear(self) -> None:
107
+ """Clear all cache entries."""
108
+ self._cache.clear()
109
+
110
+ async def _health_check(self) -> Dict[str, Any]:
111
+ """Cache health check."""
112
+ return {
113
+ "status": "ok",
114
+ "entries": len(self._cache),
115
+ "max_size": self.config.max_size
116
+ }
@@ -0,0 +1,107 @@
1
+ """
2
+ Clean HTTP manager for requests.
3
+ """
4
+
5
+ import asyncio
6
+ import aiohttp
7
+ from typing import Dict, Any, Optional
8
+ from pydantic import Field
9
+
10
+ from .base import BaseManager, ManagerConfig
11
+
12
+
13
+ class HttpManagerConfig(ManagerConfig):
14
+ """HTTP manager configuration."""
15
+ user_agent: str = Field(default="UnrealOn-Driver/1.0", description="User agent string")
16
+ max_connections: int = Field(default=100, description="Max concurrent connections")
17
+ connector_limit: int = Field(default=30, description="Connector limit per host")
18
+
19
+
20
+ class HttpManager(BaseManager):
21
+ """Clean HTTP manager with aiohttp."""
22
+
23
+ def __init__(self, config: HttpManagerConfig):
24
+ super().__init__(config, "http")
25
+ self.config: HttpManagerConfig = config
26
+ self.session: Optional[aiohttp.ClientSession] = None
27
+
28
+ async def _initialize(self) -> bool:
29
+ """Initialize HTTP session."""
30
+ try:
31
+ # Create connector
32
+ connector = aiohttp.TCPConnector(
33
+ limit=self.config.max_connections,
34
+ limit_per_host=self.config.connector_limit,
35
+ ttl_dns_cache=300,
36
+ use_dns_cache=True
37
+ )
38
+
39
+ # Create session
40
+ timeout = aiohttp.ClientTimeout(total=self.config.timeout)
41
+ headers = {"User-Agent": self.config.user_agent}
42
+
43
+ self.session = aiohttp.ClientSession(
44
+ connector=connector,
45
+ timeout=timeout,
46
+ headers=headers
47
+ )
48
+
49
+ return True
50
+
51
+ except Exception as e:
52
+ self.logger.error(f"HTTP manager initialization failed: {e}")
53
+ return False
54
+
55
+ async def _shutdown(self):
56
+ """Shutdown HTTP session."""
57
+ if self.session:
58
+ await self.session.close()
59
+ self.session = None
60
+
61
+ async def get(self, url: str, **kwargs) -> aiohttp.ClientResponse:
62
+ """Make GET request."""
63
+ if not self.session:
64
+ raise RuntimeError("HTTP manager not initialized")
65
+
66
+ start_time = asyncio.get_event_loop().time()
67
+ success = False
68
+
69
+ try:
70
+ response = await self.session.get(url, **kwargs)
71
+ success = True
72
+ return response
73
+ finally:
74
+ duration = asyncio.get_event_loop().time() - start_time
75
+ self.stats.record_operation(success, duration)
76
+
77
+ async def post(self, url: str, **kwargs) -> aiohttp.ClientResponse:
78
+ """Make POST request."""
79
+ if not self.session:
80
+ raise RuntimeError("HTTP manager not initialized")
81
+
82
+ start_time = asyncio.get_event_loop().time()
83
+ success = False
84
+
85
+ try:
86
+ response = await self.session.post(url, **kwargs)
87
+ success = True
88
+ return response
89
+ finally:
90
+ duration = asyncio.get_event_loop().time() - start_time
91
+ self.stats.record_operation(success, duration)
92
+
93
+ async def request(self, method: str, url: str, **kwargs) -> aiohttp.ClientResponse:
94
+ """Make generic request."""
95
+ if not self.session:
96
+ raise RuntimeError("HTTP manager not initialized")
97
+
98
+ start_time = asyncio.get_event_loop().time()
99
+ success = False
100
+
101
+ try:
102
+ response = await self.session.request(method, url, **kwargs)
103
+ success = True
104
+ return response
105
+ finally:
106
+ duration = asyncio.get_event_loop().time() - start_time
107
+ self.stats.record_operation(success, duration)