Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python: Add SETRANGE command #1453

Merged
merged 5 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
30 changes: 30 additions & 0 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions python/python/glide/async_commands/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
22 changes: 22 additions & 0 deletions python/python/tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions python/python/tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading