Error Handling
Proper error handling is essential for building robust applications with the YouVersion Bible Client.
Exception Types
The client raises several types of exceptions:
ValueError
Raised for configuration and validation errors:
from youversion.clients import AsyncClient
try:
# Missing credentials
client = AsyncClient() # No credentials provided
except ValueError as e:
print(f"Configuration error: {e}")
httpx.HTTPStatusError
Raised for HTTP errors from the API:
import httpx
from youversion.clients import AsyncClient
async def handle_http_errors():
try:
async with AsyncClient() as client:
result = await client.verse_of_the_day()
except httpx.HTTPStatusError as e:
if e.response.status_code == 401:
print("Authentication failed")
elif e.response.status_code == 404:
print("Resource not found")
elif e.response.status_code == 429:
print("Rate limit exceeded")
else:
print(f"HTTP error: {e.response.status_code}")
httpx.RequestError
Raised for network and connection errors:
import httpx
from youversion.clients import AsyncClient
async def handle_network_errors():
try:
async with AsyncClient() as client:
result = await client.verse_of_the_day()
except httpx.RequestError as e:
print(f"Network error: {e}")
print("Check your internet connection")
Comprehensive Error Handling
Example with all error types:
import asyncio
import httpx
from youversion.clients import AsyncClient
async def robust_api_call():
try:
async with AsyncClient() as client:
votd = await client.verse_of_the_day()
return votd
except ValueError as e:
# Configuration errors
print(f"❌ Configuration error: {e}")
print("Please check your credentials")
return None
except httpx.HTTPStatusError as e:
# HTTP errors
status = e.response.status_code
if status == 401:
print("❌ Authentication failed")
print("Please check your username and password")
elif status == 403:
print("❌ Access forbidden")
print("Your account may not have permission")
elif status == 404:
print("❌ Resource not found")
elif status == 429:
print("❌ Rate limit exceeded")
print("Please wait before making more requests")
else:
print(f"❌ HTTP error {status}: {e}")
return None
except httpx.RequestError as e:
# Network errors
print(f"❌ Network error: {e}")
print("Please check your internet connection")
return None
except Exception as e:
# Unexpected errors
print(f"❌ Unexpected error: {e}")
return None
result = asyncio.run(robust_api_call())
if result:
print(f"✅ Success: {result.usfm}")
Retry Logic
Implement retry logic for transient errors:
import asyncio
import httpx
from youversion.clients import AsyncClient
async def retry_api_call(max_retries=3, delay=1):
for attempt in range(max_retries):
try:
async with AsyncClient() as client:
return await client.verse_of_the_day()
except httpx.RequestError as e:
if attempt < max_retries - 1:
print(f"Retry {attempt + 1}/{max_retries}...")
await asyncio.sleep(delay)
continue
raise
except httpx.HTTPStatusError as e:
# Don't retry on 4xx errors (except 429)
if e.response.status_code == 429:
if attempt < max_retries - 1:
wait_time = delay * (attempt + 1)
print(f"Rate limited. Waiting {wait_time}s...")
await asyncio.sleep(wait_time)
continue
raise
result = asyncio.run(retry_api_call())
Rate Limiting
Handle rate limiting gracefully:
import asyncio
import httpx
from youversion.clients import AsyncClient
async def rate_limited_call():
try:
async with AsyncClient() as client:
return await client.verse_of_the_day()
except httpx.HTTPStatusError as e:
if e.response.status_code == 429:
# Extract retry-after header if available
retry_after = e.response.headers.get("Retry-After", "60")
print(f"Rate limited. Retry after {retry_after} seconds")
await asyncio.sleep(int(retry_after))
# Retry the call
async with AsyncClient() as client:
return await client.verse_of_the_day()
raise
result = asyncio.run(rate_limited_call())
Error Logging
Log errors for debugging:
import logging
import httpx
from youversion.clients import AsyncClient
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def logged_api_call():
try:
async with AsyncClient() as client:
votd = await client.verse_of_the_day()
logger.info(f"Successfully retrieved VOTD: {votd.usfm}")
return votd
except ValueError as e:
logger.error(f"Configuration error: {e}", exc_info=True)
return None
except httpx.HTTPStatusError as e:
logger.error(
f"HTTP error {e.response.status_code}: {e}",
exc_info=True
)
return None
except Exception as e:
logger.exception(f"Unexpected error: {e}")
return None
import asyncio
result = asyncio.run(logged_api_call())
Custom Error Classes
Create custom error handlers:
class YouVersionError(Exception):
"""Base exception for YouVersion client errors."""
pass
class AuthenticationError(YouVersionError):
"""Raised when authentication fails."""
pass
class APIError(YouVersionError):
"""Raised when API returns an error."""
def __init__(self, status_code, message):
self.status_code = status_code
self.message = message
super().__init__(f"API error {status_code}: {message}")
async def custom_error_handler():
try:
async with AsyncClient() as client:
return await client.verse_of_the_day()
except ValueError as e:
raise AuthenticationError(f"Authentication failed: {e}")
except httpx.HTTPStatusError as e:
raise APIError(e.response.status_code, str(e))
import asyncio
try:
result = asyncio.run(custom_error_handler())
except AuthenticationError as e:
print(f"Auth error: {e}")
except APIError as e:
print(f"API error: {e.status_code} - {e.message}")
Error Recovery Strategies
Graceful Degradation
Continue operation even if some calls fail:
import asyncio
from youversion.clients import AsyncClient
async def graceful_degradation():
results = {}
async with AsyncClient() as client:
# Try to get VOTD
try:
results['votd'] = await client.verse_of_the_day()
except Exception as e:
results['votd'] = None
print(f"Could not get VOTD: {e}")
# Try to get highlights
try:
results['highlights'] = await client.highlights()
except Exception as e:
results['highlights'] = []
print(f"Could not get highlights: {e}")
# Try to get notes
try:
results['notes'] = await client.notes()
except Exception as e:
results['notes'] = []
print(f"Could not get notes: {e}")
return results
results = asyncio.run(graceful_degradation())
print(f"Successfully retrieved: {[k for k, v in results.items() if v]}")
Fallback Values
Provide fallback values when API calls fail:
import asyncio
from youversion.clients import AsyncClient
async def with_fallback():
async with AsyncClient() as client:
try:
votd = await client.verse_of_the_day()
except Exception:
# Fallback to default
votd = type('Votd', (), {
'day': 1,
'usfm': ['JHN.3.16'],
'image_id': None
})()
return votd
result = asyncio.run(with_fallback())
Best Practices
Always use try-except blocks around API calls
Handle specific exceptions rather than catching all
Provide meaningful error messages to users
Log errors for debugging
Implement retry logic for transient errors
Respect rate limits and implement backoff
Use context managers for automatic cleanup
Validate input before making API calls