API Reference¶
Everything importable from smartratelimit, plus the submodules you'll reach for.
from smartratelimit import (
RateLimiter,
AsyncRateLimiter,
RateLimitStatus,
RateLimitExceeded,
RetryConfig,
RetryHandler,
RetryStrategy,
MetricsCollector,
)
RateLimiter¶
smartratelimit.RateLimiter
Synchronous limiter built on requests.
RateLimiter(storage="memory", default_limits=None, headers_map=None, raise_on_limit=False)¶
| Parameter | Type | Default | Description |
|---|---|---|---|
storage |
str |
"memory" |
"memory", "sqlite:///path.db", or "redis://host:port/db" |
default_limits |
dict \| None |
None |
Fallback limit when nothing is detected โ one of requests_per_second, requests_per_minute, requests_per_hour |
headers_map |
dict \| None |
None |
Custom header names: keys limit, remaining, reset |
raise_on_limit |
bool |
False |
Raise RateLimitExceeded instead of waiting |
Raises ValueError for an unrecognised storage string. A SQLite or Redis backend that fails to initialise logs a warning and falls back to memory.
If default_limits contains more than one key, the shortest window present wins (second โ minute โ hour) and the rest are ignored.
.request(method, url, **kwargs) -> requests.Response¶
Make a paced request. **kwargs are passed through to requests.request().
Waits (or raises, with raise_on_limit=True) when the endpoint's bucket is empty, then updates the stored quota from the response headers. On a 429 carrying Retry-After, sleeps and retries once unless raise_on_limit=True.
.wrap_session(session) -> None¶
Replace session.request with a paced version, in place. Returns None.
The wrapped calls are issued through the limiter's own internal session, so headers, auth and cookies configured on your session are not applied โ pass them per call instead.
.get_status(endpoint) -> RateLimitStatus | None¶
Current stored status for an endpoint. Accepts a bare domain or a full URL; both normalise to scheme://host. Returns None when nothing has been detected or set.
.set_limit(endpoint, limit, window="1h") -> None¶
Store a limit explicitly, without waiting to detect one.
window is a whole number plus a unit โ "30s", "15m", "1h", "1d". Anything unparseable (including a decimal like "1.5h") silently falls back to one hour.
A subsequently detected limit from response headers replaces what you set.
.clear(endpoint=None) -> None¶
Delete the stored quota and bucket for one endpoint, or for everything in the backend when endpoint is None.
AsyncRateLimiter¶
smartratelimit.AsyncRateLimiter
Same constructor arguments as RateLimiter. Usable as an async context manager. See Async.
await .arequest_httpx(client, method, url, **kwargs)¶
Paced request through an httpx.AsyncClient. Returns the httpx.Response.
await .arequest_aiohttp(session, method, url, **kwargs)¶
Paced request through an aiohttp.ClientSession. The body is read inside the response context and returned in a wrapper exposing await .json(), await .text(), await .read(), .status, .status_code, .headers, .url. Streaming is not supported on this path.
.get_status(), .set_limit(), .clear()¶
Identical to the sync limiter, and not coroutines โ call them without await.
RateLimitStatus¶
smartratelimit.RateLimitStatus โ a dataclass returned by get_status().
| Attribute | Type | Description |
|---|---|---|
endpoint |
str |
Normalised scheme://host |
limit |
int |
Requests allowed per window |
remaining |
int |
Requests left, as last reported or estimated |
reset_time |
datetime \| None |
When the window resets (UTC, naive) |
window |
timedelta \| None |
Length of the window |
| Property | Type | Description |
|---|---|---|
reset_in |
float \| None |
Seconds until reset, floored at 0; None if reset_time is unset |
is_exceeded |
bool |
remaining <= 0 |
utilization |
float |
1 - remaining/limit, in 0.0โ1.0 (1.0 when limit is 0) |
status = limiter.get_status("api.github.com")
if status and status.utilization > 0.9:
print(f"{status.remaining} left, resets in {status.reset_in:.0f}s")
RateLimitExceeded¶
smartratelimit.RateLimitExceeded โ subclass of Exception.
Raised by request() / arequest_*() when raise_on_limit=True and the bucket is empty. The message includes the wait that would have been taken.
Retry¶
smartratelimit.retry โ also re-exported from the package root.
RetryStrategy¶
An Enum: EXPONENTIAL, LINEAR, FIXED, NONE.
RetryConfig(max_retries=3, strategy=RetryStrategy.EXPONENTIAL, base_delay=1.0, max_delay=60.0, backoff_factor=2.0, retry_on_status=None)¶
retry_on_status defaults to [429, 503, 504]. Every computed delay is capped at max_delay.
RetryHandler(config=None)¶
| Method | Description |
|---|---|
.should_retry(status_code, attempt) -> bool |
Whether this status warrants another attempt |
.retry_sync(func, *args, **kwargs) |
Call func with retries, sleeping between attempts |
await .retry_async(func, *args, **kwargs) |
Same for a coroutine function, awaiting between attempts |
Both retry on a retryable status code and on any raised exception. The final exception propagates; a final non-retryable-but-failed response is returned as-is. Details: Retry Strategies.
MetricsCollector¶
smartratelimit.metrics.MetricsCollector โ also exported from the package root.
| Method | Description |
|---|---|
.record_request(endpoint, status_code, rate_limit_status=None) |
Count one request; optionally append a status snapshot |
.get_metrics(endpoint=None) -> dict |
Counters for one endpoint ({} if unknown) or all |
.export_prometheus() -> str |
Prometheus text exposition format |
.export_json() -> str |
Indented JSON |
.reset(endpoint=None) -> None |
Drop metrics for one endpoint or all |
History is capped at the 100 most recent snapshots per endpoint. See Metrics & Monitoring.
Detector¶
smartratelimit.detector.RateLimitDetector
RateLimitDetector(custom_headers_map=None)¶
.detect_from_response(response) -> dict | None¶
Returns {"limit", "remaining", "reset_time", "window"} or None. Works on any object with .headers, .url and .status_code. Header list and resolution order: Detection & Headers.
Class attributes HEADER_PATTERNS and API_PATTERNS hold the recognised names and per-API profiles.
Storage¶
smartratelimit.storage
StorageBackend¶
Abstract base class. Implement get_rate_limit, set_rate_limit, get_token_bucket, set_token_bucket, clear.
MemoryStorage(cleanup_interval=3600)¶
Thread-safe dicts; expired rate limits are swept no more than once per cleanup_interval seconds.
SQLiteStorage(db_path=":memory:")¶
Creates rate_limits and token_buckets tables on construction. The parent directory must exist.
RedisStorage(redis_url="redis://localhost:6379/0", key_prefix="ratelimit:")¶
Requires redis. Runtime read/write failures degrade quietly โ reads return None, writes are dropped. Keys carry a TTL (window + 1h for limits, 24h for buckets). The raw client is available as .redis_client.
See Storage Backends.
Models¶
smartratelimit.models
RateLimit¶
Internal record โ endpoint, limit, remaining, reset_time, window, last_updated โ with .to_status() returning a RateLimitStatus.
TokenBucket¶
capacity, tokens, refill_rate (tokens/second), last_update.
| Method | Description |
|---|---|
.refill(now=None) |
Add tokens for elapsed time, capped at capacity |
.consume(tokens=1.0, now=None) -> bool |
Refill then take tokens; False if not enough |
.wait_time(tokens=1.0, now=None) -> float |
Seconds until available; inf if refill_rate <= 0 |
.reset() |
Back to full capacity |