diff --git a/pyproject.toml b/pyproject.toml index 99c341a..3cd6b0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,11 +10,11 @@ python = "^3.7" requests = "^2.26.0" python-dotenv = "^0.19.2" dataclasses-json = "^0.5.6" -testcontainers = {extras = ["postgresql"], version = "^3.4.2"} [tool.poetry.dev-dependencies] pytest = "^6.2.5" black = "^21.12b0" +testcontainers = {extras = ["postgresql"], version = "^3.4.2"} [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/seamapi/access_codes.py b/seamapi/access_codes.py index 54d67f9..b25e400 100644 --- a/seamapi/access_codes.py +++ b/seamapi/access_codes.py @@ -271,8 +271,4 @@ def delete( action_attempt = self.seam.action_attempts.poll_until_ready( res.json()["action_attempt"]["action_attempt_id"] ) - if action_attempt.status == "error" and action_attempt.error: - raise Exception( - f"{action_attempt.error.type}: {action_attempt.error.message}" - ) return action_attempt diff --git a/seamapi/action_attempts.py b/seamapi/action_attempts.py index 5b44d94..0c7e24f 100644 --- a/seamapi/action_attempts.py +++ b/seamapi/action_attempts.py @@ -3,6 +3,7 @@ ActionAttemptError, ActionAttempt, AbstractSeam as Seam, + ActionAttemptFailedException, ActionAttemptId, ) import time @@ -87,7 +88,9 @@ def get( ) def poll_until_ready( - self, action_attempt: Union[ActionAttemptId, ActionAttempt] + self, + action_attempt: Union[ActionAttemptId, ActionAttempt], + should_raise: bool = True, ) -> ActionAttempt: """ Polls an action attempt until its status is 'success' or 'error'. @@ -104,9 +107,22 @@ def poll_until_ready( updated_action_attempt = None while ( - updated_action_attempt is None - or updated_action_attempt.status == "pending" + updated_action_attempt is None or updated_action_attempt.status == "pending" ): updated_action_attempt = self.get(action_attempt) time.sleep(0.25) + + if updated_action_attempt.status == "error" and should_raise: + error_type = None + error_message = None + if updated_action_attempt.error is not None: + error_type = updated_action_attempt.error.type + error_message = updated_action_attempt.error.message + raise ActionAttemptFailedException( + action_attempt_id=updated_action_attempt.action_attempt_id, + action_type=updated_action_attempt.action_type, + error_type=error_type, + error_message=error_message, + ) + return updated_action_attempt diff --git a/seamapi/types.py b/seamapi/types.py index b254f9a..a0950e1 100644 --- a/seamapi/types.py +++ b/seamapi/types.py @@ -16,6 +16,23 @@ WorkspaceId = str +class ActionAttemptFailedException(Exception): + def __init__( + self, + action_attempt_id: Optional[str] = None, + action_type: Optional[str] = None, + error_type: Optional[str] = None, + error_message: Optional[str] = None, + ): + self.action_attempt_id = action_attempt_id + self.action_type = action_type + self.error_type = error_type + self.error_message = error_message + super().__init__( + f'Action Attempt for "{action_type}" Failed. {error_type}: {error_message} (action_attempt_id={action_attempt_id})' + ) + + @dataclass class Device: device_id: DeviceId @@ -100,7 +117,9 @@ def get( @abc.abstractmethod def poll_until_ready( - self, action_attempt: Union[ActionAttemptId, ActionAttempt] + self, + action_attempt: Union[ActionAttemptId, ActionAttempt], + should_raise: bool = True, ) -> ActionAttempt: raise NotImplementedError @@ -158,7 +177,7 @@ def update( def delete( self, access_code: Union[AccessCodeId, AccessCode], - ) -> None: + ) -> ActionAttempt: raise NotImplementedError diff --git a/tests/access_codes/test_access_codes.py b/tests/access_codes/test_access_codes.py index 162d585..5cdc03d 100644 --- a/tests/access_codes/test_access_codes.py +++ b/tests/access_codes/test_access_codes.py @@ -1,5 +1,7 @@ from seamapi import Seam +from seamapi.types import ActionAttemptFailedException from tests.fixtures.run_august_factory import run_august_factory +import pytest def test_access_codes(seam: Seam): @@ -19,6 +21,9 @@ def test_access_codes(seam: Seam): access_code = seam.access_codes.get(created_access_code.access_code_id) assert access_code.code == "4444" + with pytest.raises(ActionAttemptFailedException): + seam.access_codes.create(some_device.device_id, "Duplicate Access Code", "4444") + access_code = seam.access_codes.update(access_code, name="Updated name") assert access_code.name == "Updated name" diff --git a/tests/conftest.py b/tests/conftest.py index 0972f8d..1551f9d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -31,8 +31,6 @@ def seam_backend(): db_url = f"postgresql://test:test@{db_host}:{pg.get_exposed_port(pg.port_to_expose)}/postgres" with DockerContainer("registry.digitalocean.com/seam/seam-connect").with_env( "DATABASE_URL", - # TODO on mac us docker.host.internal instead of 172.17.0.1 when someone - # with a mac needs to run tests db_url, ).with_env("POSTGRES_DATABASE", "postgres").with_env( "NODE_ENV", "test" diff --git a/tests/fixtures/run_august_factory.py b/tests/fixtures/run_august_factory.py index 6ff75cb..05f3ea0 100644 --- a/tests/fixtures/run_august_factory.py +++ b/tests/fixtures/run_august_factory.py @@ -4,7 +4,7 @@ def run_august_factory(seam: Seam): - factory_res = requests.post( + requests.post( f"{seam.api_url}/internal/scenarios/factories/load", json={ "factory_name": "create_august_devices", @@ -15,3 +15,6 @@ def run_august_factory(seam: Seam): "Authorization": f"Bearer {seam.api_key}", }, ) + + # TODO remove when sync is supported in /internal/scenarios/factories/load + time.sleep(0.2)