An httpx-based client for the
openQA REST API, exposing both a synchronous and an
asynchronous API. It is a port of
openQA-python-client
from requests to httpx, preserving its HMAC request signing, client.conf
discovery, and YAML-response fallback.
uv add openqa-async
# or
pip install openqa-asyncfrom openqa_async import OpenQAClient
with OpenQAClient(server="openqa.opensuse.org") as client:
jobs = client.openqa_request("GET", "/api/v1/jobs", params={"limit": 10})
print(jobs)import asyncio
from openqa_async import AsyncOpenQAClient
async def main():
async with AsyncOpenQAClient(server="openqa.opensuse.org") as client:
jobs = await client.openqa_request(
"GET", "/api/v1/jobs", params={"limit": 10}
)
print(jobs)
asyncio.run(main())Both clients share the same request method (the async one is awaitable):
openqa_request(
method, # "GET", "POST", ...
path, # e.g. "/api/v1/jobs" (a leading slash is optional)
params=None, # query-string parameters
retries=None, # override the client default (5)
wait=None, # override the initial backoff in seconds (10)
data=None, # form-encoded body
json=None, # JSON body
)Responses are parsed automatically: a text/yaml body is loaded with
yaml.safe_load, a 204 No Content (or parse=False) returns the raw
httpx.Response, and everything else is decoded as JSON. Non-2xx responses
raise RequestError; transport failures raise ConnectionError. Retryable
status codes (e.g. 503) are retried with exponential backoff.
Credentials are read from INI-style client.conf files, searched in order:
/etc/openqa/client.conf~/.config/openqa/client.conf
Each section is keyed by the server host (or full base URL) and provides the
API key/secret:
[openqa.opensuse.org]
key = YOUR_API_KEY
secret = YOUR_API_SECRETThe lookup tries the bare server section first, then the full base URL
section. When a key/secret is present, requests are HMAC-SHA1 signed and the
X-API-Key header is sent. Without credentials only unauthenticated GET
requests are possible.
Scheme defaulting: the scheme defaults to https, except for loopback
hosts (localhost, 127.0.0.1, ::1), which default to http. You can also
pass a fully-qualified server such as http://openqa.example.com.
For code written against the upstream requests-based client, a compatibility
alias keeps most call sites working unchanged:
from openqa_async import OpenQA_Client # alias for OpenQAClient
client = OpenQA_Client(server="openqa.opensuse.org")
jobs = client.openqa_request("GET", "/api/v1/jobs")OpenQA_Client is the synchronous OpenQAClient, and openqa_request keeps
the same method/path semantics. New code should prefer OpenQAClient /
AsyncOpenQAClient directly.
Both clients accept a verify parameter (default True) that is passed
straight through to httpx. It accepts a bool, a CA-bundle path, or an
ssl.SSLContext:
# Self-signed or internal-CA openQA instance:
client = OpenQAClient(server="openqa.internal", verify="/path/to/internal-ca.pem")
# Disable verification entirely:
client = OpenQAClient(server="openqa.internal", verify=False)Security warning:
verify=Falsedisables TLS certificate verification and exposes the connection to man-in-the-middle attacks. Use it only against trusted instances on trusted networks; prefer supplying the internal CA bundle instead.
GPL-2.0-or-later. See COPYING.