diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 3c39e7b..80a55b6 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -2,23 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --resolver=backtracking requirements.in +# pip-compile --strip-extras requirements.in # -beartype==0.14.0 +beartype==0.18.5 # via -r requirements.in -certifi==2023.5.7 +certifi==2024.2.2 # via requests -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests -idna==3.4 +idna==3.7 # via requests pyrfc3339==1.1 # via -r requirements.in -pytz==2023.3 +pytz==2024.1 # via pyrfc3339 requests==2.31.0 # via -r requirements.in -typing-extensions==4.6.2 +typing-extensions==4.11.0 # via -r requirements.in -urllib3==2.0.2 +urllib3==2.2.1 # via requests diff --git a/selfhost_client/base_client.py b/selfhost_client/base_client.py index 1ce8c8e..c7b916b 100644 --- a/selfhost_client/base_client.py +++ b/selfhost_client/base_client.py @@ -127,6 +127,10 @@ def _process_response(self, response: Response) -> Optional[Any]: except json.decoder.JSONDecodeError: return response.content or None elif 400 <= response.status_code < 500: - raise responses[response.status_code] + if response.json(): + raise responses[response.status_code]( + message=response.json().get("error") + ) + raise responses[response.status_code](message=response.text) else: raise SelfHostInternalServerException diff --git a/selfhost_client/things_client.py b/selfhost_client/things_client.py index b413fa8..955768c 100644 --- a/selfhost_client/things_client.py +++ b/selfhost_client/things_client.py @@ -7,7 +7,7 @@ from .base_client import BaseClient from .types.dataset_types import DatasetType -from .types.thing_types import ThingType +from .types.thing_types import ThingType, ThingParameterType from .types.timeseries_types import TimeseriesType from .utils import filter_none_values_from_dict @@ -21,20 +21,22 @@ class ThingsClient(BaseClient): """ @beartype - def __init__(self, - base_url: Optional[str] = None, - username: Optional[str] = None, - password: Optional[str] = None - ) -> None: + def __init__( + self, + base_url: Optional[str] = None, + username: Optional[str] = None, + password: Optional[str] = None, + ) -> None: super().__init__(base_url, username, password) - self._things_api_path = 'things' + self._things_api_path = "things" @beartype - def get_things(self, - limit: Optional[int] = None, - offset: Optional[int] = None, - tags: Optional[List[str]] = None - ) -> List[ThingType]: + def get_things( + self, + limit: Optional[int] = None, + offset: Optional[int] = None, + tags: Optional[List[str]] = None, + ) -> List[ThingType]: """Fetches things from NODA Self-host API Args: @@ -54,21 +56,20 @@ def get_things(self, from fulfilling the request. """ response: Response = self._session.get( - url=f'{self._base_url}/{self._api_version}/{self._things_api_path}', - params=filter_none_values_from_dict({ - 'limit': limit, - 'offset': offset, - 'tags': tags - }) + url=f"{self._base_url}/{self._api_version}/{self._things_api_path}", + params=filter_none_values_from_dict( + {"limit": limit, "offset": offset, "tags": tags} + ), ) return self._process_response(response) @beartype - def create_thing(self, - name: str, - thing_type: Optional[str] = None, - tags: Optional[List[str]] = None - ) -> ThingType: + def create_thing( + self, + name: str, + thing_type: Optional[str] = None, + tags: Optional[List[str]] = None, + ) -> ThingType: """Add a new thing to the NODA Self-host API Args: @@ -88,12 +89,10 @@ def create_thing(self, from fulfilling the request. """ response: Response = self._session.post( - url=f'{self._base_url}/{self._api_version}/{self._things_api_path}', - json=filter_none_values_from_dict({ - 'name': name, - 'type': thing_type, - 'tags': tags - }) + url=f"{self._base_url}/{self._api_version}/{self._things_api_path}", + json=filter_none_values_from_dict( + {"name": name, "type": thing_type, "tags": tags} + ), ) return self._process_response(response) @@ -117,18 +116,19 @@ def get_thing(self, thing_uuid: str) -> ThingType: from fulfilling the request. """ response: Response = self._session.get( - url=f'{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}' + url=f"{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}" ) return self._process_response(response) @beartype - def update_thing(self, - thing_uuid: str, - name: Optional[str] = None, - state: Optional[str] = None, - thing_type: Optional[str] = None, - tags: Optional[List[str]] = None - ) -> None: + def update_thing( + self, + thing_uuid: str, + name: Optional[str] = None, + state: Optional[str] = None, + thing_type: Optional[str] = None, + tags: Optional[List[str]] = None, + ) -> None: """Updates a thing from NODA Self-host API Args: @@ -157,13 +157,10 @@ def update_thing(self, from fulfilling the request. """ response: Response = self._session.put( - url=f'{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}', - json=filter_none_values_from_dict({ - 'name': name, - 'state': state, - 'type': thing_type, - 'tags': tags - }) + url=f"{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}", + json=filter_none_values_from_dict( + {"name": name, "state": state, "type": thing_type, "tags": tags} + ), ) return self._process_response(response) @@ -184,7 +181,7 @@ def delete_thing(self, thing_uuid: str) -> None: from fulfilling the request. """ response: Response = self._session.delete( - url=f'{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}' + url=f"{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}" ) return self._process_response(response) @@ -208,7 +205,7 @@ def get_thing_datasets(self, thing_uuid: str) -> List[DatasetType]: from fulfilling the request. """ response: Response = self._session.get( - url=f'{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}/datasets' + url=f"{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}/datasets" ) return self._process_response(response) @@ -232,6 +229,68 @@ def get_thing_timeseries(self, thing_uuid: str) -> List[TimeseriesType]: from fulfilling the request. """ response: Response = self._session.get( - url=f'{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}/timeseries' + url=f"{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}/timeseries" + ) + return self._process_response(response) + + @beartype + def get_thing_parameters( + self, + thing_uuid: str, + startsWith: Optional[str] = None, + endsWith: Optional[str] = None, + ) -> List[ThingParameterType]: + """Returns a list of parameters associated with the specified thing from NODA Self-host API + + Args: + thing_uuid (str): UUID of the target user. + startsWith (Optional[str], optional): Parameter name prefix. Defaults to None. + endsWith (Optional[str], optional): Parameter name suffix. Defaults to None. + + Returns: + List[:class:`.ParameterType`] + + Raises: + :class:`.SelfHostBadRequestException`: Sent request had insufficient data or invalid options. + :class:`.SelfHostUnauthorizedException`: Request was refused due to lacking authentication credentials. + :class:`.SelfHostForbiddenException`: Server understands the request but refuses to authorize it. + :class:`.SelfHostNotFoundException`: The requested resource was not found. + :class:`.SelfHostTooManyRequestsException`: Sent too many requests in a given amount of time. + :class:`.SelfHostInternalServerException`: Server encountered an unexpected condition that prevented it + from fulfilling the request. + """ + response: Response = self._session.get( + url=f"{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}/params", + params=filter_none_values_from_dict( + {"startsWith": startsWith, "endsWith": endsWith} + ), + ) + return self._process_response(response) + + @beartype + def set_thing_parameters( + self, + thing_uuid: str, + parameters: List[ThingParameterType], + ) -> None: + """Sets a list of parameters associated with the specified thing from NODA Self-host API + + Args: + thing_uuid (str): UUID of the target user. + parameters (List[:class:`.ParameterType`]): List of parameters to set. + + Raises: + :class:`.SelfHostBadRequestException`: Sent request had insufficient data or invalid options. + :class:`.SelfHostUnauthorizedException`: Request was refused due to lacking authentication credentials. + :class:`.SelfHostForbiddenException`: Server understands the request but refuses to authorize it. + :class:`.SelfHostNotFoundException`: The requested resource was not found. + :class:`.SelfHostTooManyRequestsException`: Sent too many requests in a given amount of time. + :class:`.SelfHostInternalServerException`: Server encountered an unexpected condition that prevented it + from fulfilling the request. + """ + + response: Response = self._session.put( + url=f"{self._base_url}/{self._api_version}/{self._things_api_path}/{thing_uuid}/params", + json=parameters, ) return self._process_response(response) diff --git a/selfhost_client/timeseries_client.py b/selfhost_client/timeseries_client.py index 4b7dc4f..fa03b67 100644 --- a/selfhost_client/timeseries_client.py +++ b/selfhost_client/timeseries_client.py @@ -9,11 +9,11 @@ from .base_client import BaseClient from .types.timeseries_types import ( - TimeseriesType, - TimeseriesDataPointType, - TimeseriesDataType, TimeseriesDataPointResponse, + TimeseriesDataPointType, TimeseriesDataResponse, + TimeseriesDataType, + TimeseriesType, ) from .utils import filter_none_values_from_dict diff --git a/selfhost_client/types/thing_types.py b/selfhost_client/types/thing_types.py index c88ed5f..0a9c566 100644 --- a/selfhost_client/types/thing_types.py +++ b/selfhost_client/types/thing_types.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Union try: from typing import TypedDict @@ -40,9 +40,15 @@ class ThingType(TypedDict): } """ + uuid: str name: str state: str type: str created_by: str tags: List[str] + + +class ThingParameterType(TypedDict): + key: str + value: Union[str, int, float, bool]