Skip to content

Commit

Permalink
Python: add OBJECT IDLETIME command (#313)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaron-congo authored May 28, 2024
1 parent a52a58e commit a6b1aa4
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#### Changes
* Python: Added OBJECT ENCODING command ([#1471](https://github.com/aws/glide-for-redis/pull/1471))
* Python: Added OBJECT FREQ command ([#1472](https://github.com/aws/glide-for-redis/pull/1472))
* Python: Added OBJECT IDLETIME command (TODO: add PR link)

## 0.4.0 (2024-05-26)

Expand Down
27 changes: 24 additions & 3 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3545,7 +3545,7 @@ async def object_encoding(self, key: str) -> Optional[str]:
await self._execute_command(RequestType.ObjectEncoding, [key]),
)

async def object_freq(self, key: str) -> int:
async def object_freq(self, key: str) -> Optional[int]:
"""
Returns the logarithmic access frequency counter of a Redis object stored at `key`.
Expand All @@ -3555,14 +3555,35 @@ async def object_freq(self, key: str) -> int:
key (str): The key of the object to get the logarithmic access frequency counter of.
Returns:
int: If `key` exists, returns the logarithmic access frequency counter of the object stored at `key` as an
Optional[int]: If `key` exists, returns the logarithmic access frequency counter of the object stored at `key` as an
integer. Otherwise, returns None.
Examples:
>>> await client.object_freq("my_hash")
2 # The logarithmic access frequency counter of "my_hash" has a value of 2.
"""
return cast(
int,
Optional[int],
await self._execute_command(RequestType.ObjectFreq, [key]),
)

async def object_idletime(self, key: str) -> Optional[int]:
"""
Returns the time in seconds since the last access to the value stored at `key`.
See https://valkey.io/commands/object-idletime for more details.
Args:
key (str): The key of the object to get the idle time of.
Returns:
Optional[int]: If `key` exists, returns the idle time in seconds. Otherwise, returns None.
Examples:
>>> await client.object_idletime("my_hash")
13 # "my_hash" was last accessed 13 seconds ago.
"""
return cast(
Optional[int],
await self._execute_command(RequestType.ObjectIdleTime, [key]),
)
16 changes: 15 additions & 1 deletion python/python/glide/async_commands/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -2485,11 +2485,25 @@ def object_freq(self: TTransaction, key: str) -> TTransaction:
key (str): The key of the object to get the logarithmic access frequency counter of.
Command response:
int: If `key` exists, returns the logarithmic access frequency counter of the object stored at `key` as an
Optional[int]: If `key` exists, returns the logarithmic access frequency counter of the object stored at `key` as an
integer. Otherwise, returns None.
"""
return self.append_command(RequestType.ObjectFreq, [key])

def object_idletime(self: TTransaction, key: str) -> TTransaction:
"""
Returns the time in seconds since the last access to the value stored at `key`.
See https://valkey.io/commands/object-idletime for more details.
Args:
key (str): The key of the object to get the idle time of.
Command response:
Optional[int]: If `key` exists, returns the idle time in seconds. Otherwise, returns None.
"""
return self.append_command(RequestType.ObjectIdleTime, [key])


class Transaction(BaseTransaction):
"""
Expand Down
15 changes: 14 additions & 1 deletion python/python/tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3409,10 +3409,23 @@ async def test_object_freq(self, redis_client: TRedisClient):
)
assert await redis_client.object_freq(non_existing_key) is None
assert await redis_client.set(key, "") == OK
assert await redis_client.object_freq(key) >= 0
freq = await redis_client.object_freq(key)
assert freq is not None and freq >= 0
finally:
await redis_client.config_set({maxmemory_policy_key: maxmemory_policy})

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_object_idletime(self, redis_client: TRedisClient):
string_key = get_random_string(10)
non_existing_key = get_random_string(10)

assert await redis_client.object_idletime(non_existing_key) is None
assert await redis_client.set(string_key, "foo") == OK
time.sleep(1)
idletime = await redis_client.object_idletime(string_key)
assert idletime is not None and idletime > 0


class TestMultiKeyCommandCrossSlot:
@pytest.mark.parametrize("cluster_mode", [True])
Expand Down
33 changes: 23 additions & 10 deletions python/python/tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ async def transaction_test(

transaction.set(key, value)
args.append(OK)
transaction.object_encoding(key)
args.append("embstr")
transaction.setrange(key, 0, value)
args.append(len(value))
transaction.get(key)
Expand Down Expand Up @@ -502,10 +500,13 @@ async def test_transaction_chaining_calls(self, redis_client: TRedisClient):

assert await redis_client.exec(transaction) == [OK, "value", 1]

# object_freq is not tested in transaction_test as it requires that we set and later restore the max memory policy
# The object commands are tested here instead of transaction_test because they have special requirements:
# - OBJECT FREQ and OBJECT IDLETIME require specific maxmemory policies to be set on the config
# - we cannot reliably predict the exact response values for OBJECT FREQ, OBJECT IDLETIME, and OBJECT REFCOUNT
# - OBJECT ENCODING is tested here since all the other OBJECT commands are tested here
@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_transaction_object_freq(
async def test_transaction_object_commands(
self, redis_client: TRedisClient, cluster_mode: bool
):
string_key = get_random_string(10)
Expand All @@ -515,16 +516,28 @@ async def test_transaction_object_freq(

try:
transaction = ClusterTransaction() if cluster_mode else Transaction()
transaction.config_set({maxmemory_policy_key: "allkeys-lfu"})
transaction.set(string_key, "foo")
transaction.object_encoding(string_key)
# OBJECT FREQ requires a LFU maxmemory-policy
transaction.config_set({maxmemory_policy_key: "allkeys-lfu"})
transaction.object_freq(string_key)
# OBJECT IDLETIME requires a non-LFU maxmemory-policy
transaction.config_set({maxmemory_policy_key: "allkeys-random"})
transaction.object_idletime(string_key)

response = await redis_client.exec(transaction)
assert response is not None
assert len(response) == 3
assert response[0] == OK
assert response[1] == OK
frequency = cast(int, response[2])
assert frequency >= 0
assert response[0] == OK # transaction.set(string_key, "foo")
assert response[1] == "embstr" # transaction.object_encoding(string_key)
assert (
response[2] == OK
) # transaction.config_set({maxmemory_policy_key: "allkeys-lfu"})
assert cast(int, response[3]) >= 0 # transaction.object_freq(string_key)
assert (
response[4] == OK
) # transaction.config_set({maxmemory_policy_key: "allkeys-random"})
assert (
cast(int, response[5]) >= 0
) # transaction.object_idletime(string_key)
finally:
await redis_client.config_set({maxmemory_policy_key: maxmemory_policy})

0 comments on commit a6b1aa4

Please sign in to comment.