Skip to content

Commit

Permalink
Python: add ZLEXCOUNT command (#1305)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaron-congo authored Apr 19, 2024
1 parent 4480ece commit 1c50792
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* Python: Added APPEND command ([#1152](https://github.com/aws/glide-for-redis/pull/1152))
* 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))

#### Fixes
* Python: Fix typing error "‘type’ object is not subscriptable" ([#1203](https://github.com/aws/glide-for-redis/pull/1203))
Expand Down
46 changes: 46 additions & 0 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from glide.async_commands.sorted_set import (
InfBound,
LexBoundary,
RangeByIndex,
RangeByLex,
RangeByScore,
Expand Down Expand Up @@ -2079,6 +2080,51 @@ async def zremrangebyscore(
),
)

async def zlexcount(
self,
key: str,
min_lex: Union[InfBound, LexBoundary],
max_lex: Union[InfBound, LexBoundary],
) -> int:
"""
Returns the number of members in the sorted set stored at `key` with lexicographical values between `min_lex` and `max_lex`.
See https://redis.io/commands/zlexcount/ for more details.
Args:
key (str): The key of the sorted set.
min_lex (Union[InfBound, LexBoundary]): The minimum lexicographical value to count from.
Can be an instance of InfBound representing positive/negative infinity,
or LexBoundary representing a specific lexicographical value and inclusivity.
max_lex (Union[InfBound, LexBoundary]): The maximum lexicographical to count up to.
Can be an instance of InfBound representing positive/negative infinity,
or LexBoundary representing a specific lexicographical value and inclusivity.
Returns:
int: The number of members in the specified lexicographical range.
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.
Examples:
>>> await client.zlexcount("my_sorted_set", LexBoundary("c" , is_inclusive=True), InfBound.POS_INF)
2 # Indicates that there are 2 members with lexicographical values between "c" (inclusive) and positive infinity in the sorted set "my_sorted_set".
>>> 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.value["lex_arg"] if type(min_lex) == InfBound else min_lex.value
)
max_lex_str = (
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]
),
)

async def zscore(self, key: str, member: str) -> Optional[float]:
"""
Returns the score of `member` in the sorted set stored at `key`.
Expand Down
37 changes: 37 additions & 0 deletions python/python/glide/async_commands/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)
from glide.async_commands.sorted_set import (
InfBound,
LexBoundary,
RangeByIndex,
RangeByLex,
RangeByScore,
Expand Down Expand Up @@ -1584,6 +1585,42 @@ def zremrangebyscore(
RequestType.ZRemRangeByScore, [key, score_min, score_max]
)

def zlexcount(
self: TTransaction,
key: str,
min_lex: Union[InfBound, LexBoundary],
max_lex: Union[InfBound, LexBoundary],
) -> TTransaction:
"""
Returns the number of members in the sorted set stored at `key` with lexographical values between `min_lex` and `max_lex`.
See https://redis.io/commands/zlexcount/ for more details.
Args:
key (str): The key of the sorted set.
min_lex (Union[InfBound, LexBoundary]): The minimum lexicographical value to count from.
Can be an instance of InfBound representing positive/negative infinity,
or LexBoundary representing a specific lexicographical value and inclusivity.
max_lex (Union[InfBound, LexBoundary]): The maximum lexicographical value to count up to.
Can be an instance of InfBound representing positive/negative infinity,
or LexBoundary representing a specific lexicographical value and inclusivity.
Command response:
int: The number of members in the specified lexicographical range.
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.value["lex_arg"] if type(min_lex) == InfBound else min_lex.value
)
max_lex_str = (
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]
)

def zscore(self: TTransaction, key: str, member: str) -> TTransaction:
"""
Returns the score of `member` in the sorted set stored at `key`.
Expand Down
44 changes: 44 additions & 0 deletions python/python/tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_zlexcount(self, redis_client: TRedisClient):
key1 = get_random_string(10)
key2 = get_random_string(10)
members_scores = {"a": 1.0, "b": 2.0, "c": 3.0}

assert await redis_client.zadd(key1, members_scores) == 3
assert (
await redis_client.zlexcount(key1, InfBound.NEG_INF, InfBound.POS_INF) == 3
)
assert (
await redis_client.zlexcount(
key1,
LexBoundary("a", is_inclusive=False),
LexBoundary("c", is_inclusive=True),
)
== 2
)
assert (
await redis_client.zlexcount(
key1, InfBound.NEG_INF, LexBoundary("c", is_inclusive=True)
)
== 3
)
# Incorrect range; start > end
assert (
await redis_client.zlexcount(
key1, InfBound.POS_INF, LexBoundary("c", is_inclusive=True)
)
== 0
)
assert (
await redis_client.zlexcount(
"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.zlexcount(key2, InfBound.NEG_INF, InfBound.POS_INF)

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_zcard(self, redis_client: TRedisClient):
Expand Down
9 changes: 8 additions & 1 deletion python/python/tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
import pytest
from glide import RequestError
from glide.async_commands.core import GeospatialData
from glide.async_commands.sorted_set import InfBound, RangeByIndex, ScoreBoundary
from glide.async_commands.sorted_set import (
InfBound,
LexBoundary,
RangeByIndex,
ScoreBoundary,
)
from glide.async_commands.transaction import (
BaseTransaction,
ClusterTransaction,
Expand Down Expand Up @@ -187,6 +192,8 @@ async def transaction_test(
args.append(3)
transaction.zcount(key8, ScoreBoundary(2, is_inclusive=True), InfBound.POS_INF)
args.append(3)
transaction.zlexcount(key8, LexBoundary("a", is_inclusive=True), InfBound.POS_INF)
args.append(3)
transaction.zscore(key8, "two")
args.append(2.0)
transaction.zrange(key8, RangeByIndex(start=0, stop=-1))
Expand Down

0 comments on commit 1c50792

Please sign in to comment.