Hone logo
Hone
Problems

Robust Service Wrapper in Python

This challenge focuses on creating a reusable and robust wrapper around a hypothetical external service. Service wrappers are crucial for abstracting away the complexities of interacting with external APIs, handling errors gracefully, and providing a consistent interface for your application. Your task is to design and implement a Python class that encapsulates the interaction with this service, including retry logic and error handling.

Problem Description

You are tasked with building a ServiceWrapper class in Python. This class will interact with a simulated external service (represented by a function external_service – provided below). The external_service function may occasionally fail (simulate a 50% chance of failure). The ServiceWrapper should handle these failures with a retry mechanism and provide a clean interface for calling the underlying service.

The ServiceWrapper class should have the following methods:

  • __init__(self, max_retries=3, initial_delay=1): Initializes the wrapper with a maximum number of retries and an initial delay (in seconds) for exponential backoff.
  • call_service(self, *args, **kwargs): This is the primary method. It calls the external_service function with the provided arguments. It should implement retry logic with exponential backoff. If the service call fails after the maximum number of retries, it should raise an exception.

The retry logic should work as follows:

  1. On initial failure, wait for initial_delay seconds.
  2. On subsequent failures, double the delay before retrying.
  3. The maximum number of retries is controlled by the max_retries parameter.

Examples

Example 1:

Input: wrapper = ServiceWrapper(max_retries=2, initial_delay=0.5); wrapper.call_service("request1")
Output: "Service call successful: request1"
Explanation: The external service might fail initially, but the wrapper retries and eventually succeeds.

Example 2:

Input: wrapper = ServiceWrapper(max_retries=1, initial_delay=1); wrapper.call_service("request2")
Output: Raises ServiceCallFailed exception after 2 seconds (1 + 2).
Explanation: The external service fails, the wrapper retries once after a 1-second delay, and then fails again, raising the exception.

Example 3: (Edge Case - No Retries)

Input: wrapper = ServiceWrapper(max_retries=0, initial_delay=1); wrapper.call_service("request3")
Output: Raises ServiceCallFailed exception immediately.
Explanation: No retries are configured, so the first failure immediately raises the exception.

Constraints

  • max_retries must be a non-negative integer.
  • initial_delay must be a positive float.
  • The external_service function is provided and should not be modified.
  • The ServiceWrapper class should handle exceptions raised by external_service gracefully.
  • The exponential backoff delay should be calculated correctly.
  • The ServiceCallFailed exception should be raised when the maximum number of retries is reached.

Notes

  • Consider using time.sleep() for implementing the delays.
  • You can use a custom exception class ServiceCallFailed to indicate failure after retries.
  • Think about how to handle different types of exceptions raised by the external_service. For simplicity, assume all exceptions are considered failures.
  • The external_service function is provided below for testing purposes. It simulates a service that fails 50% of the time.
import random
import time

def external_service(*args, **kwargs):
    """
    Simulates an external service that fails 50% of the time.
    """
    if random.random() < 0.5:
        raise Exception("Service unavailable")
    return f"Service call successful: {args[0] if args else kwargs.get('data', 'no data')}"

class ServiceCallFailed(Exception):
    pass
Loading editor...
python