import asyncio
import time
from typing import Optional
class RateLimiter:
"""
Rate limiter to control API request frequency.
Ensures that no more than max_requests_per_minute requests are made per minute
and no more than max_requests_per_day requests are made per day.
"""
def __init__(
self,
max_requests_per_minute: Optional[int] = None,
max_requests_per_day: Optional[int] = None
):
"""
Initialize the rate limiter.
Args:
max_requests_per_minute: Maximum number of requests allowed per minute.
If None, no per-minute limit is enforced.
max_requests_per_day: Maximum number of requests allowed per day.
If None, no per-day limit is enforced.
"""
self.max_requests_per_minute = max_requests_per_minute
self.max_requests_per_day = max_requests_per_day
self.request_times = []
self.lock = asyncio.Lock()
if max_requests_per_minute and max_requests_per_minute > 0:
self.min_delay = 60.0 / max_requests_per_minute
else:
self.min_delay = 0
async def acquire(self):
"""
Acquire permission to make a request.
This method will block until it's safe to make a request according to the rate limits.
"""
if not self.max_requests_per_minute and not self.max_requests_per_day:
return
async with self.lock:
current_time = time.time()
if self.max_requests_per_day:
self.request_times = [t for t in self.request_times if current_time - t < 86400]
elif self.max_requests_per_minute:
self.request_times = [t for t in self.request_times if current_time - t < 60]
if self.max_requests_per_day and self.max_requests_per_day > 0:
daily_requests = [t for t in self.request_times if current_time - t < 86400]
if len(daily_requests) >= self.max_requests_per_day:
oldest_request = daily_requests[0]
wait_time = 86400 - (current_time - oldest_request)
if wait_time > 0:
hours = wait_time / 3600
print(f"Daily rate limit reached ({self.max_requests_per_day} requests/day). Waiting {hours:.1f} hours...")
await asyncio.sleep(wait_time)
current_time = time.time()
self.request_times = [t for t in self.request_times if current_time - t < 86400]
if self.max_requests_per_minute and self.max_requests_per_minute > 0:
minute_requests = [t for t in self.request_times if current_time - t < 60]
if len(minute_requests) >= self.max_requests_per_minute:
oldest_request = minute_requests[0]
wait_time = 60 - (current_time - oldest_request)
if wait_time > 0:
print(f"Rate limit reached ({self.max_requests_per_minute} requests/min). Waiting {wait_time:.1f}s...")
await asyncio.sleep(wait_time)
current_time = time.time()
if self.max_requests_per_day:
self.request_times = [t for t in self.request_times if current_time - t < 86400]
else:
self.request_times = [t for t in self.request_times if current_time - t < 60]
if self.request_times and self.min_delay > 0:
last_request = self.request_times[-1]
time_since_last = current_time - last_request
if time_since_last < self.min_delay:
wait_time = self.min_delay - time_since_last
await asyncio.sleep(wait_time)
current_time = time.time()
self.request_times.append(current_time)