From 92639dffd42c660bdbfd7bc7365b1124fb4099c2 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Mon, 4 Nov 2024 11:02:05 +0000 Subject: [PATCH 1/3] Makes data argument optional on transactional delete Signed-off-by: Elena Kolevska --- dapr/clients/grpc/_request.py | 4 ++-- dapr/clients/grpc/client.py | 3 ++- examples/state_store/README.md | 3 +++ examples/state_store/state_store.py | 15 +++++++++++++-- tests/clients/test_dapr_grpc_client.py | 12 +++++++++++- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/dapr/clients/grpc/_request.py b/dapr/clients/grpc/_request.py index 0f149e1f..93c5cdbc 100644 --- a/dapr/clients/grpc/_request.py +++ b/dapr/clients/grpc/_request.py @@ -281,7 +281,7 @@ class TransactionalStateOperation: def __init__( self, key: str, - data: Union[bytes, str], + data: Optional[Union[bytes, str]] = None, etag: Optional[str] = None, operation_type: TransactionOperationType = TransactionOperationType.upsert, ): @@ -297,7 +297,7 @@ def __init__( Raises: ValueError: data is not bytes or str. """ - if not isinstance(data, (bytes, str)): + if operation_type != TransactionOperationType.delete and not isinstance(data, (bytes, str)): raise ValueError(f'invalid type for data {type(data)}') self._key = key diff --git a/dapr/clients/grpc/client.py b/dapr/clients/grpc/client.py index 94793907..5eb7999f 100644 --- a/dapr/clients/grpc/client.py +++ b/dapr/clients/grpc/client.py @@ -884,6 +884,7 @@ def execute_state_transaction( transactional_metadata: Optional[Dict[str, str]] = dict(), metadata: Optional[MetadataTuple] = None, ) -> DaprResponse: + """Saves or deletes key-value pairs to a statestore as a transaction This saves or deletes key-values to the statestore as part of a single transaction, @@ -929,7 +930,7 @@ def execute_state_transaction( operationType=o.operation_type.value, request=common_v1.StateItem( key=o.key, - value=to_bytes(o.data), + value=to_bytes(o.data) if o.data is not None else None, etag=common_v1.Etag(value=o.etag) if o.etag is not None else None, ), ) diff --git a/examples/state_store/README.md b/examples/state_store/README.md index 7c323873..67ea5791 100644 --- a/examples/state_store/README.md +++ b/examples/state_store/README.md @@ -41,6 +41,7 @@ expected_stdout_lines: - "== APP == Cannot save bulk due to bad etags. ErrorCode=StatusCode.ABORTED" - "== APP == Got value=b'value_1' eTag=1" - "== APP == Got items with etags: [(b'value_1_updated', '2'), (b'value_2', '2')]" + - "== APP == Got values after transaction delete: [b'', b'']" - "== APP == Got value after delete: b''" timeout_seconds: 5 --> @@ -67,6 +68,8 @@ The output should be as follows: == APP == Got items with etags: [(b'value_1_updated', '2'), (b'value_2', '2')] +== APP == Got values after transaction delete: [b'', b''] + == APP == Got value after delete: b'' ``` diff --git a/examples/state_store/state_store.py b/examples/state_store/state_store.py index f87167f5..bc59d006 100644 --- a/examples/state_store/state_store.py +++ b/examples/state_store/state_store.py @@ -78,6 +78,7 @@ etag=state.etag, ), TransactionalStateOperation(key=another_key, data=another_value), + TransactionalStateOperation(key=yet_another_key, data=yet_another_value), ], ) @@ -87,7 +88,17 @@ ).items print(f'Got items with etags: {[(i.data, i.etag) for i in items]}') + # Transaction delete + d.execute_state_transaction(store_name=storeName, operations=[ + TransactionalStateOperation(operation_type=TransactionOperationType.delete, key=key), + TransactionalStateOperation(operation_type=TransactionOperationType.delete, key=another_key),]) + + # Batch get + items = d.get_bulk_state(store_name=storeName, keys=[key, another_key], + states_metadata={'metakey': 'metavalue'}).items + print(f'Got values after transaction delete: {[data.data for data in items]}') + # Delete one state by key. - d.delete_state(store_name=storeName, key=key, state_metadata={'metakey': 'metavalue'}) - data = d.get_state(store_name=storeName, key=key).data + d.delete_state(store_name=storeName, key=yet_another_key, state_metadata={'metakey': 'metavalue'}) + data = d.get_state(store_name=storeName, key=yet_another_key).data print(f'Got value after delete: {data}') diff --git a/tests/clients/test_dapr_grpc_client.py b/tests/clients/test_dapr_grpc_client.py index d3eab236..15c89dd0 100644 --- a/tests/clients/test_dapr_grpc_client.py +++ b/tests/clients/test_dapr_grpc_client.py @@ -33,7 +33,7 @@ from .fake_dapr_server import FakeDaprSidecar from dapr.conf import settings from dapr.clients.grpc._helpers import to_bytes -from dapr.clients.grpc._request import TransactionalStateOperation +from dapr.clients.grpc._request import TransactionalStateOperation, TransactionOperationType from dapr.clients.grpc._state import StateOptions, Consistency, Concurrency, StateItem from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions from dapr.clients.grpc._response import ( @@ -508,6 +508,16 @@ def test_transaction_then_get_states(self): self.assertEqual(resp.items[1].key, another_key) self.assertEqual(resp.items[1].data, to_bytes(another_value.upper())) + dapr.execute_state_transaction(store_name='statestore', + operations=[TransactionalStateOperation(key=key, operation_type=TransactionOperationType.delete), + TransactionalStateOperation(key=another_key, operation_type=TransactionOperationType.delete), ], + ) + resp = dapr.get_state(store_name='statestore', key=key) + self.assertEqual(resp.data, b'') + + resp = dapr.get_state(store_name='statestore', key=another_key) + self.assertEqual(resp.data, b'') + self._fake_dapr_server.raise_exception_on_next_call( status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') ) From 5e9e02fa96eb4e77820f78849dd2565acb25aa49 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Mon, 4 Nov 2024 13:42:11 +0000 Subject: [PATCH 2/3] linter Signed-off-by: Elena Kolevska --- dapr/clients/grpc/client.py | 1 - examples/state_store/state_store.py | 21 +++++++++++++++------ tests/clients/test_dapr_grpc_client.py | 13 ++++++++++--- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/dapr/clients/grpc/client.py b/dapr/clients/grpc/client.py index 5eb7999f..73fa3e04 100644 --- a/dapr/clients/grpc/client.py +++ b/dapr/clients/grpc/client.py @@ -884,7 +884,6 @@ def execute_state_transaction( transactional_metadata: Optional[Dict[str, str]] = dict(), metadata: Optional[MetadataTuple] = None, ) -> DaprResponse: - """Saves or deletes key-value pairs to a statestore as a transaction This saves or deletes key-values to the statestore as part of a single transaction, diff --git a/examples/state_store/state_store.py b/examples/state_store/state_store.py index bc59d006..a7b449d6 100644 --- a/examples/state_store/state_store.py +++ b/examples/state_store/state_store.py @@ -89,16 +89,25 @@ print(f'Got items with etags: {[(i.data, i.etag) for i in items]}') # Transaction delete - d.execute_state_transaction(store_name=storeName, operations=[ - TransactionalStateOperation(operation_type=TransactionOperationType.delete, key=key), - TransactionalStateOperation(operation_type=TransactionOperationType.delete, key=another_key),]) + d.execute_state_transaction( + store_name=storeName, + operations=[ + TransactionalStateOperation(operation_type=TransactionOperationType.delete, key=key), + TransactionalStateOperation( + operation_type=TransactionOperationType.delete, key=another_key + ), + ], + ) # Batch get - items = d.get_bulk_state(store_name=storeName, keys=[key, another_key], - states_metadata={'metakey': 'metavalue'}).items + items = d.get_bulk_state( + store_name=storeName, keys=[key, another_key], states_metadata={'metakey': 'metavalue'} + ).items print(f'Got values after transaction delete: {[data.data for data in items]}') # Delete one state by key. - d.delete_state(store_name=storeName, key=yet_another_key, state_metadata={'metakey': 'metavalue'}) + d.delete_state( + store_name=storeName, key=yet_another_key, state_metadata={'metakey': 'metavalue'} + ) data = d.get_state(store_name=storeName, key=yet_another_key).data print(f'Got value after delete: {data}') diff --git a/tests/clients/test_dapr_grpc_client.py b/tests/clients/test_dapr_grpc_client.py index 15c89dd0..ad90d8cc 100644 --- a/tests/clients/test_dapr_grpc_client.py +++ b/tests/clients/test_dapr_grpc_client.py @@ -508,9 +508,16 @@ def test_transaction_then_get_states(self): self.assertEqual(resp.items[1].key, another_key) self.assertEqual(resp.items[1].data, to_bytes(another_value.upper())) - dapr.execute_state_transaction(store_name='statestore', - operations=[TransactionalStateOperation(key=key, operation_type=TransactionOperationType.delete), - TransactionalStateOperation(key=another_key, operation_type=TransactionOperationType.delete), ], + dapr.execute_state_transaction( + store_name='statestore', + operations=[ + TransactionalStateOperation( + key=key, operation_type=TransactionOperationType.delete + ), + TransactionalStateOperation( + key=another_key, operation_type=TransactionOperationType.delete + ), + ], ) resp = dapr.get_state(store_name='statestore', key=key) self.assertEqual(resp.data, b'') From e203d14d3cb759ba63c0fd0ee19045186b41ee85 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Mon, 11 Nov 2024 16:50:55 +0000 Subject: [PATCH 3/3] Linter/type check Signed-off-by: Elena Kolevska --- dapr/clients/grpc/_request.py | 2 +- dapr/clients/grpc/client.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dapr/clients/grpc/_request.py b/dapr/clients/grpc/_request.py index 93c5cdbc..9ad291cf 100644 --- a/dapr/clients/grpc/_request.py +++ b/dapr/clients/grpc/_request.py @@ -311,7 +311,7 @@ def key(self) -> str: return self._key @property - def data(self) -> Union[bytes, str]: + def data(self) -> Union[bytes, str, None]: """Gets raw data.""" return self._data diff --git a/dapr/clients/grpc/client.py b/dapr/clients/grpc/client.py index 9bc4c9b1..4aa75b55 100644 --- a/dapr/clients/grpc/client.py +++ b/dapr/clients/grpc/client.py @@ -933,7 +933,7 @@ def execute_state_transaction( operationType=o.operation_type.value, request=common_v1.StateItem( key=o.key, - value=to_bytes(o.data) if o.data is not None else None, + value=to_bytes(o.data) if o.data is not None else to_bytes(''), etag=common_v1.Etag(value=o.etag) if o.etag is not None else None, ), )