Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Mokkari

PyPI - Version PyPI - Python Ruff Pre-Commit

A python wrapper for the Metron Comic Book Database API.

Installation

pip install mokkari

Authentication

Authenticate with an API token, which you can generate from your metron.cloud account page:

import mokkari

m = mokkari.api(api_token="your-api-token")

Username/password (Basic Auth) is still accepted with mokkari.api(username, password), but it will be deprecated in the near future, so new code should use a token.

Example Usage

import mokkari

# Your own config file to keep your credentials secret
from config import api_token

m = mokkari.api(api_token=api_token)

# Get all Marvel comics for the week of 2021-06-07
this_week = m.issues_list(
    {
        "store_date_range_after": "2021-06-07",
        "store_date_range_before": "2021-06-13",
        "publisher_name": "marvel",
    }
)

# Print the results
for i in this_week:
    print(f"{i.id} {i.issue_name}")

    # Retrieve the detail for an individual issue
    asm_68 = m.issue(31660)

# Print the issue Description
print(asm_68.desc)

Rate Limiting

The API allows at least 20 requests per minute (the server may allow more when load is low), plus a daily limit that starts at 5,000 requests and is raised for OpenCollective donors (up to 25,000/day). Because the daily limit varies per user, mokkari doesn't hardcode it — it reads the X-RateLimit-* headers Metron returns with every response and pre-empts a request once those headers show a window is exhausted, avoiding an HTTP call that would fail anyway. When a rate limit is exceeded, a RateLimitError is raised.

The most recently observed state is available via session.rate_limit_status:

status = m.rate_limit_status
print(f"Sustained remaining: {status.sustained.remaining}/{status.sustained.limit}")

Handling Rate Limits

The RateLimitError includes a retry_after attribute that tells you exactly how many seconds to wait before making another request:

import mokkari
from mokkari.exceptions import RateLimitError
import time

m = mokkari.api(api_token=api_token)

try:
    issue = m.issue(31660)
except RateLimitError as e:
    # Display user-friendly message
    print(f"Rate limited: {e}")

    # Programmatically wait for the exact time needed
    print(f"Waiting {e.retry_after} seconds...")
    time.sleep(e.retry_after)

    # Retry the request
    issue = m.issue(31660)

Thread Safety

A Session can be shared across threads, but the rate-limit check above is advisory rather than a hard gate: it only blocks once the last known response headers show a window is exhausted, and that check isn't synchronized with sending the request. Concurrent threads can therefore each pass the check and send their requests before either response updates rate_limit_status, letting a burst of threads momentarily exceed the per-minute limit (Metron's server-side limit still applies and will reject the excess requests).

If you're calling a shared Session from multiple threads, cap your own concurrency instead of relying on Session to do it for you, e.g. keep a ThreadPoolExecutor at or below the burst limit:

from concurrent.futures import ThreadPoolExecutor

m = mokkari.api(api_token=api_token)

# Keep worker count at or below the burst limit (20/min at minimum) to avoid
# racing past the local rate-limit check.
with ThreadPoolExecutor(max_workers=20) as executor:
    issues = list(executor.map(m.issue, issue_ids))

Pacing (opt-in)

Passing rate_limiter closes the gap described above: instead of racing past an advisory check, every HTTP send is dispatched through the rate limiter first, which can block a caller until capacity actually frees rather than letting it send anyway. mokkari.rate_limit.HeaderPacedRateLimiter is a ready-to-use implementation. It sizes the per-minute window from the X-RateLimit-* headers but paces it from its own monotonic log of send times, so a local clock that has drifted from Metron's doesn't matter, and it spaces sends evenly across the window (at the 20/min floor, one every 3 seconds). If Metron still answers with a 429, it backs every caller off by the Retry-After value the server sent:

from concurrent.futures import ThreadPoolExecutor

import mokkari
from mokkari.rate_limit import HeaderPacedRateLimiter

m = mokkari.api(api_token=api_token, rate_limiter=HeaderPacedRateLimiter())

with ThreadPoolExecutor(max_workers=20) as executor:
    issues = list(executor.map(m.issue, issue_ids))

The limiter only blocks for the per-minute window, whose waits are seconds long. When the daily limit is exhausted it raises RateLimitError instead of blocking for what could be hours, with retry_after set to the time until the daily window resets. That leaves it to your application to tell the user and either wait or quit:

import time

from mokkari.exceptions import RateLimitError
from mokkari.utils import format_time

try:
    issue = m.issue(31660)
except RateLimitError as e:
    if input(f"Daily limit reached. Wait {format_time(e.retry_after)}? (y/n): ") == "y":
        time.sleep(e.retry_after)
        issue = m.issue(31660)

Paginated list calls follow the same rules: if a page is rejected with a 429 they retry it through the limiter, which blocks until it's safe to send, and an exhausted daily limit raises RateLimitError from the list call rather than being waited out.

A rate limiter object is scoped to the Session it's passed to — construct one per Session rather than sharing an instance across sessions using different credentials. Passing your own object works too, as long as it implements the acquire/on_rate_limited/release methods described in mokkari.rate_limit.RateLimiter. Leaving rate_limiter unset (the default) keeps the raise-immediately behavior described above.

Pacing across processes with Redis

