Skip to content
This repository was archived by the owner on Jun 28, 2024. It is now read-only.

Commit 6d16709

Browse files
feat: Support webhooks API (#135)
* Add webhooks API * Test webhooks * Fix docstrings
1 parent 1d58e88 commit 6d16709

File tree

5 files changed

+243
-4
lines changed

5 files changed

+243
-4
lines changed

seamapi/routes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .access_codes import AccessCodes
1010
from .action_attempts import ActionAttempts
1111
from .thermostats import Thermostats
12+
from .webhooks import Webhooks
1213

1314
class Routes(AbstractRoutes):
1415
def __init__(self):
@@ -22,6 +23,7 @@ def __init__(self):
2223
self.action_attempts = ActionAttempts(seam=self)
2324
self.noise_sensors = NoiseSensors(seam=self)
2425
self.thermostats = Thermostats(seam=self)
26+
self.webhooks = Webhooks(seam=self)
2527

2628
def make_request(self):
2729
raise NotImplementedError()

seamapi/types.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
DeviceType = str # e.g. august_lock
1818
WorkspaceId = str
1919
ClimateSettingScheduleId = str
20+
WebhookId = str
2021

2122

2223
class SeamApiException(Exception):
@@ -297,6 +298,15 @@ class ClimateSettingScheduleUpdate(ClimateSettingSchedule):
297298
pass
298299

299300

301+
@dataclass_json
302+
@dataclass
303+
class Webhook:
304+
webhook_id: str
305+
url: str
306+
event_types: List[str] = None
307+
secret: str = None
308+
309+
300310
class AbstractActionAttempts(abc.ABC):
301311
@abc.abstractmethod
302312
def get(
@@ -776,6 +786,34 @@ def set_fan_mode(
776786
raise NotImplementedError
777787

778788

789+
class AbstractWebhooks(abc.ABC):
790+
@abc.abstractmethod
791+
def create(
792+
self,
793+
url: str,
794+
event_types: Optional[list] = None,
795+
) -> Webhook:
796+
raise NotImplementedError
797+
798+
@abc.abstractmethod
799+
def delete(
800+
self,
801+
webhook: Union[WebhookId, Webhook],
802+
) -> bool:
803+
raise NotImplementedError
804+
805+
@abc.abstractmethod
806+
def get(
807+
self,
808+
webhook: Union[WebhookId, Webhook],
809+
) -> Webhook:
810+
raise NotImplementedError
811+
812+
@abc.abstractmethod
813+
def list(self) -> List[Webhook]:
814+
raise NotImplementedError
815+
816+
779817
@dataclass
780818
class AbstractRoutes(abc.ABC):
781819
workspaces: AbstractWorkspaces
@@ -788,6 +826,7 @@ class AbstractRoutes(abc.ABC):
788826
thermostats: AbstractThermostats
789827
events: AbstractEvents
790828
connected_accounts: AbstractConnectedAccounts
829+
webhooks: AbstractWebhooks
791830

792831
@abc.abstractmethod
793832
def make_request(self, method: str, path: str, **kwargs) -> Any:

seamapi/utils/convert_to_id.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
ConnectedAccountId,
1212
Device,
1313
DeviceId,
14+
Webhook,
15+
WebhookId,
1416
Workspace,
1517
WorkspaceId,
1618
ClimateSettingSchedule,
@@ -31,15 +33,16 @@ def to_device_id(device: Union[DeviceId, Device]) -> str:
3133
return device
3234
return device.device_id
3335

34-
def to_climate_setting_schedule_id(climate_setting_schedule: Union[ClimateSettingScheduleId, ClimateSettingSchedule]) -> str:
36+
37+
def to_climate_setting_schedule_id(
38+
climate_setting_schedule: Union[ClimateSettingScheduleId, ClimateSettingSchedule]
39+
) -> str:
3540
if isinstance(climate_setting_schedule, str):
3641
return climate_setting_schedule
3742
return climate_setting_schedule.climate_setting_schedule_id
3843

3944

40-
def to_action_attempt_id(
41-
action_attempt: Union[ActionAttemptId, ActionAttempt]
42-
) -> str:
45+
def to_action_attempt_id(action_attempt: Union[ActionAttemptId, ActionAttempt]) -> str:
4346
if isinstance(action_attempt, str):
4447
return action_attempt
4548
return action_attempt.action_attempt_id
@@ -71,3 +74,9 @@ def to_event_id(event: Union[EventId, Event]) -> str:
7174
if isinstance(event, str):
7275
return event
7376
return event.event_id
77+
78+
79+
def to_webhook_id(webhook: Union[WebhookId, Webhook]) -> str:
80+
if isinstance(webhook, str):
81+
return webhook
82+
return webhook.webhook_id

seamapi/webhooks.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
from seamapi.types import (
2+
AbstractSeam as Seam,
3+
AbstractWebhooks,
4+
Webhook,
5+
WebhookId,
6+
)
7+
import time
8+
from typing import List, Union, Optional, cast
9+
import requests
10+
from seamapi.utils.convert_to_id import (
11+
to_connect_webview_id,
12+
to_connected_account_id,
13+
to_device_id,
14+
to_webhook_id,
15+
)
16+
from seamapi.utils.report_error import report_error
17+
18+
19+
class Webhooks(AbstractWebhooks):
20+
"""
21+
A class used to interact with webhooks API
22+
23+
...
24+
25+
Attributes
26+
----------
27+
seam : Seam
28+
Initial seam class
29+
30+
Methods
31+
-------
32+
create(url, event_types=None)
33+
Creates a new webhook
34+
delete(webhook_id)
35+
Deletes a webhook
36+
get(webhook_id)
37+
Fetches a webhook
38+
list()
39+
Lists webhooks
40+
"""
41+
42+
seam: Seam
43+
44+
def __init__(self, seam: Seam):
45+
"""
46+
Parameters
47+
----------
48+
seam : Seam
49+
Initial seam class
50+
"""
51+
52+
self.seam = seam
53+
54+
@report_error
55+
def create(
56+
self,
57+
url: str,
58+
event_types: Optional[list] = None,
59+
) -> Webhook:
60+
"""Creates a new webhook.
61+
62+
Parameters
63+
----------
64+
url : str
65+
URL to send webhook events to
66+
event_types : Optional[List[str]]
67+
List of event types to send to webhook eg. ["connected_account.connected"]. Defaults to ["*"]
68+
69+
Raises
70+
------
71+
Exception
72+
If the API request wasn't successful.
73+
74+
Returns
75+
------
76+
A webhook.
77+
"""
78+
create_payload = {"url": url}
79+
if event_types is not None:
80+
create_payload["event_types"] = event_types
81+
82+
res = self.seam.make_request(
83+
"POST",
84+
"/webhooks/create",
85+
json=create_payload,
86+
)
87+
88+
return Webhook.from_dict(res["webhook"])
89+
90+
@report_error
91+
def delete(
92+
self,
93+
webhook: Union[WebhookId, Webhook],
94+
) -> bool:
95+
"""Deletes a webhook.
96+
97+
Parameters
98+
----------
99+
webhook : Union[WebhookId, Webhook]
100+
Webhook ID or Webhook
101+
102+
Raises
103+
------
104+
Exception
105+
If the API request wasn't successful.
106+
107+
Returns
108+
------
109+
Boolean.
110+
"""
111+
112+
res = self.seam.make_request(
113+
"DELETE",
114+
"/webhooks/delete",
115+
json={"webhook_id": to_webhook_id(webhook)},
116+
)
117+
118+
return True
119+
120+
@report_error
121+
def get(
122+
self,
123+
webhook: Union[WebhookId, Webhook],
124+
) -> Webhook:
125+
"""Fetches a webhook.
126+
127+
Parameters
128+
----------
129+
webhook : Union[WebhookId, Webhook]
130+
Webhook ID or Webhook
131+
132+
Raises
133+
------
134+
Exception
135+
If the API request wasn't successful.
136+
137+
Returns
138+
------
139+
A webhook.
140+
"""
141+
142+
res = self.seam.make_request(
143+
"GET",
144+
"/webhooks/get",
145+
params={"webhook_id": to_webhook_id(webhook)},
146+
)
147+
148+
return Webhook.from_dict(res["webhook"])
149+
150+
@report_error
151+
def list(
152+
self,
153+
) -> List[Webhook]:
154+
"""Lists webhooks.
155+
156+
Raises
157+
------
158+
Exception
159+
If the API request wasn't successful.
160+
161+
Returns
162+
------
163+
A list of webhooks.
164+
"""
165+
166+
res = self.seam.make_request(
167+
"GET",
168+
"/webhooks/list",
169+
)
170+
171+
return [Webhook.from_dict(w) for w in res["webhooks"]]

tests/webhooks/test_webhooks.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from seamapi import Seam
2+
3+
4+
def test_workspaces(seam: Seam):
5+
webhook = seam.webhooks.create(
6+
url="https://example.com", event_types=["connected_account.connected"]
7+
)
8+
assert webhook.url == "https://example.com"
9+
10+
webhook = seam.webhooks.get(webhook)
11+
assert webhook is not None
12+
13+
webhook_list = seam.webhooks.list()
14+
assert len(webhook_list) > 0
15+
16+
seam.webhooks.delete(webhook)
17+
webhook_list = seam.webhooks.list()
18+
assert len(webhook_list) == 0

0 commit comments

Comments
 (0)