Skip to content
This repository was archived by the owner on Jun 28, 2024. It is now read-only.
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
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ jobs:
python-version: 3.8
- name: Install Poetry
uses: snok/install-poetry@v1
- name: Run pre-build script (embed Sentry DSN)
run: |
poetry run python scripts/prebuild.py
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
- name: Release
uses: bjoluc/semantic-release-config-poetry@v1
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dist
seamapi/utils/get_sentry_dsn.py
.env
__pycache__
*/__pycache__
430 changes: 121 additions & 309 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ python = "^3.7"
requests = "^2.26.0"
python-dotenv = "^0.19.2"
dataclasses-json = "^0.5.6"
sentry-sdk = "^1.9.10"

[tool.poetry.dev-dependencies]
pytest = "^6.2.5"
black = "^21.12b0"
testcontainers = {extras = ["postgresql"], version = "^3.4.2"}
responses = "^0.22.0"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
11 changes: 11 additions & 0 deletions scripts/prebuild.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import os

value = os.environ.get("SENTRY_DSN", "None")
if value != "None":
value = f'"{value}"'

f = open("seamapi/utils/get_sentry_dsn.py", "w")
f.write(f"""
def get_sentry_dsn():
return {value}
""")
6 changes: 6 additions & 0 deletions seamapi/access_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import List, Optional, Union, Any
import requests
from seamapi.utils.convert_to_id import to_access_code_id, to_device_id
from seamapi.utils.report_error import report_error


class AccessCodes(AbstractAccessCodes):
Expand Down Expand Up @@ -48,6 +49,7 @@ def __init__(self, seam: Seam):

self.seam = seam

@report_error
def list(self, device: Union[DeviceId, Device]) -> List[AccessCode]:
"""Gets a list of access codes for a device.

Expand Down Expand Up @@ -76,6 +78,7 @@ def list(self, device: Union[DeviceId, Device]) -> List[AccessCode]:

return [AccessCode.from_dict(ac) for ac in access_codes]

@report_error
def get(
self,
access_code: Optional[Union[AccessCodeId, AccessCode]] = None,
Expand Down Expand Up @@ -114,6 +117,7 @@ def get(

return AccessCode.from_dict(res["access_code"])

@report_error
def create(
self,
device: Union[DeviceId, Device],
Expand Down Expand Up @@ -171,6 +175,7 @@ def create(

return AccessCode.from_dict(success_res["access_code"])

@report_error
def update(
self,
access_code: Union[AccessCodeId, AccessCode],
Expand Down Expand Up @@ -233,6 +238,7 @@ def update(

return AccessCode.from_dict(success_res["access_code"])

@report_error
def delete(
self,
access_code: Union[AccessCodeId, AccessCode],
Expand Down
3 changes: 3 additions & 0 deletions seamapi/action_attempts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import requests
from typing import Union
from seamapi.utils.convert_to_id import to_action_attempt_id
from seamapi.utils.report_error import report_error


class ActionAttempts(AbstractActionAttempts):
Expand Down Expand Up @@ -44,6 +45,7 @@ def __init__(self, seam: Seam):

self.seam = seam

@report_error
def get(
self, action_attempt: Union[ActionAttemptId, ActionAttempt]
) -> ActionAttempt:
Expand Down Expand Up @@ -87,6 +89,7 @@ def get(
error=error,
)

@report_error
def poll_until_ready(
self,
action_attempt: Union[ActionAttemptId, ActionAttempt],
Expand Down
4 changes: 4 additions & 0 deletions seamapi/connect_webviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import requests
from typing import List, Optional, Union
from seamapi.utils.convert_to_id import to_connect_webview_id
from seamapi.utils.report_error import report_error


class ConnectWebviews(AbstractConnectWebviews):
Expand Down Expand Up @@ -46,6 +47,7 @@ def __init__(self, seam: Seam):

self.seam = seam

@report_error
def list(self) -> List[ConnectWebview]:
"""Gets a list of connect webviews.

Expand All @@ -69,6 +71,7 @@ def list(self) -> List[ConnectWebview]:
ConnectWebview(**json_webview) for json_webview in json_webviews
]

