NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #1373 most downloaded on PyPI
Python rate limiter with pluggable algorithms and backends
Last release 1 months ago
30 Aug 2026
Release timing varies
gaps range from 2 weeks to 10 months
Most releases are documented
notes for 51 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
7 years old
64 releases · first in 2019
feat: Add httpx2 support by @frostyfeet909 in #309
Full Changelog: v4.4.0...v4.5.0
Pluggable rate-limiting algorithms. Additive — no breaking public API changes; the default behaviour of every existing bucket is unchanged.
GCRA / TokenBucket algorithms, and StateBucket to run them. These
keep a couple of numbers per key instead of one entry per consumed unit, so
storage does not grow with traffic and the wait is exact without any lookup.
TokenBucket is GCRA under a familiar name — one implementation, not two.
from pyrate_limiter import Duration, Limiter, Rate, StateBucket, TokenBucket
limiter = Limiter(StateBucket([Rate(5, Duration.SECOND, burst=10)], algorithm=TokenBucket()))
Stores: InMemoryStateStore, MultiprocessStateStore, and RedisStateStore
(transition runs as a Lua script, so the read-modify-write is atomic across
clients; keys carry a TTL and need no leak()). For a 1000/minute limit at
saturation the Redis state is ~100 bytes against roughly 89 KB of sorted set.
GCRA's state is integer microseconds rather than fractional milliseconds:
accumulating a fractional emission interval onto an absolute timestamp loses
the low bits, which would reject the last unit of a full burst.
Rate(..., burst=N) — how many units may be spent at once. Read only by
the constant-state algorithms; defaults to limit, which is classic
token-bucket behaviour. burst=1 is a perfectly smooth drip.
WallClock — epoch-millisecond clock, for state compared across machines
where a monotonic clock is meaningless. RedisStateStore defaults to it.
limiter_factory.create_token_bucket_limiter().
FixedWindow algorithm. Counts within a wall-clock-aligned window that
resets every interval, rather than a rolling one. Pass it to any built-in
bucket: InMemoryBucket(rates, algorithm=FixedWindow()). Cheaper and coarser
than the default — up to 2 * limit can pass across a boundary — and the
right choice for mirroring an upstream API that genuinely resets on the hour.
Works on all five backends.
Every built-in bucket now takes an algorithm= argument, defaulting to
SlidingWindowLog(). Existing code is unaffected.
Decision now carries retry_after_ms alongside the verdict, and put()
records it. AbstractBucket.waiting() reads that recording instead of
deriving the wait from storage a second time. Custom buckets that record
nothing keep working: waiting() falls back to the previous peek()-based
derivation.
AbstractBucket.put_decision() returns the full Decision for a put, so the
verdict and the retry-after arrive together rather than via the
failing_rate attribute plus a follow-up waiting() call.
Algorithm, LogAlgorithm, Decision and SlidingWindowLog are exported
from the package root.
pip install "pyrate-limiter[all]" — the command the README has always
documented — previously resolved to nothing: the project declared only PEP 735
[dependency-groups], which pip cannot reach through extras syntax, so the
install emitted WARNING: does not provide the extra 'all' and then failed at
import redis. redis, postgres, filelock and all now all work.put() now clears failing_rate. Every other
backend already did; SQLite left the last denial standing indefinitely.ZRANGE round
trip to learn how long to wait — and the wait can no longer be computed
against a sorted set that moved in between.EXCLUSIVE
table lock as the check, removing both a round trip and that same race.put()'s existing lock hold, so
the background Leaker cannot delete rows between the verdict and the wait.put() already performs — no second scan, and no allocation on the admit path.GCRA, so the term is reserved for it.StateAlgorithm declares redis_args(rates) so a policy's Lua script and its
arguments stay a matched pair it owns. RedisStateStore passes them through
without inspecting them, rather than assuming GCRA's shape.Algorithm.max_weight(rate) is the one place asking whether a weight can ever
be admitted — rate.limit for the window algorithms, rate.burst for GCRA.Algorithm now has two sub-interfaces: LogAlgorithm (an entry per consumed
unit) and StateAlgorithm (a fixed tuple of numbers). StateAlgorithm.step()
must evaluate every rate before committing any of them, so a rate failing late
never leaves an earlier one debited.LogAlgorithm out of Algorithm for policies whose state is a log of
timestamped items. leak_bound() and the new blocking_offset() /
retry_after() hooks live there; constant-state policies (token bucket, GCRA)
will not implement it.+1 boundary correction now lives in exactly one place
(SlidingWindowLog.retry_after) instead of being inlined in waiting().One column per quarter.
fix: leaker/InMemory data race, Postgres clock fallback, leaker restart by @vutran1710 in #302
Full Changelog: v4.3.1...v4.4.0
Bug-fix, scalability, and internal-refactor release. No public API changes
(the new AbstractBucket.is_async attribute is additive).
Leaker thread can no longer race put/peek/leak. This was a
data race in the default configuration (in-memory bucket + scheduled leak).
MultiprocessBucket aliases this lock to its shared cross-process lock. (#302)RuntimeError: threads can only be started once. (#302)Limiter picklable after the InMemoryBucket lock addition. (#302)ZADDs in bounded chunks inside the atomic
Lua script, lowering latency for high-weight puts. (#284)is_async bucket attribute so the Leaker no longer detects
async by executing a side-effecting leak(0) probe. RedisBucket still probes
because it may wrap either a sync or an async client. (#305)Algorithm/Decision seam (SlidingWindowLog) that the
built-in buckets delegate their per-rate admit decision and leak bound to —
the foundation for pluggable algorithms (e.g. GCRA, sliding-window-counter) in
a future release. (#307)RedisBucket keeps one sorted-set member per consumed unit, and
that long-window / high-volume quotas may want a coarser counter-based backend
for bounded memory. (#284)dist/* artifacts, in addition to publishing to PyPI.GitHub Actions moved off the deprecated Node-20 runtime to Node-24.
Performance and maintenance release. No API or behavior changes.
generate_series) instead of one INSERT per unit: ~3.4× faster, shorter EXCLUSIVE lock hold. (#296)COUNT(*) FILTER) instead of one round trip per rate: ~2× faster multi-rate checks. (#297)SingleBucketFactory.wrap_item — inlined sync fast path (no per-acquire closures), ~23% faster wrapping. (#296)_delay_waiter. (#294)Full changelog: https://github.com/vutran1710/PyrateLimiter/blob/master/CHANGELOG.md
Performance and maintenance release. No API or behavior changes.
weight unit-rows in a single statement
(SELECT … FROM generate_series) instead of one INSERT per unit — ~3.4×
faster weighted puts and a shorter EXCLUSIVE lock hold. (#296)COUNT(*) FILTER) instead of one round trip per rate — ~2× faster
multi-rate checks, fewer round trips under the lock. (#297)SingleBucketFactory.wrap_item: inline the sync fast path (no
per-acquire closures) — ~23% faster item wrapping on the hot path. (#296)_delay_waiter into a single
shared helper. (#294)checkout@v5, setup-python@v6, setup-uv@v7, upload-artifact@v6,
download-artifact@v7). (#295)Bug-fix and hardening release. Contains a few breaking changes that affect only edge/undocumented usage — see below.
Bug-fix and hardening release. Contains a few breaking changes that affect only edge/undocumented usage — see below.
ValueError at construction instead of being silently mis-enforced (must be ordered by increasing interval, with increasing limits and non-increasing density). (#239)binary_search removed from the public API (undocumented internal helper, now stdlib bisect). (#290)PgQueries SQL templates use bound %s parameters instead of {offset}/{timestamp}. (#233)try_acquire no longer busy-spins or spuriously times out with buffer_ms=0 (waiting() now clears the inclusive window boundary). (#289)try_acquire_async(timeout=0) succeeds when capacity is available instead of always returning False. (#289)leak() no longer crashes on a closed connection during teardown. (#244)leak() bug with unsorted rates on Redis/SQLite/Postgres. (#239)binary_search with stdlib bisect. (#290)Full changelog: https://github.com/vutran1710/PyrateLimiter/blob/master/CHANGELOG.md
Bug-fix and hardening release. It contains a few breaking changes that affect only edge or undocumented usage — see Breaking Changes below.
ValueError at bucket construction instead
of being silently mis-enforced. A valid list is ordered by strictly
increasing interval, with strictly increasing limits and non-increasing
density (the "generous-before-tight" contract). (#239)binary_search has been removed from the public API. It was an undocumented
internal helper, now replaced internally by the standard library bisect. (#290)PgQueries SQL templates now use bound %s parameters instead of the
{offset} / {timestamp} format placeholders. (#233)try_acquire no longer busy-spins (burning CPU until the next
background leak) or spuriously times out when buffer_ms=0; waiting() now
clears the inclusive window lower bound correctly. (#289)try_acquire_async(timeout=0) now succeeds when capacity is available
instead of always returning False. (#289)leak() no longer raises AttributeError when invoked on a closed
connection during teardown (fixes the unstable test_sqlite_filelock_bucket). (#244)leak() bug when unsorted rates were passed to the Redis,
SQLite, or Postgres buckets. (#239)binary_search with the standard library bisect. (#290)clock=,
raise_when_fail, and max_delay parameters) and documented how to use a
custom / distributed clock in v4. (#261)Guard sync _delay_waiter against negative wait values from bucket backends by @Copilot in #278
_delay_waiter against negative wait values from bucket backends by @Copilot in #278Full Changelog: v4.1.0...v4.2.0
Update psycopg dependency to support macOS by @Olegt0rr in #258
__init__.py files by @Copilot in #266Full Changelog: v4.0.2...v4.1.0
Include docs and tests in sdist by @vutran1710 in #256
Include missing docs & tests
Full Changelog: v4.0.1...v4.0.2
Nothing, just the same as v4.0.0
Nothing, just the same as v4.0.0
Updte documentation to include MultiProcessBucket
Keep Retrying until Max Delay Has Expired
Add FileLock option for SQLiteBucket
Update package metadata and local dev config to support python 3.13
* Add method to remove bucket
Fix table creation for SQLiteBucket
Support creating/getting bucket asynchronously
Use psycopg3 for PostgresBucket
Fix dependencies for "all" package extra
* Add PostgresBucket backend
Fix: unnecessary warning during async check
Improved in-memory-bucket performance
Fix background task for leaking
Fix Redis CROSSSLOT Keys following issue #126
Nothing published for this version
Fix broken SqliteBucket following issue #132
Allow to pass rates directly to Limiter to use default ImMemoryBucket with Limiter
max_delay argument of LimiterCritical bug fix: importing redis fail crashing apps
Nothing published for this version
Third major release with API breaking changes:
Third major release with API breaking changes:
Force check some bucket-keyword arguments
Fix unit test to make test results stable
Nothing published for this version
Fix SQLite OperationalError when getting more items than SQLite variable limit
Build both wheel and sdist on publish
wheel and sdist on publishAdd option to expire redis key when using RedisBucket
### Removed * Python 3.6 support
Add documentation site: https://pyrate-limiter.readthedocs.io
Add flush() method to all bucket classes
flush() method to all bucket classesAdd FileLockSQliteBucket for a SQLite backend with file-based locking
FileLockSQliteBucket for a SQLite backend with file-based lockingMake SQLite bucket thread-safe and multiprocess-safe
Remove development scripts from package published on PyPI
nox to run development scriptsReplace all formatting/linting tools with *pre-commit*
Add SQliteBucket to persist rate limit data in a SQLite database
SQliteBucket to persist rate limit data in a SQLite database### Added * Custom time source
Nothing published for this version
Add RedisClusterBucket to support using PyrateLimiter with redis-py-cluster
RedisClusterBucket to support using PyrateLimiter with redis-py-clusterRun CI tests for all supported python versions
Use time.monotonic() instead of time.time()
time.monotonic() instead of time.time()### Fixed * Bucket group initialization
### Added * Support for python 3.6
### Fixed * Incorrect type hint
LICENSE file to be included in PyPI package
Limiter.ratelimit() with delay=TrueSupport for using Limiter.ratelimit() as a contextmanager or async contextmanager
Limiter.ratelimit() as a contextmanager or async contextmanagerLimitContextDecorator class to handle Limiter.ratelimit() behaviorInternal: Reduce cognitive complexity
Incorrect check log against time-window
Limiter.ratelimit() method, an async-compatible decorator that optionally adds rate-limiting delays
Limiter.ratelimit() method, an async-compatible decorator that optionally adds rate-limiting delaysNothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →