unrealon 1.0.9__py3-none-any.whl → 1.1.1__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/__init__.py +23 -21
- unrealon-1.1.1.dist-info/METADATA +722 -0
- unrealon-1.1.1.dist-info/RECORD +82 -0
- {unrealon-1.0.9.dist-info → unrealon-1.1.1.dist-info}/WHEEL +1 -1
- unrealon-1.1.1.dist-info/entry_points.txt +9 -0
- {unrealon-1.0.9.dist-info → unrealon-1.1.1.dist-info/licenses}/LICENSE +1 -1
- unrealon_bridge/__init__.py +114 -0
- unrealon_bridge/cli.py +316 -0
- unrealon_bridge/client/__init__.py +93 -0
- unrealon_bridge/client/base.py +78 -0
- unrealon_bridge/client/commands.py +89 -0
- unrealon_bridge/client/connection.py +90 -0
- unrealon_bridge/client/events.py +65 -0
- unrealon_bridge/client/health.py +38 -0
- unrealon_bridge/client/html_parser.py +146 -0
- unrealon_bridge/client/logging.py +139 -0
- unrealon_bridge/client/proxy.py +70 -0
- unrealon_bridge/client/scheduler.py +450 -0
- unrealon_bridge/client/session.py +70 -0
- unrealon_bridge/configs/__init__.py +14 -0
- unrealon_bridge/configs/bridge_config.py +212 -0
- unrealon_bridge/configs/bridge_config.yaml +39 -0
- unrealon_bridge/models/__init__.py +138 -0
- unrealon_bridge/models/base.py +28 -0
- unrealon_bridge/models/command.py +41 -0
- unrealon_bridge/models/events.py +40 -0
- unrealon_bridge/models/html_parser.py +79 -0
- unrealon_bridge/models/logging.py +55 -0
- unrealon_bridge/models/parser.py +63 -0
- unrealon_bridge/models/proxy.py +41 -0
- unrealon_bridge/models/requests.py +95 -0
- unrealon_bridge/models/responses.py +88 -0
- unrealon_bridge/models/scheduler.py +592 -0
- unrealon_bridge/models/session.py +28 -0
- unrealon_bridge/server/__init__.py +91 -0
- unrealon_bridge/server/base.py +171 -0
- unrealon_bridge/server/handlers/__init__.py +23 -0
- unrealon_bridge/server/handlers/command.py +110 -0
- unrealon_bridge/server/handlers/html_parser.py +139 -0
- unrealon_bridge/server/handlers/logging.py +95 -0
- unrealon_bridge/server/handlers/parser.py +95 -0
- unrealon_bridge/server/handlers/proxy.py +75 -0
- unrealon_bridge/server/handlers/scheduler.py +545 -0
- unrealon_bridge/server/handlers/session.py +66 -0
- unrealon_browser/__init__.py +61 -18
- unrealon_browser/{src/cli → cli}/browser_cli.py +6 -13
- unrealon_browser/{src/cli → cli}/cookies_cli.py +5 -1
- unrealon_browser/{src/core → core}/browser_manager.py +2 -2
- unrealon_browser/{src/managers → managers}/captcha.py +1 -1
- unrealon_browser/{src/managers → managers}/cookies.py +1 -1
- unrealon_browser/managers/logger_bridge.py +231 -0
- unrealon_browser/{src/managers → managers}/profile.py +1 -1
- unrealon_driver/__init__.py +73 -19
- unrealon_driver/browser/__init__.py +8 -0
- unrealon_driver/browser/config.py +74 -0
- unrealon_driver/browser/manager.py +416 -0
- unrealon_driver/exceptions.py +28 -0
- unrealon_driver/parser/__init__.py +55 -0
- unrealon_driver/parser/cli_manager.py +141 -0
- unrealon_driver/parser/daemon_manager.py +227 -0
- unrealon_driver/parser/managers/__init__.py +46 -0
- unrealon_driver/parser/managers/browser.py +51 -0
- unrealon_driver/parser/managers/config.py +281 -0
- unrealon_driver/parser/managers/error.py +412 -0
- unrealon_driver/parser/managers/html.py +732 -0
- unrealon_driver/parser/managers/logging.py +609 -0
- unrealon_driver/parser/managers/result.py +321 -0
- unrealon_driver/parser/parser_manager.py +628 -0
- unrealon/sdk_config.py +0 -88
- unrealon-1.0.9.dist-info/METADATA +0 -810
- unrealon-1.0.9.dist-info/RECORD +0 -246
- unrealon_browser/pyproject.toml +0 -182
- unrealon_browser/src/__init__.py +0 -62
- unrealon_browser/src/managers/logger_bridge.py +0 -395
- unrealon_driver/README.md +0 -204
- unrealon_driver/pyproject.toml +0 -187
- unrealon_driver/src/__init__.py +0 -90
- unrealon_driver/src/cli/__init__.py +0 -10
- unrealon_driver/src/cli/main.py +0 -66
- unrealon_driver/src/cli/simple.py +0 -510
- unrealon_driver/src/config/__init__.py +0 -11
- unrealon_driver/src/config/auto_config.py +0 -478
- unrealon_driver/src/core/__init__.py +0 -18
- unrealon_driver/src/core/exceptions.py +0 -289
- unrealon_driver/src/core/parser.py +0 -638
- unrealon_driver/src/dto/__init__.py +0 -66
- unrealon_driver/src/dto/cli.py +0 -119
- unrealon_driver/src/dto/config.py +0 -18
- unrealon_driver/src/dto/events.py +0 -237
- unrealon_driver/src/dto/execution.py +0 -313
- unrealon_driver/src/dto/services.py +0 -311
- unrealon_driver/src/execution/__init__.py +0 -23
- unrealon_driver/src/execution/daemon_mode.py +0 -317
- unrealon_driver/src/execution/interactive_mode.py +0 -88
- unrealon_driver/src/execution/modes.py +0 -45
- unrealon_driver/src/execution/scheduled_mode.py +0 -209
- unrealon_driver/src/execution/test_mode.py +0 -250
- unrealon_driver/src/logging/__init__.py +0 -24
- unrealon_driver/src/logging/driver_logger.py +0 -512
- unrealon_driver/src/services/__init__.py +0 -24
- unrealon_driver/src/services/browser_service.py +0 -726
- unrealon_driver/src/services/llm/__init__.py +0 -15
- unrealon_driver/src/services/llm/browser_llm_service.py +0 -363
- unrealon_driver/src/services/llm/llm.py +0 -195
- unrealon_driver/src/services/logger_service.py +0 -232
- unrealon_driver/src/services/metrics_service.py +0 -185
- unrealon_driver/src/services/scheduler_service.py +0 -489
- unrealon_driver/src/services/websocket_service.py +0 -362
- unrealon_driver/src/utils/__init__.py +0 -16
- unrealon_driver/src/utils/service_factory.py +0 -317
- unrealon_driver/src/utils/time_formatter.py +0 -338
- unrealon_llm/README.md +0 -44
- unrealon_llm/__init__.py +0 -26
- unrealon_llm/pyproject.toml +0 -154
- unrealon_llm/src/__init__.py +0 -228
- unrealon_llm/src/cli/__init__.py +0 -0
- unrealon_llm/src/core/__init__.py +0 -11
- unrealon_llm/src/core/smart_client.py +0 -438
- unrealon_llm/src/dto/__init__.py +0 -155
- unrealon_llm/src/dto/models/__init__.py +0 -0
- unrealon_llm/src/dto/models/config.py +0 -343
- unrealon_llm/src/dto/models/core.py +0 -328
- unrealon_llm/src/dto/models/enums.py +0 -123
- unrealon_llm/src/dto/models/html_analysis.py +0 -345
- unrealon_llm/src/dto/models/statistics.py +0 -473
- unrealon_llm/src/dto/models/translation.py +0 -383
- unrealon_llm/src/dto/models/type_conversion.py +0 -462
- unrealon_llm/src/dto/schemas/__init__.py +0 -0
- unrealon_llm/src/exceptions.py +0 -392
- unrealon_llm/src/llm_config/__init__.py +0 -20
- unrealon_llm/src/llm_config/logging_config.py +0 -178
- unrealon_llm/src/llm_logging/__init__.py +0 -42
- unrealon_llm/src/llm_logging/llm_events.py +0 -107
- unrealon_llm/src/llm_logging/llm_logger.py +0 -466
- unrealon_llm/src/managers/__init__.py +0 -15
- unrealon_llm/src/managers/cache_manager.py +0 -67
- unrealon_llm/src/managers/cost_manager.py +0 -107
- unrealon_llm/src/managers/request_manager.py +0 -298
- unrealon_llm/src/modules/__init__.py +0 -0
- unrealon_llm/src/modules/html_processor/__init__.py +0 -25
- unrealon_llm/src/modules/html_processor/base_processor.py +0 -415
- unrealon_llm/src/modules/html_processor/details_processor.py +0 -85
- unrealon_llm/src/modules/html_processor/listing_processor.py +0 -91
- unrealon_llm/src/modules/html_processor/models/__init__.py +0 -20
- unrealon_llm/src/modules/html_processor/models/processing_models.py +0 -40
- unrealon_llm/src/modules/html_processor/models/universal_model.py +0 -56
- unrealon_llm/src/modules/html_processor/processor.py +0 -102
- unrealon_llm/src/modules/llm/__init__.py +0 -0
- unrealon_llm/src/modules/translator/__init__.py +0 -0
- unrealon_llm/src/provider.py +0 -116
- unrealon_llm/src/utils/__init__.py +0 -95
- unrealon_llm/src/utils/common.py +0 -64
- unrealon_llm/src/utils/data_extractor.py +0 -188
- unrealon_llm/src/utils/html_cleaner.py +0 -767
- unrealon_llm/src/utils/language_detector.py +0 -308
- unrealon_llm/src/utils/models_cache.py +0 -592
- unrealon_llm/src/utils/smart_counter.py +0 -229
- unrealon_llm/src/utils/token_counter.py +0 -189
- unrealon_sdk/README.md +0 -25
- unrealon_sdk/__init__.py +0 -30
- unrealon_sdk/pyproject.toml +0 -231
- unrealon_sdk/src/__init__.py +0 -150
- unrealon_sdk/src/cli/__init__.py +0 -12
- unrealon_sdk/src/cli/commands/__init__.py +0 -22
- unrealon_sdk/src/cli/commands/benchmark.py +0 -42
- unrealon_sdk/src/cli/commands/diagnostics.py +0 -573
- unrealon_sdk/src/cli/commands/health.py +0 -46
- unrealon_sdk/src/cli/commands/integration.py +0 -498
- unrealon_sdk/src/cli/commands/reports.py +0 -43
- unrealon_sdk/src/cli/commands/security.py +0 -36
- unrealon_sdk/src/cli/commands/server.py +0 -483
- unrealon_sdk/src/cli/commands/servers.py +0 -56
- unrealon_sdk/src/cli/commands/tests.py +0 -55
- unrealon_sdk/src/cli/main.py +0 -126
- unrealon_sdk/src/cli/utils/reporter.py +0 -519
- unrealon_sdk/src/clients/openapi.yaml +0 -3347
- unrealon_sdk/src/clients/python_http/__init__.py +0 -3
- unrealon_sdk/src/clients/python_http/api_config.py +0 -228
- unrealon_sdk/src/clients/python_http/models/BaseModel.py +0 -12
- unrealon_sdk/src/clients/python_http/models/BroadcastDeliveryStats.py +0 -33
- unrealon_sdk/src/clients/python_http/models/BroadcastMessage.py +0 -17
- unrealon_sdk/src/clients/python_http/models/BroadcastMessageRequest.py +0 -35
- unrealon_sdk/src/clients/python_http/models/BroadcastPriority.py +0 -10
- unrealon_sdk/src/clients/python_http/models/BroadcastResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/BroadcastResultResponse.py +0 -33
- unrealon_sdk/src/clients/python_http/models/BroadcastTarget.py +0 -11
- unrealon_sdk/src/clients/python_http/models/ConnectionStats.py +0 -27
- unrealon_sdk/src/clients/python_http/models/ConnectionsResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/DeveloperMessageResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ErrorResponse.py +0 -25
- unrealon_sdk/src/clients/python_http/models/HTTPValidationError.py +0 -16
- unrealon_sdk/src/clients/python_http/models/HealthResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/HealthStatus.py +0 -33
- unrealon_sdk/src/clients/python_http/models/LogLevel.py +0 -10
- unrealon_sdk/src/clients/python_http/models/LoggingRequest.py +0 -27
- unrealon_sdk/src/clients/python_http/models/LoggingResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/MaintenanceMode.py +0 -9
- unrealon_sdk/src/clients/python_http/models/MaintenanceModeRequest.py +0 -33
- unrealon_sdk/src/clients/python_http/models/MaintenanceStatusResponse.py +0 -39
- unrealon_sdk/src/clients/python_http/models/ParserCommandRequest.py +0 -25
- unrealon_sdk/src/clients/python_http/models/ParserMessageResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/ParserRegistrationRequest.py +0 -28
- unrealon_sdk/src/clients/python_http/models/ParserRegistrationResponse.py +0 -25
- unrealon_sdk/src/clients/python_http/models/ParserType.py +0 -10
- unrealon_sdk/src/clients/python_http/models/ProxyBlockRequest.py +0 -19
- unrealon_sdk/src/clients/python_http/models/ProxyEndpointResponse.py +0 -20
- unrealon_sdk/src/clients/python_http/models/ProxyListResponse.py +0 -19
- unrealon_sdk/src/clients/python_http/models/ProxyProvider.py +0 -10
- unrealon_sdk/src/clients/python_http/models/ProxyPurchaseRequest.py +0 -25
- unrealon_sdk/src/clients/python_http/models/ProxyResponse.py +0 -47
- unrealon_sdk/src/clients/python_http/models/ProxyRotationRequest.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ProxyStatus.py +0 -10
- unrealon_sdk/src/clients/python_http/models/ProxyUsageRequest.py +0 -19
- unrealon_sdk/src/clients/python_http/models/ProxyUsageStatsResponse.py +0 -26
- unrealon_sdk/src/clients/python_http/models/ServiceRegistrationDto.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ServiceStatsResponse.py +0 -31
- unrealon_sdk/src/clients/python_http/models/SessionStartRequest.py +0 -23
- unrealon_sdk/src/clients/python_http/models/SuccessResponse.py +0 -25
- unrealon_sdk/src/clients/python_http/models/SystemNotificationResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ValidationError.py +0 -18
- unrealon_sdk/src/clients/python_http/models/ValidationErrorResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/WebSocketMetrics.py +0 -21
- unrealon_sdk/src/clients/python_http/models/__init__.py +0 -44
- unrealon_sdk/src/clients/python_http/services/None_service.py +0 -35
- unrealon_sdk/src/clients/python_http/services/ParserManagement_service.py +0 -190
- unrealon_sdk/src/clients/python_http/services/ProxyManagement_service.py +0 -289
- unrealon_sdk/src/clients/python_http/services/SocketLogging_service.py +0 -187
- unrealon_sdk/src/clients/python_http/services/SystemHealth_service.py +0 -119
- unrealon_sdk/src/clients/python_http/services/WebSocketAPI_service.py +0 -198
- unrealon_sdk/src/clients/python_http/services/__init__.py +0 -0
- unrealon_sdk/src/clients/python_http/services/admin_service.py +0 -125
- unrealon_sdk/src/clients/python_http/services/async_None_service.py +0 -35
- unrealon_sdk/src/clients/python_http/services/async_ParserManagement_service.py +0 -190
- unrealon_sdk/src/clients/python_http/services/async_ProxyManagement_service.py +0 -289
- unrealon_sdk/src/clients/python_http/services/async_SocketLogging_service.py +0 -189
- unrealon_sdk/src/clients/python_http/services/async_SystemHealth_service.py +0 -123
- unrealon_sdk/src/clients/python_http/services/async_WebSocketAPI_service.py +0 -200
- unrealon_sdk/src/clients/python_http/services/async_admin_service.py +0 -125
- unrealon_sdk/src/clients/python_websocket/__init__.py +0 -28
- unrealon_sdk/src/clients/python_websocket/client.py +0 -490
- unrealon_sdk/src/clients/python_websocket/events.py +0 -732
- unrealon_sdk/src/clients/python_websocket/example.py +0 -136
- unrealon_sdk/src/clients/python_websocket/types.py +0 -871
- unrealon_sdk/src/core/__init__.py +0 -64
- unrealon_sdk/src/core/client.py +0 -556
- unrealon_sdk/src/core/config.py +0 -465
- unrealon_sdk/src/core/exceptions.py +0 -239
- unrealon_sdk/src/core/metadata.py +0 -191
- unrealon_sdk/src/core/models.py +0 -142
- unrealon_sdk/src/core/types.py +0 -68
- unrealon_sdk/src/dto/__init__.py +0 -268
- unrealon_sdk/src/dto/authentication.py +0 -108
- unrealon_sdk/src/dto/cache.py +0 -208
- unrealon_sdk/src/dto/common.py +0 -19
- unrealon_sdk/src/dto/concurrency.py +0 -393
- unrealon_sdk/src/dto/events.py +0 -108
- unrealon_sdk/src/dto/health.py +0 -339
- unrealon_sdk/src/dto/load_balancing.py +0 -336
- unrealon_sdk/src/dto/logging.py +0 -230
- unrealon_sdk/src/dto/performance.py +0 -165
- unrealon_sdk/src/dto/rate_limiting.py +0 -295
- unrealon_sdk/src/dto/resource_pooling.py +0 -128
- unrealon_sdk/src/dto/structured_logging.py +0 -112
- unrealon_sdk/src/dto/task_scheduling.py +0 -121
- unrealon_sdk/src/dto/websocket.py +0 -55
- unrealon_sdk/src/enterprise/__init__.py +0 -59
- unrealon_sdk/src/enterprise/authentication.py +0 -401
- unrealon_sdk/src/enterprise/cache_manager.py +0 -578
- unrealon_sdk/src/enterprise/error_recovery.py +0 -494
- unrealon_sdk/src/enterprise/event_system.py +0 -549
- unrealon_sdk/src/enterprise/health_monitor.py +0 -747
- unrealon_sdk/src/enterprise/load_balancer.py +0 -964
- unrealon_sdk/src/enterprise/logging/__init__.py +0 -68
- unrealon_sdk/src/enterprise/logging/cleanup.py +0 -156
- unrealon_sdk/src/enterprise/logging/development.py +0 -744
- unrealon_sdk/src/enterprise/logging/service.py +0 -410
- unrealon_sdk/src/enterprise/multithreading_manager.py +0 -853
- unrealon_sdk/src/enterprise/performance_monitor.py +0 -539
- unrealon_sdk/src/enterprise/proxy_manager.py +0 -696
- unrealon_sdk/src/enterprise/rate_limiter.py +0 -652
- unrealon_sdk/src/enterprise/resource_pool.py +0 -763
- unrealon_sdk/src/enterprise/task_scheduler.py +0 -709
- unrealon_sdk/src/internal/__init__.py +0 -10
- unrealon_sdk/src/internal/command_router.py +0 -497
- unrealon_sdk/src/internal/connection_manager.py +0 -397
- unrealon_sdk/src/internal/http_client.py +0 -446
- unrealon_sdk/src/internal/websocket_client.py +0 -420
- unrealon_sdk/src/provider.py +0 -471
- unrealon_sdk/src/utils.py +0 -234
- /unrealon_browser/{src/cli → cli}/__init__.py +0 -0
- /unrealon_browser/{src/cli → cli}/interactive_mode.py +0 -0
- /unrealon_browser/{src/cli → cli}/main.py +0 -0
- /unrealon_browser/{src/core → core}/__init__.py +0 -0
- /unrealon_browser/{src/dto → dto}/__init__.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/config.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/core.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/dataclasses.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/detection.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/enums.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/statistics.py +0 -0
- /unrealon_browser/{src/managers → managers}/__init__.py +0 -0
- /unrealon_browser/{src/managers → managers}/stealth.py +0 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Error Manager - Smart error handling and retry logic with Pydantic v2
|
|
3
|
+
|
|
4
|
+
Strict compliance with CRITICAL_REQUIREMENTS.md:
|
|
5
|
+
- No Dict[str, Any] usage
|
|
6
|
+
- Complete type annotations
|
|
7
|
+
- Custom exception hierarchy
|
|
8
|
+
- No bare except clauses
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import random
|
|
13
|
+
from datetime import datetime, timedelta
|
|
14
|
+
from typing import Callable, Optional, List, Type, TypeVar, Any
|
|
15
|
+
from pydantic import BaseModel, Field, ConfigDict, field_validator
|
|
16
|
+
from enum import Enum
|
|
17
|
+
import functools
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
F = TypeVar('F', bound=Callable[..., Any])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ErrorSeverity(str, Enum):
|
|
25
|
+
"""Error severity levels"""
|
|
26
|
+
LOW = "low"
|
|
27
|
+
MEDIUM = "medium"
|
|
28
|
+
HIGH = "high"
|
|
29
|
+
CRITICAL = "critical"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ErrorInfo(BaseModel):
|
|
33
|
+
"""Error information with strict typing"""
|
|
34
|
+
model_config = ConfigDict(
|
|
35
|
+
validate_assignment=True,
|
|
36
|
+
extra="forbid"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
exception_type: str = Field(
|
|
40
|
+
...,
|
|
41
|
+
description="Exception type name"
|
|
42
|
+
)
|
|
43
|
+
exception_message: str = Field(
|
|
44
|
+
...,
|
|
45
|
+
description="Exception message"
|
|
46
|
+
)
|
|
47
|
+
severity: ErrorSeverity = Field(
|
|
48
|
+
...,
|
|
49
|
+
description="Error severity level"
|
|
50
|
+
)
|
|
51
|
+
timestamp: datetime = Field(
|
|
52
|
+
default_factory=datetime.now,
|
|
53
|
+
description="Error occurrence timestamp"
|
|
54
|
+
)
|
|
55
|
+
operation: str = Field(
|
|
56
|
+
...,
|
|
57
|
+
min_length=1,
|
|
58
|
+
description="Operation where error occurred"
|
|
59
|
+
)
|
|
60
|
+
retry_count: int = Field(
|
|
61
|
+
default=0,
|
|
62
|
+
ge=0,
|
|
63
|
+
description="Number of retry attempts"
|
|
64
|
+
)
|
|
65
|
+
recoverable: bool = Field(
|
|
66
|
+
default=True,
|
|
67
|
+
description="Whether error is recoverable"
|
|
68
|
+
)
|
|
69
|
+
context: dict[str, str] = Field(
|
|
70
|
+
default_factory=dict,
|
|
71
|
+
description="Additional error context"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class RetryConfig(BaseModel):
|
|
76
|
+
"""Retry configuration with validation"""
|
|
77
|
+
model_config = ConfigDict(
|
|
78
|
+
validate_assignment=True,
|
|
79
|
+
extra="forbid"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
max_attempts: int = Field(
|
|
83
|
+
default=3,
|
|
84
|
+
ge=1,
|
|
85
|
+
le=10,
|
|
86
|
+
description="Maximum retry attempts"
|
|
87
|
+
)
|
|
88
|
+
base_delay: float = Field(
|
|
89
|
+
default=1.0,
|
|
90
|
+
ge=0.1,
|
|
91
|
+
le=60.0,
|
|
92
|
+
description="Base delay between retries in seconds"
|
|
93
|
+
)
|
|
94
|
+
max_delay: float = Field(
|
|
95
|
+
default=60.0,
|
|
96
|
+
ge=1.0,
|
|
97
|
+
le=300.0,
|
|
98
|
+
description="Maximum delay between retries in seconds"
|
|
99
|
+
)
|
|
100
|
+
exponential_base: float = Field(
|
|
101
|
+
default=2.0,
|
|
102
|
+
ge=1.1,
|
|
103
|
+
le=10.0,
|
|
104
|
+
description="Exponential backoff base"
|
|
105
|
+
)
|
|
106
|
+
jitter: bool = Field(
|
|
107
|
+
default=True,
|
|
108
|
+
description="Add random jitter to delays"
|
|
109
|
+
)
|
|
110
|
+
retry_on_exceptions: List[str] = Field(
|
|
111
|
+
default_factory=lambda: ["Exception"],
|
|
112
|
+
description="Exception types to retry on"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
@field_validator('max_delay')
|
|
116
|
+
@classmethod
|
|
117
|
+
def validate_max_delay(cls, v: float, info) -> float:
|
|
118
|
+
"""Validate max_delay is greater than base_delay"""
|
|
119
|
+
if 'base_delay' in info.data and v < info.data['base_delay']:
|
|
120
|
+
raise ValueError("max_delay must be greater than base_delay")
|
|
121
|
+
return v
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class CircuitBreakerState(BaseModel):
|
|
125
|
+
"""Circuit breaker state tracking"""
|
|
126
|
+
model_config = ConfigDict(
|
|
127
|
+
validate_assignment=True,
|
|
128
|
+
extra="forbid"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
total_requests: int = Field(default=0, ge=0)
|
|
132
|
+
failures: int = Field(default=0, ge=0)
|
|
133
|
+
failure_rate: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
134
|
+
last_failure: Optional[datetime] = Field(default=None)
|
|
135
|
+
is_open: bool = Field(default=False)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ErrorManagerError(Exception):
|
|
139
|
+
"""Base exception for error manager"""
|
|
140
|
+
def __init__(self, message: str, operation: str, details: Optional[dict[str, str]] = None):
|
|
141
|
+
self.message = message
|
|
142
|
+
self.operation = operation
|
|
143
|
+
self.details = details or {}
|
|
144
|
+
super().__init__(message)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class RetryExhaustedException(ErrorManagerError):
|
|
148
|
+
"""Raised when all retry attempts are exhausted"""
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class CircuitBreakerOpenError(ErrorManagerError):
|
|
153
|
+
"""Raised when circuit breaker is open"""
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ErrorManager:
|
|
158
|
+
"""
|
|
159
|
+
🛡️ Error Manager - Smart error handling and retry logic
|
|
160
|
+
|
|
161
|
+
Features:
|
|
162
|
+
- Automatic retry with exponential backoff
|
|
163
|
+
- Error classification and severity
|
|
164
|
+
- Circuit breaker pattern
|
|
165
|
+
- Error pattern detection
|
|
166
|
+
- Type-safe error handling
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def __init__(self, logger: Optional[logging.Logger] = None):
|
|
170
|
+
self.logger: logging.Logger = logger or logging.getLogger(__name__)
|
|
171
|
+
self._error_history: List[ErrorInfo] = []
|
|
172
|
+
self._retry_configs: dict[str, RetryConfig] = {}
|
|
173
|
+
self._circuit_breaker_states: dict[str, CircuitBreakerState] = {}
|
|
174
|
+
|
|
175
|
+
def register_retry_config(self, operation: str, config: RetryConfig) -> None:
|
|
176
|
+
"""Register retry configuration for an operation"""
|
|
177
|
+
if not operation.strip():
|
|
178
|
+
raise ValueError("Operation name cannot be empty")
|
|
179
|
+
|
|
180
|
+
self._retry_configs[operation] = config
|
|
181
|
+
|
|
182
|
+
def classify_error(self, exception: Exception, operation: str) -> ErrorSeverity:
|
|
183
|
+
"""Classify error severity based on exception type"""
|
|
184
|
+
exception_type = type(exception).__name__
|
|
185
|
+
|
|
186
|
+
# Network/connection errors - usually recoverable
|
|
187
|
+
if exception_type in ['ConnectionError', 'TimeoutError', 'ConnectTimeout']:
|
|
188
|
+
return ErrorSeverity.MEDIUM
|
|
189
|
+
|
|
190
|
+
# Browser/automation errors - might be recoverable
|
|
191
|
+
if 'playwright' in exception_type.lower() or 'selenium' in exception_type.lower():
|
|
192
|
+
return ErrorSeverity.MEDIUM
|
|
193
|
+
|
|
194
|
+
# Parse/data errors - usually low severity
|
|
195
|
+
if exception_type in ['ValueError', 'KeyError', 'AttributeError', 'TypeError']:
|
|
196
|
+
return ErrorSeverity.LOW
|
|
197
|
+
|
|
198
|
+
# Memory/system errors - critical
|
|
199
|
+
if exception_type in ['MemoryError', 'OSError', 'SystemError']:
|
|
200
|
+
return ErrorSeverity.CRITICAL
|
|
201
|
+
|
|
202
|
+
# Permission errors - high severity
|
|
203
|
+
if exception_type in ['PermissionError', 'FileNotFoundError']:
|
|
204
|
+
return ErrorSeverity.HIGH
|
|
205
|
+
|
|
206
|
+
# Default to medium
|
|
207
|
+
return ErrorSeverity.MEDIUM
|
|
208
|
+
|
|
209
|
+
def should_retry(self, error_info: ErrorInfo, config: RetryConfig) -> bool:
|
|
210
|
+
"""Determine if operation should be retried"""
|
|
211
|
+
# Check max attempts
|
|
212
|
+
if error_info.retry_count >= config.max_attempts:
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
# Check if exception type is retryable
|
|
216
|
+
if error_info.exception_type not in config.retry_on_exceptions:
|
|
217
|
+
# Check if "Exception" is in the list (catch-all)
|
|
218
|
+
if "Exception" not in config.retry_on_exceptions:
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
# Check circuit breaker
|
|
222
|
+
if self._is_circuit_open(error_info.operation):
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
# Critical errors shouldn't be retried
|
|
226
|
+
if error_info.severity == ErrorSeverity.CRITICAL:
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
return error_info.recoverable
|
|
230
|
+
|
|
231
|
+
def calculate_delay(self, retry_count: int, config: RetryConfig) -> float:
|
|
232
|
+
"""Calculate retry delay with exponential backoff"""
|
|
233
|
+
delay = config.base_delay * (config.exponential_base ** retry_count)
|
|
234
|
+
delay = min(delay, config.max_delay)
|
|
235
|
+
|
|
236
|
+
if config.jitter:
|
|
237
|
+
# Add random jitter (±25%)
|
|
238
|
+
jitter = delay * 0.25 * (2 * random.random() - 1)
|
|
239
|
+
delay += jitter
|
|
240
|
+
|
|
241
|
+
return max(0.1, delay) # Minimum 0.1 second delay
|
|
242
|
+
|
|
243
|
+
def record_error(
|
|
244
|
+
self,
|
|
245
|
+
exception: Exception,
|
|
246
|
+
operation: str,
|
|
247
|
+
context: Optional[dict[str, str]] = None
|
|
248
|
+
) -> ErrorInfo:
|
|
249
|
+
"""Record an error occurrence"""
|
|
250
|
+
error_info = ErrorInfo(
|
|
251
|
+
exception_type=type(exception).__name__,
|
|
252
|
+
exception_message=str(exception),
|
|
253
|
+
severity=self.classify_error(exception, operation),
|
|
254
|
+
operation=operation,
|
|
255
|
+
recoverable=self._is_recoverable(exception),
|
|
256
|
+
context=context or {}
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
self._error_history.append(error_info)
|
|
260
|
+
self._update_circuit_breaker(operation, success=False)
|
|
261
|
+
|
|
262
|
+
return error_info
|
|
263
|
+
|
|
264
|
+
def record_success(self, operation: str) -> None:
|
|
265
|
+
"""Record a successful operation"""
|
|
266
|
+
self._update_circuit_breaker(operation, success=True)
|
|
267
|
+
|
|
268
|
+
def with_retry(
|
|
269
|
+
self,
|
|
270
|
+
operation: str,
|
|
271
|
+
config: Optional[RetryConfig] = None
|
|
272
|
+
) -> Callable[[F], F]:
|
|
273
|
+
"""Decorator for automatic retry logic"""
|
|
274
|
+
def decorator(func: F) -> F:
|
|
275
|
+
@functools.wraps(func)
|
|
276
|
+
async def wrapper(*args, **kwargs):
|
|
277
|
+
retry_config = config or self._retry_configs.get(operation, RetryConfig())
|
|
278
|
+
last_error: Optional[Exception] = None
|
|
279
|
+
|
|
280
|
+
for attempt in range(retry_config.max_attempts):
|
|
281
|
+
try:
|
|
282
|
+
result = await func(*args, **kwargs)
|
|
283
|
+
self.record_success(operation)
|
|
284
|
+
return result
|
|
285
|
+
|
|
286
|
+
except Exception as e:
|
|
287
|
+
error_info = self.record_error(e, operation)
|
|
288
|
+
error_info.retry_count = attempt
|
|
289
|
+
last_error = e
|
|
290
|
+
|
|
291
|
+
if not self.should_retry(error_info, retry_config):
|
|
292
|
+
break
|
|
293
|
+
|
|
294
|
+
if attempt < retry_config.max_attempts - 1:
|
|
295
|
+
delay = self.calculate_delay(attempt, retry_config)
|
|
296
|
+
self.logger.warning(
|
|
297
|
+
f"Operation '{operation}' failed (attempt {attempt + 1}), "
|
|
298
|
+
f"retrying in {delay:.1f}s: {e}"
|
|
299
|
+
)
|
|
300
|
+
await asyncio.sleep(delay)
|
|
301
|
+
|
|
302
|
+
# All retries exhausted
|
|
303
|
+
self.logger.error(f"Operation '{operation}' failed after {retry_config.max_attempts} attempts")
|
|
304
|
+
if last_error:
|
|
305
|
+
raise RetryExhaustedException(
|
|
306
|
+
message=f"Operation failed after {retry_config.max_attempts} attempts",
|
|
307
|
+
operation=operation,
|
|
308
|
+
details={"last_error": str(last_error)}
|
|
309
|
+
) from last_error
|
|
310
|
+
else:
|
|
311
|
+
raise RetryExhaustedException(
|
|
312
|
+
message=f"Operation failed after {retry_config.max_attempts} attempts",
|
|
313
|
+
operation=operation
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return wrapper # type: ignore
|
|
317
|
+
return decorator
|
|
318
|
+
|
|
319
|
+
def _is_recoverable(self, exception: Exception) -> bool:
|
|
320
|
+
"""Determine if an error is recoverable"""
|
|
321
|
+
exception_type = type(exception).__name__
|
|
322
|
+
|
|
323
|
+
# System errors are usually not recoverable
|
|
324
|
+
if exception_type in ['MemoryError', 'OSError', 'KeyboardInterrupt', 'SystemExit']:
|
|
325
|
+
return False
|
|
326
|
+
|
|
327
|
+
# Network errors are usually recoverable
|
|
328
|
+
if exception_type in ['ConnectionError', 'TimeoutError', 'ConnectTimeout']:
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
# Most other errors are recoverable
|
|
332
|
+
return True
|
|
333
|
+
|
|
334
|
+
def _is_circuit_open(self, operation: str) -> bool:
|
|
335
|
+
"""Check if circuit breaker is open for operation"""
|
|
336
|
+
state = self._circuit_breaker_states.get(operation)
|
|
337
|
+
if not state:
|
|
338
|
+
return False
|
|
339
|
+
|
|
340
|
+
# Circuit is open if failure rate is too high
|
|
341
|
+
if state.failure_rate > 0.5 and state.total_requests > 10:
|
|
342
|
+
# Check if cooldown period has passed
|
|
343
|
+
if state.last_failure:
|
|
344
|
+
cooldown_period = timedelta(minutes=5)
|
|
345
|
+
if datetime.now() - state.last_failure < cooldown_period:
|
|
346
|
+
return True
|
|
347
|
+
|
|
348
|
+
return False
|
|
349
|
+
|
|
350
|
+
def _update_circuit_breaker(self, operation: str, success: bool) -> None:
|
|
351
|
+
"""Update circuit breaker state"""
|
|
352
|
+
if operation not in self._circuit_breaker_states:
|
|
353
|
+
self._circuit_breaker_states[operation] = CircuitBreakerState()
|
|
354
|
+
|
|
355
|
+
state = self._circuit_breaker_states[operation]
|
|
356
|
+
state.total_requests += 1
|
|
357
|
+
|
|
358
|
+
if not success:
|
|
359
|
+
state.failures += 1
|
|
360
|
+
state.last_failure = datetime.now()
|
|
361
|
+
|
|
362
|
+
state.failure_rate = state.failures / state.total_requests
|
|
363
|
+
state.is_open = self._is_circuit_open(operation)
|
|
364
|
+
|
|
365
|
+
def get_error_stats(self) -> dict[str, float]:
|
|
366
|
+
"""Get error statistics"""
|
|
367
|
+
if not self._error_history:
|
|
368
|
+
return {
|
|
369
|
+
"total_errors": 0.0,
|
|
370
|
+
"recent_errors_24h": 0.0,
|
|
371
|
+
"critical_errors": 0.0,
|
|
372
|
+
"high_errors": 0.0,
|
|
373
|
+
"medium_errors": 0.0,
|
|
374
|
+
"low_errors": 0.0
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
recent_errors = [
|
|
378
|
+
e for e in self._error_history
|
|
379
|
+
if e.timestamp > datetime.now() - timedelta(hours=24)
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
severity_counts = {
|
|
383
|
+
"critical": len([e for e in recent_errors if e.severity == ErrorSeverity.CRITICAL]),
|
|
384
|
+
"high": len([e for e in recent_errors if e.severity == ErrorSeverity.HIGH]),
|
|
385
|
+
"medium": len([e for e in recent_errors if e.severity == ErrorSeverity.MEDIUM]),
|
|
386
|
+
"low": len([e for e in recent_errors if e.severity == ErrorSeverity.LOW])
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
"total_errors": float(len(self._error_history)),
|
|
391
|
+
"recent_errors_24h": float(len(recent_errors)),
|
|
392
|
+
"critical_errors": float(severity_counts["critical"]),
|
|
393
|
+
"high_errors": float(severity_counts["high"]),
|
|
394
|
+
"medium_errors": float(severity_counts["medium"]),
|
|
395
|
+
"low_errors": float(severity_counts["low"])
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
def get_circuit_breaker_states(self) -> dict[str, CircuitBreakerState]:
|
|
399
|
+
"""Get circuit breaker states"""
|
|
400
|
+
return {
|
|
401
|
+
operation: CircuitBreakerState.model_validate(state.model_dump())
|
|
402
|
+
for operation, state in self._circuit_breaker_states.items()
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
def reset_circuit_breaker(self, operation: str) -> None:
|
|
406
|
+
"""Reset circuit breaker for operation"""
|
|
407
|
+
if operation in self._circuit_breaker_states:
|
|
408
|
+
self._circuit_breaker_states[operation] = CircuitBreakerState()
|
|
409
|
+
|
|
410
|
+
def clear_error_history(self) -> None:
|
|
411
|
+
"""Clear error history"""
|
|
412
|
+
self._error_history.clear()
|