MorphGuard / src /README_API_UTILS.md
juanquy's picture
Initial clean commit of modular MorphGuard
2978bba
|
Raw
History Blame Contribute Delete
7.64 kB

MorphGuard API Utilities

The api_utils.py module provides a sophisticated and resilient API client system with intelligent fallbacks for the MorphGuard application. This system is designed to handle network instability, server errors, and other common issues that can affect API communication.

Key Features

  • Smart Request Queuing: Prioritize critical requests to ensure they are processed first
  • Comprehensive Error Handling: Detailed error diagnostics and classification
  • Intelligent Caching: Stale-while-revalidate pattern for improved performance and resilience
  • Circuit Breaker Pattern: Prevent cascading failures by failing fast when services are down
  • Extended Metrics and Monitoring: Track performance and reliability metrics
  • Context-Aware Retry Strategies: Intelligent backoff and jitter for optimal retry behavior

Core Components

1. CircuitBreaker

Implements the circuit breaker pattern with three states:

  • CLOSED: Normal operation, requests are sent normally
  • OPEN: Circuit is tripped, all requests fail fast
  • HALF-OPEN: Testing if the service has recovered
# Example usage
cb = CircuitBreaker(failure_threshold=5, recovery_timeout=30.0)

if cb.allow_request():
    try:
        # Make request
        result = make_api_call()
        cb.record_success()
        return result
    except Exception as e:
        cb.record_failure()
        raise
else:
    # Circuit is open, use fallback or fail fast
    return fallback_data

2. StaleWhileRevalidateCache

Enhanced cache with stale-while-revalidate strategy:

  • Returns stale data immediately while asynchronously fetching fresh data
  • Reduces perceived latency and improves availability
  • Supports custom TTL for different cache entries
# Example usage
cache = StaleWhileRevalidateCache(cache_dir=".cache", stale_timeout=300.0)

# Define refresh callback
def refresh_data():
    return fetch_fresh_data_from_api()

# Get data from cache, refresh in background if stale
data, is_fresh = cache.get("cache_key", refresh_callback=refresh_data)

# Use data immediately, even if stale
process_data(data)

3. APIUtils

The main API client with comprehensive resilience features:

  • Configurable timeout, retries, and caching
  • Circuit breaker pattern integration
  • Priority-based request handling
  • Detailed metrics and telemetry
# Example usage
api = get_api_utils({
    "base_url": "https://api.example.com",
    "timeout": 30.0,
    "retries": 3,
    "cache_enabled": True
})

# Make a GET request with fallback
try:
    result = api.get("/users", {"limit": 10}, {
        "fallback": {"users": []},
        "priority": "high"
    })
    process_users(result)
except Exception as e:
    handle_error(e)

4. Decorator Functions

Utility decorators for applying resilience patterns to functions:

@retry

Automatically retry functions on failure:

@retry(retries=3, retry_delay=1.0, fallback=default_value)
def fetch_user_data(user_id):
    # This function will be retried up to 3 times if it fails
    return api_call(f"/users/{user_id}")

@circuit_breaker

Apply circuit breaker pattern to functions:

@circuit_breaker(failure_threshold=5, recovery_timeout=30.0, fallback=empty_list)
def search_products(query):
    # This function will fail fast if it fails 5 times in a row
    return api_call("/search", {"q": query})

@cacheable

Cache function results with TTL:

@cacheable(ttl=60.0)  # Cache for 1 minute
def get_product_categories():
    # Results will be cached for 60 seconds
    return api_call("/categories")

Usage Example

Here's a complete example of using the API utilities:

from src.api_utils import get_api_utils, retry, circuit_breaker, cacheable

# Configure API utilities
api = get_api_utils({
    "base_url": "https://api.example.com",
    "timeout": 30.0,
    "retries": 3,
    "cache_enabled": True
})

# Make a simple GET request
users = api.get("/users")

# POST request with error handling
try:
    new_user = api.post("/users", {
        "name": "John Doe",
        "email": "john@example.com"
    }, {
        "critical": True,  # High priority
        "timeout": 60.0    # Longer timeout
    })
    print(f"Created user: {new_user}")
except Exception as e:
    print(f"Failed to create user: {e}")

# Function with retry decorator
@retry(retries=3, retry_delay=1.0)
def fetch_user_data(user_id):
    return api.get(f"/users/{user_id}")

# Function with circuit breaker
@circuit_breaker(failure_threshold=5, recovery_timeout=30.0)
def search_products(query):
    return api.get("/search", {"q": query})

# Function with caching
@cacheable(ttl=60.0)
def get_product_categories():
    return api.get("/categories")

# Get API metrics
metrics = api.get_metrics()
print(f"API metrics: {metrics}")

Configuration Options

The API utilities can be configured with the following options:

Option Description Default
base_url Base URL for the API "http://localhost:5000/api"
timeout Request timeout in seconds 30.0
retries Number of retries for failed requests 3
retry_delay Base delay between retries in seconds 1.0
retry_status_codes HTTP status codes to retry [408, 429, 500, 502, 503, 504]
cache_enabled Whether to enable caching True
cache_dir Directory to store cache files ".mg_api_cache"
max_cache_entries Maximum number of in-memory cache entries 100
stale_timeout Time in seconds before data is considered stale 300.0
revalidation_timeout Maximum age for using stale data 600.0
circuit_breaker_enabled Whether to enable circuit breaker True
failure_threshold Number of failures before circuit opens 5
recovery_timeout Time in seconds before circuit half-opens 30.0
priority_queue_enabled Whether to enable priority-based request queue True
max_queue_size Maximum size of request queue 100
debug Whether to enable debug logging False

Error Handling

The API utilities integrate with MorphGuard's error handling system to provide detailed error information:

try:
    result = api.get("/users/123")
except APIError as e:
    if e.code == ErrorCode.NOT_FOUND:
        print("User not found")
    elif e.code == ErrorCode.UNAUTHORIZED:
        print("Authentication required")
    else:
        print(f"API error: {e.message}")
except NetworkError as e:
    print(f"Network error: {e.message}")
except Exception as e:
    print(f"Unexpected error: {e}")

Metrics and Monitoring

The API utilities track various metrics for monitoring:

metrics = api.get_metrics()
print(f"Total requests: {metrics['total_requests']}")
print(f"Successful requests: {metrics['successful_requests']}")
print(f"Failed requests: {metrics['failed_requests']}")
print(f"Cached responses: {metrics['cached_responses']}")
print(f"Stale responses: {metrics['stale_responses']}")
print(f"Retries: {metrics['retries']}")
print(f"Circuit breaks: {metrics['circuit_breaks']}")
print(f"Average response time: {metrics['total_time'] / max(1, metrics['total_requests']):.2f}s")

Testing

The API utilities include comprehensive unit tests to ensure reliability:

# Run all tests
python -m unittest tests.test_api_utils

# Run specific test class
python -m unittest tests.test_api_utils.TestCircuitBreaker

# Run specific test method
python -m unittest tests.test_api_utils.TestAPIUtils.test_circuit_breaker