unrealon 1.0.8__py3-none-any.whl → 1.1.0__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.0.dist-info/METADATA +164 -0
- unrealon-1.1.0.dist-info/RECORD +82 -0
- {unrealon-1.0.8.dist-info → unrealon-1.1.0.dist-info}/WHEEL +1 -1
- unrealon-1.1.0.dist-info/entry_points.txt +9 -0
- {unrealon-1.0.8.dist-info → unrealon-1.1.0.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.8.dist-info/METADATA +0 -803
- unrealon-1.0.8.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
|
@@ -1,696 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ProxyManager - Intelligent Proxy Management for Enterprise Parsing
|
|
3
|
-
|
|
4
|
-
Layer 3: Infrastructure Services - Core proxy management with:
|
|
5
|
-
- Geographic rotation and health checking
|
|
6
|
-
- Automatic failover and recovery
|
|
7
|
-
- Integration with multiple proxy providers
|
|
8
|
-
- Performance monitoring and optimization
|
|
9
|
-
- Type-safe operations with Pydantic v2
|
|
10
|
-
|
|
11
|
-
Enterprise Features:
|
|
12
|
-
- Smart proxy rotation based on success rates
|
|
13
|
-
- Geographic targeting for region-specific parsing
|
|
14
|
-
- Health monitoring with automatic blacklisting
|
|
15
|
-
- Provider failover and load balancing
|
|
16
|
-
- Real-time proxy performance metrics
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
import asyncio
|
|
20
|
-
import logging
|
|
21
|
-
from datetime import datetime, timedelta, timezone
|
|
22
|
-
from typing import Dict, List, Optional, Set, Any
|
|
23
|
-
from dataclasses import dataclass, field
|
|
24
|
-
from enum import Enum
|
|
25
|
-
import random
|
|
26
|
-
|
|
27
|
-
# Pydantic v2 for all data models
|
|
28
|
-
from pydantic import BaseModel, Field, ConfigDict
|
|
29
|
-
|
|
30
|
-
# Auto-generated models from the API - HTTP models
|
|
31
|
-
from unrealon_sdk.src.clients.python_http.models import (
|
|
32
|
-
ProxyListResponse,
|
|
33
|
-
ProxyUsageStatsResponse,
|
|
34
|
-
ProxyRotationRequest,
|
|
35
|
-
ProxyPurchaseRequest,
|
|
36
|
-
ProxyBlockRequest,
|
|
37
|
-
ProxyEndpointResponse,
|
|
38
|
-
ProxyResponse,
|
|
39
|
-
ErrorResponse,
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
# Auto-generated models from WebSocket types
|
|
43
|
-
from unrealon_sdk.src.clients.python_websocket.types import (
|
|
44
|
-
ProxyProvider,
|
|
45
|
-
ProxyStatus,
|
|
46
|
-
ProxyProtocol,
|
|
47
|
-
ProxyRotationStrategy,
|
|
48
|
-
Proxy,
|
|
49
|
-
ProxyEndpoint,
|
|
50
|
-
ProxyCredentials,
|
|
51
|
-
ProxyUsageStats,
|
|
52
|
-
ProxyAllocation,
|
|
53
|
-
ProxySummary,
|
|
54
|
-
ProxyDetails,
|
|
55
|
-
ProxyStatistics,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
# Import auto-generated proxy management services
|
|
59
|
-
from unrealon_sdk.src.clients.python_http.services.async_ProxyManagement_service import (
|
|
60
|
-
list_proxies_api_v1_proxies__get,
|
|
61
|
-
get_proxy_statistics_api_v1_proxies_statistics_get,
|
|
62
|
-
record_proxy_usage_api_v1_proxies__proxy_id__usage_post,
|
|
63
|
-
report_blocked_proxy_api_v1_proxies_rotation_block_post,
|
|
64
|
-
request_proxy_rotation_api_v1_proxies_rotation_request_post,
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
# Core SDK components
|
|
68
|
-
from unrealon_sdk.src.core.config import AdapterConfig, ProxyConfig
|
|
69
|
-
from unrealon_sdk.src.core.exceptions import ProxyError, ConnectionError
|
|
70
|
-
from unrealon_sdk.src.utils import generate_correlation_id
|
|
71
|
-
|
|
72
|
-
# Development logging
|
|
73
|
-
from .logging.development import (
|
|
74
|
-
get_development_logger,
|
|
75
|
-
SDKEventType,
|
|
76
|
-
SDKContext,
|
|
77
|
-
track_development_operation,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
logger = logging.getLogger(__name__)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
# Use auto-generated ProxyUsageStats but extend with helper methods
|
|
84
|
-
class ExtendedProxyUsageStats(ProxyUsageStats):
|
|
85
|
-
"""Extended proxy usage stats with helper methods."""
|
|
86
|
-
|
|
87
|
-
@property
|
|
88
|
-
def success_rate_percentage(self) -> float:
|
|
89
|
-
"""Calculate success rate as percentage."""
|
|
90
|
-
if self.total_requests == 0:
|
|
91
|
-
return 0.0
|
|
92
|
-
return (self.successful_requests / self.total_requests) * 100
|
|
93
|
-
|
|
94
|
-
@property
|
|
95
|
-
def is_healthy(self) -> bool:
|
|
96
|
-
"""Check if proxy usage indicates healthy status."""
|
|
97
|
-
return self.consecutive_failures < 3 and self.success_rate >= 70.0
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# Extended proxy model with management features
|
|
101
|
-
class ManagedProxy(Proxy):
|
|
102
|
-
"""Extended Proxy model with management features."""
|
|
103
|
-
|
|
104
|
-
# Management fields
|
|
105
|
-
is_active: bool = Field(default=True, description="Whether proxy is currently active")
|
|
106
|
-
last_health_check: Optional[datetime] = Field(
|
|
107
|
-
default=None, description="Last health check timestamp"
|
|
108
|
-
)
|
|
109
|
-
blacklisted_until: Optional[datetime] = Field(
|
|
110
|
-
default=None, description="Blacklist expiration time"
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
@property
|
|
114
|
-
def proxy_url(self) -> str:
|
|
115
|
-
"""Generate proxy URL for HTTP clients."""
|
|
116
|
-
if self.credentials:
|
|
117
|
-
auth = f"{self.credentials.username}:{self.credentials.password}@"
|
|
118
|
-
else:
|
|
119
|
-
auth = ""
|
|
120
|
-
protocol = (
|
|
121
|
-
self.endpoint.protocol.value
|
|
122
|
-
if hasattr(self.endpoint.protocol, "value")
|
|
123
|
-
else str(self.endpoint.protocol)
|
|
124
|
-
)
|
|
125
|
-
return f"{protocol}://{auth}{self.endpoint.host}:{self.endpoint.port}"
|
|
126
|
-
|
|
127
|
-
@property
|
|
128
|
-
def identifier(self) -> str:
|
|
129
|
-
"""Unique identifier for this proxy."""
|
|
130
|
-
return f"{self.endpoint.host}:{self.endpoint.port}"
|
|
131
|
-
|
|
132
|
-
@property
|
|
133
|
-
def is_blacklisted(self) -> bool:
|
|
134
|
-
"""Check if proxy is currently blacklisted."""
|
|
135
|
-
if self.blacklisted_until is None:
|
|
136
|
-
return False
|
|
137
|
-
return datetime.now(timezone.utc) < self.blacklisted_until
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
class ProxyPool:
|
|
141
|
-
"""Manages a pool of proxy endpoints with intelligent selection using auto-generated models."""
|
|
142
|
-
|
|
143
|
-
def __init__(self, strategy: ProxyRotationStrategy = ProxyRotationStrategy.SUCCESS_RATE):
|
|
144
|
-
self.proxies: Dict[str, ManagedProxy] = {}
|
|
145
|
-
self.strategy = strategy
|
|
146
|
-
self._rotation_index = 0
|
|
147
|
-
|
|
148
|
-
def add_proxy(self, proxy: ManagedProxy) -> None:
|
|
149
|
-
"""Add a proxy to the pool."""
|
|
150
|
-
self.proxies[proxy.identifier] = proxy
|
|
151
|
-
logger.debug(f"Added proxy {proxy.identifier} to pool")
|
|
152
|
-
|
|
153
|
-
def remove_proxy(self, proxy_id: str) -> None:
|
|
154
|
-
"""Remove a proxy from the pool."""
|
|
155
|
-
if proxy_id in self.proxies:
|
|
156
|
-
del self.proxies[proxy_id]
|
|
157
|
-
logger.debug(f"Removed proxy {proxy_id} from pool")
|
|
158
|
-
|
|
159
|
-
def blacklist_proxy(self, proxy_id: str, duration_minutes: int = 30) -> None:
|
|
160
|
-
"""Temporarily blacklist a proxy."""
|
|
161
|
-
if proxy_id in self.proxies:
|
|
162
|
-
blacklist_until = datetime.now(timezone.utc) + timedelta(minutes=duration_minutes)
|
|
163
|
-
self.proxies[proxy_id].blacklisted_until = blacklist_until
|
|
164
|
-
self.proxies[proxy_id].is_active = False
|
|
165
|
-
|
|
166
|
-
logger.warning(f"Blacklisted proxy {proxy_id} for {duration_minutes} minutes")
|
|
167
|
-
|
|
168
|
-
# Schedule automatic removal from blacklist
|
|
169
|
-
asyncio.create_task(self._remove_from_blacklist_later(proxy_id, duration_minutes))
|
|
170
|
-
|
|
171
|
-
async def _remove_from_blacklist_later(self, proxy_id: str, minutes: int) -> None:
|
|
172
|
-
"""Remove proxy from blacklist after specified time."""
|
|
173
|
-
await asyncio.sleep(minutes * 60)
|
|
174
|
-
if proxy_id in self.proxies:
|
|
175
|
-
self.proxies[proxy_id].blacklisted_until = None
|
|
176
|
-
self.proxies[proxy_id].is_active = True
|
|
177
|
-
logger.info(f"Proxy {proxy_id} removed from blacklist")
|
|
178
|
-
|
|
179
|
-
def get_available_proxies(self) -> List[ManagedProxy]:
|
|
180
|
-
"""Get list of available (non-blacklisted, healthy) proxies."""
|
|
181
|
-
return [
|
|
182
|
-
proxy
|
|
183
|
-
for proxy in self.proxies.values()
|
|
184
|
-
if proxy.is_active and not proxy.is_blacklisted and proxy.usage_stats.is_healthy
|
|
185
|
-
]
|
|
186
|
-
|
|
187
|
-
def select_proxy(self, region: Optional[str] = None) -> Optional[ManagedProxy]:
|
|
188
|
-
"""Select best proxy based on strategy."""
|
|
189
|
-
available = self.get_available_proxies()
|
|
190
|
-
|
|
191
|
-
if not available:
|
|
192
|
-
logger.warning("No available proxies in pool")
|
|
193
|
-
return None
|
|
194
|
-
|
|
195
|
-
# Filter by region if specified
|
|
196
|
-
if region:
|
|
197
|
-
regional = [p for p in available if p.region == region or p.country == region]
|
|
198
|
-
if regional:
|
|
199
|
-
available = regional
|
|
200
|
-
|
|
201
|
-
if self.strategy == ProxyRotationStrategy.ROUND_ROBIN:
|
|
202
|
-
return self._select_round_robin(available)
|
|
203
|
-
elif self.strategy == ProxyRotationStrategy.SUCCESS_RATE:
|
|
204
|
-
return self._select_by_success_rate(available)
|
|
205
|
-
elif self.strategy == ProxyRotationStrategy.WEIGHTED_RANDOM:
|
|
206
|
-
return self._select_weighted_random(available)
|
|
207
|
-
elif self.strategy == ProxyRotationStrategy.LEAST_FAILURES:
|
|
208
|
-
return self._select_least_failures(available)
|
|
209
|
-
elif self.strategy == ProxyRotationStrategy.LEAST_USED:
|
|
210
|
-
return self._select_least_used(available)
|
|
211
|
-
else:
|
|
212
|
-
return available[0] # Fallback
|
|
213
|
-
|
|
214
|
-
def _select_round_robin(self, available: List[ManagedProxy]) -> ManagedProxy:
|
|
215
|
-
"""Round-robin selection."""
|
|
216
|
-
proxy = available[self._rotation_index % len(available)]
|
|
217
|
-
self._rotation_index += 1
|
|
218
|
-
return proxy
|
|
219
|
-
|
|
220
|
-
def _select_by_success_rate(self, available: List[ManagedProxy]) -> ManagedProxy:
|
|
221
|
-
"""Select proxy with highest success rate."""
|
|
222
|
-
return max(available, key=lambda p: p.usage_stats.success_rate)
|
|
223
|
-
|
|
224
|
-
def _select_weighted_random(self, available: List[ManagedProxy]) -> ManagedProxy:
|
|
225
|
-
"""Weighted random selection based on success rate."""
|
|
226
|
-
weights = [max(p.usage_stats.success_rate, 0.1) for p in available]
|
|
227
|
-
return random.choices(available, weights=weights)[0]
|
|
228
|
-
|
|
229
|
-
def _select_least_failures(self, available: List[ManagedProxy]) -> ManagedProxy:
|
|
230
|
-
"""Select proxy with least consecutive failures."""
|
|
231
|
-
return min(available, key=lambda p: p.usage_stats.consecutive_failures)
|
|
232
|
-
|
|
233
|
-
def _select_least_used(self, available: List[ManagedProxy]) -> ManagedProxy:
|
|
234
|
-
"""Select proxy used least recently."""
|
|
235
|
-
|
|
236
|
-
def last_used_timestamp(proxy: ManagedProxy) -> float:
|
|
237
|
-
if proxy.usage_stats.last_used_at:
|
|
238
|
-
# Convert string timestamp to comparable format
|
|
239
|
-
try:
|
|
240
|
-
dt = datetime.fromisoformat(
|
|
241
|
-
proxy.usage_stats.last_used_at.replace("Z", "+00:00")
|
|
242
|
-
)
|
|
243
|
-
return dt.timestamp()
|
|
244
|
-
except:
|
|
245
|
-
return 0.0
|
|
246
|
-
return 0.0
|
|
247
|
-
|
|
248
|
-
return min(available, key=last_used_timestamp)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
class ProxyManager:
|
|
252
|
-
"""
|
|
253
|
-
Enterprise-grade proxy management with intelligent rotation and health monitoring.
|
|
254
|
-
|
|
255
|
-
Features:
|
|
256
|
-
- Multiple proxy provider integration
|
|
257
|
-
- Smart rotation strategies
|
|
258
|
-
- Health monitoring and automatic failover
|
|
259
|
-
- Performance metrics and optimization
|
|
260
|
-
- Geographic targeting
|
|
261
|
-
"""
|
|
262
|
-
|
|
263
|
-
def __init__(self, config: AdapterConfig):
|
|
264
|
-
"""
|
|
265
|
-
Initialize ProxyManager with configuration.
|
|
266
|
-
|
|
267
|
-
Args:
|
|
268
|
-
config: Adapter configuration containing proxy settings
|
|
269
|
-
"""
|
|
270
|
-
self.config = config
|
|
271
|
-
self.proxy_config = config.proxy_config
|
|
272
|
-
self.logger = logger
|
|
273
|
-
|
|
274
|
-
# Development logging
|
|
275
|
-
self.dev_logger = get_development_logger()
|
|
276
|
-
if self.dev_logger:
|
|
277
|
-
self.dev_logger.set_component_context("ProxyManager")
|
|
278
|
-
self.dev_logger.log_info(
|
|
279
|
-
SDKEventType.PROXY_MANAGER_INITIALIZED,
|
|
280
|
-
f"ProxyManager initialization started with strategy: {self.proxy_config.rotation_strategy}",
|
|
281
|
-
context=SDKContext(
|
|
282
|
-
component_name="ProxyManager",
|
|
283
|
-
metadata={
|
|
284
|
-
"rotation_strategy": self.proxy_config.rotation_strategy,
|
|
285
|
-
"enabled": self.proxy_config.enabled,
|
|
286
|
-
"max_retries": self.proxy_config.max_retries,
|
|
287
|
-
},
|
|
288
|
-
),
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
# Initialize proxy pool with configured strategy
|
|
292
|
-
strategy = ProxyRotationStrategy(self.proxy_config.rotation_strategy)
|
|
293
|
-
self.pool = ProxyPool(strategy)
|
|
294
|
-
|
|
295
|
-
# Health monitoring
|
|
296
|
-
self._health_check_interval = 300 # 5 minutes
|
|
297
|
-
self._health_check_task: Optional[asyncio.Task] = None
|
|
298
|
-
|
|
299
|
-
# Statistics
|
|
300
|
-
self._total_requests = 0
|
|
301
|
-
self._successful_requests = 0
|
|
302
|
-
self._failed_requests = 0
|
|
303
|
-
|
|
304
|
-
# HTTP client for proxy API calls (will be injected)
|
|
305
|
-
self._http_client = None
|
|
306
|
-
|
|
307
|
-
self.logger.info(f"ProxyManager initialized with strategy: {strategy.value}")
|
|
308
|
-
|
|
309
|
-
if self.dev_logger:
|
|
310
|
-
self.dev_logger.log_info(
|
|
311
|
-
SDKEventType.COMPONENT_CREATED,
|
|
312
|
-
"ProxyManager initialization completed successfully",
|
|
313
|
-
success=True,
|
|
314
|
-
context=SDKContext(component_name="ProxyManager"),
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
async def initialize(self, http_client) -> None:
|
|
318
|
-
"""Initialize proxy manager with HTTP client."""
|
|
319
|
-
self._http_client = http_client
|
|
320
|
-
|
|
321
|
-
if self.proxy_config.enabled:
|
|
322
|
-
await self._load_initial_proxies()
|
|
323
|
-
await self._start_health_monitoring()
|
|
324
|
-
|
|
325
|
-
self.logger.info("ProxyManager initialization complete")
|
|
326
|
-
|
|
327
|
-
async def shutdown(self) -> None:
|
|
328
|
-
"""Shutdown proxy manager and cleanup resources."""
|
|
329
|
-
if self._health_check_task:
|
|
330
|
-
self._health_check_task.cancel()
|
|
331
|
-
try:
|
|
332
|
-
await self._health_check_task
|
|
333
|
-
except asyncio.CancelledError:
|
|
334
|
-
pass
|
|
335
|
-
|
|
336
|
-
self.logger.info("ProxyManager shutdown complete")
|
|
337
|
-
|
|
338
|
-
async def _load_initial_proxies(self) -> None:
|
|
339
|
-
"""Load initial proxy list from configured providers using real API."""
|
|
340
|
-
try:
|
|
341
|
-
if not self._http_client:
|
|
342
|
-
raise ProxyError("HTTP client not initialized")
|
|
343
|
-
|
|
344
|
-
self.logger.info("Loading initial proxy list from providers via API...")
|
|
345
|
-
|
|
346
|
-
# Call the real proxy list API
|
|
347
|
-
proxy_list_response = await list_proxies_api_v1_proxies__get(
|
|
348
|
-
healthy_only=True, limit=100, api_config_override=self._http_client._api_config
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
if not proxy_list_response or not proxy_list_response.items:
|
|
352
|
-
self.logger.warning("No proxies returned from API")
|
|
353
|
-
return
|
|
354
|
-
|
|
355
|
-
# Convert API response to ManagedProxy objects
|
|
356
|
-
for proxy_response in proxy_list_response.items:
|
|
357
|
-
managed_proxy = self._convert_api_proxy_to_managed(proxy_response)
|
|
358
|
-
self.pool.add_proxy(managed_proxy)
|
|
359
|
-
|
|
360
|
-
self.logger.info(f"Loaded {len(proxy_list_response.items)} proxies into pool")
|
|
361
|
-
|
|
362
|
-
except Exception as e:
|
|
363
|
-
self.logger.error(f"Failed to load initial proxies: {e}")
|
|
364
|
-
raise ProxyError(f"Failed to initialize proxy pool: {e}")
|
|
365
|
-
|
|
366
|
-
def _convert_api_proxy_to_managed(self, proxy_response: ProxyResponse) -> ManagedProxy:
|
|
367
|
-
"""Convert API ProxyResponse to ManagedProxy."""
|
|
368
|
-
return ManagedProxy(
|
|
369
|
-
proxy_id=proxy_response.proxy_id,
|
|
370
|
-
provider=proxy_response.provider,
|
|
371
|
-
provider_proxy_id=proxy_response.provider_proxy_id,
|
|
372
|
-
endpoint=ProxyEndpoint(
|
|
373
|
-
host=proxy_response.endpoint.host,
|
|
374
|
-
port=proxy_response.endpoint.port,
|
|
375
|
-
protocol=ProxyProtocol(proxy_response.endpoint.protocol),
|
|
376
|
-
),
|
|
377
|
-
credentials=(
|
|
378
|
-
ProxyCredentials(username="", password="") # Will be populated from provider config
|
|
379
|
-
if proxy_response.endpoint.connection_string
|
|
380
|
-
else None
|
|
381
|
-
),
|
|
382
|
-
country=proxy_response.country or "Unknown",
|
|
383
|
-
region=proxy_response.region,
|
|
384
|
-
city=proxy_response.city,
|
|
385
|
-
status=ProxyStatus(proxy_response.status),
|
|
386
|
-
created_at=proxy_response.created_at,
|
|
387
|
-
expires_at=proxy_response.expires_at,
|
|
388
|
-
last_validated_at=None, # Not in API response
|
|
389
|
-
usage_stats=ExtendedProxyUsageStats(
|
|
390
|
-
total_requests=proxy_response.usage_stats.total_requests,
|
|
391
|
-
successful_requests=proxy_response.usage_stats.successful_requests,
|
|
392
|
-
failed_requests=proxy_response.usage_stats.failed_requests,
|
|
393
|
-
consecutive_failures=proxy_response.usage_stats.consecutive_failures,
|
|
394
|
-
avg_response_time_ms=proxy_response.usage_stats.avg_response_time_ms or 0.0,
|
|
395
|
-
last_used_at=proxy_response.usage_stats.last_used_at,
|
|
396
|
-
blocked_count=0, # Not in current API
|
|
397
|
-
),
|
|
398
|
-
shared=False, # Default
|
|
399
|
-
tags=proxy_response.tags or [],
|
|
400
|
-
metadata=proxy_response.metadata or {},
|
|
401
|
-
# Management fields
|
|
402
|
-
is_active=proxy_response.is_healthy or False,
|
|
403
|
-
last_health_check=datetime.now(timezone.utc),
|
|
404
|
-
blacklisted_until=None,
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
async def _start_health_monitoring(self) -> None:
|
|
408
|
-
"""Start background health monitoring task."""
|
|
409
|
-
if self._health_check_task is None:
|
|
410
|
-
self._health_check_task = asyncio.create_task(self._health_monitor_loop())
|
|
411
|
-
self.logger.info("Started proxy health monitoring")
|
|
412
|
-
|
|
413
|
-
async def _health_monitor_loop(self) -> None:
|
|
414
|
-
"""Background task for monitoring proxy health."""
|
|
415
|
-
while True:
|
|
416
|
-
try:
|
|
417
|
-
await self._perform_health_checks()
|
|
418
|
-
await asyncio.sleep(self._health_check_interval)
|
|
419
|
-
except asyncio.CancelledError:
|
|
420
|
-
break
|
|
421
|
-
except Exception as e:
|
|
422
|
-
self.logger.error(f"Error in health monitor loop: {e}")
|
|
423
|
-
await asyncio.sleep(60) # Wait before retrying
|
|
424
|
-
|
|
425
|
-
async def _perform_health_checks(self) -> None:
|
|
426
|
-
"""Perform health checks on all proxies."""
|
|
427
|
-
self.logger.debug("Performing proxy health checks...")
|
|
428
|
-
|
|
429
|
-
for proxy_id, proxy in self.pool.proxies.items():
|
|
430
|
-
try:
|
|
431
|
-
await self._check_proxy_health(proxy)
|
|
432
|
-
except Exception as e:
|
|
433
|
-
self.logger.warning(f"Health check failed for proxy {proxy_id}: {e}")
|
|
434
|
-
proxy.metrics.health_status = ProxyHealthStatus.FAILING
|
|
435
|
-
|
|
436
|
-
async def _check_proxy_health(self, proxy: ProxyEndpoint) -> None:
|
|
437
|
-
"""Check health of a specific proxy."""
|
|
438
|
-
# Simple health check - in production this would make actual HTTP requests
|
|
439
|
-
proxy.last_health_check = datetime.now()
|
|
440
|
-
|
|
441
|
-
# Simulate health check logic
|
|
442
|
-
if proxy.metrics.consecutive_failures >= 5:
|
|
443
|
-
proxy.metrics.health_status = ProxyHealthStatus.BLOCKED
|
|
444
|
-
elif proxy.metrics.consecutive_failures >= 3:
|
|
445
|
-
proxy.metrics.health_status = ProxyHealthStatus.FAILING
|
|
446
|
-
elif proxy.metrics.success_rate < 50:
|
|
447
|
-
proxy.metrics.health_status = ProxyHealthStatus.DEGRADED
|
|
448
|
-
else:
|
|
449
|
-
proxy.metrics.health_status = ProxyHealthStatus.HEALTHY
|
|
450
|
-
|
|
451
|
-
@track_development_operation("Proxy Selection", SDKEventType.PROXY_ALLOCATED)
|
|
452
|
-
def get_proxy(self, region: Optional[str] = None) -> Optional[ManagedProxy]:
|
|
453
|
-
"""
|
|
454
|
-
Get next proxy for use based on rotation strategy.
|
|
455
|
-
|
|
456
|
-
Args:
|
|
457
|
-
region: Optional region preference for geographic routing
|
|
458
|
-
|
|
459
|
-
Returns:
|
|
460
|
-
ManagedProxy if available, None if no proxies available
|
|
461
|
-
"""
|
|
462
|
-
if not self.proxy_config.enabled:
|
|
463
|
-
if self.dev_logger:
|
|
464
|
-
self.dev_logger.log_warning(
|
|
465
|
-
SDKEventType.PROXY_ALLOCATED,
|
|
466
|
-
"Proxy allocation requested but proxy management is disabled",
|
|
467
|
-
context=SDKContext(component_name="ProxyManager"),
|
|
468
|
-
)
|
|
469
|
-
return None
|
|
470
|
-
|
|
471
|
-
proxy = self.pool.select_proxy(region)
|
|
472
|
-
if proxy:
|
|
473
|
-
# Update last used timestamp
|
|
474
|
-
proxy.usage_stats.last_used_at = datetime.now(timezone.utc).isoformat()
|
|
475
|
-
self.logger.debug(f"Selected proxy {proxy.identifier} for use")
|
|
476
|
-
|
|
477
|
-
if self.dev_logger:
|
|
478
|
-
self.dev_logger.log_info(
|
|
479
|
-
SDKEventType.PROXY_ALLOCATED,
|
|
480
|
-
f"Proxy allocated: {proxy.identifier}",
|
|
481
|
-
context=SDKContext(
|
|
482
|
-
component_name="ProxyManager",
|
|
483
|
-
metadata={
|
|
484
|
-
"proxy_id": proxy.proxy_id,
|
|
485
|
-
"region": proxy.region,
|
|
486
|
-
"country": proxy.country,
|
|
487
|
-
"provider": (
|
|
488
|
-
proxy.provider.value
|
|
489
|
-
if hasattr(proxy.provider, "value")
|
|
490
|
-
else str(proxy.provider)
|
|
491
|
-
),
|
|
492
|
-
"success_rate": proxy.usage_stats.success_rate,
|
|
493
|
-
"requested_region": region,
|
|
494
|
-
},
|
|
495
|
-
),
|
|
496
|
-
)
|
|
497
|
-
else:
|
|
498
|
-
if self.dev_logger:
|
|
499
|
-
self.dev_logger.log_error(
|
|
500
|
-
SDKEventType.PROXY_ALLOCATED,
|
|
501
|
-
f"No available proxies for region: {region or 'any'}",
|
|
502
|
-
context=SDKContext(
|
|
503
|
-
component_name="ProxyManager",
|
|
504
|
-
metadata={
|
|
505
|
-
"requested_region": region,
|
|
506
|
-
"total_proxies": len(self.pool.proxies),
|
|
507
|
-
"available_proxies": len(self.pool.get_available_proxies()),
|
|
508
|
-
},
|
|
509
|
-
),
|
|
510
|
-
)
|
|
511
|
-
|
|
512
|
-
return proxy
|
|
513
|
-
|
|
514
|
-
async def record_success(self, proxy: ManagedProxy, response_time_ms: float) -> None:
|
|
515
|
-
"""Record successful proxy usage and sync with API."""
|
|
516
|
-
# Update local stats
|
|
517
|
-
proxy.usage_stats.total_requests += 1
|
|
518
|
-
proxy.usage_stats.successful_requests += 1
|
|
519
|
-
proxy.usage_stats.consecutive_failures = 0
|
|
520
|
-
|
|
521
|
-
# Update rolling average response time
|
|
522
|
-
if proxy.usage_stats.avg_response_time_ms == 0:
|
|
523
|
-
proxy.usage_stats.avg_response_time_ms = response_time_ms
|
|
524
|
-
else:
|
|
525
|
-
# Simple moving average
|
|
526
|
-
proxy.usage_stats.avg_response_time_ms = (
|
|
527
|
-
proxy.usage_stats.avg_response_time_ms * 0.8 + response_time_ms * 0.2
|
|
528
|
-
)
|
|
529
|
-
|
|
530
|
-
proxy.usage_stats.last_used_at = datetime.now(timezone.utc).isoformat()
|
|
531
|
-
|
|
532
|
-
# Update global stats
|
|
533
|
-
self._successful_requests += 1
|
|
534
|
-
self._total_requests += 1
|
|
535
|
-
|
|
536
|
-
# Sync with API (non-blocking)
|
|
537
|
-
if self._http_client:
|
|
538
|
-
try:
|
|
539
|
-
# Use auto-generated API service
|
|
540
|
-
from unrealon_sdk.src.clients.python_http.models.ProxyUsageRequest import (
|
|
541
|
-
ProxyUsageRequest,
|
|
542
|
-
)
|
|
543
|
-
|
|
544
|
-
usage_request = ProxyUsageRequest(
|
|
545
|
-
successful=True,
|
|
546
|
-
response_time_ms=response_time_ms,
|
|
547
|
-
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
548
|
-
)
|
|
549
|
-
|
|
550
|
-
await record_proxy_usage_api_v1_proxies__proxy_id__usage_post(
|
|
551
|
-
proxy_id=proxy.proxy_id,
|
|
552
|
-
body=usage_request,
|
|
553
|
-
api_config_override=self._http_client._api_config,
|
|
554
|
-
)
|
|
555
|
-
except Exception as e:
|
|
556
|
-
self.logger.warning(f"Failed to sync success with API: {e}")
|
|
557
|
-
|
|
558
|
-
self.logger.debug(
|
|
559
|
-
f"Recorded success for proxy {proxy.identifier} ({response_time_ms:.2f}ms)"
|
|
560
|
-
)
|
|
561
|
-
|
|
562
|
-
if self.dev_logger:
|
|
563
|
-
self.dev_logger.log_performance_metric(
|
|
564
|
-
"proxy_response_time",
|
|
565
|
-
response_time_ms,
|
|
566
|
-
"ms",
|
|
567
|
-
threshold=5000.0, # 5 second threshold
|
|
568
|
-
context=SDKContext(
|
|
569
|
-
component_name="ProxyManager",
|
|
570
|
-
metadata={
|
|
571
|
-
"proxy_id": proxy.proxy_id,
|
|
572
|
-
"proxy_identifier": proxy.identifier,
|
|
573
|
-
"success_rate": proxy.usage_stats.success_rate,
|
|
574
|
-
"total_requests": proxy.usage_stats.total_requests,
|
|
575
|
-
},
|
|
576
|
-
),
|
|
577
|
-
)
|
|
578
|
-
|
|
579
|
-
async def record_failure(self, proxy: ManagedProxy, error: str) -> None:
|
|
580
|
-
"""Record failed proxy usage and sync with API."""
|
|
581
|
-
# Update local stats
|
|
582
|
-
proxy.usage_stats.total_requests += 1
|
|
583
|
-
proxy.usage_stats.failed_requests += 1
|
|
584
|
-
proxy.usage_stats.consecutive_failures += 1
|
|
585
|
-
proxy.usage_stats.last_used_at = datetime.now(timezone.utc).isoformat()
|
|
586
|
-
|
|
587
|
-
# Update global stats
|
|
588
|
-
self._failed_requests += 1
|
|
589
|
-
self._total_requests += 1
|
|
590
|
-
|
|
591
|
-
# Auto-blacklist if too many consecutive failures
|
|
592
|
-
if proxy.usage_stats.consecutive_failures >= 5:
|
|
593
|
-
self.pool.blacklist_proxy(proxy.identifier, duration_minutes=60)
|
|
594
|
-
|
|
595
|
-
# Report blocked proxy to API
|
|
596
|
-
if self._http_client:
|
|
597
|
-
try:
|
|
598
|
-
block_request = ProxyBlockRequest(
|
|
599
|
-
proxy_id=proxy.proxy_id,
|
|
600
|
-
reason=f"Consecutive failures: {error}",
|
|
601
|
-
block_duration_minutes=60,
|
|
602
|
-
)
|
|
603
|
-
|
|
604
|
-
await report_blocked_proxy_api_v1_proxies_rotation_block_post(
|
|
605
|
-
body=block_request, api_config_override=self._http_client._api_config
|
|
606
|
-
)
|
|
607
|
-
except Exception as e:
|
|
608
|
-
self.logger.warning(f"Failed to report blocked proxy to API: {e}")
|
|
609
|
-
|
|
610
|
-
# Sync usage with API (non-blocking)
|
|
611
|
-
if self._http_client:
|
|
612
|
-
try:
|
|
613
|
-
from unrealon_sdk.src.clients.python_http.models.ProxyUsageRequest import (
|
|
614
|
-
ProxyUsageRequest,
|
|
615
|
-
)
|
|
616
|
-
|
|
617
|
-
usage_request = ProxyUsageRequest(
|
|
618
|
-
successful=False,
|
|
619
|
-
error_message=error,
|
|
620
|
-
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
621
|
-
)
|
|
622
|
-
|
|
623
|
-
await record_proxy_usage_api_v1_proxies__proxy_id__usage_post(
|
|
624
|
-
proxy_id=proxy.proxy_id,
|
|
625
|
-
body=usage_request,
|
|
626
|
-
api_config_override=self._http_client._api_config,
|
|
627
|
-
)
|
|
628
|
-
except Exception as e:
|
|
629
|
-
self.logger.warning(f"Failed to sync failure with API: {e}")
|
|
630
|
-
|
|
631
|
-
self.logger.warning(f"Recorded failure for proxy {proxy.identifier}: {error}")
|
|
632
|
-
|
|
633
|
-
def get_statistics(self) -> ProxyStatistics:
|
|
634
|
-
"""Get comprehensive proxy usage statistics using auto-generated model."""
|
|
635
|
-
available_proxies = self.pool.get_available_proxies()
|
|
636
|
-
blacklisted_count = len([p for p in self.pool.proxies.values() if p.is_blacklisted])
|
|
637
|
-
|
|
638
|
-
# Use the auto-generated ProxyStatistics model
|
|
639
|
-
return ProxyStatistics(
|
|
640
|
-
success=True,
|
|
641
|
-
message="Proxy statistics retrieved successfully",
|
|
642
|
-
data=None,
|
|
643
|
-
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
644
|
-
request_id=generate_correlation_id(),
|
|
645
|
-
total_proxies=len(self.pool.proxies),
|
|
646
|
-
active_proxies=len(available_proxies),
|
|
647
|
-
inactive_proxies=len(self.pool.proxies) - len(available_proxies),
|
|
648
|
-
blocked_proxies=blacklisted_count,
|
|
649
|
-
expired_proxies=0, # TODO: Calculate from expiry dates
|
|
650
|
-
error_proxies=0, # TODO: Calculate from error states
|
|
651
|
-
healthy_proxies=len([p for p in available_proxies if p.usage_stats.is_healthy]),
|
|
652
|
-
degraded_proxies=len([p for p in available_proxies if not p.usage_stats.is_healthy]),
|
|
653
|
-
critical_proxies=blacklisted_count,
|
|
654
|
-
provider_breakdown={}, # TODO: Calculate provider stats
|
|
655
|
-
country_breakdown={}, # TODO: Calculate country stats
|
|
656
|
-
overall_success_rate=(
|
|
657
|
-
self._successful_requests / self._total_requests * 100
|
|
658
|
-
if self._total_requests > 0
|
|
659
|
-
else 0.0
|
|
660
|
-
),
|
|
661
|
-
avg_response_time_ms=0.0, # TODO: Calculate average from all proxies
|
|
662
|
-
total_requests_last_24h=self._total_requests, # Simplified for now
|
|
663
|
-
successful_requests_last_24h=self._successful_requests,
|
|
664
|
-
expiring_in_24h=0, # TODO: Calculate from expiry dates
|
|
665
|
-
expiring_in_7d=0,
|
|
666
|
-
expiring_in_30d=0,
|
|
667
|
-
most_used_providers=[], # TODO: Calculate top providers
|
|
668
|
-
most_active_countries=[], # TODO: Calculate top countries
|
|
669
|
-
proxies_added_last_24h=0, # TODO: Track new additions
|
|
670
|
-
proxies_blocked_last_24h=blacklisted_count,
|
|
671
|
-
generated_at=datetime.now(timezone.utc).isoformat(),
|
|
672
|
-
)
|
|
673
|
-
|
|
674
|
-
async def rotate_proxies(self, provider: Optional[str] = None) -> int:
|
|
675
|
-
"""
|
|
676
|
-
Force rotation of proxies from provider.
|
|
677
|
-
|
|
678
|
-
Args:
|
|
679
|
-
provider: Optional provider to rotate, None for all providers
|
|
680
|
-
|
|
681
|
-
Returns:
|
|
682
|
-
Number of proxies rotated
|
|
683
|
-
"""
|
|
684
|
-
rotated = 0
|
|
685
|
-
|
|
686
|
-
for proxy_id in list(self.pool.proxies.keys()):
|
|
687
|
-
proxy = self.pool.proxies[proxy_id]
|
|
688
|
-
if provider is None or proxy.provider == provider:
|
|
689
|
-
self.pool.remove_proxy(proxy_id)
|
|
690
|
-
rotated += 1
|
|
691
|
-
|
|
692
|
-
# Reload proxies from providers
|
|
693
|
-
await self._load_initial_proxies()
|
|
694
|
-
|
|
695
|
-
self.logger.info(f"Rotated {rotated} proxies")
|
|
696
|
-
return rotated
|