Rate Limiting Utilities

raw JSON →
5.8.0 verified Tue May 12 auth: no python install: verified

limits is a Python library for rate limiting via multiple strategies with commonly used storage backends such as Redis, Memcached, MongoDB, and Valkey. It provides identical APIs for use in synchronous and asynchronous codebases, enabling robust control over request rates. The library maintains an active development status with regular releases, with version 5.8.0 being the latest stable release at the time of verification.

pip install limits
error ModuleNotFoundError: No module named 'limits'
cause The 'limits' library is not installed in your Python environment or is not accessible via the Python path.
fix
Install the library using pip: pip install limits
error ConfigurationError: redis library is not available
cause The specified storage backend (Redis in this case) requires its corresponding Python client library to be installed, which is missing, or the connection URI is invalid/unreachable.
fix
Install the necessary backend client (e.g., pip install redis for Redis) and ensure your connection URI is correct and the server is accessible.
error limits.errors.RateLimitExceeded: 5 per 1 minute
cause The configured rate limit for a specific operation or endpoint has been exceeded, preventing further execution until the limit resets.
fix
This is an expected behavior of rate limiting. To handle it gracefully, catch the RateLimitExceeded exception and implement retry logic, inform the user to wait, or adjust your application's request rate. For example, try: ... except RateLimitExceeded: print('Rate limit hit, please wait.')
error TypeError: 'Rate' object is not callable
cause This error can occur if you attempt to call a `Rate` object directly as a function, rather than using it as an argument for rate limiting functions or decorators.
fix
Ensure you are passing the Rate object to the appropriate limits function or decorator (e.g., limiter.hit(rate)) instead of trying to execute it like a function.
error AttributeError: 'X' object has no attribute 'Y'
cause You are attempting to access an attribute or method on a `limits` object (e.g., a `Storage` or `Limiter` instance) that does not exist or is misspelled. This can also happen with incorrect usage of decorators.
fix
Review the limits library's API documentation for the specific object you are using to ensure you are calling the correct attributes or methods and that your object is correctly initialized.
breaking Version 5.0.0 introduced several backward-incompatible changes, including dropping support for the `Fixed Window with Elastic Expiry` strategy and the `etcd` storage backend. Additionally, the default implementation for `async+memcached` was changed from `emcache` to `memcachio`.
fix Review your application's rate limiting strategies and storage configurations. If using `Fixed Window with Elastic Expiry` or `etcd`, migrate to an alternative strategy/storage. If using `async+memcached`, ensure `memcachio` is compatible or manually configure `emcache` if preferred and still supported via an extra.
gotcha Using `MemoryStorage` (the default in basic examples) in a multi-process or distributed environment will not provide a global rate limit. Each process will have its own independent rate counter, which can lead to limits being exceeded across the entire system.
fix For production or distributed applications, configure a shared storage backend such as Redis, Memcached, or MongoDB. These backends enable centralized rate limit tracking across multiple application instances.
gotcha When migrating from `limits` versions prior to 5.6.0, note that project metadata moved to `pyproject.toml` and the project now uses `hatch` for package builds. While this primarily affects contributors, it's worth noting for build system interactions.
fix For development or build automation, ensure your tooling is compatible with `pyproject.toml` and `hatch`. Direct users are generally unaffected unless they interact with the build system.
deprecated Older versions of `limits` (e.g., prior to v2.7.0 and v2.4.0) had specific requirements for `redis` and `coredis` versions. While current versions relax these, always verify compatibility.
fix Always check the `limits` library's `requires` and `Provides-Extra` on PyPI or the `pyproject.toml` for the most up-to-date dependency compatibility, especially when upgrading or installing specific optional backends.
pip install 'limits[redis]' # For Redis storage
pip install 'limits[memcached]' # For Memcached storage
pip install 'limits[mongodb]' # For MongoDB storage
pip install 'limits[async-redis]' # For Async Redis storage
pip install 'limits[all]' # Installs all optional dependencies
python os / libc variant status wheel install import disk
3.10 alpine (musl) all - - - -
3.10 alpine (musl) all - - - -
3.10 alpine (musl) async-redis - - - -
3.10 alpine (musl) async-redis - - - -
3.10 alpine (musl) memcached - - - -
3.10 alpine (musl) memcached - - - -
3.10 alpine (musl) mongodb - - - -
3.10 alpine (musl) mongodb - - - -
3.10 alpine (musl) redis - - - -
3.10 alpine (musl) redis - - - -
3.10 alpine (musl) limits wheel - 0.27s 20.1M
3.10 alpine (musl) limits - - 0.28s 19.9M
3.10 slim (glibc) all - - - -
3.10 slim (glibc) all - - - -
3.10 slim (glibc) async-redis - - - -
3.10 slim (glibc) async-redis - - - -
3.10 slim (glibc) memcached - - - -
3.10 slim (glibc) memcached - - - -
3.10 slim (glibc) mongodb - - - -
3.10 slim (glibc) mongodb - - - -
3.10 slim (glibc) redis - - - -
3.10 slim (glibc) redis - - - -
3.10 slim (glibc) limits wheel 2.3s 0.19s 21M
3.10 slim (glibc) limits - - 0.19s 20M
3.11 alpine (musl) all - - - -
3.11 alpine (musl) all - - - -
3.11 alpine (musl) async-redis - - - -
3.11 alpine (musl) async-redis - - - -
3.11 alpine (musl) memcached - - - -
3.11 alpine (musl) memcached - - - -
3.11 alpine (musl) mongodb - - - -
3.11 alpine (musl) mongodb - - - -
3.11 alpine (musl) redis - - - -
3.11 alpine (musl) redis - - - -
3.11 alpine (musl) limits wheel - 0.33s 22.3M
3.11 alpine (musl) limits - - 0.38s 22.0M
3.11 slim (glibc) all - - - -
3.11 slim (glibc) all - - - -
3.11 slim (glibc) async-redis - - - -
3.11 slim (glibc) async-redis - - - -
3.11 slim (glibc) memcached - - - -
3.11 slim (glibc) memcached - - - -
3.11 slim (glibc) mongodb - - - -
3.11 slim (glibc) mongodb - - - -
3.11 slim (glibc) redis - - - -
3.11 slim (glibc) redis - - - -
3.11 slim (glibc) limits wheel 2.2s 0.29s 23M
3.11 slim (glibc) limits - - 0.30s 23M
3.12 alpine (musl) all - - - -
3.12 alpine (musl) all - - - -
3.12 alpine (musl) async-redis - - - -
3.12 alpine (musl) async-redis - - - -
3.12 alpine (musl) memcached - - - -
3.12 alpine (musl) memcached - - - -
3.12 alpine (musl) mongodb - - - -
3.12 alpine (musl) mongodb - - - -
3.12 alpine (musl) redis - - - -
3.12 alpine (musl) redis - - - -
3.12 alpine (musl) limits wheel - 0.55s 14.1M
3.12 alpine (musl) limits - - 0.58s 13.9M
3.12 slim (glibc) all - - - -
3.12 slim (glibc) all - - - -
3.12 slim (glibc) async-redis - - - -
3.12 slim (glibc) async-redis - - - -
3.12 slim (glibc) memcached - - - -
3.12 slim (glibc) memcached - - - -
3.12 slim (glibc) mongodb - - - -
3.12 slim (glibc) mongodb - - - -
3.12 slim (glibc) redis - - - -
3.12 slim (glibc) redis - - - -
3.12 slim (glibc) limits wheel 2.0s 0.59s 15M
3.12 slim (glibc) limits - - 0.54s 14M
3.13 alpine (musl) all - - - -
3.13 alpine (musl) all - - - -
3.13 alpine (musl) async-redis - - - -
3.13 alpine (musl) async-redis - - - -
3.13 alpine (musl) memcached - - - -
3.13 alpine (musl) memcached - - - -
3.13 alpine (musl) mongodb - - - -
3.13 alpine (musl) mongodb - - - -
3.13 alpine (musl) redis - - - -
3.13 alpine (musl) redis - - - -
3.13 alpine (musl) limits wheel - 0.55s 13.8M
3.13 alpine (musl) limits - - 0.57s 13.5M
3.13 slim (glibc) all - - - -
3.13 slim (glibc) all - - - -
3.13 slim (glibc) async-redis - - - -
3.13 slim (glibc) async-redis - - - -
3.13 slim (glibc) memcached - - - -
3.13 slim (glibc) memcached - - - -
3.13 slim (glibc) mongodb - - - -
3.13 slim (glibc) mongodb - - - -
3.13 slim (glibc) redis - - - -
3.13 slim (glibc) redis - - - -
3.13 slim (glibc) limits wheel 2.3s 0.53s 14M
3.13 slim (glibc) limits - - 0.54s 14M
3.9 alpine (musl) all - - - -
3.9 alpine (musl) all - - - -
3.9 alpine (musl) async-redis - - - -
3.9 alpine (musl) async-redis - - - -
3.9 alpine (musl) memcached - - - -
3.9 alpine (musl) memcached - - - -
3.9 alpine (musl) mongodb - - - -
3.9 alpine (musl) mongodb - - - -
3.9 alpine (musl) redis - - - -
3.9 alpine (musl) redis - - - -
3.9 alpine (musl) limits wheel - 0.22s 19.3M
3.9 alpine (musl) limits - - 0.23s 19.3M
3.9 slim (glibc) all - - - -
3.9 slim (glibc) all - - - -
3.9 slim (glibc) async-redis - - - -
3.9 slim (glibc) async-redis - - - -
3.9 slim (glibc) memcached - - - -
3.9 slim (glibc) memcached - - - -
3.9 slim (glibc) mongodb - - - -
3.9 slim (glibc) mongodb - - - -
3.9 slim (glibc) redis - - - -
3.9 slim (glibc) redis - - - -
3.9 slim (glibc) limits wheel 2.7s 0.20s 20M
3.9 slim (glibc) limits - - 0.20s 20M

