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.
Files changed (302) hide show
  1. unrealon/__init__.py +23 -21
  2. unrealon-1.1.1.dist-info/METADATA +722 -0
  3. unrealon-1.1.1.dist-info/RECORD +82 -0
  4. {unrealon-1.0.9.dist-info → unrealon-1.1.1.dist-info}/WHEEL +1 -1
  5. unrealon-1.1.1.dist-info/entry_points.txt +9 -0
  6. {unrealon-1.0.9.dist-info → unrealon-1.1.1.dist-info/licenses}/LICENSE +1 -1
  7. unrealon_bridge/__init__.py +114 -0
  8. unrealon_bridge/cli.py +316 -0
  9. unrealon_bridge/client/__init__.py +93 -0
  10. unrealon_bridge/client/base.py +78 -0
  11. unrealon_bridge/client/commands.py +89 -0
  12. unrealon_bridge/client/connection.py +90 -0
  13. unrealon_bridge/client/events.py +65 -0
  14. unrealon_bridge/client/health.py +38 -0
  15. unrealon_bridge/client/html_parser.py +146 -0
  16. unrealon_bridge/client/logging.py +139 -0
  17. unrealon_bridge/client/proxy.py +70 -0
  18. unrealon_bridge/client/scheduler.py +450 -0
  19. unrealon_bridge/client/session.py +70 -0
  20. unrealon_bridge/configs/__init__.py +14 -0
  21. unrealon_bridge/configs/bridge_config.py +212 -0
  22. unrealon_bridge/configs/bridge_config.yaml +39 -0
  23. unrealon_bridge/models/__init__.py +138 -0
  24. unrealon_bridge/models/base.py +28 -0
  25. unrealon_bridge/models/command.py +41 -0
  26. unrealon_bridge/models/events.py +40 -0
  27. unrealon_bridge/models/html_parser.py +79 -0
  28. unrealon_bridge/models/logging.py +55 -0
  29. unrealon_bridge/models/parser.py +63 -0
  30. unrealon_bridge/models/proxy.py +41 -0
  31. unrealon_bridge/models/requests.py +95 -0
  32. unrealon_bridge/models/responses.py +88 -0
  33. unrealon_bridge/models/scheduler.py +592 -0
  34. unrealon_bridge/models/session.py +28 -0
  35. unrealon_bridge/server/__init__.py +91 -0
  36. unrealon_bridge/server/base.py +171 -0
  37. unrealon_bridge/server/handlers/__init__.py +23 -0
  38. unrealon_bridge/server/handlers/command.py +110 -0
  39. unrealon_bridge/server/handlers/html_parser.py +139 -0
  40. unrealon_bridge/server/handlers/logging.py +95 -0
  41. unrealon_bridge/server/handlers/parser.py +95 -0
  42. unrealon_bridge/server/handlers/proxy.py +75 -0
  43. unrealon_bridge/server/handlers/scheduler.py +545 -0
  44. unrealon_bridge/server/handlers/session.py +66 -0
  45. unrealon_browser/__init__.py +61 -18
  46. unrealon_browser/{src/cli → cli}/browser_cli.py +6 -13
  47. unrealon_browser/{src/cli → cli}/cookies_cli.py +5 -1
  48. unrealon_browser/{src/core → core}/browser_manager.py +2 -2
  49. unrealon_browser/{src/managers → managers}/captcha.py +1 -1
  50. unrealon_browser/{src/managers → managers}/cookies.py +1 -1
  51. unrealon_browser/managers/logger_bridge.py +231 -0
  52. unrealon_browser/{src/managers → managers}/profile.py +1 -1
  53. unrealon_driver/__init__.py +73 -19
  54. unrealon_driver/browser/__init__.py +8 -0
  55. unrealon_driver/browser/config.py +74 -0
  56. unrealon_driver/browser/manager.py +416 -0
  57. unrealon_driver/exceptions.py +28 -0
  58. unrealon_driver/parser/__init__.py +55 -0
  59. unrealon_driver/parser/cli_manager.py +141 -0
  60. unrealon_driver/parser/daemon_manager.py +227 -0
  61. unrealon_driver/parser/managers/__init__.py +46 -0
  62. unrealon_driver/parser/managers/browser.py +51 -0
  63. unrealon_driver/parser/managers/config.py +281 -0
  64. unrealon_driver/parser/managers/error.py +412 -0
  65. unrealon_driver/parser/managers/html.py +732 -0
  66. unrealon_driver/parser/managers/logging.py +609 -0
  67. unrealon_driver/parser/managers/result.py +321 -0
  68. unrealon_driver/parser/parser_manager.py +628 -0
  69. unrealon/sdk_config.py +0 -88
  70. unrealon-1.0.9.dist-info/METADATA +0 -810
  71. unrealon-1.0.9.dist-info/RECORD +0 -246
  72. unrealon_browser/pyproject.toml +0 -182
  73. unrealon_browser/src/__init__.py +0 -62
  74. unrealon_browser/src/managers/logger_bridge.py +0 -395
  75. unrealon_driver/README.md +0 -204
  76. unrealon_driver/pyproject.toml +0 -187
  77. unrealon_driver/src/__init__.py +0 -90
  78. unrealon_driver/src/cli/__init__.py +0 -10
  79. unrealon_driver/src/cli/main.py +0 -66
  80. unrealon_driver/src/cli/simple.py +0 -510
  81. unrealon_driver/src/config/__init__.py +0 -11
  82. unrealon_driver/src/config/auto_config.py +0 -478
  83. unrealon_driver/src/core/__init__.py +0 -18
  84. unrealon_driver/src/core/exceptions.py +0 -289
  85. unrealon_driver/src/core/parser.py +0 -638
  86. unrealon_driver/src/dto/__init__.py +0 -66
  87. unrealon_driver/src/dto/cli.py +0 -119
  88. unrealon_driver/src/dto/config.py +0 -18
  89. unrealon_driver/src/dto/events.py +0 -237
  90. unrealon_driver/src/dto/execution.py +0 -313
  91. unrealon_driver/src/dto/services.py +0 -311
  92. unrealon_driver/src/execution/__init__.py +0 -23
  93. unrealon_driver/src/execution/daemon_mode.py +0 -317
  94. unrealon_driver/src/execution/interactive_mode.py +0 -88
  95. unrealon_driver/src/execution/modes.py +0 -45
  96. unrealon_driver/src/execution/scheduled_mode.py +0 -209
  97. unrealon_driver/src/execution/test_mode.py +0 -250
  98. unrealon_driver/src/logging/__init__.py +0 -24
  99. unrealon_driver/src/logging/driver_logger.py +0 -512
  100. unrealon_driver/src/services/__init__.py +0 -24
  101. unrealon_driver/src/services/browser_service.py +0 -726
  102. unrealon_driver/src/services/llm/__init__.py +0 -15
  103. unrealon_driver/src/services/llm/browser_llm_service.py +0 -363
  104. unrealon_driver/src/services/llm/llm.py +0 -195
  105. unrealon_driver/src/services/logger_service.py +0 -232
  106. unrealon_driver/src/services/metrics_service.py +0 -185
  107. unrealon_driver/src/services/scheduler_service.py +0 -489
  108. unrealon_driver/src/services/websocket_service.py +0 -362
  109. unrealon_driver/src/utils/__init__.py +0 -16
  110. unrealon_driver/src/utils/service_factory.py +0 -317
  111. unrealon_driver/src/utils/time_formatter.py +0 -338
  112. unrealon_llm/README.md +0 -44
  113. unrealon_llm/__init__.py +0 -26
  114. unrealon_llm/pyproject.toml +0 -154
  115. unrealon_llm/src/__init__.py +0 -228
  116. unrealon_llm/src/cli/__init__.py +0 -0
  117. unrealon_llm/src/core/__init__.py +0 -11
  118. unrealon_llm/src/core/smart_client.py +0 -438
  119. unrealon_llm/src/dto/__init__.py +0 -155
  120. unrealon_llm/src/dto/models/__init__.py +0 -0
  121. unrealon_llm/src/dto/models/config.py +0 -343
  122. unrealon_llm/src/dto/models/core.py +0 -328
  123. unrealon_llm/src/dto/models/enums.py +0 -123
  124. unrealon_llm/src/dto/models/html_analysis.py +0 -345
  125. unrealon_llm/src/dto/models/statistics.py +0 -473
  126. unrealon_llm/src/dto/models/translation.py +0 -383
  127. unrealon_llm/src/dto/models/type_conversion.py +0 -462
  128. unrealon_llm/src/dto/schemas/__init__.py +0 -0
  129. unrealon_llm/src/exceptions.py +0 -392
  130. unrealon_llm/src/llm_config/__init__.py +0 -20
  131. unrealon_llm/src/llm_config/logging_config.py +0 -178
  132. unrealon_llm/src/llm_logging/__init__.py +0 -42
  133. unrealon_llm/src/llm_logging/llm_events.py +0 -107
  134. unrealon_llm/src/llm_logging/llm_logger.py +0 -466
  135. unrealon_llm/src/managers/__init__.py +0 -15
  136. unrealon_llm/src/managers/cache_manager.py +0 -67
  137. unrealon_llm/src/managers/cost_manager.py +0 -107
  138. unrealon_llm/src/managers/request_manager.py +0 -298
  139. unrealon_llm/src/modules/__init__.py +0 -0
  140. unrealon_llm/src/modules/html_processor/__init__.py +0 -25
  141. unrealon_llm/src/modules/html_processor/base_processor.py +0 -415
  142. unrealon_llm/src/modules/html_processor/details_processor.py +0 -85
  143. unrealon_llm/src/modules/html_processor/listing_processor.py +0 -91
  144. unrealon_llm/src/modules/html_processor/models/__init__.py +0 -20
  145. unrealon_llm/src/modules/html_processor/models/processing_models.py +0 -40
  146. unrealon_llm/src/modules/html_processor/models/universal_model.py +0 -56
  147. unrealon_llm/src/modules/html_processor/processor.py +0 -102
  148. unrealon_llm/src/modules/llm/__init__.py +0 -0
  149. unrealon_llm/src/modules/translator/__init__.py +0 -0
  150. unrealon_llm/src/provider.py +0 -116
  151. unrealon_llm/src/utils/__init__.py +0 -95
  152. unrealon_llm/src/utils/common.py +0 -64
  153. unrealon_llm/src/utils/data_extractor.py +0 -188
  154. unrealon_llm/src/utils/html_cleaner.py +0 -767
  155. unrealon_llm/src/utils/language_detector.py +0 -308
  156. unrealon_llm/src/utils/models_cache.py +0 -592
  157. unrealon_llm/src/utils/smart_counter.py +0 -229
  158. unrealon_llm/src/utils/token_counter.py +0 -189
  159. unrealon_sdk/README.md +0 -25
  160. unrealon_sdk/__init__.py +0 -30
  161. unrealon_sdk/pyproject.toml +0 -231
  162. unrealon_sdk/src/__init__.py +0 -150
  163. unrealon_sdk/src/cli/__init__.py +0 -12
  164. unrealon_sdk/src/cli/commands/__init__.py +0 -22
  165. unrealon_sdk/src/cli/commands/benchmark.py +0 -42
  166. unrealon_sdk/src/cli/commands/diagnostics.py +0 -573
  167. unrealon_sdk/src/cli/commands/health.py +0 -46
  168. unrealon_sdk/src/cli/commands/integration.py +0 -498
  169. unrealon_sdk/src/cli/commands/reports.py +0 -43
  170. unrealon_sdk/src/cli/commands/security.py +0 -36
  171. unrealon_sdk/src/cli/commands/server.py +0 -483
  172. unrealon_sdk/src/cli/commands/servers.py +0 -56
  173. unrealon_sdk/src/cli/commands/tests.py +0 -55
  174. unrealon_sdk/src/cli/main.py +0 -126
  175. unrealon_sdk/src/cli/utils/reporter.py +0 -519
  176. unrealon_sdk/src/clients/openapi.yaml +0 -3347
  177. unrealon_sdk/src/clients/python_http/__init__.py +0 -3
  178. unrealon_sdk/src/clients/python_http/api_config.py +0 -228
  179. unrealon_sdk/src/clients/python_http/models/BaseModel.py +0 -12
  180. unrealon_sdk/src/clients/python_http/models/BroadcastDeliveryStats.py +0 -33
  181. unrealon_sdk/src/clients/python_http/models/BroadcastMessage.py +0 -17
  182. unrealon_sdk/src/clients/python_http/models/BroadcastMessageRequest.py +0 -35
  183. unrealon_sdk/src/clients/python_http/models/BroadcastPriority.py +0 -10
  184. unrealon_sdk/src/clients/python_http/models/BroadcastResponse.py +0 -21
  185. unrealon_sdk/src/clients/python_http/models/BroadcastResultResponse.py +0 -33
  186. unrealon_sdk/src/clients/python_http/models/BroadcastTarget.py +0 -11
  187. unrealon_sdk/src/clients/python_http/models/ConnectionStats.py +0 -27
  188. unrealon_sdk/src/clients/python_http/models/ConnectionsResponse.py +0 -21
  189. unrealon_sdk/src/clients/python_http/models/DeveloperMessageResponse.py +0 -23
  190. unrealon_sdk/src/clients/python_http/models/ErrorResponse.py +0 -25
  191. unrealon_sdk/src/clients/python_http/models/HTTPValidationError.py +0 -16
  192. unrealon_sdk/src/clients/python_http/models/HealthResponse.py +0 -23
  193. unrealon_sdk/src/clients/python_http/models/HealthStatus.py +0 -33
  194. unrealon_sdk/src/clients/python_http/models/LogLevel.py +0 -10
  195. unrealon_sdk/src/clients/python_http/models/LoggingRequest.py +0 -27
  196. unrealon_sdk/src/clients/python_http/models/LoggingResponse.py +0 -23
  197. unrealon_sdk/src/clients/python_http/models/MaintenanceMode.py +0 -9
  198. unrealon_sdk/src/clients/python_http/models/MaintenanceModeRequest.py +0 -33
  199. unrealon_sdk/src/clients/python_http/models/MaintenanceStatusResponse.py +0 -39
  200. unrealon_sdk/src/clients/python_http/models/ParserCommandRequest.py +0 -25
  201. unrealon_sdk/src/clients/python_http/models/ParserMessageResponse.py +0 -21
  202. unrealon_sdk/src/clients/python_http/models/ParserRegistrationRequest.py +0 -28
  203. unrealon_sdk/src/clients/python_http/models/ParserRegistrationResponse.py +0 -25
  204. unrealon_sdk/src/clients/python_http/models/ParserType.py +0 -10
  205. unrealon_sdk/src/clients/python_http/models/ProxyBlockRequest.py +0 -19
  206. unrealon_sdk/src/clients/python_http/models/ProxyEndpointResponse.py +0 -20
  207. unrealon_sdk/src/clients/python_http/models/ProxyListResponse.py +0 -19
  208. unrealon_sdk/src/clients/python_http/models/ProxyProvider.py +0 -10
  209. unrealon_sdk/src/clients/python_http/models/ProxyPurchaseRequest.py +0 -25
  210. unrealon_sdk/src/clients/python_http/models/ProxyResponse.py +0 -47
  211. unrealon_sdk/src/clients/python_http/models/ProxyRotationRequest.py +0 -23
  212. unrealon_sdk/src/clients/python_http/models/ProxyStatus.py +0 -10
  213. unrealon_sdk/src/clients/python_http/models/ProxyUsageRequest.py +0 -19
  214. unrealon_sdk/src/clients/python_http/models/ProxyUsageStatsResponse.py +0 -26
  215. unrealon_sdk/src/clients/python_http/models/ServiceRegistrationDto.py +0 -23
  216. unrealon_sdk/src/clients/python_http/models/ServiceStatsResponse.py +0 -31
  217. unrealon_sdk/src/clients/python_http/models/SessionStartRequest.py +0 -23
  218. unrealon_sdk/src/clients/python_http/models/SuccessResponse.py +0 -25
  219. unrealon_sdk/src/clients/python_http/models/SystemNotificationResponse.py +0 -23
  220. unrealon_sdk/src/clients/python_http/models/ValidationError.py +0 -18
  221. unrealon_sdk/src/clients/python_http/models/ValidationErrorResponse.py +0 -21
  222. unrealon_sdk/src/clients/python_http/models/WebSocketMetrics.py +0 -21
  223. unrealon_sdk/src/clients/python_http/models/__init__.py +0 -44
  224. unrealon_sdk/src/clients/python_http/services/None_service.py +0 -35
  225. unrealon_sdk/src/clients/python_http/services/ParserManagement_service.py +0 -190
  226. unrealon_sdk/src/clients/python_http/services/ProxyManagement_service.py +0 -289
  227. unrealon_sdk/src/clients/python_http/services/SocketLogging_service.py +0 -187
  228. unrealon_sdk/src/clients/python_http/services/SystemHealth_service.py +0 -119
  229. unrealon_sdk/src/clients/python_http/services/WebSocketAPI_service.py +0 -198
  230. unrealon_sdk/src/clients/python_http/services/__init__.py +0 -0
  231. unrealon_sdk/src/clients/python_http/services/admin_service.py +0 -125
  232. unrealon_sdk/src/clients/python_http/services/async_None_service.py +0 -35
  233. unrealon_sdk/src/clients/python_http/services/async_ParserManagement_service.py +0 -190
  234. unrealon_sdk/src/clients/python_http/services/async_ProxyManagement_service.py +0 -289
  235. unrealon_sdk/src/clients/python_http/services/async_SocketLogging_service.py +0 -189
  236. unrealon_sdk/src/clients/python_http/services/async_SystemHealth_service.py +0 -123
  237. unrealon_sdk/src/clients/python_http/services/async_WebSocketAPI_service.py +0 -200
  238. unrealon_sdk/src/clients/python_http/services/async_admin_service.py +0 -125
  239. unrealon_sdk/src/clients/python_websocket/__init__.py +0 -28
  240. unrealon_sdk/src/clients/python_websocket/client.py +0 -490
  241. unrealon_sdk/src/clients/python_websocket/events.py +0 -732
  242. unrealon_sdk/src/clients/python_websocket/example.py +0 -136
  243. unrealon_sdk/src/clients/python_websocket/types.py +0 -871
  244. unrealon_sdk/src/core/__init__.py +0 -64
  245. unrealon_sdk/src/core/client.py +0 -556
  246. unrealon_sdk/src/core/config.py +0 -465
  247. unrealon_sdk/src/core/exceptions.py +0 -239
  248. unrealon_sdk/src/core/metadata.py +0 -191
  249. unrealon_sdk/src/core/models.py +0 -142
  250. unrealon_sdk/src/core/types.py +0 -68
  251. unrealon_sdk/src/dto/__init__.py +0 -268
  252. unrealon_sdk/src/dto/authentication.py +0 -108
  253. unrealon_sdk/src/dto/cache.py +0 -208
  254. unrealon_sdk/src/dto/common.py +0 -19
  255. unrealon_sdk/src/dto/concurrency.py +0 -393
  256. unrealon_sdk/src/dto/events.py +0 -108
  257. unrealon_sdk/src/dto/health.py +0 -339
  258. unrealon_sdk/src/dto/load_balancing.py +0 -336
  259. unrealon_sdk/src/dto/logging.py +0 -230
  260. unrealon_sdk/src/dto/performance.py +0 -165
  261. unrealon_sdk/src/dto/rate_limiting.py +0 -295
  262. unrealon_sdk/src/dto/resource_pooling.py +0 -128
  263. unrealon_sdk/src/dto/structured_logging.py +0 -112
  264. unrealon_sdk/src/dto/task_scheduling.py +0 -121
  265. unrealon_sdk/src/dto/websocket.py +0 -55
  266. unrealon_sdk/src/enterprise/__init__.py +0 -59
  267. unrealon_sdk/src/enterprise/authentication.py +0 -401
  268. unrealon_sdk/src/enterprise/cache_manager.py +0 -578
  269. unrealon_sdk/src/enterprise/error_recovery.py +0 -494
  270. unrealon_sdk/src/enterprise/event_system.py +0 -549
  271. unrealon_sdk/src/enterprise/health_monitor.py +0 -747
  272. unrealon_sdk/src/enterprise/load_balancer.py +0 -964
  273. unrealon_sdk/src/enterprise/logging/__init__.py +0 -68
  274. unrealon_sdk/src/enterprise/logging/cleanup.py +0 -156
  275. unrealon_sdk/src/enterprise/logging/development.py +0 -744
  276. unrealon_sdk/src/enterprise/logging/service.py +0 -410
  277. unrealon_sdk/src/enterprise/multithreading_manager.py +0 -853
  278. unrealon_sdk/src/enterprise/performance_monitor.py +0 -539
  279. unrealon_sdk/src/enterprise/proxy_manager.py +0 -696
  280. unrealon_sdk/src/enterprise/rate_limiter.py +0 -652
  281. unrealon_sdk/src/enterprise/resource_pool.py +0 -763
  282. unrealon_sdk/src/enterprise/task_scheduler.py +0 -709
  283. unrealon_sdk/src/internal/__init__.py +0 -10
  284. unrealon_sdk/src/internal/command_router.py +0 -497
  285. unrealon_sdk/src/internal/connection_manager.py +0 -397
  286. unrealon_sdk/src/internal/http_client.py +0 -446
  287. unrealon_sdk/src/internal/websocket_client.py +0 -420
  288. unrealon_sdk/src/provider.py +0 -471
  289. unrealon_sdk/src/utils.py +0 -234
  290. /unrealon_browser/{src/cli → cli}/__init__.py +0 -0
  291. /unrealon_browser/{src/cli → cli}/interactive_mode.py +0 -0
  292. /unrealon_browser/{src/cli → cli}/main.py +0 -0
  293. /unrealon_browser/{src/core → core}/__init__.py +0 -0
  294. /unrealon_browser/{src/dto → dto}/__init__.py +0 -0
  295. /unrealon_browser/{src/dto → dto}/models/config.py +0 -0
  296. /unrealon_browser/{src/dto → dto}/models/core.py +0 -0
  297. /unrealon_browser/{src/dto → dto}/models/dataclasses.py +0 -0
  298. /unrealon_browser/{src/dto → dto}/models/detection.py +0 -0
  299. /unrealon_browser/{src/dto → dto}/models/enums.py +0 -0
  300. /unrealon_browser/{src/dto → dto}/models/statistics.py +0 -0
  301. /unrealon_browser/{src/managers → managers}/__init__.py +0 -0
  302. /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
- ]