Source code for klea_utils.api.utils
#!/usr/bin/env python3
"""
Utility functions for the Klea API layer.
File: klea_utils/api/utils.py
Copyright 2026 Ankur Sinha
Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
"""
import httpx
from pydantic import AnyUrl
from pydantic import ValidationError as PydanticValidationError
from tenacity import (
AsyncRetrying,
retry_if_exception_type,
stop_after_attempt,
stop_after_delay,
wait_random_exponential,
)
[docs]
def validate_url(value: str) -> str:
"""Return *value* if it is a valid HTTP(S) URL, else raise ``ValueError``."""
try:
AnyUrl(value)
except PydanticValidationError:
raise ValueError(f"'{value}' is not a valid HTTP(S) URL")
return value
def _make_retryer(attempts: int | None = None, timeout: float = 180.0) -> AsyncRetrying:
"""Create an ``AsyncRetrying`` that retries transient API call errors.
:param attempts: If set, bound the number of probe attempts. Takes
precedence over *timeout* (useful for fast single-shot probes).
:param timeout: Total wall-clock seconds to keep probing when
*attempts* is unset. Generous by default so a server that is slow
to initialize (MCP servers, embedding model downloads) is not given
up on prematurely.
:returns: A configured :class:`tenacity.AsyncRetrying` instance
"""
if attempts is not None:
stop = stop_after_attempt(attempts)
else:
stop = stop_after_delay(timeout)
return AsyncRetrying(
wait=wait_random_exponential(multiplier=1, max=10),
stop=stop,
retry=retry_if_exception_type(
(
httpx.ConnectError,
httpx.HTTPStatusError,
httpx.ReadError,
httpx.ReadTimeout,
)
),
reraise=True,
)
async def _get_ready(url: str) -> dict:
"""GET the health endpoint and return its JSON, raising on non-2xx."""
async with httpx.AsyncClient() as client:
response = await client.get(url)
response.raise_for_status()
return response.json()
[docs]
async def check_api_is_ready(
url: str, attempts: int | None = None, timeout: float = 180.0
):
"""Exponentially back off checking that the API is ready.
:param url: Health check endpoint URL
:param attempts: If set, maximum number of probe attempts (overrides
*timeout*)
:param timeout: Total wall-clock seconds to keep probing when
*attempts* is unset
"""
retryer = _make_retryer(attempts, timeout)
return await retryer(_get_ready, url)