animedex.transport.ratelimit
Per-backend rate limiting for animedex’s outgoing HTTP traffic.
This module provides three pieces:
TokenBucket- a small, monotonic-clock-driven token bucket with both non-blocking (TokenBucket.try_acquire()) and blocking (TokenBucket.acquire()) variants. Backends use the blocking form on the request hot path;try_acquire()is for transparent fast-fail in tests and back-pressure logic.RateLimitRegistry- a name-keyed map from backend identifier to its configured bucket. The substrate ships exactly one default registry (default_registry()) so the per-backend caps fromplans/01and the P1 obligations fromplans/02live in one place.default_registry()- the wired-up registry with the caps every backend documented inplans/01-public-apis-anime-survey.mdhonours.
The Rate.slow override (CLI flag --rate slow) halves the
refill rate; we never expose a faster-than-default mode because the
upstream contract is a P1 ceiling, not a preference.
The module pulls its clock primitives through two indirection points,
_monotonic and _sleep, so unit tests can substitute a
deterministic fake without monkeypatching the standard library
globally.
TokenBucket
- class animedex.transport.ratelimit.TokenBucket(capacity: int, refill_per_second: float)[source]
Bases:
objectA monotonic-clock token bucket.
The bucket holds at most
capacitytokens and accumulates them atrefill_per_secondper second. Each acquire consumes one token. The implementation is thread-safe and uses_monotonic/_sleeprather than directtimecalls so tests can swap in a fake clock.- Parameters:
- Raises:
ValueError – When
capacityorrefill_per_secondis not strictly positive.
- try_acquire() bool[source]
Consume a token without blocking.
- Returns:
Trueif a token was available and consumed,Falseotherwise.- Return type:
- acquire() None[source]
Consume a token, blocking via
_sleepuntil one is available.Used by every real-request hot path. Sleep is computed from the deficit and the refill rate, so we wake exactly when the next token arrives - there is no polling.
- Returns:
None.- Return type:
None
- with_rate(mode: str) TokenBucket[source]
Return a bucket with the requested rate-mode applied.
"normal"returnsselfunchanged."slow"returns a new bucket with the refill rate halved. We never support a faster mode because that would violate the upstream P1 ceiling.- Parameters:
mode (str) –
"normal"or"slow".- Returns:
A bucket honouring the requested mode.
- Return type:
- Raises:
ValueError – When
modeis unrecognised.
RateLimitRegistry
- class animedex.transport.ratelimit.RateLimitRegistry[source]
Bases:
objectPer-backend bucket map.
A backend identifier (the same short string used in
SourceTag) maps to oneTokenBucket.register()is idempotent across the same name;get()raisesApiErrorwhen the backend is unknown so a typo at the call site fails loudly.- Variables:
_buckets (dict) – Internal mapping from backend identifier to bucket.
- register(name: str, *, capacity: int, refill_per_second: float) TokenBucket[source]
Register or replace a backend’s bucket.
- Parameters:
- Returns:
The registered bucket.
- Return type:
default_registry
- animedex.transport.ratelimit.default_registry() RateLimitRegistry[source]
Build the project-wide default rate-limit registry.
The caps reflect what each upstream actually enforces, not what we wish for; see
plans/01per-source notes. Anything not listed here either has no documented cap or ships its own persistent scheduler (AniDB).- Returns:
A registry pre-populated with every public backend.
- Return type:
selftest
- animedex.transport.ratelimit.selftest() bool[source]
Smoke-test the bucket and the default registry.
Builds a small bucket, exercises both acquire variants, builds the default registry, and verifies every plan-01 backend resolves. Does not call real
time.sleep; the bucket is sized to fit the capacity so no waits are required.- Returns:
Trueon success.- Return type: