Sobes.tech
Middle+

How to implement the Waiter pattern for an API?

sobes.tech AI

Answer from AI

The Waiter pattern for API is implemented by periodically polling a resource until a specific target state is reached or a timeout occurs.

Main components of the implementation:

  1. Waiting goal: Define the condition under which the wait is considered complete (e.g., resource status becomes "ready", a field value reaches a certain value).
  2. Polling interval: The time between consecutive API requests.
  3. Timeout: The maximum time during which the Waiter will perform polls.
  4. Polling logic: A function or method that performs a GET request to the API to get the current state of the resource.
  5. State check: Logic that analyzes the API response and checks if the target state has been reached.
  6. Waiting mechanism: An implementation of a loop that performs polls at a set interval, checks the state, and terminates upon reaching the goal, timeout, or error.

Example implementation in Python:

import time
import requests
from typing import Dict, Any, Optional, Callable

def wait_until(
    url: str,
    api_key: str,
    condition: Callable[[Dict[str, Any]], bool],
    timeout: int = 60,
    polling_interval: int = 5
) -> Dict[str, Any]:
    """
    Waits until the resource at the given URL satisfies the condition.

    Args:
        url: API resource URL.
        api_key: API key for authentication.
        condition: Function that takes the response (dict) and returns True if the condition is met.
        timeout: Maximum wait time in seconds.
        polling_interval: Interval between requests in seconds.

    Returns:
        The last response received from the API when the condition was met.

    Raises:
        TimeoutError: If the condition is not met within the specified timeout.
        requests.exceptions.RequestException: If an HTTP request error occurs.
    """
    start_time = time.time()
    headers = {"X-API-Key": api_key} # Example header for API key

    while time.time() - start_time < timeout:
        try:
            response = requests.get(url, headers=headers)
            response.raise_for_status() # Raises exception for bad statuses (4xx or 5xx)
            data = response.json()

            if condition(data):
                return data # Condition met

        except requests.exceptions.RequestException as e:
            print(f"Request error: {e}")
            # Optional: add retry logic or exit

        time.sleep(polling_interval) # Wait before next poll

    raise TimeoutError(f"Waiting for condition on {url} timed out ({timeout} seconds)")

# Usage example:
# Assume the API returns a processing status in the 'status' field
# and we wait until the status becomes 'COMPLETED'
# resource_url = "https://api.example.com/processing_job/123"
# my_api_key = "YOUR_API_KEY"

# def is_completed(data: Dict[str, Any]) -> bool:
#     return data.get("status") == "COMPLETED"

# try:
#     completed_resource_data = wait_until(
#         resource_url,
#         my_api_key,
#         is_completed,
#         timeout=120,
#         polling_interval=10
#     )
#     print("Resource is ready:", completed_resource_data)
# except (TimeoutError, requests.exceptions.RequestException) as e:
#     print("Failed to wait for resource readiness:", e)

Implementation features:

  • Error handling: Handle HTTP errors (bad statuses, network issues).
  • Exponential backoff: Increase the polling interval after each failed attempt to reduce load on the API.
  • Cancellation: Provide a way to interrupt the wait.
  • Logging: Add logging for progress tracking and issues.
  • Parameterization: Make the Waiter flexible, allowing configuration of URL, request method (GET, POST), headers, parameters, and wait conditions.