diff --git a/codegen/layouts/partials/route-method.hbs b/codegen/layouts/partials/route-method.hbs index ad5454d6..31e54712 100644 --- a/codegen/layouts/partials/route-method.hbs +++ b/codegen/layouts/partials/route-method.hbs @@ -24,7 +24,7 @@ return {{#if isAsync}}await resolve_action_attempt_async{{else}}resolve_action_attempt{{/if}}( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict(unwrap(res, "action_attempt", "{{path}}")), wait_for_action_attempt=wait_for_action_attempt ) {{else if (eq returnType "None")}} @@ -32,8 +32,8 @@ return None {{else if (isListType returnType)}} - return [{{fromDict (listItemType returnType)}}(item) for item in res{{#each returnPath}}["{{this}}"]{{/each}}] + return [{{fromDict (listItemType returnType)}}(item) for item in unwrap_list(res, "{{returnPath.[0]}}", "{{path}}")] {{else}} - return {{fromDict returnType}}(res{{#each returnPath}}["{{this}}"]{{/each}}) + return {{fromDict returnType}}(unwrap(res, "{{returnPath.[0]}}", "{{path}}")) {{/if}} diff --git a/codegen/layouts/route.hbs b/codegen/layouts/route.hbs index edd132af..f5bdbbe0 100644 --- a/codegen/layouts/route.hbs +++ b/codegen/layouts/route.hbs @@ -14,6 +14,12 @@ from .{{module}} import {{abstractClassName}}, {{className}}, {{asyncAbstractCla {{#if importResolveActionAttempt}} from ..modules.action_attempts import resolve_action_attempt, resolve_action_attempt_async {{/if}} +{{#if importUnwrap}} +from ..response import unwrap +{{/if}} +{{#if importUnwrapList}} +from ..response import unwrap_list +{{/if}} {{> abstract-route-class abstractClass}} diff --git a/codegen/lib/layouts/route.ts b/codegen/lib/layouts/route.ts index 029f2ea0..edebac8c 100644 --- a/codegen/lib/layouts/route.ts +++ b/codegen/lib/layouts/route.ts @@ -61,6 +61,8 @@ export interface RouteLayoutContext { }> importResolveActionAttempt: boolean importNull: boolean + importUnwrap: boolean + importUnwrapList: boolean methods: MethodLayoutContext[] } @@ -130,6 +132,16 @@ export const setRouteLayoutContext = (cls: ClassModel): RouteLayoutContext => { params.some(({ isNullable }) => isNullable), ) + const importUnwrap = methods.some( + ({ returnPath, returnType }) => + returnPath.length > 0 && !returnType.startsWith('List['), + ) + + const importUnwrapList = methods.some( + ({ returnPath, returnType }) => + returnPath.length > 0 && returnType.startsWith('List['), + ) + const showPass = cls.methods.length === 0 && cls.childClassIdentifiers.length === 0 @@ -172,6 +184,8 @@ export const setRouteLayoutContext = (cls: ClassModel): RouteLayoutContext => { })), importResolveActionAttempt, importNull, + importUnwrap, + importUnwrapList, methods, } } diff --git a/seam/__init__.py b/seam/__init__.py index 890e4ec2..cc74741f 100644 --- a/seam/__init__.py +++ b/seam/__init__.py @@ -8,6 +8,7 @@ from .exceptions import ( SeamError, SeamHttpApiError, + SeamHttpInvalidResponseError, SeamHttpUnauthorizedError, SeamHttpInvalidInputError, SeamValidationError, diff --git a/seam/client.py b/seam/client.py index 0f9e7fe0..3711ecdf 100644 --- a/seam/client.py +++ b/seam/client.py @@ -1,4 +1,5 @@ from collections.abc import Mapping +from json import JSONDecodeError from typing import Any, Dict, Optional from importlib.metadata import version import abc @@ -68,7 +69,13 @@ def _handle_response(self, response: Response): self._handle_error_response(response) if "application/json" in response.headers.get("content-type", ""): - return response.json() + try: + return response.json() + except JSONDecodeError: + # A body that lies about its content type is handed on as + # text, so readers report an invalid response instead of + # leaking a decode error. + return response.text return response.text diff --git a/seam/exceptions.py b/seam/exceptions.py index 667ba724..06bb6ba6 100644 --- a/seam/exceptions.py +++ b/seam/exceptions.py @@ -16,6 +16,36 @@ class SeamError(Exception): # HTTP +class SeamHttpInvalidResponseError(SeamError): + """ + Exception raised when a success response from the Seam API has an + unexpected shape, e.g., a proxy rewrote the body or the expected + response key is missing. + + :ivar path: The request path that produced the response + :vartype path: str + :ivar response_key: The response key the SDK expected to read + :vartype response_key: str + """ + + def __init__(self, path: str, response_key: str, reason: str): + """ + :param path: The request path that produced the response + :type path: str + :param response_key: The response key the SDK expected to read + :type response_key: str + :param reason: Description of how the response diverged + :type reason: str + """ + + super().__init__( + f"Seam returned an invalid response for {path}: " + f'expected "{response_key}", {reason}' + ) + self.path = path + self.response_key = response_key + + class SeamHttpApiError(SeamError): """ Base exception for Seam HTTP API errors. diff --git a/seam/modules/action_attempts.py b/seam/modules/action_attempts.py index 01fdd4a0..01d8f872 100644 --- a/seam/modules/action_attempts.py +++ b/seam/modules/action_attempts.py @@ -6,6 +6,7 @@ from ..exceptions import SeamActionAttemptFailedError, SeamActionAttemptTimeoutError from ..options import SeamInvalidOptionsError from ..resources import ActionAttempt, SuccessActionAttempt, action_attempt_from_dict +from ..response import unwrap TIMEOUT = 5.0 POLLING_INTERVAL = 0.5 @@ -72,7 +73,9 @@ def get_action_attempt(client: SeamHttpClient, action_attempt_id: str) -> Action "/action_attempts/get", params={"action_attempt_id": action_attempt_id} ) - return action_attempt_from_dict(res["action_attempt"]) + return action_attempt_from_dict( + unwrap(res, "action_attempt", "/action_attempts/get") + ) def poll_until_ready( @@ -142,7 +145,9 @@ async def get_action_attempt_async( "/action_attempts/get", params={"action_attempt_id": action_attempt_id} ) - return action_attempt_from_dict(res["action_attempt"]) + return action_attempt_from_dict( + unwrap(res, "action_attempt", "/action_attempts/get") + ) async def poll_until_ready_async( diff --git a/seam/response.py b/seam/response.py new file mode 100644 index 00000000..d871d935 --- /dev/null +++ b/seam/response.py @@ -0,0 +1,52 @@ +"""Read success response payloads defensively. + +A 2xx response with an unexpected shape, e.g., a proxy rewrote the body or +the response key was renamed, raises the SDK's own error instead of leaking +a bare KeyError or TypeError from inside a generated route method. +""" + +from typing import Any, Dict, List + +from .exceptions import SeamHttpInvalidResponseError + + +def _read_response_key(res: Any, response_key: str, path: str) -> Any: + if not isinstance(res, dict): + raise SeamHttpInvalidResponseError( + path, + response_key, + f"got {type(res).__name__} instead of a response object", + ) + + if response_key not in res: + raise SeamHttpInvalidResponseError( + path, response_key, "which the response does not contain" + ) + + return res[response_key] + + +def unwrap(res: Any, response_key: str, path: str) -> Dict[str, Any]: + """Read an object under the response key, or raise for a malformed response.""" + + value = _read_response_key(res, response_key, path) + + if not isinstance(value, dict): + raise SeamHttpInvalidResponseError( + path, response_key, f"got {type(value).__name__} instead of an object" + ) + + return value + + +def unwrap_list(res: Any, response_key: str, path: str) -> List[Any]: + """Read a list under the response key, or raise for a malformed response.""" + + value = _read_response_key(res, response_key, path) + + if not isinstance(value, list): + raise SeamHttpInvalidResponseError( + path, response_key, f"got {type(value).__name__} instead of a list" + ) + + return value diff --git a/seam/routes/access_codes.py b/seam/routes/access_codes.py index b9b4e92e..f53a0aa1 100644 --- a/seam/routes/access_codes.py +++ b/seam/routes/access_codes.py @@ -16,6 +16,8 @@ AbstractAsyncAccessCodesUnmanaged, AsyncAccessCodesUnmanaged, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractAccessCodes(abc.ABC): @@ -865,7 +867,7 @@ def create( res = self.client.post("/access_codes/create", json=json_payload) - return AccessCode.from_dict(res["access_code"]) + return AccessCode.from_dict(unwrap(res, "access_code", "/access_codes/create")) @route_metadata( path="/access_codes/create_multiple", @@ -973,7 +975,12 @@ def create_multiple( res = self.client.put("/access_codes/create_multiple", json=json_payload) - return [AccessCode.from_dict(item) for item in res["access_codes"]] + return [ + AccessCode.from_dict(item) + for item in unwrap_list( + res, "access_codes", "/access_codes/create_multiple" + ) + ] @route_metadata( path="/access_codes/delete", has_required_parameters=True, has_pagination=False @@ -1027,7 +1034,9 @@ def generate_code(self, *, device_id: str) -> AccessCode: res = self.client.get("/access_codes/generate_code", params=params) - return AccessCode.from_dict(res["generated_code"]) + return AccessCode.from_dict( + unwrap(res, "generated_code", "/access_codes/generate_code") + ) @route_metadata( path="/access_codes/get", has_required_parameters=True, has_pagination=False @@ -1066,7 +1075,7 @@ def get( res = self.client.get("/access_codes/get", params=params) - return AccessCode.from_dict(res["access_code"]) + return AccessCode.from_dict(unwrap(res, "access_code", "/access_codes/get")) @route_metadata( path="/access_codes/list", has_required_parameters=True, has_pagination=True @@ -1142,7 +1151,10 @@ def list( res = self.client.get("/access_codes/list", params=params) - return [AccessCode.from_dict(item) for item in res["access_codes"]] + return [ + AccessCode.from_dict(item) + for item in unwrap_list(res, "access_codes", "/access_codes/list") + ] @route_metadata( path="/access_codes/pull_backup_access_code", @@ -1179,7 +1191,9 @@ def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: "/access_codes/pull_backup_access_code", json=json_payload ) - return AccessCode.from_dict(res["access_code"]) + return AccessCode.from_dict( + unwrap(res, "access_code", "/access_codes/pull_backup_access_code") + ) @route_metadata( path="/access_codes/report_device_constraints", @@ -1494,7 +1508,7 @@ async def create( res = await self.client.post("/access_codes/create", json=json_payload) - return AccessCode.from_dict(res["access_code"]) + return AccessCode.from_dict(unwrap(res, "access_code", "/access_codes/create")) @route_metadata( path="/access_codes/create_multiple", @@ -1602,7 +1616,12 @@ async def create_multiple( res = await self.client.put("/access_codes/create_multiple", json=json_payload) - return [AccessCode.from_dict(item) for item in res["access_codes"]] + return [ + AccessCode.from_dict(item) + for item in unwrap_list( + res, "access_codes", "/access_codes/create_multiple" + ) + ] @route_metadata( path="/access_codes/delete", has_required_parameters=True, has_pagination=False @@ -1658,7 +1677,9 @@ async def generate_code(self, *, device_id: str) -> AccessCode: res = await self.client.get("/access_codes/generate_code", params=params) - return AccessCode.from_dict(res["generated_code"]) + return AccessCode.from_dict( + unwrap(res, "generated_code", "/access_codes/generate_code") + ) @route_metadata( path="/access_codes/get", has_required_parameters=True, has_pagination=False @@ -1697,7 +1718,7 @@ async def get( res = await self.client.get("/access_codes/get", params=params) - return AccessCode.from_dict(res["access_code"]) + return AccessCode.from_dict(unwrap(res, "access_code", "/access_codes/get")) @route_metadata( path="/access_codes/list", has_required_parameters=True, has_pagination=True @@ -1773,7 +1794,10 @@ async def list( res = await self.client.get("/access_codes/list", params=params) - return [AccessCode.from_dict(item) for item in res["access_codes"]] + return [ + AccessCode.from_dict(item) + for item in unwrap_list(res, "access_codes", "/access_codes/list") + ] @route_metadata( path="/access_codes/pull_backup_access_code", @@ -1810,7 +1834,9 @@ async def pull_backup_access_code(self, *, access_code_id: str) -> AccessCode: "/access_codes/pull_backup_access_code", json=json_payload ) - return AccessCode.from_dict(res["access_code"]) + return AccessCode.from_dict( + unwrap(res, "access_code", "/access_codes/pull_backup_access_code") + ) @route_metadata( path="/access_codes/report_device_constraints", diff --git a/seam/routes/access_codes_simulate.py b/seam/routes/access_codes_simulate.py index 3c1040e2..474db7ef 100644 --- a/seam/routes/access_codes_simulate.py +++ b/seam/routes/access_codes_simulate.py @@ -3,6 +3,7 @@ from ..client import SeamHttpClient, AsyncSeamHttpClient from ..route import route_metadata from ..resources import UnmanagedAccessCode +from ..response import unwrap class AbstractAccessCodesSimulate(abc.ABC): @@ -87,7 +88,13 @@ def create_unmanaged_access_code( "/access_codes/simulate/create_unmanaged_access_code", json=json_payload ) - return UnmanagedAccessCode.from_dict(res["access_code"]) + return UnmanagedAccessCode.from_dict( + unwrap( + res, + "access_code", + "/access_codes/simulate/create_unmanaged_access_code", + ) + ) class AsyncAccessCodesSimulate(AbstractAsyncAccessCodesSimulate): @@ -132,4 +139,10 @@ async def create_unmanaged_access_code( "/access_codes/simulate/create_unmanaged_access_code", json=json_payload ) - return UnmanagedAccessCode.from_dict(res["access_code"]) + return UnmanagedAccessCode.from_dict( + unwrap( + res, + "access_code", + "/access_codes/simulate/create_unmanaged_access_code", + ) + ) diff --git a/seam/routes/access_codes_unmanaged.py b/seam/routes/access_codes_unmanaged.py index 8a8cd367..29c6581a 100644 --- a/seam/routes/access_codes_unmanaged.py +++ b/seam/routes/access_codes_unmanaged.py @@ -4,6 +4,8 @@ from ..route import route_metadata from ..null import Null from ..resources import UnmanagedAccessCode +from ..response import unwrap +from ..response import unwrap_list class AbstractAccessCodesUnmanaged(abc.ABC): @@ -355,7 +357,9 @@ def get( res = self.client.get("/access_codes/unmanaged/get", params=params) - return UnmanagedAccessCode.from_dict(res["access_code"]) + return UnmanagedAccessCode.from_dict( + unwrap(res, "access_code", "/access_codes/unmanaged/get") + ) @route_metadata( path="/access_codes/unmanaged/list", @@ -406,7 +410,10 @@ def list( res = self.client.get("/access_codes/unmanaged/list", params=params) - return [UnmanagedAccessCode.from_dict(item) for item in res["access_codes"]] + return [ + UnmanagedAccessCode.from_dict(item) + for item in unwrap_list(res, "access_codes", "/access_codes/unmanaged/list") + ] @route_metadata( path="/access_codes/unmanaged/update", @@ -583,7 +590,9 @@ async def get( res = await self.client.get("/access_codes/unmanaged/get", params=params) - return UnmanagedAccessCode.from_dict(res["access_code"]) + return UnmanagedAccessCode.from_dict( + unwrap(res, "access_code", "/access_codes/unmanaged/get") + ) @route_metadata( path="/access_codes/unmanaged/list", @@ -634,7 +643,10 @@ async def list( res = await self.client.get("/access_codes/unmanaged/list", params=params) - return [UnmanagedAccessCode.from_dict(item) for item in res["access_codes"]] + return [ + UnmanagedAccessCode.from_dict(item) + for item in unwrap_list(res, "access_codes", "/access_codes/unmanaged/list") + ] @route_metadata( path="/access_codes/unmanaged/update", diff --git a/seam/routes/access_grants.py b/seam/routes/access_grants.py index 850c98e6..f7e16491 100644 --- a/seam/routes/access_grants.py +++ b/seam/routes/access_grants.py @@ -10,6 +10,8 @@ AbstractAsyncAccessGrantsUnmanaged, AsyncAccessGrantsUnmanaged, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractAccessGrants(abc.ABC): @@ -581,7 +583,9 @@ def create( res = self.client.post("/access_grants/create", json=json_payload) - return AccessGrant.from_dict(res["access_grant"]) + return AccessGrant.from_dict( + unwrap(res, "access_grant", "/access_grants/create") + ) @route_metadata( path="/access_grants/delete", has_required_parameters=True, has_pagination=False @@ -638,7 +642,7 @@ def get( res = self.client.get("/access_grants/get", params=params) - return AccessGrant.from_dict(res["access_grant"]) + return AccessGrant.from_dict(unwrap(res, "access_grant", "/access_grants/get")) @route_metadata( path="/access_grants/get_related", @@ -710,7 +714,7 @@ def get_related( res = self.client.get("/access_grants/get_related", params=params) - return Batch.from_dict(res["batch"]) + return Batch.from_dict(unwrap(res, "batch", "/access_grants/get_related")) @route_metadata( path="/access_grants/list", has_required_parameters=False, has_pagination=True @@ -792,7 +796,10 @@ def list( res = self.client.get("/access_grants/list", params=params) - return [AccessGrant.from_dict(item) for item in res["access_grants"]] + return [ + AccessGrant.from_dict(item) + for item in unwrap_list(res, "access_grants", "/access_grants/list") + ] @route_metadata( path="/access_grants/request_access_methods", @@ -827,7 +834,9 @@ def request_access_methods( "/access_grants/request_access_methods", json=json_payload ) - return AccessGrant.from_dict(res["access_grant"]) + return AccessGrant.from_dict( + unwrap(res, "access_grant", "/access_grants/request_access_methods") + ) @route_metadata( path="/access_grants/update", has_required_parameters=True, has_pagination=False @@ -984,7 +993,9 @@ async def create( res = await self.client.post("/access_grants/create", json=json_payload) - return AccessGrant.from_dict(res["access_grant"]) + return AccessGrant.from_dict( + unwrap(res, "access_grant", "/access_grants/create") + ) @route_metadata( path="/access_grants/delete", has_required_parameters=True, has_pagination=False @@ -1041,7 +1052,7 @@ async def get( res = await self.client.get("/access_grants/get", params=params) - return AccessGrant.from_dict(res["access_grant"]) + return AccessGrant.from_dict(unwrap(res, "access_grant", "/access_grants/get")) @route_metadata( path="/access_grants/get_related", @@ -1113,7 +1124,7 @@ async def get_related( res = await self.client.get("/access_grants/get_related", params=params) - return Batch.from_dict(res["batch"]) + return Batch.from_dict(unwrap(res, "batch", "/access_grants/get_related")) @route_metadata( path="/access_grants/list", has_required_parameters=False, has_pagination=True @@ -1195,7 +1206,10 @@ async def list( res = await self.client.get("/access_grants/list", params=params) - return [AccessGrant.from_dict(item) for item in res["access_grants"]] + return [ + AccessGrant.from_dict(item) + for item in unwrap_list(res, "access_grants", "/access_grants/list") + ] @route_metadata( path="/access_grants/request_access_methods", @@ -1230,7 +1244,9 @@ async def request_access_methods( "/access_grants/request_access_methods", json=json_payload ) - return AccessGrant.from_dict(res["access_grant"]) + return AccessGrant.from_dict( + unwrap(res, "access_grant", "/access_grants/request_access_methods") + ) @route_metadata( path="/access_grants/update", has_required_parameters=True, has_pagination=False diff --git a/seam/routes/access_grants_unmanaged.py b/seam/routes/access_grants_unmanaged.py index 0164bef5..01ebfbe1 100644 --- a/seam/routes/access_grants_unmanaged.py +++ b/seam/routes/access_grants_unmanaged.py @@ -4,6 +4,8 @@ from ..route import route_metadata from ..null import Null from ..resources import UnmanagedAccessGrant +from ..response import unwrap +from ..response import unwrap_list class AbstractAccessGrantsUnmanaged(abc.ABC): @@ -166,7 +168,9 @@ def get(self, *, access_grant_id: str) -> UnmanagedAccessGrant: res = self.client.get("/access_grants/unmanaged/get", params=params) - return UnmanagedAccessGrant.from_dict(res["access_grant"]) + return UnmanagedAccessGrant.from_dict( + unwrap(res, "access_grant", "/access_grants/unmanaged/get") + ) @route_metadata( path="/access_grants/unmanaged/list", @@ -215,7 +219,12 @@ def list( res = self.client.get("/access_grants/unmanaged/list", params=params) - return [UnmanagedAccessGrant.from_dict(item) for item in res["access_grants"]] + return [ + UnmanagedAccessGrant.from_dict(item) + for item in unwrap_list( + res, "access_grants", "/access_grants/unmanaged/list" + ) + ] @route_metadata( path="/access_grants/unmanaged/update", @@ -291,7 +300,9 @@ async def get(self, *, access_grant_id: str) -> UnmanagedAccessGrant: res = await self.client.get("/access_grants/unmanaged/get", params=params) - return UnmanagedAccessGrant.from_dict(res["access_grant"]) + return UnmanagedAccessGrant.from_dict( + unwrap(res, "access_grant", "/access_grants/unmanaged/get") + ) @route_metadata( path="/access_grants/unmanaged/list", @@ -340,7 +351,12 @@ async def list( res = await self.client.get("/access_grants/unmanaged/list", params=params) - return [UnmanagedAccessGrant.from_dict(item) for item in res["access_grants"]] + return [ + UnmanagedAccessGrant.from_dict(item) + for item in unwrap_list( + res, "access_grants", "/access_grants/unmanaged/list" + ) + ] @route_metadata( path="/access_grants/unmanaged/update", diff --git a/seam/routes/access_methods.py b/seam/routes/access_methods.py index 2d447f97..db946621 100644 --- a/seam/routes/access_methods.py +++ b/seam/routes/access_methods.py @@ -14,6 +14,8 @@ resolve_action_attempt, resolve_action_attempt_async, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractAccessMethods(abc.ABC): @@ -439,7 +441,9 @@ def assign_card( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/access_methods/assign_card") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -527,7 +531,9 @@ def encode( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/access_methods/encode") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -554,7 +560,9 @@ def get(self, *, access_method_id: str) -> AccessMethod: res = self.client.get("/access_methods/get", params=params) - return AccessMethod.from_dict(res["access_method"]) + return AccessMethod.from_dict( + unwrap(res, "access_method", "/access_methods/get") + ) @route_metadata( path="/access_methods/get_related", @@ -621,7 +629,7 @@ def get_related( res = self.client.get("/access_methods/get_related", params=params) - return Batch.from_dict(res["batch"]) + return Batch.from_dict(unwrap(res, "batch", "/access_methods/get_related")) @route_metadata( path="/access_methods/list", has_required_parameters=True, has_pagination=True @@ -685,7 +693,10 @@ def list( res = self.client.get("/access_methods/list", params=params) - return [AccessMethod.from_dict(item) for item in res["access_methods"]] + return [ + AccessMethod.from_dict(item) + for item in unwrap_list(res, "access_methods", "/access_methods/list") + ] @route_metadata( path="/access_methods/unlock_door", @@ -732,7 +743,9 @@ def unlock_door( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/access_methods/unlock_door") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -792,7 +805,9 @@ async def assign_card( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/access_methods/assign_card") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -880,7 +895,9 @@ async def encode( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/access_methods/encode") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -907,7 +924,9 @@ async def get(self, *, access_method_id: str) -> AccessMethod: res = await self.client.get("/access_methods/get", params=params) - return AccessMethod.from_dict(res["access_method"]) + return AccessMethod.from_dict( + unwrap(res, "access_method", "/access_methods/get") + ) @route_metadata( path="/access_methods/get_related", @@ -974,7 +993,7 @@ async def get_related( res = await self.client.get("/access_methods/get_related", params=params) - return Batch.from_dict(res["batch"]) + return Batch.from_dict(unwrap(res, "batch", "/access_methods/get_related")) @route_metadata( path="/access_methods/list", has_required_parameters=True, has_pagination=True @@ -1038,7 +1057,10 @@ async def list( res = await self.client.get("/access_methods/list", params=params) - return [AccessMethod.from_dict(item) for item in res["access_methods"]] + return [ + AccessMethod.from_dict(item) + for item in unwrap_list(res, "access_methods", "/access_methods/list") + ] @route_metadata( path="/access_methods/unlock_door", @@ -1085,6 +1107,8 @@ async def unlock_door( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/access_methods/unlock_door") + ), wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/access_methods_unmanaged.py b/seam/routes/access_methods_unmanaged.py index 9fd5315c..5725a396 100644 --- a/seam/routes/access_methods_unmanaged.py +++ b/seam/routes/access_methods_unmanaged.py @@ -3,6 +3,8 @@ from ..client import SeamHttpClient, AsyncSeamHttpClient from ..route import route_metadata from ..resources import UnmanagedAccessMethod +from ..response import unwrap +from ..response import unwrap_list class AbstractAccessMethodsUnmanaged(abc.ABC): @@ -111,7 +113,9 @@ def get(self, *, access_method_id: str) -> UnmanagedAccessMethod: res = self.client.get("/access_methods/unmanaged/get", params=params) - return UnmanagedAccessMethod.from_dict(res["access_method"]) + return UnmanagedAccessMethod.from_dict( + unwrap(res, "access_method", "/access_methods/unmanaged/get") + ) @route_metadata( path="/access_methods/unmanaged/list", @@ -157,7 +161,12 @@ def list( res = self.client.get("/access_methods/unmanaged/list", params=params) - return [UnmanagedAccessMethod.from_dict(item) for item in res["access_methods"]] + return [ + UnmanagedAccessMethod.from_dict(item) + for item in unwrap_list( + res, "access_methods", "/access_methods/unmanaged/list" + ) + ] class AsyncAccessMethodsUnmanaged(AbstractAsyncAccessMethodsUnmanaged): @@ -190,7 +199,9 @@ async def get(self, *, access_method_id: str) -> UnmanagedAccessMethod: res = await self.client.get("/access_methods/unmanaged/get", params=params) - return UnmanagedAccessMethod.from_dict(res["access_method"]) + return UnmanagedAccessMethod.from_dict( + unwrap(res, "access_method", "/access_methods/unmanaged/get") + ) @route_metadata( path="/access_methods/unmanaged/list", @@ -236,4 +247,9 @@ async def list( res = await self.client.get("/access_methods/unmanaged/list", params=params) - return [UnmanagedAccessMethod.from_dict(item) for item in res["access_methods"]] + return [ + UnmanagedAccessMethod.from_dict(item) + for item in unwrap_list( + res, "access_methods", "/access_methods/unmanaged/list" + ) + ] diff --git a/seam/routes/acs_access_groups.py b/seam/routes/acs_access_groups.py index 507e7196..4a7b2bf8 100644 --- a/seam/routes/acs_access_groups.py +++ b/seam/routes/acs_access_groups.py @@ -3,6 +3,8 @@ from ..client import SeamHttpClient, AsyncSeamHttpClient from ..route import route_metadata from ..resources import AcsAccessGroup, AcsEntrance, AcsUser +from ..response import unwrap +from ..response import unwrap_list class AbstractAcsAccessGroups(abc.ABC): @@ -313,7 +315,9 @@ def get(self, *, acs_access_group_id: str) -> AcsAccessGroup: res = self.client.get("/acs/access_groups/get", params=params) - return AcsAccessGroup.from_dict(res["acs_access_group"]) + return AcsAccessGroup.from_dict( + unwrap(res, "acs_access_group", "/acs/access_groups/get") + ) @route_metadata( path="/acs/access_groups/list", @@ -352,7 +356,10 @@ def list( res = self.client.get("/acs/access_groups/list", params=params) - return [AcsAccessGroup.from_dict(item) for item in res["acs_access_groups"]] + return [ + AcsAccessGroup.from_dict(item) + for item in unwrap_list(res, "acs_access_groups", "/acs/access_groups/list") + ] @route_metadata( path="/acs/access_groups/list_accessible_entrances", @@ -383,7 +390,12 @@ def list_accessible_entrances( "/acs/access_groups/list_accessible_entrances", params=params ) - return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] + return [ + AcsEntrance.from_dict(item) + for item in unwrap_list( + res, "acs_entrances", "/acs/access_groups/list_accessible_entrances" + ) + ] @route_metadata( path="/acs/access_groups/list_users", @@ -410,7 +422,10 @@ def list_users(self, *, acs_access_group_id: str) -> List[AcsUser]: res = self.client.get("/acs/access_groups/list_users", params=params) - return [AcsUser.from_dict(item) for item in res["acs_users"]] + return [ + AcsUser.from_dict(item) + for item in unwrap_list(res, "acs_users", "/acs/access_groups/list_users") + ] @route_metadata( path="/acs/access_groups/remove_user", @@ -546,7 +561,9 @@ async def get(self, *, acs_access_group_id: str) -> AcsAccessGroup: res = await self.client.get("/acs/access_groups/get", params=params) - return AcsAccessGroup.from_dict(res["acs_access_group"]) + return AcsAccessGroup.from_dict( + unwrap(res, "acs_access_group", "/acs/access_groups/get") + ) @route_metadata( path="/acs/access_groups/list", @@ -585,7 +602,10 @@ async def list( res = await self.client.get("/acs/access_groups/list", params=params) - return [AcsAccessGroup.from_dict(item) for item in res["acs_access_groups"]] + return [ + AcsAccessGroup.from_dict(item) + for item in unwrap_list(res, "acs_access_groups", "/acs/access_groups/list") + ] @route_metadata( path="/acs/access_groups/list_accessible_entrances", @@ -616,7 +636,12 @@ async def list_accessible_entrances( "/acs/access_groups/list_accessible_entrances", params=params ) - return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] + return [ + AcsEntrance.from_dict(item) + for item in unwrap_list( + res, "acs_entrances", "/acs/access_groups/list_accessible_entrances" + ) + ] @route_metadata( path="/acs/access_groups/list_users", @@ -643,7 +668,10 @@ async def list_users(self, *, acs_access_group_id: str) -> List[AcsUser]: res = await self.client.get("/acs/access_groups/list_users", params=params) - return [AcsUser.from_dict(item) for item in res["acs_users"]] + return [ + AcsUser.from_dict(item) + for item in unwrap_list(res, "acs_users", "/acs/access_groups/list_users") + ] @route_metadata( path="/acs/access_groups/remove_user", diff --git a/seam/routes/acs_credentials.py b/seam/routes/acs_credentials.py index 398f6874..3acfbcc8 100644 --- a/seam/routes/acs_credentials.py +++ b/seam/routes/acs_credentials.py @@ -4,6 +4,8 @@ from ..route import route_metadata from ..null import Null from ..resources import AcsCredential, AcsEntrance +from ..response import unwrap +from ..response import unwrap_list class AbstractAcsCredentials(abc.ABC): @@ -497,7 +499,9 @@ def create( res = self.client.post("/acs/credentials/create", json=json_payload) - return AcsCredential.from_dict(res["acs_credential"]) + return AcsCredential.from_dict( + unwrap(res, "acs_credential", "/acs/credentials/create") + ) @route_metadata( path="/acs/credentials/delete", @@ -547,7 +551,9 @@ def get(self, *, acs_credential_id: str) -> AcsCredential: res = self.client.get("/acs/credentials/get", params=params) - return AcsCredential.from_dict(res["acs_credential"]) + return AcsCredential.from_dict( + unwrap(res, "acs_credential", "/acs/credentials/get") + ) @route_metadata( path="/acs/credentials/list", has_required_parameters=False, has_pagination=True @@ -604,7 +610,10 @@ def list( res = self.client.get("/acs/credentials/list", params=params) - return [AcsCredential.from_dict(item) for item in res["acs_credentials"]] + return [ + AcsCredential.from_dict(item) + for item in unwrap_list(res, "acs_credentials", "/acs/credentials/list") + ] @route_metadata( path="/acs/credentials/list_accessible_entrances", @@ -633,7 +642,12 @@ def list_accessible_entrances(self, *, acs_credential_id: str) -> List[AcsEntran "/acs/credentials/list_accessible_entrances", params=params ) - return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] + return [ + AcsEntrance.from_dict(item) + for item in unwrap_list( + res, "acs_entrances", "/acs/credentials/list_accessible_entrances" + ) + ] @route_metadata( path="/acs/credentials/unassign", @@ -851,7 +865,9 @@ async def create( res = await self.client.post("/acs/credentials/create", json=json_payload) - return AcsCredential.from_dict(res["acs_credential"]) + return AcsCredential.from_dict( + unwrap(res, "acs_credential", "/acs/credentials/create") + ) @route_metadata( path="/acs/credentials/delete", @@ -901,7 +917,9 @@ async def get(self, *, acs_credential_id: str) -> AcsCredential: res = await self.client.get("/acs/credentials/get", params=params) - return AcsCredential.from_dict(res["acs_credential"]) + return AcsCredential.from_dict( + unwrap(res, "acs_credential", "/acs/credentials/get") + ) @route_metadata( path="/acs/credentials/list", has_required_parameters=False, has_pagination=True @@ -958,7 +976,10 @@ async def list( res = await self.client.get("/acs/credentials/list", params=params) - return [AcsCredential.from_dict(item) for item in res["acs_credentials"]] + return [ + AcsCredential.from_dict(item) + for item in unwrap_list(res, "acs_credentials", "/acs/credentials/list") + ] @route_metadata( path="/acs/credentials/list_accessible_entrances", @@ -989,7 +1010,12 @@ async def list_accessible_entrances( "/acs/credentials/list_accessible_entrances", params=params ) - return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] + return [ + AcsEntrance.from_dict(item) + for item in unwrap_list( + res, "acs_entrances", "/acs/credentials/list_accessible_entrances" + ) + ] @route_metadata( path="/acs/credentials/unassign", diff --git a/seam/routes/acs_encoders.py b/seam/routes/acs_encoders.py index 8fca3e02..3482b2e1 100644 --- a/seam/routes/acs_encoders.py +++ b/seam/routes/acs_encoders.py @@ -14,6 +14,8 @@ resolve_action_attempt, resolve_action_attempt_async, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractAcsEncoders(abc.ABC): @@ -308,7 +310,9 @@ def encode_credential( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/acs/encoders/encode_credential") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -333,7 +337,7 @@ def get(self, *, acs_encoder_id: str) -> AcsEncoder: res = self.client.get("/acs/encoders/get", params=params) - return AcsEncoder.from_dict(res["acs_encoder"]) + return AcsEncoder.from_dict(unwrap(res, "acs_encoder", "/acs/encoders/get")) @route_metadata( path="/acs/encoders/list", has_required_parameters=False, has_pagination=True @@ -375,7 +379,10 @@ def list( res = self.client.get("/acs/encoders/list", params=params) - return [AcsEncoder.from_dict(item) for item in res["acs_encoders"]] + return [ + AcsEncoder.from_dict(item) + for item in unwrap_list(res, "acs_encoders", "/acs/encoders/list") + ] @route_metadata( path="/acs/encoders/scan_credential", @@ -422,7 +429,9 @@ def scan_credential( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/acs/encoders/scan_credential") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -483,7 +492,9 @@ def scan_to_assign_credential( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/acs/encoders/scan_to_assign_credential") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -550,7 +561,9 @@ async def encode_credential( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/acs/encoders/encode_credential") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -575,7 +588,7 @@ async def get(self, *, acs_encoder_id: str) -> AcsEncoder: res = await self.client.get("/acs/encoders/get", params=params) - return AcsEncoder.from_dict(res["acs_encoder"]) + return AcsEncoder.from_dict(unwrap(res, "acs_encoder", "/acs/encoders/get")) @route_metadata( path="/acs/encoders/list", has_required_parameters=False, has_pagination=True @@ -617,7 +630,10 @@ async def list( res = await self.client.get("/acs/encoders/list", params=params) - return [AcsEncoder.from_dict(item) for item in res["acs_encoders"]] + return [ + AcsEncoder.from_dict(item) + for item in unwrap_list(res, "acs_encoders", "/acs/encoders/list") + ] @route_metadata( path="/acs/encoders/scan_credential", @@ -664,7 +680,9 @@ async def scan_credential( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/acs/encoders/scan_credential") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -725,6 +743,8 @@ async def scan_to_assign_credential( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/acs/encoders/scan_to_assign_credential") + ), wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/acs_entrances.py b/seam/routes/acs_entrances.py index 173b1491..8f233493 100644 --- a/seam/routes/acs_entrances.py +++ b/seam/routes/acs_entrances.py @@ -13,6 +13,8 @@ resolve_action_attempt, resolve_action_attempt_async, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractAcsEntrances(abc.ABC): @@ -273,7 +275,7 @@ def get(self, *, acs_entrance_id: str) -> AcsEntrance: res = self.client.get("/acs/entrances/get", params=params) - return AcsEntrance.from_dict(res["acs_entrance"]) + return AcsEntrance.from_dict(unwrap(res, "acs_entrance", "/acs/entrances/get")) @route_metadata( path="/acs/entrances/grant_access", @@ -384,7 +386,10 @@ def list( res = self.client.get("/acs/entrances/list", params=params) - return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] + return [ + AcsEntrance.from_dict(item) + for item in unwrap_list(res, "acs_entrances", "/acs/entrances/list") + ] @route_metadata( path="/acs/entrances/list_credentials_with_access", @@ -422,7 +427,12 @@ def list_credentials_with_access( "/acs/entrances/list_credentials_with_access", params=params ) - return [AcsCredential.from_dict(item) for item in res["acs_credentials"]] + return [ + AcsCredential.from_dict(item) + for item in unwrap_list( + res, "acs_credentials", "/acs/entrances/list_credentials_with_access" + ) + ] @route_metadata( path="/acs/entrances/unlock", has_required_parameters=True, has_pagination=False @@ -467,7 +477,9 @@ def unlock( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/acs/entrances/unlock") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -500,7 +512,7 @@ async def get(self, *, acs_entrance_id: str) -> AcsEntrance: res = await self.client.get("/acs/entrances/get", params=params) - return AcsEntrance.from_dict(res["acs_entrance"]) + return AcsEntrance.from_dict(unwrap(res, "acs_entrance", "/acs/entrances/get")) @route_metadata( path="/acs/entrances/grant_access", @@ -611,7 +623,10 @@ async def list( res = await self.client.get("/acs/entrances/list", params=params) - return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] + return [ + AcsEntrance.from_dict(item) + for item in unwrap_list(res, "acs_entrances", "/acs/entrances/list") + ] @route_metadata( path="/acs/entrances/list_credentials_with_access", @@ -649,7 +664,12 @@ async def list_credentials_with_access( "/acs/entrances/list_credentials_with_access", params=params ) - return [AcsCredential.from_dict(item) for item in res["acs_credentials"]] + return [ + AcsCredential.from_dict(item) + for item in unwrap_list( + res, "acs_credentials", "/acs/entrances/list_credentials_with_access" + ) + ] @route_metadata( path="/acs/entrances/unlock", has_required_parameters=True, has_pagination=False @@ -694,6 +714,8 @@ async def unlock( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/acs/entrances/unlock") + ), wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/acs_systems.py b/seam/routes/acs_systems.py index 58474d02..fb102eb9 100644 --- a/seam/routes/acs_systems.py +++ b/seam/routes/acs_systems.py @@ -3,6 +3,8 @@ from ..client import SeamHttpClient, AsyncSeamHttpClient from ..route import route_metadata from ..resources import AcsSystem +from ..response import unwrap +from ..response import unwrap_list class AbstractAcsSystems(abc.ABC): @@ -169,7 +171,7 @@ def get(self, *, acs_system_id: str) -> AcsSystem: res = self.client.get("/acs/systems/get", params=params) - return AcsSystem.from_dict(res["acs_system"]) + return AcsSystem.from_dict(unwrap(res, "acs_system", "/acs/systems/get")) @route_metadata( path="/acs/systems/list", has_required_parameters=False, has_pagination=False @@ -203,7 +205,10 @@ def list( res = self.client.get("/acs/systems/list", params=params) - return [AcsSystem.from_dict(item) for item in res["acs_systems"]] + return [ + AcsSystem.from_dict(item) + for item in unwrap_list(res, "acs_systems", "/acs/systems/list") + ] @route_metadata( path="/acs/systems/list_compatible_credential_manager_acs_systems", @@ -236,7 +241,14 @@ def list_compatible_credential_manager_acs_systems( "/acs/systems/list_compatible_credential_manager_acs_systems", params=params ) - return [AcsSystem.from_dict(item) for item in res["acs_systems"]] + return [ + AcsSystem.from_dict(item) + for item in unwrap_list( + res, + "acs_systems", + "/acs/systems/list_compatible_credential_manager_acs_systems", + ) + ] @route_metadata( path="/acs/systems/report_devices", @@ -304,7 +316,7 @@ async def get(self, *, acs_system_id: str) -> AcsSystem: res = await self.client.get("/acs/systems/get", params=params) - return AcsSystem.from_dict(res["acs_system"]) + return AcsSystem.from_dict(unwrap(res, "acs_system", "/acs/systems/get")) @route_metadata( path="/acs/systems/list", has_required_parameters=False, has_pagination=False @@ -338,7 +350,10 @@ async def list( res = await self.client.get("/acs/systems/list", params=params) - return [AcsSystem.from_dict(item) for item in res["acs_systems"]] + return [ + AcsSystem.from_dict(item) + for item in unwrap_list(res, "acs_systems", "/acs/systems/list") + ] @route_metadata( path="/acs/systems/list_compatible_credential_manager_acs_systems", @@ -371,7 +386,14 @@ async def list_compatible_credential_manager_acs_systems( "/acs/systems/list_compatible_credential_manager_acs_systems", params=params ) - return [AcsSystem.from_dict(item) for item in res["acs_systems"]] + return [ + AcsSystem.from_dict(item) + for item in unwrap_list( + res, + "acs_systems", + "/acs/systems/list_compatible_credential_manager_acs_systems", + ) + ] @route_metadata( path="/acs/systems/report_devices", diff --git a/seam/routes/acs_users.py b/seam/routes/acs_users.py index 2bafe822..6ef67c5f 100644 --- a/seam/routes/acs_users.py +++ b/seam/routes/acs_users.py @@ -4,6 +4,8 @@ from ..route import route_metadata from ..null import Null from ..resources import AcsUser, AcsEntrance +from ..response import unwrap +from ..response import unwrap_list class AbstractAcsUsers(abc.ABC): @@ -622,7 +624,7 @@ def create( res = self.client.post("/acs/users/create", json=json_payload) - return AcsUser.from_dict(res["acs_user"]) + return AcsUser.from_dict(unwrap(res, "acs_user", "/acs/users/create")) @route_metadata( path="/acs/users/delete", has_required_parameters=True, has_pagination=False @@ -694,7 +696,7 @@ def get( res = self.client.get("/acs/users/get", params=params) - return AcsUser.from_dict(res["acs_user"]) + return AcsUser.from_dict(unwrap(res, "acs_user", "/acs/users/get")) @route_metadata( path="/acs/users/list", has_required_parameters=False, has_pagination=True @@ -751,7 +753,10 @@ def list( res = self.client.get("/acs/users/list", params=params) - return [AcsUser.from_dict(item) for item in res["acs_users"]] + return [ + AcsUser.from_dict(item) + for item in unwrap_list(res, "acs_users", "/acs/users/list") + ] @route_metadata( path="/acs/users/list_accessible_entrances", @@ -792,7 +797,12 @@ def list_accessible_entrances( res = self.client.get("/acs/users/list_accessible_entrances", params=params) - return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] + return [ + AcsEntrance.from_dict(item) + for item in unwrap_list( + res, "acs_entrances", "/acs/users/list_accessible_entrances" + ) + ] @route_metadata( path="/acs/users/remove_from_access_group", @@ -1108,7 +1118,7 @@ async def create( res = await self.client.post("/acs/users/create", json=json_payload) - return AcsUser.from_dict(res["acs_user"]) + return AcsUser.from_dict(unwrap(res, "acs_user", "/acs/users/create")) @route_metadata( path="/acs/users/delete", has_required_parameters=True, has_pagination=False @@ -1180,7 +1190,7 @@ async def get( res = await self.client.get("/acs/users/get", params=params) - return AcsUser.from_dict(res["acs_user"]) + return AcsUser.from_dict(unwrap(res, "acs_user", "/acs/users/get")) @route_metadata( path="/acs/users/list", has_required_parameters=False, has_pagination=True @@ -1237,7 +1247,10 @@ async def list( res = await self.client.get("/acs/users/list", params=params) - return [AcsUser.from_dict(item) for item in res["acs_users"]] + return [ + AcsUser.from_dict(item) + for item in unwrap_list(res, "acs_users", "/acs/users/list") + ] @route_metadata( path="/acs/users/list_accessible_entrances", @@ -1280,7 +1293,12 @@ async def list_accessible_entrances( "/acs/users/list_accessible_entrances", params=params ) - return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] + return [ + AcsEntrance.from_dict(item) + for item in unwrap_list( + res, "acs_entrances", "/acs/users/list_accessible_entrances" + ) + ] @route_metadata( path="/acs/users/remove_from_access_group", diff --git a/seam/routes/action_attempts.py b/seam/routes/action_attempts.py index 771f6b08..aace2c14 100644 --- a/seam/routes/action_attempts.py +++ b/seam/routes/action_attempts.py @@ -8,6 +8,8 @@ resolve_action_attempt, resolve_action_attempt_async, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractActionAttempts(abc.ABC): @@ -139,7 +141,9 @@ def get( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/action_attempts/get") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -178,7 +182,10 @@ def list( res = self.client.get("/action_attempts/list", params=params) - return [action_attempt_from_dict(item) for item in res["action_attempts"]] + return [ + action_attempt_from_dict(item) + for item in unwrap_list(res, "action_attempts", "/action_attempts/list") + ] class AsyncActionAttempts(AbstractAsyncActionAttempts): @@ -224,7 +231,9 @@ async def get( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/action_attempts/get") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -263,4 +272,7 @@ async def list( res = await self.client.get("/action_attempts/list", params=params) - return [action_attempt_from_dict(item) for item in res["action_attempts"]] + return [ + action_attempt_from_dict(item) + for item in unwrap_list(res, "action_attempts", "/action_attempts/list") + ] diff --git a/seam/routes/client_sessions.py b/seam/routes/client_sessions.py index d57b1417..70ecf5cb 100644 --- a/seam/routes/client_sessions.py +++ b/seam/routes/client_sessions.py @@ -4,6 +4,8 @@ from ..route import route_metadata from ..null import Null from ..resources import ClientSession +from ..response import unwrap +from ..response import unwrap_list class AbstractClientSessions(abc.ABC): @@ -376,7 +378,9 @@ def create( res = self.client.put("/client_sessions/create", json=json_payload) - return ClientSession.from_dict(res["client_session"]) + return ClientSession.from_dict( + unwrap(res, "client_session", "/client_sessions/create") + ) @route_metadata( path="/client_sessions/delete", @@ -428,7 +432,9 @@ def get( res = self.client.get("/client_sessions/get", params=params) - return ClientSession.from_dict(res["client_session"]) + return ClientSession.from_dict( + unwrap(res, "client_session", "/client_sessions/get") + ) @route_metadata( path="/client_sessions/get_or_create", @@ -477,7 +483,9 @@ def get_or_create( res = self.client.post("/client_sessions/get_or_create", json=json_payload) - return ClientSession.from_dict(res["client_session"]) + return ClientSession.from_dict( + unwrap(res, "client_session", "/client_sessions/get_or_create") + ) @route_metadata( path="/client_sessions/grant_access", @@ -575,7 +583,10 @@ def list( res = self.client.get("/client_sessions/list", params=params) - return [ClientSession.from_dict(item) for item in res["client_sessions"]] + return [ + ClientSession.from_dict(item) + for item in unwrap_list(res, "client_sessions", "/client_sessions/list") + ] @route_metadata( path="/client_sessions/revoke", @@ -667,7 +678,9 @@ async def create( res = await self.client.put("/client_sessions/create", json=json_payload) - return ClientSession.from_dict(res["client_session"]) + return ClientSession.from_dict( + unwrap(res, "client_session", "/client_sessions/create") + ) @route_metadata( path="/client_sessions/delete", @@ -719,7 +732,9 @@ async def get( res = await self.client.get("/client_sessions/get", params=params) - return ClientSession.from_dict(res["client_session"]) + return ClientSession.from_dict( + unwrap(res, "client_session", "/client_sessions/get") + ) @route_metadata( path="/client_sessions/get_or_create", @@ -770,7 +785,9 @@ async def get_or_create( "/client_sessions/get_or_create", json=json_payload ) - return ClientSession.from_dict(res["client_session"]) + return ClientSession.from_dict( + unwrap(res, "client_session", "/client_sessions/get_or_create") + ) @route_metadata( path="/client_sessions/grant_access", @@ -868,7 +885,10 @@ async def list( res = await self.client.get("/client_sessions/list", params=params) - return [ClientSession.from_dict(item) for item in res["client_sessions"]] + return [ + ClientSession.from_dict(item) + for item in unwrap_list(res, "client_sessions", "/client_sessions/list") + ] @route_metadata( path="/client_sessions/revoke", diff --git a/seam/routes/connect_webviews.py b/seam/routes/connect_webviews.py index 7ba06356..6ff45877 100644 --- a/seam/routes/connect_webviews.py +++ b/seam/routes/connect_webviews.py @@ -4,6 +4,8 @@ from ..route import route_metadata from ..null import Null from ..resources import ConnectWebview +from ..response import unwrap +from ..response import unwrap_list class AbstractConnectWebviews(abc.ABC): @@ -561,7 +563,9 @@ def create( res = self.client.post("/connect_webviews/create", json=json_payload) - return ConnectWebview.from_dict(res["connect_webview"]) + return ConnectWebview.from_dict( + unwrap(res, "connect_webview", "/connect_webviews/create") + ) @route_metadata( path="/connect_webviews/delete", @@ -615,7 +619,9 @@ def get(self, *, connect_webview_id: str) -> ConnectWebview: res = self.client.get("/connect_webviews/get", params=params) - return ConnectWebview.from_dict(res["connect_webview"]) + return ConnectWebview.from_dict( + unwrap(res, "connect_webview", "/connect_webviews/get") + ) @route_metadata( path="/connect_webviews/list", @@ -664,7 +670,10 @@ def list( res = self.client.get("/connect_webviews/list", params=params) - return [ConnectWebview.from_dict(item) for item in res["connect_webviews"]] + return [ + ConnectWebview.from_dict(item) + for item in unwrap_list(res, "connect_webviews", "/connect_webviews/list") + ] class AsyncConnectWebviews(AbstractAsyncConnectWebviews): @@ -838,7 +847,9 @@ async def create( res = await self.client.post("/connect_webviews/create", json=json_payload) - return ConnectWebview.from_dict(res["connect_webview"]) + return ConnectWebview.from_dict( + unwrap(res, "connect_webview", "/connect_webviews/create") + ) @route_metadata( path="/connect_webviews/delete", @@ -892,7 +903,9 @@ async def get(self, *, connect_webview_id: str) -> ConnectWebview: res = await self.client.get("/connect_webviews/get", params=params) - return ConnectWebview.from_dict(res["connect_webview"]) + return ConnectWebview.from_dict( + unwrap(res, "connect_webview", "/connect_webviews/get") + ) @route_metadata( path="/connect_webviews/list", @@ -941,4 +954,7 @@ async def list( res = await self.client.get("/connect_webviews/list", params=params) - return [ConnectWebview.from_dict(item) for item in res["connect_webviews"]] + return [ + ConnectWebview.from_dict(item) + for item in unwrap_list(res, "connect_webviews", "/connect_webviews/list") + ] diff --git a/seam/routes/connected_accounts.py b/seam/routes/connected_accounts.py index 50530e8f..1921ff14 100644 --- a/seam/routes/connected_accounts.py +++ b/seam/routes/connected_accounts.py @@ -10,6 +10,8 @@ AbstractAsyncConnectedAccountsSimulate, AsyncConnectedAccountsSimulate, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractConnectedAccounts(abc.ABC): @@ -302,7 +304,9 @@ def get( res = self.client.get("/connected_accounts/get", params=params) - return ConnectedAccount.from_dict(res["connected_account"]) + return ConnectedAccount.from_dict( + unwrap(res, "connected_account", "/connected_accounts/get") + ) @route_metadata( path="/connected_accounts/list", @@ -356,7 +360,12 @@ def list( res = self.client.get("/connected_accounts/list", params=params) - return [ConnectedAccount.from_dict(item) for item in res["connected_accounts"]] + return [ + ConnectedAccount.from_dict(item) + for item in unwrap_list( + res, "connected_accounts", "/connected_accounts/list" + ) + ] @route_metadata( path="/connected_accounts/sync", @@ -518,7 +527,9 @@ async def get( res = await self.client.get("/connected_accounts/get", params=params) - return ConnectedAccount.from_dict(res["connected_account"]) + return ConnectedAccount.from_dict( + unwrap(res, "connected_account", "/connected_accounts/get") + ) @route_metadata( path="/connected_accounts/list", @@ -572,7 +583,12 @@ async def list( res = await self.client.get("/connected_accounts/list", params=params) - return [ConnectedAccount.from_dict(item) for item in res["connected_accounts"]] + return [ + ConnectedAccount.from_dict(item) + for item in unwrap_list( + res, "connected_accounts", "/connected_accounts/list" + ) + ] @route_metadata( path="/connected_accounts/sync", diff --git a/seam/routes/customers.py b/seam/routes/customers.py index 9343a110..8cf79c34 100644 --- a/seam/routes/customers.py +++ b/seam/routes/customers.py @@ -3,6 +3,7 @@ from ..client import SeamHttpClient, AsyncSeamHttpClient from ..route import route_metadata from ..resources import CustomerPortal +from ..response import unwrap class AbstractCustomers(abc.ABC): @@ -485,7 +486,9 @@ def create_portal( res = self.client.post("/customers/create_portal", json=json_payload) - return CustomerPortal.from_dict(res["customer_portal"]) + return CustomerPortal.from_dict( + unwrap(res, "customer_portal", "/customers/create_portal") + ) @route_metadata( path="/customers/delete_data", @@ -813,7 +816,9 @@ async def create_portal( res = await self.client.post("/customers/create_portal", json=json_payload) - return CustomerPortal.from_dict(res["customer_portal"]) + return CustomerPortal.from_dict( + unwrap(res, "customer_portal", "/customers/create_portal") + ) @route_metadata( path="/customers/delete_data", diff --git a/seam/routes/devices.py b/seam/routes/devices.py index 55f80df0..150b3540 100644 --- a/seam/routes/devices.py +++ b/seam/routes/devices.py @@ -16,6 +16,8 @@ AbstractAsyncDevicesUnmanaged, AsyncDevicesUnmanaged, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractDevices(abc.ABC): @@ -670,7 +672,7 @@ def get( res = self.client.get("/devices/get", params=params) - return Device.from_dict(res["device"]) + return Device.from_dict(unwrap(res, "device", "/devices/get")) @route_metadata( path="/devices/list", has_required_parameters=False, has_pagination=True @@ -916,7 +918,10 @@ def list( res = self.client.get("/devices/list", params=params) - return [Device.from_dict(item) for item in res["devices"]] + return [ + Device.from_dict(item) + for item in unwrap_list(res, "devices", "/devices/list") + ] @route_metadata( path="/devices/list_device_providers", @@ -955,7 +960,12 @@ def list_device_providers( res = self.client.get("/devices/list_device_providers", params=params) - return [DeviceProvider.from_dict(item) for item in res["device_providers"]] + return [ + DeviceProvider.from_dict(item) + for item in unwrap_list( + res, "device_providers", "/devices/list_device_providers" + ) + ] @route_metadata( path="/devices/report_provider_metadata", @@ -1081,7 +1091,7 @@ async def get( res = await self.client.get("/devices/get", params=params) - return Device.from_dict(res["device"]) + return Device.from_dict(unwrap(res, "device", "/devices/get")) @route_metadata( path="/devices/list", has_required_parameters=False, has_pagination=True @@ -1327,7 +1337,10 @@ async def list( res = await self.client.get("/devices/list", params=params) - return [Device.from_dict(item) for item in res["devices"]] + return [ + Device.from_dict(item) + for item in unwrap_list(res, "devices", "/devices/list") + ] @route_metadata( path="/devices/list_device_providers", @@ -1366,7 +1379,12 @@ async def list_device_providers( res = await self.client.get("/devices/list_device_providers", params=params) - return [DeviceProvider.from_dict(item) for item in res["device_providers"]] + return [ + DeviceProvider.from_dict(item) + for item in unwrap_list( + res, "device_providers", "/devices/list_device_providers" + ) + ] @route_metadata( path="/devices/report_provider_metadata", diff --git a/seam/routes/devices_unmanaged.py b/seam/routes/devices_unmanaged.py index d73348ce..c8bf106d 100644 --- a/seam/routes/devices_unmanaged.py +++ b/seam/routes/devices_unmanaged.py @@ -4,6 +4,8 @@ from ..route import route_metadata from ..null import Null from ..resources import UnmanagedDevice +from ..response import unwrap +from ..response import unwrap_list class AbstractDevicesUnmanaged(abc.ABC): @@ -526,7 +528,9 @@ def get( res = self.client.get("/devices/unmanaged/get", params=params) - return UnmanagedDevice.from_dict(res["device"]) + return UnmanagedDevice.from_dict( + unwrap(res, "device", "/devices/unmanaged/get") + ) @route_metadata( path="/devices/unmanaged/list", @@ -756,7 +760,10 @@ def list( res = self.client.get("/devices/unmanaged/list", params=params) - return [UnmanagedDevice.from_dict(item) for item in res["devices"]] + return [ + UnmanagedDevice.from_dict(item) + for item in unwrap_list(res, "devices", "/devices/unmanaged/list") + ] @route_metadata( path="/devices/unmanaged/update", @@ -840,7 +847,9 @@ async def get( res = await self.client.get("/devices/unmanaged/get", params=params) - return UnmanagedDevice.from_dict(res["device"]) + return UnmanagedDevice.from_dict( + unwrap(res, "device", "/devices/unmanaged/get") + ) @route_metadata( path="/devices/unmanaged/list", @@ -1070,7 +1079,10 @@ async def list( res = await self.client.get("/devices/unmanaged/list", params=params) - return [UnmanagedDevice.from_dict(item) for item in res["devices"]] + return [ + UnmanagedDevice.from_dict(item) + for item in unwrap_list(res, "devices", "/devices/unmanaged/list") + ] @route_metadata( path="/devices/unmanaged/update", diff --git a/seam/routes/events.py b/seam/routes/events.py index 4250914c..1374d36d 100644 --- a/seam/routes/events.py +++ b/seam/routes/events.py @@ -3,6 +3,8 @@ from ..client import SeamHttpClient, AsyncSeamHttpClient from ..route import route_metadata from ..resources import SeamEvent, seam_event_from_dict +from ..response import unwrap +from ..response import unwrap_list class AbstractEvents(abc.ABC): @@ -757,7 +759,7 @@ def get( res = self.client.get("/events/get", params=params) - return seam_event_from_dict(res["event"]) + return seam_event_from_dict(unwrap(res, "event", "/events/get")) @route_metadata( path="/events/list", has_required_parameters=True, has_pagination=False @@ -1155,7 +1157,10 @@ def list( res = self.client.get("/events/list", params=params) - return [seam_event_from_dict(item) for item in res["events"]] + return [ + seam_event_from_dict(item) + for item in unwrap_list(res, "events", "/events/list") + ] class AsyncEvents(AbstractAsyncEvents): @@ -1198,7 +1203,7 @@ async def get( res = await self.client.get("/events/get", params=params) - return seam_event_from_dict(res["event"]) + return seam_event_from_dict(unwrap(res, "event", "/events/get")) @route_metadata( path="/events/list", has_required_parameters=True, has_pagination=False @@ -1596,4 +1601,7 @@ async def list( res = await self.client.get("/events/list", params=params) - return [seam_event_from_dict(item) for item in res["events"]] + return [ + seam_event_from_dict(item) + for item in unwrap_list(res, "events", "/events/list") + ] diff --git a/seam/routes/instant_keys.py b/seam/routes/instant_keys.py index 5201b243..ed8eb43e 100644 --- a/seam/routes/instant_keys.py +++ b/seam/routes/instant_keys.py @@ -3,6 +3,8 @@ from ..client import SeamHttpClient, AsyncSeamHttpClient from ..route import route_metadata from ..resources import InstantKey +from ..response import unwrap +from ..response import unwrap_list class AbstractInstantKeys(abc.ABC): @@ -141,7 +143,7 @@ def get( res = self.client.get("/instant_keys/get", params=params) - return InstantKey.from_dict(res["instant_key"]) + return InstantKey.from_dict(unwrap(res, "instant_key", "/instant_keys/get")) @route_metadata( path="/instant_keys/list", has_required_parameters=False, has_pagination=False @@ -159,7 +161,10 @@ def list(self, *, user_identity_id: Optional[str] = None) -> List[InstantKey]: res = self.client.get("/instant_keys/list", params=params) - return [InstantKey.from_dict(item) for item in res["instant_keys"]] + return [ + InstantKey.from_dict(item) + for item in unwrap_list(res, "instant_keys", "/instant_keys/list") + ] class AsyncInstantKeys(AbstractAsyncInstantKeys): @@ -220,7 +225,7 @@ async def get( res = await self.client.get("/instant_keys/get", params=params) - return InstantKey.from_dict(res["instant_key"]) + return InstantKey.from_dict(unwrap(res, "instant_key", "/instant_keys/get")) @route_metadata( path="/instant_keys/list", has_required_parameters=False, has_pagination=False @@ -238,4 +243,7 @@ async def list(self, *, user_identity_id: Optional[str] = None) -> List[InstantK res = await self.client.get("/instant_keys/list", params=params) - return [InstantKey.from_dict(item) for item in res["instant_keys"]] + return [ + InstantKey.from_dict(item) + for item in unwrap_list(res, "instant_keys", "/instant_keys/list") + ] diff --git a/seam/routes/locks.py b/seam/routes/locks.py index 7fda971c..a3a95464 100644 --- a/seam/routes/locks.py +++ b/seam/routes/locks.py @@ -13,6 +13,8 @@ resolve_action_attempt, resolve_action_attempt_async, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractLocks(abc.ABC): @@ -515,7 +517,9 @@ def configure_auto_lock( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/locks/configure_auto_lock") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -549,7 +553,7 @@ def get( res = self.client.get("/locks/get", params=params) - return Device.from_dict(res["device"]) + return Device.from_dict(unwrap(res, "device", "/locks/get")) @route_metadata( path="/locks/list", has_required_parameters=False, has_pagination=False @@ -702,7 +706,10 @@ def list( res = self.client.get("/locks/list", params=params) - return [Device.from_dict(item) for item in res["devices"]] + return [ + Device.from_dict(item) + for item in unwrap_list(res, "devices", "/locks/list") + ] @route_metadata( path="/locks/lock_door", has_required_parameters=True, has_pagination=False @@ -740,7 +747,9 @@ def lock_door( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/locks/lock_door") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -782,7 +791,9 @@ def unlock_door( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/locks/unlock_door") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -847,7 +858,9 @@ async def configure_auto_lock( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/locks/configure_auto_lock") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -881,7 +894,7 @@ async def get( res = await self.client.get("/locks/get", params=params) - return Device.from_dict(res["device"]) + return Device.from_dict(unwrap(res, "device", "/locks/get")) @route_metadata( path="/locks/list", has_required_parameters=False, has_pagination=False @@ -1034,7 +1047,10 @@ async def list( res = await self.client.get("/locks/list", params=params) - return [Device.from_dict(item) for item in res["devices"]] + return [ + Device.from_dict(item) + for item in unwrap_list(res, "devices", "/locks/list") + ] @route_metadata( path="/locks/lock_door", has_required_parameters=True, has_pagination=False @@ -1072,7 +1088,9 @@ async def lock_door( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/locks/lock_door") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -1114,6 +1132,8 @@ async def unlock_door( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/locks/unlock_door") + ), wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/locks_simulate.py b/seam/routes/locks_simulate.py index 4b75249a..4e9c00f4 100644 --- a/seam/routes/locks_simulate.py +++ b/seam/routes/locks_simulate.py @@ -7,6 +7,7 @@ resolve_action_attempt, resolve_action_attempt_async, ) +from ..response import unwrap class AbstractLocksSimulate(abc.ABC): @@ -143,7 +144,9 @@ def keypad_code_entry( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/locks/simulate/keypad_code_entry") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -189,7 +192,9 @@ def manual_lock_via_keypad( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/locks/simulate/manual_lock_via_keypad") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -246,7 +251,9 @@ async def keypad_code_entry( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/locks/simulate/keypad_code_entry") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -292,6 +299,8 @@ async def manual_lock_via_keypad( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/locks/simulate/manual_lock_via_keypad") + ), wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/noise_sensors.py b/seam/routes/noise_sensors.py index f6ddc74c..480cb2be 100644 --- a/seam/routes/noise_sensors.py +++ b/seam/routes/noise_sensors.py @@ -15,6 +15,7 @@ AbstractAsyncNoiseSensorsSimulate, AsyncNoiseSensorsSimulate, ) +from ..response import unwrap_list class AbstractNoiseSensors(abc.ABC): @@ -173,7 +174,10 @@ def list( res = self.client.get("/noise_sensors/list", params=params) - return [Device.from_dict(item) for item in res["devices"]] + return [ + Device.from_dict(item) + for item in unwrap_list(res, "devices", "/noise_sensors/list") + ] class AsyncNoiseSensors(AbstractAsyncNoiseSensors): @@ -242,4 +246,7 @@ async def list( res = await self.client.get("/noise_sensors/list", params=params) - return [Device.from_dict(item) for item in res["devices"]] + return [ + Device.from_dict(item) + for item in unwrap_list(res, "devices", "/noise_sensors/list") + ] diff --git a/seam/routes/noise_sensors_noise_thresholds.py b/seam/routes/noise_sensors_noise_thresholds.py index 46e12eb5..1cf94d82 100644 --- a/seam/routes/noise_sensors_noise_thresholds.py +++ b/seam/routes/noise_sensors_noise_thresholds.py @@ -3,6 +3,8 @@ from ..client import SeamHttpClient, AsyncSeamHttpClient from ..route import route_metadata from ..resources import NoiseThreshold +from ..response import unwrap +from ..response import unwrap_list class AbstractNoiseSensorsNoiseThresholds(abc.ABC): @@ -260,7 +262,9 @@ def create( "/noise_sensors/noise_thresholds/create", json=json_payload ) - return NoiseThreshold.from_dict(res["noise_threshold"]) + return NoiseThreshold.from_dict( + unwrap(res, "noise_threshold", "/noise_sensors/noise_thresholds/create") + ) @route_metadata( path="/noise_sensors/noise_thresholds/delete", @@ -316,7 +320,9 @@ def get(self, *, noise_threshold_id: str) -> NoiseThreshold: res = self.client.get("/noise_sensors/noise_thresholds/get", params=params) - return NoiseThreshold.from_dict(res["noise_threshold"]) + return NoiseThreshold.from_dict( + unwrap(res, "noise_threshold", "/noise_sensors/noise_thresholds/get") + ) @route_metadata( path="/noise_sensors/noise_thresholds/list", @@ -343,7 +349,12 @@ def list(self, *, device_id: str) -> List[NoiseThreshold]: res = self.client.get("/noise_sensors/noise_thresholds/list", params=params) - return [NoiseThreshold.from_dict(item) for item in res["noise_thresholds"]] + return [ + NoiseThreshold.from_dict(item) + for item in unwrap_list( + res, "noise_thresholds", "/noise_sensors/noise_thresholds/list" + ) + ] @route_metadata( path="/noise_sensors/noise_thresholds/update", @@ -466,7 +477,9 @@ async def create( "/noise_sensors/noise_thresholds/create", json=json_payload ) - return NoiseThreshold.from_dict(res["noise_threshold"]) + return NoiseThreshold.from_dict( + unwrap(res, "noise_threshold", "/noise_sensors/noise_thresholds/create") + ) @route_metadata( path="/noise_sensors/noise_thresholds/delete", @@ -526,7 +539,9 @@ async def get(self, *, noise_threshold_id: str) -> NoiseThreshold: "/noise_sensors/noise_thresholds/get", params=params ) - return NoiseThreshold.from_dict(res["noise_threshold"]) + return NoiseThreshold.from_dict( + unwrap(res, "noise_threshold", "/noise_sensors/noise_thresholds/get") + ) @route_metadata( path="/noise_sensors/noise_thresholds/list", @@ -555,7 +570,12 @@ async def list(self, *, device_id: str) -> List[NoiseThreshold]: "/noise_sensors/noise_thresholds/list", params=params ) - return [NoiseThreshold.from_dict(item) for item in res["noise_thresholds"]] + return [ + NoiseThreshold.from_dict(item) + for item in unwrap_list( + res, "noise_thresholds", "/noise_sensors/noise_thresholds/list" + ) + ] @route_metadata( path="/noise_sensors/noise_thresholds/update", diff --git a/seam/routes/phones.py b/seam/routes/phones.py index b291296a..1eece7da 100644 --- a/seam/routes/phones.py +++ b/seam/routes/phones.py @@ -9,6 +9,8 @@ AbstractAsyncPhonesSimulate, AsyncPhonesSimulate, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractPhones(abc.ABC): @@ -153,7 +155,7 @@ def get(self, *, device_id: str) -> Phone: res = self.client.get("/phones/get", params=params) - return Phone.from_dict(res["phone"]) + return Phone.from_dict(unwrap(res, "phone", "/phones/get")) @route_metadata( path="/phones/list", has_required_parameters=False, has_pagination=False @@ -180,7 +182,9 @@ def list( res = self.client.get("/phones/list", params=params) - return [Phone.from_dict(item) for item in res["phones"]] + return [ + Phone.from_dict(item) for item in unwrap_list(res, "phones", "/phones/list") + ] class AsyncPhones(AbstractAsyncPhones): @@ -237,7 +241,7 @@ async def get(self, *, device_id: str) -> Phone: res = await self.client.get("/phones/get", params=params) - return Phone.from_dict(res["phone"]) + return Phone.from_dict(unwrap(res, "phone", "/phones/get")) @route_metadata( path="/phones/list", has_required_parameters=False, has_pagination=False @@ -264,4 +268,6 @@ async def list( res = await self.client.get("/phones/list", params=params) - return [Phone.from_dict(item) for item in res["phones"]] + return [ + Phone.from_dict(item) for item in unwrap_list(res, "phones", "/phones/list") + ] diff --git a/seam/routes/phones_simulate.py b/seam/routes/phones_simulate.py index 8a9f4d36..45f8c3a4 100644 --- a/seam/routes/phones_simulate.py +++ b/seam/routes/phones_simulate.py @@ -3,6 +3,7 @@ from ..client import SeamHttpClient, AsyncSeamHttpClient from ..route import route_metadata from ..resources import Phone +from ..response import unwrap class AbstractPhonesSimulate(abc.ABC): @@ -110,7 +111,9 @@ def create_sandbox_phone( "/phones/simulate/create_sandbox_phone", json=json_payload ) - return Phone.from_dict(res["phone"]) + return Phone.from_dict( + unwrap(res, "phone", "/phones/simulate/create_sandbox_phone") + ) class AsyncPhonesSimulate(AbstractAsyncPhonesSimulate): @@ -164,4 +167,6 @@ async def create_sandbox_phone( "/phones/simulate/create_sandbox_phone", json=json_payload ) - return Phone.from_dict(res["phone"]) + return Phone.from_dict( + unwrap(res, "phone", "/phones/simulate/create_sandbox_phone") + ) diff --git a/seam/routes/spaces.py b/seam/routes/spaces.py index cda8006d..f0d9ad8b 100644 --- a/seam/routes/spaces.py +++ b/seam/routes/spaces.py @@ -4,6 +4,8 @@ from ..route import route_metadata from ..null import Null from ..resources import Space, Batch +from ..response import unwrap +from ..response import unwrap_list class AbstractSpaces(abc.ABC): @@ -617,7 +619,7 @@ def create( res = self.client.post("/spaces/create", json=json_payload) - return Space.from_dict(res["space"]) + return Space.from_dict(unwrap(res, "space", "/spaces/create")) @route_metadata( path="/spaces/delete", has_required_parameters=True, has_pagination=False @@ -667,7 +669,7 @@ def get( res = self.client.get("/spaces/get", params=params) - return Space.from_dict(res["space"]) + return Space.from_dict(unwrap(res, "space", "/spaces/get")) @route_metadata( path="/spaces/get_related", has_required_parameters=True, has_pagination=False @@ -733,7 +735,7 @@ def get_related( res = self.client.get("/spaces/get_related", params=params) - return Batch.from_dict(res["batch"]) + return Batch.from_dict(unwrap(res, "batch", "/spaces/get_related")) @route_metadata( path="/spaces/list", has_required_parameters=False, has_pagination=True @@ -775,7 +777,9 @@ def list( res = self.client.get("/spaces/list", params=params) - return [Space.from_dict(item) for item in res["spaces"]] + return [ + Space.from_dict(item) for item in unwrap_list(res, "spaces", "/spaces/list") + ] @route_metadata( path="/spaces/remove_acs_entrances", @@ -913,7 +917,7 @@ def update( res = self.client.patch("/spaces/update", json=json_payload) - return Space.from_dict(res["space"]) + return Space.from_dict(unwrap(res, "space", "/spaces/update")) class AsyncSpaces(AbstractAsyncSpaces): @@ -1065,7 +1069,7 @@ async def create( res = await self.client.post("/spaces/create", json=json_payload) - return Space.from_dict(res["space"]) + return Space.from_dict(unwrap(res, "space", "/spaces/create")) @route_metadata( path="/spaces/delete", has_required_parameters=True, has_pagination=False @@ -1115,7 +1119,7 @@ async def get( res = await self.client.get("/spaces/get", params=params) - return Space.from_dict(res["space"]) + return Space.from_dict(unwrap(res, "space", "/spaces/get")) @route_metadata( path="/spaces/get_related", has_required_parameters=True, has_pagination=False @@ -1181,7 +1185,7 @@ async def get_related( res = await self.client.get("/spaces/get_related", params=params) - return Batch.from_dict(res["batch"]) + return Batch.from_dict(unwrap(res, "batch", "/spaces/get_related")) @route_metadata( path="/spaces/list", has_required_parameters=False, has_pagination=True @@ -1223,7 +1227,9 @@ async def list( res = await self.client.get("/spaces/list", params=params) - return [Space.from_dict(item) for item in res["spaces"]] + return [ + Space.from_dict(item) for item in unwrap_list(res, "spaces", "/spaces/list") + ] @route_metadata( path="/spaces/remove_acs_entrances", @@ -1361,4 +1367,4 @@ async def update( res = await self.client.patch("/spaces/update", json=json_payload) - return Space.from_dict(res["space"]) + return Space.from_dict(unwrap(res, "space", "/spaces/update")) diff --git a/seam/routes/thermostats.py b/seam/routes/thermostats.py index e2cbde9d..b2e21b5f 100644 --- a/seam/routes/thermostats.py +++ b/seam/routes/thermostats.py @@ -26,6 +26,8 @@ resolve_action_attempt, resolve_action_attempt_async, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractThermostats(abc.ABC): @@ -963,7 +965,9 @@ def activate_climate_preset( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/activate_climate_preset") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -1013,7 +1017,9 @@ def cool( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/cool") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -1180,7 +1186,9 @@ def heat( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/heat") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -1244,7 +1252,9 @@ def heat_cool( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/heat_cool") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -1317,7 +1327,10 @@ def list( res = self.client.get("/thermostats/list", params=params) - return [Device.from_dict(item) for item in res["devices"]] + return [ + Device.from_dict(item) + for item in unwrap_list(res, "devices", "/thermostats/list") + ] @route_metadata( path="/thermostats/off", has_required_parameters=True, has_pagination=False @@ -1355,7 +1368,9 @@ def off( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/off") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -1440,7 +1455,9 @@ def set_fan_mode( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/set_fan_mode") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -1509,7 +1526,9 @@ def set_hvac_mode( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/set_hvac_mode") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -1725,7 +1744,9 @@ def update_weekly_program( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/update_weekly_program") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -1799,7 +1820,9 @@ async def activate_climate_preset( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/activate_climate_preset") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -1849,7 +1872,9 @@ async def cool( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/cool") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -2018,7 +2043,9 @@ async def heat( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/heat") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -2082,7 +2109,9 @@ async def heat_cool( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/heat_cool") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -2155,7 +2184,10 @@ async def list( res = await self.client.get("/thermostats/list", params=params) - return [Device.from_dict(item) for item in res["devices"]] + return [ + Device.from_dict(item) + for item in unwrap_list(res, "devices", "/thermostats/list") + ] @route_metadata( path="/thermostats/off", has_required_parameters=True, has_pagination=False @@ -2193,7 +2225,9 @@ async def off( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/off") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -2280,7 +2314,9 @@ async def set_fan_mode( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/set_fan_mode") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -2349,7 +2385,9 @@ async def set_hvac_mode( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/set_hvac_mode") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -2569,6 +2607,8 @@ async def update_weekly_program( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/update_weekly_program") + ), wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/thermostats_daily_programs.py b/seam/routes/thermostats_daily_programs.py index da3dc49d..7dacc537 100644 --- a/seam/routes/thermostats_daily_programs.py +++ b/seam/routes/thermostats_daily_programs.py @@ -7,6 +7,7 @@ resolve_action_attempt, resolve_action_attempt_async, ) +from ..response import unwrap class AbstractThermostatsDailyPrograms(abc.ABC): @@ -155,7 +156,11 @@ def create( res = self.client.post("/thermostats/daily_programs/create", json=json_payload) - return ThermostatDailyProgram.from_dict(res["thermostat_daily_program"]) + return ThermostatDailyProgram.from_dict( + unwrap( + res, "thermostat_daily_program", "/thermostats/daily_programs/create" + ) + ) @route_metadata( path="/thermostats/daily_programs/delete", @@ -232,7 +237,9 @@ def update( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/daily_programs/update") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -279,7 +286,11 @@ async def create( "/thermostats/daily_programs/create", json=json_payload ) - return ThermostatDailyProgram.from_dict(res["thermostat_daily_program"]) + return ThermostatDailyProgram.from_dict( + unwrap( + res, "thermostat_daily_program", "/thermostats/daily_programs/create" + ) + ) @route_metadata( path="/thermostats/daily_programs/delete", @@ -358,6 +369,8 @@ async def update( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/thermostats/daily_programs/update") + ), wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/seam/routes/thermostats_schedules.py b/seam/routes/thermostats_schedules.py index 84201408..eb1a269f 100644 --- a/seam/routes/thermostats_schedules.py +++ b/seam/routes/thermostats_schedules.py @@ -4,6 +4,8 @@ from ..route import route_metadata from ..null import Null from ..resources import ThermostatSchedule +from ..response import unwrap +from ..response import unwrap_list class AbstractThermostatsSchedules(abc.ABC): @@ -274,7 +276,9 @@ def create( res = self.client.post("/thermostats/schedules/create", json=json_payload) - return ThermostatSchedule.from_dict(res["thermostat_schedule"]) + return ThermostatSchedule.from_dict( + unwrap(res, "thermostat_schedule", "/thermostats/schedules/create") + ) @route_metadata( path="/thermostats/schedules/delete", @@ -326,7 +330,9 @@ def get(self, *, thermostat_schedule_id: str) -> ThermostatSchedule: res = self.client.get("/thermostats/schedules/get", params=params) - return ThermostatSchedule.from_dict(res["thermostat_schedule"]) + return ThermostatSchedule.from_dict( + unwrap(res, "thermostat_schedule", "/thermostats/schedules/get") + ) @route_metadata( path="/thermostats/schedules/list", @@ -360,7 +366,10 @@ def list( res = self.client.get("/thermostats/schedules/list", params=params) return [ - ThermostatSchedule.from_dict(item) for item in res["thermostat_schedules"] + ThermostatSchedule.from_dict(item) + for item in unwrap_list( + res, "thermostat_schedules", "/thermostats/schedules/list" + ) ] @route_metadata( @@ -487,7 +496,9 @@ async def create( res = await self.client.post("/thermostats/schedules/create", json=json_payload) - return ThermostatSchedule.from_dict(res["thermostat_schedule"]) + return ThermostatSchedule.from_dict( + unwrap(res, "thermostat_schedule", "/thermostats/schedules/create") + ) @route_metadata( path="/thermostats/schedules/delete", @@ -539,7 +550,9 @@ async def get(self, *, thermostat_schedule_id: str) -> ThermostatSchedule: res = await self.client.get("/thermostats/schedules/get", params=params) - return ThermostatSchedule.from_dict(res["thermostat_schedule"]) + return ThermostatSchedule.from_dict( + unwrap(res, "thermostat_schedule", "/thermostats/schedules/get") + ) @route_metadata( path="/thermostats/schedules/list", @@ -573,7 +586,10 @@ async def list( res = await self.client.get("/thermostats/schedules/list", params=params) return [ - ThermostatSchedule.from_dict(item) for item in res["thermostat_schedules"] + ThermostatSchedule.from_dict(item) + for item in unwrap_list( + res, "thermostat_schedules", "/thermostats/schedules/list" + ) ] @route_metadata( diff --git a/seam/routes/user_identities.py b/seam/routes/user_identities.py index a13f4c62..69ef5921 100644 --- a/seam/routes/user_identities.py +++ b/seam/routes/user_identities.py @@ -17,6 +17,8 @@ AbstractAsyncUserIdentitiesUnmanaged, AsyncUserIdentitiesUnmanaged, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractUserIdentities(abc.ABC): @@ -644,7 +646,9 @@ def create( res = self.client.post("/user_identities/create", json=json_payload) - return UserIdentity.from_dict(res["user_identity"]) + return UserIdentity.from_dict( + unwrap(res, "user_identity", "/user_identities/create") + ) @route_metadata( path="/user_identities/delete", @@ -712,7 +716,9 @@ def generate_instant_key( "/user_identities/generate_instant_key", json=json_payload ) - return InstantKey.from_dict(res["instant_key"]) + return InstantKey.from_dict( + unwrap(res, "instant_key", "/user_identities/generate_instant_key") + ) @route_metadata( path="/user_identities/get", has_required_parameters=True, has_pagination=False @@ -746,7 +752,9 @@ def get( res = self.client.get("/user_identities/get", params=params) - return UserIdentity.from_dict(res["user_identity"]) + return UserIdentity.from_dict( + unwrap(res, "user_identity", "/user_identities/get") + ) @route_metadata( path="/user_identities/grant_access_to_device", @@ -824,7 +832,10 @@ def list( res = self.client.get("/user_identities/list", params=params) - return [UserIdentity.from_dict(item) for item in res["user_identities"]] + return [ + UserIdentity.from_dict(item) + for item in unwrap_list(res, "user_identities", "/user_identities/list") + ] @route_metadata( path="/user_identities/list_accessible_devices", @@ -851,7 +862,12 @@ def list_accessible_devices(self, *, user_identity_id: str) -> List[Device]: res = self.client.get("/user_identities/list_accessible_devices", params=params) - return [Device.from_dict(item) for item in res["devices"]] + return [ + Device.from_dict(item) + for item in unwrap_list( + res, "devices", "/user_identities/list_accessible_devices" + ) + ] @route_metadata( path="/user_identities/list_accessible_entrances", @@ -880,7 +896,12 @@ def list_accessible_entrances(self, *, user_identity_id: str) -> List[AcsEntranc "/user_identities/list_accessible_entrances", params=params ) - return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] + return [ + AcsEntrance.from_dict(item) + for item in unwrap_list( + res, "acs_entrances", "/user_identities/list_accessible_entrances" + ) + ] @route_metadata( path="/user_identities/list_acs_systems", @@ -907,7 +928,12 @@ def list_acs_systems(self, *, user_identity_id: str) -> List[AcsSystem]: res = self.client.get("/user_identities/list_acs_systems", params=params) - return [AcsSystem.from_dict(item) for item in res["acs_systems"]] + return [ + AcsSystem.from_dict(item) + for item in unwrap_list( + res, "acs_systems", "/user_identities/list_acs_systems" + ) + ] @route_metadata( path="/user_identities/list_acs_users", @@ -934,7 +960,10 @@ def list_acs_users(self, *, user_identity_id: str) -> List[AcsUser]: res = self.client.get("/user_identities/list_acs_users", params=params) - return [AcsUser.from_dict(item) for item in res["acs_users"]] + return [ + AcsUser.from_dict(item) + for item in unwrap_list(res, "acs_users", "/user_identities/list_acs_users") + ] @route_metadata( path="/user_identities/merge", @@ -1189,7 +1218,9 @@ async def create( res = await self.client.post("/user_identities/create", json=json_payload) - return UserIdentity.from_dict(res["user_identity"]) + return UserIdentity.from_dict( + unwrap(res, "user_identity", "/user_identities/create") + ) @route_metadata( path="/user_identities/delete", @@ -1257,7 +1288,9 @@ async def generate_instant_key( "/user_identities/generate_instant_key", json=json_payload ) - return InstantKey.from_dict(res["instant_key"]) + return InstantKey.from_dict( + unwrap(res, "instant_key", "/user_identities/generate_instant_key") + ) @route_metadata( path="/user_identities/get", has_required_parameters=True, has_pagination=False @@ -1291,7 +1324,9 @@ async def get( res = await self.client.get("/user_identities/get", params=params) - return UserIdentity.from_dict(res["user_identity"]) + return UserIdentity.from_dict( + unwrap(res, "user_identity", "/user_identities/get") + ) @route_metadata( path="/user_identities/grant_access_to_device", @@ -1373,7 +1408,10 @@ async def list( res = await self.client.get("/user_identities/list", params=params) - return [UserIdentity.from_dict(item) for item in res["user_identities"]] + return [ + UserIdentity.from_dict(item) + for item in unwrap_list(res, "user_identities", "/user_identities/list") + ] @route_metadata( path="/user_identities/list_accessible_devices", @@ -1402,7 +1440,12 @@ async def list_accessible_devices(self, *, user_identity_id: str) -> List[Device "/user_identities/list_accessible_devices", params=params ) - return [Device.from_dict(item) for item in res["devices"]] + return [ + Device.from_dict(item) + for item in unwrap_list( + res, "devices", "/user_identities/list_accessible_devices" + ) + ] @route_metadata( path="/user_identities/list_accessible_entrances", @@ -1433,7 +1476,12 @@ async def list_accessible_entrances( "/user_identities/list_accessible_entrances", params=params ) - return [AcsEntrance.from_dict(item) for item in res["acs_entrances"]] + return [ + AcsEntrance.from_dict(item) + for item in unwrap_list( + res, "acs_entrances", "/user_identities/list_accessible_entrances" + ) + ] @route_metadata( path="/user_identities/list_acs_systems", @@ -1460,7 +1508,12 @@ async def list_acs_systems(self, *, user_identity_id: str) -> List[AcsSystem]: res = await self.client.get("/user_identities/list_acs_systems", params=params) - return [AcsSystem.from_dict(item) for item in res["acs_systems"]] + return [ + AcsSystem.from_dict(item) + for item in unwrap_list( + res, "acs_systems", "/user_identities/list_acs_systems" + ) + ] @route_metadata( path="/user_identities/list_acs_users", @@ -1487,7 +1540,10 @@ async def list_acs_users(self, *, user_identity_id: str) -> List[AcsUser]: res = await self.client.get("/user_identities/list_acs_users", params=params) - return [AcsUser.from_dict(item) for item in res["acs_users"]] + return [ + AcsUser.from_dict(item) + for item in unwrap_list(res, "acs_users", "/user_identities/list_acs_users") + ] @route_metadata( path="/user_identities/merge", diff --git a/seam/routes/user_identities_unmanaged.py b/seam/routes/user_identities_unmanaged.py index ad224074..d92f51c3 100644 --- a/seam/routes/user_identities_unmanaged.py +++ b/seam/routes/user_identities_unmanaged.py @@ -4,6 +4,8 @@ from ..route import route_metadata from ..null import Null from ..resources import UnmanagedUserIdentity +from ..response import unwrap +from ..response import unwrap_list class AbstractUserIdentitiesUnmanaged(abc.ABC): @@ -150,7 +152,9 @@ def get(self, *, user_identity_id: str) -> UnmanagedUserIdentity: res = self.client.get("/user_identities/unmanaged/get", params=params) - return UnmanagedUserIdentity.from_dict(res["user_identity"]) + return UnmanagedUserIdentity.from_dict( + unwrap(res, "user_identity", "/user_identities/unmanaged/get") + ) @route_metadata( path="/user_identities/unmanaged/list", @@ -190,7 +194,10 @@ def list( res = self.client.get("/user_identities/unmanaged/list", params=params) return [ - UnmanagedUserIdentity.from_dict(item) for item in res["user_identities"] + UnmanagedUserIdentity.from_dict(item) + for item in unwrap_list( + res, "user_identities", "/user_identities/unmanaged/list" + ) ] @route_metadata( @@ -265,7 +272,9 @@ async def get(self, *, user_identity_id: str) -> UnmanagedUserIdentity: res = await self.client.get("/user_identities/unmanaged/get", params=params) - return UnmanagedUserIdentity.from_dict(res["user_identity"]) + return UnmanagedUserIdentity.from_dict( + unwrap(res, "user_identity", "/user_identities/unmanaged/get") + ) @route_metadata( path="/user_identities/unmanaged/list", @@ -305,7 +314,10 @@ async def list( res = await self.client.get("/user_identities/unmanaged/list", params=params) return [ - UnmanagedUserIdentity.from_dict(item) for item in res["user_identities"] + UnmanagedUserIdentity.from_dict(item) + for item in unwrap_list( + res, "user_identities", "/user_identities/unmanaged/list" + ) ] @route_metadata( diff --git a/seam/routes/webhooks.py b/seam/routes/webhooks.py index 749435c4..7742cb70 100644 --- a/seam/routes/webhooks.py +++ b/seam/routes/webhooks.py @@ -3,6 +3,8 @@ from ..client import SeamHttpClient, AsyncSeamHttpClient from ..route import route_metadata from ..resources import Webhook +from ..response import unwrap +from ..response import unwrap_list class AbstractWebhooks(abc.ABC): @@ -145,7 +147,7 @@ def create(self, *, url: str, event_types: Optional[List[str]] = None) -> Webhoo res = self.client.post("/webhooks/create", json=json_payload) - return Webhook.from_dict(res["webhook"]) + return Webhook.from_dict(unwrap(res, "webhook", "/webhooks/create")) @route_metadata( path="/webhooks/delete", has_required_parameters=True, has_pagination=False @@ -189,7 +191,7 @@ def get(self, *, webhook_id: str) -> Webhook: res = self.client.get("/webhooks/get", params=params) - return Webhook.from_dict(res["webhook"]) + return Webhook.from_dict(unwrap(res, "webhook", "/webhooks/get")) @route_metadata( path="/webhooks/list", has_required_parameters=False, has_pagination=False @@ -202,7 +204,10 @@ def list(self) -> List[Webhook]: res = self.client.get("/webhooks/list", params=params) - return [Webhook.from_dict(item) for item in res["webhooks"]] + return [ + Webhook.from_dict(item) + for item in unwrap_list(res, "webhooks", "/webhooks/list") + ] @route_metadata( path="/webhooks/update", has_required_parameters=True, has_pagination=False @@ -262,7 +267,7 @@ async def create( res = await self.client.post("/webhooks/create", json=json_payload) - return Webhook.from_dict(res["webhook"]) + return Webhook.from_dict(unwrap(res, "webhook", "/webhooks/create")) @route_metadata( path="/webhooks/delete", has_required_parameters=True, has_pagination=False @@ -306,7 +311,7 @@ async def get(self, *, webhook_id: str) -> Webhook: res = await self.client.get("/webhooks/get", params=params) - return Webhook.from_dict(res["webhook"]) + return Webhook.from_dict(unwrap(res, "webhook", "/webhooks/get")) @route_metadata( path="/webhooks/list", has_required_parameters=False, has_pagination=False @@ -319,7 +324,10 @@ async def list(self) -> List[Webhook]: res = await self.client.get("/webhooks/list", params=params) - return [Webhook.from_dict(item) for item in res["webhooks"]] + return [ + Webhook.from_dict(item) + for item in unwrap_list(res, "webhooks", "/webhooks/list") + ] @route_metadata( path="/webhooks/update", has_required_parameters=True, has_pagination=False diff --git a/seam/routes/workspaces.py b/seam/routes/workspaces.py index 1831687b..2068c668 100644 --- a/seam/routes/workspaces.py +++ b/seam/routes/workspaces.py @@ -8,6 +8,8 @@ resolve_action_attempt, resolve_action_attempt_async, ) +from ..response import unwrap +from ..response import unwrap_list class AbstractWorkspaces(abc.ABC): @@ -285,7 +287,7 @@ def create( res = self.client.post("/workspaces/create", json=json_payload) - return Workspace.from_dict(res["workspace"]) + return Workspace.from_dict(unwrap(res, "workspace", "/workspaces/create")) @route_metadata( path="/workspaces/get", has_required_parameters=False, has_pagination=False @@ -298,7 +300,7 @@ def get(self) -> Workspace: res = self.client.get("/workspaces/get", params=params) - return Workspace.from_dict(res["workspace"]) + return Workspace.from_dict(unwrap(res, "workspace", "/workspaces/get")) @route_metadata( path="/workspaces/list", has_required_parameters=False, has_pagination=False @@ -311,7 +313,10 @@ def list(self) -> List[Workspace]: res = self.client.get("/workspaces/list", params=params) - return [Workspace.from_dict(item) for item in res["workspaces"]] + return [ + Workspace.from_dict(item) + for item in unwrap_list(res, "workspaces", "/workspaces/list") + ] @route_metadata( path="/workspaces/reset_sandbox", @@ -338,7 +343,9 @@ def reset_sandbox( return resolve_action_attempt( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/workspaces/reset_sandbox") + ), wait_for_action_attempt=wait_for_action_attempt, ) @@ -474,7 +481,7 @@ async def create( res = await self.client.post("/workspaces/create", json=json_payload) - return Workspace.from_dict(res["workspace"]) + return Workspace.from_dict(unwrap(res, "workspace", "/workspaces/create")) @route_metadata( path="/workspaces/get", has_required_parameters=False, has_pagination=False @@ -487,7 +494,7 @@ async def get(self) -> Workspace: res = await self.client.get("/workspaces/get", params=params) - return Workspace.from_dict(res["workspace"]) + return Workspace.from_dict(unwrap(res, "workspace", "/workspaces/get")) @route_metadata( path="/workspaces/list", has_required_parameters=False, has_pagination=False @@ -500,7 +507,10 @@ async def list(self) -> List[Workspace]: res = await self.client.get("/workspaces/list", params=params) - return [Workspace.from_dict(item) for item in res["workspaces"]] + return [ + Workspace.from_dict(item) + for item in unwrap_list(res, "workspaces", "/workspaces/list") + ] @route_metadata( path="/workspaces/reset_sandbox", @@ -527,7 +537,9 @@ async def reset_sandbox( return await resolve_action_attempt_async( client=self.client, - action_attempt=action_attempt_from_dict(res["action_attempt"]), + action_attempt=action_attempt_from_dict( + unwrap(res, "action_attempt", "/workspaces/reset_sandbox") + ), wait_for_action_attempt=wait_for_action_attempt, ) diff --git a/test/invalid_response_test.py b/test/invalid_response_test.py new file mode 100644 index 00000000..cae36d7f --- /dev/null +++ b/test/invalid_response_test.py @@ -0,0 +1,155 @@ +import pytest + +from seam import AsyncSeam, Seam, SeamError, SeamHttpInvalidResponseError + +DEVICE_ID = "22222222-2222-2222-2222-222222222222" + + +def make_seam(endpoint): + return Seam.from_api_key("seam_apikey_token", endpoint=endpoint) + + +def test_a_response_missing_the_expected_key_raises(recording_server): + with recording_server([(200, {"wrong_key": {}})]) as (endpoint, _): + seam = make_seam(endpoint) + + with pytest.raises( + SeamHttpInvalidResponseError, + match="Seam returned an invalid response for /devices/get: " + 'expected "device", which the response does not contain', + ): + seam.devices.get(device_id=DEVICE_ID) + + +def test_a_null_response_body_raises(recording_server): + with recording_server([(200, None)]) as (endpoint, _): + seam = make_seam(endpoint) + + with pytest.raises( + SeamHttpInvalidResponseError, + match='expected "device", got NoneType instead of a response object', + ): + seam.devices.get(device_id=DEVICE_ID) + + +def test_a_string_response_body_raises(recording_server): + with recording_server([(200, '"a json string"', "application/json")]) as ( + endpoint, + _, + ): + seam = make_seam(endpoint) + + with pytest.raises( + SeamHttpInvalidResponseError, + match='expected "device", got str instead of a response object', + ): + seam.devices.get(device_id=DEVICE_ID) + + +def test_a_non_json_gateway_page_raises(recording_server): + with recording_server( + [(200, "Scheduled maintenance", "application/json")] + ) as (endpoint, _): + seam = make_seam(endpoint) + + with pytest.raises( + SeamHttpInvalidResponseError, + match='expected "device", got str instead of a response object', + ): + seam.devices.get(device_id=DEVICE_ID) + + +def test_a_plain_text_response_raises(recording_server): + with recording_server([(200, "ok")]) as (endpoint, _): + seam = make_seam(endpoint) + + with pytest.raises( + SeamHttpInvalidResponseError, + match='expected "device", got str instead of a response object', + ): + seam.devices.get(device_id=DEVICE_ID) + + +def test_a_non_object_value_under_the_response_key_raises(recording_server): + with recording_server([(200, {"device": "not-an-object"})]) as (endpoint, _): + seam = make_seam(endpoint) + + with pytest.raises( + SeamHttpInvalidResponseError, + match='expected "device", got str instead of an object', + ): + seam.devices.get(device_id=DEVICE_ID) + + +def test_a_non_list_value_under_a_list_response_key_raises(recording_server): + with recording_server([(200, {"devices": {"not": "a list"}})]) as (endpoint, _): + seam = make_seam(endpoint) + + with pytest.raises( + SeamHttpInvalidResponseError, + match="Seam returned an invalid response for /devices/list: " + 'expected "devices", got dict instead of a list', + ): + seam.devices.list() + + +def test_a_malformed_poll_response_raises_mid_wait(recording_server): + pending_response = { + "action_attempt": { + "action_attempt_id": "11111111-1111-1111-1111-111111111111", + "action_type": "UNLOCK_DOOR", + "status": "pending", + "result": None, + "error": None, + } + } + + with recording_server([(200, pending_response), (200, {"nonsense": True})]) as ( + endpoint, + _, + ): + seam = make_seam(endpoint) + + with pytest.raises( + SeamHttpInvalidResponseError, + match="Seam returned an invalid response for /action_attempts/get: " + 'expected "action_attempt", which the response does not contain', + ): + seam.action_attempts.get( + action_attempt_id="11111111-1111-1111-1111-111111111111", + wait_for_action_attempt={"timeout": 5, "polling_interval": 0.05}, + ) + + +def test_the_invalid_response_error_is_a_seam_error(recording_server): + with recording_server([(200, {"wrong_key": {}})]) as (endpoint, _): + seam = make_seam(endpoint) + + with pytest.raises(SeamError) as exc_info: + seam.devices.get(device_id=DEVICE_ID) + + assert isinstance(exc_info.value, SeamHttpInvalidResponseError) + assert exc_info.value.path == "/devices/get" + assert exc_info.value.response_key == "device" + + +async def test_a_response_missing_the_expected_key_raises_async(recording_server): + with recording_server([(200, {"wrong_key": {}})]) as (endpoint, _): + async with AsyncSeam(api_key="seam_apikey_token", endpoint=endpoint) as seam: + with pytest.raises( + SeamHttpInvalidResponseError, + match='expected "device", which the response does not contain', + ): + await seam.devices.get(device_id=DEVICE_ID) + + +async def test_a_non_list_value_under_a_list_response_key_raises_async( + recording_server, +): + with recording_server([(200, {"devices": None})]) as (endpoint, _): + async with AsyncSeam(api_key="seam_apikey_token", endpoint=endpoint) as seam: + with pytest.raises( + SeamHttpInvalidResponseError, + match='expected "devices", got NoneType instead of a list', + ): + await seam.devices.list()