Comprehensive guide to handling errors in the supermetrics Python SDK.
All SDK exceptions inherit from the SupermetricsError base class:
SupermetricsError (Base exception)
├── AuthenticationError (HTTP 401)
├── ValidationError (HTTP 400, 422)
├── APIError (HTTP 403, 404, 429, 5xx)
└── NetworkError (Network-level failures)
You can catch specific exceptions for granular error handling, or catch SupermetricsError to handle all SDK errors.
Base exception for all SDK errors.
from supermetrics import SupermetricsError
try:
client.accounts.list(ds_id="GAWA")
except SupermetricsError as e:
print(f"SDK Error: {e.message}")
print(f"HTTP Status: {e.status_code}")
print(f"Endpoint: {e.endpoint}")
print(f"Response: {e.response_body}")
Attributes:
message (str): Human-readable error descriptionstatus_code (int |
None): HTTP status code (if applicable) |
endpoint (str |
None): API endpoint that was called |
response_body (str |
None): Raw API response for debugging |
When to use:
Raised when API authentication fails (HTTP 401).
from supermetrics import AuthenticationError
try:
client = SupermetricsClient(api_key="invalid_key")
client.login_links.list()
except AuthenticationError as e:
print(f"Authentication failed: {e.message}")
# Log error and prompt user to check API key
Common causes:
How to fix:
Example: Graceful authentication handling
import os
from supermetrics import SupermetricsClient, AuthenticationError
def get_authenticated_client():
"""Get authenticated client with error handling."""
api_key = os.getenv("SUPERMETRICS_API_KEY")
if not api_key:
raise ValueError("SUPERMETRICS_API_KEY environment variable not set")
try:
client = SupermetricsClient(api_key=api_key)
# Test authentication
client.login_links.list()
return client
except AuthenticationError:
raise ValueError("Invalid API key. Please check your credentials.")
try:
client = get_authenticated_client()
except ValueError as e:
print(f"Setup error: {e}")
exit(1)
Raised when request validation fails (HTTP 400, 422).
from supermetrics import ValidationError
try:
# Missing required parameter
client.accounts.list(ds_id="")
except ValidationError as e:
print(f"Validation error: {e.message}")
print(f"Response details: {e.response_body}")
Common causes:
How to fix:
Example: Parameter validation
from supermetrics import SupermetricsClient, ValidationError
from datetime import datetime
def execute_query_with_validation(
client,
ds_id: str,
account_ids: list[str],
fields: list[str],
start_date: str,
end_date: str
):
"""Execute query with input validation."""
# Validate inputs
if not ds_id:
raise ValueError("ds_id cannot be empty")
if not account_ids:
raise ValueError("At least one account ID required")
if not fields:
raise ValueError("At least one field required")
if not start_date or not end_date:
raise ValueError("Start and end dates are required")
try:
return client.queries.execute(
ds_id=ds_id,
ds_accounts=account_ids,
fields=fields,
start_date=start_date,
end_date=end_date
)
except ValidationError as e:
print(f"API validation failed: {e.message}")
# Log the full response for debugging
if e.response_body:
print(f"Details: {e.response_body}")
raise
# Usage
client = SupermetricsClient(api_key="your_key")
try:
result = execute_query_with_validation(
client,
ds_id="GAWA",
account_ids=["123456789"],
fields=["Date", "Sessions"],
start_date="2024-01-01",
end_date="2024-01-31"
)
except ValueError as e:
print(f"Input error: {e}")
except ValidationError as e:
print(f"API validation error: {e.message}")
Raised for API-level errors during request processing (HTTP 403, 404, 429, 5xx).
from supermetrics import APIError
try:
client.logins.get("nonexistent_id")
except APIError as e:
print(f"API error ({e.status_code}): {e.message}")
# Handle specific status codes
if e.status_code == 404:
print("Resource not found")
elif e.status_code == 429:
print("Rate limited - retry later")
elif e.status_code == 403:
print("Forbidden - insufficient permissions")
elif e.status_code >= 500:
print("Server error - try again later")
Common causes:
How to fix:
Example: Comprehensive API error handling
import time
from supermetrics import SupermetricsClient, APIError
def execute_with_retry(client, max_retries=3, **query_params):
"""Execute query with retry logic for transient errors."""
for attempt in range(max_retries):
try:
return client.queries.execute(**query_params)
except APIError as e:
# Rate limiting - exponential backoff
if e.status_code == 429:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # 1s, 2s, 4s
print(f"Rate limited. Waiting {wait_time}s...")
time.sleep(wait_time)
continue
else:
raise
# Server errors - retry with delay
elif e.status_code in [500, 503, 504]:
if attempt < max_retries - 1:
wait_time = 5
print(f"Server error. Retrying in {wait_time}s...")
time.sleep(wait_time)
continue
else:
raise
# Not found or forbidden - don't retry
elif e.status_code in [403, 404]:
print(f"Error {e.status_code}: {e.message}")
raise
# Other errors - raise immediately
else:
raise
# Usage
client = SupermetricsClient(api_key="your_key")
try:
result = execute_with_retry(
client,
ds_id="GAWA",
ds_accounts=["123456789"],
fields=["Date", "Sessions"],
start_date="2024-01-01",
end_date="2024-01-31"
)
except APIError as e:
print(f"Query failed after retries: {e.message}")
Raised for network-level failures before/during HTTP request.
from supermetrics import NetworkError
try:
client = SupermetricsClient(api_key="key", timeout=1.0)
client.login_links.list()
except NetworkError as e:
print(f"Network error: {e.message}")
# Check network connectivity
# Verify firewall/proxy settings
Common causes:
How to fix:
Example: Network error handling with timeout
from supermetrics import SupermetricsClient, NetworkError
import time
def create_robust_client(api_key, timeout=30.0, max_retries=3):
"""Create client with network error retry logic."""
for attempt in range(max_retries):
try:
client = SupermetricsClient(api_key=api_key, timeout=timeout)
# Test connectivity
client.login_links.list()
return client
except NetworkError as e:
print(f"Network error (attempt {attempt + 1}/{max_retries}): {e.message}")
if attempt < max_retries - 1:
wait_time = 2 ** attempt
print(f"Retrying in {wait_time}s...")
time.sleep(wait_time)
else:
print("Failed to establish connection after retries")
raise
# Usage
try:
client = create_robust_client(
api_key="your_key",
timeout=30.0,
max_retries=3
)
print("Client connected successfully")
except NetworkError:
print("Unable to connect. Check network connectivity.")
Handle each exception type differently:
from supermetrics import (
SupermetricsClient,
AuthenticationError,
ValidationError,
APIError,
NetworkError
)
client = SupermetricsClient(api_key="your_key")
try:
result = client.queries.execute(
ds_id="GAWA",
ds_accounts=["123456789"],
fields=["Date", "Sessions"],
start_date="2024-01-01",
end_date="2024-01-31"
)
except AuthenticationError as e:
# Authentication issues - likely configuration problem
print(f"Auth error: {e.message}")
# Action: Check API key configuration
except ValidationError as e:
# Validation issues - likely code bug
print(f"Validation error: {e.message}")
# Action: Fix parameter values
except APIError as e:
# API issues - handle based on status code
if e.status_code == 429:
print("Rate limited - wait and retry")
elif e.status_code == 404:
print("Resource not found - check IDs")
else:
print(f"API error: {e.message}")
except NetworkError as e:
# Network issues - likely transient
print(f"Network error: {e.message}")
# Action: Retry request
Handle all SDK errors uniformly:
from supermetrics import SupermetricsClient, SupermetricsError
client = SupermetricsClient(api_key="your_key")
try:
result = client.queries.execute(
ds_id="GAWA",
ds_accounts=["123456789"],
fields=["Date", "Sessions"],
start_date="2024-01-01",
end_date="2024-01-31"
)
except SupermetricsError as e:
# Handle all SDK errors
print(f"SDK Error: {e.message}")
print(f"Status Code: {e.status_code}")
# Log error details
import logging
logging.error(
f"SDK error: {e.message}",
extra={
"status_code": e.status_code,
"endpoint": e.endpoint,
"response_body": e.response_body
}
)
Different error handling for different operations:
from supermetrics import SupermetricsClient, APIError
client = SupermetricsClient(api_key="your_key")
# Critical operation - fail fast
try:
login = client.logins.get(login_id="login_123")
except APIError as e:
if e.status_code == 404:
raise ValueError(f"Login not found: {login_id}")
raise
# Optional operation - continue on error
try:
client.login_links.close(link_id="link_123")
except APIError as e:
if e.status_code == 404:
print("Link already closed or doesn't exist")
else:
print(f"Warning: Failed to close link: {e.message}")
# Batch operation - collect errors
errors = []
for account_id in account_ids:
try:
result = client.queries.execute(...)
results.append(result)
except APIError as e:
errors.append({"account_id": account_id, "error": e.message})
if errors:
print(f"Errors occurred for {len(errors)} accounts")
for error in errors:
print(f" {error['account_id']}: {error['error']}")
Error handling in async code:
import asyncio
from supermetrics import SupermetricsAsyncClient, APIError
async def fetch_with_error_handling(client, account_id):
"""Fetch data with error handling."""
try:
result = await client.queries.execute(
ds_id="GAWA",
ds_accounts=[account_id],
fields=["Date", "Sessions"],
start_date="2024-01-01",
end_date="2024-01-31"
)
return {"account_id": account_id, "result": result, "error": None}
except APIError as e:
return {"account_id": account_id, "result": None, "error": e.message}
async def main():
async with SupermetricsAsyncClient(api_key="your_key") as client:
# Fetch data for multiple accounts
tasks = [
fetch_with_error_handling(client, account_id)
for account_id in ["123", "456", "789"]
]
results = await asyncio.gather(*tasks)
# Process results
successful = [r for r in results if r["error"] is None]
failed = [r for r in results if r["error"] is not None]
print(f"Successful: {len(successful)}")
print(f"Failed: {len(failed)}")
for failure in failed:
print(f" {failure['account_id']}: {failure['error']}")
asyncio.run(main())
from supermetrics import SupermetricsClient, AuthenticationError
try:
client = SupermetricsClient(api_key="invalid_key")
client.login_links.list()
except AuthenticationError as e:
print(f"Authentication failed: {e.message}")
print("Please check your API key in the environment variables")
from supermetrics import SupermetricsClient, APIError
client = SupermetricsClient(api_key="your_key")
try:
login = client.logins.get(login_id="nonexistent_id")
except APIError as e:
if e.status_code == 404:
print("Login not found. Verify the login ID is correct.")
else:
raise
import time
from supermetrics import SupermetricsClient, APIError
client = SupermetricsClient(api_key="your_key")
def execute_with_backoff(client, **query_params):
"""Execute query with exponential backoff on rate limits."""
max_retries = 5
base_delay = 1
for attempt in range(max_retries):
try:
return client.queries.execute(**query_params)
except APIError as e:
if e.status_code == 429 and attempt < max_retries - 1:
delay = base_delay * (2 ** attempt) # 1, 2, 4, 8, 16 seconds
print(f"Rate limited. Retrying in {delay}s...")
time.sleep(delay)
else:
raise
result = execute_with_backoff(
client,
ds_id="GAWA",
ds_accounts=["123456789"],
fields=["Date", "Sessions"],
start_date="2024-01-01",
end_date="2024-01-31"
)
from supermetrics import SupermetricsClient, NetworkError
# Increase timeout for slow connections
client = SupermetricsClient(api_key="your_key", timeout=60.0)
try:
result = client.queries.execute(
ds_id="GAWA",
ds_accounts=["123456789"],
fields=["Date", "Sessions"],
start_date="2024-01-01",
end_date="2024-12-31" # Large date range
)
except NetworkError as e:
print(f"Request timed out: {e.message}")
print("Try reducing the date range or increasing the timeout")
from supermetrics import SupermetricsClient, ValidationError
client = SupermetricsClient(api_key="your_key")
try:
result = client.queries.execute(
ds_id="GAWA",
ds_accounts=[], # Empty list - invalid
fields=["Date", "Sessions"],
start_date="2024-01-01",
end_date="2024-01-31"
)
except ValidationError as e:
print(f"Validation error: {e.message}")
print("At least one account ID is required")
Never let exceptions go unhandled:
# Bad
result = client.queries.execute(...)
# Good
try:
result = client.queries.execute(...)
except SupermetricsError as e:
logger.error(f"Query failed: {e.message}")
# Handle error appropriately
Include all available error information:
import logging
logger = logging.getLogger(__name__)
try:
result = client.queries.execute(...)
except SupermetricsError as e:
logger.error(
f"API request failed: {e.message}",
extra={
"status_code": e.status_code,
"endpoint": e.endpoint,
"response_body": e.response_body,
}
)
Retry transient errors (rate limits, server errors):
import time
from supermetrics import APIError
def retry_on_transient_error(func, max_retries=3):
"""Decorator to retry on transient errors."""
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except APIError as e:
if e.status_code in [429, 500, 503, 504]:
if attempt < max_retries - 1:
wait = 2 ** attempt
time.sleep(wait)
continue
raise
return wrapper
@retry_on_transient_error
def fetch_data(client):
return client.queries.execute(...)
Convert technical errors to user-friendly messages:
from supermetrics import AuthenticationError, ValidationError, APIError
try:
result = client.queries.execute(...)
except AuthenticationError:
print("Your API key is invalid. Please check your configuration.")
except ValidationError as e:
print(f"Invalid input: {e.message}")
print("Please verify your query parameters.")
except APIError as e:
if e.status_code == 429:
print("Too many requests. Please wait a moment and try again.")
elif e.status_code == 404:
print("The requested resource was not found.")
else:
print(f"An error occurred: {e.message}")
Catch specific exceptions for better control:
# Good - specific handling
try:
result = client.queries.execute(...)
except ValidationError as e:
fix_parameters()
except APIError as e:
if e.status_code == 429:
retry_later()
# Less ideal - catch-all
try:
result = client.queries.execute(...)
except SupermetricsError as e:
# Harder to handle different error types
pass
Always close clients, even when errors occur:
# Good - context manager handles cleanup
with SupermetricsClient(api_key="key") as client:
result = client.queries.execute(...)
# Also good - manual cleanup
client = SupermetricsClient(api_key="key")
try:
result = client.queries.execute(...)
finally:
client.close()
Set up monitoring for production errors:
import logging
from supermetrics import SupermetricsError
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
try:
result = client.queries.execute(...)
except SupermetricsError as e:
# Log error
logger.error(f"Query failed: {e.message}", extra={
"status_code": e.status_code,
"endpoint": e.endpoint
})
# Send alert for critical errors
if isinstance(e, AuthenticationError):
send_alert("Critical: API authentication failed")
elif isinstance(e, APIError) and e.status_code >= 500:
send_alert(f"Server error: {e.message}")
AuthenticationError, ValidationError, etc.) for granular controlFor more information, see: