EXAMPLE_ERROR_HANDLING.py•5.88 kB
"""
Example demonstrating the Hybrid Error Handling Pattern (Option A)
This shows how the pattern works in practice.
"""
# ===== Example 1: Technical Error (raises Exception) =====
def get_instance_example_technical_error(compute_client, instance_id):
    """
    Example: Getting an instance that doesn't exist
    Result: ServiceError 404 is raised and propagates
    """
    # This will raise oci.exceptions.ServiceError if not found
    instance = compute_client.get_instance(instance_id).data
    return {
        "id": instance.id,
        "name": instance.display_name,
        "state": instance.lifecycle_state
    }
    # No try/except - let exception propagate to decorator
# ===== Example 2: Business State (returns dict with success) =====
def start_instance_example_business_state(compute_client, instance_id):
    """
    Example: Starting an instance that's already running
    Result: Returns {"success": True, "already_running": True, ...}
    """
    # Get instance (may raise ServiceError if doesn't exist - technical error)
    instance = compute_client.get_instance(instance_id).data
    # Business state: already running
    if instance.lifecycle_state == "RUNNING":
        return {
            "success": True,
            "already_running": True,
            "message": "Instance is already running",
            "current_state": "RUNNING",
            "instance_id": instance_id
        }
    # Business state: cannot start from this state
    if instance.lifecycle_state not in ["STOPPED"]:
        return {
            "success": False,
            "message": f"Cannot start instance from state {instance.lifecycle_state}",
            "current_state": instance.lifecycle_state,
            "instance_id": instance_id
        }
    # Perform operation (may raise ServiceError - technical error)
    compute_client.instance_action(instance_id, "START")
    return {
        "success": True,
        "message": "Instance is starting",
        "current_state": "STARTING",
        "instance_id": instance_id
    }
# ===== Example 3: Simple Data Query (returns data directly) =====
def list_instances_example_data_query(compute_client, compartment_id):
    """
    Example: List all instances in a compartment
    Result: Returns list of dicts (or raises ServiceError if auth fails)
    """
    # This will raise ServiceError if auth fails or compartment doesn't exist
    instances_response = compute_client.list_instances(compartment_id).data
    return [
        {
            "id": instance.id,
            "name": instance.display_name,
            "state": instance.lifecycle_state
        }
        for instance in instances_response
    ]
    # No try/except - let exceptions propagate
# ===== How the Decorator Handles Each Case =====
"""
@mcp_tool_wrapper decorator behavior:
CASE 1: Technical Error (Exception raised)
    Input:  get_instance_example_technical_error(client, "invalid-id")
    Raises: oci.exceptions.ServiceError: 404 Not Found
    Decorator catches and returns: {"error": "Error getting instance: 404 Not Found"}
    User sees: {"error": "..."}
CASE 2: Business State Success (dict with success=True)
    Input:  start_instance_example_business_state(client, "running-instance")
    Returns: {"success": True, "already_running": True, "message": "..."}
    Decorator logs: "Instance is already running"
    User sees: {"success": True, "already_running": True, ...}
CASE 3: Business State Failure (dict with success=False)
    Input:  start_instance_example_business_state(client, "provisioning-instance")
    Returns: {"success": False, "message": "Cannot start from PROVISIONING"}
    Decorator logs: "Business state: Cannot start from PROVISIONING"
    User sees: {"success": False, "message": "..."}
CASE 4: Normal Data Query (returns data)
    Input:  list_instances_example_data_query(client, "compartment-id")
    Returns: [{"id": "...", "name": "...", "state": "..."}]
    Decorator logs success message (if provided)
    User sees: [{"id": "...", ...}]
"""
# ===== Client-Side Usage =====
def client_usage_example(mcp_client):
    """
    How a client would use the MCP tools
    """
    # Query data
    instances = mcp_client.call_tool("list_instances", {"compartment_id": "ocid1..."})
    if "error" in instances:
        print(f"Technical error: {instances['error']}")
    else:
        print(f"Found {len(instances)} instances")
    # Perform operation with business states
    result = mcp_client.call_tool("start_instance", {"instance_id": "ocid1..."})
    if "error" in result:
        # Technical error (network, permissions, etc.)
        print(f"Technical error: {result['error']}")
    elif result.get("success"):
        # Operation succeeded
        if result.get("already_running"):
            print("Instance was already running")
        else:
            print(f"Started instance: {result['message']}")
    else:
        # Business state prevents operation
        print(f"Cannot perform operation: {result['message']}")
# ===== Summary =====
"""
Pattern Decision Matrix:
Situation                           → Pattern to Use
=====================================================================================================
API call fails (404, 401, timeout)  → raise Exception (technical error)
Resource doesn't exist              → raise Exception (technical error)
Network error                       → raise Exception (technical error)
Invalid parameters                  → raise ValueError (technical error)
Resource already in target state    → return {"success": True, "already_done": True}
Resource in wrong state for op      → return {"success": False, "message": "why"}
Operation initiated successfully    → return {"success": True, "message": "initiated"}
Simple data query                   → return data directly (list or dict)
"""