head 1.5; access; symbols pkgsrc-2026Q3:1.5.0.2 pkgsrc-2026Q3-base:1.5 pkgsrc-2026Q2:1.3.0.2 pkgsrc-2026Q2-base:1.3 pkgsrc-2026Q1:1.1.0.2 pkgsrc-2026Q1-base:1.1; locks; strict; comment @# @; 1.5 date 2026.09.03.11.14.17; author adam; state Exp; branches; next 1.4; commitid KinQ9MFd5i69oaUG; 1.4 date 2026.06.23.11.42.42; author wiz; state Exp; branches; next 1.3; commitid VdNm4TkD7gWuRUKG; 1.3 date 2026.06.01.08.05.59; author adam; state Exp; branches; next 1.2; commitid qZKpMorR7hKVm4IG; 1.2 date 2026.03.29.18.30.19; author wiz; state Exp; branches; next 1.1; commitid fRr1W5uFHMsGSTzG; 1.1 date 2026.02.27.13.33.35; author adam; state Exp; branches; next ; commitid tHUfe9ywcJ7Bc1wG; desc @@ 1.5 log @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()`. @ text @# $NetBSD: Makefile,v 1.4 2026/06/23 11:42:42 wiz Exp $ DISTNAME= pyrate_limiter-4.5.0 PKGNAME= ${PYPKGPREFIX}-${DISTNAME:S/_/-/} CATEGORIES= net python MASTER_SITES= ${MASTER_SITE_PYPI:=p/pyrate-limiter/} MAINTAINER= pkgsrc-users@@NetBSD.org HOMEPAGE= https://github.com/vutran1710/PyrateLimiter 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 # 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 TEST_DEPENDS+= ${PYPKGPREFIX}-test-cov>=6.2.1:../../devel/py-test-cov TEST_DEPENDS+= ${PYPKGPREFIX}-test-randomly>=3.16.0:../../devel/py-test-randomly TEST_DEPENDS+= ${PYPKGPREFIX}-test-xdist>=3.8.0:../../devel/py-test-xdist USE_LANGUAGES= # none .include "../../lang/python/wheel.mk" .include "../../mk/bsd.pkg.mk" @ 1.4 log @py-pyrate-limiter: update to 4.4.0. [4.4.0] Bug-fix, scalability, and internal-refactor release. No public API changes (the new AbstractBucket.is_async attribute is additive). Fixed InMemoryBucket: guard the internal item list with a lock so the background 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) PostgresClock: when the DB time query fails, fall back to local wall-clock epoch time instead of monotonic time. The monotonic fallback was ~5 orders of magnitude smaller than the stored epoch-ms timestamps and would corrupt every window comparison and leak bound. (#302) Leaker: make the background sync-leak worker restartable. Re-registering a bucket after every bucket had been disposed previously raised RuntimeError: threads can only be started once. (#302) Keep Limiter picklable after the InMemoryBucket lock addition. (#302) Performance & Scalability Limiter: release the limiter lock during the synchronous blocking wait, so a long wait on one key no longer serializes acquisitions for every other key sharing the limiter. (#304) RedisBucket: batch weighted ZADDs in bounded chunks inside the atomic Lua script, lowering latency for high-weight puts. (#284) Internal / Refactor Unify the limiter's sync/async acquire plumbing into a single coroutine and share the delay-step decision across the sync and async branches. (#303) Add a declarative 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) Introduce an internal 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) Documentation Document that 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) CI The release workflow now also creates a GitHub Release for the pushed tag and attaches the built dist/* artifacts, in addition to publishing to PyPI. [4.3.1] Performance and maintenance release. No API or behavior changes. Performance PostgresBucket: insert all 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) PostgresBucket: compute every rate's windowed count in one query (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) Deduplicate the sync/async deadline math in _delay_waiter into a single shared helper. (#294) Documentation Rewrite the README for accuracy, structure, and presentation, and replace the outdated architecture image with a component-level Mermaid diagram that renders on both GitHub and ReadTheDocs. (#293, #295) CI Move GitHub Actions off the deprecated Node-20 runtime to Node-24 (checkout@@v5, setup-python@@v6, setup-uv@@v7, upload-artifact@@v6, download-artifact@@v7). (#295) [4.3.0] Bug-fix and hardening release. It contains a few breaking changes that affect only edge or undocumented usage — see Breaking Changes below. Breaking Changes Ill-formed rate lists now raise 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) Security SQLite and Postgres backends no longer build SQL through string interpolation. The user-supplied item name (SQLite) and the table name (Postgres) are now bound/quoted, closing a SQL-injection vector and fixing crashes on names containing quotes or other metacharacters. (#233, #244) Fixed Blocking 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) SQLite leak() no longer raises AttributeError when invoked on a closed connection during teardown (fixes the unstable test_sqlite_filelock_bucket). (#244) Rate lists are now consistently sorted by interval across all backends, fixing a latent leak() bug when unsorted rates were passed to the Redis, SQLite, or Postgres buckets. (#239) Changed Replaced the custom binary_search with the standard library bisect. (#290) Documentation Fixed stale v4 examples in the README (removed the long-gone clock=, raise_when_fail, and max_delay parameters) and documented how to use a custom / distributed clock in v4. (#261) @ text @d1 1 a1 1 # $NetBSD: Makefile,v 1.3 2026/06/01 08:05:59 adam Exp $ d3 1 a3 1 DISTNAME= pyrate_limiter-4.4.0 d13 3 a15 3 TOOL_DEPENDS+= ${PYPKGPREFIX}-hatchling>0:../../devel/py-hatchling TOOL_DEPENDS+= ${PYPKGPREFIX}-uv-dynamic-versioning>0:../../devel/py-uv-dynamic-versioning # optional dependency @ 1.3 log @py-pyrate-limiter: updated to 4.2.0 4.2.0 Guard sync _delay_waiter against negative wait values from bucket backends fix(docs): align docstrings and fix: db_path=None temporary DB behavior Typo fix for pypi bug fix: Limiter initialization in README for SQLite fix: add close() method to avoid ResourceWarning about unclosed transports @ text @d1 1 a1 1 # $NetBSD: Makefile,v 1.2 2026/03/29 18:30:19 wiz Exp $ d3 1 a3 1 DISTNAME= pyrate_limiter-4.2.0 @ 1.2 log @py-pyrate-limiter: update to 4.1.0. Changes not documented. @ text @d1 1 a1 1 # $NetBSD: Makefile,v 1.1 2026/02/27 13:33:35 adam Exp $ d3 1 a3 1 DISTNAME= pyrate_limiter-4.1.0 d16 2 a17 1 TEST_DEPENDS+= ${PYPKGPREFIX}-filelock>=0:../../devel/py-filelock @ 1.1 log @py-pyrate-limiter: added version 4.0.2 The request rate limiter using Leaky-bucket Algorithm. * Supports unlimited rate limits and custom intervals. * Separately tracks limits for different services or resources. * Manages limit breaches with configurable blocking or non-blocking behavior. * Offers multiple usage modes: direct calls or decorators. * Fully compatible with both synchronous and asynchronous workflows. * Provides SQLite and Redis backends for persistent limit tracking across threads or restarts. * Includes MultiprocessBucket and SQLite File Lock backends for multiprocessing environments. @ text @d1 1 a1 1 # $NetBSD$ d3 1 a3 1 DISTNAME= pyrate_limiter-4.0.2 d15 2 @