Skip to main content

content-gateway-client

The Python client of content-gateway: the gateway's own routes typed from its OpenAPI document, and the CurseForge and Modrinth passthroughs with the gateway's refusals handled. Sync and async, on httpx.

pip install content-gateway-client

Python 3.11 or newer.

Usage

from content_gateway import ContentGateway, ContentGatewayError

with ContentGateway("http://127.0.0.1:5005", token=TOKEN, pool="app") as gateway:
    answer = gateway.curseforge(
        "mods/search", query={"gameId": 432, "searchFilter": "jei", "classId": 6}
    )
    if not answer.ok:
        raise RuntimeError(f"CurseForge answered {answer.status}")
    for mod in answer.body["data"]:
        print(mod["id"], mod["name"])

curseforge(path, ...) sends api.curseforge.com/v1/<path> through the gateway and answers whatever CurseForge answered as a PassthroughAnswer: status, ok, body (parsed JSON, the text when it is not JSON, None when empty), headers, and cache, which is hit, miss or stale from the gateway's X-Cache. A body makes the request a POST; fresh=True asks CurseForge again instead of the cache; pool draws from another of the token's pools. query values of None are left out.

Only the gateway's own answers raise, as ContentGatewayError with the gateway's code (BUDGET_SPENT, UPSTREAM_HOLD, UNAUTHORIZED, ...), status, pool and retry_after_ms. code is None when the answer was not the gateway's JSON error at all, such as a proxy's HTML page.

modrinth(path, ...) sends api.modrinth.com/v2/<path> through the gateway the same way; the calls draw from the pool's Modrinth budget. modrinth_version_file(sha1) and modrinth_version_files(hashes) are the two SHA-1 lookups, and modrinth_file_url(version, sha1) picks the address of the file itself on Modrinth's CDN out of a version.

resolve(file_ids, cdn=True, pool=None, fresh=False, wait_up_to=None, timeout=60) reads /v1/resolve: a download address per CurseForge file, from CurseForge's own link, else Modrinth's copy of the file found by SHA-1, else the file's built address on CurseForge's CDN when it answers a HEAD with the file's size (cdn=False skips that step). A file with a source of None and retry true could not be fully checked this time. The consumer downloads from the address itself; no bytes pass through the gateway. The gateway tries the three sources in a row, so a call can take 40 s at worst; resolve() therefore waits 60 s rather than the client's timeout.

minecraft_versions(type=None, loader=None), minecraft_version(id) and loader_versions(loader, minecraft=None) read /v1/minecraft/*: Mojang's version list (releases by default, narrowed to what a loader builds for), one version with the Java major it needs, and a loader's builds (NeoForge, Forge, Fabric) with stable and recommended flags. java_runtime(platform, major=None, component=None) finds a Java runtime Mojang ships for a platform (linux, windows-x64, mac-os-arm64 and the rest of Mojang's names), by Java major or by the javaComponent a version names, with the address, SHA-1 and size of Mojang's manifest of its files.

health() reads /v1/healthz and budget(days=None) reads /v1/budget, one UpstreamBudget for CurseForge and one for Modrinth with the last days UTC days of counts (14 when left out, more than the gateway's historyDays answers the days kept).

The gateway's own answers are plain dicts typed as TypedDicts generated from openapi.json (Health, BudgetSnapshot, Resolved, MinecraftVersions, ...), so a type checker knows their keys. The passthrough bodies are CurseForge's and Modrinth's JSON as it came, typed Any.

Async

AsyncContentGateway has the same methods as coroutines, on httpx.AsyncClient:

from content_gateway import AsyncContentGateway

async with AsyncContentGateway("http://127.0.0.1:5005", token=TOKEN) as gateway:
    health = await gateway.health()

Waiting out a refusal

A refused call answers 503 with Retry-After. A background job can sleep it out:

answer = gateway.curseforge("mods", body={"modIds": mod_ids}, wait_up_to=300)

The call is sent again after each refusal that fits in what is left of wait_up_to seconds; one that does not fit raises at once. A request answering a user should leave it unset and pass the retry_after_ms on.

Timeouts and connections

timeout, 15 s by default, is httpx's limit on each phase of a request (connecting, waiting for a read, a write), not on the whole call; the gateway answers in one body, so a call ends within it in practice. The gateway gives CurseForge 10 s before it answers 504 or a stale answer, so keep the client's timeout above that: a stalled call then comes back as the gateway's answer instead of an httpx.TimeoutException here. The client keeps connections to the gateway open between calls; pass your own httpx.Client or httpx.AsyncClient as client to change that or to test (an httpx.MockTransport makes a fake gateway).

Development

uv sync                     # the virtual environment with the dev tools
uv run pytest
uv run mypy
uv run ruff check . && uv run ruff format --check .
uv run python scripts/generate.py          # rewrite src/content_gateway/generated.py from openapi.json
uv run python scripts/generate.py --check  # fail when it is stale

src/content_gateway/generated.py is generated from the gateway's openapi.json with datamodel-code-generator and checked in; pnpm openapi at the repository root regenerates it with the TypeScript types, and CI fails when it is stale. Never edit it by hand.

The version lives in pyproject.toml and CHANGELOG.md holds its entries. A release tag of the gateway publishes the package to PyPI when that version is not there yet, through PyPI's trusted publishing from the release workflow, so the PyPI project trusts release.yml of the GitHub repository and no token is stored anywhere.

Metadata

Release files for content-gateway-client 0.2.0

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

Source distribution (sdist)

Source distribution for content-gateway-client 0.2.0
File Size Uploaded Provenance Actions
content_gateway_client-0.2.0.tar.gz 10.0 kB With provenance Details for content_gateway_client-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for content-gateway-client 0.2.0
File Interpreter ABI Platform Actions
content_gateway_client-0.2.0-py3-none-any.whl Python 3 none any Details for content_gateway_client-0.2.0-py3-none-any.whl

Total release size: 20.9 kB

Release files / content_gateway_client-0.2.0.tar.gz

Download URL content_gateway_client-0.2.0.tar.gz
Size 10.0 kB
Tags Source
SHA-256 checksum
How to use checksums
e4a93d840c4082b1295818c32de91b7cc4867101f5efe904f30fc5a687c43d31
BLAKE2b-256 checksum
How to use checksums
406583fc4b072a98f14c32c0b1c8c3b711b6a94da1a9f3c1c4d3cf8df98b97fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Release files / content_gateway_client-0.2.0-py3-none-any.whl

Download URL content_gateway_client-0.2.0-py3-none-any.whl
Size 10.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
293327c33ff333714942b92247d7e1aac3661537079ba9bae762db6beae47fe7
BLAKE2b-256 checksum
How to use checksums
a941ae5fb3a673d24087bc7f11aa170c3f1202b7fd3ded077ba72f9809ef911c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Security for content-gateway-client 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for content_gateway_client-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

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