agent_os_kernel 3.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.
Files changed (337) hide show
  1. agent_control_plane/__init__.py +662 -0
  2. agent_control_plane/a2a_adapter.py +543 -0
  3. agent_control_plane/adapter.py +417 -0
  4. agent_control_plane/agent_hibernation.py +394 -0
  5. agent_control_plane/agent_kernel.py +470 -0
  6. agent_control_plane/compliance.py +720 -0
  7. agent_control_plane/constraint_graphs.py +478 -0
  8. agent_control_plane/control_plane.py +854 -0
  9. agent_control_plane/example_executors.py +195 -0
  10. agent_control_plane/execution_engine.py +231 -0
  11. agent_control_plane/flight_recorder.py +846 -0
  12. agent_control_plane/governance_layer.py +435 -0
  13. agent_control_plane/hf_utils.py +563 -0
  14. agent_control_plane/interfaces/__init__.py +55 -0
  15. agent_control_plane/interfaces/kernel_interface.py +361 -0
  16. agent_control_plane/interfaces/plugin_interface.py +497 -0
  17. agent_control_plane/interfaces/protocol_interfaces.py +387 -0
  18. agent_control_plane/kernel_space.py +1009 -0
  19. agent_control_plane/langchain_adapter.py +424 -0
  20. agent_control_plane/lifecycle.py +3113 -0
  21. agent_control_plane/mcp_adapter.py +653 -0
  22. agent_control_plane/ml_safety.py +563 -0
  23. agent_control_plane/multimodal.py +727 -0
  24. agent_control_plane/mute_agent.py +422 -0
  25. agent_control_plane/observability.py +787 -0
  26. agent_control_plane/orchestrator.py +482 -0
  27. agent_control_plane/plugin_registry.py +750 -0
  28. agent_control_plane/policy_engine.py +954 -0
  29. agent_control_plane/process_isolation.py +777 -0
  30. agent_control_plane/shadow_mode.py +310 -0
  31. agent_control_plane/signals.py +493 -0
  32. agent_control_plane/supervisor_agents.py +430 -0
  33. agent_control_plane/time_travel_debugger.py +557 -0
  34. agent_control_plane/tool_registry.py +452 -0
  35. agent_control_plane/vfs.py +697 -0
  36. agent_kernel/__init__.py +69 -0
  37. agent_kernel/analyzer.py +435 -0
  38. agent_kernel/auditor.py +36 -0
  39. agent_kernel/completeness_auditor.py +237 -0
  40. agent_kernel/detector.py +203 -0
  41. agent_kernel/kernel.py +744 -0
  42. agent_kernel/memory_manager.py +85 -0
  43. agent_kernel/models.py +374 -0
  44. agent_kernel/nudge_mechanism.py +263 -0
  45. agent_kernel/outcome_analyzer.py +338 -0
  46. agent_kernel/patcher.py +582 -0
  47. agent_kernel/semantic_analyzer.py +316 -0
  48. agent_kernel/semantic_purge.py +349 -0
  49. agent_kernel/simulator.py +449 -0
  50. agent_kernel/teacher.py +85 -0
  51. agent_kernel/triage.py +152 -0
  52. agent_os/__init__.py +409 -0
  53. agent_os/_adversarial_impl.py +200 -0
  54. agent_os/_circuit_breaker_impl.py +232 -0
  55. agent_os/_mcp_metrics.py +193 -0
  56. agent_os/adversarial.py +20 -0
  57. agent_os/agents_compat.py +490 -0
  58. agent_os/audit_logger.py +135 -0
  59. agent_os/base_agent.py +651 -0
  60. agent_os/circuit_breaker.py +34 -0
  61. agent_os/cli/__init__.py +659 -0
  62. agent_os/cli/cmd_audit.py +128 -0
  63. agent_os/cli/cmd_init.py +152 -0
  64. agent_os/cli/cmd_policy.py +41 -0
  65. agent_os/cli/cmd_policy_gen.py +180 -0
  66. agent_os/cli/cmd_validate.py +258 -0
  67. agent_os/cli/mcp_scan.py +265 -0
  68. agent_os/cli/output.py +192 -0
  69. agent_os/cli/policy_checker.py +330 -0
  70. agent_os/compat.py +74 -0
  71. agent_os/constraint_graph.py +234 -0
  72. agent_os/content_governance.py +140 -0
  73. agent_os/context_budget.py +305 -0
  74. agent_os/credential_redactor.py +224 -0
  75. agent_os/diff_policy.py +89 -0
  76. agent_os/egress_policy.py +159 -0
  77. agent_os/escalation.py +276 -0
  78. agent_os/event_bus.py +124 -0
  79. agent_os/exceptions.py +180 -0
  80. agent_os/execution_context_policy.py +141 -0
  81. agent_os/github_enterprise.py +96 -0
  82. agent_os/health.py +20 -0
  83. agent_os/integrations/__init__.py +279 -0
  84. agent_os/integrations/a2a_adapter.py +279 -0
  85. agent_os/integrations/agent_lightning/__init__.py +30 -0
  86. agent_os/integrations/anthropic_adapter.py +420 -0
  87. agent_os/integrations/autogen_adapter.py +620 -0
  88. agent_os/integrations/base.py +1137 -0
  89. agent_os/integrations/compat.py +229 -0
  90. agent_os/integrations/config.py +98 -0
  91. agent_os/integrations/conversation_guardian.py +957 -0
  92. agent_os/integrations/crewai_adapter.py +467 -0
  93. agent_os/integrations/drift_detector.py +425 -0
  94. agent_os/integrations/dry_run.py +124 -0
  95. agent_os/integrations/escalation.py +582 -0
  96. agent_os/integrations/gemini_adapter.py +364 -0
  97. agent_os/integrations/google_adk_adapter.py +633 -0
  98. agent_os/integrations/guardrails_adapter.py +394 -0
  99. agent_os/integrations/health.py +197 -0
  100. agent_os/integrations/langchain_adapter.py +654 -0
  101. agent_os/integrations/llamafirewall.py +343 -0
  102. agent_os/integrations/llamaindex_adapter.py +188 -0
  103. agent_os/integrations/logging.py +191 -0
  104. agent_os/integrations/maf_adapter.py +631 -0
  105. agent_os/integrations/mistral_adapter.py +365 -0
  106. agent_os/integrations/openai_adapter.py +816 -0
  107. agent_os/integrations/openai_agents_sdk.py +406 -0
  108. agent_os/integrations/policy_compose.py +171 -0
  109. agent_os/integrations/profiling.py +144 -0
  110. agent_os/integrations/pydantic_ai_adapter.py +420 -0
  111. agent_os/integrations/rate_limiter.py +130 -0
  112. agent_os/integrations/rbac.py +143 -0
  113. agent_os/integrations/registry.py +113 -0
  114. agent_os/integrations/scope_guard.py +303 -0
  115. agent_os/integrations/semantic_kernel_adapter.py +769 -0
  116. agent_os/integrations/smolagents_adapter.py +629 -0
  117. agent_os/integrations/templates.py +178 -0
  118. agent_os/integrations/token_budget.py +134 -0
  119. agent_os/integrations/tool_aliases.py +190 -0
  120. agent_os/integrations/webhooks.py +177 -0
  121. agent_os/lite.py +208 -0
  122. agent_os/mcp_gateway.py +385 -0
  123. agent_os/mcp_message_signer.py +273 -0
  124. agent_os/mcp_protocols.py +161 -0
  125. agent_os/mcp_response_scanner.py +232 -0
  126. agent_os/mcp_security.py +924 -0
  127. agent_os/mcp_session_auth.py +231 -0
  128. agent_os/mcp_sliding_rate_limiter.py +184 -0
  129. agent_os/memory_guard.py +409 -0
  130. agent_os/metrics.py +134 -0
  131. agent_os/mute.py +428 -0
  132. agent_os/mute_agent.py +209 -0
  133. agent_os/policies/__init__.py +77 -0
  134. agent_os/policies/async_evaluator.py +275 -0
  135. agent_os/policies/backends.py +670 -0
  136. agent_os/policies/bridge.py +169 -0
  137. agent_os/policies/budget.py +85 -0
  138. agent_os/policies/cli.py +294 -0
  139. agent_os/policies/conflict_resolution.py +270 -0
  140. agent_os/policies/data_classification.py +252 -0
  141. agent_os/policies/evaluator.py +239 -0
  142. agent_os/policies/policy_schema.json +228 -0
  143. agent_os/policies/rate_limiting.py +145 -0
  144. agent_os/policies/schema.py +115 -0
  145. agent_os/policies/shared.py +331 -0
  146. agent_os/prompt_injection.py +694 -0
  147. agent_os/providers.py +182 -0
  148. agent_os/py.typed +0 -0
  149. agent_os/retry.py +81 -0
  150. agent_os/reversibility.py +251 -0
  151. agent_os/sandbox.py +432 -0
  152. agent_os/sandbox_provider.py +140 -0
  153. agent_os/secure_codegen.py +525 -0
  154. agent_os/security_skills.py +538 -0
  155. agent_os/semantic_policy.py +422 -0
  156. agent_os/server/__init__.py +15 -0
  157. agent_os/server/__main__.py +25 -0
  158. agent_os/server/app.py +277 -0
  159. agent_os/server/models.py +104 -0
  160. agent_os/shift_left_metrics.py +130 -0
  161. agent_os/stateless.py +742 -0
  162. agent_os/supervisor.py +148 -0
  163. agent_os/task_outcome.py +148 -0
  164. agent_os/transparency.py +181 -0
  165. agent_os/trust_root.py +128 -0
  166. agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
  167. agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
  168. agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
  169. agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
  170. agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
  171. agent_os_observability/__init__.py +27 -0
  172. agent_os_observability/dashboards.py +898 -0
  173. agent_os_observability/metrics.py +398 -0
  174. agent_os_observability/server.py +223 -0
  175. agent_os_observability/tracer.py +232 -0
  176. agent_primitives/__init__.py +24 -0
  177. agent_primitives/failures.py +84 -0
  178. agent_primitives/py.typed +0 -0
  179. amb_core/__init__.py +177 -0
  180. amb_core/adapters/__init__.py +57 -0
  181. amb_core/adapters/aws_sqs_broker.py +376 -0
  182. amb_core/adapters/azure_servicebus_broker.py +340 -0
  183. amb_core/adapters/kafka_broker.py +260 -0
  184. amb_core/adapters/nats_broker.py +285 -0
  185. amb_core/adapters/rabbitmq_broker.py +235 -0
  186. amb_core/adapters/redis_broker.py +262 -0
  187. amb_core/broker.py +145 -0
  188. amb_core/bus.py +481 -0
  189. amb_core/cloudevents.py +509 -0
  190. amb_core/dlq.py +345 -0
  191. amb_core/hf_utils.py +536 -0
  192. amb_core/memory_broker.py +410 -0
  193. amb_core/models.py +141 -0
  194. amb_core/persistence.py +529 -0
  195. amb_core/schema.py +294 -0
  196. amb_core/tracing.py +358 -0
  197. atr/__init__.py +640 -0
  198. atr/access.py +348 -0
  199. atr/composition.py +645 -0
  200. atr/decorator.py +357 -0
  201. atr/executor.py +384 -0
  202. atr/health.py +557 -0
  203. atr/hf_utils.py +449 -0
  204. atr/injection.py +422 -0
  205. atr/metrics.py +440 -0
  206. atr/policies.py +403 -0
  207. atr/py.typed +2 -0
  208. atr/registry.py +452 -0
  209. atr/schema.py +480 -0
  210. atr/tools/safe/__init__.py +75 -0
  211. atr/tools/safe/calculator.py +467 -0
  212. atr/tools/safe/datetime_tool.py +443 -0
  213. atr/tools/safe/file_reader.py +402 -0
  214. atr/tools/safe/http_client.py +316 -0
  215. atr/tools/safe/json_parser.py +374 -0
  216. atr/tools/safe/text_tool.py +537 -0
  217. atr/tools/safe/toolkit.py +175 -0
  218. caas/__init__.py +162 -0
  219. caas/api/__init__.py +7 -0
  220. caas/api/server.py +1328 -0
  221. caas/caching.py +834 -0
  222. caas/cli.py +210 -0
  223. caas/conversation.py +223 -0
  224. caas/decay.py +72 -0
  225. caas/detection/__init__.py +9 -0
  226. caas/detection/detector.py +238 -0
  227. caas/enrichment.py +130 -0
  228. caas/gateway/__init__.py +27 -0
  229. caas/gateway/trust_gateway.py +474 -0
  230. caas/hf_utils.py +479 -0
  231. caas/ingestion/__init__.py +23 -0
  232. caas/ingestion/processors.py +253 -0
  233. caas/ingestion/structure_parser.py +188 -0
  234. caas/models.py +356 -0
  235. caas/pragmatic_truth.py +444 -0
  236. caas/routing/__init__.py +10 -0
  237. caas/routing/heuristic_router.py +58 -0
  238. caas/storage/__init__.py +9 -0
  239. caas/storage/store.py +389 -0
  240. caas/triad.py +213 -0
  241. caas/tuning/__init__.py +9 -0
  242. caas/tuning/tuner.py +329 -0
  243. caas/vfs/__init__.py +14 -0
  244. caas/vfs/filesystem.py +452 -0
  245. cmvk/__init__.py +218 -0
  246. cmvk/audit.py +402 -0
  247. cmvk/benchmarks.py +478 -0
  248. cmvk/constitutional.py +904 -0
  249. cmvk/hf_utils.py +301 -0
  250. cmvk/metrics.py +473 -0
  251. cmvk/profiles.py +300 -0
  252. cmvk/py.typed +0 -0
  253. cmvk/types.py +12 -0
  254. cmvk/verification.py +956 -0
  255. emk/__init__.py +89 -0
  256. emk/causal.py +352 -0
  257. emk/hf_utils.py +421 -0
  258. emk/indexer.py +83 -0
  259. emk/py.typed +0 -0
  260. emk/schema.py +204 -0
  261. emk/sleep_cycle.py +347 -0
  262. emk/store.py +281 -0
  263. iatp/__init__.py +166 -0
  264. iatp/attestation.py +461 -0
  265. iatp/cli.py +317 -0
  266. iatp/hf_utils.py +472 -0
  267. iatp/ipc_pipes.py +580 -0
  268. iatp/main.py +412 -0
  269. iatp/models/__init__.py +447 -0
  270. iatp/policy_engine.py +337 -0
  271. iatp/py.typed +2 -0
  272. iatp/recovery.py +321 -0
  273. iatp/security/__init__.py +270 -0
  274. iatp/sidecar/__init__.py +519 -0
  275. iatp/telemetry/__init__.py +164 -0
  276. iatp/tests/__init__.py +1 -0
  277. iatp/tests/test_attestation.py +370 -0
  278. iatp/tests/test_cli.py +131 -0
  279. iatp/tests/test_ed25519_attestation.py +211 -0
  280. iatp/tests/test_models.py +130 -0
  281. iatp/tests/test_policy_engine.py +347 -0
  282. iatp/tests/test_recovery.py +281 -0
  283. iatp/tests/test_security.py +222 -0
  284. iatp/tests/test_sidecar.py +167 -0
  285. iatp/tests/test_telemetry.py +175 -0
  286. mcp_kernel_server/__init__.py +28 -0
  287. mcp_kernel_server/cli.py +274 -0
  288. mcp_kernel_server/resources.py +217 -0
  289. mcp_kernel_server/server.py +564 -0
  290. mcp_kernel_server/tools.py +1174 -0
  291. mute_agent/__init__.py +68 -0
  292. mute_agent/core/__init__.py +1 -0
  293. mute_agent/core/execution_agent.py +166 -0
  294. mute_agent/core/handshake_protocol.py +201 -0
  295. mute_agent/core/reasoning_agent.py +238 -0
  296. mute_agent/knowledge_graph/__init__.py +1 -0
  297. mute_agent/knowledge_graph/graph_elements.py +65 -0
  298. mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
  299. mute_agent/knowledge_graph/subgraph.py +224 -0
  300. mute_agent/listener/__init__.py +43 -0
  301. mute_agent/listener/adapters/__init__.py +31 -0
  302. mute_agent/listener/adapters/base_adapter.py +189 -0
  303. mute_agent/listener/adapters/caas_adapter.py +344 -0
  304. mute_agent/listener/adapters/control_plane_adapter.py +436 -0
  305. mute_agent/listener/adapters/iatp_adapter.py +332 -0
  306. mute_agent/listener/adapters/scak_adapter.py +251 -0
  307. mute_agent/listener/listener.py +610 -0
  308. mute_agent/listener/state_observer.py +436 -0
  309. mute_agent/listener/threshold_config.py +313 -0
  310. mute_agent/super_system/__init__.py +1 -0
  311. mute_agent/super_system/router.py +204 -0
  312. mute_agent/visualization/__init__.py +10 -0
  313. mute_agent/visualization/graph_debugger.py +502 -0
  314. nexus/README.md +60 -0
  315. nexus/__init__.py +51 -0
  316. nexus/arbiter.py +359 -0
  317. nexus/client.py +466 -0
  318. nexus/dmz.py +444 -0
  319. nexus/escrow.py +430 -0
  320. nexus/exceptions.py +286 -0
  321. nexus/pyproject.toml +36 -0
  322. nexus/registry.py +393 -0
  323. nexus/reputation.py +425 -0
  324. nexus/schemas/__init__.py +51 -0
  325. nexus/schemas/compliance.py +276 -0
  326. nexus/schemas/escrow.py +251 -0
  327. nexus/schemas/manifest.py +225 -0
  328. nexus/schemas/receipt.py +208 -0
  329. nexus/tests/__init__.py +0 -0
  330. nexus/tests/conftest.py +146 -0
  331. nexus/tests/test_arbiter.py +192 -0
  332. nexus/tests/test_dmz.py +194 -0
  333. nexus/tests/test_escrow.py +276 -0
  334. nexus/tests/test_exceptions.py +225 -0
  335. nexus/tests/test_registry.py +232 -0
  336. nexus/tests/test_reputation.py +328 -0
  337. nexus/tests/test_schemas.py +295 -0