This quickstart demonstrates how to set up a simple rate limit using in-memory storage. It defines a rate limit, initializes a fixed-window rate limiter, and then simulates multiple actions for a user, demonstrating how the `test` and `hit` methods interact with `RateLimitExceeded` exceptions. It also shows how to get remaining quota and reset time.

from limits import parse, strategies, storage, RateLimitExceeded
import time
import os

# 1. Initialize a storage backend
# For simplicity, using in-memory storage. For production, use Redis/Memcached.
# A Redis example:
# REDIS_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379')
# store = storage.RedisStorage(REDIS_URL)

store = storage.MemoryStorage()

# 2. Define a rate limit (e.g., 5 requests per minute)
# parse() converts a string like '5/minute' into a RateLimitItem
rate_limit_string = '5/minute'
rate_limit = parse(rate_limit_string)

# 3. Initialize a rate limiter strategy
# FixedWindowRateLimiter is a common strategy
limiter = strategies.FixedWindowRateLimiter(store)

def do_limited_action(user_id):
    try:
        # 4. Test the limit for a specific identifier (e.g., user_id)
        if limiter.test(rate_limit, user_id):
            # 5. Consume the limit (i.e., record a hit)
            limiter.hit(rate_limit, user_id)
            print(f"[{user_id}] Action performed at {time.strftime('%H:%M:%S')}. Remaining: {limiter.get_remaining(rate_limit, user_id)}")
        else:
            raise RateLimitExceeded(rate_limit)
    except RateLimitExceeded as e:
        # Query available capacity and reset time
        remaining = limiter.get_remaining(e.limit, user_id)
        reset_at = limiter.get_reset_time(e.limit, user_id)
        print(f"[{user_id}] Rate limit exceeded for {e.limit}. Try again in {reset_at - time.time():.1f} seconds. Remaining: {remaining}")

# Simulate some requests
user = "test_user_1"
print(f"--- Simulating requests for {user} ({rate_limit_string}) ---")
for i in range(7):
    do_limited_action(user)
    time.sleep(1) # Simulate some delay

print("\n--- Waiting for reset time ---")
time.sleep(60)

print("\n--- Simulating requests after reset ---")
for i in range(3):
    do_limited_action(user)
    time.sleep(1)