Skip to main content

Error Handling

The Ashr Labs SDK provides specific exception classes for different error scenarios, making it easy to handle errors gracefully.

Exception Hierarchy

AshrLabsError (base)
├── AuthenticationError (401)
├── AuthorizationError (403)
├── NotFoundError (404)
├── ValidationError (422)
├── RateLimitError (429)
└── ServerError (5xx)

Exception Classes

AshrLabsError

Base exception for all SDK errors.

Attributes:

  • message (str): Error message
  • status_code (int | None): HTTP status code if applicable
  • response (dict | None): Raw response body if available
from ashr_labs import AshrLabsError

try:
result = client.get_dataset(dataset_id=42)
except AshrLabsError as e:
print(f"Error: {e.message}")
print(f"Status Code: {e.status_code}")
print(f"Response: {e.response}")

AuthenticationError

Raised when API key authentication fails (HTTP 401).

Common causes:

  • Invalid API key
  • Expired API key
  • Revoked API key
  • Malformed Authorization header
from ashr_labs import AuthenticationError

try:
datasets = client.list_datasets(tenant_id=1)
except AuthenticationError as e:
print("Authentication failed!")
print("Please check your API key is valid and not expired.")
# Log the user out or prompt for new credentials

AuthorizationError

Raised when the API key lacks permission (HTTP 403).

Common causes:

  • Trying to access a resource in a different tenant
  • Using API key for OAuth-only endpoints
  • Insufficient scopes on the API key
from ashr_labs import AuthorizationError

try:
# Attempting to access another tenant's data
dataset = client.get_dataset(dataset_id=999)
except AuthorizationError as e:
print("Access denied!")
print("You don't have permission to access this resource.")

NotFoundError

Raised when a requested resource doesn't exist (HTTP 404).

Common causes:

  • Invalid resource ID
  • Resource was deleted
  • Typo in the ID
from ashr_labs import NotFoundError

try:
dataset = client.get_dataset(dataset_id=99999)
except NotFoundError as e:
print(f"Dataset not found: {e.message}")
# Handle missing resource gracefully

ValidationError

Raised when request validation fails (HTTP 422).

Common causes:

  • Missing required fields
  • Invalid field types
  • Schema validation failure
  • Invalid parameter values
from ashr_labs import ValidationError

try:
run = client.create_run(
tenant_id=1,
dataset_id=-1, # Invalid ID
result={}
)
except ValidationError as e:
print(f"Validation failed: {e.message}")
# Show user which fields need correction

RateLimitError

Raised when rate limits are exceeded (HTTP 429).

Common causes:

  • Too many requests in a short period
  • Burst limit exceeded
import time
from ashr_labs import RateLimitError

def fetch_with_retry(client, dataset_id, max_retries=3):
for attempt in range(max_retries):
try:
return client.get_dataset(dataset_id=dataset_id)
except RateLimitError:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # Exponential backoff
print(f"Rate limited. Waiting {wait_time}s...")
time.sleep(wait_time)
else:
raise

ServerError

Raised when the server encounters an internal error (HTTP 5xx).

Common causes:

  • Server maintenance
  • Temporary outage
  • Internal service failure
from ashr_labs import ServerError

try:
datasets = client.list_datasets(tenant_id=1)
except ServerError as e:
print("Server error occurred. Please try again later.")
# Log the error for monitoring
log_error(f"Server error: {e.status_code} - {e.message}")

Best Practices

1. Catch Specific Exceptions

Always catch the most specific exception first:

from ashr_labs import (
AshrLabsClient,
AuthenticationError,
AuthorizationError,
NotFoundError,
ValidationError,
RateLimitError,
ServerError,
AshrLabsError,
)

try:
dataset = client.get_dataset(dataset_id=42)
except AuthenticationError:
# Handle auth failure - maybe refresh credentials
handle_auth_failure()
except AuthorizationError:
# Handle permission denied
show_permission_error()
except NotFoundError:
# Handle missing resource
show_not_found_message()
except ValidationError as e:
# Handle validation errors
show_validation_errors(e.response)
except RateLimitError:
# Handle rate limiting
schedule_retry()
except ServerError:
# Handle server errors
show_temporary_error()
except AshrLabsError as e:
# Catch-all for other API errors
log_unexpected_error(e)

2. Implement Retry Logic

For transient errors, implement retry with exponential backoff:

import time
from ashr_labs import RateLimitError, ServerError

def robust_request(func, *args, max_retries=3, **kwargs):
"""Execute a request with automatic retry for transient errors."""
last_error = None

for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except (RateLimitError, ServerError) as e:
last_error = e
if attempt < max_retries - 1:
wait_time = (2 ** attempt) + (random.random() * 0.1)
time.sleep(wait_time)
continue
except AshrLabsError:
# Don't retry auth/validation/not-found errors
raise

raise last_error

# Usage
dataset = robust_request(client.get_dataset, dataset_id=42)

3. Log Errors Appropriately

import logging

logger = logging.getLogger(__name__)

try:
result = client.create_run(...)
except AuthenticationError as e:
logger.error(f"Auth failed: {e.message}")
raise
except ValidationError as e:
logger.warning(f"Validation error: {e.message}", extra={"response": e.response})
raise
except ServerError as e:
logger.critical(f"Server error {e.status_code}: {e.message}")
raise

4. User-Friendly Error Messages

Map technical errors to user-friendly messages:

ERROR_MESSAGES = {
AuthenticationError: "Your session has expired. Please log in again.",
AuthorizationError: "You don't have permission to perform this action.",
NotFoundError: "The requested item could not be found.",
ValidationError: "Please check your input and try again.",
RateLimitError: "Too many requests. Please wait a moment and try again.",
ServerError: "We're experiencing technical difficulties. Please try again later.",
}

def get_user_message(error):
for error_type, message in ERROR_MESSAGES.items():
if isinstance(error, error_type):
return message
return "An unexpected error occurred."

5. Context Managers for Cleanup

from contextlib import contextmanager

@contextmanager
def safe_api_operation(operation_name):
"""Context manager for safe API operations with cleanup."""
try:
yield
except AshrLabsError as e:
logger.error(f"{operation_name} failed: {e}")
# Perform any necessary cleanup
raise

# Usage
with safe_api_operation("Create Run"):
run = client.create_run(...)

Network Errors

The SDK also handles network-level errors:

from ashr_labs import AshrLabsError

try:
result = client.health_check()
except AshrLabsError as e:
if "Network error" in str(e):
print("Cannot connect to the API. Check your internet connection.")
elif "timed out" in str(e).lower():
print("Request timed out. The server may be slow or unreachable.")
else:
raise

Debugging Tips

Enable detailed logging to debug issues:

import logging

# Enable debug logging for the SDK
logging.basicConfig(level=logging.DEBUG)

# Or specifically for urllib
logging.getLogger("urllib.request").setLevel(logging.DEBUG)

Check the raw response in exceptions:

try:
result = client.get_dataset(dataset_id=42)
except AshrLabsError as e:
print(f"Status: {e.status_code}")
print(f"Message: {e.message}")
print(f"Raw response: {e.response}")