atr/policies.py ADDED
@@ -0,0 +1,403 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Policy definitions for tool execution control.
5
+
6
+ Provides retry policies, rate limiting, and other execution policies.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import threading
13
+ import time
14
+ from collections import deque
15
+ from dataclasses import dataclass, field
16
+ from enum import Enum
17
+ from typing import Any, Callable, Optional, TypeVar, Union
18
+
19
+
20
+ class BackoffStrategy(str, Enum):
21
+ """Backoff strategies for retry policies."""
22
+
23
+ CONSTANT = "constant"
24
+ LINEAR = "linear"
25
+ EXPONENTIAL = "exponential"
26
+ FIBONACCI = "fibonacci"
27
+
28
+
29
+ @dataclass
30
+ class RetryPolicy:
31
+ """Configuration for retry behavior.
32
+
33
+ Attributes:
34
+ max_attempts: Maximum number of retry attempts (including initial).
35
+ backoff: Backoff strategy between retries.
36
+ initial_delay: Initial delay in seconds before first retry.
37
+ max_delay: Maximum delay in seconds between retries.
38
+ jitter: Whether to add random jitter to delays.
39
+ retry_on: Exception types to retry on. None means retry on all exceptions.
40
+ on_retry: Optional callback called on each retry with (attempt, exception, delay).
41
+
42
+ Example:
43
+ >>> policy = RetryPolicy(
44
+ ... max_attempts=3,
45
+ ... backoff=BackoffStrategy.EXPONENTIAL,
46
+ ... initial_delay=1.0,
47
+ ... max_delay=30.0
48
+ ... )
49
+ """
50
+
51
+ max_attempts: int = 3
52
+ backoff: Union[BackoffStrategy, str] = BackoffStrategy.EXPONENTIAL
53
+ initial_delay: float = 1.0
54
+ max_delay: float = 60.0
55
+ jitter: bool = True
56
+ retry_on: Optional[tuple] = None
57
+ on_retry: Optional[Callable[[int, Exception, float], None]] = None
58
+
59
+ def __post_init__(self):
60
+ """Convert string backoff to enum."""
61
+ if isinstance(self.backoff, str):
62
+ self.backoff = BackoffStrategy(self.backoff.lower())
63
+
64
+ def calculate_delay(self, attempt: int) -> float:
65
+ """Calculate delay before the given attempt.
66
+
67
+ Args:
68
+ attempt: The attempt number (1-indexed).
69
+
70
+ Returns:
71
+ Delay in seconds.
72
+ """
73
+ if attempt <= 1:
74
+ return 0
75
+
76
+ retry_number = attempt - 1 # 0-indexed for calculation
77
+
78
+ if self.backoff == BackoffStrategy.CONSTANT:
79
+ delay = self.initial_delay
80
+ elif self.backoff == BackoffStrategy.LINEAR:
81
+ delay = self.initial_delay * retry_number
82
+ elif self.backoff == BackoffStrategy.EXPONENTIAL:
83
+ delay = self.initial_delay * (2 ** (retry_number - 1))
84
+ elif self.backoff == BackoffStrategy.FIBONACCI:
85
+ delay = self.initial_delay * self._fibonacci(retry_number)
86
+ else:
87
+ delay = self.initial_delay
88
+
89
+ # Apply max delay cap
90
+ delay = min(delay, self.max_delay)
91
+
92
+ # Add jitter if enabled (±25%)
93
+ if self.jitter:
94
+ import random
95
+
96
+ jitter_range = delay * 0.25
97
+ delay += random.uniform(-jitter_range, jitter_range)
98
+ delay = max(0, delay)
99
+
100
+ return delay
101
+
102
+ @staticmethod
103
+ def _fibonacci(n: int) -> int:
104
+ """Calculate nth Fibonacci number."""
105
+ if n <= 1:
106
+ return 1
107
+ a, b = 1, 1
108
+ for _ in range(n - 1):
109
+ a, b = b, a + b
110
+ return b
111
+
112
+ def should_retry(self, exception: Exception) -> bool:
113
+ """Check if we should retry on this exception.
114
+
115
+ Args:
116
+ exception: The exception that occurred.
117
+
118
+ Returns:
119
+ True if should retry, False otherwise.
120
+ """
121
+ if self.retry_on is None:
122
+ return True
123
+ return isinstance(exception, self.retry_on)
124
+
125
+
126
+ @dataclass
127
+ class RateLimitPolicy:
128
+ """Configuration for rate limiting.
129
+
130
+ Supports various rate limiting formats:
131
+ - "10/minute" - 10 calls per minute
132
+ - "100/hour" - 100 calls per hour
133
+ - "5/second" - 5 calls per second
134
+
135
+ Attributes:
136
+ limit: Maximum number of calls allowed.
137
+ period: Time period in seconds.
138
+ burst: Optional burst allowance above the limit.
139
+ on_limited: Optional callback when rate limited.
140
+
141
+ Example:
142
+ >>> policy = RateLimitPolicy.from_string("10/minute")
143
+ >>> # or
144
+ >>> policy = RateLimitPolicy(limit=10, period=60)
145
+ """
146
+
147
+ limit: int
148
+ period: float # in seconds
149
+ burst: Optional[int] = None
150
+ on_limited: Optional[Callable[[], None]] = None
151
+
152
+ _calls: deque = field(default_factory=deque, repr=False)
153
+ _lock: threading.Lock = field(default_factory=threading.Lock, repr=False)
154
+
155
+ @classmethod
156
+ def from_string(cls, rate_string: str) -> "RateLimitPolicy":
157
+ """Parse a rate limit string like '10/minute'.
158
+
159
+ Args:
160
+ rate_string: String in format 'N/period' where period is
161
+ 'second', 'minute', 'hour', or 'day'.
162
+
163
+ Returns:
164
+ RateLimitPolicy instance.
165
+
166
+ Raises:
167
+ ValueError: If format is invalid.
168
+ """
169
+ if "/" not in rate_string:
170
+ raise ValueError(f"Invalid rate limit format: {rate_string}. Expected 'N/period'")
171
+
172
+ parts = rate_string.split("/")
173
+ if len(parts) != 2:
174
+ raise ValueError(f"Invalid rate limit format: {rate_string}")
175
+
176
+ try:
177
+ limit = int(parts[0])
178
+ except ValueError as err:
179
+ raise ValueError(f"Invalid limit value: {parts[0]}") from err
180
+
181
+ period_map = {
182
+ "second": 1,
183
+ "sec": 1,
184
+ "s": 1,
185
+ "minute": 60,
186
+ "min": 60,
187
+ "m": 60,
188
+ "hour": 3600,
189
+ "hr": 3600,
190
+ "h": 3600,
191
+ "day": 86400,
192
+ "d": 86400,
193
+ }
194
+
195
+ period_str = parts[1].lower().strip()
196
+ if period_str not in period_map:
197
+ raise ValueError(f"Unknown period: {period_str}. Valid: {list(period_map.keys())}")
198
+
199
+ return cls(limit=limit, period=period_map[period_str])
200
+
201
+ def acquire(self, blocking: bool = True, timeout: Optional[float] = None) -> bool:
202
+ """Attempt to acquire a rate limit slot.
203
+
204
+ Args:
205
+ blocking: If True, wait until a slot is available.
206
+ timeout: Maximum time to wait if blocking (None = forever).
207
+
208
+ Returns:
209
+ True if acquired, False if rate limited (non-blocking mode).
210
+ """
211
+ start_time = time.monotonic()
212
+
213
+ while True:
214
+ with self._lock:
215
+ now = time.monotonic()
216
+
217
+ # Remove expired calls
218
+ cutoff = now - self.period
219
+ while self._calls and self._calls[0] < cutoff:
220
+ self._calls.popleft()
221
+
222
+ # Check if we can make a call
223
+ effective_limit = self.limit + (self.burst or 0)
224
+ if len(self._calls) < effective_limit:
225
+ self._calls.append(now)
226
+ return True
227
+
228
+ if not blocking:
229
+ if self.on_limited:
230
+ self.on_limited()
231
+ return False
232
+
233
+ # Calculate wait time
234
+ wait_time = self._calls[0] + self.period - now if self._calls else 0.1
235
+
236
+ # Check timeout
237
+ if timeout is not None:
238
+ elapsed = time.monotonic() - start_time
239
+ if elapsed >= timeout:
240
+ if self.on_limited:
241
+ self.on_limited()
242
+ return False
243
+ wait_time = min(wait_time, timeout - elapsed)
244
+
245
+ time.sleep(min(wait_time, 0.1)) # Sleep in small increments
246
+
247
+ async def acquire_async(self, blocking: bool = True, timeout: Optional[float] = None) -> bool:
248
+ """Async version of acquire.
249
+
250
+ Args:
251
+ blocking: If True, wait until a slot is available.
252
+ timeout: Maximum time to wait if blocking (None = forever).
253
+
254
+ Returns:
255
+ True if acquired, False if rate limited.
256
+ """
257
+ start_time = asyncio.get_event_loop().time()
258
+
259
+ while True:
260
+ with self._lock:
261
+ now = time.monotonic()
262
+
263
+ # Remove expired calls
264
+ cutoff = now - self.period
265
+ while self._calls and self._calls[0] < cutoff:
266
+ self._calls.popleft()
267
+
268
+ # Check if we can make a call
269
+ effective_limit = self.limit + (self.burst or 0)
270
+ if len(self._calls) < effective_limit:
271
+ self._calls.append(now)
272
+ return True
273
+
274
+ if not blocking:
275
+ if self.on_limited:
276
+ self.on_limited()
277
+ return False
278
+
279
+ # Calculate wait time
280
+ wait_time = self._calls[0] + self.period - now if self._calls else 0.1
281
+
282
+ # Check timeout
283
+ if timeout is not None:
284
+ elapsed = asyncio.get_event_loop().time() - start_time
285
+ if elapsed >= timeout:
286
+ if self.on_limited:
287
+ self.on_limited()
288
+ return False
289
+ wait_time = min(wait_time, timeout - elapsed)
290
+
291
+ await asyncio.sleep(min(wait_time, 0.1))
292
+
293
+ def reset(self) -> None:
294
+ """Reset the rate limiter state."""
295
+ with self._lock:
296
+ self._calls.clear()
297
+
298
+
299
+ class RateLimitExceeded(Exception):
300
+ """Raised when rate limit is exceeded in non-blocking mode."""
301
+
302
+ pass
303
+
304
+
305
+ class RetryExhausted(Exception):
306
+ """Raised when all retry attempts have been exhausted."""
307
+
308
+ def __init__(self, message: str, last_exception: Exception, attempts: int):
309
+ super().__init__(message)
310
+ self.last_exception = last_exception
311
+ self.attempts = attempts
312
+
313
+
314
+ T = TypeVar("T")
315
+
316
+
317
+ def with_retry(policy: RetryPolicy, func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
318
+ """Execute a function with retry policy.
319
+
320
+ Args:
321
+ policy: The retry policy to use.
322
+ func: The function to execute.
323
+ *args: Positional arguments to pass to func.
324
+ **kwargs: Keyword arguments to pass to func.
325
+
326
+ Returns:
327
+ The result of the function.
328
+
329
+ Raises:
330
+ RetryExhausted: If all attempts fail.
331
+ """
332
+ last_exception = None
333
+
334
+ for attempt in range(1, policy.max_attempts + 1):
335
+ try:
336
+ # Calculate and apply delay
337
+ delay = policy.calculate_delay(attempt)
338
+ if delay > 0:
339
+ time.sleep(delay)
340
+
341
+ return func(*args, **kwargs)
342
+
343
+ except Exception as e:
344
+ last_exception = e
345
+
346
+ if not policy.should_retry(e):
347
+ raise
348
+
349
+ if attempt < policy.max_attempts:
350
+ next_delay = policy.calculate_delay(attempt + 1)
351
+ if policy.on_retry:
352
+ policy.on_retry(attempt, e, next_delay)
353
+
354
+ raise RetryExhausted(
355
+ f"All {policy.max_attempts} attempts failed", last_exception, policy.max_attempts
356
+ )
357
+
358
+
359
+ async def with_retry_async(
360
+ policy: RetryPolicy, func: Callable[..., T], *args: Any, **kwargs: Any
361
+ ) -> T:
362
+ """Execute an async function with retry policy.
363
+
364
+ Args:
365
+ policy: The retry policy to use.
366
+ func: The async function to execute.
367
+ *args: Positional arguments to pass to func.
368
+ **kwargs: Keyword arguments to pass to func.
369
+
370
+ Returns:
371
+ The result of the function.
372
+
373
+ Raises:
374
+ RetryExhausted: If all attempts fail.
375
+ """
376
+ last_exception = None
377
+
378
+ for attempt in range(1, policy.max_attempts + 1):
379
+ try:
380
+ # Calculate and apply delay
381
+ delay = policy.calculate_delay(attempt)
382
+ if delay > 0:
383
+ await asyncio.sleep(delay)
384
+
385
+ result = func(*args, **kwargs)
386
+ if asyncio.iscoroutine(result):
387
+ result = await result
388
+ return result
389
+
390
+ except Exception as e:
391
+ last_exception = e
392
+
393
+ if not policy.should_retry(e):
394
+ raise
395
+
396
+ if attempt < policy.max_attempts:
397
+ next_delay = policy.calculate_delay(attempt + 1)
398
+ if policy.on_retry:
399
+ policy.on_retry(attempt, e, next_delay)
400
+
401
+ raise RetryExhausted(
402
+ f"All {policy.max_attempts} attempts failed", last_exception, policy.max_attempts
403
+ )
atr/py.typed ADDED
@@ -0,0 +1,2 @@
1
+ # This file indicates that the package is PEP 561 compliant
2
+ # and supports type checking with mypy, pyright, etc.