Error Propagation¶
Every adapter in the OpenFrame ecosystem raises only AdapterError subclasses — never raw driver exceptions. This page shows how an error travels from the database driver to the HTTP response.
Error Flow¶
sequenceDiagram
participant DB as asyncpg
participant AD as PostgresRepository
participant TP as TracingProxy
participant SVC as Service layer
participant API as FastAPI route
participant MW as TelemetryMiddleware
DB-->>AD: asyncpg.PostgresError
AD-->>AD: raise AdapterQueryError(\n message="...",\n adapter="postgres",\n operation="get",\n cause=exc\n) from exc
AD-->>TP: AdapterQueryError propagates
TP-->>TP: span closes (error recorded automatically)
TP-->>SVC: AdapterQueryError propagates
SVC-->>API: catches AdapterNotFoundError → 404\nor catches AdapterError → 500
API-->>MW: response via send_with_telemetry
MW-->>MW: status >= 400 → StatusCode.ERROR\nerror_count.add(1)
MW-->>MW: emit WARNING log
Exception Hierarchy¶
AdapterError (base)
├── AdapterConnectionError — backend unreachable
├── AdapterQueryError — operation failed post-connection
├── AdapterNotFoundError — entity does not exist
├── AdapterConfigurationError— missing / invalid config
└── AdapterTimeoutError — operation exceeded timeout
Service layers catch at the appropriate specificity:
try:
entity = await repo.get(entity_id)
except AdapterNotFoundError:
raise HTTPException(status_code=404, detail="not found")
except AdapterError:
raise HTTPException(status_code=500, detail="internal error")
str() Format¶
AdapterError.__str__ produces a structured string — not a raw tuple repr:
[postgres.get] entity not found
[postgres.create] insert failed — caused by: null constraint violation
The format is [adapter.operation] message with an optional — caused by: <cause> suffix when a cause is present.
→ See exceptions module for the full class reference.