diff --git a/CHANGELOG.md b/CHANGELOG.md index e4e81e2526..a93b96f61f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * Python: Added ZPOPMIN command ([#975](https://github.com/aws/glide-for-redis/pull/975)) * Node: Added STRLEN command ([#993](https://github.com/aws/glide-for-redis/pull/993)) * Node: Added LINDEX command ([#999](https://github.com/aws/glide-for-redis/pull/999)) +* Python: Added ZPOPMAX command ([#996](https://github.com/aws/glide-for-redis/pull/996)) #### Features * Python, Node: Added support in Lua Scripts ([#775](https://github.com/aws/glide-for-redis/pull/775), [#860](https://github.com/aws/glide-for-redis/pull/860)) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 1fd6eb939c..034fa9e518 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -109,6 +109,7 @@ enum RequestType { ZPopMin = 71; Strlen = 72; Lindex = 73; + ZPopMax = 74; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index d541234f9e..338e689307 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -352,6 +352,7 @@ fn get_command(request: &Command) -> Option { RequestType::ZPopMin => Some(cmd("ZPOPMIN")), RequestType::Strlen => Some(cmd("STRLEN")), RequestType::Lindex => Some(cmd("LINDEX")), + RequestType::ZPopMax => Some(cmd("ZPOPMAX")), } } diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 868c2afac7..a0d4c71e25 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -1342,6 +1342,38 @@ async def zcount( ), ) + async def zpopmax( + self, key: str, count: Optional[int] = None + ) -> Mapping[str, float]: + """ + Removes and returns the members with the highest scores from the sorted set stored at `key`. + If `count` is provided, up to `count` members with the highest scores are removed and returned. + Otherwise, only one member with the highest score is removed and returned. + + See https://redis.io/commands/zpopmax for more details. + + Args: + key (str): The key of the sorted set. + count (Optional[int]): Specifies the quantity of members to pop. If not specified, pops one member. + If `count` is higher than the sorted set's cardinality, returns all members and their scores, ordered from highest to lowest. + + Returns: + Mapping[str, float]: A map of the removed members and their scores, ordered from the one with the highest score to the one with the lowest. + If `key` doesn't exist, it will be treated as an empy sorted set and the command returns an empty map. + + Examples: + >>> await client.zpopmax("my_sorted_set") + {'member1': 10.0} # Indicates that 'member1' with a score of 10.0 has been removed from the sorted set. + >>> await client.zpopmax("my_sorted_set", 2) + {'member2': 8.0, 'member3': 7.5} # Indicates that 'member2' with a score of 8.0 and 'member3' with a score of 7.5 have been removed from the sorted set. + """ + return cast( + Mapping[str, float], + await self._execute_command( + RequestType.ZPopMax, [key, str(count)] if count else [key] + ), + ) + async def zpopmin( self, key: str, count: Optional[int] = None ) -> Mapping[str, float]: @@ -1363,9 +1395,9 @@ async def zpopmin( Examples: >>> await client.zpopmin("my_sorted_set") - {'member1': 10.0} # Indicates that 'member1' with a score of 10.0 has been removed from the sorted set. + {'member1': 5.0} # Indicates that 'member1' with a score of 5.0 has been removed from the sorted set. >>> await client.zpopmin("my_sorted_set", 2) - {'member2': 8.0, 'member3': 7.5} # Indicates that 'member2' with a score of 8.0 and 'member3' with a score of 7.5 have been removed from the sorted set. + {'member3': 7.5 , 'member2': 8.0} # Indicates that 'member3' with a score of 7.5 and 'member2' with a score of 8.0 have been removed from the sorted set. """ return cast( Mapping[str, float], diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 110847aa5c..89550c5bee 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -1066,6 +1066,29 @@ def zcount( RequestType.Zcount, [key, min_score.value, max_score.value] ) + def zpopmax( + self: TTransaction, key: str, count: Optional[int] = None + ) -> TTransaction: + """ + Removes and returns the members with the highest scores from the sorted set stored at `key`. + If `count` is provided, up to `count` members with the highest scores are removed and returned. + Otherwise, only one member with the highest score is removed and returned. + + See https://redis.io/commands/zpopmax for more details. + + Args: + key (str): The key of the sorted set. + count (Optional[int]): Specifies the quantity of members to pop. If not specified, pops one member. + If `count` is higher than the sorted set's cardinality, returns all members and their scores, ordered from highest to lowest. + + Commands response: + Mapping[str, float]: A map of the removed members and their scores, ordered from the one with the highest score to the one with the lowest. + If `key` doesn't exist, it will be treated as an empy sorted set and the command returns an empty map. + """ + return self.append_command( + RequestType.ZPopMax, [key, str(count)] if count else [key] + ) + def zpopmin( self: TTransaction, key: str, count: Optional[int] = None ) -> TTransaction: diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index df31821545..723fe63968 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -1208,6 +1208,21 @@ async def test_zpopmin(self, redis_client: TRedisClient): assert await redis_client.zpopmin("non_exisitng_key") == {} + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_zpopmax(self, redis_client: TRedisClient): + key = get_random_string(10) + members_scores = {"a": 1.0, "b": 2.0, "c": 3.0} + assert await redis_client.zadd(key, members_scores) == 3 + assert await redis_client.zpopmax(key) == {"c": 3.0} + assert await redis_client.zpopmax(key, 3) == {"b": 2.0, "a": 1.0} + assert await redis_client.zpopmax(key) == {} + assert await redis_client.set(key, "value") == OK + with pytest.raises(RequestError): + await redis_client.zpopmax(key) + + assert await redis_client.zpopmax("non_exisitng_key") == {} + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_type(self, redis_client: TRedisClient): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 9fcf259eea..2e0bef1f74 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -149,6 +149,8 @@ def transaction_test( args.append(2.0) transaction.zpopmin(key8) args.append({"two": 2.0}) + transaction.zpopmax(key8) + args.append({"three": 3}) return args