diff --git a/CHANGELOG.md b/CHANGELOG.md index f57ef4668d..444de3d6a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Python: Added GEOADD command ([#1259](https://github.com/aws/glide-for-redis/pull/1259)) * Python: Added GEOHASH command ([#1281](https://github.com/aws/glide-for-redis/pull/1281)) * Python: Added ZLEXCOUNT command ([#1305](https://github.com/aws/glide-for-redis/pull/1305)) +* Python: Added ZREMRANGEBYLEX command ([#1306](https://github.com/aws/glide-for-redis/pull/1306)) #### Fixes * Python: Fix typing error "‘type’ object is not subscriptable" ([#1203](https://github.com/aws/glide-for-redis/pull/1203)) diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 5697bef008..7faa39ddeb 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -2080,6 +2080,52 @@ async def zremrangebyscore( ), ) + async def zremrangebylex( + self, + key: str, + min_lex: Union[InfBound, LexBoundary], + max_lex: Union[InfBound, LexBoundary], + ) -> int: + """ + Removes all elements in the sorted set stored at `key` with a lexicographical order between `min_lex` and + `max_lex`. + + See https://redis.io/commands/zremrangebylex/ for more details. + + Args: + key (str): The key of the sorted set. + min_lex (Union[InfBound, LexBoundary]): The minimum bound of the lexicographical range. + Can be an instance of `InfBound` representing positive/negative infinity, or `LexBoundary` + representing a specific lex and inclusivity. + max_lex (Union[InfBound, LexBoundary]): The maximum bound of the lexicographical range. + Can be an instance of `InfBound` representing positive/negative infinity, or `LexBoundary` + representing a specific lex and inclusivity. + + Returns: + int: The number of members that were removed from the sorted set. + If `key` does not exist, it is treated as an empty sorted set, and the command returns `0`. + If `min_lex` is greater than `max_lex`, `0` is returned. + + Examples: + >>> await client.zremrangebylex("my_sorted_set", LexBoundary("a", is_inclusive=False), LexBoundary("e")) + 4 # Indicates that 4 members, with lexicographical values ranging from "a" (exclusive) to "e" (inclusive), have been removed from "my_sorted_set". + >>> await client.zremrangebylex("non_existing_sorted_set", InfBound.NEG_INF, LexBoundary("e")) + 0 # Indicates that no members were removed as the sorted set "non_existing_sorted_set" does not exist. + """ + min_lex_arg = ( + min_lex.value["lex_arg"] if type(min_lex) == InfBound else min_lex.value + ) + max_lex_arg = ( + max_lex.value["lex_arg"] if type(max_lex) == InfBound else max_lex.value + ) + + return cast( + int, + await self._execute_command( + RequestType.ZRemRangeByLex, [key, min_lex_arg, max_lex_arg] + ), + ) + async def zlexcount( self, key: str, @@ -2111,17 +2157,17 @@ async def zlexcount( >>> await client.zlexcount("my_sorted_set", LexBoundary("c" , is_inclusive=True), LexBoundary("k" , is_inclusive=False)) 1 # Indicates that there is one member with LexBoundary "c" <= lexicographical value < "k" in the sorted set "my_sorted_set". """ - min_lex_str = ( + min_lex_arg = ( min_lex.value["lex_arg"] if type(min_lex) == InfBound else min_lex.value ) - max_lex_str = ( + max_lex_arg = ( max_lex.value["lex_arg"] if type(max_lex) == InfBound else max_lex.value ) return cast( int, await self._execute_command( - RequestType.ZLexCount, [key, min_lex_str, max_lex_str] + RequestType.ZLexCount, [key, min_lex_arg, max_lex_arg] ), ) diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 76b13b0455..609108852f 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -1585,6 +1585,43 @@ def zremrangebyscore( RequestType.ZRemRangeByScore, [key, score_min, score_max] ) + def zremrangebylex( + self: TTransaction, + key: str, + min_lex: Union[InfBound, LexBoundary], + max_lex: Union[InfBound, LexBoundary], + ) -> TTransaction: + """ + Removes all elements in the sorted set stored at `key` with a lexicographical order between `min_lex` and + `max_lex`. + + See https://redis.io/commands/zremrangebylex/ for more details. + + Args: + key (str): The key of the sorted set. + min_lex (Union[InfBound, LexBoundary]): The minimum bound of the lexicographical range. + Can be an instance of `InfBound` representing positive/negative infinity, or `LexBoundary` + representing a specific lex and inclusivity. + max_lex (Union[InfBound, LexBoundary]): The maximum bound of the lexicographical range. + Can be an instance of `InfBound` representing positive/negative infinity, or `LexBoundary` + representing a specific lex and inclusivity. + + Command response: + int: The number of members that were removed from the sorted set. + If `key` does not exist, it is treated as an empty sorted set, and the command returns `0`. + If `min_lex` is greater than `max_lex`, `0` is returned. + """ + min_lex_arg = ( + min_lex.value["lex_arg"] if type(min_lex) == InfBound else min_lex.value + ) + max_lex_arg = ( + max_lex.value["lex_arg"] if type(max_lex) == InfBound else max_lex.value + ) + + return self.append_command( + RequestType.ZRemRangeByLex, [key, min_lex_arg, max_lex_arg] + ) + def zlexcount( self: TTransaction, key: str, @@ -1610,15 +1647,15 @@ def zlexcount( If `key` does not exist, it is treated as an empty sorted set, and the command returns `0`. If `max_lex < min_lex`, `0` is returned. """ - min_lex_str = ( + min_lex_arg = ( min_lex.value["lex_arg"] if type(min_lex) == InfBound else min_lex.value ) - max_lex_str = ( + max_lex_arg = ( max_lex.value["lex_arg"] if type(max_lex) == InfBound else max_lex.value ) return self.append_command( - RequestType.ZLexCount, [key, min_lex_str, max_lex_str] + RequestType.ZLexCount, [key, min_lex_arg, max_lex_arg] ) def zscore(self: TTransaction, key: str, member: str) -> TTransaction: diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index e98962ad0f..00c13ebd30 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -1443,6 +1443,50 @@ async def test_zremrangebyscore(self, redis_client: TRedisClient): with pytest.raises(RequestError): await redis_client.zremrangebyscore(key, InfBound.NEG_INF, InfBound.POS_INF) + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_zremrangebylex(self, redis_client: TRedisClient): + key1 = get_random_string(10) + key2 = get_random_string(10) + range = RangeByIndex(0, -1) + members_scores = {"a": 1, "b": 2, "c": 3, "d": 4} + assert await redis_client.zadd(key1, members_scores) == 4 + + assert ( + await redis_client.zremrangebylex( + key1, LexBoundary("a", False), LexBoundary("c") + ) + == 2 + ) + assert await redis_client.zrange_withscores(key1, range) == {"a": 1.0, "d": 4.0} + + assert ( + await redis_client.zremrangebylex(key1, LexBoundary("d"), InfBound.POS_INF) + == 1 + ) + assert await redis_client.zrange_withscores(key1, range) == {"a": 1.0} + + # min_lex > max_lex + assert ( + await redis_client.zremrangebylex(key1, LexBoundary("a"), InfBound.NEG_INF) + == 0 + ) + assert await redis_client.zrange_withscores(key1, range) == {"a": 1.0} + + assert ( + await redis_client.zremrangebylex( + "non_existing_key", InfBound.NEG_INF, InfBound.POS_INF + ) + == 0 + ) + + # key exists, but it is not a sorted set + assert await redis_client.set(key2, "value") == OK + with pytest.raises(RequestError): + await redis_client.zremrangebylex( + key2, LexBoundary("a", False), LexBoundary("c") + ) + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_zlexcount(self, redis_client: TRedisClient): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 8903b33fc8..9d19ecf272 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -206,6 +206,8 @@ async def transaction_test( args.append({"four": 4}) transaction.zremrangebyscore(key8, InfBound.NEG_INF, InfBound.POS_INF) args.append(1) + transaction.zremrangebylex(key8, InfBound.NEG_INF, InfBound.POS_INF) + args.append(0) transaction.geoadd( key9,