@report_error
def get(
self, connect_webview: Union[ConnectWebviewId, ConnectWebview]
) -> ConnectWebview:
Expand Down Expand Up @@ -99,6 +102,7 @@ def get(

return ConnectWebview(**json_webview)

@report_error
def create(
self,
accepted_providers: List[AcceptedProvider],
Expand Down
4 changes: 4 additions & 0 deletions seamapi/connected_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import requests
from typing import Any, Dict, List, Union
from seamapi.utils.convert_to_id import to_connected_account_id
from seamapi.utils.report_error import report_error


class ConnectedAccounts(AbstractConnectedAccounts):
Expand Down Expand Up @@ -43,6 +44,7 @@ def __init__(self, seam: Seam):

self.seam = seam

@report_error
def list(self) -> List[ConnectedAccount]:
"""Gets a list of connected accounts.

Expand Down Expand Up @@ -73,6 +75,7 @@ def list(self) -> List[ConnectedAccount]:
for json_account in json_accounts
]

@report_error
def get(
self,
connected_account: Union[
Expand Down Expand Up @@ -129,6 +132,7 @@ def get(
errors=json_account.get("errors", []),
)

@report_error
def delete(
self,
connected_account: Union[ConnectedAccountId, ConnectedAccount],
Expand Down
6 changes: 5 additions & 1 deletion seamapi/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
DeviceType,
)
from typing import List, Union, Optional
import requests
from seamapi.utils.convert_to_id import (
to_connect_webview_id,
to_connected_account_id,
to_device_id,
)
from seamapi.utils.report_error import report_error


class Devices(AbstractDevices):
Expand Down Expand Up @@ -52,6 +52,7 @@ def __init__(self, seam: Seam):

self.seam = seam

@report_error
def list(
self,
connected_account: Union[ConnectedAccountId, ConnectedAccount] = None,
Expand Down Expand Up @@ -100,6 +101,7 @@ def list(

return [Device.from_dict(d) for d in devices]

@report_error
def get(
self,
device: Optional[Union[DeviceId, Device]] = None,
Expand Down Expand Up @@ -133,6 +135,7 @@ def get(
json_device = res["device"]
return Device.from_dict(json_device)

@report_error
def update(
self,
device: Union[DeviceId, Device],
Expand Down Expand Up @@ -184,6 +187,7 @@ def update(

return True

@report_error
def delete(self, device: Union[DeviceId, Device]) -> bool:
"""Deletes a device.

Expand Down
4 changes: 4 additions & 0 deletions seamapi/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from typing import List, Optional
import requests

from seamapi.utils.report_error import report_error


class Events(AbstractEvents):
"""
Expand Down Expand Up @@ -35,6 +37,7 @@ def __init__(self, seam: Seam):
"""
self.seam = seam

@report_error
def list(
self,
since: str,
Expand Down Expand Up @@ -93,6 +96,7 @@ def list(

return res["events"]

@report_error
def get(
self,
event_id: Optional[str] = None,
Expand Down
5 changes: 5 additions & 0 deletions seamapi/locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
to_connected_account_id,
to_device_id,
)
from seamapi.utils.report_error import report_error


class Locks(AbstractLocks):
Expand Down Expand Up @@ -55,6 +56,7 @@ def __init__(self, seam: Seam):

self.seam = seam

@report_error
def list(
self,
connected_account: Optional[
Expand Down Expand Up @@ -102,6 +104,7 @@ def list(

return [Device.from_dict(d) for d in json_locks]

@report_error
def get(
self,
device: Optional[Union[DeviceId, Device]] = None,
Expand Down Expand Up @@ -141,6 +144,7 @@ def get(

return Device.from_dict(json_lock)

@report_error
def lock_door(self, device: Union[DeviceId, Device]) -> ActionAttempt:
"""Locks a lock.

Expand Down Expand Up @@ -170,6 +174,7 @@ def lock_door(self, device: Union[DeviceId, Device]) -> ActionAttempt:
res["action_attempt"]["action_attempt_id"]
)

@report_error
def unlock_door(self, device: Union[DeviceId, Device]) -> ActionAttempt:
"""Unlocks a lock.

Expand Down
31 changes: 31 additions & 0 deletions seamapi/seam.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import os

from seamapi.utils.get_sentry_dsn import get_sentry_dsn
from .routes import Routes
import requests
import sentry_sdk
import pkg_resources
from typing import Optional, cast
from .types import AbstractSeam, SeamAPIException
Expand Down Expand Up @@ -43,6 +46,7 @@ def __init__(
self,
api_key: Optional[str] = None,
api_url: Optional[str] = None,
should_report_exceptions: Optional[bool] = False,
):
"""
Parameters
Expand All @@ -51,6 +55,8 @@ def __init__(
API key
api_url : str, optional
API url
should_report_exceptions : bool, optional
Defaults to False. If true, thrown exceptions will be reported to Seam.
"""
Routes.__init__(self)

Expand All @@ -64,6 +70,17 @@ def __init__(
api_url = os.environ.get("SEAM_API_URL", self.api_url)
self.api_key = api_key
self.api_url = cast(str, api_url)
self.should_report_exceptions = should_report_exceptions

if self.should_report_exceptions:
self.sentry_client = sentry_sdk.Hub(sentry_sdk.Client(
dsn=get_sentry_dsn(),
))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

self.sentry_client.scope.set_context("sdk_info", {
"repository": "https://github.com/seamapi/python",
"version": pkg_resources.get_distribution("seamapi").version,
"endpoint": self.api_url,
})

def make_request(self, method: str, path: str, **kwargs):
"""
Expand All @@ -87,6 +104,20 @@ def make_request(self, method: str, path: str, **kwargs):
}
response = requests.request(method, url, headers=headers, **kwargs)

if self.should_report_exceptions and response.status_code:
# Add breadcrumb
self.sentry_client.add_breadcrumb(
category="http",
level="info",
data={
"method": method,
"url": url,
"status_code": response.status_code,
"request_id": response.headers.get("seam-request-id", "unknown"),
},
)


parsed_response = response.json()

if response.status_code != 200:
Expand Down
5 changes: 5 additions & 0 deletions seamapi/utils/get_sentry_dsn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import os

def get_sentry_dsn():
# This is replaced with a hard-coded value during the build process (see scripts/prebuild.py)
return os.environ.get("SENTRY_DSN", None)
16 changes: 16 additions & 0 deletions seamapi/utils/report_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from seamapi.types import SeamAPIException

"""
A decorator for model methods that will report errors to Sentry (if enabled).
Expects that the model has a `seam` attribute that is a `Seam` instance.
"""
def report_error(f):
def wrapper(self, *args, **kwargs):
try:
return f(self, *args, **kwargs)
except Exception as error:
if self.seam.should_report_exceptions and type(error) is not SeamAPIException:
self.seam.sentry_client.capture_exception(error)

raise error
return wrapper
Loading