|
| 1 | +# Async Usage |
| 2 | + |
| 3 | +The SDK provides async equivalents for all sync APIs, built on `grpc.aio`. |
| 4 | + |
| 5 | +## AsyncConfigClient |
| 6 | + |
| 7 | +```python |
| 8 | +from opendecree import AsyncConfigClient |
| 9 | + |
| 10 | +async with AsyncConfigClient("localhost:9090", subject="myapp") as client: |
| 11 | + # Typed gets (same overload pattern as sync) |
| 12 | + fee = await client.get("tenant-id", "payments.fee") # → str |
| 13 | + retries = await client.get("tenant-id", "payments.retries", int) # → int |
| 14 | + enabled = await client.get("tenant-id", "payments.enabled", bool)# → bool |
| 15 | + |
| 16 | + # Get all config |
| 17 | + all_config = await client.get_all("tenant-id") # → dict[str, str] |
| 18 | + |
| 19 | + # Writes |
| 20 | + await client.set("tenant-id", "payments.fee", "0.5%") |
| 21 | + await client.set_many("tenant-id", {"a": "1", "b": "2"}) |
| 22 | + await client.set_null("tenant-id", "payments.fee") |
| 23 | +``` |
| 24 | + |
| 25 | +Same constructor options as `ConfigClient` — see [Configuration](configuration.md). |
| 26 | + |
| 27 | +## AsyncConfigWatcher |
| 28 | + |
| 29 | +```python |
| 30 | +from opendecree import AsyncConfigClient |
| 31 | + |
| 32 | +async with AsyncConfigClient("localhost:9090", subject="myapp") as client: |
| 33 | + async with client.watch("tenant-id") as watcher: |
| 34 | + fee = watcher.field("payments.fee", float, default=0.01) |
| 35 | + enabled = watcher.field("payments.enabled", bool, default=False) |
| 36 | + |
| 37 | + # .value works the same |
| 38 | + print(fee.value) |
| 39 | + |
| 40 | + # __bool__ works the same |
| 41 | + if enabled: |
| 42 | + print("enabled") |
| 43 | +``` |
| 44 | + |
| 45 | +### Async change iteration |
| 46 | + |
| 47 | +Use `async for` instead of `for`: |
| 48 | + |
| 49 | +```python |
| 50 | +async with client.watch("tenant-id") as watcher: |
| 51 | + fee = watcher.field("payments.fee", float, default=0.01) |
| 52 | + |
| 53 | + async for change in fee.changes(): |
| 54 | + print(f"{change.old_value} -> {change.new_value}") |
| 55 | +``` |
| 56 | + |
| 57 | +### Callbacks |
| 58 | + |
| 59 | +Callbacks work the same as the sync watcher — they are plain functions (not coroutines): |
| 60 | + |
| 61 | +```python |
| 62 | +@fee.on_change |
| 63 | +def handle_change(old: float, new: float): |
| 64 | + print(f"Fee changed: {old} -> {new}") |
| 65 | +``` |
| 66 | + |
| 67 | +## Differences from sync |
| 68 | + |
| 69 | +| Aspect | Sync | Async | |
| 70 | +|--------|------|-------| |
| 71 | +| Client | `ConfigClient` | `AsyncConfigClient` | |
| 72 | +| Context manager | `with` | `async with` | |
| 73 | +| Methods | `client.get(...)` | `await client.get(...)` | |
| 74 | +| Watcher | `ConfigWatcher` | `AsyncConfigWatcher` | |
| 75 | +| Change iterator | `for change in field.changes()` | `async for change in field.changes()` | |
| 76 | +| Background work | Thread | asyncio Task | |
| 77 | +| Callbacks | Same (plain functions) | Same (plain functions) | |
| 78 | + |
| 79 | +The public API is otherwise identical — same constructor options, same `get()` overloads, same `WatchedField[T]` interface. |
| 80 | + |
| 81 | +## When to use async |
| 82 | + |
| 83 | +Use the async API when: |
| 84 | +- Your application already uses asyncio (FastAPI, aiohttp, etc.) |
| 85 | +- You need to manage many concurrent connections efficiently |
| 86 | + |
| 87 | +Use the sync API when: |
| 88 | +- Your application is synchronous (Flask, Django, scripts) |
| 89 | +- Simplicity matters more than concurrency |
| 90 | + |
| 91 | +Both APIs are equally capable and tested. |
0 commit comments