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.2

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.2
File Size Uploaded
rust_py_rate_limit-0.2.2.tar.gz 39.3 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for rust-py-rate-limit 0.2.2
File
rust_py_rate_limit-0.2.2-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
rust_py_rate_limit-0.2.2-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.2-cp310-abi3-musllinux_1_2_aarch64.whl CPython 3.10 abi3 Linux musl 1.2+ ARM64 Details
rust_py_rate_limit-0.2.2-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.2-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.2-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
rust_py_rate_limit-0.2.2-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.2.tar.gz

Download URL rust_py_rate_limit-0.2.2.tar.gz
Size 39.3 kB
Tags Source
SHA-256 checksum
How to use checksums
29f4504235d113475c7d7267591b2aaf2bb1d95282699bb7af9c05f83fa8e236
BLAKE2b-256 checksum
How to use checksums
f0676bef50522fd80f97e121da7236dd35c46926de44ebc294f978dfa50c7e50
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 12, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.2.2-cp310-abi3-win_amd64.whl
Size 157.8 kB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
92241b0069558e45e9937b0c1fad0626fb23c28db4bb7b554eb4da55d20a5d70
BLAKE2b-256 checksum
How to use checksums
52e0393e7d645655f65c264f607f3bf892c64069c6992ce5d3ece8a9c77543f2
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 12, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.2.2-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
e8a01f581c2c63c18d941779cf24e0936fa377ccb677f1926de9e1818ae60e43
BLAKE2b-256 checksum
How to use checksums
5945e33566326a131d922e2b459e122bc6092a6d208e5a3f8848444d1fce6a63
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 12, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.2.2-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
15bb403b222582973519515157c33ff14e8736450a8f04000ff734257a20a868
BLAKE2b-256 checksum
How to use checksums
6255335d7b5bff6c74226d672375af2d482c41d86325ede93e5dd1d4909d1bfe
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 12, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.2.2-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
02925589dcb9682f80dc6e29d9144aca960ffc4cfc049703cf6474090a509dde
BLAKE2b-256 checksum
How to use checksums
0660dca84a83cfd5b2919d4f6096b60d247f849abd424da2078eb45febb8319e
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 12, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.2.2-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
96867b83f2a34d4066acc29538ef320514a01524038f30b74fa8782cfcb7c90e
BLAKE2b-256 checksum
How to use checksums
884f9a3b37d78827eb55f6d5b67853b1565dbd3fb22a5c5379cad467072cb075
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 12, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.2.2-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
e1fdbc710d0f0a5a85354743ec5c0c4e2ef63289cbbfd3a09b40a9fb107705f4
BLAKE2b-256 checksum
How to use checksums
f38c0d27ff8b16c0f3bafdb8a2f8fc0c600c8c10beedf39fc5c7dd9f8b484c99
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 12, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.2.2-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
4b88151b1f1c2629a68ee17503112e3c6bc354480dc3287b7ec4346f61fe198a
BLAKE2b-256 checksum
How to use checksums
5b1a6b3a1389ce1cca05abefc65ba72f534031f1bd106f2f16116d080dc4a543
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 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.2 This release

8 release files

0.2.1

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