Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python: add ZLEXCOUNT command (#230) #1305

Merged
merged 3 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Expand Up @@ -10,6 +10,7 @@
* Python: Added APPEND command ([#1152](https:/aws/glide-for-redis/pull/1152))
* Python: Added GEOADD command ([#1259](https:/aws/glide-for-redis/pull/1259))
* Python: Added GEOHASH command ([#1281](https:/aws/glide-for-redis/pull/1281))
* Python: Added ZLEXCOUNT command ([#1305](https:/aws/glide-for-redis/pull/1305))

#### Fixes
* Python: Fix typing error "‘type’ object is not subscriptable" ([#1203](https:/aws/glide-for-redis/pull/1203))
Expand Down
44 changes: 44 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,49 @@ 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".
"""
lex_min = (
aaron-congo marked this conversation as resolved.
Show resolved Hide resolved
min_lex.value["lex_arg"] if type(min_lex) == InfBound else min_lex.value
)
lex_max = (
max_lex.value["lex_arg"] if type(max_lex) == InfBound else max_lex.value
)

return cast(
int,
await self._execute_command(RequestType.ZLexCount, [key, lex_min, lex_max]),
)

async def zscore(self, key: str, member: str) -> Optional[float]:
"""
Returns the score of `member` in the sorted set stored at `key`.
Expand Down
35 changes: 35 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,40 @@ 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.
"""
lex_min = (
min_lex.value["lex_arg"] if type(min_lex) == InfBound else min_lex.value
)
lex_max = (
max_lex.value["lex_arg"] if type(max_lex) == InfBound else max_lex.value
)

return self.append_command(RequestType.ZLexCount, [key, lex_min, lex_max])

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
Loading