Architecture and resource ownership
Base provider interfaces separate application contracts from provider adapters. Shared validation, HTTP mechanics and result models reduce repetition. Adapters retain their own serialization, schema handling and code mappings. Strategy-based injection simplifies tests; it does not promise identical provider capabilities.
Transport lifetime
Each provider lazily creates one owned synchronous HTTPX client and reuses it.
close() is idempotent; closed providers cannot be reused. Context managers close
owned clients even on errors. Injecting http_client= borrows an existing client:
provider shutdown does not close it. The caller must close it after all borrowers
finish. Configure timeout= and owned pool limits= deliberately.
Do not close a shared provider while requests are active. FastAPI lifespan closes after serving ends; the Django example uses a simpler request-owned scope, at the cost of no pooling across requests. Multi-process workers have separate clients and Pathao token caches; do not initialize network resources before forking.
Pathao uses process-local locking, monotonic expiry and atomic token replacement.
GET replay is bounded to one retry after 401; order POST replay requires explicit
retry_unauthorized_orders=True. No generic retry layer is added.
Results and application boundaries
Parcel, provider options, capabilities and outcome models have annotations and
ship in a py.typed package. Raw JSON and provider statuses remain dynamic. SDK
capabilities describe available methods, not live certification or verified
upstream status semantics; see the matrix.
The fake provider and framework examples demonstrate injection, client lifetime and error conversion. Native async adapters are planned; all current provider HTTP operations block the calling thread.