From ec21fb3ef765c7baef1963b3588e4dc7fc94d99f Mon Sep 17 00:00:00 2001 From: Yi-Pin Chen Date: Thu, 23 May 2024 13:44:03 -0700 Subject: [PATCH 1/4] Added Python: SETRANGE command (#299) --- python/python/glide/async_commands/core.py | 30 +++++++++++++++++++ .../glide/async_commands/transaction.py | 20 +++++++++++++ python/python/tests/test_async_client.py | 22 ++++++++++++++ python/python/tests/test_transaction.py | 2 ++ 4 files changed, 74 insertions(+) diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 96c11b6852..c7d359b3bc 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -599,6 +599,36 @@ async def incrbyfloat(self, key: str, amount: float) -> float: await self._execute_command(RequestType.IncrByFloat, [key, str(amount)]), ) + async def setrange(self, key: str, offset: int, value: str) -> int: + """ + Overwrites part of the string stored at `key`, starting at the specified + `offset`, for the entire length of `value`. + If the `offset` is larger than the current length of the string at `key`, + the string is padded with zero bytes to make `offset` fit. Create the `key` + if it doesn't exist. + + See https://valkey.io/commands/setrange for more details. + + Args: + key (str): The key of the string to update. + offset (int): The position in the string where `value` should be written. + value (str): The string written with `offset`. + + Returns: + int: The length of the string stored at `key` after it was modified. + + Examples: + >>> await client.set("key", "Hello World") + >>> await client.setrange("key", 6, "Redis") + 11 # The length of the string stored at `key` after it was modified. + """ + return cast( + int, + await self._execute_command( + RequestType.SetRange, [key, str(offset), value] + ), + ) + async def mset(self, key_value_map: Mapping[str, str]) -> TOK: """ Set multiple keys to multiple values in a single atomic operation. diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 3dd9d03e87..1e8023a77d 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -403,6 +403,26 @@ def decrby(self: TTransaction, key: str, amount: int) -> TTransaction: """ return self.append_command(RequestType.DecrBy, [key, str(amount)]) + def setrange(self: TTransaction, key: str, offset: int, value: str) -> TTransaction: + """ + Overwrites part of the string stored at `key`, starting at the specified + `offset`, for the entire length of `value`. + If the `offset` is larger than the current length of the string at `key`, + the string is padded with zero bytes to make `offset` fit. Create the `key` + if it doesn't exist. + + See https://valkey.io/commands/setrange for more details. + + Args: + key (str): The key of the string to update. + offset (int): The position in the string where `value` should be written. + value (str): The string written with `offset`. + + Command response: + int: The length of the string stored at `key` after it was modified. + """ + return self.append_command(RequestType.SetRange, [key, str(offset), value]) + def hset( self: TTransaction, key: str, field_value_map: Mapping[str, str] ) -> TTransaction: diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index c656ad60ac..ac323049bb 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -562,6 +562,28 @@ async def test_decr_with_str_value(self, redis_client: TRedisClient): assert "value is not an integer" in str(e) + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_setrange(self, redis_client: TRedisClient): + key1 = get_random_string(10) + key2 = get_random_string(10) + + # test new key and existing key + assert await redis_client.setrange(key1, 0, "Hello World") == 11 + assert await redis_client.setrange(key1, 6, "GLIDE") == 11 + + # offset > len + assert await redis_client.setrange(key1, 15, "GLIDE") == 20 + + # negative offset + with pytest.raises(RequestError): + assert await redis_client.setrange(key1, -1, "GLIDE") + + # non-string key + assert await redis_client.lpush(key2, ["_"]) == 1 + with pytest.raises(RequestError): + assert await redis_client.setrange(key2, 0, "_") + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_hset_hget_hgetall(self, redis_client: TRedisClient): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index ffbdcd3ad0..94ab6df08a 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -63,6 +63,8 @@ async def transaction_test( transaction.set(key, value) args.append(OK) + transaction.setrange(key, 0, value) + args.append(len(value)) transaction.get(key) args.append(value) transaction.type(key) From 0526c6d3256b4e6634e0e865d80050f32742012f Mon Sep 17 00:00:00 2001 From: Yi-Pin Chen Date: Thu, 23 May 2024 13:51:54 -0700 Subject: [PATCH 2/4] Update CHANGELOG to include Python: SETRANGE command --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b028ee3bc9..ea8130e4e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ * Python: Added SINTER command ([#1434](https://github.com/aws/glide-for-redis/pull/1434)) * Python: Added SDIFF command ([#1437](https://github.com/aws/glide-for-redis/pull/1437)) * Python: Added SDIFFSTORE command ([#1449](https://github.com/aws/glide-for-redis/pull/1449)) +* Python: Added SETRANGE command ([#1453](https://github.com/aws/glide-for-redis/pull/1453) #### Fixes From 85c319c2d1dba56cf15ed1be01959e433fa5712b Mon Sep 17 00:00:00 2001 From: Yi-Pin Chen Date: Fri, 24 May 2024 09:42:35 -0700 Subject: [PATCH 3/4] Updated comments --- python/python/glide/async_commands/core.py | 2 +- python/python/glide/async_commands/transaction.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index c7d359b3bc..4f99ba256a 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -604,7 +604,7 @@ async def setrange(self, key: str, offset: int, value: str) -> int: Overwrites part of the string stored at `key`, starting at the specified `offset`, for the entire length of `value`. If the `offset` is larger than the current length of the string at `key`, - the string is padded with zero bytes to make `offset` fit. Create the `key` + the string is padded with zero bytes to make `offset` fit. Creates the `key` if it doesn't exist. See https://valkey.io/commands/setrange for more details. diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 1e8023a77d..15f8160723 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -408,7 +408,7 @@ def setrange(self: TTransaction, key: str, offset: int, value: str) -> TTransact Overwrites part of the string stored at `key`, starting at the specified `offset`, for the entire length of `value`. If the `offset` is larger than the current length of the string at `key`, - the string is padded with zero bytes to make `offset` fit. Create the `key` + the string is padded with zero bytes to make `offset` fit. Creates the `key` if it doesn't exist. See https://valkey.io/commands/setrange for more details. From c8960d0455faee79e3b26c902d674bbf98ac58f2 Mon Sep 17 00:00:00 2001 From: Yi-Pin Chen Date: Fri, 24 May 2024 09:45:09 -0700 Subject: [PATCH 4/4] Updated test comment --- python/python/tests/test_async_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index ac323049bb..8aa0ee3ab2 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -579,7 +579,7 @@ async def test_setrange(self, redis_client: TRedisClient): with pytest.raises(RequestError): assert await redis_client.setrange(key1, -1, "GLIDE") - # non-string key + # non-string key throws RequestError assert await redis_client.lpush(key2, ["_"]) == 1 with pytest.raises(RequestError): assert await redis_client.setrange(key2, 0, "_")