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 GETRANGE command #1585

Merged
merged 2 commits into from
Jun 18, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Python: Added XLEN command ([#1503](https://github.com/aws/glide-for-redis/pull/1503))
* Python: Added LASTSAVE command ([#1509](https://github.com/aws/glide-for-redis/pull/1509))
* Python: Added GETDEL command ([#1514](https://github.com/aws/glide-for-redis/pull/1514))
* Python: Added GETRANGE command ([#1585](https://github.com/aws/glide-for-redis/pull/1585))
* Python: Added ZINTER, ZUNION commands ([#1478](https://github.com/aws/glide-for-redis/pull/1478))
* Python: Added SINTERCARD command ([#1511](https://github.com/aws/glide-for-redis/pull/1511))
* Python: Added SORT command ([#1439](https://github.com/aws/glide-for-redis/pull/1439))
Expand Down
37 changes: 37 additions & 0 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,43 @@ async def getdel(self, key: str) -> Optional[str]:
Optional[str], await self._execute_command(RequestType.GetDel, [key])
)

async def getrange(self, key: str, start: int, end: int) -> str:
"""
Returns the substring of the string value stored at `key`, determined by the offsets `start` and `end` (both are inclusive).
Negative offsets can be used in order to provide an offset starting from the end of the string.
So `-1` means the last character, `-2` the penultimate and so forth.

If `key` does not exist, an empty string is returned. If `start` or `end`
are out of range, returns the substring within the valid range of the string.

See https://valkey.io/commands/getrange/ for more details.

Args:
key (str): The key of the string.
start (int): The starting offset.
end (int): The ending offset.

Returns:
str: A substring extracted from the value stored at `key`.

Examples:
>>> await client.set("mykey", "This is a string")
>>> await client.getrange("mykey", 0, 3)
"This"
>>> await client.getrange("mykey", -3, -1)
"ing" # extracted last 3 characters of a string
>>> await client.getrange("mykey", 0, 100)
"This is a string"
>>> await client.getrange("non_existing", 5, 6)
""
"""
return cast(
str,
await self._execute_command(
RequestType.GetRange, [key, str(start), str(end)]
),
)

async def append(self, key: str, value: str) -> int:
"""
Appends a value to a key.
Expand Down
21 changes: 21 additions & 0 deletions python/python/glide/async_commands/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,27 @@ def getdel(self: TTransaction, key: str) -> TTransaction:
"""
return self.append_command(RequestType.GetDel, [key])

def getrange(self: TTransaction, key: str, start: int, end: int) -> TTransaction:
"""
Returns the substring of the string value stored at `key`, determined by the offsets `start` and `end` (both are inclusive).
Negative offsets can be used in order to provide an offset starting from the end of the string.
So `-1` means the last character, `-2` the penultimate and so forth.

If `key` does not exist, an empty string is returned. If `start` or `end`
are out of range, returns the substring within the valid range of the string.

See https://valkey.io/commands/getrange/ for more details.

Args:
key (str): The key of the string.
start (int): The starting offset.
end (int): The ending offset.

Commands response:
str: A substring extracted from the value stored at `key`.
"""
return self.append_command(RequestType.GetRange, [key, str(start), str(end)])

def set(
self: TTransaction,
key: str,
Expand Down
34 changes: 34 additions & 0 deletions python/python/tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,40 @@ async def test_getdel(self, redis_client: TRedisClient):
with pytest.raises(RequestError) as e:
await redis_client.getdel(list_key)

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_getrange(self, redis_client: TRedisClient):
key = get_random_string(16)
value = get_random_string(10)
non_string_key = get_random_string(10)

assert await redis_client.set(key, value) == OK
assert await redis_client.getrange(key, 0, 3) == value[:4]
assert await redis_client.getrange(key, -3, -1) == value[-3:]
assert await redis_client.getrange(key, 0, -1) == value

# out of range
assert await redis_client.getrange(key, 10, 100) == value[10:]
assert await redis_client.getrange(key, -200, -3) == value[-200:-2]
assert await redis_client.getrange(key, 100, 200) == ""

# incorrect range
assert await redis_client.getrange(key, -1, -3) == ""

# a redis bug, fixed in version 8: https://github.com/redis/redis/issues/13207
if await check_if_server_version_lt(redis_client, "8.0.0"):
assert await redis_client.getrange(key, -200, -100) == value[0]
else:
assert await redis_client.getrange(key, -200, -100) == ""

if await check_if_server_version_lt(redis_client, "8.0.0"):
assert await redis_client.getrange(non_string_key, 0, -1) == ""

# non-string key
assert await redis_client.lpush(non_string_key, ["_"]) == 1
with pytest.raises(RequestError):
await redis_client.getrange(non_string_key, 0, -1)

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_config_reset_stat(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 @@ -96,6 +96,8 @@ async def transaction_test(

transaction.set(key, value)
args.append(OK)
transaction.getrange(key, 0, -1)
args.append(value)
transaction.getdel(key)
args.append(value)
transaction.getdel(key)
Expand Down
Loading