Error Handling¶
haystack-py uses a structured exception hierarchy to distinguish between different failure modes. This guide covers the exception types, error grids, and patterns for robust error handling.
See also
Core for the full exception API reference.
Exception Hierarchy¶
HaystackError Base for all haystack-py exceptions
├── AuthError Authentication failures (SCRAM, token, cert)
├── CallError Server returned an error grid
└── NetworkError Connection, timeout, transport failures
All exceptions inherit from HaystackError, so you
can catch everything with a single handler or be specific:
from hs_py import Client, HaystackError, CallError, AuthError, NetworkError
async with Client("http://host/api", "user", "pass") as c:
try:
data = await c.read("point")
except AuthError:
# Bad credentials, expired token, cert rejected
...
except CallError as e:
# Server returned an error grid
print(e.grid) # The error Grid
...
except NetworkError:
# Connection refused, timeout, DNS failure
...
except HaystackError:
# Catch-all for any Haystack error
...
CallError and Error Grids¶
When a Haystack server encounters an error processing a request, it returns
an error grid — a grid with error metadata in its meta dict.
The client detects error grids and raises CallError.
from hs_py.errors import CallError
try:
data = await c.read("invalid:::filter")
except CallError as e:
print(f"Error: {e.dis}") # Human-readable description
print(f"Trace: {e.trace}") # Optional server stack trace (or None)
print(f"Error grid: {e.grid}") # The full error Grid
# e.grid.meta typically contains:
# "err": Marker
# "dis": "error message"
# "errTrace": "stack trace..."
Creating Error Grids¶
On the server side, create error grids with the factory method:
from hs_py import Grid
err = Grid.make_error("something went wrong")
assert err.is_error
# Check if any grid is an error
if grid.is_error:
print("Got an error grid:", grid.meta.get("dis"))
Raising Errors in Server Handlers¶
In HaystackOps methods, raise CallError to
return an error grid to the client:
from hs_py.errors import CallError
from hs_py import Grid, HaystackOps
class MyOps(HaystackOps):
async def read(self, grid: Grid) -> Grid:
if not grid.meta.get("filter"):
raise CallError("filter is required", Grid.make_error("filter is required"))
...
Unhandled exceptions in handlers are caught by the error middleware and converted to error grids automatically — but prefer explicit errors for known failure modes.
AuthError¶
AuthError is raised for authentication failures:
Wrong username or password during SCRAM handshake
Expired or invalid bearer token
Rejected client certificate in mTLS
from hs_py import Client
from hs_py.errors import AuthError
try:
async with Client("http://host/api", "wrong", "creds") as c:
await c.about()
except AuthError as e:
print(f"Authentication failed: {e}")
NetworkError¶
NetworkError wraps transport-level failures:
Connection refused or reset
DNS resolution failure
Request timeout
TLS handshake failure
from hs_py import Client
from hs_py.errors import NetworkError
try:
async with Client("http://unreachable:9999/api") as c:
await c.about()
except NetworkError as e:
print(f"Connection failed: {e}")
Common Patterns¶
Retry with Backoff¶
import asyncio
from hs_py import Client, NetworkError
async def read_with_retry(c: Client, filter_str: str, max_retries: int = 3):
for attempt in range(max_retries):
try:
return await c.read(filter_str)
except NetworkError:
if attempt == max_retries - 1:
raise
await asyncio.sleep(2 ** attempt)
Graceful Degradation¶
from hs_py import Client, CallError, NetworkError
async def safe_read(c: Client, filter_str: str):
try:
return await c.read(filter_str)
except CallError:
# Server can't fulfill the request — return empty
return Grid.make_empty()
except NetworkError:
# Server unreachable — return cached data or empty
return Grid.make_empty()
Distinguishing Error Types¶
from hs_py import HaystackError, CallError, AuthError, NetworkError
try:
result = await c.read("point")
except AuthError:
# Credentials issue — re-authenticate or alert admin
log.error("Authentication expired, re-authenticating")
except CallError as e:
# Application error — log and investigate
log.warning("Server error: %s", e.grid.meta.get("dis"))
except NetworkError:
# Infrastructure issue — retry or failover
log.error("Network unreachable, switching to backup")
except HaystackError:
# Unexpected — log full context
log.exception("Unexpected Haystack error")