diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ce6beafea..c80ae463bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ * Python: Added SDIFFSTORE command ([#1449](https://github.com/aws/glide-for-redis/pull/1449)) * Python: Added SINTERSTORE command ([#1459](https://github.com/aws/glide-for-redis/pull/1459)) * Python: Added SMISMEMBER command ([#1461](https://github.com/aws/glide-for-redis/pull/1461)) - +* Python: Added SETRANGE command ([#1453](https://github.com/aws/glide-for-redis/pull/1453) #### Fixes * Python: Fix typing error "‘type’ object is not subscriptable" ([#1203](https://github.com/aws/glide-for-redis/pull/1203)) diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index a5a8420819..e7c4036769 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. Creates 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 8b780e6072..14a593e9bc 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. Creates 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 b54e37f76a..1f20cfe1e6 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 throws RequestError + 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 c5071d8239..548c2a4934 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)