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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.