pkgsrc-Changes archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
CVS commit: pkgsrc/net/py-pyrate-limiter
Module Name: pkgsrc
Committed By: adam
Date: Thu Sep 3 11:14:17 UTC 2026
Modified Files:
pkgsrc/net/py-pyrate-limiter: Makefile PLIST distinfo
Log Message:
py-pyrate-limiter: updated to 4.5.0
4.5.0
Pluggable rate-limiting algorithms. Additive — no breaking public API changes;
the default behaviour of every existing bucket is unchanged.
Added
- **`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.
```python
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.
Fixed
- **Packaging**: the optional backends are now real, pip-installable extras.
`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.
- **SQLiteBucket**: a successful `put()` now clears `failing_rate`. Every other
backend already did; SQLite left the last denial standing indefinitely.
Performance
- **RedisBucket**: the Lua script returns the blocking item's timestamp with the
verdict, so a rate-limited request no longer needs a second `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.
- **PostgresBucket**: the retry-after is resolved inside the same `EXCLUSIVE`
table lock as the check, removing both a round trip and that same race.
- **SQLiteBucket**: likewise resolved inside `put()`'s existing lock hold, so
the background `Leaker` cannot delete rows between the verdict and the wait.
- **InMemoryBucket** / **MultiprocessBucket**: the wait falls out of the bisect
`put()` already performs — no second scan, and no allocation on the admit path.
Documentation
- The README and package description no longer describe the library as
implementing "the Leaky-Bucket algorithm". The default has always been a
sliding-window log; the leaky-bucket meter is now genuinely available as
`GCRA`, so the term is reserved for it.
Internal / Refactor
- `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.
- Split `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.
- The inclusive-window `+1` boundary correction now lives in exactly one place
(`SlidingWindowLog.retry_after`) instead of being inlined in `waiting()`.
To generate a diff of this commit:
cvs rdiff -u -r1.4 -r1.5 pkgsrc/net/py-pyrate-limiter/Makefile \
pkgsrc/net/py-pyrate-limiter/distinfo
cvs rdiff -u -r1.2 -r1.3 pkgsrc/net/py-pyrate-limiter/PLIST
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: pkgsrc/net/py-pyrate-limiter/Makefile
diff -u pkgsrc/net/py-pyrate-limiter/Makefile:1.4 pkgsrc/net/py-pyrate-limiter/Makefile:1.5
--- pkgsrc/net/py-pyrate-limiter/Makefile:1.4 Tue Jun 23 11:42:42 2026
+++ pkgsrc/net/py-pyrate-limiter/Makefile Thu Sep 3 11:14:17 2026
@@ -1,6 +1,6 @@
-# $NetBSD: Makefile,v 1.4 2026/06/23 11:42:42 wiz Exp $
+# $NetBSD: Makefile,v 1.5 2026/09/03 11:14:17 adam Exp $
-DISTNAME= pyrate_limiter-4.4.0
+DISTNAME= pyrate_limiter-4.5.0
PKGNAME= ${PYPKGPREFIX}-${DISTNAME:S/_/-/}
CATEGORIES= net python
MASTER_SITES= ${MASTER_SITE_PYPI:=p/pyrate-limiter/}
@@ -10,9 +10,9 @@ HOMEPAGE= https://github.com/vutran1710/
COMMENT= Rate-Limiter using Leaky-Bucket Algorithm
LICENSE= mit
-TOOL_DEPENDS+= ${PYPKGPREFIX}-hatchling>0:../../devel/py-hatchling
-TOOL_DEPENDS+= ${PYPKGPREFIX}-uv-dynamic-versioning>0:../../devel/py-uv-dynamic-versioning
-# optional dependency
+TOOL_DEPENDS+= ${PYPKGPREFIX}-hatchling>=0:../../devel/py-hatchling
+TOOL_DEPENDS+= ${PYPKGPREFIX}-uv-dynamic-versioning>=0:../../devel/py-uv-dynamic-versioning
+# filelock
DEPENDS+= ${PYPKGPREFIX}-filelock>=0:../../devel/py-filelock
TEST_DEPENDS+= ${PYPKGPREFIX}-aiohttp>=0:../../www/py-aiohttp
TEST_DEPENDS+= ${PYPKGPREFIX}-test-asyncio>=1.1.0:../../devel/py-test-asyncio
Index: pkgsrc/net/py-pyrate-limiter/distinfo
diff -u pkgsrc/net/py-pyrate-limiter/distinfo:1.4 pkgsrc/net/py-pyrate-limiter/distinfo:1.5
--- pkgsrc/net/py-pyrate-limiter/distinfo:1.4 Tue Jun 23 11:42:42 2026
+++ pkgsrc/net/py-pyrate-limiter/distinfo Thu Sep 3 11:14:17 2026
@@ -1,5 +1,5 @@
-$NetBSD: distinfo,v 1.4 2026/06/23 11:42:42 wiz Exp $
+$NetBSD: distinfo,v 1.5 2026/09/03 11:14:17 adam Exp $
-BLAKE2s (pyrate_limiter-4.4.0.tar.gz) = 926303ffaa401da803913846a10b18aa51fa79edd4f52c5bb192c748331edb08
-SHA512 (pyrate_limiter-4.4.0.tar.gz) = e4b6d63ebaec949c154f117ab30d04e3b94cb4b771299a08ba8bad1bd92ed004ceb89c352c66914c9fb8ae29dbe33b8ef5103a74d174dc83398a45f733ac5f84
-Size (pyrate_limiter-4.4.0.tar.gz) = 90955 bytes
+BLAKE2s (pyrate_limiter-4.5.0.tar.gz) = 3a50427db0a4a16a94df12b4f919b29c16d6d37a194fabc03140b44602e86de6
+SHA512 (pyrate_limiter-4.5.0.tar.gz) = 35ee3bef555260df68ad1881d64b72f7a51535e164abf640c133ac96c5caa4073b56a43edc30cdb982d1e322f9b01f6eccc8edbb6d94be84046be11a305074b7
+Size (pyrate_limiter-4.5.0.tar.gz) = 123240 bytes
Index: pkgsrc/net/py-pyrate-limiter/PLIST
diff -u pkgsrc/net/py-pyrate-limiter/PLIST:1.2 pkgsrc/net/py-pyrate-limiter/PLIST:1.3
--- pkgsrc/net/py-pyrate-limiter/PLIST:1.2 Tue Jun 23 11:42:42 2026
+++ pkgsrc/net/py-pyrate-limiter/PLIST Thu Sep 3 11:14:17 2026
@@ -1,4 +1,4 @@
-@comment $NetBSD: PLIST,v 1.2 2026/06/23 11:42:42 wiz Exp $
+@comment $NetBSD: PLIST,v 1.3 2026/09/03 11:14:17 adam Exp $
${PYSITELIB}/${WHEEL_INFODIR}/METADATA
${PYSITELIB}/${WHEEL_INFODIR}/RECORD
${PYSITELIB}/${WHEEL_INFODIR}/WHEEL
@@ -21,6 +21,9 @@ ${PYSITELIB}/pyrate_limiter/abstracts/bu
${PYSITELIB}/pyrate_limiter/abstracts/rate.py
${PYSITELIB}/pyrate_limiter/abstracts/rate.pyc
${PYSITELIB}/pyrate_limiter/abstracts/rate.pyo
+${PYSITELIB}/pyrate_limiter/abstracts/store.py
+${PYSITELIB}/pyrate_limiter/abstracts/store.pyc
+${PYSITELIB}/pyrate_limiter/abstracts/store.pyo
${PYSITELIB}/pyrate_limiter/abstracts/wrappers.py
${PYSITELIB}/pyrate_limiter/abstracts/wrappers.pyc
${PYSITELIB}/pyrate_limiter/abstracts/wrappers.pyo
@@ -39,9 +42,15 @@ ${PYSITELIB}/pyrate_limiter/buckets/post
${PYSITELIB}/pyrate_limiter/buckets/redis_bucket.py
${PYSITELIB}/pyrate_limiter/buckets/redis_bucket.pyc
${PYSITELIB}/pyrate_limiter/buckets/redis_bucket.pyo
+${PYSITELIB}/pyrate_limiter/buckets/redis_state.py
+${PYSITELIB}/pyrate_limiter/buckets/redis_state.pyc
+${PYSITELIB}/pyrate_limiter/buckets/redis_state.pyo
${PYSITELIB}/pyrate_limiter/buckets/sqlite_bucket.py
${PYSITELIB}/pyrate_limiter/buckets/sqlite_bucket.pyc
${PYSITELIB}/pyrate_limiter/buckets/sqlite_bucket.pyo
+${PYSITELIB}/pyrate_limiter/buckets/state_bucket.py
+${PYSITELIB}/pyrate_limiter/buckets/state_bucket.pyc
+${PYSITELIB}/pyrate_limiter/buckets/state_bucket.pyo
${PYSITELIB}/pyrate_limiter/clocks.py
${PYSITELIB}/pyrate_limiter/clocks.pyc
${PYSITELIB}/pyrate_limiter/clocks.pyo
@@ -51,6 +60,9 @@ ${PYSITELIB}/pyrate_limiter/extras/__ini
${PYSITELIB}/pyrate_limiter/extras/aiohttp_limiter.py
${PYSITELIB}/pyrate_limiter/extras/aiohttp_limiter.pyc
${PYSITELIB}/pyrate_limiter/extras/aiohttp_limiter.pyo
+${PYSITELIB}/pyrate_limiter/extras/httpx2_limiter.py
+${PYSITELIB}/pyrate_limiter/extras/httpx2_limiter.pyc
+${PYSITELIB}/pyrate_limiter/extras/httpx2_limiter.pyo
${PYSITELIB}/pyrate_limiter/extras/httpx_limiter.py
${PYSITELIB}/pyrate_limiter/extras/httpx_limiter.pyc
${PYSITELIB}/pyrate_limiter/extras/httpx_limiter.pyo
Home |
Main Index |
Thread Index |
Old Index