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