Skip to content

Commit 0838f68

Browse files
feat: Pass client instead of Seam instance to subclasses, update project structure (#63)
* Bump generator to 1.10.1 * ci: Generate code * Fix tests * Bump generator to 1.10.2 * ci: Generate code * Remove unnecessary import * Bump generator to 1.10.3 to fix circular imports with a separate file options.py * ci: Generate code * Add SeamMultiWorkspace * Add constants.py * Add get_endpoint helper fn * Add RequestMixin to define common make_request method * ci: Format code * Disable generate workflow * Add abstract classes, add helper for getting PAT, fix create ws test * Enforce keyword only args in WorkspacesProxy methods * Define WorkspacesProxy outside SeamMultiWorkspace * Fix proxy class usage * Add SeamHttpClient class * Adjust the routest to test new requests.Session * Remove .yalc files * Remove yalc.lock * Update seam http client to inherit from requests.Session with custom response handling * Remove endpoint rfom route clients as it's baked in the http client * Mark certain files as not generated * Fix client_options default value to fix linting * Remove AbstractRequestMixin * Swap requests for niquests * Uncomment innit imports * Remove duplicat SeamApiException definition * Add docstring for client and client_options * Revert generate-routes script * Revert package.json * Add AbstractSeamHttpClient * Update client_options docstring * Add seam/exceptions.py * Bump generator to 1.11.1 to update imports and structure * Update generator's tarball link * ci: Generate code * Add deep_attr_dict to /seam/utils * Renam types.py to models.py * Update top level imports * Remove seam/types.py, use seam/models.py * Update test/events/test_events.py import * Update response type on SeamApiException * Move SeamApiException to exceptions.py and update imports * Update imports in tests * More relative imports * Pass client and defaults to subclasses instead of seam instance * ci: Format code * Define routes on seam instance, update some imports * Update resolve_action_attempt import * Rename lib to modules * Update readme * Update imports * Remove client and client_options * Remove request.py * Bump generator, enable generation * ci: Generate code * Remove duplicate import --------- Co-authored-by: Seam Bot <devops@getseam.com>
1 parent 8ae9e68 commit 0838f68

58 files changed

Lines changed: 598 additions & 590 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Unlock a door
6666
6767
seam = Seam()
6868
lock = seam.locks.get(name="Front Door")
69-
seam.locks.unlock_door(device_id="lock.device_id")
69+
seam.locks.unlock_door(device_id=lock.device_id)
7070
7171
Authentication Method
7272
~~~~~~~~~~~~~~~~~~~~~

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"format": "prettier --write --ignore-path .gitignore ."
99
},
1010
"devDependencies": {
11-
"@seamapi/nextlove-sdk-generator": "^1.11.3",
11+
"@seamapi/nextlove-sdk-generator": "1.12.0",
1212
"@seamapi/types": "1.172.0",
1313
"del": "^7.1.0",
1414
"prettier": "^3.2.5"

seam/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
# type: ignore
33

44
from .seam import Seam
5-
from .types import SeamHttpApiError
65
from .seam_multi_workspace import SeamMultiWorkspace
76
from .options import SeamHttpInvalidOptionsError
87
from .auth import SeamHttpInvalidTokenError
9-
from .routes.action_attempts import (
8+
from .exceptions import (
9+
SeamHttpApiError,
1010
SeamActionAttemptError,
1111
SeamActionAttemptFailedError,
1212
SeamActionAttemptTimeoutError,

seam/auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from typing import Optional
2-
from seam.options import (
2+
from .options import (
33
SeamHttpInvalidOptionsError,
44
is_seam_http_options_with_api_key,
55
is_seam_http_options_with_personal_access_token,
66
)
7-
from seam.token import (
7+
from .token import (
88
is_jwt,
99
is_access_token,
1010
is_client_session_token,

seam/client.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import niquests as requests
44
from importlib.metadata import version
55

6-
from seam.constants import LTS_VERSION
7-
from seam.types import AbstractSeamHttpClient, SeamHttpApiError
6+
from .constants import LTS_VERSION
7+
from .exceptions import SeamHttpApiError
8+
from .models import AbstractSeamHttpClient
89

910
SDK_HEADERS = {
1011
"seam-sdk-name": "seamapi/python",

seam/exceptions.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from niquests import Response
2+
from .routes.models import ActionAttempt
3+
4+
5+
# HTTP
6+
class SeamHttpApiError(Exception):
7+
def __init__(
8+
self,
9+
response: Response,
10+
):
11+
self.status_code = response.status_code
12+
self.request_id = response.headers.get("seam-request-id", None)
13+
14+
self.metadata = None
15+
if "application/json" in response.headers["content-type"]:
16+
parsed_response = response.json()
17+
self.metadata = parsed_response.get("error", None)
18+
19+
super().__init__(
20+
f"SeamApiException: status={self.status_code}, request_id={self.request_id}, metadata={self.metadata}"
21+
)
22+
23+
24+
# Action Attempt
25+
class SeamActionAttemptError(Exception):
26+
def __init__(self, message: str, action_attempt: ActionAttempt):
27+
super().__init__(message)
28+
self.name = self.__class__.__name__
29+
self.action_attempt = action_attempt
30+
31+
32+
class SeamActionAttemptFailedError(SeamActionAttemptError):
33+
def __init__(self, action_attempt: ActionAttempt):
34+
super().__init__(action_attempt.error.message, action_attempt)
35+
self.name = self.__class__.__name__
36+
self.code = action_attempt.error.type
37+
38+
39+
class SeamActionAttemptTimeoutError(SeamActionAttemptError):
40+
def __init__(self, action_attempt: ActionAttempt, timeout: str):
41+
message = f"Timed out waiting for action attempt after {timeout}s"
42+
super().__init__(message, action_attempt)
43+
self.name = self.__class__.__name__

seam/types.py renamed to seam/models.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,7 @@
33
from typing_extensions import Self
44
import abc
55

6-
from seam.routes.types import AbstractRoutes, Workspace
7-
8-
9-
class SeamHttpApiError(Exception):
10-
def __init__(
11-
self,
12-
response,
13-
):
14-
self.status_code = response.status_code
15-
self.request_id = response.headers.get("seam-request-id", None)
16-
17-
self.metadata = None
18-
if "application/json" in response.headers["content-type"]:
19-
parsed_response = response.json()
20-
self.metadata = parsed_response.get("error", None)
21-
22-
super().__init__(
23-
f"SeamHttpApiError: status={self.status_code}, request_id={self.request_id}, metadata={self.metadata}"
24-
)
6+
from .routes.models import AbstractRoutes, Workspace
257

268

279
class AbstractSeamHttpClient(abc.ABC):
@@ -40,7 +22,7 @@ def _handle_response(self, response: requests.Response):
4022

4123
class AbstractSeam(AbstractRoutes):
4224
lts_version: str
43-
wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]
25+
defaults: Dict[str, Any]
4426

4527
@abc.abstractmethod
4628
def __init__(

seam/modules/action_attempts.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from typing import Dict, Optional, Union
2+
import time
3+
4+
from ..client import SeamHttpClient
5+
from ..exceptions import SeamActionAttemptFailedError, SeamActionAttemptTimeoutError
6+
from ..routes.models import ActionAttempt
7+
8+
TIMEOUT = 5.0
9+
POLLING_INTERVAL = 0.5
10+
11+
12+
def get_action_attempt(client: SeamHttpClient, action_attempt_id: str) -> ActionAttempt:
13+
res = client.post(
14+
"/action_attempts/get", json={"action_attempt_id": action_attempt_id}
15+
)
16+
17+
return ActionAttempt.from_dict(res["action_attempt"])
18+
19+
20+
def poll_until_ready(
21+
client: SeamHttpClient,
22+
*,
23+
action_attempt_id: str,
24+
timeout: Optional[float] = TIMEOUT,
25+
polling_interval: Optional[float] = POLLING_INTERVAL
26+
) -> ActionAttempt:
27+
time_waiting = 0.0
28+
29+
action_attempt = get_action_attempt(client, action_attempt_id)
30+
31+
while action_attempt.status == "pending":
32+
time.sleep(polling_interval)
33+
time_waiting += polling_interval
34+
35+
if time_waiting > timeout:
36+
raise SeamActionAttemptTimeoutError(action_attempt, timeout)
37+
38+
action_attempt = get_action_attempt(client, action_attempt_id)
39+
40+
if action_attempt.status == "failed":
41+
raise SeamActionAttemptFailedError(action_attempt)
42+
43+
return action_attempt
44+
45+
46+
def resolve_action_attempt(
47+
client: SeamHttpClient,
48+
*,
49+
action_attempt: ActionAttempt,
50+
wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None
51+
) -> ActionAttempt:
52+
if wait_for_action_attempt is True:
53+
return poll_until_ready(
54+
client=client, action_attempt_id=action_attempt.action_attempt_id
55+
)
56+
57+
if isinstance(wait_for_action_attempt, dict):
58+
return poll_until_ready(
59+
client=client,
60+
action_attempt_id=action_attempt.action_attempt_id,
61+
timeout=wait_for_action_attempt.get("timeout", TIMEOUT),
62+
polling_interval=wait_for_action_attempt.get(
63+
"polling_interval", POLLING_INTERVAL
64+
),
65+
)
66+
67+
return action_attempt

seam/options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
from typing import Optional
33

4-
from seam.constants import DEFAULT_ENDPOINT
4+
from .constants import DEFAULT_ENDPOINT
55

66

77
def get_endpoint(endpoint: Optional[str] = None):

0 commit comments

Comments
 (0)