# Use Case 3: The OperationId-Driven Client `OpenAPIClient` is the other side of the contract. It reads the *same* OpenAPI document the server runs on and exposes one callable per `operationId` โ€” so "client" and "server" are two views of one truth. --- ## ๐Ÿ” 1. Before You Start The client is `httpx`-based and returns raw `httpx.Response` objects โ€” no magic deserialization, no hidden schema inference: - **No** response Pydantic models - **No** implicit URL construction (path params are explicit) - **No** hand-written `requests.get(...)` scattered through your code --- ## ๐Ÿงฌ 2. Constructing the Client ```python from openapi_first.loader import load_openapi from openapi_first.client import OpenAPIClient spec = load_openapi("openapi.yaml") client = OpenAPIClient(spec) ``` The base URL comes from the spec's `servers` list (first entry) unless you pass `base_url` explicitly: ```python client = OpenAPIClient(spec, base_url="https://api.internal.myco/v1") ``` You can also hand over a preconfigured `httpx.Client` (custom transport, TLS, retries): ```python import httpx transport = httpx.HTTPTransport(retries=3) client = OpenAPIClient( spec, client=httpx.Client(transport=transport), ) ``` --- ## ๐Ÿ’ฅ 3. Fail-Fast at Construction `OpenAPIClient(...)` **raises immediately** if the contract is broken โ€” you find out at startup, not on the first request: | Violation | Error | |----------------------------------------|-----------------------------------| | Spec has no `servers` entry | `OpenAPIClientError` | | Spec has no `paths` | `OpenAPIClientError` | | Operation missing `operationId` | `OpenAPIClientError` | | Duplicate `operationId` | `OpenAPIClientError` | | Operation references unknown parameters| `OpenAPIClientError` | Missing *required parameters* fail at **call time** โ€” pydoclint-grade strictness on the wire. --- ## ๐Ÿ“ž 4. Calling Operations Every `operationId` becomes a method. The call signature is uniform across the whole client: ```python response = client.( *, path_params: dict | None = None, query: dict | None = None, headers: dict | None = None, body: Any | None = None, timeout: float | None = None, ) -> httpx.Response ``` Concrete examples (from the `crud_app` / `health_app` templates): ```python # No parameters โ€” simplest response = client.get_health() assert response.status_code == 200 # Path parameter response = client.get_item(path_params={"item_id": 3}) # Query parameters response = client.list_items(query={"limit": 10, "offset": 20}) # JSON request body response = client.create_item(body={"name": "Orange", "price": 0.8}) # Custom headers / timeout response = client.get_user( path_params={"user_id": 1}, headers={"X-Internal-Key": "..."}, timeout=30, ) ``` **Returns:** the raw `httpx.Response`, so `status_code`, `.json()`, `.headers` are all yours to inspect. --- ## ๐Ÿง  5. How Parameters Are Bound For each operation the client knows exactly *where* each parameter belongs: | OpenAPI location | Client kwarg | |------------------|-------------------| | `in: path` | `path_params[name]` | | `in: query` | `query[name]` | | `in: header` | `headers[name]` | | `requestBody` | `body` | JSON media types are sent as `json=`; other media types as raw `content=`. --- ## ๐Ÿ”„ 6. Server โ†” Client, One Spec ```bash # One directory, two processes uvicorn main:app --port 8000 # server python -c "import asyncio; from client_script import run; asyncio.run(run())" # client ``` Or the same client against a remote environment: ```python client = OpenAPIClient( spec, base_url="https://staging.internal.myco", ) ``` The URL is the *only* thing that changes between environments โ€” the contract never does. --- ## โœ๏ธ 7. OperationId as the API Because the client is operationId-driven: - Adding an operation = adding a method (and vice versa) - No operation can be "forgotten" by the client โ€” it's constructed from the spec - `operationId` is the one name you remember; the HTTP verb/path is an implementation detail If an `operationId` you call doesn't exist, you get an `AttributeError` at construction-scan time โ€” never a silent 404. --- ## Related - [01 โ€“ Overview](../01_overview.md) ยท [01 โ€“ Quickstart](01_quickstart.md) ยท [02 โ€“ Templates](02_templates.md)