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",
    max_keys: int | None = None,
    cleanup_interval: float | None = None,
)

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. max_keys and cleanup_interval bound memory — see Bounded memory.

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, limiter.algorithm ("fixed" or "sliding"), limiter.max_keys and limiter.cleanup_interval. (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):
    ...

Bounded memory

The key is usually a client IP or token — something the caller controls. An unbounded limiter lets a client that mints a fresh key per request grow the map until the process runs out of memory: the library meant to protect you becomes the attack vector. Two options close that:

limiter = RateLimiter(
    limit=100,
    window_seconds=60,
    max_keys=100_000,      # hard cap on tracked keys
    cleanup_interval=60,   # background thread drops expired keys every 60s
)
  • max_keys — when the map is full, expired keys are reclaimed first (at most once per second, so a flood can't make every request pay a full sweep). If it is still full, the new key is rejected (allowed=False, retry_after_seconds=window_seconds) and counted in stats()["rejected_full"]. Keys already tracked are never evicted to make room — evicting would reset a legitimate client's counter on the attacker's behalf.
  • cleanup_interval — a native thread (no GIL) that calls cleanup_expired() on that period. It holds only a weak reference, so it stops on its own when the limiter is garbage-collected.

Both default to None, which keeps the pre-0.3 behaviour (unbounded, manual cleanup). For anything keyed by client input, set both. The FastAPI middleware and FlaskRateLimiter accept the same two keyword arguments; on Django, use "MAX_KEYS" and "CLEANUP_INTERVAL" in RUST_PY_RATE_LIMIT.

The cap is checked without a global lock, so under heavy concurrency the map can overshoot max_keys by at most the number of threads inserting new keys at that same instant.

Statistics

limiter.stats()
# {
#     "allowed": 1200,
#     "blocked": 35,
#     "total_checks": 1235,
#     "active_keys": 20,
#     "rejected_full": 0,   # new keys refused by max_keys (also in "blocked")
# }

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 max_keys (bounded memory) · background cleanup (cleanup_interval) ✅
v0.3.x Token Bucket 🔜
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

Metadata

Release files for rust-py-rate-limit 0.3.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.3.1
File Size Uploaded Provenance Actions
rust_py_rate_limit-0.3.1.tar.gz 45.9 kB With provenance Details for rust_py_rate_limit-0.3.1.tar.gz

Built distributions (wheels)

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

Total release size: 2.4 MB

Release files / rust_py_rate_limit-0.3.1.tar.gz

Download URL rust_py_rate_limit-0.3.1.tar.gz
Size 45.9 kB
Tags Source
SHA-256 checksum
How to use checksums
609cfeea3bbbc950bc8e1de8c8a4d4b6fcdc56fe1056799dd7c70ce5e08efff0
BLAKE2b-256 checksum
How to use checksums
4c55c0df47a22abedfe3ab60209028794d3ecac7e024c669e7117f3985d4e2a6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 20, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.3.1-cp310-abi3-win_amd64.whl
Size 185.0 kB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
2f021c93df0f156db43b9de0f6de83ef7a693c07d42cd7382f8619d437156d12
BLAKE2b-256 checksum
How to use checksums
58dc632df7862ff2094c5ac8e49e3338bf8dfd5d2d9141500826244070d6dead
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 20, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.3.1-cp310-abi3-musllinux_1_2_x86_64.whl
Size 515.0 kB
Tags CPython 3.10 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
befa7cf64415cfe48a95a8711de4d79813d0a95311eb1a8daabb327fc5207c9a
BLAKE2b-256 checksum
How to use checksums
28b3d37cb970862afe44624fc8c7a89c06cce036ace93394dfc8988623cedc58
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 20, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.3.1-cp310-abi3-musllinux_1_2_aarch64.whl
Size 479.5 kB
Tags CPython 3.10 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
19e20913f11459d77b3e865e6fa2f7372d1c435544f76805b3b04f9bbe162ebb
BLAKE2b-256 checksum
How to use checksums
f17eb2f08361d1f9d578698ceae56d8f1906b3dad0ac60640d8a796fc257f778
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 20, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.3.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 304.0 kB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
7e50970496e91ccac56bcfb973730d1d4e5369f6310f34e0417f060975825c1d
BLAKE2b-256 checksum
How to use checksums
878bbaf48ec0efe15f9c98da1da45e9f9a88cebde5d4fc27795890ffe2f633f3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 20, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.3.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 302.1 kB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
1a7ca422c027528e3341485ebc82e4e89caabcd8cbe5d2a62c27ff497e23063e
BLAKE2b-256 checksum
How to use checksums
7d0f723e715830ca378e136163b302b39f4468619e0d853932dfa76267438e7e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 20, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.3.1-cp310-abi3-macosx_11_0_arm64.whl
Size 271.4 kB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
b294929ab160c05ea1f673c4622d16ebd883810bd542d279660b0613597a99d8
BLAKE2b-256 checksum
How to use checksums
61fde38df10208edcf1ee6559d54111243a0bf4cb3190766eb76a4351e06ab1d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 20, 2026.

Transparency log

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

Download URL rust_py_rate_limit-0.3.1-cp310-abi3-macosx_10_12_x86_64.whl
Size 280.3 kB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
b9e07c32506e7f272c3cb41bf0d2490a87d6202504f340f788b7bec91e8bc9c5
BLAKE2b-256 checksum
How to use checksums
50c0b8359bb360a14bb5614efc4caaba5a5aaede6c39ce9aca65d8776dfb15fc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 20, 2026.

Transparency log

Security for rust-py-rate-limit 0.3.1

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.

Full provenance

All 8 files in this release have been uploaded with provenance metadata.

Select from 8 files

Attestation for rust_py_rate_limit-0.3.1.tar.gz

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 Oct 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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