Skip to content
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
@@ -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 ([#1474](https://github.com/aws/glide-for-redis/pull/1474))

## 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]:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strange - we have a test for this but I guess Python doesn't really care

"""
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})