Skip to content

Commit 788b417

Browse files
committed
Merge branch 'claude/python-sdk-audit-o2iid9-03-poll-query' into claude/python-sdk-audit-o2iid9-04-wait-option
2 parents 0022092 + 346dea3 commit 788b417

11 files changed

Lines changed: 84 additions & 9 deletions

‎README.rst‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,18 @@ The ``AsyncSeamWithoutWorkspace`` client is the equivalent async variant of
515515
Error Handling
516516
~~~~~~~~~~~~~~
517517

518+
Errors raised by the SDK subclass ``SeamError``, so one except block
519+
covers everything the SDK raises:
520+
521+
.. code-block:: python
522+
523+
from seam import SeamError
524+
525+
try:
526+
seam.locks.unlock_door(device_id=device_id)
527+
except SeamError as error:
528+
print(error)
529+
518530
Requests rejected by the Seam API raise a ``SeamHttpApiError`` subclass
519531
carrying the HTTP ``status_code``, API error ``code``, and ``request_id``.
520532

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "seam"
3-
version = "3.12.0"
3+
version = "3.13.1"
44
description = "SDK for the Seam API written in Python."
55
authors = [{ name = "Seam Labs, Inc.", email = "engineering@getseam.com" }]
66
license = "MIT"

‎seam/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .options import SeamInvalidOptionsError
77
from .auth import SeamInvalidTokenError
88
from .exceptions import (
9+
SeamError,
910
SeamHttpApiError,
1011
SeamHttpUnauthorizedError,
1112
SeamHttpInvalidInputError,

‎seam/auth.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Optional
2+
from .exceptions import SeamError
23
from .options import (
34
SeamInvalidOptionsError,
45
is_seam_options_with_api_key,
@@ -15,7 +16,7 @@
1516
)
1617

1718

18-
class SeamInvalidTokenError(Exception):
19+
class SeamInvalidTokenError(SeamError):
1920
"""
2021
Exception raised when an invalid token is provided to the Seam client.
2122

‎seam/exceptions.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ class SeamValidationError:
1111
error_messages: List[str]
1212

1313

14+
class SeamError(Exception):
15+
"""Base exception for all errors raised by the Seam SDK."""
16+
17+
1418
# HTTP
15-
class SeamHttpApiError(Exception):
19+
class SeamHttpApiError(SeamError):
1620
"""
1721
Base exception for Seam HTTP API errors.
1822
@@ -123,7 +127,7 @@ def get_validation_error_messages(self, param_name: str) -> List[str]:
123127

124128

125129
# Action Attempt
126-
class SeamActionAttemptError(Exception):
130+
class SeamActionAttemptError(SeamError):
127131
"""
128132
Base exception for Seam Action Attempt errors.
129133

‎seam/models.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ async def list(
120120
raise NotImplementedError()
121121

122122

123-
class AbstractSeamWithoutWorkspace:
123+
class AbstractSeamWithoutWorkspace(abc.ABC):
124124
wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
125125

126126
@abc.abstractmethod
@@ -145,7 +145,7 @@ def from_personal_access_token(
145145
raise NotImplementedError
146146

147147

148-
class AbstractAsyncSeamWithoutWorkspace:
148+
class AbstractAsyncSeamWithoutWorkspace(abc.ABC):
149149
wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
150150

151151
@abc.abstractmethod

‎seam/options.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Optional
33

44
from .constants import DEFAULT_ENDPOINT
5+
from .exceptions import SeamError
56

67

78
def get_endpoint(endpoint: Optional[str] = None):
@@ -31,7 +32,7 @@ def get_endpoint_from_env():
3132
return seam_endpoint or seam_api_url
3233

3334

34-
class SeamInvalidOptionsError(Exception):
35+
class SeamInvalidOptionsError(SeamError):
3536
"""
3637
Exception raised when invalid options are provided to Seam client.
3738

‎seam/url_search_params_serializer.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,13 @@
4040
from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union
4141
from urllib.parse import parse_qsl
4242

43+
from .exceptions import SeamError
4344
from .null import is_null
4445

4546
Params = Mapping[str, Any]
4647

4748

48-
class UnserializableParamError(Exception):
49+
class UnserializableParamError(SeamError):
4950
"""Exception raised when a param could not be serialized.
5051
5152
:ivar name: Name of the param that could not be serialized

‎test/abstract_models_test.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import pytest
2+
3+
from seam.models import (
4+
AbstractAsyncSeamWithoutWorkspace,
5+
AbstractSeamWithoutWorkspace,
6+
)
7+
8+
9+
def test_abstract_seam_without_workspace_cannot_be_instantiated():
10+
with pytest.raises(TypeError, match="abstract"):
11+
# pylint: disable-next=abstract-class-instantiated
12+
AbstractSeamWithoutWorkspace("seam_at_token") # type: ignore[abstract]
13+
14+
15+
def test_abstract_async_seam_without_workspace_cannot_be_instantiated():
16+
with pytest.raises(TypeError, match="abstract"):
17+
# pylint: disable-next=abstract-class-instantiated
18+
AbstractAsyncSeamWithoutWorkspace("seam_at_token") # type: ignore[abstract]

‎test/seam_error_test.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from seam import (
2+
SeamActionAttemptError,
3+
SeamActionAttemptFailedError,
4+
SeamActionAttemptTimeoutError,
5+
SeamError,
6+
SeamHttpApiError,
7+
SeamHttpInvalidInputError,
8+
SeamHttpUnauthorizedError,
9+
SeamInvalidOptionsError,
10+
SeamInvalidTokenError,
11+
UnserializableParamError,
12+
)
13+
14+
15+
def test_seam_error_is_the_base_of_every_sdk_error():
16+
for error_class in [
17+
SeamHttpApiError,
18+
SeamHttpUnauthorizedError,
19+
SeamHttpInvalidInputError,
20+
SeamActionAttemptError,
21+
SeamActionAttemptFailedError,
22+
SeamActionAttemptTimeoutError,
23+
SeamInvalidOptionsError,
24+
SeamInvalidTokenError,
25+
UnserializableParamError,
26+
]:
27+
assert issubclass(error_class, SeamError)
28+
29+
30+
def test_seam_error_catches_an_api_error():
31+
error = SeamHttpApiError(
32+
{"type": "device_not_found", "message": "Device not found"},
33+
404,
34+
"request-id",
35+
)
36+
37+
assert isinstance(error, SeamError)

0 commit comments

Comments
 (0)