From 9e07d4354d117cafbfac9080345a20a2ddb1c014 Mon Sep 17 00:00:00 2001 From: Mateus Silva Date: Thu, 11 Dec 2025 16:37:37 -0300 Subject: [PATCH 1/5] feat: add chunk management and data backup/restore methods to Devices class Implemented four new methods to achieve feature parity with JavaScript SDK: - getChunk: retrieve chunk information from immutable devices - deleteChunk: delete specific chunks from immutable devices - dataBackup: export device data to TagoIO Files with CSV format - dataRestore: import device data from CSV files Added corresponding TypedDict definitions for type safety and updated all existing method docstrings to follow consistent documentation pattern. --- .../modules/Resources/Device_Type.py | 57 ++ src/tagoio_sdk/modules/Resources/Devices.py | 534 ++++++++++++++---- tests/Resources/test_devices.py | 418 +++++++++++++- 3 files changed, 880 insertions(+), 129 deletions(-) diff --git a/src/tagoio_sdk/modules/Resources/Device_Type.py b/src/tagoio_sdk/modules/Resources/Device_Type.py index ee56efc..6b87af0 100644 --- a/src/tagoio_sdk/modules/Resources/Device_Type.py +++ b/src/tagoio_sdk/modules/Resources/Device_Type.py @@ -488,3 +488,60 @@ class ListDeviceTokenQuery(TypedDict): """ Tuple with a field and an order """ + + +class DeviceChunkData(TypedDict): + id: str + """ + Chunk ID in format 'from_to' (Unix timestamps). + """ + from_date: str + """ + Start date of the chunk (ISO 8601). + """ + to_date: str + """ + End date of the chunk (ISO 8601). + """ + amount: Union[int, float] + """ + Amount of data records in the chunk. + """ + + +class DeviceDataBackup(TypedDict): + deviceID: GenericID + """ + Device ID. + """ + file_address: str + """ + File path in TagoIO Files where backup will be saved. + Can use template variables: $DEVICE$, $CHUNK$, $FROM$, $TO$, $TIMESTAMP$. + """ + headers: Optional[bool] + """ + Include CSV headers in the exported file. + """ + + +class DeviceDataBackupResponse(TypedDict): + file_address: str + """ + Final file path where the backup was saved. + """ + chunk_id: Optional[str] + """ + Chunk ID if backup was for a specific chunk. + """ + + +class DeviceDataRestore(TypedDict): + deviceID: GenericID + """ + Device ID. + """ + file_address: str + """ + File path in TagoIO Files to restore data from (CSV format). + """ diff --git a/src/tagoio_sdk/modules/Resources/Devices.py b/src/tagoio_sdk/modules/Resources/Devices.py index 60782eb..674b58a 100644 --- a/src/tagoio_sdk/modules/Resources/Devices.py +++ b/src/tagoio_sdk/modules/Resources/Devices.py @@ -1,3 +1,4 @@ +from typing import List from typing import Optional from typing import Union @@ -11,8 +12,12 @@ from tagoio_sdk.modules.Device.Device_Type import DataQuery from tagoio_sdk.modules.Device.Device_Type import DeviceInfo from tagoio_sdk.modules.Resources.Device_Type import ConfigurationParams +from tagoio_sdk.modules.Resources.Device_Type import DeviceChunkData from tagoio_sdk.modules.Resources.Device_Type import DeviceCreateInfo from tagoio_sdk.modules.Resources.Device_Type import DeviceCreateResponse +from tagoio_sdk.modules.Resources.Device_Type import DeviceDataBackup +from tagoio_sdk.modules.Resources.Device_Type import DeviceDataBackupResponse +from tagoio_sdk.modules.Resources.Device_Type import DeviceDataRestore from tagoio_sdk.modules.Resources.Device_Type import DeviceEditInfo from tagoio_sdk.modules.Resources.Device_Type import DeviceListItem from tagoio_sdk.modules.Resources.Device_Type import DeviceQuery @@ -26,20 +31,25 @@ class Devices(TagoIOModule): def listDevice(self, queryObj: DeviceQuery = None) -> list[DeviceListItem]: """ - Retrieves a list with all devices from the account + @description: + Retrieves a list of all devices from the account with optional filtering and pagination. - :default: + @see: + https://help.tago.io/portal/en/kb/articles/478-devices Devices Overview - queryObj: { + @example: + If receive an error "Authorization Denied", check policy **Device** / **Access** in Access Management. + ```python + resources = Resources() + devices = resources.devices.listDevice({ "page": 1, - "fields": ["id", "name"], - "filter": {}, "amount": 20, - "orderBy": ["name", "asc"], - "resolveBucketName": False - } - - :param DeviceQuery queryObj: Search query params + "fields": ["id", "name", "active"], + "filter": {"name": "Temperature*"}, + "orderBy": ["name", "asc"] + }) + print(devices) # [{'id': 'device-id-123', 'name': 'Temperature Sensor', ...}] + ``` """ if queryObj is None: queryObj = {} @@ -80,9 +90,28 @@ def listDevice(self, queryObj: DeviceQuery = None) -> list[DeviceListItem]: def create(self, deviceObj: DeviceCreateInfo) -> DeviceCreateResponse: """ - Generates and retrieves a new action from the Device - - :param DeviceCreateInfo deviceObj: Object data to create new device + @description: + Creates a new device in the account with specified configuration and returns device credentials. + + @see: + https://help.tago.io/portal/en/kb/articles/478-devices Devices Overview + https://help.tago.io/portal/en/kb/articles/481-device-types Device Types + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Create** in Access Management. + ```python + resources = Resources() + result = resources.devices.create({ + "name": "Temperature Sensor", + "network": "network-id-123", + "connector": "connector-id-456", + "type": "immutable", + "chunk_period": "month", + "chunk_retention": 6, + "active": True + }) + print(result) # {'device_id': '...', 'bucket_id': '...', 'token': '...'} + ``` """ result = self.doRequest( { @@ -95,11 +124,23 @@ def create(self, deviceObj: DeviceCreateInfo) -> DeviceCreateResponse: def edit(self, deviceID: GenericID, deviceObj: DeviceEditInfo) -> str: """ - Modify any property of the device + @description: + Modifies any property of an existing device. - :param GenericID deviceID: Device ID + @see: + https://help.tago.io/portal/en/kb/articles/478-devices Devices Overview - :param DeviceEditInfo deviceObj: Device object with fields to replace + @example: + If receive an error "Authorization Denied", check policy **Device** / **Edit** in Access Management. + ```python + resources = Resources() + result = resources.devices.edit("device-id-123", { + "name": "Updated Sensor Name", + "active": False, + "tags": [{"key": "location", "value": "warehouse"}] + }) + print(result) # Successfully Updated + ``` """ result = self.doRequest( { @@ -112,9 +153,19 @@ def edit(self, deviceID: GenericID, deviceObj: DeviceEditInfo) -> str: def delete(self, deviceID: GenericID) -> str: """ - Deletes an device from the account + @description: + Permanently deletes a device from the account along with all its data. - :param GenericID deviceID: Device ID + @see: + https://help.tago.io/portal/en/kb/articles/478-devices Devices Overview + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Delete** in Access Management. + ```python + resources = Resources() + result = resources.devices.delete("device-id-123") + print(result) # Successfully Removed + ``` """ result = self.doRequest( { @@ -126,9 +177,19 @@ def delete(self, deviceID: GenericID) -> str: def info(self, deviceID: GenericID) -> DeviceInfo: """ - Get Info of the Device + @description: + Retrieves detailed information about a specific device. + + @see: + https://help.tago.io/portal/en/kb/articles/478-devices Devices Overview - :param GenericID deviceID: Device ID + @example: + If receive an error "Authorization Denied", check policy **Device** / **Access** in Access Management. + ```python + resources = Resources() + device_info = resources.devices.info("device-id-123") + print(device_info) # {'id': '...', 'name': '...', 'type': 'mutable', ...} + ``` """ result = self.doRequest( { @@ -157,13 +218,30 @@ def paramSet( paramID: Optional[GenericID] = None, ) -> str: """ - Create or edit param for the Device - - :param deviceID Device ID - - :param configObj Configuration Data - - :param paramID Parameter ID + @description: + Creates new configuration parameters or updates existing ones for a device. + + @see: + https://help.tago.io/portal/en/kb/articles/configuration-parameters Configuration Parameters + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Edit** in Access Management. + ```python + resources = Resources() + # Create new parameter + result = resources.devices.paramSet("device-id-123", { + "key": "threshold", + "value": "25.5", + "sent": False + }) + # Update existing parameter + result = resources.devices.paramSet("device-id-123", { + "key": "threshold", + "value": "30.0", + "sent": False + }, "param-id-456") + print(result) # Successfully Updated + ``` """ body = configObj if paramID and not isinstance(configObj, list): @@ -186,11 +264,22 @@ def paramList( self, deviceID: GenericID, sentStatus: bool = None ) -> list[ConfigurationParams]: """ - List Params for the Device + @description: + Retrieves a list of configuration parameters for a device. - :param GenericID deviceID: Device ID + @see: + https://help.tago.io/portal/en/kb/articles/configuration-parameters Configuration Parameters - :param bool sentStatus: True return only sent=true, False return only sent=false + @example: + If receive an error "Authorization Denied", check policy **Device** / **Access** in Access Management. + ```python + resources = Resources() + # Get all parameters + params = resources.devices.paramList("device-id-123") + # Get only sent parameters + sent_params = resources.devices.paramList("device-id-123", sentStatus=True) + print(params) # [{'id': '...', 'key': 'threshold', 'value': '25.5', 'sent': False}] + ``` """ result = self.doRequest( { @@ -203,11 +292,19 @@ def paramList( def paramRemove(self, deviceID: GenericID, paramID: GenericID) -> str: """ - Remove param for the Device + @description: + Removes a specific configuration parameter from a device. - :param GenericID deviceID: Device ID + @see: + https://help.tago.io/portal/en/kb/articles/configuration-parameters Configuration Parameters - :param GenericID paramID: Parameter ID + @example: + If receive an error "Authorization Denied", check policy **Device** / **Edit** in Access Management. + ```python + resources = Resources() + result = resources.devices.paramRemove("device-id-123", "param-id-456") + print(result) # Successfully Removed + ``` """ result = self.doRequest( { @@ -223,21 +320,24 @@ def tokenList( queryObj: ListDeviceTokenQuery = None, ) -> list[DeviceTokenDataList]: """ - Retrieves a list of all tokens + @description: + Retrieves a list of all authentication tokens for a device with optional filtering. - :default: + @see: + https://help.tago.io/portal/en/kb/articles/495-access-tokens Device Tokens - queryObj: { + @example: + If receive an error "Authorization Denied", check policy **Device** / **Access** in Access Management. + ```python + resources = Resources() + tokens = resources.devices.tokenList("device-id-123", { "page": 1, - "fields": ["name", "token", "permission"], - "filter": {}, "amount": 20, - "orderBy": ["created_at", "desc"], - } - - :param GenericID deviceID: Device ID - - :param ListDeviceTokenQuery queryObj: Search query params + "fields": ["name", "token", "permission"], + "orderBy": ["created_at", "desc"] + }) + print(tokens) # [{'name': 'Default Token', 'token': '...', 'permission': 'full', ...}] + ``` """ if queryObj is None: @@ -262,9 +362,7 @@ def tokenList( }, } ) - result = dateParserList( - result, ["created_at", "expire_time"] - ) + result = dateParserList(result, ["created_at", "expire_time"]) return result @@ -272,11 +370,23 @@ def tokenCreate( self, deviceID: GenericID, tokenParams: TokenData ) -> TokenCreateResponse: """ - Generates and retrieves a new token + @description: + Generates and retrieves a new authentication token for a device. - :param deviceID: Device ID + @see: + https://help.tago.io/portal/en/kb/articles/495-access-tokens Device Tokens - :param tokenParams: Params for new token + @example: + If receive an error "Authorization Denied", check policy **Device** / **Create Token** in Access Management. + ```python + resources = Resources() + result = resources.devices.tokenCreate("device-id-123", { + "name": "Production Token", + "permission": "write", + "expire_time": "never" + }) + print(result) # {'token': 'new-token-value', 'expire_date': None} + ``` """ result = self.doRequest( { @@ -290,9 +400,19 @@ def tokenCreate( def tokenDelete(self, token: GenericToken) -> str: """ - Delete a token + @description: + Permanently deletes an authentication token. + + @see: + https://help.tago.io/portal/en/kb/articles/495-access-tokens Device Tokens - :param GenericToken token: Token + @example: + If receive an error "Authorization Denied", check policy **Device** / **Delete Token** in Access Management. + ```python + resources = Resources() + result = resources.devices.tokenDelete("token-to-delete") + print(result) # Successfully Removed + ``` """ result = self.doRequest( { @@ -306,21 +426,25 @@ def getDeviceData( self, deviceID: GenericID, queryParams: DataQuery = None ) -> list[Data]: """ - Get data from all variables in the device. - - :param deviceID: Device ID. - - :param queryParams: Query parameters to filter the results. - - :rtype: Array with the data values stored in the device. - - :example: - - myDevice = Account({ "token": "my_device_token" }) - - result = myAccount.devices.getDeviceData( - "Device Id", {"variables": "location"} - ) + @description: + Retrieves data from all variables in the device with optional query filters. + + @see: + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Access** in Access Management. + ```python + resources = Resources() + # Get all data + data = resources.devices.getDeviceData("device-id-123") + # Get specific variable data + temp_data = resources.devices.getDeviceData("device-id-123", { + "variables": ["temperature"], + "qty": 10 + }) + print(temp_data) # [{'variable': 'temperature', 'value': 25.5, 'time': '...', ...}] + ``` """ if queryParams is None: queryParams = {} @@ -335,11 +459,19 @@ def getDeviceData( def emptyDeviceData(self, deviceID: GenericID) -> str: """ - Empty all data in a device. + @description: + Permanently removes all data from a device. This operation cannot be undone. - :param GenericID deviceID: Device ID. + @see: + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management - :rtype: Success message. + @example: + If receive an error "Authorization Denied", check policy **Device** / **Edit** in Access Management. + ```python + resources = Resources() + result = resources.devices.emptyDeviceData("device-id-123") + print(result) # All data has been removed + ``` """ result = self.doRequest( { @@ -353,26 +485,30 @@ def sendDeviceData( self, deviceID: GenericID, data: Union[DataCreate, list[DataCreate]] ) -> str: """ - Send data to a device. - - :param GenericID deviceID: Device ID. - - :param DataCreate data: An array or one object with data to be send to TagoIO. - - :rtype: Success message. - - Example:: - ```python - # Example of using the function - resources = Resource() - resource.devices.sendDeviceData("myDeviceID", { - "variable": "temperature", - "unit": "F", - "value": 55, - "time": "2015-11-03 13:44:33", - "location": { "lat": 42.2974279, "lng": -85.628292 }, - }) - ``` + @description: + Sends data to a device. Accepts a single data object or an array of data objects. + + @see: + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Edit** in Access Management. + ```python + resources = Resources() + result = resources.devices.sendDeviceData("device-id-123", { + "variable": "temperature", + "value": 25.5, + "unit": "°C", + "time": "2025-01-09 13:44:33", + "location": {"lat": 42.2974279, "lng": -85.628292} + }) + # Send multiple data points + result = resources.devices.sendDeviceData("device-id-123", [ + {"variable": "temperature", "value": 25.5}, + {"variable": "humidity", "value": 60} + ]) + print(result) # Successfully Inserted + ``` """ result = self.doRequest( { @@ -388,20 +524,28 @@ def editDeviceData( self, deviceID: GenericID, updatedData: Union[DataEdit, list[DataEdit]] ) -> str: """ - Edit data in a device. - - :param GenericID deviceID: Device ID. - - :param DataEdit data: A single or an array of updated data records. - - :rtype: Success message. - - Example:: - ```python - # Example of using the function - resources = Resource() - resource.devices.editDeviceData("myDeviceID", {"id": "idOfTheRecord", "value": "new value", "unit": "new unit"}) - ``` + @description: + Modifies existing data records in a device. Requires the data record ID. + + @see: + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Edit** in Access Management. + ```python + resources = Resources() + result = resources.devices.editDeviceData("device-id-123", { + "id": "data-record-id", + "value": 30.0, + "unit": "°F" + }) + # Edit multiple records + result = resources.devices.editDeviceData("device-id-123", [ + {"id": "record-1-id", "value": 25.5}, + {"id": "record-2-id", "value": 65} + ]) + print(result) # Device Data Updated + ``` """ result = self.doRequest( { @@ -417,20 +561,27 @@ def deleteDeviceData( self, deviceID: GenericID, queryParam: DataQuery = None ) -> str: """ - Delete data from a device. - - :param GenericID deviceID: Device ID. - - :param DataQuery queryParam: Parameters to specify what should be deleted on the device's data. - - :rtype: Success message. - - Example:: - ```python - # Example of using the function - resources = Resource() - resource.devices.deleteDeviceData("myDeviceID", {"ids": ["recordIdToDelete", "anotherRecordIdToDelete" ]}) - ``` + @description: + Deletes data from a device based on specified query parameters. + + @see: + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Edit** in Access Management. + ```python + resources = Resources() + # Delete specific records by ID + result = resources.devices.deleteDeviceData("device-id-123", { + "ids": ["record-id-1", "record-id-2"] + }) + # Delete by variable + result = resources.devices.deleteDeviceData("device-id-123", { + "variables": ["old_sensor"], + "qty": 100 + }) + print(result) # Successfully Removed + ``` """ if queryParam is None: queryParam = {} @@ -446,8 +597,19 @@ def deleteDeviceData( def amount(self, deviceID: GenericID) -> Union[int, float]: """ - Get Amount of data stored in the Device - :param deviceID: Device ID + @description: + Gets the amount of data stored in a device. + + @see: + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Access** in Access Management. + ```python + resources = Resources() + amount = resources.devices.amount("device-id-123") + print(amount) # 15234 + ``` """ result = self.doRequest( { @@ -456,3 +618,135 @@ def amount(self, deviceID: GenericID) -> Union[int, float]: } ) return result + + def getChunk(self, deviceID: GenericID) -> List[DeviceChunkData]: + """ + @description: + Retrieves chunk information from an immutable device. + + @see: + https://help.tago.io/portal/en/kb/articles/chunk-management Chunk Management + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Manage chunks** in Access Management. + ```python + resources = Resources() + chunks = resources.devices.getChunk("device-id-123") + print(chunks) # [{'amount': 0, 'id': 'chunk-id-123', 'from_date': '2025-01-09T00:00:00.000+00:00', ...}] + ``` + """ + result = self.doRequest( + { + "path": f"/device/{deviceID}/chunk", + "method": "GET", + } + ) + + return result + + def deleteChunk(self, deviceID: GenericID, chunkID: GenericID) -> str: + """ + @description: + Deletes a chunk from an immutable device. + + @see: + https://help.tago.io/portal/en/kb/articles/chunk-management#Delete_chunks Delete Chunks + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Manage chunks** in Access Management. + ```python + resources = Resources() + result = resources.devices.deleteChunk("device-id-123", "chunk-id-123") + print(result) # Chunk chunk-id-123 deleted + ``` + """ + result = self.doRequest( + { + "path": f"/device/{deviceID}/chunk/{chunkID}", + "method": "DELETE", + } + ) + + return result + + def dataBackup( + self, params: DeviceDataBackup, chunkID: Optional[GenericID] = None + ) -> DeviceDataBackupResponse: + """ + @description: + Schedule to export the device's data to TagoIO Files. + For mutable devices, exports all data. For immutable devices with chunkID, exports specific chunk. + + @see: + https://help.tago.io/portal/en/kb/articles/55-data-export Data Export + https://help.tago.io/portal/en/kb/articles/device-data#Backing_Up_Data Backing Up Data + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Export Data** in Access Management. + ```python + resources = Resources() + import time + timestamp = int(time.time()) + result = resources.devices.dataBackup({ + "deviceID": "device-id-123", + "file_address": f"/backups/device-id-123/{timestamp}", + "headers": True + }) + print(result) # {'file_address': 'backups/device-id-123/1736433519380.csv'} + ``` + """ + body = { + "chunk_id": chunkID, + "headers": params.get("headers"), + "file_address": params.get("file_address"), + } + + path = ( + f"/device/{params['deviceID']}/chunk/backup" + if chunkID + else f"/device/{params['deviceID']}/data/backup" + ) + + result = self.doRequest( + { + "path": path, + "method": "POST", + "body": body, + } + ) + + return result + + def dataRestore(self, params: DeviceDataRestore) -> str: + """ + @description: + Restores data to a device from a CSV file in TagoIO Files. + + @see: + https://help.tago.io/portal/en/kb/articles/device-data#Importing Importing Device Data + https://api.docs.tago.io/#7ebca255-6c38-43d3-97d0-9b62155f202e Import Data API + + @example: + If receive an error "Authorization Denied", check policy **Device** / **Import Data** in Access Management. + ```python + resources = Resources() + result = resources.devices.dataRestore({ + "deviceID": "device-id-123", + "file_address": "/backups/backup.csv" + }) + print(result) # Data import added to the queue successfully! + ``` + """ + body = { + "file_address": params.get("file_address"), + } + + result = self.doRequest( + { + "path": f"/device/{params['deviceID']}/data/restore", + "method": "POST", + "body": body, + } + ) + + return result diff --git a/tests/Resources/test_devices.py b/tests/Resources/test_devices.py index 5969776..e700c74 100644 --- a/tests/Resources/test_devices.py +++ b/tests/Resources/test_devices.py @@ -1,4 +1,8 @@ import os + +from typing import Any +from typing import Dict + from requests_mock.mocker import Mocker from tagoio_sdk.modules.Resources.Resources import Resources @@ -7,17 +11,139 @@ os.environ["T_ANALYSIS_TOKEN"] = "your_token_value" +# Mock data generators +def mockDeviceList() -> Dict[str, Any]: + """Generate mock device list response.""" + return { + "status": True, + "result": [ + { + "id": "device-id-123", + "name": "Temperature Sensor", + "active": True, + "type": "mutable", + "last_input": "2025-01-09T10:00:00.000Z", + "created_at": "2025-01-01T00:00:00.000Z", + } + ], + } + + +def mockDeviceCreate() -> Dict[str, Any]: + """Generate mock device create response.""" + return { + "status": True, + "result": { + "device_id": "device-id-new", + "bucket_id": "bucket-id-new", + "token": "new-token-123", + }, + } + + +def mockDeviceInfo() -> Dict[str, Any]: + """Generate mock device info response.""" + return { + "status": True, + "result": { + "id": "device-id-123", + "name": "Temperature Sensor", + "active": True, + "type": "mutable", + "bucket": {"id": "bucket-id-123", "name": "Device Data"}, + "network": "network-id-123", + "connector": "connector-id-123", + "last_input": "2025-01-09T10:00:00.000Z", + "created_at": "2025-01-01T00:00:00.000Z", + }, + } + + +def mockConfigParams() -> Dict[str, Any]: + """Generate mock configuration parameters.""" + return { + "status": True, + "result": [ + {"id": "param-id-123", "key": "threshold", "value": "25.5", "sent": False} + ], + } + + +def mockTokenList() -> Dict[str, Any]: + """Generate mock token list response.""" + return { + "status": True, + "result": [ + { + "token": "token-123", + "name": "Default Token", + "permission": "full", + "device_id": "device-id-123", + "created_at": "2025-01-01T00:00:00.000Z", + } + ], + } + + +def mockTokenCreate() -> Dict[str, Any]: + """Generate mock token create response.""" + return {"status": True, "result": {"token": "new-token-456", "expire_date": None}} + + +def mockDeviceData() -> Dict[str, Any]: + """Generate mock device data response.""" + return { + "status": True, + "result": [ + { + "id": "data-id-123", + "variable": "temperature", + "value": 25.5, + "unit": "°C", + "time": "2025-01-09T10:00:00.000Z", + } + ], + } + + +def mockChunkList() -> Dict[str, Any]: + """Generate mock chunk list response.""" + return { + "status": True, + "result": [ + { + "id": "1704067200_1706745600", + "from_date": "2025-01-01T00:00:00.000Z", + "to_date": "2025-02-01T00:00:00.000Z", + "amount": 1000, + } + ], + } + + +def mockBackupResponse() -> Dict[str, Any]: + """Generate mock backup response.""" + return { + "status": True, + "result": {"file_address": "/backups/device-123/backup.csv"}, + } + + def testSendDeviceDataMethod(requests_mock: Mocker) -> None: """ :param requests_mock are a plugin of pytest to mock the requests. """ deviceID = "device1" - requests_mock.post(f"https://api.tago.io/device/{deviceID}/data", - json={"status": True, "result": "1 Data Added"}) + requests_mock.post( + f"https://api.tago.io/device/{deviceID}/data", + json={"status": True, "result": "1 Data Added"}, + ) resources = Resources() - response = resources.devices.sendDeviceData(deviceID, {"variable": "test", "value": 1}) + response = resources.devices.sendDeviceData( + deviceID, {"variable": "test", "value": 1} + ) assert response == "1 Data Added" assert isinstance(response, str) @@ -29,11 +155,15 @@ def testEditDeviceDataMethod(requests_mock: Mocker) -> None: """ deviceID = "device1" - requests_mock.put(f"https://api.tago.io/device/{deviceID}/data", - json={"status": True, "result": "1 item(s) updated"}) + requests_mock.put( + f"https://api.tago.io/device/{deviceID}/data", + json={"status": True, "result": "1 item(s) updated"}, + ) resources = Resources() - response = resources.devices.editDeviceData(deviceID, {"id": "idOfTheRecord", "value": "new value", "unit": "new unit"}) + response = resources.devices.editDeviceData( + deviceID, {"id": "idOfTheRecord", "value": "new value", "unit": "new unit"} + ) assert response == "1 item(s) updated" assert isinstance(response, str) @@ -45,11 +175,281 @@ def testDeleteDeviceDataMethod(requests_mock: Mocker) -> None: """ deviceID = "device1" - requests_mock.delete(f"https://api.tago.io/device/{deviceID}/data", - json={"status": True, "result": "2 Data Removed"}) + requests_mock.delete( + f"https://api.tago.io/device/{deviceID}/data", + json={"status": True, "result": "2 Data Removed"}, + ) resources = Resources() - response = resources.devices.deleteDeviceData(deviceID, {"ids": ["recordIdToDelete", "anotherRecordIdToDelete"]}) + response = resources.devices.deleteDeviceData( + deviceID, {"ids": ["recordIdToDelete", "anotherRecordIdToDelete"]} + ) assert response == "2 Data Removed" assert isinstance(response, str) + + +def testListDevice(requests_mock: Mocker) -> None: + """Test listDevice method of Devices class.""" + mock_response = mockDeviceList() + requests_mock.get("https://api.tago.io/device", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.listDevice({"page": 1, "amount": 20}) + + assert isinstance(result, list) + assert len(result) > 0 + assert result[0]["id"] == "device-id-123" + + +def testCreateDevice(requests_mock: Mocker) -> None: + """Test create method of Devices class.""" + mock_response = mockDeviceCreate() + requests_mock.post("https://api.tago.io/device", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.create( + { + "name": "New Device", + "network": "network-id-123", + "connector": "connector-id-123", + "type": "mutable", + } + ) + + assert result["device_id"] == "device-id-new" + assert result["token"] == "new-token-123" + + +def testEditDevice(requests_mock: Mocker) -> None: + """Test edit method of Devices class.""" + device_id = "device-id-123" + requests_mock.put( + f"https://api.tago.io/device/{device_id}", + json={"status": True, "result": "Successfully Updated"}, + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.edit(device_id, {"name": "Updated Name"}) + + assert result == "Successfully Updated" + + +def testDeleteDevice(requests_mock: Mocker) -> None: + """Test delete method of Devices class.""" + device_id = "device-id-123" + requests_mock.delete( + f"https://api.tago.io/device/{device_id}", + json={"status": True, "result": "Successfully Removed"}, + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.delete(device_id) + + assert result == "Successfully Removed" + + +def testDeviceInfo(requests_mock: Mocker) -> None: + """Test info method of Devices class.""" + device_id = "device-id-123" + mock_response = mockDeviceInfo() + requests_mock.get(f"https://api.tago.io/device/{device_id}", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.info(device_id) + + assert result["id"] == "device-id-123" + assert result["name"] == "Temperature Sensor" + + +def testParamSet(requests_mock: Mocker) -> None: + """Test paramSet method of Devices class.""" + device_id = "device-id-123" + requests_mock.post( + f"https://api.tago.io/device/{device_id}/params", + json={"status": True, "result": "Successfully Updated"}, + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.paramSet( + device_id, {"key": "threshold", "value": "30", "sent": False} + ) + + assert result == "Successfully Updated" + + +def testParamList(requests_mock: Mocker) -> None: + """Test paramList method of Devices class.""" + device_id = "device-id-123" + mock_response = mockConfigParams() + requests_mock.get( + f"https://api.tago.io/device/{device_id}/params", json=mock_response + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.paramList(device_id) + + assert isinstance(result, list) + assert result[0]["key"] == "threshold" + + +def testParamRemove(requests_mock: Mocker) -> None: + """Test paramRemove method of Devices class.""" + device_id = "device-id-123" + param_id = "param-id-123" + requests_mock.delete( + f"https://api.tago.io/device/{device_id}/params/{param_id}", + json={"status": True, "result": "Successfully Removed"}, + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.paramRemove(device_id, param_id) + + assert result == "Successfully Removed" + + +def testTokenList(requests_mock: Mocker) -> None: + """Test tokenList method of Devices class.""" + device_id = "device-id-123" + mock_response = mockTokenList() + requests_mock.get( + f"https://api.tago.io/device/token/{device_id}", json=mock_response + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.tokenList(device_id) + + assert isinstance(result, list) + assert result[0]["token"] == "token-123" + + +def testTokenCreate(requests_mock: Mocker) -> None: + """Test tokenCreate method of Devices class.""" + mock_response = mockTokenCreate() + requests_mock.post("https://api.tago.io/device/token", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.tokenCreate( + "device-id-123", {"name": "New Token", "permission": "write"} + ) + + assert result["token"] == "new-token-456" + + +def testTokenDelete(requests_mock: Mocker) -> None: + """Test tokenDelete method of Devices class.""" + token = "token-to-delete" + requests_mock.delete( + f"https://api.tago.io/device/token/{token}", + json={"status": True, "result": "Successfully Removed"}, + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.tokenDelete(token) + + assert result == "Successfully Removed" + + +def testGetDeviceData(requests_mock: Mocker) -> None: + """Test getDeviceData method of Devices class.""" + device_id = "device-id-123" + mock_response = mockDeviceData() + requests_mock.get( + f"https://api.tago.io/device/{device_id}/data", json=mock_response + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.getDeviceData(device_id, {"variables": ["temperature"]}) + + assert isinstance(result, list) + assert result[0]["variable"] == "temperature" + + +def testEmptyDeviceData(requests_mock: Mocker) -> None: + """Test emptyDeviceData method of Devices class.""" + device_id = "device-id-123" + requests_mock.post( + f"https://api.tago.io/device/{device_id}/empty", + json={"status": True, "result": "All data removed"}, + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.emptyDeviceData(device_id) + + assert result == "All data removed" + + +def testAmount(requests_mock: Mocker) -> None: + """Test amount method of Devices class.""" + device_id = "device-id-123" + requests_mock.get( + f"https://api.tago.io/device/{device_id}/data_amount", + json={"status": True, "result": 15234}, + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.amount(device_id) + + assert result == 15234 + + +def testGetChunk(requests_mock: Mocker) -> None: + """Test getChunk method of Devices class.""" + device_id = "device-id-123" + mock_response = mockChunkList() + requests_mock.get( + f"https://api.tago.io/device/{device_id}/chunk", json=mock_response + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.getChunk(device_id) + + assert isinstance(result, list) + assert result[0]["amount"] == 1000 + + +def testDeleteChunk(requests_mock: Mocker) -> None: + """Test deleteChunk method of Devices class.""" + device_id = "device-id-123" + chunk_id = "chunk-id-456" + requests_mock.delete( + f"https://api.tago.io/device/{device_id}/chunk/{chunk_id}", + json={"status": True, "result": f"Chunk {chunk_id} deleted"}, + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.deleteChunk(device_id, chunk_id) + + assert result == f"Chunk {chunk_id} deleted" + + +def testDataBackup(requests_mock: Mocker) -> None: + """Test dataBackup method of Devices class.""" + device_id = "device-id-123" + mock_response = mockBackupResponse() + requests_mock.post( + f"https://api.tago.io/device/{device_id}/data/backup", json=mock_response + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.dataBackup( + {"deviceID": device_id, "file_address": "/backups/backup.csv", "headers": True} + ) + + assert result["file_address"] == "/backups/device-123/backup.csv" + + +def testDataRestore(requests_mock: Mocker) -> None: + """Test dataRestore method of Devices class.""" + device_id = "device-id-123" + requests_mock.post( + f"https://api.tago.io/device/{device_id}/data/restore", + json={"status": True, "result": "Data import added to the queue successfully!"}, + ) + + resources = Resources({"token": "your_token_value"}) + result = resources.devices.dataRestore( + {"deviceID": device_id, "file_address": "/backups/restore.csv"} + ) + + assert result == "Data import added to the queue successfully!" From 0e512645975de6fb18cf9dd4157479a28bb0a741 Mon Sep 17 00:00:00 2001 From: Mateus Silva Date: Thu, 11 Dec 2025 16:38:33 -0300 Subject: [PATCH 2/5] docs: add comprehensive Sphinx RST documentation for Devices class Documented all 21 Devices class methods with descriptions, parameters, return types, and code examples. Added TypedDict documentation for new data structures including DeviceChunkData, DeviceDataBackup, DeviceDataBackupResponse, and DeviceDataRestore. All documentation follows RST formatting standards with proper cross-references and builds successfully without warnings. --- .../source/Resources/Devices/Devices_Type.rst | 74 ++ docs/source/Resources/Devices/index.rst | 666 ++++++++++++++---- 2 files changed, 615 insertions(+), 125 deletions(-) diff --git a/docs/source/Resources/Devices/Devices_Type.rst b/docs/source/Resources/Devices/Devices_Type.rst index adc54c3..2b2f5d8 100644 --- a/docs/source/Resources/Devices/Devices_Type.rst +++ b/docs/source/Resources/Devices/Devices_Type.rst @@ -485,3 +485,77 @@ ListDeviceTokenQuery ]] | [Optional] Tuple with a field and an order + +.. _DeviceChunkData: + +DeviceChunkData +--------------- + + Chunk information from an immutable device. + + **Attributes:** + + | **id**: str + | Chunk ID in format 'from_to' (Unix timestamps). + + | **from_date**: str + | Start date of the chunk (ISO 8601). + + | **to_date**: str + | End date of the chunk (ISO 8601). + + | **amount**: int or float + | Amount of data records in the chunk. + + +.. _DeviceDataBackup: + +DeviceDataBackup +---------------- + + Parameters for device data backup operation. + + **Attributes:** + + | **deviceID**: :ref:`GenericID` + | Device ID. + + | **file_address**: str + | File path in TagoIO Files where backup will be saved. + | Can use template variables: $DEVICE$, $CHUNK$, $FROM$, $TO$, $TIMESTAMP$. + + | **headers**: Optional[bool] + | [Optional] Include CSV headers in the exported file. + + +.. _DeviceDataBackupResponse: + +DeviceDataBackupResponse +------------------------ + + Response from device data backup operation. + + **Attributes:** + + | **file_address**: str + | Final file path where the backup was saved. + + | **chunk_id**: Optional[str] + | [Optional] Chunk ID if backup was for a specific chunk. + + +.. _DeviceDataRestore: + +DeviceDataRestore +----------------- + + Parameters for device data restore operation. + + **Attributes:** + + | **deviceID**: :ref:`GenericID` + | Device ID. + + | **file_address**: str + | File path in TagoIO Files to restore data from (CSV format). + diff --git a/docs/source/Resources/Devices/index.rst b/docs/source/Resources/Devices/index.rst index 996834b..cf019d5 100644 --- a/docs/source/Resources/Devices/index.rst +++ b/docs/source/Resources/Devices/index.rst @@ -3,283 +3,699 @@ Manage devices in account. -======= +====== +amount +====== + +Gets the amount of data stored in a device. + + **See:** + + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management + + **Parameters:** + + | **deviceID**: :ref:`GenericID` + | Device ID + + **Returns:** + + Union[int, float] + +.. code-block:: + :caption: **Example:** + + resources = Resources() + amount = resources.devices.amount("device-id-123") + print(amount) # 15234 + + +====== create -======= +====== + +Creates a new device in the account with specified configuration and returns device credentials. + + **See:** -Generates and retrieves a new action from the Device + https://help.tago.io/portal/en/kb/articles/478-devices Devices Overview + https://help.tago.io/portal/en/kb/articles/481-device-types Device Types **Parameters:** | **deviceObj**: :ref:`DeviceCreateInfo` | Object data to create new device + **Returns:** + + :ref:`DeviceCreateResponse` + +.. code-block:: + :caption: **Example:** + + resources = Resources() + result = resources.devices.create({ + "name": "Temperature Sensor", + "network": "network-id-123", + "connector": "connector-id-456", + "type": "immutable", + "chunk_period": "month", + "chunk_retention": 6, + "active": True + }) + print(result) # {'device_id': '...', 'bucket_id': '...', 'token': '...'} + + +========== +dataBackup +========== + +Schedule to export the device's data to TagoIO Files. +For mutable devices, exports all data. For immutable devices with chunkID, exports specific chunk. + + **See:** + + https://help.tago.io/portal/en/kb/articles/55-data-export Data Export + https://help.tago.io/portal/en/kb/articles/device-data#Backing_Up_Data Backing Up Data + + **Parameters:** + + | **params**: :ref:`DeviceDataBackup` + | Parameters for device data backup operation + + | **chunkID**: Optional[:ref:`GenericID`] + | [Optional] Chunk ID if backup is for a specific chunk + + **Returns:** + + :ref:`DeviceDataBackupResponse` + +.. code-block:: + :caption: **Example:** + + resources = Resources() + import time + timestamp = int(time.time()) + result = resources.devices.dataBackup({ + "deviceID": "device-id-123", + "file_address": f"/backups/device-id-123/{timestamp}", + "headers": True + }) + print(result) # {'file_address': 'backups/device-id-123/1736433519380.csv'} + + +=========== +dataRestore +=========== + +Restores data to a device from a CSV file in TagoIO Files. + + **See:** + + https://help.tago.io/portal/en/kb/articles/device-data#Importing Importing Device Data + https://api.docs.tago.io/#7ebca255-6c38-43d3-97d0-9b62155f202e Import Data API + + **Parameters:** + + | **params**: :ref:`DeviceDataRestore` + | Parameters for device data restore operation + + **Returns:** + + str + +.. code-block:: + :caption: **Example:** + + resources = Resources() + result = resources.devices.dataRestore({ + "deviceID": "device-id-123", + "file_address": "/backups/backup.csv" + }) + print(result) # Data import added to the queue successfully! + + ====== delete ====== -Deletes an device from the account +Permanently deletes a device from the account along with all its data. + + **See:** + + https://help.tago.io/portal/en/kb/articles/478-devices Devices Overview **Parameters:** - | **deviceID**: GenericID: str + | **deviceID**: :ref:`GenericID` | Device ID + **Returns:** -====== + str + +.. code-block:: + :caption: **Example:** + + resources = Resources() + result = resources.devices.delete("device-id-123") + print(result) # Successfully Removed + + +=========== +deleteChunk +=========== + +Deletes a chunk from an immutable device. + + **See:** + + https://help.tago.io/portal/en/kb/articles/chunk-management#Delete_chunks Delete Chunks + + **Parameters:** + + | **deviceID**: :ref:`GenericID` + | Device ID + + | **chunkID**: :ref:`GenericID` + | Chunk ID + + **Returns:** + + str + +.. code-block:: + :caption: **Example:** + + resources = Resources() + result = resources.devices.deleteChunk("device-id-123", "chunk-id-123") + print(result) # Chunk chunk-id-123 deleted + + +================ +deleteDeviceData +================ + +Deletes data from a device based on specified query parameters. + + **See:** + + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management + + **Parameters:** + + | **deviceID**: :ref:`GenericID` + | Device ID + + | **queryParam**: Optional[:ref:`DataQuery`] + | [Optional] Query parameters to filter the results + + **Returns:** + + str + +.. code-block:: + :caption: **Example:** + + resources = Resources() + # Delete specific records by ID + result = resources.devices.deleteDeviceData("device-id-123", { + "ids": ["record-id-1", "record-id-2"] + }) + # Delete by variable + result = resources.devices.deleteDeviceData("device-id-123", { + "variables": ["old_sensor"], + "qty": 100 + }) + print(result) # Successfully Removed + + +==== edit -====== +==== -Modify any property of the device +Modifies any property of an existing device. + + **See:** + + https://help.tago.io/portal/en/kb/articles/478-devices Devices Overview **Parameters:** - | **deviceID**: GenericID: str + | **deviceID**: :ref:`GenericID` | Device ID | **deviceObj**: :ref:`DeviceEditInfo` | Device object with fields to replace + **Returns:** -================ + str + +.. code-block:: + :caption: **Example:** + + resources = Resources() + result = resources.devices.edit("device-id-123", { + "name": "Updated Sensor Name", + "active": False, + "tags": [{"key": "location", "value": "warehouse"}] + }) + print(result) # Successfully Updated + + +============== +editDeviceData +============== + +Modifies existing data records in a device. Requires the data record ID. + + **See:** + + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management + + **Parameters:** + + | **deviceID**: :ref:`GenericID` + | Device ID + + | **updatedData**: Union[DataEdit, List[DataEdit]] + | An array or one object with data to be updated + + **Returns:** + + str + +.. code-block:: + :caption: **Example:** + + resources = Resources() + result = resources.devices.editDeviceData("device-id-123", { + "id": "data-record-id", + "value": 30.0, + "unit": "°F" + }) + # Edit multiple records + result = resources.devices.editDeviceData("device-id-123", [ + {"id": "record-1-id", "value": 25.5}, + {"id": "record-2-id", "value": 65} + ]) + print(result) # Device Data Updated + + +=============== emptyDeviceData -================ +=============== + +Permanently removes all data from a device. This operation cannot be undone. + + **See:** -Empty all data in a device. + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management **Parameters:** - | **deviceID**: GenericID: str + | **deviceID**: :ref:`GenericID` | Device ID + **Returns:** -================ -getDeviceData -================ + str -Get data from all variables in the device. +.. code-block:: + :caption: **Example:** + + resources = Resources() + result = resources.devices.emptyDeviceData("device-id-123") + print(result) # All data has been removed + + +======== +getChunk +======== + +Retrieves chunk information from an immutable device. + + **See:** + + https://help.tago.io/portal/en/kb/articles/chunk-management Chunk Management **Parameters:** - | **deviceID**: GenericID: str + | **deviceID**: :ref:`GenericID` | Device ID - | *Optional* **queryParams**: :ref:`DataQuery` - | Query parameters to filter the results. + **Returns:** + + List[:ref:`DeviceChunkData`] .. code-block:: :caption: **Example:** - from tagoio_sdk import Resources + resources = Resources() + chunks = resources.devices.getChunk("device-id-123") + print(chunks) # [{'amount': 0, 'id': 'chunk-id-123', 'from_date': '2025-01-09T00:00:00.000+00:00', ...}] + + +============= +getDeviceData +============= + +Retrieves data from all variables in the device with optional query filters. + + **See:** + + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management + + **Parameters:** + + | **deviceID**: :ref:`GenericID` + | Device ID + + | **queryParams**: Optional[:ref:`DataQuery`] + | [Optional] Query parameters to filter the results + + **Returns:** + + List[:ref:`CommonData`] + +.. code-block:: + :caption: **Example:** resources = Resources() - resources.devices.getDeviceData("myDeviceId"); + # Get all data + data = resources.devices.getDeviceData("device-id-123") + # Get specific variable data + temp_data = resources.devices.getDeviceData("device-id-123", { + "variables": ["temperature"], + "qty": 10 + }) + print(temp_data) # [{'variable': 'temperature', 'value': 25.5, 'time': '...', ...}] + -===== +==== info -===== +==== + +Retrieves detailed information about a specific device. + + **See:** -Get Info of the Device + https://help.tago.io/portal/en/kb/articles/478-devices Devices Overview **Parameters:** - | **deviceID**: GenericID: str + | **deviceID**: :ref:`GenericID` | Device ID + **Returns:** + + :ref:`DeviceInfo` + +.. code-block:: + :caption: **Example:** + + resources = Resources() + device_info = resources.devices.info("device-id-123") + print(device_info) # {'id': '...', 'name': '...', 'type': 'mutable', ...} + ========== listDevice ========== -Retrieves a list with all devices from the account +Retrieves a list of all devices from the account with optional filtering and pagination. + + **See:** + + https://help.tago.io/portal/en/kb/articles/478-devices Devices Overview **Parameters:** - | *Optional* **queryObj**: :ref:`DeviceQuery` - | Search query params + | **queryObj**: Optional[:ref:`DeviceQuery`] + | [Optional] Search query params + + **Returns:** + + List[:ref:`DeviceListItem`] .. code-block:: - :caption: **Default queryObj:** + :caption: **Example:** - queryObj: { + resources = Resources() + devices = resources.devices.listDevice({ "page": 1, - "fields": ["id", "name"], - "filter": {}, "amount": 20, - "orderBy": ["name", "asc"], - "resolveBucketName": False - } + "fields": ["id", "name", "active"], + "filter": {"name": "Temperature*"}, + "orderBy": ["name", "asc"] + }) + print(devices) # [{'id': 'device-id-123', 'name': 'Temperature Sensor', ...}] + ========= -paramSet +paramList ========= -Create or edit param for the Device +Retrieves a list of configuration parameters for a device. + + **See:** + + https://help.tago.io/portal/en/kb/articles/configuration-parameters Configuration Parameters **Parameters:** - | **deviceID**: GenericID: str + | **deviceID**: :ref:`GenericID` | Device ID - | **configObj**: :ref:`ConfigurationParams` or list[:ref:`ConfigurationParams`] - | Configuration Data + | **sentStatus**: Optional[bool] + | [Optional] True return only sent=true, False return only sent=false - | **paramID**: Optional[GenericID: str] - | Parameter ID + **Returns:** + List[:ref:`ConfigurationParams`] -========== -paramList -========== +.. code-block:: + :caption: **Example:** -List Params for the Device + resources = Resources() + # Get all parameters + params = resources.devices.paramList("device-id-123") + # Get only sent parameters + sent_params = resources.devices.paramList("device-id-123", sentStatus=True) + print(params) # [{'id': '...', 'key': 'threshold', 'value': '25.5', 'sent': False}] - **Parameters:** - | **deviceID**: GenericID: str - | Device ID +=========== +paramRemove +=========== - | *Optional* **sentStatus**: bool - | True return only sent=true, False return only sent=false +Removes a specific configuration parameter from a device. -============ -paramRemove -============ + **See:** -Remove param for the Device + https://help.tago.io/portal/en/kb/articles/configuration-parameters Configuration Parameters **Parameters:** - | **deviceID**: GenericID: str + | **deviceID**: :ref:`GenericID` | Device ID - | **paramID**: GenericID: str + | **paramID**: :ref:`GenericID` | Parameter ID + **Returns:** -============ -tokenCreate -============ + str + +.. code-block:: + :caption: **Example:** -Generates and retrieves a new token + resources = Resources() + result = resources.devices.paramRemove("device-id-123", "param-id-456") + print(result) # Successfully Removed - **Parameters:** - | **deviceID**: GenericID: str - | Device ID +======== +paramSet +======== - | **tokenParams**: :ref:`DevicesTokenData` - | Params for new token +Creates new configuration parameters or updates existing ones for a device. -============ -tokenDelete -============ + **See:** -Delete a token + https://help.tago.io/portal/en/kb/articles/configuration-parameters Configuration Parameters **Parameters:** - | **token**: GenericToken: str + | **deviceID**: :ref:`GenericID` | Device ID -========== -tokenList -========== + | **configObj**: Union[:ref:`ConfigurationParams`, List[:ref:`ConfigurationParams`]] + | Configuration Data -Retrieves a list of all tokens + | **paramID**: Optional[:ref:`GenericID`] + | [Optional] Parameter ID - **Parameters:** + **Returns:** - | **token**: GenericToken: str - | Device ID - - | *Optional* **queryObj**: :ref:`ListDeviceTokenQuery` - | Search query params + str .. code-block:: - :caption: **Default queryObj:** + :caption: **Example:** + + resources = Resources() + # Create new parameter + result = resources.devices.paramSet("device-id-123", { + "key": "threshold", + "value": "25.5", + "sent": False + }) + # Update existing parameter + result = resources.devices.paramSet("device-id-123", { + "key": "threshold", + "value": "30.0", + "sent": False + }, "param-id-456") + print(result) # Successfully Updated - queryObj: { - "page": 1, - "fields": ["name", "token", "permission"], - "filter": {}, - "amount": 20, - "orderBy": ["created_at", "desc"], - } ============== sendDeviceData ============== -Send data to a device. +Sends data to a device. Accepts a single data object or an array of data objects. + + **See:** + + https://help.tago.io/portal/en/kb/articles/device-data Device Data Management **Parameters:** - | **deviceID**: GenericID: str + | **deviceID**: :ref:`GenericID` | Device ID - | **data**: Union[:ref:`CommonData`, list[:ref:`CommonData`]] - | An array or one object with data to be send to TagoIO. + | **data**: Union[DataCreate, List[DataCreate]] + | An array or one object with data to be sent to TagoIO + + **Returns:** + + str .. code-block:: :caption: **Example:** - from tagoio_sdk import Resources - resources = Resources() - resource.devices.sendDeviceData("myDeviceID", { + result = resources.devices.sendDeviceData("device-id-123", { "variable": "temperature", - "unit": "F", - "value": 55, - "time": "2015-11-03 13:44:33", - "location": { "lat": 42.2974279, "lng": -85.628292 }, + "value": 25.5, + "unit": "°C", + "time": "2025-01-09 13:44:33", + "location": {"lat": 42.2974279, "lng": -85.628292} }) + # Send multiple data points + result = resources.devices.sendDeviceData("device-id-123", [ + {"variable": "temperature", "value": 25.5}, + {"variable": "humidity", "value": 60} + ]) + print(result) # Successfully Inserted -============== -editDeviceData -============== -Edit data in a device. +=========== +tokenCreate +=========== + +Generates and retrieves a new authentication token for a device. + + **See:** + + https://help.tago.io/portal/en/kb/articles/495-access-tokens Device Tokens **Parameters:** - | **deviceID**: GenericID: str + | **deviceID**: :ref:`GenericID` | Device ID - | **updatedData**: Union[:ref:`CommonData`, list[:ref:`CommonData`]] - | An array or one object with data to be send to TagoIO. + | **tokenParams**: :ref:`CommonTokenData` + | Params for new token + + **Returns:** + + :ref:`TokenCreateResponse` .. code-block:: :caption: **Example:** - resources = Resource() - resource.devices.editDeviceData("myDeviceID", { - "id": "idOfTheRecord", - "value": "new value", - "unit": "new unit" - }) + resources = Resources() + result = resources.devices.tokenCreate("device-id-123", { + "name": "Production Token", + "permission": "write", + "expire_time": "never" + }) + print(result) # {'token': 'new-token-value', 'expire_date': None} -================ -deleteDeviceData -================ -Delete data from a device. +=========== +tokenDelete +=========== + +Permanently deletes an authentication token. + + **See:** + + https://help.tago.io/portal/en/kb/articles/495-access-tokens Device Tokens **Parameters:** - | **deviceID**: GenericID: str + | **token**: :ref:`GenericToken` + | Token to delete + + **Returns:** + + str + +.. code-block:: + :caption: **Example:** + + resources = Resources() + result = resources.devices.tokenDelete("token-to-delete") + print(result) # Successfully Removed + + +========= +tokenList +========= + +Retrieves a list of all authentication tokens for a device with optional filtering. + + **See:** + + https://help.tago.io/portal/en/kb/articles/495-access-tokens Device Tokens + + **Parameters:** + + | **deviceID**: :ref:`GenericID` | Device ID - | *Optional* **queryParams**: :ref:`DataQuery` - | Query parameters to filter the results. + | **queryObj**: Optional[:ref:`ListDeviceTokenQuery`] + | [Optional] Search query params + + **Returns:** + + List[:ref:`DeviceTokenDataList`] .. code-block:: :caption: **Example:** - resources = Resource() - resource.devices.deleteDeviceData("myDeviceID", { - "ids": ["recordIdToDelete", "anotherRecordIdToDelete" ] - }) + resources = Resources() + tokens = resources.devices.tokenList("device-id-123", { + "page": 1, + "amount": 20, + "fields": ["name", "token", "permission"], + "orderBy": ["created_at", "desc"] + }) + print(tokens) # [{'name': 'Default Token', 'token': '...', 'permission': 'full', ...}] + .. toctree:: From 587eea29cbe8438d059f325ed204e802645a2136 Mon Sep 17 00:00:00 2001 From: Mateus Silva Date: Mon, 15 Dec 2025 11:51:43 -0300 Subject: [PATCH 3/5] fix: correct sentStatus parameter handling in paramList method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sentStatus boolean parameter was being passed directly to the API query, but the API expects a lowercase string representation. Updated to convert boolean to 'true'/'false' string format and handle None values properly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/tagoio_sdk/modules/Resources/Devices.py | 36 ++++++--------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/src/tagoio_sdk/modules/Resources/Devices.py b/src/tagoio_sdk/modules/Resources/Devices.py index 674b58a..988fc17 100644 --- a/src/tagoio_sdk/modules/Resources/Devices.py +++ b/src/tagoio_sdk/modules/Resources/Devices.py @@ -260,9 +260,7 @@ def paramSet( return result - def paramList( - self, deviceID: GenericID, sentStatus: bool = None - ) -> list[ConfigurationParams]: + def paramList(self, deviceID: GenericID, sentStatus: bool = None) -> list[ConfigurationParams]: """ @description: Retrieves a list of configuration parameters for a device. @@ -285,7 +283,7 @@ def paramList( { "path": f"/device/{deviceID}/params", "method": "GET", - "params": {"sent_status": sentStatus}, + "params": {"sent_status": str(sentStatus).lower() if sentStatus else None}, } ) return result @@ -366,9 +364,7 @@ def tokenList( return result - def tokenCreate( - self, deviceID: GenericID, tokenParams: TokenData - ) -> TokenCreateResponse: + def tokenCreate(self, deviceID: GenericID, tokenParams: TokenData) -> TokenCreateResponse: """ @description: Generates and retrieves a new authentication token for a device. @@ -422,9 +418,7 @@ def tokenDelete(self, token: GenericToken) -> str: ) return result - def getDeviceData( - self, deviceID: GenericID, queryParams: DataQuery = None - ) -> list[Data]: + def getDeviceData(self, deviceID: GenericID, queryParams: DataQuery = None) -> list[Data]: """ @description: Retrieves data from all variables in the device with optional query filters. @@ -481,9 +475,7 @@ def emptyDeviceData(self, deviceID: GenericID) -> str: ) return result - def sendDeviceData( - self, deviceID: GenericID, data: Union[DataCreate, list[DataCreate]] - ) -> str: + def sendDeviceData(self, deviceID: GenericID, data: Union[DataCreate, list[DataCreate]]) -> str: """ @description: Sends data to a device. Accepts a single data object or an array of data objects. @@ -520,9 +512,7 @@ def sendDeviceData( return result - def editDeviceData( - self, deviceID: GenericID, updatedData: Union[DataEdit, list[DataEdit]] - ) -> str: + def editDeviceData(self, deviceID: GenericID, updatedData: Union[DataEdit, list[DataEdit]]) -> str: """ @description: Modifies existing data records in a device. Requires the data record ID. @@ -557,9 +547,7 @@ def editDeviceData( return result - def deleteDeviceData( - self, deviceID: GenericID, queryParam: DataQuery = None - ) -> str: + def deleteDeviceData(self, deviceID: GenericID, queryParam: DataQuery = None) -> str: """ @description: Deletes data from a device based on specified query parameters. @@ -669,9 +657,7 @@ def deleteChunk(self, deviceID: GenericID, chunkID: GenericID) -> str: return result - def dataBackup( - self, params: DeviceDataBackup, chunkID: Optional[GenericID] = None - ) -> DeviceDataBackupResponse: + def dataBackup(self, params: DeviceDataBackup, chunkID: Optional[GenericID] = None) -> DeviceDataBackupResponse: """ @description: Schedule to export the device's data to TagoIO Files. @@ -701,11 +687,7 @@ def dataBackup( "file_address": params.get("file_address"), } - path = ( - f"/device/{params['deviceID']}/chunk/backup" - if chunkID - else f"/device/{params['deviceID']}/data/backup" - ) + path = f"/device/{params['deviceID']}/chunk/backup" if chunkID else f"/device/{params['deviceID']}/data/backup" result = self.doRequest( { From d42f83aae52c4d3f3499992ff411d1a52876ed5c Mon Sep 17 00:00:00 2001 From: Mateus Silva Date: Mon, 12 Jan 2026 14:25:29 -0300 Subject: [PATCH 4/5] fix: update sentStatus parameter handling to check for None explicitly --- src/tagoio_sdk/modules/Resources/Devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tagoio_sdk/modules/Resources/Devices.py b/src/tagoio_sdk/modules/Resources/Devices.py index 988fc17..7292ca3 100644 --- a/src/tagoio_sdk/modules/Resources/Devices.py +++ b/src/tagoio_sdk/modules/Resources/Devices.py @@ -283,7 +283,7 @@ def paramList(self, deviceID: GenericID, sentStatus: bool = None) -> list[Config { "path": f"/device/{deviceID}/params", "method": "GET", - "params": {"sent_status": str(sentStatus).lower() if sentStatus else None}, + "params": {"sent_status": str(sentStatus).lower() if sentStatus is not None else None}, } ) return result From 6fd4a124c60242f8bd0e32b5576d1e57930b4c03 Mon Sep 17 00:00:00 2001 From: Mateus Henrique Date: Mon, 12 Jan 2026 14:40:55 -0300 Subject: [PATCH 5/5] feat/update-class-networks/SDKPY-148 (#56) * feat: add missing methods and complete Networks class implementation Implemented 6 missing methods (create, edit, delete, tokenList, tokenCreate, tokenDelete) to achieve feature parity with JavaScript SDK. Added 3 new TypedDict definitions (NetworkTokenInfo, NetworkQuery, ListTokenQuery) for type safety. All methods include comprehensive docstrings following the standard pattern with description, documentation links, and examples. * docs: add comprehensive Sphinx RST documentation for Networks class Added complete RST documentation for all 8 Networks class methods and 3 new TypedDict definitions. Documentation includes detailed parameter descriptions, return types, code examples, and links to official TagoIO documentation following the established pattern. * feat/update-class-profile/SDKPY-149 (#57) * feat: add comprehensive Profile class implementation with all methods Added complete implementation of Profile resource management including profile CRUD operations, team management, token operations, usage statistics, and audit logs. Includes new TypedDict definitions and comprehensive test coverage for all methods. * docs: add comprehensive Sphinx RST documentation for Profile class Added complete RST documentation for all Profile methods and TypedDict definitions. Includes 10 new TypedDict blocks in Profile_Types.rst and all 17 methods documented in index.rst with proper formatting and cross-references. * docs: update Help Center link to the new TagoIO Support URL * fix: update resource references from 'resources.profiles' to 'resources.profile' in doc string examples * feat/update-class-run/SDKPY-150 (#58) * feat: add comprehensive documentation and tests for Run class Add detailed docstrings for all Run class methods with usage examples and authorization policy references. Include new EmailTestData type for email testing functionality. Implement comprehensive test suite covering all Run methods with mocked API responses. * docs: update Sphinx documentation for Run class methods Update RST documentation with detailed method descriptions, usage examples, and authorization policy references. Add EmailTestData type documentation and restructure all Run method docs with consistent formatting and Help Center links. * feat: make query parameter optional in Run.listUsers method The query parameter is now optional with an empty dict as default value, making it easier to retrieve all users without specifying query filters. * feat: update listUsers method to use None as default for query parameter --- .../IntegrationNetwork_Type.rst | 40 ++ .../Resources/IntegrationNetwork/index.rst | 262 ++++++++- .../Resources/Profile/Profile_Types.rst | 227 ++++++++ docs/source/Resources/Profile/index.rst | 478 ++++++++++++++++- docs/source/Resources/Run/Run_Types.rst | 15 + docs/source/Resources/Run/index.rst | 495 +++++++++++++----- docs/source/index.rst | 2 +- .../modules/Resources/IntegrationNetwork.py | 255 ++++++++- .../Resources/IntegrationNetworkType.py | 27 +- src/tagoio_sdk/modules/Resources/Profile.py | 396 +++++++++++++- .../modules/Resources/Profile_Type.py | 100 ++++ src/tagoio_sdk/modules/Resources/Run.py | 342 ++++++++++-- src/tagoio_sdk/modules/Resources/Run_Type.py | 11 + tests/Resources/test_integration_network.py | 233 ++++++++- tests/Resources/test_profile.py | 307 ++++++++++- tests/Resources/test_run.py | 421 +++++++++++++++ 16 files changed, 3328 insertions(+), 283 deletions(-) create mode 100644 tests/Resources/test_run.py diff --git a/docs/source/Resources/IntegrationNetwork/IntegrationNetwork_Type.rst b/docs/source/Resources/IntegrationNetwork/IntegrationNetwork_Type.rst index 986d6a4..ff2ab4f 100644 --- a/docs/source/Resources/IntegrationNetwork/IntegrationNetwork_Type.rst +++ b/docs/source/Resources/IntegrationNetwork/IntegrationNetwork_Type.rst @@ -96,3 +96,43 @@ TokenData | **serie_number**: Optional[str] | **verification_code**: Optional[str] | **middleware**: Optional[str] + + +.. _NetworkTokenInfo: + +NetworkTokenInfo +---------------- + **Attributes:** + + | **token**: str + | **network**: :ref:`GenericID` + | **name**: str + | **permission**: str + | **created_at**: datetime + | **updated_at**: Optional[datetime] + + +.. _NetworkQuery: + +NetworkQuery +------------ + **Attributes:** + + | **page**: int + | **amount**: int + | **fields**: list[str] + | **filter**: dict + | **orderBy**: list[str] + + +.. _ListTokenQuery: + +ListTokenQuery +-------------- + **Attributes:** + + | **page**: int + | **amount**: int + | **fields**: list[str] + | **filter**: dict + | **orderBy**: list[str] diff --git a/docs/source/Resources/IntegrationNetwork/index.rst b/docs/source/Resources/IntegrationNetwork/index.rst index 703d5b5..8f79334 100644 --- a/docs/source/Resources/IntegrationNetwork/index.rst +++ b/docs/source/Resources/IntegrationNetwork/index.rst @@ -1,29 +1,277 @@ **Integration Network** ======================= -Manage integration network in the account +Manage integration networks in the account. ============ listNetwork ============ -Get list of networks + +Retrieves a list of all networks from the account with pagination support. +Use this to retrieve and manage networks in your application. + +See: `Network Integration `_ **Parameters:** - | **queryObj**: :ref:`Query` - | Network Query + | *Optional* **queryObj**: :ref:`NetworkQuery` + | Query parameters to filter the results. + + .. code-block:: + :caption: **Default queryObj:** + + queryObj = { + "page": 1, + "fields": ["id", "name"], + "filter": {}, + "amount": 20, + "orderBy": ["name", "asc"] + } + + **Returns:** + + | List[:ref:`NetworkInfo`] + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy "Integration Network" / "Access" in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + networks = resources.integration.networks.listNetwork({ + "page": 1, + "fields": ["id", "name"], + "amount": 20, + "orderBy": ["name", "asc"] + }) + print(networks) # [{'id': 'network-id-123', 'name': 'My Network', ...}] ====== info ====== -Gets information about the network +Retrieves detailed information about a specific network. + +See: `Network Integration `_ + + **Parameters:** + + | **networkID**: :ref:`GenericID` + | Network ID + + | *Optional* **fields**: List[str] + | Fields to retrieve (default: ["id", "name"]) + + **Returns:** + + | :ref:`NetworkInfo` + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy "Integration Network" / "Access" in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + network_info = resources.integration.networks.info("network-id-123") + print(network_info) # {'id': '...', 'name': 'My Network', ...} + + +====== +create +====== + +Creates a new integration network in the account. + +See: `Creating a Network Integration `_ + + **Parameters:** + + | **networkObj**: :ref:`NetworkCreateInfo` + | Network information + + **Returns:** + + | Dict[str, str] + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy "Integration Network" / "Create" in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + new_network = resources.integration.networks.create({ + "name": "My Custom Network", + "description": "Custom integration network", + "middleware_endpoint": "https://my-middleware.com/endpoint", + "public": False + }) + print(new_network) # {'network': 'network-id-123'} + + +====== +edit +====== + +Modifies any property of an existing network. + + **Parameters:** + + | **networkID**: :ref:`GenericID` + | Network ID + + | **networkObj**: :ref:`NetworkCreateInfo` + | Network information to update + + **Returns:** + + | str + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy "Integration Network" / "Edit" in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.integration.networks.edit("network-id-123", { + "name": "Updated Network Name", + "description": "Updated description", + "public": True + }) + print(result) # Successfully Updated + + +====== +delete +====== + +Permanently deletes a network from the account. **Parameters:** - | **networkID**: GenericID: str - | Network identification + | **networkID**: :ref:`GenericID` + | Network ID + + **Returns:** + + | str + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy "Integration Network" / "Delete" in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.integration.networks.delete("network-id-123") + print(result) # Successfully Removed + + +========== +tokenList +========== + +Retrieves a list of all authentication tokens for a network with optional filtering. + +See: `Tokens and Getting Devices `_ + + **Parameters:** + + | **networkID**: :ref:`GenericID` + | Network ID + + | *Optional* **queryObj**: :ref:`ListTokenQuery` + | Query parameters to filter the results. + + .. code-block:: + :caption: **Default queryObj:** + + queryObj = { + "page": 1, + "fields": ["name", "token", "permission"], + "filter": {}, + "amount": 20, + "orderBy": ["created_at", "desc"] + } + + **Returns:** + + | List[:ref:`NetworkTokenInfo`] + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy "Integration Network" / "Access" in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + tokens = resources.integration.networks.tokenList("network-id-123", { + "page": 1, + "amount": 20, + "fields": ["name", "token", "permission"], + "orderBy": ["created_at", "desc"] + }) + print(tokens) # [{'name': 'Default Token', 'token': '...', 'permission': 'full', ...}] + + +=========== +tokenCreate +=========== + +Generates and retrieves a new authentication token for a network. + +See: `Tokens and Getting Devices `_ + + **Parameters:** + + | **networkID**: :ref:`GenericID` + | Network ID + + | **tokenParams**: :ref:`IntegrationTokenData` + | Parameters for the new token + + **Returns:** + + | :ref:`TokenCreateResponse` + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy "Integration Network" / "Create Token" in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.integration.networks.tokenCreate("network-id-123", { + "name": "Production Token", + "permission": "write", + "expire_time": "never" + }) + print(result) # {'token': 'new-token-value', 'expire_date': None} + + +=========== +tokenDelete +=========== + +Permanently deletes an authentication token. + +See: `Tokens and Getting Devices `_ + + **Parameters:** + + | **token**: :ref:`GenericToken` + | Token to delete + + **Returns:** + + | str + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy "Integration Network" / "Delete Token" in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.integration.networks.tokenDelete("token-to-delete") + print(result) # Successfully Removed + .. toctree:: diff --git a/docs/source/Resources/Profile/Profile_Types.rst b/docs/source/Resources/Profile/Profile_Types.rst index 98e34cd..39453c8 100644 --- a/docs/source/Resources/Profile/Profile_Types.rst +++ b/docs/source/Resources/Profile/Profile_Types.rst @@ -226,3 +226,230 @@ TypedDict representing the summary of a profile. | **addons**: :ref:`ProfileAddOns` | The add-ons of the profile. + + +.. _UsageStatistic: + +UsageStatistic +----------------- +TypedDict representing a single usage statistic with timestamp. + +Not all of the services will be present for every statistic, only if for the usage period the service was used. + + **Properties:** + + | **time**: datetime + | Timestamp for the usage statistic. + + | **input**: Union[int, float] + | Input data usage. + + | **output**: Union[int, float] + | Output data usage. + + | **analysis**: Union[int, float] + | Analysis execution time used. + + | **sms**: Union[int, float] + | SMS messages sent. + + | **email**: Union[int, float] + | Email messages sent. + + | **data_records**: Union[int, float] + | Data records stored. + + | **run_users**: Union[int, float] + | Run users used. + + | **push_notification**: Union[int, float] + | Push notifications sent. + + | **file_storage**: Union[int, float] + | File storage used. + + | **tcore**: Union[int, float] + | TCore resources used. + + +.. _AuditLogEvent: + +AuditLogEvent +----------------- +TypedDict representing a single audit log event. + + **Properties:** + + | **resourceName**: str + | Name of the resource that triggered the event. + + | **message**: str + | Descriptive message about the event. + + | **resourceID**: :ref:`GenericID` + | ID of the resource that triggered the event. + + | **who**: :ref:`GenericID` + | ID of the account that performed the action. + + | **date**: datetime + | Timestamp when the event occurred. + + +.. _AuditLogStatistics: + +AuditLogStatistics +------------------ +TypedDict representing statistics for an audit log query. + + **Properties:** + + | **recordsMatched**: int + | Number of records that matched the query. + + | **recordsScanned**: int + | Number of records scanned during the query. + + | **bytesScanned**: int + | Number of bytes scanned during the query. + + +.. _AuditLog: + +AuditLog +----------------- +TypedDict representing an audit log query result. + + **Properties:** + + | **events**: list[:ref:`AuditLogEvent`] + | List of audit log events. + + | **statistics**: :ref:`AuditLogStatistics` + | Statistics about the query execution. + + | **status**: Literal["Running", "Complete", "Failed", "Timeout", "Unknown"] + | Current status of the audit log query. + + | **queryId**: str + | Unique identifier for the audit log query. + + +.. _AuditLogFilter: + +AuditLogFilter +----------------- +TypedDict representing filters for audit log queries. + + **Properties:** + + | **resourceID**: :ref:`GenericID` + | Filter by specific resource ID. + + | **resourceName**: Literal["action", "am", "analysis", "connector", "dashboard", "device", "dictionary", "network", "profile", "run", "runuser"] + | Filter by resource type. + + | **find**: str + | Search string for filtering events. + + | **start_date**: Union[str, datetime] + | Start date for the query range. + + | **end_date**: Union[str, datetime] + | End date for the query range. + + | **limit**: int + | Maximum number of results to return. + + +.. _AddonInfo: + +AddonInfo +----------------- +TypedDict representing profile addon information. + + **Properties:** + + | **id**: :ref:`GenericID` + | The addon ID. + + | **name**: str + | The addon name. + + | **logo_url**: Optional[str] + | URL of the addon's logo. + + +.. _StatisticsDate: + +StatisticsDate +----------------- +TypedDict representing parameters for fetching usage statistics. + + **Properties:** + + | **timezone**: str + | Timezone to be used in the statistics entries (default: "UTC"). + + | **date**: Union[str, datetime] + | Timestamp for fetching hourly statistics in a day. + + | **start_date**: Union[str, datetime] + | Starting date for fetching statistics in an interval. + + | **end_date**: Union[str, datetime] + | End date for fetching statistics in an interval. + + | **periodicity**: Literal["hour", "day", "month"] + | Periodicity of the statistics to fetch (default: "hour"). + + +.. _ProfileTeam: + +ProfileTeam +----------------- +TypedDict representing a team member with access to a profile. + + **Properties:** + + | **active**: bool + | Whether the team member's access is active. + + | **created_at**: datetime + | When the team member was added. + + | **email**: str + | Email address of the team member. + + | **id**: str + | Account ID of the team member. + + | **name**: str + | Name of the team member. + + +.. _ProfileCreateInfo: + +ProfileCreateInfo +----------------- +TypedDict representing the information needed to create a new profile. + + **Properties:** + + | **name**: str + | Name of the profile to be created. + + +.. _ProfileCredentials: + +ProfileCredentials +------------------ +TypedDict representing credentials required for sensitive profile operations. + + **Properties:** + + | **password**: str + | Account password. + + | **pin_code**: str + | Two-factor authentication PIN code (required when 2FA is enabled). diff --git a/docs/source/Resources/Profile/index.rst b/docs/source/Resources/Profile/index.rst index 8f884fe..4f66a67 100644 --- a/docs/source/Resources/Profile/index.rst +++ b/docs/source/Resources/Profile/index.rst @@ -1,98 +1,518 @@ **Profile** -============ +=========== -Manage profiles in account. +Manage profiles in your TagoIO account. ==== info ==== -Gets information about the bucket +Retrieves detailed information about a specific profile using its ID or 'current' for the active profile. + +See: `Profiles `_ **Parameters:** - | **profileID**: GenericID: str - | Profile identification + | **profileID**: :ref:`GenericID` + | Profile identification (use "current" for the active profile) **Returns:** - | **result**: :ref:`ProfileInfo` + | :ref:`ProfileInfo` -.. code-block:: - :caption: **Example:** + .. code-block:: python + # If receive an error "Authorization Denied", check policy **Account** / **Access profile** in Access Management. from tagoio_sdk import Resources resources = Resources() - result = resources.profile.info("Profile ID") + profile_info = resources.profile.info("profile-id-123") + # Or get current profile + current_profile = resources.profile.info("current") + print(profile_info) # {'info': {'id': 'profile-id-123', 'account': 'account-id-123', ...}, ...} + ==== list ==== -Lists all the profiles in your account +Retrieves a list of all profiles associated with the current account. + +See: `Profiles `_ **Returns:** - | **result**: list[:ref:`ProfileListInfo`] + | list[:ref:`ProfileListInfo`] -.. code-block:: - :caption: **Example:** + .. code-block:: python + # If receive an error "Authorization Denied", check policy **Account** / **Access profile** in Access Management. from tagoio_sdk import Resources resources = Resources() result = resources.profile.list() + print(result) # [{'id': 'profile-id-123', 'name': 'Profile Test', ...}] -======== +======= summary -======== +======= + +Retrieves a summary of the profile's usage and statistics. -Gets profile summary +See: `Profiles `_ **Parameters:** - | **profileID**: GenericID: str + | **profileID**: :ref:`GenericID` | Profile identification **Returns:** - | **result**: :ref:`ProfileSummary` + | :ref:`ProfileSummary` -.. code-block:: - :caption: **Example:** + .. code-block:: python + # If receive an error "Authorization Denied", check policy **Account** / **Access profile** in Access Management. from tagoio_sdk import Resources resources = Resources() - result = resources.profile.summary("Profile ID") + result = resources.profile.summary("profile-id-123") + print(result) # {'amount': {'device': 10, 'bucket': 10, 'dashboard': 5, ...}, ...} ========= tokenList ========= -Gets profile tokenList +Retrieves a list of all tokens associated with a specific profile. + +See: `Account Token `_ + + **Parameters:** + + | **profileID**: :ref:`GenericID` + | Profile identification + + | *Optional* **queryObj**: :ref:`Query` + | Query parameters to filter the results + + .. code-block:: + :caption: **Default queryObj:** + + queryObj = { + "page": 1, + "amount": 20, + "fields": ["name", "token", "permission"], + "filter": {}, + "orderBy": ["created_at", "asc"] + } + + **Returns:** + + | list[:ref:`TokenDataList`] + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.tokenList("profile-id-123", { + "page": 1, + "amount": 20, + "fields": ["name", "token", "permission"] + }) + print(result) # [{'name': 'Token #1', 'token': 'token-value', 'permission': 'full', ...}, ...] + + +====== +create +====== + +Creates a new profile with the specified name and optional resource allocation settings. + +See: `Profiles `_ + + **Parameters:** + + | **profileObj**: :ref:`ProfileCreateInfo` + | Profile information to create + + | *Optional* **allocate_free_resources**: bool + | Whether to allocate free resources to the new profile (default: False) + + **Returns:** + + | dict with key "id" containing the new profile ID + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.create({"name": "New Profile"}, allocate_free_resources=True) + print(result) # {'id': 'profile-id-123'} + + +==== +edit +==== + +Updates profile information with the provided data. + +See: `Profiles `_ + + **Parameters:** + + | **profileID**: :ref:`GenericID` + | Profile identification + + | **profileObj**: dict + | Profile information to update + + **Returns:** + + | str - Success message + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.edit("profile-id-123", {"name": "Updated Profile Name"}) + print(result) # Successfully Updated + + +====== +delete +====== + +Permanently removes a profile from the account. + +See: `Two-Factor Authentication (2FA) `_ + + **Parameters:** + + | **profileID**: :ref:`GenericID` + | Profile identification + + | **credentials**: :ref:`ProfileCredentials` + | Account credentials (password and pin_code if 2FA is enabled) + + **Returns:** + + | str - Success message + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + # The "pin_code" field is required when 2FA is activated + result = resources.profile.delete("profile-id-123", {"password": "your-password", "pin_code": "123456"}) + print(result) # Successfully Removed + + +================== +usageStatisticList +================== + +Retrieves usage statistics for a profile within a specified time period. + +Usage statistics are cumulative: if a service was not used in a time period, the statistics for that time period will not be in the object. + + **Parameters:** + + | **profileID**: :ref:`GenericID` + | Profile identification + + | *Optional* **dateObj**: :ref:`StatisticsDate` + | Date range and periodicity parameters + + **Returns:** + + | list[:ref:`UsageStatistic`] + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy **Account** / **Access profile statistics** in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.profile.usageStatisticList("profile-id-123", { + "start_date": "2024-09-01", + "end_date": "2024-12-31", + "periodicity": "day" + }) + print(result) # [{'time': '2024-09-02T00:01:29.749Z', 'analysis': 0.07, 'data_records': 67254, ...}, ...] + + +======== +auditLog +======== + +Creates a new audit log query for tracking profile activities. + +See: `Audit Log `_ + + **Parameters:** + + | **profileID**: :ref:`GenericID` + | Profile identification + + | *Optional* **filterObj**: :ref:`AuditLogFilter` + | Filters to apply to the audit log query + + **Returns:** + + | :ref:`AuditLog` + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.auditLog("profile-id-123", { + "start_date": "2024-12-01", + "end_date": "2024-12-07" + }) + print(result) + + +============== +auditLogQuery +============== + +Retrieves audit log entries using a previously created query. + +See: `Audit Log `_ + + **Parameters:** + + | **profileID**: :ref:`GenericID` + | Profile identification + + | **queryId**: str + | Query ID from a previous auditLog call + + **Returns:** + + | :ref:`AuditLog` + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.auditLogQuery("profile-id-123", "query-id-456") + print(result) + + +=========== +serviceEdit +=========== + +Updates service configuration and resource limits for a profile. + + **Parameters:** + + | **profileID**: :ref:`GenericID` + | Profile identification + + | **serviceObj**: dict + | Service configuration and resource limits to update + + **Returns:** + + | str - Success message + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.serviceEdit("profile-id-123", { + "input": 350000, + "output": 342153, + "analysis": 5 + }) + print(result) # Profile resource allocation Successfully Updated + + +================================= +transferTokenToAnotherProfile +================================= + +Transfers the current authentication token to another profile. + + **Parameters:** + + | **targetProfileID**: :ref:`GenericID` + | Target profile identification + + **Returns:** + + | str - Success message + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.transferTokenToAnotherProfile("target-profile-123") + print(result) + + +=========== +tokenCreate +=========== + +Creates a new authentication token for the specified profile. + +See: `Account Token `_ + +See: `Two-Factor Authentication (2FA) `_ **Parameters:** - | **profileID**: GenericID: str + | **profileID**: :ref:`GenericID` | Profile identification - | **queryObj**: Optional[:ref:`Query`] - | Token Query + + | **tokenParams**: :ref:`CommonTokenData` + | Token parameters including name, permission, email, and password **Returns:** - | **result**: list[:ref:`TokenDataList`] + | :ref:`TokenCreateResponse` -.. code-block:: - :caption: **Example:** + .. code-block:: python from tagoio_sdk import Resources + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + # The "pin_code" / "otp_type" field is required when 2FA is activated + result = resources.profile.tokenCreate("profile-id-123", { + "name": "API Access", + "permission": "full", + "email": "example@email.com", + "password": "your-password" + }) + print(result) # {'token': 'token-value', 'name': 'API Access', ...} + + +=========== +tokenDelete +=========== + +Revokes and removes an authentication token from the profile. + +See: `Account Token `_ + + **Parameters:** + + | **profileID**: :ref:`GenericID` + | Profile identification + + | **token**: :ref:`GenericToken` + | Token to be deleted + + **Returns:** + + | str - Success message + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.tokenDelete("profile-id-123", "token-xyz") + print(result) # Token Successfully Removed + + +============= +addTeamMember +============= + +Adds a new team member to the profile using their email address. + +See: `Team Management - Sharing your Profile `_ + + **Parameters:** + + | **profileID**: :ref:`GenericID` + | Profile identification + + | **email**: str + | Email address of the team member to invite + + **Returns:** + + | str - Success message + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.addTeamMember("profile-id-123", "user@example.com") + print(result) # User invited + + +======== +teamList +======== + +Retrieves a list of all team members that have access to the specified profile. + +See: `Team Management - Sharing your Profile `_ + + **Parameters:** + + | **profileID**: :ref:`GenericID` + | Profile identification + + **Returns:** + + | list[:ref:`ProfileTeam`] + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.teamList("profile-id-123") + print(result) # [{'id': 'account-id-123', 'active': False, 'name': 'John Doe', ...}, ...] + + +================ +deleteTeamMember +================ + +Removes a team member from the profile. + +See: `Team Management - Sharing your Profile `_ + + **Parameters:** + + | **profileID**: :ref:`GenericID` + | Profile identification + + | **accountId**: str + | Account ID of the team member to remove + + **Returns:** + + | str - Success message + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy in Access Management. + from tagoio_sdk import Resources + resources = Resources() - result = resources.profile.tokenList("Profile ID") + result = resources.profile.deleteTeamMember("profile-id-123", "account-id-456") + print(result) # Account Successfully Removed + .. toctree:: diff --git a/docs/source/Resources/Run/Run_Types.rst b/docs/source/Resources/Run/Run_Types.rst index 588da9b..0620063 100644 --- a/docs/source/Resources/Run/Run_Types.rst +++ b/docs/source/Resources/Run/Run_Types.rst @@ -319,6 +319,21 @@ LoginAsUserOptions | :default: "8 hours" +.. _EmailTestData: + +EmailTestData +------------- + +Type for the email test data. + + **Attributes:** + + | **subject**: str + | Subject of the test email. + + | **body**: str + | Body content of the test email. + .. _SAMLAttributeMappings: diff --git a/docs/source/Resources/Run/index.rst b/docs/source/Resources/Run/index.rst index 3a27538..ce60e23 100644 --- a/docs/source/Resources/Run/index.rst +++ b/docs/source/Resources/Run/index.rst @@ -1,320 +1,543 @@ - **Run** -======== +======= -Manage services in account. +Manage TagoRUN environment configuration and users. -======= +==== info -======= +==== -Get information about the TagoRUN service. +Retrieves information about the current Run environment configuration. - **Returns** +See: `TagoRun `_ | `Run Themes `_ - | **result**: :ref:`RunInfo` - | Information about the TagoRUN service. + **Returns:** + | :ref:`RunInfo` -======= + .. code-block:: python + + # If receive an error "Authorization Denied", check policy **Profile** / **Access TagoRun settings** in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.run.info() + print(result) # {'name': 'My Run Environment', 'logo': 'https://example.com/logo.png', ...} + + +==== edit -======= +==== + +Updates the Run environment configuration settings. -Edit the TagoRUN service information. +See: `TagoRun `_ | `Run Themes `_ **Parameters:** - | **data**: :ref:`RunInfo` - | Updated information for the TagoRUN service. + | **data**: dict + | Run configuration data to update **Returns:** - | **result**: str - | Success message. + | str + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy **Profile** / **Edit TagoRun settings** in Access Management. + from tagoio_sdk import Resources + resources = Resources() + result = resources.run.edit({"name": "My Run Environment", "logo": "https://example.com/logo.png"}) + print(result) # TagoIO Run Successfully Updated -============ + +========= listUsers -============ +========= + +Retrieves a paginated list of Run users with customizable fields and filtering options. -List users in the TagoRUN service. +See: `TagoRun `_ **Parameters:** | **query**: :ref:`Query` - | Query parameters for filtering and sorting the user list. + | Query parameters for filtering and sorting - **Returns:** + .. code-block:: + :caption: **Default query:** - | **result**: list[:ref:`UserInfo`] - | List of user information. + query = { + "page": 1, + "fields": ["id", "name"], + "filter": {}, + "amount": 20, + "orderBy": ["name", "asc"] + } + **Returns:** -============ -userInfo -============ - -Get information about a specific user in the TagoRUN service. + | list[:ref:`UserInfo`] - **Parameters:** + .. code-block:: python - | **userID**: :ref:`GenericID` - | ID of the user. + # If receive an error "Authorization Denied", or return empty list check policy **Run User** / **Access** in Access Management. + from tagoio_sdk import Resources - **Returns:** + resources = Resources() + result = resources.run.listUsers({ + "page": 1, + "fields": ["id", "name", "email"], + "amount": 20 + }) + print(result) # [{'id': 'user-id-123', 'name': 'John Doe', 'email': 'example@email.com'}] - | **result**: :ref:`UserInfo` - | Information about the user. +======== +userInfo +======== -================ -userCreate -================ +Retrieves detailed information about a specific Run user. -Create a new user in the TagoRUN service. +See: `TagoRun `_ **Parameters:** - | **data**: :ref:`UserCreateInfo` - | Information for creating the user. + | **userID**: :ref:`GenericID` + | User identification **Returns:** - | **result**: str - | Success message. + | :ref:`UserInfo` + .. code-block:: python -================ + # If receive an error "Authorization Denied", check policy **Run User** / **Access** in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.run.userInfo("user-id-123") + print(result) # {'id': 'user-id-123', 'name': 'John Doe', 'email': 'example@email.com', ...} + + +========== userCreate -================ +========== + +Creates a new user in the Run environment. -Create a new user in the TagoRUN service. +See: `TagoRun `_ **Parameters:** | **data**: :ref:`UserCreateInfo` - | Information for creating the user. + | User creation data **Returns:** - | **result**: str - | Success message. + | dict + .. code-block:: python -================ + # If receive an error "Authorization Denied", check policy **Run User** / **Create** in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.run.userCreate({ + "name": "John Doe", + "email": "john@example.com", + "password": "secure123", + "timezone": "America/New_York" + }) + print(result) # {'user': 'user-id-123'} + + +======== userEdit -================ +======== -Edit information about a specific user in the TagoRUN service. +Updates information for an existing Run user. + +See: `TagoRun `_ **Parameters:** | **userID**: :ref:`GenericID` - | ID of the user. + | User identification - | **data**: :ref:`UserInfo` - | Updated information for the user. + | **data**: dict + | User data to update **Returns:** - | **result**: str - | Success message. + | str + .. code-block:: python -================== + # If receive an error "Authorization Denied", check policy **Run User** / **Edit** in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.run.userEdit("user-id-123", {"name": "Updated Name"}) + print(result) # TagoIO Run User Successfully Updated + + +========== userDelete -================== +========== + +Permanently deletes a user from the Run environment. -Delete a specific user from the TagoRUN service. +See: `TagoRun `_ **Parameters:** | **userID**: :ref:`GenericID` - | ID of the user. + | User identification **Returns:** - | **result**: str - | Success message. + | str + .. code-block:: python -================== + # If receive an error "Authorization Denied", check policy **Run User** / **Delete** in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.run.userDelete("user-id-123") + print(result) # Successfully Removed + + +=========== loginAsUser -================== +=========== -Log in as a specific user in the TagoRUN service. +Generates a login token to authenticate as a specific Run user. **Parameters:** | **userID**: :ref:`GenericID` - | ID of the user. + | User identification - | **options**: Optional[:ref:`LoginAsUserOptions`] - | Additional options for the login. + | *Optional* **options**: :ref:`LoginAsUserOptions` + | Login options (e.g., expire_time) **Returns:** - | **result**: :ref:`LoginResponseRunUser` - | Login response. + | :ref:`LoginResponse` + .. code-block:: python -================ + # If receive an error "Authorization Denied", check policy **Run User** / **Login as user** in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.run.loginAsUser("user-id-123") + print(result["token"]) # eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ... + + +========= emailTest -================ +========= -Send a test email from the TagoRUN service. +Tests the email configuration by sending a test message. **Parameters:** - | **data**: :ref:`EmailBase` - | Email data including subject and body. + | **data**: :ref:`EmailTestData` + | Email test data with subject and body **Returns:** - | **result**: str - | Success message. + | str + .. code-block:: python -====================== + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.emailTest({"subject": "Test Email", "body": "This is a test message"}) + print(result) # E-mail sent to example@email.com + + +================ notificationList -====================== +================ + +Retrieves a list of notifications for a specific Run user. -List notifications for a specific user in the TagoRUN service. +See: `Notifications for Users `_ **Parameters:** | **userID**: :ref:`GenericID` - | ID of the user. + | User identification **Returns:** - | **result**: list[:ref:`NotificationInfo`] - | List of notification information. + | list[:ref:`NotificationInfo`] + .. code-block:: python -====================== + # If receive an error "Authorization Denied", check policy **Run User** / **Access notification** in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.run.notificationList("user-id-123") + print(result) # [{'id': 'notification-id-123', 'title': 'System Update', 'message': 'Features', ...}] + + +================== notificationCreate -====================== +================== -Create a new notification for a specific user in the TagoRUN service. +Creates a new notification for a Run user. + +See: `Notifications for Users `_ **Parameters:** | **userID**: :ref:`GenericID` - | ID of the user. + | User identification | **data**: :ref:`NotificationCreate` - | Information for creating the notification. + | Notification data **Returns:** - | **result**: :ref:`NotificationCreateReturn` - | Information about the created notification. + | :ref:`NotificationCreateReturn` + .. code-block:: python -====================== + # If receive an error "Authorization Denied", check policy **Run User** / **Create notification** in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.run.notificationCreate("user-id-123", { + "title": "Update", + "message": "New feature available" + }) + print(result) # {'id': 'notification-id-123'} + + +================ notificationEdit -====================== +================ + +Updates an existing notification in the Run environment. -Edit information about a specific notification in the TagoRUN service. +See: `Notifications for Users `_ **Parameters:** | **notificationID**: :ref:`GenericID` - | ID of the notification. + | Notification identification - | **data**: :ref:`NotificationCreate` - | Updated information for the notification. + | **data**: dict + | Notification data to update **Returns:** - | **result**: str - | Success message. + | str + .. code-block:: python -====================== + # If receive an error "Authorization Denied", check policy **Run User** / **Edit notification** in Access Management. + from tagoio_sdk import Resources + + resources = Resources() + result = resources.run.notificationEdit("notification-id-123", {"title": "Updated Title"}) + print(result) # TagoIO Notification User Successfully Updated + + +================== notificationDelete -====================== +================== -Delete a specific notification from the TagoRUN service. +Deletes a notification from the Run environment. + +See: `Notifications for Users `_ **Parameters:** | **notificationID**: :ref:`GenericID` - | ID of the notification. + | Notification identification **Returns:** - | **result**: str - | Success message. + | str + + .. code-block:: python + + # If receive an error "Authorization Denied", check policy **Run User** / **Delete notification** in Access Management. + from tagoio_sdk import Resources + resources = Resources() + result = resources.run.notificationDelete("notification-id-123") + print(result) # Successfully Removed -============ + +=========== ssoSAMLInfo -============ +=========== -Get the SAML Single Sign-On information for the account's RUN. +Retrieves the SAML Single Sign-On configuration information for the Run environment. +See: `Single Sign-On (SSO) `_ -============ + **Returns:** + + | :ref:`RunSAMLInfo` + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.ssoSAMLInfo() + print(result) # {'sp': {'entity_id': 'https://example.com', ...}, ...} + + +=========== ssoSAMLEdit -============ +=========== + +Updates the SAML SSO configuration for the Run environment. -Edit the SAML Single Sign-On metadata and mappings for the account's RUN. +See: `Single Sign-On (SSO) `_ **Parameters:** | **data**: :ref:`RunSAMLEditInfo` - | Updated data for a RUN's SAML Single Sign-On configuration. + | SAML SSO configuration data + + **Returns:** + + | str + .. code-block:: python -=================== + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.ssoSAMLEdit({ + "active": True, + "idp_metadata": "..." + }) + print(result) # TagoIO Run SAML SSO Successfully Updated + + +================== createCustomDomain -=================== +================== -Create a TagoRUN custom domain for the profile. +Creates a custom domain configuration for the Run environment. + +See: `Custom Domain Configuration `_ **Parameters:** - | **profile_id**: str - | ID of the profile + | **profile_id**: str + | Profile identification -.. toctree:: + | **customDomainData**: :ref:`CustomDomainCreate` + | Custom domain configuration data - Run_Types + **Returns:** + | str -================ + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.createCustomDomain("profile-id-123", { + "domain": "app.mycompany.com" + }) + print(result) # Custom domain created successfully + + +=============== getCustomDomain -================ +=============== + +Retrieves the custom domain configuration for a Run profile. + +See: `Custom Domain Configuration `_ + + **Parameters:** -Set details of TagoRun custom domain for the profile. + | **profile_id**: str + | Profile identification - **Parameters** + **Returns:** + + | :ref:`CustomDomainInfo` + + .. code-block:: python - | **profile_id**: str - | ID of the profile + from tagoio_sdk import Resources + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.getCustomDomain("profile-id-123") + print(result) # {'domain': 'app.mycompany.com', 'verified': True, ...} -=================== + +================== deleteCustomDomain -=================== +================== + +Removes the custom domain configuration from a Run profile. -Delete a TagoRUN custom domain for the profile. +See: `Custom Domain Configuration `_ - **Parameters** + **Parameters:** - | **profile_id**: str - | ID of the profile + | **profile_id**: str + | Profile identification + + **Returns:** + | str -======================= + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.deleteCustomDomain("profile-id-123") + print(result) # Custom domain deleted successfully + + +====================== regenerateCustomDomain -======================= +====================== + +Regenerates the custom domain configuration for a Run profile. -Regenerate a TagoRUN custom domain for the profile. +See: `Custom Domain Configuration `_ - **Parameters** + **Parameters:** - | **profile_id**: str - | ID of the profile + | **profile_id**: str + | Profile identification + + **Returns:** + + | str + + .. code-block:: python + + from tagoio_sdk import Resources + + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.regenerateCustomDomain("profile-id-123") + print(result) # Custom domain regenerated successfully + + +.. toctree:: + + Run_Types diff --git a/docs/source/index.rst b/docs/source/index.rst index ab56705..50471cf 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -340,7 +340,7 @@ Support & Resources 🐛 **Issues & Bugs:** `GitHub Issues `_ -💬 **Help Center:** `TagoIO Support `_ +💬 **Help Center:** `TagoIO Support `_ 🌐 **Platform:** `TagoIO Console `_ diff --git a/src/tagoio_sdk/modules/Resources/IntegrationNetwork.py b/src/tagoio_sdk/modules/Resources/IntegrationNetwork.py index b5e6188..8dc9fcc 100644 --- a/src/tagoio_sdk/modules/Resources/IntegrationNetwork.py +++ b/src/tagoio_sdk/modules/Resources/IntegrationNetwork.py @@ -1,26 +1,48 @@ +from typing import Dict +from typing import List +from typing import Optional + from tagoio_sdk.common.Common_Type import GenericID -from tagoio_sdk.common.Common_Type import Query +from tagoio_sdk.common.Common_Type import GenericToken +from tagoio_sdk.common.Common_Type import TokenCreateResponse +from tagoio_sdk.common.Common_Type import TokenData from tagoio_sdk.common.tagoio_module import TagoIOModule +from tagoio_sdk.modules.Resources.IntegrationNetworkType import ListTokenQuery +from tagoio_sdk.modules.Resources.IntegrationNetworkType import NetworkCreateInfo from tagoio_sdk.modules.Resources.IntegrationNetworkType import NetworkInfo +from tagoio_sdk.modules.Resources.IntegrationNetworkType import NetworkQuery +from tagoio_sdk.modules.Resources.IntegrationNetworkType import NetworkTokenInfo +from tagoio_sdk.modules.Utils.dateParser import dateParser +from tagoio_sdk.modules.Utils.dateParser import dateParserList class Networks(TagoIOModule): - def listNetwork(self, queryObj: Query = None) -> list[NetworkInfo]: + def listNetwork(self, queryObj: Optional[NetworkQuery] = None) -> List[NetworkInfo]: """ - Retrieves a list with all networks from the account + @description: + Retrieves a list of all networks from the account with pagination support. + Use this to retrieve and manage networks in your application. - :default: - fields: ["id", "name"] + @see: + https://docs.tago.io/docs/tagoio/integrations/general/creating-a-network-integration Network Integration - :param fields Fields to be returned + @example: + If receive an error "Authorization Denied", check policy **Integration Network** / **Access** in Access Management. + ```python + resources = Resources() + networks = resources.integration.networks.listNetwork({ + "page": 1, + "fields": ["id", "name"], + "amount": 20, + "orderBy": ["name", "asc"] + }) + print(networks) # [{'id': 'network-id-123', 'name': 'My Network', ...}] + ``` """ + queryObj = queryObj or {} - if queryObj is None: - queryObj = {} if "orderBy" in queryObj: - firstArgument = queryObj["orderBy"][0] - secondArgument = queryObj["orderBy"][1] - orderBy = f"{firstArgument},{secondArgument}" + orderBy = f"{queryObj['orderBy'][0]},{queryObj['orderBy'][1]}" else: orderBy = "name,asc" @@ -29,10 +51,10 @@ def listNetwork(self, queryObj: Query = None) -> list[NetworkInfo]: "path": "/integration/network", "method": "GET", "params": { - "page": queryObj.get("page") or 1, - "fields": queryObj.get("fields") or ["id", "name"], - "filter": queryObj.get("filter") or {}, - "amount": queryObj.get("amount") or 20, + "page": queryObj.get("page", 1), + "fields": queryObj.get("fields", ["id", "name"]), + "filter": queryObj.get("filter", {}), + "amount": queryObj.get("amount", 20), "orderBy": orderBy, }, } @@ -40,19 +62,25 @@ def listNetwork(self, queryObj: Query = None) -> list[NetworkInfo]: return result - def info(self, networkID: GenericID, fields: NetworkInfo = None) -> NetworkInfo: + def info(self, networkID: GenericID, fields: Optional[List[str]] = None) -> NetworkInfo: """ - Retrieves the information of the network. + @description: + Retrieves detailed information about a specific network. - :default: - fields: ["id", "name"] + @see: + https://docs.tago.io/docs/tagoio/integrations/general/creating-a-network-integration Network Integration - :param networkID Network ID - :param fields Fields to be returned + @example: + If receive an error "Authorization Denied", check policy **Integration Network** / **Access** in Access Management. + ```python + resources = Resources() + network_info = resources.integration.networks.info("network-id-123") + print(network_info) # {'id': '...', 'name': 'My Network', ...} + ``` """ - if fields is None: fields = ["id", "name"] + result = self.doRequest( { "path": f"/integration/network/{networkID}", @@ -64,3 +92,186 @@ def info(self, networkID: GenericID, fields: NetworkInfo = None) -> NetworkInfo: ) return result + + def create(self, networkObj: NetworkCreateInfo) -> Dict[str, str]: + """ + @description: + Creates a new integration network in the account. + + @see: + https://docs.tago.io/docs/tagoio/integrations/general/creating-a-network-integration#create-a-new-integration Creating a Network Integration + + @example: + If receive an error "Authorization Denied", check policy **Integration Network** / **Create** in Access Management. + ```python + resources = Resources() + new_network = resources.integration.networks.create({ + "name": "My Custom Network", + "description": "Custom integration network", + "middleware_endpoint": "https://my-middleware.com/endpoint", + "public": False + }) + print(new_network) # {'network': 'network-id-123'} + ``` + """ + result = self.doRequest( + { + "path": "/integration/network", + "method": "POST", + "body": networkObj, + } + ) + + return result + + def edit(self, networkID: GenericID, networkObj: NetworkCreateInfo) -> str: + """ + @description: + Modifies any property of an existing network. + + @example: + If receive an error "Authorization Denied", check policy **Integration Network** / **Edit** in Access Management. + ```python + resources = Resources() + result = resources.integration.networks.edit("network-id-123", { + "name": "Updated Network Name", + "description": "Updated description", + "public": True + }) + print(result) # Successfully Updated + ``` + """ + result = self.doRequest( + { + "path": f"/integration/network/{networkID}", + "method": "PUT", + "body": networkObj, + } + ) + + return result + + def delete(self, networkID: GenericID) -> str: + """ + @description: + Permanently deletes a network from the account. + + @example: + If receive an error "Authorization Denied", check policy **Integration Network** / **Delete** in Access Management. + ```python + resources = Resources() + result = resources.integration.networks.delete("network-id-123") + print(result) # Successfully Removed + ``` + """ + result = self.doRequest( + { + "path": f"/integration/network/{networkID}", + "method": "DELETE", + } + ) + + return result + + def tokenList(self, networkID: GenericID, queryObj: Optional[ListTokenQuery] = None) -> List[NetworkTokenInfo]: + """ + @description: + Retrieves a list of all authentication tokens for a network with optional filtering. + + @see: + https://docs.tago.io/docs/tagoio/integrations/general/creating-a-network-integration#tokens-and-getting-devices Tokens and Getting Devices + + @example: + If receive an error "Authorization Denied", check policy **Integration Network** / **Access** in Access Management. + ```python + resources = Resources() + tokens = resources.integration.networks.tokenList("network-id-123", { + "page": 1, + "amount": 20, + "fields": ["name", "token", "permission"], + "orderBy": ["created_at", "desc"] + }) + print(tokens) # [{'name': 'Default Token', 'token': '...', 'permission': 'full', ...}] + ``` + """ + queryObj = queryObj or {} + + if "orderBy" in queryObj: + orderBy = f"{queryObj['orderBy'][0]},{queryObj['orderBy'][1]}" + else: + orderBy = "created_at,desc" + + result = self.doRequest( + { + "path": f"/integration/network/token/{networkID}", + "method": "GET", + "params": { + "page": queryObj.get("page", 1), + "fields": queryObj.get("fields", ["name", "token", "permission"]), + "filter": queryObj.get("filter", {}), + "amount": queryObj.get("amount", 20), + "orderBy": orderBy, + }, + } + ) + + result = dateParserList(result, ["created_at", "updated_at"]) + + return result + + def tokenCreate(self, networkID: GenericID, tokenParams: TokenData) -> TokenCreateResponse: + """ + @description: + Generates and retrieves a new authentication token for a network. + + @see: + https://docs.tago.io/docs/tagoio/integrations/general/creating-a-network-integration#tokens-and-getting-devices Tokens and Getting Devices + + @example: + If receive an error "Authorization Denied", check policy **Integration Network** / **Create Token** in Access Management. + ```python + resources = Resources() + result = resources.integration.networks.tokenCreate("network-id-123", { + "name": "Production Token", + "permission": "write", + "expire_time": "never" + }) + print(result) # {'token': 'new-token-value', 'expire_date': None} + ``` + """ + result = self.doRequest( + { + "path": "/integration/network/token", + "method": "POST", + "body": {"network": networkID, **tokenParams}, + } + ) + + result = dateParser(result, ["expire_date"]) + + return result + + def tokenDelete(self, token: GenericToken) -> str: + """ + @description: + Permanently deletes an authentication token. + + @see: + https://docs.tago.io/docs/tagoio/integrations/general/creating-a-network-integration#tokens-and-getting-devices Tokens and Getting Devices + + @example: + If receive an error "Authorization Denied", check policy **Integration Network** / **Delete Token** in Access Management. + ```python + resources = Resources() + result = resources.integration.networks.tokenDelete("token-to-delete") + print(result) # Successfully Removed + ``` + """ + result = self.doRequest( + { + "path": f"/integration/network/token/{token}", + "method": "DELETE", + } + ) + + return result diff --git a/src/tagoio_sdk/modules/Resources/IntegrationNetworkType.py b/src/tagoio_sdk/modules/Resources/IntegrationNetworkType.py index 23f58fc..df4de38 100644 --- a/src/tagoio_sdk/modules/Resources/IntegrationNetworkType.py +++ b/src/tagoio_sdk/modules/Resources/IntegrationNetworkType.py @@ -58,8 +58,33 @@ class NetworkInfo(NetworkCreateInfo): serial_number: Optional[serial_number] +class NetworkTokenInfo(TypedDict): + token: str + network: GenericID + name: str + permission: str + created_at: datetime + updated_at: Optional[datetime] + + class DeviceNetworkToken(TypedDict): token: uuid.UUID network: GenericID name: str - crated_at: datetime + created_at: datetime + + +class NetworkQuery(TypedDict, total=False): + page: int + amount: int + fields: list[str] + filter: dict + orderBy: list[str] + + +class ListTokenQuery(TypedDict, total=False): + page: int + amount: int + fields: list[str] + filter: dict + orderBy: list[str] diff --git a/src/tagoio_sdk/modules/Resources/Profile.py b/src/tagoio_sdk/modules/Resources/Profile.py index ca9fdf2..9f8f803 100644 --- a/src/tagoio_sdk/modules/Resources/Profile.py +++ b/src/tagoio_sdk/modules/Resources/Profile.py @@ -1,28 +1,46 @@ +from typing import Dict from typing import List from typing import Optional from tagoio_sdk.common.Common_Type import GenericID +from tagoio_sdk.common.Common_Type import GenericToken from tagoio_sdk.common.Common_Type import Query +from tagoio_sdk.common.Common_Type import TokenCreateResponse +from tagoio_sdk.common.Common_Type import TokenData from tagoio_sdk.common.Common_Type import TokenDataList from tagoio_sdk.common.tagoio_module import TagoIOModule +from tagoio_sdk.modules.Resources.Profile_Type import AuditLog +from tagoio_sdk.modules.Resources.Profile_Type import AuditLogFilter +from tagoio_sdk.modules.Resources.Profile_Type import ProfileCreateInfo +from tagoio_sdk.modules.Resources.Profile_Type import ProfileCredentials from tagoio_sdk.modules.Resources.Profile_Type import ProfileInfo from tagoio_sdk.modules.Resources.Profile_Type import ProfileListInfo from tagoio_sdk.modules.Resources.Profile_Type import ProfileSummary +from tagoio_sdk.modules.Resources.Profile_Type import ProfileTeam +from tagoio_sdk.modules.Resources.Profile_Type import StatisticsDate +from tagoio_sdk.modules.Resources.Profile_Type import UsageStatistic from tagoio_sdk.modules.Utils.dateParser import dateParser from tagoio_sdk.modules.Utils.dateParser import dateParserList class Profile(TagoIOModule): - """ - Manage profiles in account be sure to use an - account token with “write” permissions when - using functions like create, edit and delete. - """ - def info(self, profileID: GenericID) -> ProfileInfo: """ - Get Profile info - :param: profileID Profile identification + @description: + Retrieves detailed information about a specific profile using its ID or 'current' for the active profile. + + @see: + https://help.tago.io/portal/en/kb/articles/198-profiles Profiles + + @example: + If receive an error "Authorization Denied", check policy **Account** / **Access profile** in Access Management. + ```python + resources = Resources() + profile_info = resources.profile.info("profile-id-123") + # Or get current profile + current_profile = resources.profile.info("current") + print(profile_info) # {'info': {'id': 'profile-id-123', 'account': 'account-id-123', ...}, ...} + ``` """ result = self.doRequest( { @@ -37,7 +55,19 @@ def info(self, profileID: GenericID) -> ProfileInfo: def list(self) -> list[ProfileListInfo]: """ - Lists all the profiles in your account + @description: + Retrieves a list of all profiles associated with the current account. + + @see: + https://help.tago.io/portal/en/kb/articles/198-profiles Profiles + + @example: + If receive an error "Authorization Denied", check policy **Account** / **Access profile** in Access Management. + ```python + resources = Resources() + result = resources.profile.list() + print(result) # [{'id': 'profile-id-123', 'name': 'Profile Test', ...}] + ``` """ result = self.doRequest( { @@ -49,8 +79,19 @@ def list(self) -> list[ProfileListInfo]: def summary(self, profileID: GenericID) -> ProfileSummary: """ - Gets profile summary - :param: profileID Profile identification + @description: + Retrieves a summary of the profile's usage and statistics. + + @see: + https://help.tago.io/portal/en/kb/articles/198-profiles Profiles + + @example: + If receive an error "Authorization Denied", check policy **Account** / **Access profile** in Access Management. + ```python + resources = Resources() + result = resources.profile.summary("profile-id-123") + print(result) # {'amount': {'device': 10, 'bucket': 10, 'dashboard': 5, ...}, ...} + ``` """ result = self.doRequest( { @@ -60,12 +101,24 @@ def summary(self, profileID: GenericID) -> ProfileSummary: ) return result - def tokenList( - self, profileID: GenericID, queryObj: Optional[Query] = None - ) -> List[TokenDataList]: + def tokenList(self, profileID: GenericID, queryObj: Optional[Query] = None) -> List[TokenDataList]: """ - Lists all the tokens in your account - :param: profileID Profile identification + @description: + Retrieves a list of all tokens associated with a specific profile. + + @see: + https://help.tago.io/portal/en/kb/articles/495-account-token Account Token + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.tokenList("profile-id-123", { + "page": 1, + "amount": 20, + "fields": ["name", "token", "permission"] + }) + print(result) # [{'name': 'Token #1', 'token': 'token-value', 'permission': 'full', ...}, ...] + ``` """ if queryObj is None: @@ -91,8 +144,315 @@ def tokenList( } ) - result = dateParserList( - result, ["last_authorization", "expire_time", "created_at"] + result = dateParserList(result, ["last_authorization", "expire_time", "created_at"]) + + return result + + def create(self, profileObj: ProfileCreateInfo, allocate_free_resources: bool = False) -> Dict[str, GenericID]: + """ + @description: + Creates a new profile with the specified name and optional resource allocation settings. + + @see: + https://help.tago.io/portal/en/kb/articles/198-profiles Profiles + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.create({"name": "New Profile"}, allocate_free_resources=True) + print(result) # {'id': 'profile-id-123'} + ``` + """ + params = {} + if allocate_free_resources: + params["allocate_free_resources"] = allocate_free_resources + + result = self.doRequest({"path": "/profile/", "method": "POST", "body": profileObj, "params": params}) + + return result + + def edit(self, profileID: GenericID, profileObj: Dict) -> str: + """ + @description: + Updates profile information with the provided data. + + @see: + https://help.tago.io/portal/en/kb/articles/198-profiles Profiles + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.edit("profile-id-123", {"name": "Updated Profile Name"}) + print(result) # Successfully Updated + ``` + """ + result = self.doRequest({"path": f"/profile/{profileID}", "method": "PUT", "body": profileObj}) + + return result + + def delete(self, profileID: GenericID, credentials: ProfileCredentials) -> str: + """ + @description: + Permanently removes a profile from the account. + + @see: + https://help.tago.io/portal/en/kb/articles/526-two-factor-authentication Two-Factor Authentication (2FA) + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + # The "pin_code" field is required when 2FA is activated + result = resources.profile.delete("profile-id-123", {"password": "your-password", "pin_code": "123456"}) + print(result) # Successfully Removed + ``` + """ + result = self.doRequest({"path": f"/profile/{profileID}", "method": "DELETE", "body": credentials}) + + return result + + def usageStatisticList( + self, profileID: GenericID, dateObj: Optional[StatisticsDate] = None + ) -> List[UsageStatistic]: + """ + @description: + Retrieves usage statistics for a profile within a specified time period. + Usage statistics are cumulative: if a service was not used in a time period, + the statistics for that time period will not be in the object. + + @example: + If receive an error "Authorization Denied", check policy **Account** / **Access profile statistics** in Access Management. + ```python + resources = Resources() + result = resources.profile.usageStatisticList("profile-id-123", { + "start_date": "2024-09-01", + "end_date": "2024-12-31", + "periodicity": "day" + }) + print(result) # [{'time': '2024-09-02T00:01:29.749Z', 'analysis': 0.07, 'data_records': 67254, ...}, ...] + ``` + """ + result = self.doRequest( + { + "path": f"/profile/{profileID}/statistics", + "method": "GET", + "params": dateObj or {}, + } ) + result = dateParserList(result, ["time"]) + + return result + + def auditLog(self, profileID: GenericID, filterObj: Optional[AuditLogFilter] = None) -> AuditLog: + """ + @description: + Creates a new audit log query for tracking profile activities. + + @see: + https://help.tago.io/portal/en/kb/articles/audit-log Audit Log + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.auditLog("profile-id-123", { + "start_date": "2024-12-01", + "end_date": "2024-12-07" + }) + print(result) + ``` + """ + result = self.doRequest( + { + "path": f"/profile/{profileID}/auditlog", + "method": "GET", + "params": filterObj or {}, + } + ) + + if result.get("events"): + result["events"] = dateParserList(result["events"], ["date"]) + + return result + + def auditLogQuery(self, profileID: GenericID, queryId: str) -> AuditLog: + """ + @description: + Retrieves audit log entries using a previously created query. + + @see: + https://help.tago.io/portal/en/kb/articles/audit-log Audit Log + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.auditLogQuery("profile-id-123", "query-id-456") + print(result) + ``` + """ + result = self.doRequest({"path": f"/profile/{profileID}/auditlog/{queryId}", "method": "GET"}) + + if result.get("events"): + result["events"] = dateParserList(result["events"], ["date"]) + + return result + + def serviceEdit(self, profileID: GenericID, serviceObj: Dict) -> str: + """ + @description: + Updates service configuration and resource limits for a profile. + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.serviceEdit("profile-id-123", { + "input": 350000, + "output": 342153, + "analysis": 5 + }) + print(result) # Profile resource allocation Successfully Updated + ``` + """ + result = self.doRequest( + { + "path": f"/profile/{profileID}/services", + "method": "POST", + "body": serviceObj, + } + ) + + return result + + def transferTokenToAnotherProfile(self, targetProfileID: GenericID) -> str: + """ + @description: + Transfers the current authentication token to another profile. + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.transferTokenToAnotherProfile("target-profile-123") + print(result) + ``` + """ + result = self.doRequest({"path": f"/profile/switch/{targetProfileID}", "method": "PUT"}) + + return result + + def tokenCreate(self, profileID: GenericID, tokenParams: TokenData) -> TokenCreateResponse: + """ + @description: + Creates a new authentication token for the specified profile. + + @see: + https://help.tago.io/portal/en/kb/articles/495-account-token Account Token + https://help.tago.io/portal/en/kb/articles/526-two-factor-authentication Two-Factor Authentication (2FA) + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + # The "pin_code" / "otp_type" field is required when 2FA is activated + result = resources.profile.tokenCreate("profile-id-123", { + "name": "API Access", + "permission": "full", + "email": "example@email.com", + "password": "your-password" + }) + print(result) # {'token': 'token-value', 'name': 'API Access', ...} + ``` + """ + result = self.doRequest( + { + "path": f"/profile/{profileID}/token", + "method": "POST", + "body": tokenParams, + } + ) + + result = dateParser(result, ["expire_date"]) + + return result + + def tokenDelete(self, profileID: GenericID, token: GenericToken) -> str: + """ + @description: + Revokes and removes an authentication token from the profile. + + @see: + https://help.tago.io/portal/en/kb/articles/495-account-token Account Token + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.tokenDelete("profile-id-123", "token-xyz") + print(result) # Token Successfully Removed + ``` + """ + result = self.doRequest({"path": f"/profile/{profileID}/token/{token}", "method": "DELETE"}) + + return result + + def addTeamMember(self, profileID: GenericID, email: str) -> str: + """ + @description: + Adds a new team member to the profile using their email address. + + @see: + https://help.tago.io/portal/en/kb/articles/106-sharing-your-profile Team Management - Sharing your Profile + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.addTeamMember("profile-id-123", "user@example.com") + print(result) # User invited + ``` + """ + result = self.doRequest( + { + "path": f"/profile/{profileID}/team", + "method": "POST", + "body": {"email": email}, + } + ) + + return result + + def teamList(self, profileID: GenericID) -> List[ProfileTeam]: + """ + @description: + Retrieves a list of all team members that have access to the specified profile. + + @see: + https://help.tago.io/portal/en/kb/articles/106-sharing-your-profile Team Management - Sharing your Profile + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.profile.teamList("profile-id-123") + print(result) # [{'id': 'account-id-123', 'active': False, 'name': 'John Doe', ...}, ...] + ``` + """ + result = self.doRequest({"path": f"/profile/{profileID}/team", "method": "GET"}) + + result = dateParserList(result, ["created_at"]) + + return result + + def deleteTeamMember(self, profileID: GenericID, accountId: str) -> str: + """ + @description: + Removes a team member from the profile. + + @see: + https://help.tago.io/portal/en/kb/articles/106-sharing-your-profile Team Management - Sharing your Profile + + @example: + If receive an error "Authorization Denied", check policy in Access Management. + ```python + resources = Resources() + result = resources.profile.deleteTeamMember("profile-id-123", "account-id-456") + print(result) # Account Successfully Removed + ``` + """ + result = self.doRequest({"path": f"/profile/{profileID}/team/{accountId}", "method": "DELETE"}) + return result diff --git a/src/tagoio_sdk/modules/Resources/Profile_Type.py b/src/tagoio_sdk/modules/Resources/Profile_Type.py index 2367372..fcf0acf 100644 --- a/src/tagoio_sdk/modules/Resources/Profile_Type.py +++ b/src/tagoio_sdk/modules/Resources/Profile_Type.py @@ -1,4 +1,6 @@ from datetime import datetime +from typing import Literal +from typing import Optional from typing import TypedDict from typing import Union @@ -84,3 +86,101 @@ class ProfileSummary(TypedDict): amount: amount limit_used: limit_used addons: ProfileAddOns + + +class UsageStatistic(TypedDict, total=False): + """ + Type for a single usage statistic with timestamp. + + Not all of the services will be present for every statistic, + only if for the usage period the service was used. + """ + + time: datetime + input: Union[int, float] + output: Union[int, float] + analysis: Union[int, float] + sms: Union[int, float] + email: Union[int, float] + data_records: Union[int, float] + run_users: Union[int, float] + push_notification: Union[int, float] + file_storage: Union[int, float] + tcore: Union[int, float] + + +class AuditLogEvent(TypedDict): + resourceName: str + message: str + resourceID: GenericID + who: GenericID + date: datetime + + +class AuditLogStatistics(TypedDict): + recordsMatched: int + recordsScanned: int + bytesScanned: int + + +class AuditLog(TypedDict, total=False): + events: list[AuditLogEvent] + statistics: AuditLogStatistics + status: Literal["Running", "Complete", "Failed", "Timeout", "Unknown"] + queryId: str + + +class AuditLogFilter(TypedDict, total=False): + resourceID: GenericID + resourceName: Literal[ + "action", + "am", + "analysis", + "connector", + "dashboard", + "device", + "dictionary", + "network", + "profile", + "run", + "runuser", + ] + find: str + start_date: Union[str, datetime] + end_date: Union[str, datetime] + limit: int + + +class AddonInfo(TypedDict): + id: GenericID + name: str + logo_url: Optional[str] + + +class StatisticsDate(TypedDict, total=False): + """ + Parameters for fetching usage statistics. + """ + + timezone: str + date: Union[str, datetime] + start_date: Union[str, datetime] + end_date: Union[str, datetime] + periodicity: Literal["hour", "day", "month"] + + +class ProfileTeam(TypedDict): + active: bool + created_at: datetime + email: str + id: str + name: str + + +class ProfileCreateInfo(TypedDict): + name: str + + +class ProfileCredentials(TypedDict, total=False): + password: str + pin_code: str diff --git a/src/tagoio_sdk/modules/Resources/Run.py b/src/tagoio_sdk/modules/Resources/Run.py index 63d7f8e..e7c4bf2 100644 --- a/src/tagoio_sdk/modules/Resources/Run.py +++ b/src/tagoio_sdk/modules/Resources/Run.py @@ -1,5 +1,5 @@ +from typing import Dict from typing import Optional -from typing import TypedDict from tagoio_sdk.common.Common_Type import GenericID from tagoio_sdk.common.Common_Type import Query @@ -9,6 +9,7 @@ from tagoio_sdk.modules.Resources.Notification_Type import NotificationInfo from tagoio_sdk.modules.Resources.Run_Type import CustomDomainCreate from tagoio_sdk.modules.Resources.Run_Type import CustomDomainInfo +from tagoio_sdk.modules.Resources.Run_Type import EmailTestData from tagoio_sdk.modules.Resources.Run_Type import LoginAsUserOptions from tagoio_sdk.modules.Resources.Run_Type import LoginResponse from tagoio_sdk.modules.Resources.Run_Type import RunInfo @@ -21,13 +22,23 @@ class Run(TagoIOModule): - """ - Manage services in account - Be sure to use an account token with “write” permissions when using - functions like create, edit and delete. - """ - def info(self) -> RunInfo: + """ + @description: + Retrieves information about the current Run environment configuration. + + @see: + https://help.tago.io/portal/en/kb/articles/191-tagorun TagoRun + https://help.tago.io/portal/en/kb/articles/run-themes Run Themes + + @example: + If receive an error "Authorization Denied", check policy **Profile** / **Access TagoRun settings** in Access Management. + ```python + resources = Resources() + result = resources.run.info() + print(result) # {'name': 'My Run Environment', 'logo': 'https://example.com/logo.png', ...} + ``` + """ result = self.doRequest( { "path": "/run", @@ -37,7 +48,23 @@ def info(self) -> RunInfo: return result - def edit(self, data: RunInfo) -> str: + def edit(self, data: Dict) -> str: + """ + @description: + Updates the Run environment configuration settings. + + @see: + https://help.tago.io/portal/en/kb/articles/191-tagorun TagoRun + https://help.tago.io/portal/en/kb/articles/run-themes Run Themes + + @example: + If receive an error "Authorization Denied", check policy **Profile** / **Edit TagoRun settings** in Access Management. + ```python + resources = Resources() + result = resources.run.edit({"name": "My Run Environment", "logo": "https://example.com/logo.png"}) + print(result) # TagoIO Run Successfully Updated + ``` + """ result = self.doRequest( { "path": "/run", @@ -48,7 +75,28 @@ def edit(self, data: RunInfo) -> str: return result - def listUsers(self, query: Query) -> list[UserInfo]: + def listUsers(self, query: Optional[Query] = None) -> list[UserInfo]: + """ + @description: + Retrieves a paginated list of Run users with customizable fields and filtering options. + + @see: + https://help.tago.io/portal/en/kb/articles/191-tagorun TagoRun + + @example: + If receive an error "Authorization Denied", or return empty list check policy **Run User** / **Access** in Access Management. + ```python + resources = Resources() + result = resources.run.listUsers({ + "page": 1, + "fields": ["id", "name", "email"], + "amount": 20 + }) + print(result) # [{'id': 'user-id-123', 'name': 'John Doe', 'email': 'example@email.com'}] + ``` + """ + if query is None: + query = {} if "orderBy" in query: firstArgument = query["orderBy"][0] secondArgument = query["orderBy"][1] @@ -74,6 +122,21 @@ def listUsers(self, query: Query) -> list[UserInfo]: return result def userInfo(self, userID: GenericID) -> UserInfo: + """ + @description: + Retrieves detailed information about a specific Run user. + + @see: + https://help.tago.io/portal/en/kb/articles/191-tagorun TagoRun + + @example: + If receive an error "Authorization Denied", check policy **Run User** / **Access** in Access Management. + ```python + resources = Resources() + result = resources.run.userInfo("user-id-123") + print(result) # {'id': 'user-id-123', 'name': 'John Doe', 'email': 'example@email.com', ...} + ``` + """ result = self.doRequest( { "path": f"/run/users/{userID}", @@ -85,7 +148,27 @@ def userInfo(self, userID: GenericID) -> UserInfo: return result - def userCreate(self, data: UserCreateInfo) -> str: + def userCreate(self, data: UserCreateInfo) -> Dict[str, str]: + """ + @description: + Creates a new user in the Run environment. + + @see: + https://help.tago.io/portal/en/kb/articles/191-tagorun TagoRun + + @example: + If receive an error "Authorization Denied", check policy **Run User** / **Create** in Access Management. + ```python + resources = Resources() + result = resources.run.userCreate({ + "name": "John Doe", + "email": "john@example.com", + "password": "secure123", + "timezone": "America/New_York" + }) + print(result) # {'user': 'user-id-123'} + ``` + """ result = self.doRequest( { "path": "/run/users", @@ -96,7 +179,22 @@ def userCreate(self, data: UserCreateInfo) -> str: return result - def userEdit(self, userID: GenericID, data: UserInfo) -> str: + def userEdit(self, userID: GenericID, data: Dict) -> str: + """ + @description: + Updates information for an existing Run user. + + @see: + https://help.tago.io/portal/en/kb/articles/191-tagorun TagoRun + + @example: + If receive an error "Authorization Denied", check policy **Run User** / **Edit** in Access Management. + ```python + resources = Resources() + result = resources.run.userEdit("user-id-123", {"name": "Updated Name"}) + print(result) # TagoIO Run User Successfully Updated + ``` + """ result = self.doRequest( { "path": f"/run/users/{userID}", @@ -108,6 +206,21 @@ def userEdit(self, userID: GenericID, data: UserInfo) -> str: return result def userDelete(self, userID: GenericID) -> str: + """ + @description: + Permanently deletes a user from the Run environment. + + @see: + https://help.tago.io/portal/en/kb/articles/191-tagorun TagoRun + + @example: + If receive an error "Authorization Denied", check policy **Run User** / **Delete** in Access Management. + ```python + resources = Resources() + result = resources.run.userDelete("user-id-123") + print(result) # Successfully Removed + ``` + """ result = self.doRequest( { "path": f"/run/users/{userID}", @@ -117,9 +230,19 @@ def userDelete(self, userID: GenericID) -> str: return result - def loginAsUser( - self, userID: GenericID, options: Optional[LoginAsUserOptions] - ) -> LoginResponse: + def loginAsUser(self, userID: GenericID, options: Optional[LoginAsUserOptions] = None) -> LoginResponse: + """ + @description: + Generates a login token to authenticate as a specific Run user. + + @example: + If receive an error "Authorization Denied", check policy **Run User** / **Login as user** in Access Management. + ```python + resources = Resources() + result = resources.run.loginAsUser("user-id-123") + print(result["token"]) # eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ... + ``` + """ result = self.doRequest( { "path": f"/run/users/{userID}/login", @@ -132,11 +255,18 @@ def loginAsUser( return result - class emailData(TypedDict): - subject: str - body: str - - def emailTest(self, data: emailData) -> str: + def emailTest(self, data: EmailTestData) -> str: + """ + @description: + Tests the email configuration by sending a test message. + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.emailTest({"subject": "Test Email", "body": "This is a test message"}) + print(result) # E-mail sent to example@email.com + ``` + """ result = self.doRequest( { "path": "/run/email_test", @@ -148,6 +278,21 @@ def emailTest(self, data: emailData) -> str: return result def notificationList(self, userID: GenericID) -> list[NotificationInfo]: + """ + @description: + Retrieves a list of notifications for a specific Run user. + + @see: + https://help.tago.io/portal/en/kb/articles/223-notifications-for-users Notifications for Users + + @example: + If receive an error "Authorization Denied", check policy **Run User** / **Access notification** in Access Management. + ```python + resources = Resources() + result = resources.run.notificationList("user-id-123") + print(result) # [{'id': 'notification-id-123', 'title': 'System Update', 'message': 'Features', ...}] + ``` + """ result = self.doRequest( { "path": f"/run/notification/{userID}", @@ -157,9 +302,25 @@ def notificationList(self, userID: GenericID) -> list[NotificationInfo]: return result - def notificationCreate( - self, userID: GenericID, data: NotificationCreate - ) -> NotificationCreateReturn: + def notificationCreate(self, userID: GenericID, data: NotificationCreate) -> NotificationCreateReturn: + """ + @description: + Creates a new notification for a Run user. + + @see: + https://help.tago.io/portal/en/kb/articles/223-notifications-for-users Notifications for Users + + @example: + If receive an error "Authorization Denied", check policy **Run User** / **Create notification** in Access Management. + ```python + resources = Resources() + result = resources.run.notificationCreate("user-id-123", { + "title": "Update", + "message": "New feature available" + }) + print(result) # {'id': 'notification-id-123'} + ``` + """ result = self.doRequest( { "path": "/run/notification/", @@ -173,9 +334,22 @@ def notificationCreate( return result - def notificationEdit( - self, notificationID: GenericID, data: NotificationCreate - ) -> str: + def notificationEdit(self, notificationID: GenericID, data: Dict) -> str: + """ + @description: + Updates an existing notification in the Run environment. + + @see: + https://help.tago.io/portal/en/kb/articles/223-notifications-for-users Notifications for Users + + @example: + If receive an error "Authorization Denied", check policy **Run User** / **Edit notification** in Access Management. + ```python + resources = Resources() + result = resources.run.notificationEdit("notification-id-123", {"title": "Updated Title"}) + print(result) # TagoIO Notification User Successfully Updated + ``` + """ result = self.doRequest( { "path": f"/run/notification/{notificationID}", @@ -187,6 +361,21 @@ def notificationEdit( return result def notificationDelete(self, notificationID: GenericID) -> str: + """ + @description: + Deletes a notification from the Run environment. + + @see: + https://help.tago.io/portal/en/kb/articles/223-notifications-for-users Notifications for Users + + @example: + If receive an error "Authorization Denied", check policy **Run User** / **Delete notification** in Access Management. + ```python + resources = Resources() + result = resources.run.notificationDelete("notification-id-123") + print(result) # Successfully Removed + ``` + """ result = self.doRequest( { "path": f"/run/notification/{notificationID}", @@ -198,9 +387,19 @@ def notificationDelete(self, notificationID: GenericID) -> str: def ssoSAMLInfo(self) -> RunSAMLInfo: """ - Get the SAML Single Sign-On information for the account's RUN. + @description: + Retrieves the SAML Single Sign-On configuration information for the Run environment. + + @see: + https://help.tago.io/portal/en/kb/articles/491-single-sign-on-sso Single Sign-On (SSO) + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.ssoSAMLInfo() + print(result) # {'sp': {'entity_id': 'https://example.com', ...}, ...} + ``` """ - result = self.doRequest( { "path": "/run/sso/saml", @@ -212,10 +411,22 @@ def ssoSAMLInfo(self) -> RunSAMLInfo: def ssoSAMLEdit(self, data: RunSAMLEditInfo) -> str: """ - Edit the SAML Single Sign-On metadata and mappings for the account's RUN. - :param: data Updated data for a RUN's SAML Single Sign-On configuration. + @description: + Updates the SAML SSO configuration for the Run environment. + + @see: + https://help.tago.io/portal/en/kb/articles/491-single-sign-on-sso Single Sign-On (SSO) + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.ssoSAMLEdit({ + "active": True, + "idp_metadata": "..." + }) + print(result) # TagoIO Run SAML SSO Successfully Updated + ``` """ - result = self.doRequest( { "path": "/run/sso/saml", @@ -226,16 +437,23 @@ def ssoSAMLEdit(self, data: RunSAMLEditInfo) -> str: return result - def createCustomDomain( - self, profile_id: str, customDomainData: CustomDomainCreate - ) -> str: + def createCustomDomain(self, profile_id: str, customDomainData: CustomDomainCreate) -> str: """ - Create a TagoRUN custom domain for the profile. - :param: profile_id ID of the profile - :param: customDomainData query params - :returns: Success message. + @description: + Creates a custom domain configuration for the Run environment. + + @see: + https://help.tago.io/portal/en/kb/articles/custom-domain-configuration Custom Domain Configuration + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.createCustomDomain("profile-id-123", { + "domain": "app.mycompany.com" + }) + print(result) # Custom domain created successfully + ``` """ - result = self.doRequest( { "path": f"/run/customdomain/{profile_id}", @@ -248,11 +466,19 @@ def createCustomDomain( def getCustomDomain(self, profile_id: str) -> CustomDomainInfo: """ - set details of TagoRun custom domain for the profile. - :param: profile_id ID of the profile - :returns: Data for the profile's custom DNS configuration. + @description: + Retrieves the custom domain configuration for a Run profile. + + @see: + https://help.tago.io/portal/en/kb/articles/custom-domain-configuration Custom Domain Configuration + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.getCustomDomain("profile-id-123") + print(result) # {'domain': 'app.mycompany.com', 'verified': True, ...} + ``` """ - result = self.doRequest( { "path": f"/run/customdomain/{profile_id}", @@ -266,11 +492,19 @@ def getCustomDomain(self, profile_id: str) -> CustomDomainInfo: def deleteCustomDomain(self, profile_id: str) -> str: """ - delete a TagoRUN custom domain for the profile. - :param: profile_id ID of the profile - :returns: Success message. + @description: + Removes the custom domain configuration from a Run profile. + + @see: + https://help.tago.io/portal/en/kb/articles/custom-domain-configuration Custom Domain Configuration + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.deleteCustomDomain("profile-id-123") + print(result) # Custom domain deleted successfully + ``` """ - result = self.doRequest( { "path": f"/run/customdomain/{profile_id}", @@ -281,11 +515,19 @@ def deleteCustomDomain(self, profile_id: str) -> str: def regenerateCustomDomain(self, profile_id: str) -> str: """ - Regenerate a TagoRUN custom domain for the profile. - :param: profile_id ID of the profile - :returns: Success message. + @description: + Regenerates the custom domain configuration for a Run profile. + + @see: + https://help.tago.io/portal/en/kb/articles/custom-domain-configuration Custom Domain Configuration + + @example: + ```python + resources = Resources({"token": "YOUR-PROFILE-TOKEN"}) + result = resources.run.regenerateCustomDomain("profile-id-123") + print(result) # Custom domain regenerated successfully + ``` """ - result = self.doRequest( { "path": f"/run/customdomain/regenerate/{profile_id}", diff --git a/src/tagoio_sdk/modules/Resources/Run_Type.py b/src/tagoio_sdk/modules/Resources/Run_Type.py index e18dcb8..6e22b66 100644 --- a/src/tagoio_sdk/modules/Resources/Run_Type.py +++ b/src/tagoio_sdk/modules/Resources/Run_Type.py @@ -255,6 +255,17 @@ class LoginAsUserOptions(TypedDict): """ +class EmailTestData(TypedDict): + subject: str + """ + Subject of the test email. + """ + body: str + """ + Body content of the test email. + """ + + class SAMLAttributeMappings(TypedDict): email: str firstName: str diff --git a/tests/Resources/test_integration_network.py b/tests/Resources/test_integration_network.py index 35e4516..4c1aea7 100644 --- a/tests/Resources/test_integration_network.py +++ b/tests/Resources/test_integration_network.py @@ -1,39 +1,236 @@ -from requests_mock.mocker import Mocker +from typing import Any +from typing import Dict +from requests_mock.mocker import Mocker from src.tagoio_sdk.modules.Resources.IntegrationNetwork import Networks -from tests.conftest import mockListNetwork, mockNetworkInfo -def testNetworksMethodListNetworks( - requests_mock: Mocker, mockListNetwork: mockListNetwork -): - """ - :param requests_mock are a plugin of pytest to mock the requests. - :param mockListNetwork is a fixture to return list of NetworkInfo. - """ +def mockListNetwork() -> Dict[str, Any]: + """Mock to return the list of NetworkInfo.""" + return { + "status": True, + "result": [ + {"id": "60af66df0ae39d0012b0bbe9", "name": "AWS IoT"}, + {"id": "5d48632019b67f001c874a6b", "name": "BeWhere"}, + ], + } + + +def mockNetworkInfo() -> Dict[str, Any]: + """Mock to return the object NetworkInfo.""" + return { + "status": True, + "result": { + "id": "5ede22a7427104001c248b08", + "name": "LoRaWAN Activity", + "profile": "5bbcb03b667d7a002e56664b", + "middleware_endpoint": "https://lorawan.tago.io", + }, + } + + +def mockNetworkCreate() -> Dict[str, Any]: + """Mock to return network create response.""" + return { + "status": True, + "result": {"network": "network-id-123"}, + } + + +def mockNetworkEdit() -> Dict[str, Any]: + """Mock to return network edit response.""" + return { + "status": True, + "result": "Successfully Updated", + } + + +def mockNetworkDelete() -> Dict[str, Any]: + """Mock to return network delete response.""" + return { + "status": True, + "result": "Successfully Removed", + } + + +def mockNetworkTokenList() -> Dict[str, Any]: + """Mock to return list of network tokens.""" + return { + "status": True, + "result": [ + { + "token": "token-123", + "name": "Default Token", + "permission": "full", + "created_at": "2025-01-09T10:00:00.000Z", + }, + { + "token": "token-456", + "name": "Read-Only Token", + "permission": "read", + "created_at": "2025-01-08T15:30:00.000Z", + }, + ], + } + + +def mockNetworkTokenCreate() -> Dict[str, Any]: + """Mock to return token create response.""" + return { + "status": True, + "result": { + "token": "new-token-value", + "expire_date": None, + }, + } + - requests_mock.get("https://api.tago.io/integration/network", json=mockListNetwork) +def mockNetworkTokenDelete() -> Dict[str, Any]: + """Mock to return token delete response.""" + return { + "status": True, + "result": "Successfully Removed", + } + + +def testNetworksMethodListNetworks(requests_mock: Mocker): + """Test listNetwork method with pagination.""" + requests_mock.get("https://api.tago.io/integration/network", json=mockListNetwork()) tokenFake = {"token": "fake_token"} response = Networks(params=tokenFake).listNetwork(queryObj={"amount": 100}) - assert response == mockListNetwork["result"] + assert response == mockListNetwork()["result"] assert isinstance(response, list) assert isinstance(response[0], dict) -def testNetworksMethodInfo(requests_mock: Mocker, mockNetworkInfo: mockNetworkInfo): - """ - :param requests_mock are a plugin of pytest to mock the requests. - :param mockNetworkInfo is a fixture to return the object NetworkInfo. - """ +def testNetworksMethodInfo(requests_mock: Mocker): + """Test info method to retrieve network details.""" networkID = "fake_network_id" requests_mock.get( - f"https://api.tago.io/integration/network/{networkID}", json=mockNetworkInfo + f"https://api.tago.io/integration/network/{networkID}", json=mockNetworkInfo() ) tokenFake = {"token": "fake_token"} response = Networks(params=tokenFake).info(networkID=networkID) - assert response == mockNetworkInfo["result"] + assert response == mockNetworkInfo()["result"] + assert isinstance(response, dict) + + +def testNetworksMethodCreate(requests_mock: Mocker): + """Test create method to create a new network.""" + requests_mock.post("https://api.tago.io/integration/network", json=mockNetworkCreate()) + + tokenFake = {"token": "fake_token"} + response = Networks(params=tokenFake).create( + { + "name": "My Custom Network", + "description": "Custom integration network", + "middleware_endpoint": "https://my-middleware.com/endpoint", + "public": False, + } + ) + + assert response == mockNetworkCreate()["result"] + assert isinstance(response, dict) + assert "network" in response + + +def testNetworksMethodEdit(requests_mock: Mocker): + """Test edit method to update network properties.""" + networkID = "network-id-123" + requests_mock.put( + f"https://api.tago.io/integration/network/{networkID}", json=mockNetworkEdit() + ) + + tokenFake = {"token": "fake_token"} + response = Networks(params=tokenFake).edit( + networkID, + { + "name": "Updated Network Name", + "description": "Updated description", + "public": True, + }, + ) + + assert response == mockNetworkEdit()["result"] + assert response == "Successfully Updated" + + +def testNetworksMethodDelete(requests_mock: Mocker): + """Test delete method to remove a network.""" + networkID = "network-id-123" + requests_mock.delete( + f"https://api.tago.io/integration/network/{networkID}", json=mockNetworkDelete() + ) + + tokenFake = {"token": "fake_token"} + response = Networks(params=tokenFake).delete(networkID) + + assert response == mockNetworkDelete()["result"] + assert response == "Successfully Removed" + + +def testNetworksMethodTokenList(requests_mock: Mocker): + """Test tokenList method to retrieve network tokens.""" + networkID = "network-id-123" + requests_mock.get( + f"https://api.tago.io/integration/network/token/{networkID}", + json=mockNetworkTokenList(), + ) + + tokenFake = {"token": "fake_token"} + response = Networks(params=tokenFake).tokenList( + networkID, + { + "page": 1, + "amount": 20, + "fields": ["name", "token", "permission"], + "orderBy": ["created_at", "desc"], + }, + ) + + # dateParserList converts created_at strings to datetime objects + assert isinstance(response, list) + assert len(response) == 2 + assert response[0]["token"] == "token-123" + assert response[0]["name"] == "Default Token" + assert response[0]["permission"] == "full" + + +def testNetworksMethodTokenCreate(requests_mock: Mocker): + """Test tokenCreate method to generate a new token.""" + requests_mock.post( + "https://api.tago.io/integration/network/token", json=mockNetworkTokenCreate() + ) + + tokenFake = {"token": "fake_token"} + response = Networks(params=tokenFake).tokenCreate( + "network-id-123", + { + "name": "Production Token", + "permission": "write", + "expire_time": "never", + }, + ) + + assert response == mockNetworkTokenCreate()["result"] assert isinstance(response, dict) + assert "token" in response + + +def testNetworksMethodTokenDelete(requests_mock: Mocker): + """Test tokenDelete method to remove a token.""" + token = "token-to-delete" + requests_mock.delete( + f"https://api.tago.io/integration/network/token/{token}", + json=mockNetworkTokenDelete(), + ) + + tokenFake = {"token": "fake_token"} + response = Networks(params=tokenFake).tokenDelete(token) + + assert response == mockNetworkTokenDelete()["result"] + assert response == "Successfully Removed" diff --git a/tests/Resources/test_profile.py b/tests/Resources/test_profile.py index 784238f..463312f 100644 --- a/tests/Resources/test_profile.py +++ b/tests/Resources/test_profile.py @@ -1,8 +1,10 @@ import os + from requests_mock.mocker import Mocker from tagoio_sdk.common.Common_Type import TokenDataList -from tagoio_sdk.modules.Resources.Profile_Type import ProfileInfo, ProfileSummary +from tagoio_sdk.modules.Resources.Profile_Type import ProfileInfo +from tagoio_sdk.modules.Resources.Profile_Type import ProfileSummary from tagoio_sdk.modules.Resources.Resources import Resources @@ -187,3 +189,306 @@ def testProfileMethodTokenList(requests_mock: Mocker) -> None: assert response[1]["token"] == mockTokenDataList()["result"][1]["token"] assert isinstance(response, list) assert isinstance(response[1], dict) + + +def mockProfileCreate() -> dict: + return {"status": True, "result": {"id": "new-profile-id-123"}} + + +def mockProfileEdit() -> dict: + return {"status": True, "result": "Successfully Updated"} + + +def mockProfileDelete() -> dict: + return {"status": True, "result": "Successfully Removed"} + + +def mockUsageStatisticList() -> dict: + return { + "status": True, + "result": [ + { + "time": "2024-09-02T00:01:29.749Z", + "analysis": 0.07, + "data_records": 67254, + "input": 1500, + "output": 250, + }, + { + "time": "2024-09-03T00:01:29.749Z", + "analysis": 0.12, + "data_records": 85123, + "input": 2100, + "output": 340, + }, + ], + } + + +def mockAuditLog() -> dict: + return { + "status": True, + "result": { + "events": [ + { + "resourceName": "device", + "message": "Device created", + "resourceID": "device-id-123", + "who": "account-id-456", + "date": "2024-12-01T10:00:00.000Z", + } + ], + "statistics": { + "recordsMatched": 1, + "recordsScanned": 100, + "bytesScanned": 5000, + }, + "status": "Complete", + "queryId": "query-id-789", + }, + } + + +def mockAddonInfo() -> dict: + return { + "status": True, + "result": {"id": "addon-id-123", "name": "Custom DNS", "logo_url": None}, + } + + +def mockServiceEdit() -> dict: + return {"status": True, "result": "Profile resource allocation Successfully Updated"} + + +def mockTransferToken() -> dict: + return {"status": True, "result": "Token Successfully Transferred"} + + +def mockTokenCreate() -> dict: + return { + "status": True, + "result": { + "token": "new-token-value", + "name": "API Access", + "permission": "full", + "expire_date": "2025-12-31T23:59:59.000Z", + }, + } + + +def mockTokenDelete() -> dict: + return {"status": True, "result": "Token Successfully Removed"} + + +def mockTeamList() -> dict: + return { + "status": True, + "result": [ + { + "id": "account-id-123", + "active": False, + "name": "John Doe", + "email": "john@example.com", + "created_at": "2024-01-01T10:00:00.000Z", + }, + { + "id": "account-id-456", + "active": True, + "name": "Jane Smith", + "email": "jane@example.com", + "created_at": "2024-02-01T10:00:00.000Z", + }, + ], + } + + +def mockAddTeamMember() -> dict: + return {"status": True, "result": "User invited"} + + +def mockDeleteTeamMember() -> dict: + return {"status": True, "result": "Account Successfully Removed"} + + +def testProfileMethodCreate(requests_mock: Mocker) -> None: + """Test profile creation""" + requests_mock.post("https://api.tago.io/profile/", json=mockProfileCreate()) + + resources = Resources() + response = resources.profile.create( + {"name": "New Profile"}, allocate_free_resources=True + ) + + assert response["id"] == mockProfileCreate()["result"]["id"] + assert isinstance(response, dict) + + +def testProfileMethodEdit(requests_mock: Mocker) -> None: + """Test profile editing""" + requests_mock.put( + "https://api.tago.io/profile/profile-id-123", json=mockProfileEdit() + ) + + resources = Resources() + response = resources.profile.edit("profile-id-123", {"name": "Updated Name"}) + + assert response == mockProfileEdit()["result"] + assert isinstance(response, str) + + +def testProfileMethodDelete(requests_mock: Mocker) -> None: + """Test profile deletion""" + requests_mock.delete( + "https://api.tago.io/profile/profile-id-123", json=mockProfileDelete() + ) + + resources = Resources() + response = resources.profile.delete( + "profile-id-123", {"password": "test-password"} + ) + + assert response == mockProfileDelete()["result"] + assert isinstance(response, str) + + +def testProfileMethodUsageStatisticList(requests_mock: Mocker) -> None: + """Test usage statistics listing""" + requests_mock.get( + "https://api.tago.io/profile/profile-id-123/statistics", + json=mockUsageStatisticList(), + ) + + resources = Resources() + response = resources.profile.usageStatisticList("profile-id-123") + + assert len(response) == 2 + assert response[0]["analysis"] == 0.07 + assert isinstance(response, list) + + +def testProfileMethodAuditLog(requests_mock: Mocker) -> None: + """Test audit log creation""" + requests_mock.get( + "https://api.tago.io/profile/profile-id-123/auditlog", json=mockAuditLog() + ) + + resources = Resources() + response = resources.profile.auditLog("profile-id-123") + + assert response["queryId"] == mockAuditLog()["result"]["queryId"] + assert response["status"] == "Complete" + assert isinstance(response, dict) + + +def testProfileMethodAuditLogQuery(requests_mock: Mocker) -> None: + """Test audit log query""" + requests_mock.get( + "https://api.tago.io/profile/profile-id-123/auditlog/query-id-789", + json=mockAuditLog(), + ) + + resources = Resources() + response = resources.profile.auditLogQuery("profile-id-123", "query-id-789") + + assert response["queryId"] == mockAuditLog()["result"]["queryId"] + assert isinstance(response, dict) + + +def testProfileMethodServiceEdit(requests_mock: Mocker) -> None: + """Test service editing""" + requests_mock.post( + "https://api.tago.io/profile/profile-id-123/services", json=mockServiceEdit() + ) + + resources = Resources() + response = resources.profile.serviceEdit( + "profile-id-123", {"input": 350000, "output": 342153} + ) + + assert response == mockServiceEdit()["result"] + assert isinstance(response, str) + + +def testProfileMethodTransferToken(requests_mock: Mocker) -> None: + """Test token transfer""" + requests_mock.put( + "https://api.tago.io/profile/switch/target-profile-123", + json=mockTransferToken(), + ) + + resources = Resources() + response = resources.profile.transferTokenToAnotherProfile("target-profile-123") + + assert response == mockTransferToken()["result"] + assert isinstance(response, str) + + +def testProfileMethodTokenCreate(requests_mock: Mocker) -> None: + """Test token creation""" + requests_mock.post( + "https://api.tago.io/profile/profile-id-123/token", json=mockTokenCreate() + ) + + resources = Resources() + response = resources.profile.tokenCreate( + "profile-id-123", + {"name": "API Access", "permission": "full", "email": "test@example.com"}, + ) + + assert response["token"] == mockTokenCreate()["result"]["token"] + assert isinstance(response, dict) + + +def testProfileMethodTokenDelete(requests_mock: Mocker) -> None: + """Test token deletion""" + requests_mock.delete( + "https://api.tago.io/profile/profile-id-123/token/token-xyz", + json=mockTokenDelete(), + ) + + resources = Resources() + response = resources.profile.tokenDelete("profile-id-123", "token-xyz") + + assert response == mockTokenDelete()["result"] + assert isinstance(response, str) + + +def testProfileMethodAddTeamMember(requests_mock: Mocker) -> None: + """Test adding team member""" + requests_mock.post( + "https://api.tago.io/profile/profile-id-123/team", json=mockAddTeamMember() + ) + + resources = Resources() + response = resources.profile.addTeamMember("profile-id-123", "user@example.com") + + assert response == mockAddTeamMember()["result"] + assert isinstance(response, str) + + +def testProfileMethodTeamList(requests_mock: Mocker) -> None: + """Test team member listing""" + requests_mock.get( + "https://api.tago.io/profile/profile-id-123/team", json=mockTeamList() + ) + + resources = Resources() + response = resources.profile.teamList("profile-id-123") + + assert len(response) == 2 + assert response[0]["name"] == "John Doe" + assert isinstance(response, list) + + +def testProfileMethodDeleteTeamMember(requests_mock: Mocker) -> None: + """Test team member deletion""" + requests_mock.delete( + "https://api.tago.io/profile/profile-id-123/team/account-id-456", + json=mockDeleteTeamMember(), + ) + + resources = Resources() + response = resources.profile.deleteTeamMember("profile-id-123", "account-id-456") + + assert response == mockDeleteTeamMember()["result"] + assert isinstance(response, str) diff --git a/tests/Resources/test_run.py b/tests/Resources/test_run.py new file mode 100644 index 0000000..67216d8 --- /dev/null +++ b/tests/Resources/test_run.py @@ -0,0 +1,421 @@ +import os + +from requests_mock.mocker import Mocker + +from tagoio_sdk.modules.Resources.Resources import Resources + + +os.environ["T_ANALYSIS_TOKEN"] = "your_token_value" + + +def mockRunInfo() -> dict: + return { + "status": True, + "result": { + "profile": "profile_id_123", + "active": True, + "name": "My Run Environment", + "sub_title": "IoT Application", + "url": "myapp.run.tago.io", + "email_domain": None, + "signup_method": "default", + "favicon": None, + "logo": "https://example.com/logo.png", + "signup_logo": None, + "signup_logo_options": {}, + "sidebar_buttons": [], + "signup_fields": [], + "email_templates": {}, + "feature_devicewifisetup": {}, + "feature_geolocation": {}, + "theme": {}, + "integration": {}, + "sso_saml_active": False, + "security": {}, + }, + } + + +def mockRunEdit() -> dict: + return {"status": True, "result": "TagoIO Run Successfully Updated"} + + +def mockUserList() -> dict: + return { + "status": True, + "result": [ + { + "id": "user_id_1", + "name": "John Doe", + "email": "john@example.com", + "timezone": "America/New_York", + "created_at": "2023-02-21T15:17:35.759Z", + "updated_at": "2023-02-21T15:17:35.759Z", + "last_login": "2023-02-21T15:17:35.759Z", + }, + { + "id": "user_id_2", + "name": "Jane Smith", + "email": "jane@example.com", + "timezone": "America/Los_Angeles", + "created_at": "2023-02-21T16:17:35.759Z", + "updated_at": "2023-02-21T16:17:35.759Z", + "last_login": "2023-02-21T16:17:35.759Z", + }, + ], + } + + +def mockUserInfo() -> dict: + return { + "status": True, + "result": { + "id": "user_id_1", + "name": "John Doe", + "email": "john@example.com", + "timezone": "America/New_York", + "company": "ACME Corp", + "phone": "+1234567890", + "language": "en-US", + "profile": "profile_id_123", + "active": True, + "newsletter": False, + "last_login": "2023-02-21T15:17:35.759Z", + "created_at": "2023-02-21T15:17:35.759Z", + "updated_at": "2023-02-21T15:17:35.759Z", + "options": {}, + "tags": [{"key": "role", "value": "admin"}], + }, + } + + +def mockUserCreate() -> dict: + return {"status": True, "result": {"user": "user_id_new"}} + + +def mockUserEdit() -> dict: + return {"status": True, "result": "TagoIO Run User Successfully Updated"} + + +def mockUserDelete() -> dict: + return {"status": True, "result": "Successfully Removed"} + + +def mockLoginAsUser() -> dict: + return { + "status": True, + "result": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...", + "expire_date": "2024-02-21T15:17:35.759Z", + }, + } + + +def mockEmailTest() -> dict: + return {"status": True, "result": "E-mail sent to example@email.com"} + + +def mockNotificationList() -> dict: + return { + "status": True, + "result": [ + { + "id": "notification_id_1", + "title": "System Update", + "message": "New features available", + }, + { + "id": "notification_id_2", + "title": "Maintenance", + "message": "Scheduled maintenance tonight", + }, + ], + } + + +def mockNotificationCreate() -> dict: + return {"status": True, "result": {"id": "notification_id_new"}} + + +def mockNotificationEdit() -> dict: + return {"status": True, "result": "TagoIO Notification User Successfully Updated"} + + +def mockNotificationDelete() -> dict: + return {"status": True, "result": "Successfully Removed"} + + +def mockSSOSAMLInfo() -> dict: + return { + "status": True, + "result": { + "sp": { + "entity_id": "https://example.com", + "acs_url": "https://example.com/acs", + "metadata": "...", + }, + "idp": {"issuer": "https://idp.example.com"}, + "mapping": {"email": "email", "firstName": "firstName"}, + }, + } + + +def mockSSOSAMLEdit() -> dict: + return {"status": True, "result": "TagoIO Run SAML SSO Successfully Updated"} + + +def mockCustomDomainCreate() -> dict: + return {"status": True, "result": "Custom domain created successfully"} + + +def mockCustomDomainInfo() -> dict: + return { + "status": True, + "result": { + "active": True, + "domain": "mycompany.com", + "subdomain": "app", + "email": "app.mycompany.com", + "dns_ssl": {"status": True, "type": "TXT", "key": "key1", "value": "value1"}, + "dns_page": {"status": True, "type": "CNAME", "key": "key2", "value": "value2"}, + "dns_email_1": {"status": True, "type": "MX", "key": "key3", "value": "value3"}, + "dns_email_2": {"status": True, "type": "TXT", "key": "key4", "value": "value4"}, + "dns_email_3": {"status": True, "type": "TXT", "key": "key5", "value": "value5"}, + "created_at": "2023-02-21T15:17:35.759Z", + }, + } + + +def mockCustomDomainDelete() -> dict: + return {"status": True, "result": "Custom domain deleted successfully"} + + +def mockCustomDomainRegenerate() -> dict: + return {"status": True, "result": "Custom domain regenerated successfully"} + + +def testRunMethodInfo(requests_mock: Mocker) -> None: + """Test info method of Run class.""" + mock_response = mockRunInfo() + requests_mock.get("https://api.tago.io/run", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.info() + + assert result["name"] == "My Run Environment" + assert result["active"] is True + + +def testRunMethodEdit(requests_mock: Mocker) -> None: + """Test edit method of Run class.""" + mock_response = mockRunEdit() + requests_mock.put("https://api.tago.io/run", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.edit({"name": "Updated Name"}) + + assert result == "TagoIO Run Successfully Updated" + + +def testRunMethodListUsers(requests_mock: Mocker) -> None: + """Test listUsers method of Run class.""" + mock_response = mockUserList() + requests_mock.get("https://api.tago.io/run/users", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.listUsers({"page": 1, "amount": 20}) + + assert isinstance(result, list) + assert len(result) == 2 + assert result[0]["id"] == "user_id_1" + assert result[1]["id"] == "user_id_2" + + +def testRunMethodUserInfo(requests_mock: Mocker) -> None: + """Test userInfo method of Run class.""" + mock_response = mockUserInfo() + requests_mock.get("https://api.tago.io/run/users/user_id_1", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.userInfo("user_id_1") + + assert result["id"] == "user_id_1" + assert result["name"] == "John Doe" + assert result["email"] == "john@example.com" + + +def testRunMethodUserCreate(requests_mock: Mocker) -> None: + """Test userCreate method of Run class.""" + mock_response = mockUserCreate() + requests_mock.post("https://api.tago.io/run/users", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.userCreate( + { + "name": "New User", + "email": "newuser@example.com", + "password": "secure123", + "timezone": "America/New_York", + } + ) + + assert result["user"] == "user_id_new" + + +def testRunMethodUserEdit(requests_mock: Mocker) -> None: + """Test userEdit method of Run class.""" + mock_response = mockUserEdit() + requests_mock.put("https://api.tago.io/run/users/user_id_1", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.userEdit("user_id_1", {"name": "Updated Name"}) + + assert result == "TagoIO Run User Successfully Updated" + + +def testRunMethodUserDelete(requests_mock: Mocker) -> None: + """Test userDelete method of Run class.""" + mock_response = mockUserDelete() + requests_mock.delete("https://api.tago.io/run/users/user_id_1", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.userDelete("user_id_1") + + assert result == "Successfully Removed" + + +def testRunMethodLoginAsUser(requests_mock: Mocker) -> None: + """Test loginAsUser method of Run class.""" + mock_response = mockLoginAsUser() + requests_mock.get("https://api.tago.io/run/users/user_id_1/login", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.loginAsUser("user_id_1") + + assert "token" in result + assert result["token"].startswith("eyJ") + + +def testRunMethodEmailTest(requests_mock: Mocker) -> None: + """Test emailTest method of Run class.""" + mock_response = mockEmailTest() + requests_mock.post("https://api.tago.io/run/email_test", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.emailTest({"subject": "Test", "body": "Test message"}) + + assert result == "E-mail sent to example@email.com" + + +def testRunMethodNotificationList(requests_mock: Mocker) -> None: + """Test notificationList method of Run class.""" + mock_response = mockNotificationList() + requests_mock.get("https://api.tago.io/run/notification/user_id_1", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.notificationList("user_id_1") + + assert isinstance(result, list) + assert len(result) == 2 + assert result[0]["id"] == "notification_id_1" + + +def testRunMethodNotificationCreate(requests_mock: Mocker) -> None: + """Test notificationCreate method of Run class.""" + mock_response = mockNotificationCreate() + requests_mock.post("https://api.tago.io/run/notification/", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.notificationCreate("user_id_1", {"title": "Alert", "message": "Important message"}) + + assert result["id"] == "notification_id_new" + + +def testRunMethodNotificationEdit(requests_mock: Mocker) -> None: + """Test notificationEdit method of Run class.""" + mock_response = mockNotificationEdit() + requests_mock.put("https://api.tago.io/run/notification/notification_id_1", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.notificationEdit("notification_id_1", {"title": "Updated Title"}) + + assert result == "TagoIO Notification User Successfully Updated" + + +def testRunMethodNotificationDelete(requests_mock: Mocker) -> None: + """Test notificationDelete method of Run class.""" + mock_response = mockNotificationDelete() + requests_mock.delete("https://api.tago.io/run/notification/notification_id_1", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.notificationDelete("notification_id_1") + + assert result == "Successfully Removed" + + +def testRunMethodSSOSAMLInfo(requests_mock: Mocker) -> None: + """Test ssoSAMLInfo method of Run class.""" + mock_response = mockSSOSAMLInfo() + requests_mock.get("https://api.tago.io/run/sso/saml", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.ssoSAMLInfo() + + assert "sp" in result + assert "idp" in result + + +def testRunMethodSSOSAMLEdit(requests_mock: Mocker) -> None: + """Test ssoSAMLEdit method of Run class.""" + mock_response = mockSSOSAMLEdit() + requests_mock.put("https://api.tago.io/run/sso/saml", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.ssoSAMLEdit({"active": True, "idp_metadata": "..."}) + + assert result == "TagoIO Run SAML SSO Successfully Updated" + + +def testRunMethodCreateCustomDomain(requests_mock: Mocker) -> None: + """Test createCustomDomain method of Run class.""" + mock_response = mockCustomDomainCreate() + requests_mock.post("https://api.tago.io/run/customdomain/profile_id_123", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.createCustomDomain("profile_id_123", {"domain": "mycompany.com", "subdomain": "app"}) + + assert result == "Custom domain created successfully" + + +def testRunMethodGetCustomDomain(requests_mock: Mocker) -> None: + """Test getCustomDomain method of Run class.""" + mock_response = mockCustomDomainInfo() + requests_mock.get("https://api.tago.io/run/customdomain/profile_id_123", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.getCustomDomain("profile_id_123") + + assert result["domain"] == "mycompany.com" + assert result["active"] is True + + +def testRunMethodDeleteCustomDomain(requests_mock: Mocker) -> None: + """Test deleteCustomDomain method of Run class.""" + mock_response = mockCustomDomainDelete() + requests_mock.delete("https://api.tago.io/run/customdomain/profile_id_123", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.deleteCustomDomain("profile_id_123") + + assert result == "Custom domain deleted successfully" + + +def testRunMethodRegenerateCustomDomain(requests_mock: Mocker) -> None: + """Test regenerateCustomDomain method of Run class.""" + mock_response = mockCustomDomainRegenerate() + requests_mock.put("https://api.tago.io/run/customdomain/regenerate/profile_id_123", json=mock_response) + + resources = Resources({"token": "your_token_value"}) + result = resources.run.regenerateCustomDomain("profile_id_123") + + assert result == "Custom domain regenerated successfully"