Skip to content

Commit

Permalink
Python: adds ZCOUNT command
Browse files Browse the repository at this point in the history
  • Loading branch information
shohamazon committed Feb 4, 2024
1 parent 51b2de3 commit c54a120
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 0 deletions.
4 changes: 4 additions & 0 deletions python/python/glide/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
ExpireOptions,
ExpirySet,
ExpiryType,
InfBound,
InfoSection,
ScoreLimit,
UpdateOptions,
)
from glide.async_commands.transaction import ClusterTransaction, Transaction
Expand Down Expand Up @@ -43,10 +45,12 @@
"BaseClientConfiguration",
"ClusterClientConfiguration",
"RedisClientConfiguration",
"ScoreLimit",
"ConditionalChange",
"ExpireOptions",
"ExpirySet",
"ExpiryType",
"InfBound",
"InfoSection",
"UpdateOptions",
"Logger",
Expand Down
62 changes: 62 additions & 0 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,29 @@ class UpdateOptions(Enum):
GREATER_THAN = "GT"


class InfBound(Enum):
"""
Enumeration representing positive and negative infinity bounds for sorted set scores.
"""

POS_INF = "+inf"
NEG_INF = "-inf"


class ScoreLimit:
"""
Represents a score limit in a sorted set.
Args:
value (float): The score value.
is_inclusive (bool): Whether the score value is inclusive. Defaults to False.
"""

def __init__(self, value: float, is_inclusive: bool = True):
"""Convert the score limit to the Redis protocol format."""
self.value = str(value) if is_inclusive else f"({value}"


class ExpirySet:
"""SET option: Represents the expiry type and value to be executed with "SET" command."""

Expand Down Expand Up @@ -1214,6 +1237,45 @@ async def zcard(self, key: str) -> int:
"""
return cast(int, await self._execute_command(RequestType.Zcard, [key]))

async def zcount(
self,
key: str,
min_score: Union[InfBound, ScoreLimit],
max_score: Union[InfBound, ScoreLimit],
) -> int:
"""
Returns the number of members in the sorted set stored at `key` with scores between `min_score` and `max_score`.
See https://redis.io/commands/zcount/ for more details.
Args:
key (str): The key of the sorted set.
min_score (Union[InfBound, ScoreLimit]): The minimum score to count from.
Can be an instance of InfBound representing positive/negative infinity,
or ScoreLimit representing a specific score and inclusivity.
max_score (Union[InfBound, ScoreLimit]): The maximum score to count up to.
Can be an instance of InfBound representing positive/negative infinity,
or ScoreLimit representing a specific score and inclusivity.
Returns:
int: The number of members in the specified score range.
If `key` does not exist, it is treated as an empty sorted set, and the command returns 0.
If `max_score` < `min_score`, 0 is returned.
If `key` holds a value that is not a sorted set, an error is returned.
Examples:
>>> await zcount("my_sorted_set", ScoreLimit(5.0 , is_inclusive=true) , InfBound.POS_INF)
2 # Indicates that there are 2 members with scores between 5.0 (not exclusive) and +inf in the sorted set "my_sorted_set".
>>> await zcount("my_sorted_set", ScoreLimit(5.0 , is_inclusive=true) , ScoreLimit(10.0 , is_inclusive=false))
1 # Indicates that there is one ScoreLimit with 5.0 < score <= 10.0 in the sorted set "my_sorted_set".
"""
return cast(
int,
await self._execute_command(
RequestType.Zcount, [key, min_score.value, max_score.value]
),
)

async def zrem(
self,
key: str,
Expand Down
30 changes: 30 additions & 0 deletions python/python/glide/async_commands/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
ConditionalChange,
ExpireOptions,
ExpirySet,
InfBound,
InfoSection,
ScoreLimit,
UpdateOptions,
)
from glide.protobuf.redis_request_pb2 import RequestType
Expand Down Expand Up @@ -893,6 +895,34 @@ def zcard(self, key: str):
"""
self.append_command(RequestType.Zcard, [key])

def zcount(
self,
key: str,
min_score: Union[InfBound, ScoreLimit],
max_score: Union[InfBound, ScoreLimit],
):
"""
Returns the number of members in the sorted set stored at `key` with scores between `min_score` and `max_score`.
See https://redis.io/commands/zcount/ for more details.
Args:
key (str): The key of the sorted set.
min_score (Union[InfBound, ScoreLimit]): The minimum score to count from.
Can be an instance of InfBound representing positive/negative infinity,
or ScoreLimit representing a specific score and inclusivity.
max_score (Union[InfBound, ScoreLimit]): The maximum score to count up to.
Can be an instance of InfBound representing positive/negative infinity,
or ScoreLimit representing a specific score and inclusivity.
Commands response:
int: The number of members in the specified score range.
If key does not exist, 0 is returned.
If `max_score` < `min_score`, 0 is returned.
If `key` holds a value that is not a sorted set, an error is returned.
"""
self.append_command(RequestType.Zcount, [key, min_score.value, max_score.value])

def zrem(
self,
key: str,
Expand Down
30 changes: 30 additions & 0 deletions python/python/tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
ExpireOptions,
ExpirySet,
ExpiryType,
InfBound,
InfoSection,
ScoreLimit,
UpdateOptions,
)
from glide.config import ProtocolVersion, RedisCredentials
Expand Down Expand Up @@ -1069,6 +1071,34 @@ async def test_zcard(self, redis_client: TRedisClient):
assert await redis_client.zrem(key, ["one"]) == 1
assert await redis_client.zcard(key) == 2

@pytest.mark.parametrize("cluster_mode", [True, False])
async def test_zcount(self, redis_client: TRedisClient):
key = get_random_string(10)
members_scores = {"one": 1, "two": 2, "three": 3}
assert await redis_client.zadd(key, members_scores=members_scores) == 3

assert await redis_client.zcount(key, InfBound.NEG_INF, InfBound.POS_INF) == 3
assert (
await redis_client.zcount(key, ScoreLimit(1, False), ScoreLimit(3, False))
== 1
)
assert (
await redis_client.zcount(key, ScoreLimit(1, False), ScoreLimit(3, True))
== 2
)
assert (
await redis_client.zcount(key, InfBound.NEG_INF, ScoreLimit(3, True)) == 3
)
assert (
await redis_client.zcount(key, InfBound.POS_INF, ScoreLimit(3, True)) == 0
)
assert (
await redis_client.zcount(
"non_existing_key", InfBound.NEG_INF, InfBound.POS_INF
)
== 0
)

@pytest.mark.parametrize("cluster_mode", [True, False])
async def test_zscore(self, redis_client: TRedisClient):
key = get_random_string(10)
Expand Down
3 changes: 3 additions & 0 deletions python/python/tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pytest
from glide import RequestError
from glide.async_commands.core import InfBound, ScoreLimit
from glide.async_commands.transaction import (
BaseTransaction,
ClusterTransaction,
Expand Down Expand Up @@ -90,6 +91,7 @@ def transaction_test(
transaction.zadd_incr(key8, "one", 3)
transaction.zrem(key8, ["one"])
transaction.zcard(key8)
transaction.zcount(key8, ScoreLimit(2, True), InfBound.POS_INF)
transaction.zscore(key8, "two")
return [
OK,
Expand Down Expand Up @@ -133,6 +135,7 @@ def transaction_test(
4,
1,
2,
2,
2.0,
]

Expand Down

0 comments on commit c54a120

Please sign in to comment.