Source code for ionworks.errors
"""
Custom exception classes for the Ionworks API client.
This module defines :class:`IonworksError`, which is raised when API requests
fail or return error responses.
"""
from typing import Any
[docs]
class IonworksError(Exception):
"""Custom exception for Ionworks API errors.
Attributes
----------
message : str
A string description of the error.
data : dict[str, Any] | None
Structured error data if available (e.g., from API error response).
status_code : int | None
HTTP status code if applicable.
error_code : str | None
Machine-readable error code from the server (e.g., ``"NOT_FOUND"``).
"""
[docs]
def __init__(
self,
message: str | dict[str, Any],
status_code: int | None = None,
) -> None:
"""Initialize the IonworksError.
Parameters
----------
message : str | dict[str, Any]
Error message string or dict containing error details.
Supports both the legacy ``{"detail": ...}`` format and the new
standardized ``{"error_code": ..., "message": ..., "detail": ...}``
format.
status_code : int | None
Optional HTTP status code.
"""
self.status_code = status_code
self.error_code: str | None = None
# Parse message into string, optional data dict, and error_code
if isinstance(message, dict):
self.error_code = message.get("error_code")
self.message = message.get("message", str(message))
self.data: dict[str, Any] | None = message
else:
self.message = message
self.data = None
super().__init__(self.message)
def __str__(self) -> str:
"""Return string representation of the error."""
return f"error code: {self.status_code}, message: {self.message}"
[docs]
class MeasurementProcessingError(IonworksError):
"""Raised when a measurement upload could not be processed by the server.
Uploading a ``time_series`` measurement is a two-part operation: the
request that creates the record returns as soon as the file is stored, and
the steps are derived afterwards on the server. A file the server cannot
read — a missing or unreadable Step column, say — is therefore rejected
*after* the create call has already returned successfully.
The upload methods wait for that second part and raise this rather than
returning a measurement that looks created but holds no usable data.
A batch wait is not fail-fast, so one of these can report several
measurements at once — ``failures`` holds every one of them.
Attributes
----------
failures : dict[str, str]
Reason keyed by the id of each measurement whose processing failed.
The records exist and can be inspected or deleted; they simply have
no steps.
"""
[docs]
def __init__(self, message: str, failures: dict[str, str] | None = None) -> None:
"""Initialize the error.
Parameters
----------
message : str
Why processing failed. For a batch, a summary naming each failure.
failures : dict[str, str], optional
Reason keyed by failed measurement id.
"""
super().__init__(message)
self.failures = failures or {}
@property
def measurement_id(self) -> str | None:
"""The first — and, for a single upload, only — failed measurement id.
None if no failure was recorded. Read ``failures`` when waiting on a
batch, where this reports only one of several.
"""
return next(iter(self.failures), None)
def __str__(self) -> str:
"""Return string representation of the error."""
if len(self.failures) == 1:
return f"measurement {self.measurement_id}: {self.message}"
return self.message