comicbox.formats.base.online.rate_limits

source module comicbox.formats.base.online.rate_limits

Rate limits for online sources, with per-source overrides.

The upstream libraries (mokkari for Metron, simyan for ComicVine) already enforce sensible rate limits via pyrate_limiter’s SQLite-backed bucket that persists state across runs. Comicbox inherits Metron’s default without intervention; for ComicVine it always installs its own limiter (see below).

This module exists so:

  1. The numbers are visible in our codebase (rather than hidden in transitive dependencies) and can be cited / audited.
  2. Power users with higher API tiers can raise the cap without forking their library install — online.<source>.rate_limit.* config keys construct a custom bucket / limiter.
  3. We can lower the cap defensively if a source ever publishes a stricter policy and we need to ship a release before the upstream library bumps its constants.
  4. ComicVine specifically: simyan’s limiter blocks indefinitely on an hourly-cap hit (try_acquire(timeout=-1)), which reads as a silent, uninterruptible hang. We always wrap it so the blocking wait is bounded and long waits surface as RateLimitError through comicbox’s logged, cancellable retry layer — see build_comicvine_limiter / _BoundedComicVineLimiter. (mokkari already raises on its local cap, so Metron needs no such wrapper.)

Defaults match the upstream-library values as of 2026-05. For Metron’s un-overridden case the upstream library’s bucket is used (we read its value at session-construction time rather than passing our own).

Sources

  • Metron: 20 req/min, 5,000 req/day per IP https://metron.cloud/ — see also mokkari/session.py
  • ComicVine: 1 req/sec, 200 req/hr per IP https://comicvine.gamespot.com/api/documentation — see simyan/comicvine.py

Functions

source build_metron_bucket(limits: OnlineSourceLimits | None, db_path: Path | str)

Construct a custom mokkari bucket from override values, or None.

Returns None when no overrides are set so mokkari uses its default SQLite-backed bucket (preserving cross-process / cross-run state). When any override is set, returns a process-wide memoized SQLiteBucket persisted at db_path with the per-minute and per-day rates.

Lazy-imports pyrate_limiter so callers without the override don’t pay the import cost at module load.

source build_comicvine_limiter(limits: OnlineSourceLimits | None, db_path: Path | str)

Construct comicbox’s ComicVine limiter (always non-None).

Returns a process-wide memoized _BoundedComicVineLimiter over a SQLiteBucket persisted at db_path. Override values from online.comicvine.rate_limit.* set the per-second / per-hour rates; absent overrides fall back to the documented CV defaults (1/sec, 200/hr — the same numbers simyan ships).

Unlike build_metron_bucket, this never returns None: we always install our own limiter so the blocking wait is bounded. simyan’s default limiter calls try_acquire with timeout=-1 (wait forever), turning an hourly-cap hit into a silent, uninterruptible multi-minute block. Wrapping the limiter is the only lever — pyrate_limiter 4.x dropped max_delay from the Limiter constructor, and simyan’s transport hardcodes the try_acquire call args. See _BoundedComicVineLimiter for the bounding behaviour.