Skip to content

statuspro_public_api_client.utils

statuspro_public_api_client.utils

Utility functions for working with StatusPro API responses.

Helpers for unwrapping generated responses, extracting data, and raising typed errors on API failures.

Classes

APIError(message, status_code, error_response=None)

Bases: Exception

Base exception for API errors.

Source code in statuspro_public_api_client/utils.py
def __init__(
    self,
    message: str,
    status_code: int,
    error_response: ErrorResponse | ValidationErrorResponse | None = None,
):
    super().__init__(message)
    self.status_code = status_code
    self.error_response = error_response

AuthenticationError(message, status_code, error_response=None)

Bases: APIError

Raised when authentication fails (401).

Source code in statuspro_public_api_client/utils.py
def __init__(
    self,
    message: str,
    status_code: int,
    error_response: ErrorResponse | ValidationErrorResponse | None = None,
):
    super().__init__(message)
    self.status_code = status_code
    self.error_response = error_response

RateLimitError(message, status_code, error_response=None)

Bases: APIError

Raised when rate limit is exceeded (429).

Source code in statuspro_public_api_client/utils.py
def __init__(
    self,
    message: str,
    status_code: int,
    error_response: ErrorResponse | ValidationErrorResponse | None = None,
):
    super().__init__(message)
    self.status_code = status_code
    self.error_response = error_response

ServerError(message, status_code, error_response=None)

Bases: APIError

Raised when a server error occurs (5xx).

Source code in statuspro_public_api_client/utils.py
def __init__(
    self,
    message: str,
    status_code: int,
    error_response: ErrorResponse | ValidationErrorResponse | None = None,
):
    super().__init__(message)
    self.status_code = status_code
    self.error_response = error_response

ValidationError(message, status_code, error_response=None)

Bases: APIError

Raised when request validation fails (422).

The validation_errors attribute is a dict[str, list[str]] mapping field name to the list of human-readable error messages that StatusPro returned for that field.

Source code in statuspro_public_api_client/utils.py
def __init__(
    self,
    message: str,
    status_code: int,
    error_response: ValidationErrorResponse | None = None,
):
    super().__init__(message, status_code, error_response)
    errors = getattr(error_response, "errors", None)
    self.validation_errors: dict[str, list[str]] = unwrap_unset(errors, {}) or {}

Functions

get_error_message(response)

Extract message from an error response, if present.

Source code in statuspro_public_api_client/utils.py
def get_error_message[T](response: Response[T]) -> str | None:
    """Extract ``message`` from an error response, if present."""
    if response.parsed is None:
        return None

    parsed = response.parsed
    if not isinstance(parsed, ErrorResponse | ValidationErrorResponse):
        return None

    message_raw = getattr(parsed, "message", None)
    return message_raw if isinstance(message_raw, str) and message_raw else None

handle_response(response, *, on_success=None, on_error=None, raise_on_error=False)

Call on_success with parsed data for 2xx, on_error with an APIError otherwise.

Source code in statuspro_public_api_client/utils.py
def handle_response[T](
    response: Response[T],
    *,
    on_success: Callable[[T], Any] | None = None,
    on_error: Callable[[APIError], Any] | None = None,
    raise_on_error: bool = False,
) -> Any:
    """Call on_success with parsed data for 2xx, on_error with an APIError otherwise."""
    try:
        data = unwrap(response, raise_on_error=True)
        if on_success:
            return on_success(data)
        return data
    except APIError as e:
        if raise_on_error:
            raise
        if on_error:
            return on_error(e)
        return None

is_error(response)

True if the response has a 4xx or 5xx status code.

Source code in statuspro_public_api_client/utils.py
def is_error(response: Response[Any]) -> bool:
    """True if the response has a 4xx or 5xx status code."""
    return response.status_code >= 400

is_success(response)

True if the response has a 2xx status code.

Source code in statuspro_public_api_client/utils.py
def is_success(response: Response[Any]) -> bool:
    """True if the response has a 2xx status code."""
    return 200 <= response.status_code < 300

unwrap(response, *, raise_on_error=True)

unwrap(
    response: Response[T], *, raise_on_error: bool = True
) -> T
unwrap(
    response: Response[T], *, raise_on_error: bool = False
) -> T | None

Unwrap a Response and return parsed data, raising typed errors on failure.

Parameters:

  • response (Response[T]) –

    Response object from a generated API call.

  • raise_on_error (bool, default: True ) –

    If True (default), raise an APIError subclass for non-2xx responses; if False, return None.

