Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ Contents

* `Asynchronous Usage`_

* `Error Handling`_

* `Validation errors`_

* `Requests without a Workspace in Scope`_

* `Personal Access Token without a Workspace`_
Expand Down Expand Up @@ -486,6 +490,38 @@ and ``flatten`` returns an async generator.
The ``AsyncSeamWithoutWorkspace`` client is the equivalent async variant of
``SeamWithoutWorkspace``.

Error Handling
~~~~~~~~~~~~~~

Requests rejected by the Seam API raise a ``SeamHttpApiError`` subclass
carrying the HTTP ``status_code``, API error ``code``, and ``request_id``.

Validation errors
^^^^^^^^^^^^^^^^^

When the API rejects a request because a parameter is invalid, it raises a
``SeamHttpInvalidInputError``. Look up messages for a parameter you are already
rendering, for example a field in a form:

.. code-block:: python

from seam import SeamHttpInvalidInputError

try:
seam.devices.list(device_ids=["not-a-uuid"])
except SeamHttpInvalidInputError as error:
print(error.get_validation_error_messages("device_ids"))

Or read every parameter that failed validation to summarize the request:

.. code-block:: python

for validation_error in error.validation_errors:
print(
f"{validation_error.parameter_name}: "
f"{', '.join(validation_error.error_messages)}"
)

Requests without a Workspace in Scope
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
1 change: 1 addition & 0 deletions seam/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
SeamHttpApiError,
SeamHttpUnauthorizedError,
SeamHttpInvalidInputError,
SeamValidationError,
SeamActionAttemptError,
SeamActionAttemptFailedError,
SeamActionAttemptTimeoutError,
Expand Down
21 changes: 21 additions & 0 deletions seam/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
from dataclasses import dataclass
from typing import Any, Dict, List, Optional
from .resources import ActionAttempt


@dataclass(frozen=True)
class SeamValidationError:
"""A request parameter that failed validation and its error messages."""

parameter_name: str
error_messages: List[str]


# HTTP
class SeamHttpApiError(Exception):
"""
Expand Down Expand Up @@ -88,6 +97,18 @@ def __init__(
self.code = "invalid_input"
self._validation_errors = error.get("validation_errors") or {}

@property
def validation_errors(self) -> List[SeamValidationError]:
"""Validation errors, one entry per failed request parameter."""

return [
SeamValidationError(
param_name, self.get_validation_error_messages(param_name)
)
for param_name in self._validation_errors
if param_name != "_errors"
]

def get_validation_error_messages(self, param_name: str) -> List[str]:
"""
The validation messages for a request parameter, or an empty list when
Expand Down
24 changes: 24 additions & 0 deletions test/http_error_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
SeamHttpApiError,
SeamHttpInvalidInputError,
SeamHttpUnauthorizedError,
SeamValidationError,
)


Expand Down Expand Up @@ -48,6 +49,29 @@ def test_seam_http_throws_invalid_input_error(server):
assert err.get_validation_error_messages("device_ids") == [
"Expected array, received number"
]
assert err.validation_errors == [
SeamValidationError(
parameter_name="device_ids",
error_messages=["Expected array, received number"],
)
]


def test_validation_errors_exclude_request_wide_errors():
err = SeamHttpInvalidInputError(
{
"validation_errors": {
"_errors": ["Request is invalid"],
"device_ids": {"_errors": ["Invalid device IDs"]},
}
},
400,
None,
)

assert err.validation_errors == [
SeamValidationError("device_ids", ["Invalid device IDs"])
]


def test_seam_http_invalid_input_error_has_no_messages_for_unknown_param(server):
Expand Down
Loading