OPEN SOURCE
software/src/orchestration/rate_limiter.py
このサイトで読み、必要なファイルを保存できます。
日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。
1"""Rate limiter for coordinating parallel API calls.23Provides thread-safe rate limiting to prevent overwhelming4the Claude API when multiple book workers are running in parallel.5"""67from __future__ import annotations89import threading10import time11from contextlib import contextmanager12from typing import Generator131415class SharedRateLimiter:16"""Thread-safe rate limiter using a bounded semaphore.1718Coordinates API access across multiple worker threads to prevent19rate limiting errors. Uses a BoundedSemaphore to limit concurrent20requests and adds a delay between releases.2122Example:23limiter = SharedRateLimiter(max_concurrent=5, delay=1.2)2425# In worker thread:26with limiter:27call_claude_api()2829Attributes:30max_concurrent: Maximum concurrent API calls allowed.31delay: Delay in seconds between API calls.32"""3334def __init__(35self,36max_concurrent: int = 5,37delay: float = 1.2,38) -> None:39"""Initialize the rate limiter.4041Args:42max_concurrent: Maximum concurrent API calls (default: 5).43delay: Minimum delay between calls in seconds (default: 1.2).44"""45self.max_concurrent = max_concurrent46self.delay = delay4748# Create semaphore for limiting concurrent calls49self._semaphore = threading.BoundedSemaphore(max_concurrent)5051# Create lock for coordinating delays52self._delay_lock = threading.Lock()5354# Track last call time55self._last_call_time = 0.05657def acquire(self) -> None:58"""Acquire a rate limit slot.5960Blocks until a slot is available and the minimum delay has passed61since the last call.62"""63# First, acquire semaphore slot64self._semaphore.acquire()6566# Then, ensure minimum delay between calls67with self._delay_lock:68now = time.time()69elapsed = now - self._last_call_time70if elapsed < self.delay:71time.sleep(self.delay - elapsed)72self._last_call_time = time.time()7374def release(self) -> None:75"""Release a rate limit slot."""76self._semaphore.release()7778@contextmanager79def __call__(self) -> Generator[None, None, None]:80"""Context manager for rate limiting.8182Yields:83None, but blocks until slot is available.84"""85self.acquire()86try:87yield88finally:89self.release()9091def __enter__(self) -> SharedRateLimiter:92"""Enter context manager."""93self.acquire()94return self9596def __exit__(self, *args: object) -> None:97"""Exit context manager."""98self.release()99100def shutdown(self) -> None:101"""Clean up resources (no-op for thread-based limiter)."""102pass103104105class DummyRateLimiter:106"""No-op rate limiter for single-process usage or testing.107108Provides the same interface as SharedRateLimiter but does nothing.109Useful for testing or when rate limiting is not needed.110"""111112def __init__(113self,114max_concurrent: int = 5,115delay: float = 1.2,116) -> None:117"""Initialize the dummy rate limiter.118119Args:120max_concurrent: Ignored (for API compatibility).121delay: Delay in seconds between calls (still applied).122"""123self.max_concurrent = max_concurrent124self.delay = delay125self._last_call_time = 0.0126127def acquire(self) -> None:128"""Acquire rate limit slot (applies delay only)."""129now = time.time()130elapsed = now - self._last_call_time131if elapsed < self.delay:132time.sleep(self.delay - elapsed)133self._last_call_time = time.time()134135def release(self) -> None:136"""Release rate limit slot (no-op)."""137pass138139@contextmanager140def __call__(self) -> Generator[None, None, None]:141"""Context manager for rate limiting."""142self.acquire()143try:144yield145finally:146self.release()147148def __enter__(self) -> DummyRateLimiter:149"""Enter context manager."""150self.acquire()151return self152153def __exit__(self, *args: object) -> None:154"""Exit context manager."""155self.release()156157def shutdown(self) -> None:158"""Clean up resources (no-op)."""159pass160