ADR 0002: Keep synchronous public APIs over async internals

Status

Accepted

Context

The established public API consists of synchronous functions used heavily in scripts, notebooks, pandas workflows, and teaching examples. OGC pagination and chunk fan-out benefit from asynchronous I/O, but exposing only async functions would be a broad breaking change and would complicate common notebook use.

Calling asyncio.run directly is also unsafe when a caller already has a running event loop, as Jupyter commonly does.

Decision

Keep public service getters synchronous. Async-capable implementations may run inside a short-lived anyio blocking portal and use httpx.AsyncClient for pagination and bounded fan-out. Internal async functions are implementation details, not a second public API promise.

Ambient per-call policy must propagate into the worker context. A resumable OGC call captures the context needed to rebuild its remaining requests after the original getter has returned.

Consequences

  • Existing scripts and notebooks retain simple blocking call sites.

  • Concurrent network waits improve large paginated downloads.

  • Each top-level async-backed call pays worker-thread and portal startup cost.

  • Cancellation, context propagation, and client ownership need explicit tests.

  • A future public async API, if justified, should be additive and share the same lower-level contracts rather than duplicate behavior.

Compliance

Tests exercise calls inside an already running event loop, ambient context capture on resume, cancellation precedence, bounded in-flight work, and shared client ownership.