unrealon 1.1.6__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 (145) hide show
  1. {unrealon-1.1.6.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.6.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 -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 +160 -0
  24. unrealon_core/config/__init__.py +16 -0
  25. unrealon_core/config/environment.py +98 -0
  26. unrealon_core/config/urls.py +93 -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 +98 -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/config.py +47 -0
  49. unrealon_core/models/connection_stats.py +47 -0
  50. unrealon_core/models/driver.py +30 -0
  51. unrealon_core/models/driver_details.py +98 -0
  52. unrealon_core/models/logging.py +28 -0
  53. unrealon_core/models/task.py +21 -0
  54. unrealon_core/models/typed_responses.py +210 -0
  55. unrealon_core/models/websocket/__init__.py +91 -0
  56. unrealon_core/models/websocket/base.py +49 -0
  57. unrealon_core/models/websocket/config.py +200 -0
  58. unrealon_core/models/websocket/driver.py +215 -0
  59. unrealon_core/models/websocket/errors.py +138 -0
  60. unrealon_core/models/websocket/heartbeat.py +100 -0
  61. unrealon_core/models/websocket/logging.py +261 -0
  62. unrealon_core/models/websocket/proxy.py +496 -0
  63. unrealon_core/models/websocket/tasks.py +275 -0
  64. unrealon_core/models/websocket/utils.py +153 -0
  65. unrealon_core/models/websocket_session.py +144 -0
  66. unrealon_core/monitoring/__init__.py +43 -0
  67. unrealon_core/monitoring/alerts.py +398 -0
  68. unrealon_core/monitoring/dashboard.py +307 -0
  69. unrealon_core/monitoring/health_check.py +354 -0
  70. unrealon_core/monitoring/metrics.py +352 -0
  71. unrealon_core/utils/__init__.py +11 -0
  72. unrealon_core/utils/time.py +61 -0
  73. unrealon_core/version.py +219 -0
  74. unrealon_driver/__init__.py +90 -51
  75. unrealon_driver/core_module/__init__.py +34 -0
  76. unrealon_driver/core_module/base.py +184 -0
  77. unrealon_driver/core_module/config.py +30 -0
  78. unrealon_driver/core_module/event_manager.py +127 -0
  79. unrealon_driver/core_module/protocols.py +98 -0
  80. unrealon_driver/core_module/registry.py +146 -0
  81. unrealon_driver/decorators/__init__.py +15 -0
  82. unrealon_driver/decorators/retry.py +117 -0
  83. unrealon_driver/decorators/schedule.py +137 -0
  84. unrealon_driver/decorators/task.py +61 -0
  85. unrealon_driver/decorators/timing.py +132 -0
  86. unrealon_driver/driver/__init__.py +20 -0
  87. unrealon_driver/driver/communication/__init__.py +10 -0
  88. unrealon_driver/driver/communication/session.py +203 -0
  89. unrealon_driver/driver/communication/websocket_client.py +197 -0
  90. unrealon_driver/driver/core/__init__.py +10 -0
  91. unrealon_driver/driver/core/config.py +85 -0
  92. unrealon_driver/driver/core/driver.py +221 -0
  93. unrealon_driver/driver/factory/__init__.py +9 -0
  94. unrealon_driver/driver/factory/manager_factory.py +130 -0
  95. unrealon_driver/driver/lifecycle/__init__.py +11 -0
  96. unrealon_driver/driver/lifecycle/daemon.py +76 -0
  97. unrealon_driver/driver/lifecycle/initialization.py +97 -0
  98. unrealon_driver/driver/lifecycle/shutdown.py +48 -0
  99. unrealon_driver/driver/monitoring/__init__.py +9 -0
  100. unrealon_driver/driver/monitoring/health.py +63 -0
  101. unrealon_driver/driver/utilities/__init__.py +10 -0
  102. unrealon_driver/driver/utilities/logging.py +51 -0
  103. unrealon_driver/driver/utilities/serialization.py +61 -0
  104. unrealon_driver/managers/__init__.py +32 -0
  105. unrealon_driver/managers/base.py +174 -0
  106. unrealon_driver/managers/browser.py +98 -0
  107. unrealon_driver/managers/cache.py +116 -0
  108. unrealon_driver/managers/http.py +107 -0
  109. unrealon_driver/managers/logger.py +286 -0
  110. unrealon_driver/managers/proxy.py +99 -0
  111. unrealon_driver/managers/registry.py +87 -0
  112. unrealon_driver/managers/threading.py +54 -0
  113. unrealon_driver/managers/update.py +107 -0
  114. unrealon_driver/utils/__init__.py +9 -0
  115. unrealon_driver/utils/time.py +10 -0
  116. unrealon-1.1.6.dist-info/METADATA +0 -625
  117. unrealon-1.1.6.dist-info/RECORD +0 -55
  118. unrealon-1.1.6.dist-info/entry_points.txt +0 -9
  119. unrealon_browser/managers/stealth.py +0 -388
  120. unrealon_driver/README.md +0 -0
  121. unrealon_driver/exceptions.py +0 -33
  122. unrealon_driver/html_analyzer/__init__.py +0 -32
  123. unrealon_driver/html_analyzer/cleaner.py +0 -657
  124. unrealon_driver/html_analyzer/config.py +0 -64
  125. unrealon_driver/html_analyzer/manager.py +0 -247
  126. unrealon_driver/html_analyzer/models.py +0 -115
  127. unrealon_driver/html_analyzer/websocket_analyzer.py +0 -157
  128. unrealon_driver/models/__init__.py +0 -31
  129. unrealon_driver/models/websocket.py +0 -98
  130. unrealon_driver/parser/__init__.py +0 -36
  131. unrealon_driver/parser/cli_manager.py +0 -142
  132. unrealon_driver/parser/daemon_manager.py +0 -403
  133. unrealon_driver/parser/managers/__init__.py +0 -25
  134. unrealon_driver/parser/managers/config.py +0 -293
  135. unrealon_driver/parser/managers/error.py +0 -412
  136. unrealon_driver/parser/managers/result.py +0 -321
  137. unrealon_driver/parser/parser_manager.py +0 -458
  138. unrealon_driver/smart_logging/__init__.py +0 -24
  139. unrealon_driver/smart_logging/models.py +0 -44
  140. unrealon_driver/smart_logging/smart_logger.py +0 -406
  141. unrealon_driver/smart_logging/unified_logger.py +0 -525
  142. unrealon_driver/websocket/__init__.py +0 -31
  143. unrealon_driver/websocket/client.py +0 -249
  144. unrealon_driver/websocket/config.py +0 -188
  145. unrealon_driver/websocket/manager.py +0 -90
@@ -0,0 +1,324 @@
1
+ """
2
+ Error Context System
3
+
4
+ Rich error context for debugging and monitoring.
5
+ Following critical requirements - max 500 lines, functions < 20 lines.
6
+
7
+ Phase 2: Core Systems - Error Handling
8
+ """
9
+
10
+ import traceback
11
+ from datetime import datetime
12
+ from typing import Any, Dict, Optional, List
13
+ from enum import Enum
14
+
15
+ from pydantic import BaseModel, Field, ConfigDict
16
+
17
+ from ..utils.time import utc_now
18
+
19
+
20
+ class ErrorSeverity(str, Enum):
21
+ """Error severity levels."""
22
+ LOW = "low"
23
+ MEDIUM = "medium"
24
+ HIGH = "high"
25
+ CRITICAL = "critical"
26
+
27
+
28
+ class ErrorContext(BaseModel):
29
+ """Rich error context information."""
30
+
31
+ model_config = ConfigDict(
32
+ validate_assignment=True,
33
+ extra="forbid"
34
+ )
35
+
36
+ # Basic error info
37
+ error_id: str = Field(description="Unique error identifier")
38
+ error_type: str = Field(description="Error class name")
39
+ error_message: str = Field(description="Error message")
40
+ severity: ErrorSeverity = Field(description="Error severity level")
41
+
42
+ # Timing
43
+ timestamp: datetime = Field(description="When error occurred")
44
+ duration_ms: Optional[float] = Field(default=None, description="Operation duration before error")
45
+
46
+ # Context
47
+ operation: str = Field(description="Operation that failed")
48
+ component: str = Field(description="Component where error occurred")
49
+ user_id: Optional[str] = Field(default=None, description="Associated user ID")
50
+ request_id: Optional[str] = Field(default=None, description="Request correlation ID")
51
+
52
+ # Technical details
53
+ stack_trace: Optional[str] = Field(default=None, description="Full stack trace")
54
+ function_name: str = Field(description="Function where error occurred")
55
+ file_name: str = Field(description="File where error occurred")
56
+ line_number: int = Field(description="Line number where error occurred")
57
+
58
+ # Additional context
59
+ parameters: Dict[str, Any] = Field(default_factory=dict, description="Function parameters")
60
+ environment: Dict[str, str] = Field(default_factory=dict, description="Environment variables")
61
+ metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
62
+
63
+ # Recovery info
64
+ is_retryable: bool = Field(default=True, description="Whether error is retryable")
65
+ retry_count: int = Field(default=0, description="Number of retries attempted")
66
+ recovery_suggestions: List[str] = Field(default_factory=list, description="Recovery suggestions")
67
+
68
+
69
+ def create_error_context(
70
+ error: Exception,
71
+ operation: str,
72
+ component: str,
73
+ severity: ErrorSeverity = ErrorSeverity.MEDIUM,
74
+ **kwargs
75
+ ) -> ErrorContext:
76
+ """
77
+ Create error context from exception.
78
+
79
+ Args:
80
+ error: Exception that occurred
81
+ operation: Operation that failed
82
+ component: Component where error occurred
83
+ severity: Error severity level
84
+ **kwargs: Additional context data
85
+
86
+ Returns:
87
+ ErrorContext with rich error information
88
+ """
89
+ import uuid
90
+ import inspect
91
+ import os
92
+
93
+ # Get stack trace info
94
+ tb = traceback.extract_tb(error.__traceback__)
95
+ if tb:
96
+ frame = tb[-1] # Last frame (where error occurred)
97
+ file_name = os.path.basename(frame.filename)
98
+ function_name = frame.name
99
+ line_number = frame.lineno
100
+ else:
101
+ file_name = "unknown"
102
+ function_name = "unknown"
103
+ line_number = 0
104
+
105
+ # Generate error ID
106
+ error_id = str(uuid.uuid4())[:8]
107
+
108
+ # Extract parameters from current frame
109
+ parameters = {}
110
+ try:
111
+ frame = inspect.currentframe()
112
+ if frame and frame.f_back:
113
+ parameters = {
114
+ k: str(v) for k, v in frame.f_back.f_locals.items()
115
+ if not k.startswith('_') and not callable(v)
116
+ }
117
+ except Exception:
118
+ pass # Ignore parameter extraction errors
119
+
120
+ # Basic environment info
121
+ environment = {
122
+ 'python_version': f"{os.sys.version_info.major}.{os.sys.version_info.minor}",
123
+ 'platform': os.sys.platform,
124
+ }
125
+
126
+ return ErrorContext(
127
+ error_id=error_id,
128
+ error_type=error.__class__.__name__,
129
+ error_message=str(error),
130
+ severity=severity,
131
+ timestamp=utc_now(),
132
+ operation=operation,
133
+ component=component,
134
+ stack_trace=traceback.format_exc(),
135
+ function_name=function_name,
136
+ file_name=file_name,
137
+ line_number=line_number,
138
+ parameters=parameters,
139
+ environment=environment,
140
+ **kwargs
141
+ )
142
+
143
+
144
+ def format_error_context(context: ErrorContext, include_stack_trace: bool = False) -> str:
145
+ """
146
+ Format error context for logging.
147
+
148
+ Args:
149
+ context: Error context to format
150
+ include_stack_trace: Whether to include full stack trace
151
+
152
+ Returns:
153
+ Formatted error message
154
+ """
155
+ lines = [
156
+ f"🚨 ERROR [{context.error_id}] {context.severity.upper()}",
157
+ f" Type: {context.error_type}",
158
+ f" Message: {context.error_message}",
159
+ f" Operation: {context.operation}",
160
+ f" Component: {context.component}",
161
+ f" Location: {context.file_name}:{context.line_number} in {context.function_name}()",
162
+ f" Time: {context.timestamp.isoformat()}",
163
+ ]
164
+
165
+ if context.duration_ms:
166
+ lines.append(f" Duration: {context.duration_ms:.2f}ms")
167
+
168
+ if context.request_id:
169
+ lines.append(f" Request ID: {context.request_id}")
170
+
171
+ if context.user_id:
172
+ lines.append(f" User ID: {context.user_id}")
173
+
174
+ if context.retry_count > 0:
175
+ lines.append(f" Retries: {context.retry_count}")
176
+
177
+ if context.parameters:
178
+ lines.append(" Parameters:")
179
+ for key, value in context.parameters.items():
180
+ lines.append(f" {key}: {value}")
181
+
182
+ if context.recovery_suggestions:
183
+ lines.append(" Recovery suggestions:")
184
+ for suggestion in context.recovery_suggestions:
185
+ lines.append(f" • {suggestion}")
186
+
187
+ if include_stack_trace and context.stack_trace:
188
+ lines.append(" Stack trace:")
189
+ for line in context.stack_trace.split('\n'):
190
+ if line.strip():
191
+ lines.append(f" {line}")
192
+
193
+ return '\n'.join(lines)
194
+
195
+
196
+ def determine_severity(error: Exception) -> ErrorSeverity:
197
+ """
198
+ Determine error severity based on exception type.
199
+
200
+ Args:
201
+ error: Exception to analyze
202
+
203
+ Returns:
204
+ Appropriate severity level
205
+ """
206
+ error_name = error.__class__.__name__
207
+
208
+ # Critical errors
209
+ critical_errors = [
210
+ 'SystemError', 'MemoryError', 'KeyboardInterrupt',
211
+ 'SystemExit', 'GeneratorExit'
212
+ ]
213
+
214
+ # High severity errors
215
+ high_errors = [
216
+ 'ConnectionError', 'DatabaseError', 'AuthenticationError',
217
+ 'PermissionError', 'SecurityError'
218
+ ]
219
+
220
+ # Medium severity errors
221
+ medium_errors = [
222
+ 'ValidationError', 'ValueError', 'TypeError',
223
+ 'AttributeError', 'KeyError', 'IndexError'
224
+ ]
225
+
226
+ if error_name in critical_errors:
227
+ return ErrorSeverity.CRITICAL
228
+ elif error_name in high_errors:
229
+ return ErrorSeverity.HIGH
230
+ elif error_name in medium_errors:
231
+ return ErrorSeverity.MEDIUM
232
+ else:
233
+ return ErrorSeverity.LOW
234
+
235
+
236
+ def is_retryable_error(error: Exception) -> bool:
237
+ """
238
+ Determine if error is retryable.
239
+
240
+ Args:
241
+ error: Exception to analyze
242
+
243
+ Returns:
244
+ True if error should be retried
245
+ """
246
+ error_name = error.__class__.__name__
247
+
248
+ # Non-retryable errors
249
+ non_retryable = [
250
+ 'ValidationError', 'ValueError', 'TypeError',
251
+ 'AttributeError', 'KeyError', 'IndexError',
252
+ 'AuthenticationError', 'PermissionError',
253
+ 'NotImplementedError', 'AssertionError'
254
+ ]
255
+
256
+ # Retryable errors
257
+ retryable = [
258
+ 'ConnectionError', 'TimeoutError', 'HTTPError',
259
+ 'NetworkError', 'ServiceUnavailableError',
260
+ 'TemporaryError', 'RateLimitError'
261
+ ]
262
+
263
+ if error_name in non_retryable:
264
+ return False
265
+ elif error_name in retryable:
266
+ return True
267
+ else:
268
+ # Default to retryable for unknown errors
269
+ return True
270
+
271
+
272
+ def get_recovery_suggestions(error: Exception, operation: str) -> List[str]:
273
+ """
274
+ Get recovery suggestions for error.
275
+
276
+ Args:
277
+ error: Exception that occurred
278
+ operation: Operation that failed
279
+
280
+ Returns:
281
+ List of recovery suggestions
282
+ """
283
+ error_name = error.__class__.__name__
284
+ suggestions = []
285
+
286
+ if error_name == 'ConnectionError':
287
+ suggestions.extend([
288
+ "Check network connectivity",
289
+ "Verify service endpoint is accessible",
290
+ "Check firewall settings"
291
+ ])
292
+
293
+ elif error_name == 'TimeoutError':
294
+ suggestions.extend([
295
+ "Increase timeout value",
296
+ "Check service performance",
297
+ "Retry with exponential backoff"
298
+ ])
299
+
300
+ elif error_name == 'ValidationError':
301
+ suggestions.extend([
302
+ "Check input data format",
303
+ "Verify required fields are present",
304
+ "Review data validation rules"
305
+ ])
306
+
307
+ elif error_name == 'AuthenticationError':
308
+ suggestions.extend([
309
+ "Check authentication credentials",
310
+ "Verify token is not expired",
311
+ "Refresh authentication token"
312
+ ])
313
+
314
+ elif error_name == 'PermissionError':
315
+ suggestions.extend([
316
+ "Check user permissions",
317
+ "Verify access rights for operation",
318
+ "Contact administrator for access"
319
+ ])
320
+
321
+ else:
322
+ suggestions.append(f"Review {operation} implementation")
323
+
324
+ return suggestions
@@ -0,0 +1,371 @@
1
+ """
2
+ Recovery System
3
+
4
+ Automatic error recovery and healing mechanisms.
5
+ Following critical requirements - max 500 lines, functions < 20 lines.
6
+
7
+ Phase 2: Core Systems - Error Handling
8
+ """
9
+
10
+ import asyncio
11
+ import logging
12
+ from datetime import datetime, timedelta
13
+ from typing import Any, Callable, Dict, List, Optional
14
+ from enum import Enum
15
+
16
+ from pydantic import BaseModel, Field, ConfigDict
17
+
18
+ from .error_context import ErrorContext, ErrorSeverity
19
+ from ..utils.time import utc_now
20
+
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class RecoveryStrategy(str, Enum):
26
+ """Recovery strategy types."""
27
+ RETRY = "retry"
28
+ FALLBACK = "fallback"
29
+ CIRCUIT_BREAK = "circuit_break"
30
+ GRACEFUL_DEGRADE = "graceful_degrade"
31
+ RESTART = "restart"
32
+
33
+
34
+ class RecoveryAction(BaseModel):
35
+ """Recovery action configuration."""
36
+
37
+ model_config = ConfigDict(
38
+ validate_assignment=True,
39
+ extra="forbid"
40
+ )
41
+
42
+ strategy: RecoveryStrategy = Field(description="Recovery strategy to use")
43
+ max_attempts: int = Field(default=3, ge=1, le=10, description="Maximum recovery attempts")
44
+ delay_seconds: float = Field(default=1.0, ge=0.1, le=60.0, description="Delay between attempts")
45
+ timeout_seconds: float = Field(default=30.0, ge=1.0, le=300.0, description="Recovery timeout")
46
+ fallback_value: Optional[Any] = Field(default=None, description="Fallback value to return")
47
+ enabled: bool = Field(default=True, description="Whether recovery is enabled")
48
+
49
+
50
+ class RecoveryResult(BaseModel):
51
+ """Result of recovery attempt."""
52
+
53
+ model_config = ConfigDict(
54
+ validate_assignment=True,
55
+ extra="forbid"
56
+ )
57
+
58
+ success: bool = Field(description="Whether recovery succeeded")
59
+ strategy_used: RecoveryStrategy = Field(description="Strategy that was used")
60
+ attempts_made: int = Field(description="Number of recovery attempts")
61
+ duration_seconds: float = Field(description="Total recovery duration")
62
+ result: Optional[Any] = Field(default=None, description="Recovery result")
63
+ error_message: Optional[str] = Field(default=None, description="Error if recovery failed")
64
+
65
+
66
+ class AutoRecovery:
67
+ """
68
+ Automatic recovery system.
69
+
70
+ Provides intelligent error recovery based on error context
71
+ and configured recovery strategies.
72
+ """
73
+
74
+ def __init__(self):
75
+ """Initialize auto recovery system."""
76
+ self._recovery_actions: Dict[str, RecoveryAction] = {}
77
+ self._recovery_stats: Dict[str, Dict[str, int]] = {}
78
+ self.logger = logging.getLogger("auto_recovery")
79
+
80
+ def register_recovery_action(
81
+ self,
82
+ error_type: str,
83
+ action: RecoveryAction
84
+ ) -> None:
85
+ """
86
+ Register recovery action for error type.
87
+
88
+ Args:
89
+ error_type: Exception class name
90
+ action: Recovery action configuration
91
+ """
92
+ self._recovery_actions[error_type] = action
93
+ self._recovery_stats[error_type] = {
94
+ 'attempts': 0,
95
+ 'successes': 0,
96
+ 'failures': 0
97
+ }
98
+
99
+ self.logger.info(f"Registered recovery action for {error_type}: {action.strategy}")
100
+
101
+ async def attempt_recovery(
102
+ self,
103
+ error_context: ErrorContext,
104
+ operation_func: Callable[..., Any],
105
+ *args,
106
+ **kwargs
107
+ ) -> RecoveryResult:
108
+ """
109
+ Attempt to recover from error.
110
+
111
+ Args:
112
+ error_context: Context of the error
113
+ operation_func: Function to retry
114
+ *args, **kwargs: Function arguments
115
+
116
+ Returns:
117
+ RecoveryResult with outcome
118
+ """
119
+ start_time = utc_now()
120
+ error_type = error_context.error_type
121
+
122
+ # Get recovery action for this error type
123
+ action = self._recovery_actions.get(error_type)
124
+ if not action or not action.enabled:
125
+ self.logger.debug(f"No recovery action configured for {error_type}")
126
+ return RecoveryResult(
127
+ success=False,
128
+ strategy_used=RecoveryStrategy.RETRY,
129
+ attempts_made=0,
130
+ duration_seconds=0.0,
131
+ error_message="No recovery action configured"
132
+ )
133
+
134
+ # Update stats
135
+ self._recovery_stats[error_type]['attempts'] += 1
136
+
137
+ # Attempt recovery based on strategy
138
+ try:
139
+ if action.strategy == RecoveryStrategy.RETRY:
140
+ result = await self._retry_recovery(action, operation_func, *args, **kwargs)
141
+ elif action.strategy == RecoveryStrategy.FALLBACK:
142
+ result = await self._fallback_recovery(action, operation_func, *args, **kwargs)
143
+ elif action.strategy == RecoveryStrategy.GRACEFUL_DEGRADE:
144
+ result = await self._graceful_degrade_recovery(action, operation_func, *args, **kwargs)
145
+ else:
146
+ result = RecoveryResult(
147
+ success=False,
148
+ strategy_used=action.strategy,
149
+ attempts_made=0,
150
+ duration_seconds=0.0,
151
+ error_message=f"Recovery strategy {action.strategy} not implemented"
152
+ )
153
+
154
+ # Update stats
155
+ if result.success:
156
+ self._recovery_stats[error_type]['successes'] += 1
157
+ else:
158
+ self._recovery_stats[error_type]['failures'] += 1
159
+
160
+ # Calculate duration
161
+ duration = (utc_now() - start_time).total_seconds()
162
+ result.duration_seconds = duration
163
+
164
+ return result
165
+
166
+ except Exception as e:
167
+ self.logger.error(f"Recovery attempt failed: {e}")
168
+ self._recovery_stats[error_type]['failures'] += 1
169
+
170
+ duration = (utc_now() - start_time).total_seconds()
171
+ return RecoveryResult(
172
+ success=False,
173
+ strategy_used=action.strategy,
174
+ attempts_made=1,
175
+ duration_seconds=duration,
176
+ error_message=str(e)
177
+ )
178
+
179
+ async def _retry_recovery(
180
+ self,
181
+ action: RecoveryAction,
182
+ operation_func: Callable[..., Any],
183
+ *args,
184
+ **kwargs
185
+ ) -> RecoveryResult:
186
+ """Attempt recovery using retry strategy."""
187
+ for attempt in range(action.max_attempts):
188
+ try:
189
+ self.logger.debug(f"Recovery retry attempt {attempt + 1}/{action.max_attempts}")
190
+
191
+ result = await asyncio.wait_for(
192
+ operation_func(*args, **kwargs),
193
+ timeout=action.timeout_seconds
194
+ )
195
+
196
+ self.logger.info(f"Recovery succeeded on attempt {attempt + 1}")
197
+ return RecoveryResult(
198
+ success=True,
199
+ strategy_used=RecoveryStrategy.RETRY,
200
+ attempts_made=attempt + 1,
201
+ duration_seconds=0.0, # Will be set by caller
202
+ result=result
203
+ )
204
+
205
+ except Exception as e:
206
+ if attempt < action.max_attempts - 1:
207
+ self.logger.warning(f"Recovery attempt {attempt + 1} failed: {e}")
208
+ await asyncio.sleep(action.delay_seconds)
209
+ else:
210
+ self.logger.error(f"All recovery attempts failed: {e}")
211
+
212
+ return RecoveryResult(
213
+ success=False,
214
+ strategy_used=RecoveryStrategy.RETRY,
215
+ attempts_made=action.max_attempts,
216
+ duration_seconds=0.0,
217
+ error_message="All retry attempts failed"
218
+ )
219
+
220
+ async def _fallback_recovery(
221
+ self,
222
+ action: RecoveryAction,
223
+ operation_func: Callable[..., Any],
224
+ *args,
225
+ **kwargs
226
+ ) -> RecoveryResult:
227
+ """Attempt recovery using fallback strategy."""
228
+ self.logger.info("Using fallback recovery strategy")
229
+
230
+ return RecoveryResult(
231
+ success=True,
232
+ strategy_used=RecoveryStrategy.FALLBACK,
233
+ attempts_made=1,
234
+ duration_seconds=0.0,
235
+ result=action.fallback_value
236
+ )
237
+
238
+ async def _graceful_degrade_recovery(
239
+ self,
240
+ action: RecoveryAction,
241
+ operation_func: Callable[..., Any],
242
+ *args,
243
+ **kwargs
244
+ ) -> RecoveryResult:
245
+ """Attempt recovery using graceful degradation."""
246
+ self.logger.info("Using graceful degradation recovery strategy")
247
+
248
+ # Return a simplified/degraded version of the expected result
249
+ degraded_result = {
250
+ "status": "degraded",
251
+ "message": "Service operating in degraded mode",
252
+ "data": action.fallback_value
253
+ }
254
+
255
+ return RecoveryResult(
256
+ success=True,
257
+ strategy_used=RecoveryStrategy.GRACEFUL_DEGRADE,
258
+ attempts_made=1,
259
+ duration_seconds=0.0,
260
+ result=degraded_result
261
+ )
262
+
263
+ def get_recovery_stats(self) -> Dict[str, Dict[str, Any]]:
264
+ """Get recovery statistics."""
265
+ stats = {}
266
+
267
+ for error_type, counts in self._recovery_stats.items():
268
+ total_attempts = counts['attempts']
269
+ success_rate = 0.0
270
+
271
+ if total_attempts > 0:
272
+ success_rate = (counts['successes'] / total_attempts) * 100.0
273
+
274
+ stats[error_type] = {
275
+ 'total_attempts': total_attempts,
276
+ 'successes': counts['successes'],
277
+ 'failures': counts['failures'],
278
+ 'success_rate_percent': round(success_rate, 2),
279
+ 'recovery_action': self._recovery_actions.get(error_type, {})
280
+ }
281
+
282
+ return stats
283
+
284
+ def clear_stats(self) -> None:
285
+ """Clear recovery statistics."""
286
+ for error_type in self._recovery_stats:
287
+ self._recovery_stats[error_type] = {
288
+ 'attempts': 0,
289
+ 'successes': 0,
290
+ 'failures': 0
291
+ }
292
+
293
+ self.logger.info("Recovery statistics cleared")
294
+
295
+
296
+ # Global auto recovery instance
297
+ _auto_recovery = AutoRecovery()
298
+
299
+
300
+ def get_auto_recovery() -> AutoRecovery:
301
+ """Get global auto recovery instance."""
302
+ return _auto_recovery
303
+
304
+
305
+ def recovery_handler(
306
+ error_type: str,
307
+ strategy: RecoveryStrategy = RecoveryStrategy.RETRY,
308
+ max_attempts: int = 3,
309
+ delay_seconds: float = 1.0,
310
+ fallback_value: Optional[Any] = None
311
+ ):
312
+ """
313
+ Decorator to add automatic recovery to functions.
314
+
315
+ Args:
316
+ error_type: Exception class name to handle
317
+ strategy: Recovery strategy to use
318
+ max_attempts: Maximum recovery attempts
319
+ delay_seconds: Delay between attempts
320
+ fallback_value: Fallback value for fallback strategy
321
+ """
322
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
323
+ # Register recovery action
324
+ action = RecoveryAction(
325
+ strategy=strategy,
326
+ max_attempts=max_attempts,
327
+ delay_seconds=delay_seconds,
328
+ fallback_value=fallback_value
329
+ )
330
+
331
+ _auto_recovery.register_recovery_action(error_type, action)
332
+
333
+ async def async_wrapper(*args, **kwargs):
334
+ try:
335
+ return await func(*args, **kwargs)
336
+ except Exception as e:
337
+ from .error_context import create_error_context
338
+
339
+ # Create error context
340
+ error_context = create_error_context(
341
+ error=e,
342
+ operation=func.__name__,
343
+ component=func.__module__
344
+ )
345
+
346
+ # Attempt recovery
347
+ recovery_result = await _auto_recovery.attempt_recovery(
348
+ error_context, func, *args, **kwargs
349
+ )
350
+
351
+ if recovery_result.success:
352
+ return recovery_result.result
353
+ else:
354
+ # Re-raise original exception if recovery failed
355
+ raise
356
+
357
+ def sync_wrapper(*args, **kwargs):
358
+ # For sync functions, convert to async temporarily
359
+ import asyncio
360
+ try:
361
+ loop = asyncio.get_event_loop()
362
+ return loop.run_until_complete(async_wrapper(*args, **kwargs))
363
+ except RuntimeError:
364
+ return asyncio.run(async_wrapper(*args, **kwargs))
365
+
366
+ if asyncio.iscoroutinefunction(func):
367
+ return async_wrapper
368
+ else:
369
+ return sync_wrapper
370
+
371
+ return decorator