Skip to main content

rust-py-rate-limit

Fast local rate limiting for Python, powered by Rust.

🌐 Website: rust-py-rate-limit.vercel.app

A fast, thread-safe, in-process rate limiter for Python with a core written in Rust (via PyO3 + maturin). Use it to protect endpoints, functions, internal APIs, workers and backend scripts against bursts of traffic — with zero external services.

from rust_py_rate_limit import RateLimiter

limiter = RateLimiter(limit=10, window_seconds=60)

if limiter.allow("user:123"):
    print("allowed")
else:
    print("blocked")

Table of contents


What is this?

rust-py-rate-limit is a local (in-process) rate limiter. Every limiter instance keeps its counters in memory inside your Python process, guarded by a concurrent, sharded hash map on the Rust side. There is no Redis, no network hop, and no serialization on the hot path — just a couple of atomic operations per request.

It works anywhere Python runs:

  • Plain Python
  • FastAPI
  • Django
  • Flask (preview)
  • Background workers and scripts

Why Rust?

  • Speed — the counting logic is compiled native code; the hot path releases the GIL so multiple Python threads can check limits in parallel.
  • Safety — no data races by construction. State lives in a DashMap (a sharded concurrent map) and statistics use lock-free atomics, so there is no global lock on the critical path.
  • Simplicity — a tiny, predictable API surface that is hard to misuse.

Installation

pip install rust-py-rate-limit

Requires Python 3.10+. Wheels are published for Linux, macOS and Windows, so no Rust toolchain is needed to install.

Quick start

from rust_py_rate_limit import RateLimiter

limiter = RateLimiter(limit=3, window_seconds=60)

assert limiter.allow("ip:127.0.0.1") is True
assert limiter.allow("ip:127.0.0.1") is True
assert limiter.allow("ip:127.0.0.1") is True
assert limiter.allow("ip:127.0.0.1") is False   # limit reached

Opt into the Sliding Window algorithm to smooth bursts at the window boundary (the default is "fixed"):

limiter = RateLimiter(limit=100, window_seconds=60, algorithm="sliding")
limiter.algorithm  # "sliding"

How Fixed Window works

The default algorithm is Fixed Window. Each key gets a counter and a window start time. Within a window of window_seconds, up to limit requests are admitted; once the window elapses, the counter resets.

limit = 3, window = 60s, key = "user:1"

request 1 -> allowed
request 2 -> allowed
request 3 -> allowed
request 4 -> blocked
... 60s later ...
request 5 -> allowed   (new window)

Fixed Window is simple and cheap. Its only caveat is that it can admit up to 2 * limit requests around a window boundary (a burst at the end of one window plus a burst at the start of the next). If you need stricter smoothing, use algorithm="sliding" (below).

How Sliding Window works

Set algorithm="sliding" for the sliding window counter, which removes the boundary doubling. It keeps the count for the current aligned window plus the previous one, and weights the previous window by how much of it still overlaps the trailing window_seconds ending at now:

estimated = previous_count * weight + current_count
weight    = (window_seconds - elapsed_in_current_window) / window_seconds

A request is admitted while estimated < limit. This is O(1) in time and memory per key (unlike a sliding log, which stores every timestamp) and smooths the burst at the boundary, at the cost of being an approximation rather than an exact count.

limiter = RateLimiter(limit=10, window_seconds=60, algorithm="sliding")
# A full burst at the end of one window leaves far fewer slots right after the
# boundary, instead of a fresh `limit` as Fixed Window would.

API reference

RateLimiter(limit: int, window_seconds: int, algorithm: str = "fixed")

limit and window_seconds must be positive integers (passing 0 or a negative value raises ValueError). algorithm selects the strategy — "fixed" (default) or "sliding"; any other value raises ValueError.

Method Returns Description
allow(key: str) bool Consume one request. True if admitted, False if blocked.
check(key: str) dict Consume one request and return full detail (see below).
remaining(key: str) int Requests left in the current window without consuming one.
reset(key: str) bool Drop a key's state. True if it existed.
clear() None Drop all keys.
stats() dict Activity counters (see Statistics).
cleanup_expired() int Remove keys whose window has expired. Returns the count removed.

Read-only properties: limiter.max_requests, limiter.window_seconds and limiter.algorithm ("fixed" or "sliding"). (The configured limit is max_requests, since .limit(...) is the decorator.)

check() return value

Allowed:

{
    "allowed": True,
    "limit": 100,
    "remaining": 99,
    "reset_after_seconds": 60,
    "retry_after_seconds": 0,
}

Blocked:

{
    "allowed": False,
    "limit": 100,
    "remaining": 0,
    "reset_after_seconds": 42,
    "retry_after_seconds": 42,
}

FastAPI

Manual check

from fastapi import FastAPI, Request, HTTPException
from rust_py_rate_limit import RateLimiter

app = FastAPI()
limiter = RateLimiter(limit=100, window_seconds=60)

@app.get("/api/users")
def list_users(request: Request):
    key = request.client.host
    if not limiter.allow(key):
        raise HTTPException(status_code=429, detail="Too many requests")
    return {"users": []}

Middleware

from rust_py_rate_limit.fastapi import RateLimitMiddleware

app.add_middleware(
    RateLimitMiddleware,
    limit=100,
    window_seconds=60,
    key_func=lambda request: request.client.host,
)

When a request is blocked the middleware responds with 429 and {"detail": "Too many requests"}. Every response carries the standard headers:

X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
Retry-After      (only when blocked)

Django

# settings.py
MIDDLEWARE = [
    # ...
    "rust_py_rate_limit.django.RateLimitMiddleware",
]

RUST_PY_RATE_LIMIT = {
    "LIMIT": 100,
    "WINDOW_SECONDS": 60,
    "KEY": "ip",  # "ip" or "user"
}

Or check manually in a view:

from django.http import JsonResponse
from rust_py_rate_limit import RateLimiter

limiter = RateLimiter(limit=100, window_seconds=60)

def my_view(request):
    key = request.META.get("REMOTE_ADDR")
    if not limiter.allow(key):
        return JsonResponse({"detail": "Too many requests"}, status=429)
    return JsonResponse({"ok": True})

Flask

from flask import Flask
from rust_py_rate_limit.flask import FlaskRateLimiter

app = Flask(__name__)
limiter = FlaskRateLimiter(app, limit=100, window_seconds=60)

@app.get("/api/users")
@limiter.limit()
def list_users():
    return {"users": []}

Decorator

from rust_py_rate_limit import RateLimiter, RateLimitExceeded

limiter = RateLimiter(limit=5, window_seconds=60)

@limiter.limit("login")
def login():
    return "ok"

When the limit is exceeded the decorated function raises RateLimitExceeded (which carries .key, .limit and .retry_after). The key may also be a callable that derives the key from the function's arguments:

@limiter.limit(lambda user_id: f"user:{user_id}")
def fetch(user_id):
    ...

Statistics

limiter.stats()
# {
#     "allowed": 1200,
#     "blocked": 35,
#     "total_checks": 1235,
#     "active_keys": 20,
# }

Limitations

Be honest with yourself about what an in-process limiter can and cannot do:

  • The rate-limit state is local to the process.
  • Under Gunicorn/Uvicorn with multiple workers, each worker keeps its own counters, so the effective global limit is roughly limit × workers.
  • It is not a replacement for Redis when you need distributed rate limiting.
  • Fixed Window can allow short bursts at the boundary between two windows; use algorithm="sliding" to smooth them.
  • For distributed production setups, a Redis/Postgres backend is planned (see the roadmap).

Roadmap

Version Highlights Status
v0.1.0 Fixed Window · allow/check/remaining/reset/clear/stats/cleanup_expired · pytest · README ✅
v0.1.5 Decorator · FastAPI/Django/Flask middleware · HTTP headers ✅
v0.2.0 Sliding Window (algorithm="sliding") ✅
v0.3.0 Token Bucket · background cleanup 🔜
v0.4.0 Redis backend · distributed rate limiting 🔜
v0.5.0 Prometheus metrics · ImmutableLog integration 🔜

Development

# Rust unit tests
cargo test

# Build the extension into a virtualenv and run the Python tests
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"      # or: pip install maturin && maturin develop
maturin develop
pytest

License

MIT © Roberto Lima

Release files for rust-py-rate-limit 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rust-py-rate-limit 0.2.1
File Size Uploaded
rust_py_rate_limit-0.2.1.tar.gz 35.4 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for rust-py-rate-limit 0.2.1
File
rust_py_rate_limit-0.2.1-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
rust_py_rate_limit-0.2.1-cp310-abi3-musllinux_1_2_x86_64.whl CPython 3.10 abi3 Linux musl 1.2+ x86-64 Details
rust_py_rate_limit-0.2.1-cp310-abi3-musllinux_1_2_aarch64.whl CPython 3.10 abi3 Linux musl 1.2+ ARM64 Details
rust_py_rate_limit-0.2.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
rust_py_rate_limit-0.2.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
rust_py_rate_limit-0.2.1-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
rust_py_rate_limit-0.2.1-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 2.2 MB

