Skip to content

Errors

Every exception this library raises descends from PysepalError, so a single except PysepalError catches them all.

pysepal_api.errors

Error types raised by pysepal-api.

Every exception the library raises derives from a single root, PysepalError, so a caller can except PysepalError to catch anything this library throws. Beneath that root:

  • ApiError (and its status-specific subclasses) for non-2xx HTTP responses from a SEPAL service.
  • TransportError / NoCredentialsError / MissingHostError for non-HTTP problems (network, missing config).
  • TaskError (TaskFailed / TaskCanceled / TaskTimeout) for the terminal outcomes of tasks.wait.

PysepalError

Root of every exception raised by pysepal-api.

ApiError

ApiError(status_code: int, *, url: str, body: Any = None)

Base for non-2xx responses from a SEPAL service.

Source code in src/pysepal_api/errors.py
30
31
32
33
34
def __init__(self, status_code: int, *, url: str, body: Any = None) -> None:
    self.status_code = status_code
    self.url = url
    self.body = body
    super().__init__(f"SEPAL API {status_code} for {url}")

status_code instance-attribute

status_code = status_code

url instance-attribute

url = url

body instance-attribute

body = body

BadRequest

BadRequest(status_code: int, *, url: str, body: Any = None)
Source code in src/pysepal_api/errors.py
30
31
32
33
34
def __init__(self, status_code: int, *, url: str, body: Any = None) -> None:
    self.status_code = status_code
    self.url = url
    self.body = body
    super().__init__(f"SEPAL API {status_code} for {url}")

Unauthorized

Unauthorized(
    status_code: int, *, url: str, body: Any = None
)
Source code in src/pysepal_api/errors.py
30
31
32
33
34
def __init__(self, status_code: int, *, url: str, body: Any = None) -> None:
    self.status_code = status_code
    self.url = url
    self.body = body
    super().__init__(f"SEPAL API {status_code} for {url}")

Forbidden

Forbidden(status_code: int, *, url: str, body: Any = None)
Source code in src/pysepal_api/errors.py
30
31
32
33
34
def __init__(self, status_code: int, *, url: str, body: Any = None) -> None:
    self.status_code = status_code
    self.url = url
    self.body = body
    super().__init__(f"SEPAL API {status_code} for {url}")

NotFound

NotFound(status_code: int, *, url: str, body: Any = None)
Source code in src/pysepal_api/errors.py
30
31
32
33
34
def __init__(self, status_code: int, *, url: str, body: Any = None) -> None:
    self.status_code = status_code
    self.url = url
    self.body = body
    super().__init__(f"SEPAL API {status_code} for {url}")

Conflict

Conflict(status_code: int, *, url: str, body: Any = None)
Source code in src/pysepal_api/errors.py
30
31
32
33
34
def __init__(self, status_code: int, *, url: str, body: Any = None) -> None:
    self.status_code = status_code
    self.url = url
    self.body = body
    super().__init__(f"SEPAL API {status_code} for {url}")

TooManyRequests

TooManyRequests(
    status_code: int, *, url: str, body: Any = None
)

429 — rate limited. Distinct so callers can back off programmatically.

Source code in src/pysepal_api/errors.py
30
31
32
33
34
def __init__(self, status_code: int, *, url: str, body: Any = None) -> None:
    self.status_code = status_code
    self.url = url
    self.body = body
    super().__init__(f"SEPAL API {status_code} for {url}")

ServerError

ServerError(
    status_code: int, *, url: str, body: Any = None
)
Source code in src/pysepal_api/errors.py
30
31
32
33
34
def __init__(self, status_code: int, *, url: str, body: Any = None) -> None:
    self.status_code = status_code
    self.url = url
    self.body = body
    super().__init__(f"SEPAL API {status_code} for {url}")

TransportError

Network/send failure: DNS, connection, timeout, etc.

ResponseError

A SEPAL response could not be parsed or did not match the expected shape.

Wraps malformed JSON (json.JSONDecodeError) and pydantic ValidationError from response parsing; the underlying cause is available via __cause__.

NoCredentialsError

No usable auth could be detected.

MissingHostError

No SEPAL host could be detected.

InvalidPathError

A user-files path is outside the sandbox home or contains .. traversal.

Subclasses ValueError too, so existing except ValueError handlers keep working while the error is also catchable via the PysepalError root.

TaskError

TaskError(message: str, *, task: Task | None = None)

Base for a non-success terminal outcome of tasks.wait.

Carries the last-observed Task as task so callers can inspect status_description / task_info without re-fetching.

Source code in src/pysepal_api/errors.py
100
101
102
def __init__(self, message: str, *, task: Task | None = None) -> None:
    self.task = task
    super().__init__(message)

task instance-attribute

task = task

TaskFailed

TaskFailed(message: str, *, task: Task | None = None)

Raised by tasks.wait when a task reaches FAILED.

Source code in src/pysepal_api/errors.py
100
101
102
def __init__(self, message: str, *, task: Task | None = None) -> None:
    self.task = task
    super().__init__(message)

TaskCanceled

TaskCanceled(message: str, *, task: Task | None = None)

Raised by tasks.wait when a task reaches CANCELED.

Source code in src/pysepal_api/errors.py
100
101
102
def __init__(self, message: str, *, task: Task | None = None) -> None:
    self.task = task
    super().__init__(message)

TaskTimeout

TaskTimeout(message: str, *, task: Task | None = None)

Raised by tasks.wait when a task does not reach a terminal state in time.

Subclasses the builtin TimeoutError too, so except TimeoutError keeps working alongside except TaskError / except PysepalError.

Source code in src/pysepal_api/errors.py
100
101
102
def __init__(self, message: str, *, task: Task | None = None) -> None:
    self.task = task
    super().__init__(message)

error_for_status

error_for_status(
    status_code: int, *, url: str, body: Any
) -> ApiError

Map an HTTP status code to the most specific ApiError.

Source code in src/pysepal_api/errors.py
131
132
133
134
135
136
137
138
def error_for_status(status_code: int, *, url: str, body: Any) -> ApiError:
    """Map an HTTP status code to the most specific ApiError."""
    cls = _BY_STATUS.get(status_code)
    if cls is None and 500 <= status_code < 600:
        cls = ServerError
    if cls is None:
        cls = ApiError
    return cls(status_code, url=url, body=body)