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
|
@@ -1,652 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Rate Limiter - Layer 3 Infrastructure Service
|
|
3
|
-
|
|
4
|
-
Enterprise-grade request throttling system with intelligent backoff strategies,
|
|
5
|
-
adaptive rate limiting, and comprehensive analytics. Provides protection against
|
|
6
|
-
API rate limits and ensures optimal request distribution.
|
|
7
|
-
|
|
8
|
-
Features:
|
|
9
|
-
- Multiple rate limiting strategies (Token Bucket, Sliding Window, etc.)
|
|
10
|
-
- Intelligent backoff algorithms (Exponential, Fibonacci, Jittered)
|
|
11
|
-
- Adaptive rate limiting based on system load
|
|
12
|
-
- Request queuing with timeout management
|
|
13
|
-
- Per-scope rate limiting (global, user, API key, endpoint)
|
|
14
|
-
- Real-time metrics and analytics
|
|
15
|
-
- Circuit breaker integration
|
|
16
|
-
- Thread-safe operations for concurrent access
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
import asyncio
|
|
20
|
-
import logging
|
|
21
|
-
import time
|
|
22
|
-
import threading
|
|
23
|
-
import random
|
|
24
|
-
import math
|
|
25
|
-
from typing import Dict, List, Optional, Any, Callable, Union
|
|
26
|
-
from datetime import datetime, timezone, timedelta
|
|
27
|
-
from collections import defaultdict, deque
|
|
28
|
-
import weakref
|
|
29
|
-
|
|
30
|
-
# Core SDK components
|
|
31
|
-
from unrealon_sdk.src.core.config import AdapterConfig
|
|
32
|
-
from unrealon_sdk.src.utils import generate_correlation_id
|
|
33
|
-
|
|
34
|
-
# DTO models
|
|
35
|
-
from unrealon_sdk.src.dto.logging import SDKEventType, SDKSeverity
|
|
36
|
-
from unrealon_sdk.src.dto.rate_limiting import (
|
|
37
|
-
RateLimitStrategy,
|
|
38
|
-
BackoffStrategy,
|
|
39
|
-
RateLimitScope,
|
|
40
|
-
RateLimitStatus,
|
|
41
|
-
RateLimitEventType,
|
|
42
|
-
RateLimitConfig,
|
|
43
|
-
RateLimitQuota,
|
|
44
|
-
RateLimitRequest,
|
|
45
|
-
RateLimitStatistics,
|
|
46
|
-
BackoffState,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
# Development logging
|
|
50
|
-
from typing import TYPE_CHECKING
|
|
51
|
-
|
|
52
|
-
if TYPE_CHECKING:
|
|
53
|
-
from unrealon_sdk.src.enterprise.logging import DevelopmentLogger
|
|
54
|
-
|
|
55
|
-
logger = logging.getLogger(__name__)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class RateLimiter:
|
|
59
|
-
"""
|
|
60
|
-
Enterprise-grade rate limiter with intelligent throttling and backoff.
|
|
61
|
-
|
|
62
|
-
Provides comprehensive request throttling with multiple strategies,
|
|
63
|
-
adaptive rate limiting, and detailed analytics for UnrealOn SDK operations.
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
def __init__(
|
|
67
|
-
self,
|
|
68
|
-
config: AdapterConfig,
|
|
69
|
-
rate_config: Optional[RateLimitConfig] = None,
|
|
70
|
-
dev_logger: Optional["DevelopmentLogger"] = None,
|
|
71
|
-
):
|
|
72
|
-
"""Initialize rate limiter."""
|
|
73
|
-
self.config = config
|
|
74
|
-
self.rate_config = rate_config or RateLimitConfig()
|
|
75
|
-
self.dev_logger = dev_logger
|
|
76
|
-
|
|
77
|
-
# Thread safety
|
|
78
|
-
self._lock = threading.RLock()
|
|
79
|
-
|
|
80
|
-
# Quota tracking per scope
|
|
81
|
-
self._quotas: Dict[str, RateLimitQuota] = {}
|
|
82
|
-
|
|
83
|
-
# Backoff state tracking
|
|
84
|
-
self._backoff_states: Dict[str, BackoffState] = {}
|
|
85
|
-
|
|
86
|
-
# Request queue
|
|
87
|
-
self._request_queue: deque[RateLimitRequest] = deque()
|
|
88
|
-
self._queue_condition = threading.Condition(self._lock)
|
|
89
|
-
|
|
90
|
-
# Statistics
|
|
91
|
-
self.statistics = RateLimitStatistics()
|
|
92
|
-
|
|
93
|
-
# Token bucket state (for TOKEN_BUCKET strategy)
|
|
94
|
-
self._token_buckets: Dict[str, Dict[str, Any]] = defaultdict(dict)
|
|
95
|
-
|
|
96
|
-
# Sliding window state (for SLIDING_WINDOW strategy)
|
|
97
|
-
self._sliding_windows: Dict[str, deque[datetime]] = defaultdict(deque)
|
|
98
|
-
|
|
99
|
-
# Background tasks
|
|
100
|
-
self._cleanup_task: Optional[asyncio.Task[None]] = None
|
|
101
|
-
self._queue_processor_task: Optional[asyncio.Task[None]] = None
|
|
102
|
-
self._metrics_task: Optional[asyncio.Task[None]] = None
|
|
103
|
-
self._shutdown = False
|
|
104
|
-
|
|
105
|
-
# System load tracking for adaptive limiting
|
|
106
|
-
self._system_load = 0.0
|
|
107
|
-
self._load_samples: deque[float] = deque(maxlen=100)
|
|
108
|
-
|
|
109
|
-
self._log_info("Rate limiter initialized")
|
|
110
|
-
|
|
111
|
-
async def start(self) -> None:
|
|
112
|
-
"""Start rate limiter background tasks."""
|
|
113
|
-
if self._cleanup_task is None:
|
|
114
|
-
self._cleanup_task = asyncio.create_task(self._cleanup_loop())
|
|
115
|
-
|
|
116
|
-
if self._queue_processor_task is None:
|
|
117
|
-
self._queue_processor_task = asyncio.create_task(self._process_queue_loop())
|
|
118
|
-
|
|
119
|
-
if self._metrics_task is None and self.rate_config.enable_metrics:
|
|
120
|
-
self._metrics_task = asyncio.create_task(self._metrics_loop())
|
|
121
|
-
|
|
122
|
-
self._log_info("Rate limiter started")
|
|
123
|
-
|
|
124
|
-
async def stop(self) -> None:
|
|
125
|
-
"""Stop rate limiter and cleanup."""
|
|
126
|
-
self._shutdown = True
|
|
127
|
-
|
|
128
|
-
# Cancel background tasks
|
|
129
|
-
for task in [self._cleanup_task, self._queue_processor_task, self._metrics_task]:
|
|
130
|
-
if task:
|
|
131
|
-
task.cancel()
|
|
132
|
-
try:
|
|
133
|
-
await task
|
|
134
|
-
except asyncio.CancelledError:
|
|
135
|
-
pass
|
|
136
|
-
|
|
137
|
-
# Wake up any waiting threads
|
|
138
|
-
with self._queue_condition:
|
|
139
|
-
self._queue_condition.notify_all()
|
|
140
|
-
|
|
141
|
-
self._log_info("Rate limiter stopped")
|
|
142
|
-
|
|
143
|
-
async def check_rate_limit(
|
|
144
|
-
self,
|
|
145
|
-
scope_id: str,
|
|
146
|
-
operation_type: str,
|
|
147
|
-
scope_type: RateLimitScope = None,
|
|
148
|
-
) -> RateLimitRequest:
|
|
149
|
-
"""
|
|
150
|
-
Check if request is allowed by rate limiter.
|
|
151
|
-
|
|
152
|
-
Returns RateLimitRequest with decision and metadata.
|
|
153
|
-
"""
|
|
154
|
-
if scope_type is None:
|
|
155
|
-
scope_type = self.rate_config.scope
|
|
156
|
-
|
|
157
|
-
request_id = generate_correlation_id()
|
|
158
|
-
start_time = time.time()
|
|
159
|
-
|
|
160
|
-
try:
|
|
161
|
-
with self._lock:
|
|
162
|
-
# Create request tracking
|
|
163
|
-
request = RateLimitRequest(
|
|
164
|
-
request_id=request_id,
|
|
165
|
-
scope_id=scope_id,
|
|
166
|
-
scope_type=scope_type,
|
|
167
|
-
operation_type=operation_type,
|
|
168
|
-
quota_reset_time=datetime.now(timezone.utc) + timedelta(hours=1), # Default
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
# Get or create quota for this scope
|
|
172
|
-
quota_key = f"{scope_type.value}:{scope_id}"
|
|
173
|
-
quota = self._get_or_create_quota(quota_key, scope_id, scope_type)
|
|
174
|
-
|
|
175
|
-
# Check backoff state
|
|
176
|
-
backoff_key = f"{scope_id}:{operation_type}"
|
|
177
|
-
if backoff_key in self._backoff_states:
|
|
178
|
-
backoff_state = self._backoff_states[backoff_key]
|
|
179
|
-
if not backoff_state.can_retry():
|
|
180
|
-
request.status = RateLimitStatus.THROTTLED
|
|
181
|
-
request.retry_after_seconds = backoff_state.time_until_retry().total_seconds()
|
|
182
|
-
request.backoff_count = backoff_state.attempt_count
|
|
183
|
-
self._update_statistics_for_request(request)
|
|
184
|
-
return request
|
|
185
|
-
|
|
186
|
-
# Apply rate limiting strategy
|
|
187
|
-
decision = self._apply_rate_limiting_strategy(quota, request)
|
|
188
|
-
|
|
189
|
-
# Handle decision
|
|
190
|
-
if decision == RateLimitStatus.ALLOWED:
|
|
191
|
-
self._handle_allowed_request(quota, request)
|
|
192
|
-
elif decision == RateLimitStatus.THROTTLED:
|
|
193
|
-
self._handle_throttled_request(quota, request, backoff_key)
|
|
194
|
-
else: # REJECTED
|
|
195
|
-
self._handle_rejected_request(quota, request, backoff_key)
|
|
196
|
-
|
|
197
|
-
# Update statistics
|
|
198
|
-
self._update_statistics_for_request(request)
|
|
199
|
-
|
|
200
|
-
# Track processing time
|
|
201
|
-
processing_time = (time.time() - start_time) * 1000
|
|
202
|
-
request.processing_time_ms = processing_time
|
|
203
|
-
|
|
204
|
-
return request
|
|
205
|
-
|
|
206
|
-
except Exception as e:
|
|
207
|
-
logger.error(f"Error in rate limit check: {e}")
|
|
208
|
-
# Return permissive decision on error
|
|
209
|
-
return RateLimitRequest(
|
|
210
|
-
request_id=request_id,
|
|
211
|
-
scope_id=scope_id,
|
|
212
|
-
scope_type=scope_type,
|
|
213
|
-
operation_type=operation_type,
|
|
214
|
-
status=RateLimitStatus.ALLOWED,
|
|
215
|
-
quota_reset_time=datetime.now(timezone.utc) + timedelta(hours=1),
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
def _get_or_create_quota(
|
|
219
|
-
self, quota_key: str, scope_id: str, scope_type: RateLimitScope
|
|
220
|
-
) -> RateLimitQuota:
|
|
221
|
-
"""Get or create quota for scope."""
|
|
222
|
-
if quota_key in self._quotas:
|
|
223
|
-
quota = self._quotas[quota_key]
|
|
224
|
-
# Reset if expired
|
|
225
|
-
if quota.is_expired():
|
|
226
|
-
quota = self._create_new_quota(scope_id, scope_type)
|
|
227
|
-
self._quotas[quota_key] = quota
|
|
228
|
-
else:
|
|
229
|
-
quota = self._create_new_quota(scope_id, scope_type)
|
|
230
|
-
self._quotas[quota_key] = quota
|
|
231
|
-
|
|
232
|
-
return quota
|
|
233
|
-
|
|
234
|
-
def _create_new_quota(self, scope_id: str, scope_type: RateLimitScope) -> RateLimitQuota:
|
|
235
|
-
"""Create new quota for scope."""
|
|
236
|
-
now = datetime.now(timezone.utc)
|
|
237
|
-
|
|
238
|
-
# Determine quota limits based on scope type
|
|
239
|
-
if scope_type == RateLimitScope.GLOBAL:
|
|
240
|
-
limit = int(self.rate_config.requests_per_hour)
|
|
241
|
-
elif scope_type == RateLimitScope.USER:
|
|
242
|
-
limit = int(self.rate_config.requests_per_hour * 0.1) # 10% of global
|
|
243
|
-
elif scope_type == RateLimitScope.API_KEY:
|
|
244
|
-
limit = int(self.rate_config.requests_per_hour * 0.5) # 50% of global
|
|
245
|
-
else:
|
|
246
|
-
limit = int(self.rate_config.requests_per_hour * 0.2) # 20% of global
|
|
247
|
-
|
|
248
|
-
# Apply adaptive adjustment
|
|
249
|
-
if self.rate_config.adaptive_enabled:
|
|
250
|
-
adaptive_multiplier = self._calculate_adaptive_multiplier()
|
|
251
|
-
limit = int(limit * adaptive_multiplier)
|
|
252
|
-
|
|
253
|
-
return RateLimitQuota(
|
|
254
|
-
scope_id=scope_id,
|
|
255
|
-
scope_type=scope_type,
|
|
256
|
-
remaining_requests=limit,
|
|
257
|
-
requests_limit=limit,
|
|
258
|
-
reset_time=now + timedelta(hours=1),
|
|
259
|
-
window_start=now,
|
|
260
|
-
window_end=now + timedelta(hours=1),
|
|
261
|
-
tokens_available=float(self.rate_config.bucket_capacity),
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
def _apply_rate_limiting_strategy(
|
|
265
|
-
self, quota: RateLimitQuota, request: RateLimitRequest
|
|
266
|
-
) -> RateLimitStatus:
|
|
267
|
-
"""Apply configured rate limiting strategy."""
|
|
268
|
-
strategy = self.rate_config.strategy
|
|
269
|
-
|
|
270
|
-
if strategy == RateLimitStrategy.TOKEN_BUCKET:
|
|
271
|
-
return self._apply_token_bucket(quota, request)
|
|
272
|
-
elif strategy == RateLimitStrategy.SLIDING_WINDOW:
|
|
273
|
-
return self._apply_sliding_window(quota, request)
|
|
274
|
-
elif strategy == RateLimitStrategy.FIXED_WINDOW:
|
|
275
|
-
return self._apply_fixed_window(quota, request)
|
|
276
|
-
else:
|
|
277
|
-
# Default to simple counter
|
|
278
|
-
return self._apply_simple_counter(quota, request)
|
|
279
|
-
|
|
280
|
-
def _apply_token_bucket(self, quota: RateLimitQuota, request: RateLimitRequest) -> RateLimitStatus:
|
|
281
|
-
"""Apply token bucket rate limiting."""
|
|
282
|
-
now = datetime.now(timezone.utc)
|
|
283
|
-
|
|
284
|
-
# Refill tokens based on elapsed time
|
|
285
|
-
time_elapsed = (now - quota.last_refill).total_seconds()
|
|
286
|
-
tokens_to_add = time_elapsed * self.rate_config.refill_rate
|
|
287
|
-
quota.tokens_available = min(
|
|
288
|
-
self.rate_config.bucket_capacity,
|
|
289
|
-
quota.tokens_available + tokens_to_add
|
|
290
|
-
)
|
|
291
|
-
quota.last_refill = now
|
|
292
|
-
|
|
293
|
-
# Check if token available
|
|
294
|
-
if quota.tokens_available >= 1.0:
|
|
295
|
-
quota.tokens_available -= 1.0
|
|
296
|
-
return RateLimitStatus.ALLOWED
|
|
297
|
-
else:
|
|
298
|
-
# Calculate retry after time
|
|
299
|
-
tokens_needed = 1.0 - quota.tokens_available
|
|
300
|
-
retry_after = tokens_needed / self.rate_config.refill_rate
|
|
301
|
-
request.retry_after_seconds = retry_after
|
|
302
|
-
|
|
303
|
-
if self.rate_config.queue_enabled:
|
|
304
|
-
return RateLimitStatus.THROTTLED
|
|
305
|
-
else:
|
|
306
|
-
return RateLimitStatus.REJECTED
|
|
307
|
-
|
|
308
|
-
def _apply_sliding_window(self, quota: RateLimitQuota, request: RateLimitRequest) -> RateLimitStatus:
|
|
309
|
-
"""Apply sliding window rate limiting."""
|
|
310
|
-
now = datetime.now(timezone.utc)
|
|
311
|
-
window_start = now - timedelta(seconds=self.rate_config.window_size_seconds)
|
|
312
|
-
|
|
313
|
-
# Get window for this scope
|
|
314
|
-
scope_key = f"{request.scope_type.value}:{request.scope_id}"
|
|
315
|
-
window = self._sliding_windows[scope_key]
|
|
316
|
-
|
|
317
|
-
# Remove old entries
|
|
318
|
-
while window and window[0] < window_start:
|
|
319
|
-
window.popleft()
|
|
320
|
-
|
|
321
|
-
# Check if under limit
|
|
322
|
-
if len(window) < quota.requests_limit:
|
|
323
|
-
window.append(now)
|
|
324
|
-
return RateLimitStatus.ALLOWED
|
|
325
|
-
else:
|
|
326
|
-
# Calculate retry after time
|
|
327
|
-
oldest_request = window[0]
|
|
328
|
-
retry_after = (oldest_request + timedelta(seconds=self.rate_config.window_size_seconds) - now).total_seconds()
|
|
329
|
-
request.retry_after_seconds = max(1.0, retry_after)
|
|
330
|
-
|
|
331
|
-
if self.rate_config.queue_enabled:
|
|
332
|
-
return RateLimitStatus.THROTTLED
|
|
333
|
-
else:
|
|
334
|
-
return RateLimitStatus.REJECTED
|
|
335
|
-
|
|
336
|
-
def _apply_fixed_window(self, quota: RateLimitQuota, request: RateLimitRequest) -> RateLimitStatus:
|
|
337
|
-
"""Apply fixed window rate limiting."""
|
|
338
|
-
if quota.remaining_requests > 0:
|
|
339
|
-
return RateLimitStatus.ALLOWED
|
|
340
|
-
else:
|
|
341
|
-
retry_after = quota.time_until_reset().total_seconds()
|
|
342
|
-
request.retry_after_seconds = retry_after
|
|
343
|
-
|
|
344
|
-
if self.rate_config.queue_enabled:
|
|
345
|
-
return RateLimitStatus.THROTTLED
|
|
346
|
-
else:
|
|
347
|
-
return RateLimitStatus.REJECTED
|
|
348
|
-
|
|
349
|
-
def _apply_simple_counter(self, quota: RateLimitQuota, request: RateLimitRequest) -> RateLimitStatus:
|
|
350
|
-
"""Apply simple counter-based rate limiting."""
|
|
351
|
-
return self._apply_fixed_window(quota, request)
|
|
352
|
-
|
|
353
|
-
def _handle_allowed_request(self, quota: RateLimitQuota, request: RateLimitRequest) -> None:
|
|
354
|
-
"""Handle allowed request."""
|
|
355
|
-
quota.current_requests += 1
|
|
356
|
-
quota.remaining_requests = max(0, quota.remaining_requests - 1)
|
|
357
|
-
quota.total_requests += 1
|
|
358
|
-
|
|
359
|
-
request.status = RateLimitStatus.ALLOWED
|
|
360
|
-
request.quota_remaining = quota.remaining_requests
|
|
361
|
-
request.quota_reset_time = quota.reset_time
|
|
362
|
-
|
|
363
|
-
self._log_rate_limit_event(RateLimitEventType.REQUEST_ALLOWED, request)
|
|
364
|
-
|
|
365
|
-
def _handle_throttled_request(
|
|
366
|
-
self, quota: RateLimitQuota, request: RateLimitRequest, backoff_key: str
|
|
367
|
-
) -> None:
|
|
368
|
-
"""Handle throttled request."""
|
|
369
|
-
quota.throttled_requests += 1
|
|
370
|
-
request.status = RateLimitStatus.THROTTLED
|
|
371
|
-
request.quota_remaining = quota.remaining_requests
|
|
372
|
-
request.quota_reset_time = quota.reset_time
|
|
373
|
-
|
|
374
|
-
# Apply backoff
|
|
375
|
-
self._apply_backoff(backoff_key, request)
|
|
376
|
-
|
|
377
|
-
# Add to queue if enabled
|
|
378
|
-
if self.rate_config.queue_enabled and len(self._request_queue) < self.rate_config.max_queue_size:
|
|
379
|
-
request.queue_position = len(self._request_queue) + 1
|
|
380
|
-
self._request_queue.append(request)
|
|
381
|
-
|
|
382
|
-
self._log_rate_limit_event(RateLimitEventType.REQUEST_THROTTLED, request)
|
|
383
|
-
|
|
384
|
-
def _handle_rejected_request(
|
|
385
|
-
self, quota: RateLimitQuota, request: RateLimitRequest, backoff_key: str
|
|
386
|
-
) -> None:
|
|
387
|
-
"""Handle rejected request."""
|
|
388
|
-
quota.rejected_requests += 1
|
|
389
|
-
request.status = RateLimitStatus.REJECTED
|
|
390
|
-
request.quota_remaining = quota.remaining_requests
|
|
391
|
-
request.quota_reset_time = quota.reset_time
|
|
392
|
-
|
|
393
|
-
# Apply backoff
|
|
394
|
-
self._apply_backoff(backoff_key, request)
|
|
395
|
-
|
|
396
|
-
self._log_rate_limit_event(RateLimitEventType.REQUEST_REJECTED, request)
|
|
397
|
-
|
|
398
|
-
def _apply_backoff(self, backoff_key: str, request: RateLimitRequest) -> None:
|
|
399
|
-
"""Apply backoff strategy."""
|
|
400
|
-
if backoff_key not in self._backoff_states:
|
|
401
|
-
self._backoff_states[backoff_key] = BackoffState(
|
|
402
|
-
scope_id=request.scope_id,
|
|
403
|
-
operation_type=request.operation_type,
|
|
404
|
-
strategy=self.rate_config.backoff_strategy,
|
|
405
|
-
base_delay=self.rate_config.base_delay_seconds,
|
|
406
|
-
max_delay=self.rate_config.max_delay_seconds,
|
|
407
|
-
multiplier=self.rate_config.backoff_multiplier,
|
|
408
|
-
next_retry_time=datetime.now(timezone.utc),
|
|
409
|
-
)
|
|
410
|
-
|
|
411
|
-
backoff_state = self._backoff_states[backoff_key]
|
|
412
|
-
backoff_state.attempt_count += 1
|
|
413
|
-
backoff_state.retry_history.append(datetime.now(timezone.utc))
|
|
414
|
-
|
|
415
|
-
# Calculate delay based on strategy
|
|
416
|
-
delay = self._calculate_backoff_delay(backoff_state)
|
|
417
|
-
|
|
418
|
-
# Apply jitter if enabled
|
|
419
|
-
if self.rate_config.jitter_enabled:
|
|
420
|
-
jitter = random.uniform(0.8, 1.2)
|
|
421
|
-
delay *= jitter
|
|
422
|
-
|
|
423
|
-
# Clamp to max delay
|
|
424
|
-
delay = min(delay, self.rate_config.max_delay_seconds)
|
|
425
|
-
|
|
426
|
-
backoff_state.current_delay_seconds = delay
|
|
427
|
-
backoff_state.next_retry_time = datetime.now(timezone.utc) + timedelta(seconds=delay)
|
|
428
|
-
|
|
429
|
-
request.retry_after_seconds = delay
|
|
430
|
-
request.backoff_count = backoff_state.attempt_count
|
|
431
|
-
|
|
432
|
-
def _calculate_backoff_delay(self, backoff_state: BackoffState) -> float:
|
|
433
|
-
"""Calculate backoff delay based on strategy."""
|
|
434
|
-
strategy = backoff_state.strategy
|
|
435
|
-
attempt = backoff_state.attempt_count
|
|
436
|
-
base_delay = backoff_state.base_delay
|
|
437
|
-
|
|
438
|
-
if strategy == BackoffStrategy.LINEAR:
|
|
439
|
-
return base_delay * attempt
|
|
440
|
-
elif strategy == BackoffStrategy.EXPONENTIAL:
|
|
441
|
-
return base_delay * (backoff_state.multiplier ** (attempt - 1))
|
|
442
|
-
elif strategy == BackoffStrategy.FIBONACCI:
|
|
443
|
-
return base_delay * self._fibonacci(attempt)
|
|
444
|
-
elif strategy == BackoffStrategy.JITTERED:
|
|
445
|
-
base = base_delay * (backoff_state.multiplier ** (attempt - 1))
|
|
446
|
-
return base * random.uniform(0.5, 1.5)
|
|
447
|
-
else:
|
|
448
|
-
return base_delay
|
|
449
|
-
|
|
450
|
-
def _fibonacci(self, n: int) -> int:
|
|
451
|
-
"""Calculate fibonacci number."""
|
|
452
|
-
if n <= 2:
|
|
453
|
-
return 1
|
|
454
|
-
a, b = 1, 1
|
|
455
|
-
for _ in range(3, n + 1):
|
|
456
|
-
a, b = b, a + b
|
|
457
|
-
return b
|
|
458
|
-
|
|
459
|
-
def _calculate_adaptive_multiplier(self) -> float:
|
|
460
|
-
"""Calculate adaptive rate multiplier based on system load."""
|
|
461
|
-
if not self.rate_config.adaptive_enabled:
|
|
462
|
-
return 1.0
|
|
463
|
-
|
|
464
|
-
# Simple load-based adaptation
|
|
465
|
-
if self._system_load > self.rate_config.load_threshold:
|
|
466
|
-
reduction = (self._system_load - self.rate_config.load_threshold) * self.rate_config.adaptation_factor
|
|
467
|
-
return max(0.1, 1.0 - reduction)
|
|
468
|
-
else:
|
|
469
|
-
return 1.0
|
|
470
|
-
|
|
471
|
-
def _update_statistics_for_request(self, request: RateLimitRequest) -> None:
|
|
472
|
-
"""Update statistics for processed request."""
|
|
473
|
-
self.statistics.total_requests += 1
|
|
474
|
-
|
|
475
|
-
if request.status == RateLimitStatus.ALLOWED:
|
|
476
|
-
self.statistics.allowed_requests += 1
|
|
477
|
-
elif request.status == RateLimitStatus.THROTTLED:
|
|
478
|
-
self.statistics.throttled_requests += 1
|
|
479
|
-
elif request.status == RateLimitStatus.REJECTED:
|
|
480
|
-
self.statistics.rejected_requests += 1
|
|
481
|
-
|
|
482
|
-
# Update processing time
|
|
483
|
-
if request.processing_time_ms:
|
|
484
|
-
current_avg = self.statistics.avg_processing_time_ms
|
|
485
|
-
total = self.statistics.total_requests
|
|
486
|
-
self.statistics.avg_processing_time_ms = (
|
|
487
|
-
(current_avg * (total - 1) + request.processing_time_ms) / total
|
|
488
|
-
)
|
|
489
|
-
|
|
490
|
-
# Update rates
|
|
491
|
-
if self.statistics.total_requests > 0:
|
|
492
|
-
self.statistics.throttle_rate_percent = (
|
|
493
|
-
self.statistics.throttled_requests / self.statistics.total_requests
|
|
494
|
-
) * 100
|
|
495
|
-
self.statistics.rejection_rate_percent = (
|
|
496
|
-
self.statistics.rejected_requests / self.statistics.total_requests
|
|
497
|
-
) * 100
|
|
498
|
-
|
|
499
|
-
async def _cleanup_loop(self) -> None:
|
|
500
|
-
"""Background cleanup task."""
|
|
501
|
-
while not self._shutdown:
|
|
502
|
-
try:
|
|
503
|
-
await asyncio.sleep(60) # Run every minute
|
|
504
|
-
self._cleanup_expired_quotas()
|
|
505
|
-
self._cleanup_old_backoff_states()
|
|
506
|
-
except asyncio.CancelledError:
|
|
507
|
-
break
|
|
508
|
-
except Exception as e:
|
|
509
|
-
logger.error(f"Error in rate limiter cleanup: {e}")
|
|
510
|
-
|
|
511
|
-
async def _process_queue_loop(self) -> None:
|
|
512
|
-
"""Background queue processing task."""
|
|
513
|
-
while not self._shutdown:
|
|
514
|
-
try:
|
|
515
|
-
await asyncio.sleep(1) # Check queue every second
|
|
516
|
-
self._process_queued_requests()
|
|
517
|
-
except asyncio.CancelledError:
|
|
518
|
-
break
|
|
519
|
-
except Exception as e:
|
|
520
|
-
logger.error(f"Error in queue processing: {e}")
|
|
521
|
-
|
|
522
|
-
async def _metrics_loop(self) -> None:
|
|
523
|
-
"""Background metrics collection task."""
|
|
524
|
-
while not self._shutdown:
|
|
525
|
-
try:
|
|
526
|
-
await asyncio.sleep(self.rate_config.metrics_window_seconds)
|
|
527
|
-
self._collect_metrics()
|
|
528
|
-
except asyncio.CancelledError:
|
|
529
|
-
break
|
|
530
|
-
except Exception as e:
|
|
531
|
-
logger.error(f"Error in metrics collection: {e}")
|
|
532
|
-
|
|
533
|
-
def _cleanup_expired_quotas(self) -> None:
|
|
534
|
-
"""Remove expired quotas."""
|
|
535
|
-
with self._lock:
|
|
536
|
-
expired_keys = [
|
|
537
|
-
key for key, quota in self._quotas.items()
|
|
538
|
-
if quota.is_expired()
|
|
539
|
-
]
|
|
540
|
-
for key in expired_keys:
|
|
541
|
-
del self._quotas[key]
|
|
542
|
-
|
|
543
|
-
if expired_keys:
|
|
544
|
-
self._log_info(f"Cleaned up {len(expired_keys)} expired quotas")
|
|
545
|
-
|
|
546
|
-
def _cleanup_old_backoff_states(self) -> None:
|
|
547
|
-
"""Remove old backoff states."""
|
|
548
|
-
with self._lock:
|
|
549
|
-
now = datetime.now(timezone.utc)
|
|
550
|
-
old_threshold = now - timedelta(hours=1)
|
|
551
|
-
|
|
552
|
-
old_keys = [
|
|
553
|
-
key for key, state in self._backoff_states.items()
|
|
554
|
-
if state.retry_history and state.retry_history[-1] < old_threshold
|
|
555
|
-
]
|
|
556
|
-
for key in old_keys:
|
|
557
|
-
del self._backoff_states[key]
|
|
558
|
-
|
|
559
|
-
if old_keys:
|
|
560
|
-
self._log_info(f"Cleaned up {len(old_keys)} old backoff states")
|
|
561
|
-
|
|
562
|
-
def _process_queued_requests(self) -> None:
|
|
563
|
-
"""Process queued requests that might now be allowed."""
|
|
564
|
-
with self._lock:
|
|
565
|
-
processed = 0
|
|
566
|
-
while self._request_queue and processed < 10: # Process up to 10 per cycle
|
|
567
|
-
request = self._request_queue[0]
|
|
568
|
-
|
|
569
|
-
# Check if quota allows this request now
|
|
570
|
-
quota_key = f"{request.scope_type.value}:{request.scope_id}"
|
|
571
|
-
if quota_key in self._quotas:
|
|
572
|
-
quota = self._quotas[quota_key]
|
|
573
|
-
if quota.remaining_requests > 0:
|
|
574
|
-
self._request_queue.popleft()
|
|
575
|
-
request.status = RateLimitStatus.ALLOWED
|
|
576
|
-
self._handle_allowed_request(quota, request)
|
|
577
|
-
processed += 1
|
|
578
|
-
continue
|
|
579
|
-
|
|
580
|
-
# Check timeout
|
|
581
|
-
age = (datetime.now(timezone.utc) - request.timestamp).total_seconds()
|
|
582
|
-
if age > self.rate_config.queue_timeout_seconds:
|
|
583
|
-
self._request_queue.popleft()
|
|
584
|
-
self.statistics.queue_timeouts += 1
|
|
585
|
-
processed += 1
|
|
586
|
-
continue
|
|
587
|
-
|
|
588
|
-
break # Stop at first non-processable request
|
|
589
|
-
|
|
590
|
-
if processed > 0:
|
|
591
|
-
self._log_info(f"Processed {processed} queued requests")
|
|
592
|
-
|
|
593
|
-
def _collect_metrics(self) -> None:
|
|
594
|
-
"""Collect and update metrics."""
|
|
595
|
-
with self._lock:
|
|
596
|
-
# Update queue metrics
|
|
597
|
-
if self._request_queue:
|
|
598
|
-
self.statistics.avg_queue_size = len(self._request_queue)
|
|
599
|
-
|
|
600
|
-
# Update system load (simplified - could integrate with system monitoring)
|
|
601
|
-
self._system_load = random.uniform(0.3, 0.9) # Placeholder
|
|
602
|
-
self._load_samples.append(self._system_load)
|
|
603
|
-
|
|
604
|
-
self.statistics.system_load_percent = self._system_load * 100
|
|
605
|
-
self.statistics.current_rate_multiplier = self._calculate_adaptive_multiplier()
|
|
606
|
-
|
|
607
|
-
def get_statistics(self) -> RateLimitStatistics:
|
|
608
|
-
"""Get current rate limiting statistics."""
|
|
609
|
-
with self._lock:
|
|
610
|
-
return self.statistics.model_copy()
|
|
611
|
-
|
|
612
|
-
def reset_statistics(self) -> None:
|
|
613
|
-
"""Reset rate limiting statistics."""
|
|
614
|
-
with self._lock:
|
|
615
|
-
self.statistics = RateLimitStatistics()
|
|
616
|
-
self._log_info("Rate limiter statistics reset")
|
|
617
|
-
|
|
618
|
-
def _log_rate_limit_event(self, event_type: RateLimitEventType, request: RateLimitRequest) -> None:
|
|
619
|
-
"""Log rate limiting event."""
|
|
620
|
-
message = f"Rate limit {event_type.value}: {request.scope_id} ({request.status.value})"
|
|
621
|
-
|
|
622
|
-
if self.dev_logger:
|
|
623
|
-
self.dev_logger.log_debug(
|
|
624
|
-
SDKEventType.DEBUG_CHECKPOINT,
|
|
625
|
-
message,
|
|
626
|
-
details={
|
|
627
|
-
"event_type": event_type.value,
|
|
628
|
-
"scope_id": request.scope_id,
|
|
629
|
-
"status": request.status.value,
|
|
630
|
-
"retry_after": request.retry_after_seconds,
|
|
631
|
-
},
|
|
632
|
-
)
|
|
633
|
-
else:
|
|
634
|
-
logger.debug(message)
|
|
635
|
-
|
|
636
|
-
def _log_info(self, message: str, **kwargs: Any) -> None:
|
|
637
|
-
"""Log info message."""
|
|
638
|
-
if self.dev_logger:
|
|
639
|
-
self.dev_logger.log_info(
|
|
640
|
-
SDKEventType.PERFORMANCE_OPTIMIZATION_APPLIED, message, **kwargs
|
|
641
|
-
)
|
|
642
|
-
else:
|
|
643
|
-
logger.info(message)
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
__all__ = [
|
|
647
|
-
# Main business logic class
|
|
648
|
-
"RateLimiter",
|
|
649
|
-
|
|
650
|
-
# Note: Rate limiting models are available via DTO imports:
|
|
651
|
-
# from unrealon_sdk.src.dto.rate_limiting import ...
|
|
652
|
-
]
|