227 lines
5.7 KiB
Markdown
227 lines
5.7 KiB
Markdown
# Usage guide
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
pip install conduit-client
|
|
```
|
|
|
|
For development:
|
|
|
|
```bash
|
|
pip install conduit-client[dev]
|
|
```
|
|
|
|
## Authentication
|
|
|
|
The Conduit gateway uses API keys in the `Authorization: Bearer mega_sk_live_<prefix>_<secret>` header. Pass your key to `ConduitClient` or `AsyncConduitClient`:
|
|
|
|
```python
|
|
from conduit_client import ConduitClient
|
|
|
|
client = ConduitClient("mega_sk_live_abc123_xyz")
|
|
```
|
|
|
|
Async version:
|
|
|
|
```python
|
|
import asyncio
|
|
from conduit_client import AsyncConduitClient
|
|
|
|
async def main() -> None:
|
|
async with AsyncConduitClient("mega_sk_live_abc123_xyz") as client:
|
|
me = await client.gateway.me()
|
|
print(me.scopes)
|
|
|
|
asyncio.run(main())
|
|
```
|
|
|
|
You can create new keys programmatically using the gateway client:
|
|
|
|
```python
|
|
from conduit_client.models.common import CreateKeyRequest
|
|
|
|
new_key = client.gateway.create_key(
|
|
CreateKeyRequest(display_name="production", scopes=["sms:send", "sms:read"])
|
|
)
|
|
print(new_key.key) # returned exactly once by the server
|
|
```
|
|
|
|
Async version:
|
|
|
|
```python
|
|
from conduit_client import AsyncConduitClient
|
|
from conduit_client.models.common import CreateKeyRequest
|
|
|
|
async def main() -> None:
|
|
async with AsyncConduitClient("mega_sk_live_...") as client:
|
|
new_key = await client.gateway.create_key(
|
|
CreateKeyRequest(display_name="production", scopes=["sms:send", "sms:read"])
|
|
)
|
|
print(new_key.key)
|
|
|
|
asyncio.run(main())
|
|
```
|
|
|
|
## Sync vs async
|
|
|
|
Both clients expose the same façade subclients:
|
|
|
|
```python
|
|
# Sync
|
|
with ConduitClient("mega_sk_live_...") as client:
|
|
me = client.gateway.me()
|
|
|
|
# Async
|
|
async with AsyncConduitClient("mega_sk_live_...") as client:
|
|
me = await client.gateway.me()
|
|
```
|
|
|
|
The asynchronous client uses `httpx.AsyncClient` under the hood and must be awaited.
|
|
|
|
## Timeouts and retries
|
|
|
|
```python
|
|
client = ConduitClient(
|
|
"mega_sk_live_...",
|
|
timeout=60.0,
|
|
max_retries=2,
|
|
)
|
|
```
|
|
|
|
Async version:
|
|
|
|
```python
|
|
from conduit_client import AsyncConduitClient
|
|
|
|
client = AsyncConduitClient(
|
|
"mega_sk_live_...",
|
|
timeout=60.0,
|
|
max_retries=2,
|
|
)
|
|
```
|
|
|
|
Retries are applied only to idempotent methods (`GET`, `HEAD`, `OPTIONS`, `PUT`, `DELETE`) and only for status codes `429`, `502`, `503`, and `504`. Backoff is exponential (0.5s, 1s, 2s, ...).
|
|
|
|
## Custom httpx options
|
|
|
|
Any extra keyword arguments are forwarded to the underlying `httpx.Client` or `httpx.AsyncClient`:
|
|
|
|
```python
|
|
client = ConduitClient(
|
|
"mega_sk_live_...",
|
|
proxies="http://proxy.example.com:8080",
|
|
)
|
|
```
|
|
|
|
Async version:
|
|
|
|
```python
|
|
from conduit_client import AsyncConduitClient
|
|
|
|
client = AsyncConduitClient(
|
|
"mega_sk_live_...",
|
|
proxies="http://proxy.example.com:8080",
|
|
)
|
|
```
|
|
|
|
## Handling free-form API bodies
|
|
|
|
Some Conduit endpoints forward extra fields directly to the upstream provider (for example, Proxmox VM/container creation, Transmission session patches, DNS zone modifications). For these, the client accepts a `dict[str, Any]` or a Pydantic model that allows extra fields:
|
|
|
|
```python
|
|
client.compute.patch_vm(100, {"memory": 4096, "cores": 4})
|
|
client.torrents.patch_session({"download-dir": "/mnt/media"})
|
|
client.dns.patch_zone("example.com", {"type": "slave"})
|
|
```
|
|
|
|
Async version:
|
|
|
|
```python
|
|
from conduit_client import AsyncConduitClient
|
|
|
|
async def main() -> None:
|
|
async with AsyncConduitClient("mega_sk_live_...") as client:
|
|
await client.compute.patch_vm(100, {"memory": 4096, "cores": 4})
|
|
await client.torrents.patch_session({"download-dir": "/mnt/media"})
|
|
await client.dns.patch_zone("example.com", {"type": "slave"})
|
|
|
|
asyncio.run(main())
|
|
```
|
|
|
|
## Errors
|
|
|
|
All client errors inherit from `ConduitError`. HTTP error responses are raised as typed exceptions:
|
|
|
|
| Exception | Trigger |
|
|
| --- | --- |
|
|
| `AuthenticationError` | 401 / 403 |
|
|
| `NotFoundError` | 404 |
|
|
| `ValidationError` | 422 |
|
|
| `ConflictError` | 409 |
|
|
| `RateLimitError` | 429 |
|
|
| `ConduitAPIError` | any other 4xx / 5xx |
|
|
|
|
Each exception exposes `status_code` and `response_body`:
|
|
|
|
```python
|
|
from conduit_client import NotFoundError
|
|
|
|
try:
|
|
client.wiki.get_raw("missing:page")
|
|
except NotFoundError as exc:
|
|
print(exc.status_code)
|
|
print(exc.response_body)
|
|
```
|
|
|
|
Async version:
|
|
|
|
```python
|
|
from conduit_client import AsyncConduitClient, NotFoundError
|
|
|
|
async def main() -> None:
|
|
async with AsyncConduitClient("mega_sk_live_...") as client:
|
|
try:
|
|
await client.wiki.get_raw("missing:page")
|
|
except NotFoundError as exc:
|
|
print(exc.status_code)
|
|
print(exc.response_body)
|
|
|
|
asyncio.run(main())
|
|
```
|
|
|
|
## Façade clients
|
|
|
|
| Attribute | Scope | Description |
|
|
| --- | --- | --- |
|
|
| `client.gateway` | `gateway:read`, `keys:create`, etc. | Health, keys, audit log |
|
|
| `client.sms` | `sms:read`, `sms:send` | SMS/MMS send/list/delete |
|
|
| `client.media_ingest` | `video:read`, `video:admin` | MeTube jobs/subscriptions |
|
|
| `client.media_library` | `video:read`, `video:admin` | Jellyfin search/sessions |
|
|
| `client.torrents` | `video:read`, `video:admin` | Transmission management |
|
|
| `client.wiki` | `wiki:read`, `wiki:write` | DokuWiki read/write |
|
|
| `client.compute` | `vm:read`, `vm:start`, `vm:stop` | Proxmox VMs/containers/storage |
|
|
| `client.dns` | `dns:read`, `dns:write`, `dns:delete` | BIND9 servers/zones/records |
|
|
|
|
## Path parameters
|
|
|
|
Path parameters such as DokuWiki page IDs and DNS zone names are URL-encoded automatically. Slashes, colons, and other special characters are handled safely.
|
|
|
|
```python
|
|
client.wiki.get_raw("namespace:sub:page")
|
|
client.dns.get_zone("example.com")
|
|
```
|
|
|
|
Async version:
|
|
|
|
```python
|
|
from conduit_client import AsyncConduitClient
|
|
|
|
async def main() -> None:
|
|
async with AsyncConduitClient("mega_sk_live_...") as client:
|
|
await client.wiki.get_raw("namespace:sub:page")
|
|
await client.dns.get_zone("example.com")
|
|
|
|
asyncio.run(main())
|
|
```
|