From d56c051193d1e6a3e7a43c7b40c22aaf2e981f39 Mon Sep 17 00:00:00 2001 From: skyflow-lipsa Date: Thu, 6 Jul 2023 10:17:15 +0530 Subject: [PATCH 01/10] SK-865 development delete by skyflow id in python sdk --- samples/delete_by_id_sample.py | 36 ++++++++++ skyflow/_utils.py | 3 + skyflow/vault/_client.py | 39 +++++++++- skyflow/vault/_config.py | 4 ++ skyflow/vault/_delete_by_id.py | 40 +++++++++++ tests/vault/test_delete_by_id.py | 119 +++++++++++++++++++++++++++++++ 6 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 samples/delete_by_id_sample.py create mode 100644 skyflow/vault/_delete_by_id.py create mode 100644 tests/vault/test_delete_by_id.py diff --git a/samples/delete_by_id_sample.py b/samples/delete_by_id_sample.py new file mode 100644 index 0000000..f23a0c6 --- /dev/null +++ b/samples/delete_by_id_sample.py @@ -0,0 +1,36 @@ +''' + Copyright (c) 2022 Skyflow, Inc. +''' +from skyflow.errors import SkyflowError +from skyflow.service_account import generate_bearer_token, is_expired +from skyflow.vault import Client, Configuration,DeleteOptions + + +# cache token for reuse +bearerToken = '' + + +def token_provider(): + global bearerToken + if is_expired(bearerToken): + bearerToken, _ = generate_bearer_token('') + return bearerToken + + +try: + config = Configuration( + '', '', token_provider) + client = Client(config) + options = DeleteOptions(False) + + data = {"records": [ + { + "id": [""], + "table": "", + } + ]} + + response = client.delete_by_id(data,options=options) + print('Response:', response) +except SkyflowError as e: + print('Error Occurred:', e) diff --git a/skyflow/_utils.py b/skyflow/_utils.py index 1c56830..c53d1f9 100644 --- a/skyflow/_utils.py +++ b/skyflow/_utils.py @@ -75,6 +75,8 @@ class InfoMessages(Enum): UPDATE_DATA_SUCCESS = "Data has been updated successfully" GET_TRIGGERED = "Get triggered." GET_SUCCESS = "Data fetched successfully." + DELETE_BY_ID_TRIGGERED = "Delete by ID triggered." + DELETE_DATA_SUCCESS = "Data has been deleted successfully." class InterfaceName(Enum): @@ -89,6 +91,7 @@ class InterfaceName(Enum): IS_TOKEN_VALID = "service_account.isTokenValid" IS_EXPIRED = "service_account.is_expired" + DELETE_BY_ID = "client.delete_by_id" def http_build_query(data): diff --git a/skyflow/vault/_client.py b/skyflow/vault/_client.py index a422b10..8fc4286 100644 --- a/skyflow/vault/_client.py +++ b/skyflow/vault/_client.py @@ -4,9 +4,11 @@ import json import types import requests + +from ._delete_by_id import deleteProcessResponse from ._insert import getInsertRequestBody, processResponse, convertResponse from ._update import sendUpdateRequests, createUpdateResponseBody -from ._config import Configuration +from ._config import Configuration, DeleteOptions from ._config import InsertOptions, ConnectionConfig, UpdateOptions from ._connection import createRequest from ._detokenize import sendDetokenizeRequests, createDetokenizeResponseBody @@ -172,4 +174,37 @@ def update(self, updateInput, options: UpdateOptions = UpdateOptions()): SkyflowErrorMessages.PARTIAL_SUCCESS, result, interface=interface) else: log_info(InfoMessages.UPDATE_DATA_SUCCESS.value, interface) - return result \ No newline at end of file + return result + + def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): + interface = InterfaceName.DELETE_BY_ID.value + log_info(InfoMessages.DELETE_BY_ID_TRIGGERED.value, interface=interface) + + self._checkConfig(interface) + + self.storedToken = tokenProviderWrapper( + self.storedToken, self.tokenProvider, interface) + headers = { + "Authorization": "Bearer " + self.storedToken, + "sky-metadata": json.dumps(getMetrics()) + } + error_list = [] + result_list = [] + errors = {} + result = {} + for record in records["records"]: + request_url = self._get_complete_vault_url() + "/" + record["table"] + "/" + record["id"][0] + response = requests.delete(request_url, headers=headers) + processed_response = deleteProcessResponse(response, records) + if processed_response.get('code') == 404: + errors.update({'id': record["id"][0], 'error': processed_response}) + error_list.append(errors) + else: + result_list.append(processed_response) + if result_list: + result.update({'records': result_list}) + if errors: + result.update({'errors': error_list}) + + log_info(InfoMessages.DELETE_DATA_SUCCESS.value, interface) + return result diff --git a/skyflow/vault/_config.py b/skyflow/vault/_config.py index 25fe3cd..77abd39 100644 --- a/skyflow/vault/_config.py +++ b/skyflow/vault/_config.py @@ -38,6 +38,10 @@ class UpdateOptions: def __init__(self, tokens: bool=True): self.tokens = tokens +class DeleteOptions: + def __init__(self, tokens: bool=False): + self.tokens = tokens + class RequestMethod(Enum): GET = 'GET' POST = 'POST' diff --git a/skyflow/vault/_delete_by_id.py b/skyflow/vault/_delete_by_id.py new file mode 100644 index 0000000..c9d9219 --- /dev/null +++ b/skyflow/vault/_delete_by_id.py @@ -0,0 +1,40 @@ +''' + Copyright (c) 2022 Skyflow, Inc. +''' +import json + +import requests +from requests.models import HTTPError +from skyflow.errors._skyflow_errors import SkyflowError, SkyflowErrorCodes, SkyflowErrorMessages +from skyflow._utils import InterfaceName + +interface = InterfaceName.DELETE_BY_ID.value + + +def deleteProcessResponse(response: requests.Response, interface=interface): + statusCode = response.status_code + content = response.content.decode('utf-8') + try: + response.raise_for_status() + if statusCode == 204: + return None + try: + return json.loads(content) + except: + raise SkyflowError( + statusCode, SkyflowErrorMessages.RESPONSE_NOT_JSON.value % content, interface=interface) + except HTTPError: + message = SkyflowErrorMessages.API_ERROR.value % statusCode + if response is not None and response.content is not None: + try: + errorResponse = json.loads(content) + if 'error' in errorResponse and type(errorResponse['error']) == dict and 'message' in errorResponse[ + 'error']: + message = errorResponse['error']['message'] + except: + message = SkyflowErrorMessages.RESPONSE_NOT_JSON.value % content + error = {} + if 'x-request-id' in response.headers: + message += ' - request id: ' + response.headers['x-request-id'] + error.update({"code": statusCode, "description": message}) + return error diff --git a/tests/vault/test_delete_by_id.py b/tests/vault/test_delete_by_id.py new file mode 100644 index 0000000..bbe39ef --- /dev/null +++ b/tests/vault/test_delete_by_id.py @@ -0,0 +1,119 @@ +import json +import unittest +import os + +import asyncio +import warnings +import requests +from requests import HTTPError +from requests.models import Response +from dotenv import dotenv_values + +from skyflow.errors import SkyflowError, SkyflowErrorCodes +from skyflow.errors._skyflow_errors import SkyflowErrorMessages +from skyflow.service_account import generate_bearer_token +from skyflow.vault._client import Client +from skyflow.vault._config import Configuration,DeleteOptions +from skyflow.vault._delete_by_id import deleteProcessResponse + + +class TestDelete(unittest.TestCase): + + def setUp(self) -> None: + self.envValues = dotenv_values(".env") + self.dataPath = os.path.join(os.getcwd(), 'tests/vault/data/') + self.event_loop = asyncio.new_event_loop() + self.mocked_futures = [] + + def tokenProvider(): + token, type = generate_bearer_token( + self.envValues["CREDENTIALS_FILE_PATH"]) + return token + + config = Configuration( + self.envValues["VAULT_ID"], self.envValues["VAULT_URL"], tokenProvider) + self.client = Client(config) + warnings.filterwarnings( + action="ignore", message="unclosed", category=ResourceWarning) + + self.record_id = "123" + + self.mockResponse = { + "responses": [ + { + "records": [ + { + "skyflow_id": self.record_id, + "deleted": True + } + ] + } + ] + } + self.DeleteOptions = DeleteOptions(tokens=False) + + return super().setUp() + + def getDataPath(self, file): + return self.dataPath + file + '.json' + + def testDeleteByIdInvalidIdsType(self): + invalidData = {"records": [ + {"ids": "invalid", "table": "stripe"}]} + try: + self.client.get_by_id(invalidData) + self.fail('Should have thrown an error') + except SkyflowError as e: + self.assertEqual(e.code, SkyflowErrorCodes.INVALID_INPUT.value) + self.assertEqual( + e.message, SkyflowErrorMessages.INVALID_IDS_TYPE.value % (str)) + + def testDeleteByIdNoTable(self): + invalidData = {"records": [ + {"ids": ["id1"], "invalid": "invalid"}]} + try: + self.client.get_by_id(invalidData) + self.fail('Should have thrown an error') + except SkyflowError as e: + self.assertEqual(e.code, SkyflowErrorCodes.INVALID_INPUT.value) + self.assertEqual( + e.message, SkyflowErrorMessages.TABLE_KEY_ERROR.value) + + def testDeleteByIdInvalidTableType(self): + invalidData = {"records": [ + {"ids": ["id1"], "table": ["invalid"]}]} + try: + self.client.get_by_id(invalidData) + self.fail('Should have thrown an error') + except SkyflowError as e: + self.assertEqual(e.code, SkyflowErrorCodes.INVALID_INPUT.value) + self.assertEqual( + e.message, SkyflowErrorMessages.INVALID_TABLE_TYPE.value % (list)) + + def deleteProcessResponse(response: requests.Response, interface=None): + statusCode = response.status_code + content = response.content.decode('utf-8') + try: + response.raise_for_status() + if statusCode == 204: + return None + try: + return json.loads(content) + except: + raise SkyflowError( + statusCode, SkyflowErrorMessages.RESPONSE_NOT_JSON.value % content, interface=interface) + except HTTPError: + message = SkyflowErrorMessages.API_ERROR.value % statusCode + if content is not None: + try: + errorResponse = json.loads(content) + if 'error' in errorResponse and type(errorResponse['error']) == dict and 'message' in errorResponse[ + 'error']: + message = errorResponse['error']['message'] + except: + message = SkyflowErrorMessages.RESPONSE_NOT_JSON.value % content + error = {} + if 'x-request-id' in response.headers: + message += ' - request id: ' + response.headers['x-request-id'] + error.update({"code": statusCode, "description": message}) + return error \ No newline at end of file From 945aed4409bc0724e8106cf7023d6ec700153d69 Mon Sep 17 00:00:00 2001 From: skyflow-lipsa Date: Thu, 6 Jul 2023 15:17:08 +0530 Subject: [PATCH 02/10] SK-865 development delete by skyflow id in python sdk --- skyflow/vault/_client.py | 21 ++++++++++++ skyflow/vault/_delete_by_id.py | 3 +- tests/vault/test_delete_by_id.py | 56 ++++++++++++-------------------- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/skyflow/vault/_client.py b/skyflow/vault/_client.py index 8fc4286..0dbc3d5 100644 --- a/skyflow/vault/_client.py +++ b/skyflow/vault/_client.py @@ -192,6 +192,27 @@ def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): result_list = [] errors = {} result = {} + + try: + record_list=records["records"][0]['id'] + if not isinstance(record_list, list): + raise SkyflowError( + SkyflowErrorCodes.INVALID_INPUT.value, + SkyflowErrorMessages.INVALID_IDS_TYPE.value,interface=interface + ) + except KeyError: + raise SkyflowError(SkyflowErrorCodes.INVALID_INPUT, + SkyflowErrorMessages.IDS_KEY_ERROR, interface=interface) + try: + record_table = records["records"][0]['table'] + if isinstance(record_table, list): + raise SkyflowError( + SkyflowErrorCodes.INVALID_INPUT.value, + SkyflowErrorMessages.INVALID_TABLE_TYPE.value,interface=interface + ) + except KeyError: + raise SkyflowError(SkyflowErrorCodes.INVALID_INPUT, + SkyflowErrorMessages.TABLE_KEY_ERROR, interface=interface) for record in records["records"]: request_url = self._get_complete_vault_url() + "/" + record["table"] + "/" + record["id"][0] response = requests.delete(request_url, headers=headers) diff --git a/skyflow/vault/_delete_by_id.py b/skyflow/vault/_delete_by_id.py index c9d9219..f275c93 100644 --- a/skyflow/vault/_delete_by_id.py +++ b/skyflow/vault/_delete_by_id.py @@ -13,7 +13,7 @@ def deleteProcessResponse(response: requests.Response, interface=interface): statusCode = response.status_code - content = response.content.decode('utf-8') + content = response.content try: response.raise_for_status() if statusCode == 204: @@ -38,3 +38,4 @@ def deleteProcessResponse(response: requests.Response, interface=interface): message += ' - request id: ' + response.headers['x-request-id'] error.update({"code": statusCode, "description": message}) return error + diff --git a/tests/vault/test_delete_by_id.py b/tests/vault/test_delete_by_id.py index bbe39ef..cce30ec 100644 --- a/tests/vault/test_delete_by_id.py +++ b/tests/vault/test_delete_by_id.py @@ -57,22 +57,33 @@ def tokenProvider(): def getDataPath(self, file): return self.dataPath + file + '.json' - def testDeleteByIdInvalidIdsType(self): + def testDeleteByIdInvalidIdType(self): invalidData = {"records": [ - {"ids": "invalid", "table": "stripe"}]} + {"id": "invalid", "table": "stripe"}]} try: - self.client.get_by_id(invalidData) + self.client.delete_by_id(invalidData) self.fail('Should have thrown an error') except SkyflowError as e: self.assertEqual(e.code, SkyflowErrorCodes.INVALID_INPUT.value) self.assertEqual( - e.message, SkyflowErrorMessages.INVALID_IDS_TYPE.value % (str)) + e.message, SkyflowErrorMessages.INVALID_IDS_TYPE.value) + + def testDeleteByIdNoId(self): + invalidData = {"records": [ + {"invalid": "invalid", "table": "stripe"}]} + try: + self.client.delete_by_id(invalidData) + self.fail('Should have thrown an error') + except SkyflowError as e: + self.assertEqual(e.code, SkyflowErrorCodes.INVALID_INPUT.value) + self.assertEqual( + e.message, SkyflowErrorMessages.IDS_KEY_ERROR.value) def testDeleteByIdNoTable(self): invalidData = {"records": [ - {"ids": ["id1"], "invalid": "invalid"}]} + {"id": ["id1"], "invalid": "invalid"}]} try: - self.client.get_by_id(invalidData) + self.client.delete_by_id(invalidData) self.fail('Should have thrown an error') except SkyflowError as e: self.assertEqual(e.code, SkyflowErrorCodes.INVALID_INPUT.value) @@ -81,39 +92,12 @@ def testDeleteByIdNoTable(self): def testDeleteByIdInvalidTableType(self): invalidData = {"records": [ - {"ids": ["id1"], "table": ["invalid"]}]} + {"id": ["id1"], "table": ["invalid"]}]} try: - self.client.get_by_id(invalidData) + self.client.delete_by_id(invalidData) self.fail('Should have thrown an error') except SkyflowError as e: self.assertEqual(e.code, SkyflowErrorCodes.INVALID_INPUT.value) self.assertEqual( - e.message, SkyflowErrorMessages.INVALID_TABLE_TYPE.value % (list)) + e.message, SkyflowErrorMessages.INVALID_TABLE_TYPE.value) - def deleteProcessResponse(response: requests.Response, interface=None): - statusCode = response.status_code - content = response.content.decode('utf-8') - try: - response.raise_for_status() - if statusCode == 204: - return None - try: - return json.loads(content) - except: - raise SkyflowError( - statusCode, SkyflowErrorMessages.RESPONSE_NOT_JSON.value % content, interface=interface) - except HTTPError: - message = SkyflowErrorMessages.API_ERROR.value % statusCode - if content is not None: - try: - errorResponse = json.loads(content) - if 'error' in errorResponse and type(errorResponse['error']) == dict and 'message' in errorResponse[ - 'error']: - message = errorResponse['error']['message'] - except: - message = SkyflowErrorMessages.RESPONSE_NOT_JSON.value % content - error = {} - if 'x-request-id' in response.headers: - message += ' - request id: ' + response.headers['x-request-id'] - error.update({"code": statusCode, "description": message}) - return error \ No newline at end of file From da830fe1e4ed34db9fe56f5ce0458786c89efcf0 Mon Sep 17 00:00:00 2001 From: skyflow-lipsa Date: Thu, 6 Jul 2023 18:24:47 +0530 Subject: [PATCH 03/10] SK-865 development delete by skyflow id in python sdk --- tests/vault/test_delete_by_id.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/vault/test_delete_by_id.py b/tests/vault/test_delete_by_id.py index cce30ec..ca43833 100644 --- a/tests/vault/test_delete_by_id.py +++ b/tests/vault/test_delete_by_id.py @@ -4,6 +4,8 @@ import asyncio import warnings +from unittest.mock import patch, MagicMock + import requests from requests import HTTPError from requests.models import Response @@ -101,3 +103,32 @@ def testDeleteByIdInvalidTableType(self): self.assertEqual( e.message, SkyflowErrorMessages.INVALID_TABLE_TYPE.value) + def testDeleteProcessResponseWithSuccessfulResponse(self): + mock_response = requests.Response() + mock_response.status_code = 200 + mock_response._content = b'{"key": "value"}' + result = deleteProcessResponse(mock_response) + self.assertIsInstance(result, dict) + self.assertEqual(result, {"key": "value"}) + + def testDeleteProcessResponseWithNoContentResponse(self): + mock_response = requests.Response() + mock_response.status_code = 204 + result = deleteProcessResponse(mock_response) + self.assertIsNone(result) + + def testDeleteByIdWithNonExistentRecord(self): + records = {"records": [{"id": ["non_existent_id"], "table": "stripe"}]} + with patch("skyflow.vault._client.deleteProcessResponse") as mock_delete_process_response: + mock_delete_process_response.return_value = {"code": 404, "message": "Record not found"} + result = self.client.delete_by_id(records) + expected_result = {"errors": [{"id": "non_existent_id", "error": {"code": 404, "message": "Record not found"}}]} + self.assertEqual(result, expected_result) + + def testDeleteByIdWithSuccessfulDeletion(self): + records = {"records": [{"id": ["valid_id"], "table": "stripe"}]} + with patch("skyflow.vault._client.deleteProcessResponse") as mock_delete_process_response: + mock_delete_process_response.return_value = {"code": 200, "message": "Deletion successful"} + result = self.client.delete_by_id(records) + expected_result = {"records": [{"code": 200, "message": "Deletion successful"}]} + self.assertEqual(result, expected_result) \ No newline at end of file From 96a8ea14613892285f280ec41501e707ac685d4d Mon Sep 17 00:00:00 2001 From: skyflow-lipsa Date: Thu, 6 Jul 2023 19:53:29 +0530 Subject: [PATCH 04/10] SK-865 development delete by skyflow id in python sdk --- tests/vault/test_delete_by_id.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/tests/vault/test_delete_by_id.py b/tests/vault/test_delete_by_id.py index ca43833..3d31805 100644 --- a/tests/vault/test_delete_by_id.py +++ b/tests/vault/test_delete_by_id.py @@ -33,7 +33,7 @@ def tokenProvider(): return token config = Configuration( - self.envValues["VAULT_ID"], self.envValues["VAULT_URL"], tokenProvider) + "12345", "demo", tokenProvider) self.client = Client(config) warnings.filterwarnings( action="ignore", message="unclosed", category=ResourceWarning) @@ -116,19 +116,3 @@ def testDeleteProcessResponseWithNoContentResponse(self): mock_response.status_code = 204 result = deleteProcessResponse(mock_response) self.assertIsNone(result) - - def testDeleteByIdWithNonExistentRecord(self): - records = {"records": [{"id": ["non_existent_id"], "table": "stripe"}]} - with patch("skyflow.vault._client.deleteProcessResponse") as mock_delete_process_response: - mock_delete_process_response.return_value = {"code": 404, "message": "Record not found"} - result = self.client.delete_by_id(records) - expected_result = {"errors": [{"id": "non_existent_id", "error": {"code": 404, "message": "Record not found"}}]} - self.assertEqual(result, expected_result) - - def testDeleteByIdWithSuccessfulDeletion(self): - records = {"records": [{"id": ["valid_id"], "table": "stripe"}]} - with patch("skyflow.vault._client.deleteProcessResponse") as mock_delete_process_response: - mock_delete_process_response.return_value = {"code": 200, "message": "Deletion successful"} - result = self.client.delete_by_id(records) - expected_result = {"records": [{"code": 200, "message": "Deletion successful"}]} - self.assertEqual(result, expected_result) \ No newline at end of file From 5da23fadb09fd19dc37168cb8e632952f628c150 Mon Sep 17 00:00:00 2001 From: skyflow-lipsa Date: Fri, 7 Jul 2023 13:22:37 +0530 Subject: [PATCH 05/10] SK-865 development delete by skyflow id in python sdk --- skyflow/vault/_client.py | 2 +- tests/vault/test_delete_by_id.py | 52 ++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/skyflow/vault/_client.py b/skyflow/vault/_client.py index 0dbc3d5..cbd975c 100644 --- a/skyflow/vault/_client.py +++ b/skyflow/vault/_client.py @@ -217,7 +217,7 @@ def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): request_url = self._get_complete_vault_url() + "/" + record["table"] + "/" + record["id"][0] response = requests.delete(request_url, headers=headers) processed_response = deleteProcessResponse(response, records) - if processed_response.get('code') == 404: + if processed_response is not None and processed_response.get('code') == 404: errors.update({'id': record["id"][0], 'error': processed_response}) error_list.append(errors) else: diff --git a/tests/vault/test_delete_by_id.py b/tests/vault/test_delete_by_id.py index 3d31805..ffa48e3 100644 --- a/tests/vault/test_delete_by_id.py +++ b/tests/vault/test_delete_by_id.py @@ -4,6 +4,7 @@ import asyncio import warnings +from unittest import mock from unittest.mock import patch, MagicMock import requests @@ -116,3 +117,54 @@ def testDeleteProcessResponseWithNoContentResponse(self): mock_response.status_code = 204 result = deleteProcessResponse(mock_response) self.assertIsNone(result) + + def test_http_error_with_error_message(self): + error_response = { + 'code': 400, + 'description': 'Error occurred' + } + response = mock.Mock(spec=requests.Response) + response.status_code = 400 + response.content = json.dumps(error_response).encode() + error = deleteProcessResponse(response) + self.assertEqual(error, { + "code": 400, + "description": "Error occurred", + }) + + def test_delete_data_success(self): + records = {"records": [ + {"id": ["id1"], "table": "stripe"}]} + self.mock_response = mock.Mock(spec=requests.Response) + self.mock_response.status_code = 204 + self.mock_response.content = b'' + with mock.patch('requests.delete', return_value=self.mock_response): + result = self.client.delete_by_id(records) + self.assertIn('records', result) + self.assertEqual(result['records'], [None]) + + def test_delete_data_with_errors(self): + response = mock.Mock(spec=requests.Response) + response.status_code = 404 + response.content = b'{"code": 404, "description": "Not found"}' + with mock.patch('requests.delete', return_value=response): + records = {"records": [ + {"id": ["id1"], "table": "stripe"}, + ]} + result = self.client.delete_by_id(records) + + self.assertIn('errors', result) + error = result['errors'][0] + self.assertEqual(error['id'], "id1") + self.assertEqual(error['error'], {'code': 404, 'description': 'Not found'}) + + def testDeleteProcessInvalidResponse(self): + response = Response() + response.status_code = 500 + response._content = b"Invalid Request" + try: + deleteProcessResponse(response) + except SkyflowError as e: + self.assertEqual(e.code, 500) + self.assertEqual(e.message, SkyflowErrorMessages.RESPONSE_NOT_JSON.value % + response.content.decode('utf-8')) \ No newline at end of file From 37b5370f94da5ff7cab0df1b11431fd3bf39f772 Mon Sep 17 00:00:00 2001 From: skyflow-lipsa Date: Thu, 20 Jul 2023 20:55:28 +0530 Subject: [PATCH 06/10] SK-865 development delete by skyflow id in python sdk --- samples/delete_by_id_sample.py | 6 +- skyflow/errors/_skyflow_errors.py | 8 ++- skyflow/vault/_client.py | 55 +++++++++++----- tests/vault/test_delete_by_id.py | 101 ++++++++++++++++++++++++------ 4 files changed, 134 insertions(+), 36 deletions(-) diff --git a/samples/delete_by_id_sample.py b/samples/delete_by_id_sample.py index f23a0c6..8846c50 100644 --- a/samples/delete_by_id_sample.py +++ b/samples/delete_by_id_sample.py @@ -25,7 +25,11 @@ def token_provider(): data = {"records": [ { - "id": [""], + "id": "", + "table": "", + }, + { + "id": "", "table": "", } ]} diff --git a/skyflow/errors/_skyflow_errors.py b/skyflow/errors/_skyflow_errors.py index a2dbbf0..fad336f 100644 --- a/skyflow/errors/_skyflow_errors.py +++ b/skyflow/errors/_skyflow_errors.py @@ -7,6 +7,7 @@ class SkyflowErrorCodes(Enum): INVALID_INPUT = 400 + INVALID_INDEX = 404 SERVER_ERROR = 500 PARTIAL_SUCCESS = 500 @@ -42,12 +43,15 @@ class SkyflowErrorMessages(Enum): INVALID_JSON = "Given %s is invalid JSON" INVALID_RECORDS_TYPE = "Records key has value of type %s, expected list" INVALID_FIELDS_TYPE = "Fields key has value of type %s, expected dict" - INVALID_TABLE_TYPE = "Table key has value of type %s, expected string" + INVALID_TABLE_TYPE = "Table of type string is required in records array" INVALID_IDS_TYPE = "Ids key has value of type %s, expected list" - INVALID_ID_TYPE = "Id key has value of type %s, expected string" + INVALID_ID_TYPE = "Id of type string is required at index 0 in records array" INVALID_REDACTION_TYPE = "Redaction key has value of type %s, expected Skyflow.Redaction" INVALID_COLUMN_NAME = "Column name has value of type %s, expected string" INVALID_COLUMN_VALUE = "Column values has value of type %s, expected list" + INVALID_RECORDS_IN_DELETE = "Invalid records. records object should be an array" + EMPTY_RECORDS_IN_DELETE = "records array cannot be empty" + RECORDS_KEY_NOT_FOUND_DELETE = "records object is required" INVALID_REQUEST_BODY = "Given request body is not valid" INVALID_RESPONSE_BODY = "Given response body is not valid" diff --git a/skyflow/vault/_client.py b/skyflow/vault/_client.py index cbd975c..5433acd 100644 --- a/skyflow/vault/_client.py +++ b/skyflow/vault/_client.py @@ -192,33 +192,60 @@ def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): result_list = [] errors = {} result = {} - try: - record_list=records["records"][0]['id'] - if not isinstance(record_list, list): - raise SkyflowError( - SkyflowErrorCodes.INVALID_INPUT.value, - SkyflowErrorMessages.INVALID_IDS_TYPE.value,interface=interface - ) + if not isinstance(records, dict) or "records" not in records: + error = {"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.RECORDS_KEY_NOT_FOUND_DELETE.value}} + return error + records_list = records["records"] + if not isinstance(records_list, list): + error = {} + error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.INVALID_RECORDS_IN_DELETE.value}}) + return error + elif len(records_list) == 0: + error = {"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.EMPTY_RECORDS_IN_DELETE.value}} + return error + except KeyError: + raise SkyflowError(SkyflowErrorCodes.INVALID_INPUT, + SkyflowErrorMessages.RECORDS_KEY_ERROR, interface=interface) + try: + record_list = records["records"][0]['id'] + if not isinstance(record_list, str): + error = {} + error.update({"error": {"code": SkyflowErrorCodes.INVALID_INDEX.value, + "description": SkyflowErrorMessages.INVALID_ID_TYPE.value}}) + return error + elif record_list == "": + error = {} + error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.IDS_KEY_ERROR.value}}) + return error except KeyError: raise SkyflowError(SkyflowErrorCodes.INVALID_INPUT, SkyflowErrorMessages.IDS_KEY_ERROR, interface=interface) try: record_table = records["records"][0]['table'] - if isinstance(record_table, list): - raise SkyflowError( - SkyflowErrorCodes.INVALID_INPUT.value, - SkyflowErrorMessages.INVALID_TABLE_TYPE.value,interface=interface - ) + if not isinstance(record_table, str): + error = {} + error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.INVALID_TABLE_TYPE.value}}) + return error + elif record_table == "": + error = {} + error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.TABLE_KEY_ERROR.value}}) + return error except KeyError: raise SkyflowError(SkyflowErrorCodes.INVALID_INPUT, SkyflowErrorMessages.TABLE_KEY_ERROR, interface=interface) for record in records["records"]: - request_url = self._get_complete_vault_url() + "/" + record["table"] + "/" + record["id"][0] + request_url = self._get_complete_vault_url() + "/" + record["table"] + "/" + record["id"] response = requests.delete(request_url, headers=headers) processed_response = deleteProcessResponse(response, records) if processed_response is not None and processed_response.get('code') == 404: - errors.update({'id': record["id"][0], 'error': processed_response}) + errors.update({'id': record["id"], 'error': processed_response}) error_list.append(errors) else: result_list.append(processed_response) diff --git a/tests/vault/test_delete_by_id.py b/tests/vault/test_delete_by_id.py index ffa48e3..ce3ea03 100644 --- a/tests/vault/test_delete_by_id.py +++ b/tests/vault/test_delete_by_id.py @@ -60,16 +60,81 @@ def tokenProvider(): def getDataPath(self, file): return self.dataPath + file + '.json' + def testDeleteByIdEmptyRecordsList(self): + validData = {"records": []} + result = self.client.delete_by_id(validData) + self.assertEqual(result, { + "error": { + "code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.EMPTY_RECORDS_IN_DELETE.value + } + }) + + def testDeleteByIdInvalidRecordsType(self): + invalidData = "invalid_data" + result = self.client.delete_by_id(invalidData) + self.assertEqual(result, { + "error": { + "code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.RECORDS_KEY_NOT_FOUND_DELETE.value + } + }) + + def testDeleteByIdMissingRecordsKey(self): + invalidData = {"some_other_key": "value"} + result = self.client.delete_by_id(invalidData) + self.assertEqual(result, { + "error": { + "code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.RECORDS_KEY_NOT_FOUND_DELETE.value + } + }) + + def testDeleteByIdInvalidRecordsListType(self): + invalidData = {"records": "invalid_data"} + result = self.client.delete_by_id(invalidData) + self.assertEqual(result, { + "error": { + "code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.INVALID_RECORDS_IN_DELETE.value + } + }) + + def testDeleteByIdEmptyRecordsListType(self): + invalidData = {"records": []} + result = self.client.delete_by_id(invalidData) + # Assert the error response for an empty "records_list". + self.assertEqual(result, { + "error": { + "code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.EMPTY_RECORDS_IN_DELETE.value + } + }) + + def testDeleteByIdEmptyTable(self): + invalidData = {"records": [{"id": "id1", "table": ""}]} + response = self.client.delete_by_id(invalidData) + self.assertIn("error", response) + error = response["error"] + self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INPUT.value) + self.assertEqual(error["description"], SkyflowErrorMessages.TABLE_KEY_ERROR.value) + + def testDeleteByIdEmptyId(self): + invalidData = {"records": [{"id": "", "table": "stripe"}]} + response = self.client.delete_by_id(invalidData) + self.assertIn("error", response) + error = response["error"] + self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INPUT.value) + self.assertEqual(error["description"], SkyflowErrorMessages.IDS_KEY_ERROR.value) + def testDeleteByIdInvalidIdType(self): invalidData = {"records": [ - {"id": "invalid", "table": "stripe"}]} - try: - self.client.delete_by_id(invalidData) - self.fail('Should have thrown an error') - except SkyflowError as e: - self.assertEqual(e.code, SkyflowErrorCodes.INVALID_INPUT.value) - self.assertEqual( - e.message, SkyflowErrorMessages.INVALID_IDS_TYPE.value) + {"id": ["invalid"], "table": "stripe"}]} + response = self.client.delete_by_id(invalidData) + self.assertIn("error", response) + error = response["error"] + self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INDEX.value) + self.assertEqual(error["description"], SkyflowErrorMessages.INVALID_ID_TYPE.value) def testDeleteByIdNoId(self): invalidData = {"records": [ @@ -84,7 +149,7 @@ def testDeleteByIdNoId(self): def testDeleteByIdNoTable(self): invalidData = {"records": [ - {"id": ["id1"], "invalid": "invalid"}]} + {"id": "id1", "invalid": "invalid"}]} try: self.client.delete_by_id(invalidData) self.fail('Should have thrown an error') @@ -95,14 +160,12 @@ def testDeleteByIdNoTable(self): def testDeleteByIdInvalidTableType(self): invalidData = {"records": [ - {"id": ["id1"], "table": ["invalid"]}]} - try: - self.client.delete_by_id(invalidData) - self.fail('Should have thrown an error') - except SkyflowError as e: - self.assertEqual(e.code, SkyflowErrorCodes.INVALID_INPUT.value) - self.assertEqual( - e.message, SkyflowErrorMessages.INVALID_TABLE_TYPE.value) + {"id": "id1", "table": ["invalid"]}]} + result = self.client.delete_by_id(invalidData) + self.assertIn("error", result) + error = result["error"] + self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INPUT.value) + self.assertEqual(error["description"], SkyflowErrorMessages.INVALID_TABLE_TYPE.value) def testDeleteProcessResponseWithSuccessfulResponse(self): mock_response = requests.Response() @@ -134,7 +197,7 @@ def test_http_error_with_error_message(self): def test_delete_data_success(self): records = {"records": [ - {"id": ["id1"], "table": "stripe"}]} + {"id": "id1", "table": "stripe"}]} self.mock_response = mock.Mock(spec=requests.Response) self.mock_response.status_code = 204 self.mock_response.content = b'' @@ -149,7 +212,7 @@ def test_delete_data_with_errors(self): response.content = b'{"code": 404, "description": "Not found"}' with mock.patch('requests.delete', return_value=response): records = {"records": [ - {"id": ["id1"], "table": "stripe"}, + {"id": "id1", "table": "stripe"}, ]} result = self.client.delete_by_id(records) From 7e273c687c636537c7e2b4af07ea7eeb35dfaea5 Mon Sep 17 00:00:00 2001 From: skyflow-lipsa Date: Thu, 20 Jul 2023 21:18:10 +0530 Subject: [PATCH 07/10] SK-865 development delete by skyflow id in python sdk --- skyflow/errors/_skyflow_errors.py | 6 ++++-- skyflow/vault/_client.py | 4 ++-- tests/vault/test_delete_by_id.py | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/skyflow/errors/_skyflow_errors.py b/skyflow/errors/_skyflow_errors.py index fad336f..e277a74 100644 --- a/skyflow/errors/_skyflow_errors.py +++ b/skyflow/errors/_skyflow_errors.py @@ -43,9 +43,11 @@ class SkyflowErrorMessages(Enum): INVALID_JSON = "Given %s is invalid JSON" INVALID_RECORDS_TYPE = "Records key has value of type %s, expected list" INVALID_FIELDS_TYPE = "Fields key has value of type %s, expected dict" - INVALID_TABLE_TYPE = "Table of type string is required in records array" + INVALID_TABLE_TYPE = "Table key has value of type %s, expected string" + INVALID_TABLE_TYPE_DELETE = "Table of type string is required in records array" INVALID_IDS_TYPE = "Ids key has value of type %s, expected list" - INVALID_ID_TYPE = "Id of type string is required at index 0 in records array" + INVALID_ID_TYPE = "Id key has value of type %s, expected string" + INVALID_ID_TYPE_DELETE = "Id of type string is required at index 0 in records array" INVALID_REDACTION_TYPE = "Redaction key has value of type %s, expected Skyflow.Redaction" INVALID_COLUMN_NAME = "Column name has value of type %s, expected string" INVALID_COLUMN_VALUE = "Column values has value of type %s, expected list" diff --git a/skyflow/vault/_client.py b/skyflow/vault/_client.py index 5433acd..04cbe9f 100644 --- a/skyflow/vault/_client.py +++ b/skyflow/vault/_client.py @@ -215,7 +215,7 @@ def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): if not isinstance(record_list, str): error = {} error.update({"error": {"code": SkyflowErrorCodes.INVALID_INDEX.value, - "description": SkyflowErrorMessages.INVALID_ID_TYPE.value}}) + "description": SkyflowErrorMessages.INVALID_ID_TYPE_DELETE.value}}) return error elif record_list == "": error = {} @@ -230,7 +230,7 @@ def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): if not isinstance(record_table, str): error = {} error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, - "description": SkyflowErrorMessages.INVALID_TABLE_TYPE.value}}) + "description": SkyflowErrorMessages.INVALID_TABLE_TYPE_DELETE.value}}) return error elif record_table == "": error = {} diff --git a/tests/vault/test_delete_by_id.py b/tests/vault/test_delete_by_id.py index ce3ea03..a06d041 100644 --- a/tests/vault/test_delete_by_id.py +++ b/tests/vault/test_delete_by_id.py @@ -134,7 +134,7 @@ def testDeleteByIdInvalidIdType(self): self.assertIn("error", response) error = response["error"] self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INDEX.value) - self.assertEqual(error["description"], SkyflowErrorMessages.INVALID_ID_TYPE.value) + self.assertEqual(error["description"], SkyflowErrorMessages.INVALID_ID_TYPE_DELETE.value) def testDeleteByIdNoId(self): invalidData = {"records": [ @@ -165,7 +165,7 @@ def testDeleteByIdInvalidTableType(self): self.assertIn("error", result) error = result["error"] self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INPUT.value) - self.assertEqual(error["description"], SkyflowErrorMessages.INVALID_TABLE_TYPE.value) + self.assertEqual(error["description"], SkyflowErrorMessages.INVALID_TABLE_TYPE_DELETE.value) def testDeleteProcessResponseWithSuccessfulResponse(self): mock_response = requests.Response() From e4deba80a96e20aa386ca8e4b570d7e4282f22ee Mon Sep 17 00:00:00 2001 From: skyflow-lipsa Date: Fri, 21 Jul 2023 09:49:17 +0530 Subject: [PATCH 08/10] SK-865 development delete by skyflow id in python sdk --- skyflow/errors/_skyflow_errors.py | 2 ++ skyflow/vault/_client.py | 4 ++-- tests/vault/test_delete_by_id.py | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/skyflow/errors/_skyflow_errors.py b/skyflow/errors/_skyflow_errors.py index e277a74..175a765 100644 --- a/skyflow/errors/_skyflow_errors.py +++ b/skyflow/errors/_skyflow_errors.py @@ -53,6 +53,8 @@ class SkyflowErrorMessages(Enum): INVALID_COLUMN_VALUE = "Column values has value of type %s, expected list" INVALID_RECORDS_IN_DELETE = "Invalid records. records object should be an array" EMPTY_RECORDS_IN_DELETE = "records array cannot be empty" + EMPTY_ID_IN_DELETE = "Id cannot be empty in records array" + EMPTY_TABLE_IN_DELETE = "Table cannot be empty in records array" RECORDS_KEY_NOT_FOUND_DELETE = "records object is required" INVALID_REQUEST_BODY = "Given request body is not valid" diff --git a/skyflow/vault/_client.py b/skyflow/vault/_client.py index 04cbe9f..100655f 100644 --- a/skyflow/vault/_client.py +++ b/skyflow/vault/_client.py @@ -220,7 +220,7 @@ def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): elif record_list == "": error = {} error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, - "description": SkyflowErrorMessages.IDS_KEY_ERROR.value}}) + "description": SkyflowErrorMessages.EMPTY_ID_IN_DELETE.value}}) return error except KeyError: raise SkyflowError(SkyflowErrorCodes.INVALID_INPUT, @@ -235,7 +235,7 @@ def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): elif record_table == "": error = {} error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, - "description": SkyflowErrorMessages.TABLE_KEY_ERROR.value}}) + "description": SkyflowErrorMessages.EMPTY_TABLE_IN_DELETE.value}}) return error except KeyError: raise SkyflowError(SkyflowErrorCodes.INVALID_INPUT, diff --git a/tests/vault/test_delete_by_id.py b/tests/vault/test_delete_by_id.py index a06d041..00e05be 100644 --- a/tests/vault/test_delete_by_id.py +++ b/tests/vault/test_delete_by_id.py @@ -117,7 +117,7 @@ def testDeleteByIdEmptyTable(self): self.assertIn("error", response) error = response["error"] self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INPUT.value) - self.assertEqual(error["description"], SkyflowErrorMessages.TABLE_KEY_ERROR.value) + self.assertEqual(error["description"], SkyflowErrorMessages.EMPTY_TABLE_IN_DELETE.value) def testDeleteByIdEmptyId(self): invalidData = {"records": [{"id": "", "table": "stripe"}]} @@ -125,7 +125,7 @@ def testDeleteByIdEmptyId(self): self.assertIn("error", response) error = response["error"] self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INPUT.value) - self.assertEqual(error["description"], SkyflowErrorMessages.IDS_KEY_ERROR.value) + self.assertEqual(error["description"], SkyflowErrorMessages.EMPTY_ID_IN_DELETE.value) def testDeleteByIdInvalidIdType(self): invalidData = {"records": [ From 9db7d2cf81a7eec94c57efc3ad104664c100d051 Mon Sep 17 00:00:00 2001 From: skyflow-lipsa Date: Fri, 21 Jul 2023 12:19:47 +0530 Subject: [PATCH 09/10] SK-865 development delete by skyflow id in python sdk --- skyflow/vault/_client.py | 16 +++++++--------- tests/vault/test_delete_by_id.py | 30 ++++++++++++------------------ 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/skyflow/vault/_client.py b/skyflow/vault/_client.py index 100655f..efec17b 100644 --- a/skyflow/vault/_client.py +++ b/skyflow/vault/_client.py @@ -192,6 +192,7 @@ def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): result_list = [] errors = {} result = {} + error = {} try: if not isinstance(records, dict) or "records" not in records: error = {"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, @@ -199,7 +200,6 @@ def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): return error records_list = records["records"] if not isinstance(records_list, list): - error = {} error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, "description": SkyflowErrorMessages.INVALID_RECORDS_IN_DELETE.value}}) return error @@ -213,33 +213,31 @@ def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): try: record_list = records["records"][0]['id'] if not isinstance(record_list, str): - error = {} error.update({"error": {"code": SkyflowErrorCodes.INVALID_INDEX.value, "description": SkyflowErrorMessages.INVALID_ID_TYPE_DELETE.value}}) return error elif record_list == "": - error = {} error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, "description": SkyflowErrorMessages.EMPTY_ID_IN_DELETE.value}}) return error except KeyError: - raise SkyflowError(SkyflowErrorCodes.INVALID_INPUT, - SkyflowErrorMessages.IDS_KEY_ERROR, interface=interface) + error.update({"error": {"code": SkyflowErrorCodes.INVALID_INDEX.value, + "description": SkyflowErrorMessages.IDS_KEY_ERROR.value}}) + return error try: record_table = records["records"][0]['table'] if not isinstance(record_table, str): - error = {} error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, "description": SkyflowErrorMessages.INVALID_TABLE_TYPE_DELETE.value}}) return error elif record_table == "": - error = {} error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, "description": SkyflowErrorMessages.EMPTY_TABLE_IN_DELETE.value}}) return error except KeyError: - raise SkyflowError(SkyflowErrorCodes.INVALID_INPUT, - SkyflowErrorMessages.TABLE_KEY_ERROR, interface=interface) + error.update({"error": {"code": SkyflowErrorCodes.INVALID_INDEX.value, + "description": SkyflowErrorMessages.TABLE_KEY_ERROR.value}}) + return error for record in records["records"]: request_url = self._get_complete_vault_url() + "/" + record["table"] + "/" + record["id"] response = requests.delete(request_url, headers=headers) diff --git a/tests/vault/test_delete_by_id.py b/tests/vault/test_delete_by_id.py index 00e05be..04a024e 100644 --- a/tests/vault/test_delete_by_id.py +++ b/tests/vault/test_delete_by_id.py @@ -137,26 +137,20 @@ def testDeleteByIdInvalidIdType(self): self.assertEqual(error["description"], SkyflowErrorMessages.INVALID_ID_TYPE_DELETE.value) def testDeleteByIdNoId(self): - invalidData = {"records": [ - {"invalid": "invalid", "table": "stripe"}]} - try: - self.client.delete_by_id(invalidData) - self.fail('Should have thrown an error') - except SkyflowError as e: - self.assertEqual(e.code, SkyflowErrorCodes.INVALID_INPUT.value) - self.assertEqual( - e.message, SkyflowErrorMessages.IDS_KEY_ERROR.value) + invalidData = {"records": [{"invalid": "invalid", "table": "stripe"}]} + response = self.client.delete_by_id(invalidData) + self.assertIn("error", response) + error = response["error"] + self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INDEX.value) + self.assertEqual(error["description"], SkyflowErrorMessages.IDS_KEY_ERROR.value) def testDeleteByIdNoTable(self): - invalidData = {"records": [ - {"id": "id1", "invalid": "invalid"}]} - try: - self.client.delete_by_id(invalidData) - self.fail('Should have thrown an error') - except SkyflowError as e: - self.assertEqual(e.code, SkyflowErrorCodes.INVALID_INPUT.value) - self.assertEqual( - e.message, SkyflowErrorMessages.TABLE_KEY_ERROR.value) + invalidData = {"records": [{"id": "id1", "invalid": "invalid"}]} + response = self.client.delete_by_id(invalidData) + self.assertIn("error", response) + error = response["error"] + self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INDEX.value) + self.assertEqual(error["description"], SkyflowErrorMessages.TABLE_KEY_ERROR.value) def testDeleteByIdInvalidTableType(self): invalidData = {"records": [ From 8e28e8a6751b6360a5a1290a00f03ced77444e0c Mon Sep 17 00:00:00 2001 From: skyflow-lipsa Date: Fri, 21 Jul 2023 15:34:49 +0530 Subject: [PATCH 10/10] SK-865 development delete by skyflow id in python sdk --- skyflow/errors/_skyflow_errors.py | 8 +++---- skyflow/vault/_client.py | 38 ++++++++++++++++--------------- tests/vault/test_delete_by_id.py | 18 ++++----------- 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/skyflow/errors/_skyflow_errors.py b/skyflow/errors/_skyflow_errors.py index 175a765..414d91f 100644 --- a/skyflow/errors/_skyflow_errors.py +++ b/skyflow/errors/_skyflow_errors.py @@ -44,17 +44,17 @@ class SkyflowErrorMessages(Enum): INVALID_RECORDS_TYPE = "Records key has value of type %s, expected list" INVALID_FIELDS_TYPE = "Fields key has value of type %s, expected dict" INVALID_TABLE_TYPE = "Table key has value of type %s, expected string" - INVALID_TABLE_TYPE_DELETE = "Table of type string is required in records array" + INVALID_TABLE_TYPE_DELETE = "Table of type string is required at index %s in records array" INVALID_IDS_TYPE = "Ids key has value of type %s, expected list" INVALID_ID_TYPE = "Id key has value of type %s, expected string" - INVALID_ID_TYPE_DELETE = "Id of type string is required at index 0 in records array" + INVALID_ID_TYPE_DELETE = "Id of type string is required at index %s in records array" INVALID_REDACTION_TYPE = "Redaction key has value of type %s, expected Skyflow.Redaction" INVALID_COLUMN_NAME = "Column name has value of type %s, expected string" INVALID_COLUMN_VALUE = "Column values has value of type %s, expected list" INVALID_RECORDS_IN_DELETE = "Invalid records. records object should be an array" EMPTY_RECORDS_IN_DELETE = "records array cannot be empty" - EMPTY_ID_IN_DELETE = "Id cannot be empty in records array" - EMPTY_TABLE_IN_DELETE = "Table cannot be empty in records array" + EMPTY_ID_IN_DELETE = "Id cannot be empty in records array at index %s" + EMPTY_TABLE_IN_DELETE = "Table cannot be empty in records array at index %s" RECORDS_KEY_NOT_FOUND_DELETE = "records object is required" INVALID_REQUEST_BODY = "Given request body is not valid" diff --git a/skyflow/vault/_client.py b/skyflow/vault/_client.py index efec17b..303a4cf 100644 --- a/skyflow/vault/_client.py +++ b/skyflow/vault/_client.py @@ -211,29 +211,31 @@ def delete_by_id(self, records: dict,options: DeleteOptions = DeleteOptions()): raise SkyflowError(SkyflowErrorCodes.INVALID_INPUT, SkyflowErrorMessages.RECORDS_KEY_ERROR, interface=interface) try: - record_list = records["records"][0]['id'] - if not isinstance(record_list, str): - error.update({"error": {"code": SkyflowErrorCodes.INVALID_INDEX.value, - "description": SkyflowErrorMessages.INVALID_ID_TYPE_DELETE.value}}) - return error - elif record_list == "": - error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, - "description": SkyflowErrorMessages.EMPTY_ID_IN_DELETE.value}}) - return error + for index,record in enumerate(records["records"]): + record_list = record["id"] + if not isinstance(record_list, str): + error.update({"error": {"code": SkyflowErrorCodes.INVALID_INDEX.value, + "description": SkyflowErrorMessages.INVALID_ID_TYPE_DELETE.value % (index)}}) + return error + elif record_list == "": + error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.EMPTY_ID_IN_DELETE.value % (index)}}) + return error except KeyError: error.update({"error": {"code": SkyflowErrorCodes.INVALID_INDEX.value, "description": SkyflowErrorMessages.IDS_KEY_ERROR.value}}) return error try: - record_table = records["records"][0]['table'] - if not isinstance(record_table, str): - error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, - "description": SkyflowErrorMessages.INVALID_TABLE_TYPE_DELETE.value}}) - return error - elif record_table == "": - error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, - "description": SkyflowErrorMessages.EMPTY_TABLE_IN_DELETE.value}}) - return error + for index,record in enumerate(records["records"]): + record_table = record["table"] + if not isinstance(record_table, str): + error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.INVALID_TABLE_TYPE_DELETE.value % (index)}}) + return error + elif record_table == "": + error.update({"error": {"code": SkyflowErrorCodes.INVALID_INPUT.value, + "description": SkyflowErrorMessages.EMPTY_TABLE_IN_DELETE.value % (index)}}) + return error except KeyError: error.update({"error": {"code": SkyflowErrorCodes.INVALID_INDEX.value, "description": SkyflowErrorMessages.TABLE_KEY_ERROR.value}}) diff --git a/tests/vault/test_delete_by_id.py b/tests/vault/test_delete_by_id.py index 04a024e..9617ac4 100644 --- a/tests/vault/test_delete_by_id.py +++ b/tests/vault/test_delete_by_id.py @@ -60,16 +60,6 @@ def tokenProvider(): def getDataPath(self, file): return self.dataPath + file + '.json' - def testDeleteByIdEmptyRecordsList(self): - validData = {"records": []} - result = self.client.delete_by_id(validData) - self.assertEqual(result, { - "error": { - "code": SkyflowErrorCodes.INVALID_INPUT.value, - "description": SkyflowErrorMessages.EMPTY_RECORDS_IN_DELETE.value - } - }) - def testDeleteByIdInvalidRecordsType(self): invalidData = "invalid_data" result = self.client.delete_by_id(invalidData) @@ -117,7 +107,7 @@ def testDeleteByIdEmptyTable(self): self.assertIn("error", response) error = response["error"] self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INPUT.value) - self.assertEqual(error["description"], SkyflowErrorMessages.EMPTY_TABLE_IN_DELETE.value) + self.assertEqual(error["description"], SkyflowErrorMessages.EMPTY_TABLE_IN_DELETE.value % (0)) def testDeleteByIdEmptyId(self): invalidData = {"records": [{"id": "", "table": "stripe"}]} @@ -125,7 +115,7 @@ def testDeleteByIdEmptyId(self): self.assertIn("error", response) error = response["error"] self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INPUT.value) - self.assertEqual(error["description"], SkyflowErrorMessages.EMPTY_ID_IN_DELETE.value) + self.assertEqual(error["description"], SkyflowErrorMessages.EMPTY_ID_IN_DELETE.value % (0)) def testDeleteByIdInvalidIdType(self): invalidData = {"records": [ @@ -134,7 +124,7 @@ def testDeleteByIdInvalidIdType(self): self.assertIn("error", response) error = response["error"] self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INDEX.value) - self.assertEqual(error["description"], SkyflowErrorMessages.INVALID_ID_TYPE_DELETE.value) + self.assertEqual(error["description"], SkyflowErrorMessages.INVALID_ID_TYPE_DELETE.value % (0)) def testDeleteByIdNoId(self): invalidData = {"records": [{"invalid": "invalid", "table": "stripe"}]} @@ -159,7 +149,7 @@ def testDeleteByIdInvalidTableType(self): self.assertIn("error", result) error = result["error"] self.assertEqual(error["code"], SkyflowErrorCodes.INVALID_INPUT.value) - self.assertEqual(error["description"], SkyflowErrorMessages.INVALID_TABLE_TYPE_DELETE.value) + self.assertEqual(error["description"], SkyflowErrorMessages.INVALID_TABLE_TYPE_DELETE.value % (0)) def testDeleteProcessResponseWithSuccessfulResponse(self): mock_response = requests.Response()