HeaderPacedRateLimiter only sees the requests its own process sends, so several workers using one account can overrun the per-minute window together until Metron answers with 429s. mokkari.redis_rate_limit.RedisRateLimiter paces the same way but keeps its state in Redis, so every process and host using the same account shares one per-minute window, one daily estimate and one 429 backoff. Install the redis extra and pass it a client:

pip install mokkari[redis]
import redis

import mokkari
from mokkari.redis_rate_limit import RedisRateLimiter

client = redis.Redis(host="localhost", port=6379)
# Any stable name for your Metron account, such as its username. Don't use the
# token itself: it becomes part of key names anyone with Redis access can read.
m = mokkari.api(
    api_token=api_token,
    rate_limiter=RedisRateLimiter(client, account="your-username"),
)

It raises RateLimitError on an exhausted daily limit just like HeaderPacedRateLimiter. Times come from the Redis server's clock, and every key expires on its own, so a worker that crashes mid-request can't leave the account blocked. If Redis is unreachable, acquire raises the client's connection error and the request isn't sent.

Connection Reuse

A Session keeps its connections to Metron open between requests, so repeated calls don't pay a new TCP/TLS handshake each time. Cookies are never stored or sent back. To release the connections deterministically, use the session as a context manager or call close():

with mokkari.api(api_token="your-token") as m:
    issue = m.issue(1)

Closing is optional, and a closed session can still be used; it just opens new connections. If the server drops an idle connection, the underlying HTTP library notices and discards it when the next request checks it out, so a request opens a fresh connection instead. The only failure is a race: if the server closes a connection in the instant it is reused, that request raises an ApiError. This is rare, and mokkari doesn't retry it automatically. Don't share a Session across forked processes; create one per process.

Documentation

Read the project documentation

Bugs/Requests

Please use the GitHub issue tracker to submit bugs or request features.

Metadata

Release files for mokkari 4.9.0rc1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mokkari 4.9.0rc1
File Size Uploaded
mokkari-4.9.0rc1.tar.gz 105.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mokkari 4.9.0rc1
File Interpreter ABI Platform
mokkari-4.9.0rc1-py3-none-any.whl Python 3 none any Details

Total release size: 182.1 kB

Release files / mokkari-4.9.0rc1.tar.gz

Download URL mokkari-4.9.0rc1.tar.gz
Size 105.8 kB
Tags Source
SHA-256 checksum
How to use checksums
68e535c407bd9fe3594aa524115ea7cfcb8336576024d4c7c37b53822b65b0c9
BLAKE2b-256 checksum
How to use checksums
ece02f007767c739d22497fdfb451196e31c7847bf2f2d44d8de4bfbeeefbfc5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.22 {"installer":{"name":"uv","version":"0.12.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / mokkari-4.9.0rc1-py3-none-any.whl

Download URL mokkari-4.9.0rc1-py3-none-any.whl
Size 76.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4ecb0f74b5dc02487e127561f3d9f8a7594e381d8921da09e55c17f2b4153efb
BLAKE2b-256 checksum
How to use checksums
e5e77130fd30c3c78457a4240f9349780fc109a0b46c1c5797dc0fb97802b120
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.22 {"installer":{"name":"uv","version":"0.12.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

4.10.0

2 release files

4.9.0

2 release files

This release

4.9.0rc1 This release

2 release files

4.8.0

2 release files

4.7.1

2 release files

4.7.0

2 release files

4.6.0

2 release files

4.5.0

2 release files

4.4.0

2 release files

4.3.0

2 release files

4.2.0

2 release files

4.1.0

2 release files

4.0.1

2 release files

4.0.0

2 release files

3.29.1

2 release files

3.29.0

2 release files

3.27.0

2 release files

3.26.0

2 release files

3.25.2

2 release files

3.25.1

2 release files

3.25.0

2 release files

3.24.0

2 release files

3.23.0

2 release files

3.22.0

2 release files

3.21.0

2 release files

3.19.0

2 release files

3.16.2

2 release files

3.16.1

2 release files

3.16.0

2 release files

3.15.1

2 release files

3.14.0

2 release files

3.13.1

2 release files

3.12.0

2 release files

3.11.0

2 release files

3.9.0

2 release files

3.8.1

2 release files

3.8.0

2 release files

3.7.0

2 release files

3.6.0

2 release files

3.5.0

2 release files

3.4.0

2 release files

3.3.0

2 release files

3.2.0

2 release files

3.1.0

2 release files

3.0.1

2 release files

3.0.0

2 release files

2.6.0

2 release files

2.5.0

2 release files

2.4.0

2 release files

2.3.3

2 release files

2.3.2

2 release files

2.3.1

2 release files

2.3.0

2 release files

2.2.2

2 release files

2.2.0

2 release files

2.1.1

2 release files

2.1.0

2 release files

2.0.4

2 release files

2.0.3

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.2

2 release files

1.0.0

2 release files

0.2.4

2 release files

0.2.2

2 release files

0.2.0

1 release file

0.1.10

1 release file

0.1.8

1 release file

0.1.6

1 release file

0.1.4

1 release file

0.1.2

1 release file

0.1.0

1 release file

0.0.4

1 release file

0.0.1

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page