Release files / rust_py_rate_limit-0.2.1.tar.gz

Download URL rust_py_rate_limit-0.2.1.tar.gz
Size 35.4 kB
Tags Source
SHA-256 checksum
How to use checksums
2a6998c4da056b93dc707d64a98e4e35331709e982af7d52cec603a0d9f79743
BLAKE2b-256 checksum
How to use checksums
e68d5b157de129ebe9f7fc84293750aa85096fa1f7bc9820078d83930d067414
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release files / rust_py_rate_limit-0.2.1-cp310-abi3-win_amd64.whl

Download URL rust_py_rate_limit-0.2.1-cp310-abi3-win_amd64.whl
Size 157.7 kB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
f6f7f7be780019cd75fbc6b13ece8aa10219486c8d6ed5fa6d30270e5b6ff2c7
BLAKE2b-256 checksum
How to use checksums
530cca04aa2b2eb744ea470b06b585b5c31006bc93b3b64f3287b3c9b96a176f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release files / rust_py_rate_limit-0.2.1-cp310-abi3-musllinux_1_2_x86_64.whl

Download URL rust_py_rate_limit-0.2.1-cp310-abi3-musllinux_1_2_x86_64.whl
Size 491.4 kB
Tags CPython 3.10 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
cda3e2d3e34d77a690e1bc6db3c5272d4125c5332584f5e507ec885e2afbe537
BLAKE2b-256 checksum
How to use checksums
330469136b53edeb72e46b2d692722508f79f181b1af642f4fecb72eb9e8a538
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release files / rust_py_rate_limit-0.2.1-cp310-abi3-musllinux_1_2_aarch64.whl

Download URL rust_py_rate_limit-0.2.1-cp310-abi3-musllinux_1_2_aarch64.whl
Size 456.2 kB
Tags CPython 3.10 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
7d4b5daf37432164b2bfa2389a0706c8d2e0eb961494a4f01a0a0bb4a9032408
BLAKE2b-256 checksum
How to use checksums
a6124c23fcd050f0e6cd395c9e84297db570741391f8fd607427668104712988
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release files / rust_py_rate_limit-0.2.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL rust_py_rate_limit-0.2.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 279.6 kB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
ff7051ba76dc130bd0438bde1059d3957dfafaa1cbf4502ceec9ddc17777b048
BLAKE2b-256 checksum
How to use checksums
25eb9bdc72bb4ec7a3583db33bdf67718d9caca4bbd0771162e7f2b80ffaf0db
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release files / rust_py_rate_limit-0.2.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL rust_py_rate_limit-0.2.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 278.9 kB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
0d50dac0429389821f93487e8f4c19476679bb59e604250fa27a9a48af02b98e
BLAKE2b-256 checksum
How to use checksums
b0787427d9d0038202ef6ff1245ee0baff226c4071cbac2c7f6201c98996e983
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release files / rust_py_rate_limit-0.2.1-cp310-abi3-macosx_11_0_arm64.whl

Download URL rust_py_rate_limit-0.2.1-cp310-abi3-macosx_11_0_arm64.whl
Size 249.1 kB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
0abfa8bc5c9ba77964413f1a6154d48554e1d7df14fbf49a325a30c016db6fbf
BLAKE2b-256 checksum
How to use checksums
e6b2ef1e9a8ac35f0b172ed670100c6d54b8e1944fe78ab417fa28e069c34b76
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release files / rust_py_rate_limit-0.2.1-cp310-abi3-macosx_10_12_x86_64.whl

Download URL rust_py_rate_limit-0.2.1-cp310-abi3-macosx_10_12_x86_64.whl
Size 256.9 kB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
203b24fc2f563eb2e48a78f11c3a2738858fe0333b4590263a55d2274a60eb4e
BLAKE2b-256 checksum
How to use checksums
5e4810741a1c5dda0452337ec46ff8d257c621bc4ee768072721de499b0a68fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.2

8 release files

This release

0.2.1 This release

8 release files

0.2.0

4 release files

0.1.5

4 release files

0.1.4

4 release files

0.1.1

4 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page