docs: add OpenAPI-first wiki (overview, components, use cases for templates/client/codegen, design, security, error handling, testing) and refresh lib docs index

This commit is contained in:
2026-09-15 22:29:05 +05:30
parent adeb02e162
commit b559323dfe
26 changed files with 1590 additions and 86 deletions

View File

@@ -0,0 +1,156 @@
# 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.<operationId>(
*,
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)