Returns:

  • T | None

    The parsed response body.

Raises:

Source code in statuspro_public_api_client/utils.py
def unwrap[T](response: Response[T], *, raise_on_error: bool = True) -> T | None:
    """Unwrap a Response and return parsed data, raising typed errors on failure.

    Args:
        response: Response object from a generated API call.
        raise_on_error: If True (default), raise an APIError subclass for non-2xx
            responses; if False, return None.

    Returns:
        The parsed response body.

    Raises:
        AuthenticationError: 401
        ValidationError: 422
        RateLimitError: 429
        ServerError: 5xx
        APIError: other non-2xx statuses
    """
    if response.parsed is None:
        if raise_on_error:
            raise APIError(
                f"No parsed response data for status {response.status_code}",
                response.status_code,
            )
        return None

    parsed = response.parsed
    if isinstance(parsed, ErrorResponse | ValidationErrorResponse):
        if not raise_on_error:
            return None

        message_raw = getattr(parsed, "message", None)
        error_message = (
            message_raw
            if isinstance(message_raw, str) and message_raw
            else "No error message provided"
        )

        status = response.status_code
        if status == HTTPStatus.UNAUTHORIZED:
            raise AuthenticationError(error_message, status, parsed)
        if status == HTTPStatus.UNPROCESSABLE_ENTITY:
            detailed = parsed if isinstance(parsed, ValidationErrorResponse) else None
            raise ValidationError(error_message, status, detailed)
        if status == HTTPStatus.TOO_MANY_REQUESTS:
            raise RateLimitError(error_message, status, parsed)
        if 500 <= status < 600:
            raise ServerError(error_message, status, parsed)
        raise APIError(error_message, status, parsed)

    return response.parsed

unwrap_as(response, expected_type, *, raise_on_error=True)

unwrap_as(
    response: Response[T],
    expected_type: type[ExpectedT],
    *,
    raise_on_error: bool = True
) -> ExpectedT
unwrap_as(
    response: Response[T],
    expected_type: type[ExpectedT],
    *,
    raise_on_error: bool = False
) -> ExpectedT | None

Unwrap and assert the parsed body is of the expected type.

Source code in statuspro_public_api_client/utils.py
def unwrap_as[T, ExpectedT](
    response: Response[T],
    expected_type: type[ExpectedT],
    *,
    raise_on_error: bool = True,
) -> ExpectedT | None:
    """Unwrap and assert the parsed body is of the expected type."""
    result = unwrap(response, raise_on_error=raise_on_error)
    if result is None:
        if raise_on_error:
            raise TypeError(
                f"Expected {expected_type.__name__}, got None from response"
            )
        return None
    if not isinstance(result, expected_type):
        raise TypeError(
            f"Expected {expected_type.__name__}, got {type(result).__name__}"
        )
    return result

unwrap_data(response, *, raise_on_error=True, default=None)

unwrap_data(
    response: Response[T],
    *,
    raise_on_error: bool = True,
    default: None = None
) -> Any
unwrap_data(
    response: Response[T],
    *,
    raise_on_error: bool = False,
    default: None = None
) -> Any | None
unwrap_data(
    response: Response[T],
    *,
    raise_on_error: bool = False,
    default: list[DataT]
) -> Any

Unwrap a list response and extract the data array.

For StatusPro's wrapped list endpoints like GET /orders which return {"data": [...], "meta": {...}}. For raw-array endpoints like GET /statuses, use unwrap() directly — the parsed response is the list itself.

Source code in statuspro_public_api_client/utils.py
def unwrap_data[T, DataT](
    response: Response[T],
    *,
    raise_on_error: bool = True,
    default: list[DataT] | None = None,
) -> Any | None:
    """Unwrap a list response and extract the ``data`` array.

    For StatusPro's wrapped list endpoints like ``GET /orders`` which return
    ``{"data": [...], "meta": {...}}``. For raw-array endpoints like
    ``GET /statuses``, use ``unwrap()`` directly — the parsed response is the
    list itself.
    """
    try:
        parsed = unwrap(response, raise_on_error=raise_on_error)
    except APIError:
        if raise_on_error:
            raise
        return default

    if parsed is None:
        return default

    # If the parsed result is already a list, return it directly
    if isinstance(parsed, list):
        return parsed

    data = getattr(parsed, "data", None)
    if isinstance(data, Unset):
        return default if default is not None else []
    if data is not None:
        return data

    if default is not None:
        return default
    return [parsed]