Skip to content

Commit

Permalink
Python: adds ZRANGE command (valkey-io#906)
Browse files Browse the repository at this point in the history
  • Loading branch information
shohamazon authored and cyip10 committed Jun 24, 2024
1 parent efb1482 commit a8e51b2
Show file tree
Hide file tree
Showing 7 changed files with 583 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- Node: Allow routing Cluster requests by address. ([#1021](https://github.com/aws/glide-for-redis/pull/1021))
* Python: Added HSETNX command. ([#954](https://github.com/aws/glide-for-redis/pull/954))
* Python: Added SISMEMBER command ([#971](https://github.com/aws/glide-for-redis/pull/971))
* Python: Added ZRANGE command ([#906](https://github.com/aws/glide-for-redis/pull/906))

## 0.2.0 (2024-02-11)

Expand Down
18 changes: 15 additions & 3 deletions python/python/glide/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@
ExpireOptions,
ExpirySet,
ExpiryType,
InfBound,
InfoSection,
ScoreLimit,
UpdateOptions,
)
from glide.async_commands.sorted_set import (
InfBound,
LexBoundary,
Limit,
RangeByIndex,
RangeByLex,
RangeByScore,
ScoreBoundary,
)
from glide.async_commands.transaction import ClusterTransaction, Transaction
from glide.config import (
BaseClientConfiguration,
Expand Down Expand Up @@ -45,13 +52,18 @@
"BaseClientConfiguration",
"ClusterClientConfiguration",
"RedisClientConfiguration",
"ScoreLimit",
"ScoreBoundary",
"ConditionalChange",
"ExpireOptions",
"ExpirySet",
"ExpiryType",
"InfBound",
"InfoSection",
"LexBoundary",
"Limit",
"RangeByIndex",
"RangeByLex",
"RangeByScore",
"UpdateOptions",
"Logger",
"LogLevel",
Expand Down
145 changes: 106 additions & 39 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
get_args,
)

from glide.async_commands.sorted_set import (
InfBound,
RangeByIndex,
RangeByLex,
RangeByScore,
ScoreBoundary,
_create_zrange_args,
)
from glide.constants import TOK, TResult
from glide.protobuf.redis_request_pb2 import RequestType
from glide.routes import Route
Expand Down Expand Up @@ -123,29 +131,6 @@ 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 @@ -1253,9 +1238,9 @@ async def zadd(
If `changed` is set, returns the number of elements updated in the sorted set.
Examples:
>>> await zadd("my_sorted_set", {"member1": 10.5, "member2": 8.2})
>>> await client.zadd("my_sorted_set", {"member1": 10.5, "member2": 8.2})
2 # Indicates that two elements have been added or updated in the sorted set "my_sorted_set."
>>> await zadd("existing_sorted_set", {"member1": 15.0, "member2": 5.5}, existing_options=ConditionalChange.XX)
>>> await client.zadd("existing_sorted_set", {"member1": 15.0, "member2": 5.5}, existing_options=ConditionalChange.XX)
2 # Updates the scores of two existing members in the sorted set "existing_sorted_set."
"""
args = [key]
Expand Down Expand Up @@ -1316,9 +1301,9 @@ async def zadd_incr(
If there was a conflict with choosing the XX/NX/LT/GT options, the operation aborts and None is returned.
Examples:
>>> await zaddIncr("my_sorted_set", member , 5.0)
>>> await client.zaddIncr("my_sorted_set", member , 5.0)
5.0
>>> await zaddIncr("existing_sorted_set", member , "3.0" , UpdateOptions.LESS_THAN)
>>> await client.zaddIncr("existing_sorted_set", member , "3.0" , UpdateOptions.LESS_THAN)
None
"""
args = [key]
Expand Down Expand Up @@ -1357,18 +1342,18 @@ async def zcard(self, key: str) -> int:
If `key` does not exist, it is treated as an empty sorted set, and the command returns 0.
Examples:
>>> await zcard("my_sorted_set")
>>> await client.zcard("my_sorted_set")
3 # Indicates that there are 3 elements in the sorted set "my_sorted_set".
>>> await zcard("non_existing_key")
>>> await client.zcard("non_existing_key")
0
"""
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],
min_score: Union[InfBound, ScoreBoundary],
max_score: Union[InfBound, ScoreBoundary],
) -> int:
"""
Returns the number of members in the sorted set stored at `key` with scores between `min_score` and `max_score`.
Expand All @@ -1377,28 +1362,38 @@ async def zcount(
Args:
key (str): The key of the sorted set.
min_score (Union[InfBound, ScoreLimit]): The minimum score to count from.
min_score (Union[InfBound, ScoreBoundary]): 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.
or ScoreBoundary representing a specific score and inclusivity.
max_score (Union[InfBound, ScoreBoundary]): 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.
or ScoreBoundary 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.
Examples:
>>> await client.zcount("my_sorted_set", ScoreLimit(5.0 , is_inclusive=true) , InfBound.POS_INF)
>>> await client.zcount("my_sorted_set", ScoreBoundary(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 client.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".
>>> await client.zcount("my_sorted_set", ScoreBoundary(5.0 , is_inclusive=true) , ScoreBoundary(10.0 , is_inclusive=false))
1 # Indicates that there is one ScoreBoundary with 5.0 < score <= 10.0 in the sorted set "my_sorted_set".
"""
score_min = (
min_score.value["score_arg"]
if type(min_score) == InfBound
else min_score.value
)
score_max = (
max_score.value["score_arg"]
if type(max_score) == InfBound
else max_score.value
)
return cast(
int,
await self._execute_command(
RequestType.Zcount, [key, min_score.value, max_score.value]
RequestType.Zcount, [key, score_min, score_max]
),
)

Expand Down Expand Up @@ -1466,6 +1461,78 @@ async def zpopmin(
),
)

async def zrange(
self,
key: str,
range_query: Union[RangeByIndex, RangeByLex, RangeByScore],
reverse: bool = False,
) -> List[str]:
"""
Returns the specified range of elements in the sorted set stored at `key`.
ZRANGE can perform different types of range queries: by index (rank), by the score, or by lexicographical order.
See https://redis.io/commands/zrange/ for more details.
To get the elements with their scores, see zrange_withscores.
Args:
key (str): The key of the sorted set.
range_query (Union[RangeByIndex, RangeByLex, RangeByScore]): The range query object representing the type of range query to perform.
- For range queries by index (rank), use RangeByIndex.
- For range queries by lexicographical order, use RangeByLex.
- For range queries by score, use RangeByScore.
reverse (bool): If True, reverses the sorted set, with index 0 as the element with the highest score.
Returns:
List[str]: A list of elements within the specified range.
If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty array.
Examples:
>>> await client.zrange("my_sorted_set", RangeByIndex(0, -1))
['member1', 'member2', 'member3'] # Returns all members in ascending order.
>>> await client.zrange("my_sorted_set", RangeByScore(start=InfBound.NEG_INF, stop=ScoreBoundary(3)))
['member2', 'member3'] # Returns members with scores within the range of negative infinity to 3, in ascending order.
"""
args = _create_zrange_args(key, range_query, reverse, with_scores=False)

return cast(List[str], await self._execute_command(RequestType.Zrange, args))

async def zrange_withscores(
self,
key: str,
range_query: Union[RangeByIndex, RangeByScore],
reverse: bool = False,
) -> Mapping[str, float]:
"""
Returns the specified range of elements with their scores in the sorted set stored at `key`.
Similar to ZRANGE but with a WITHSCORE flag.
See https://redis.io/commands/zrange/ for more details.
Args:
key (str): The key of the sorted set.
range_query (Union[RangeByIndex, RangeByScore]): The range query object representing the type of range query to perform.
- For range queries by index (rank), use RangeByIndex.
- For range queries by score, use RangeByScore.
reverse (bool): If True, reverses the sorted set, with index 0 as the element with the highest score.
Returns:
Mapping[str , float]: A map of elements and their scores within the specified range.
If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty map.
Examples:
>>> await client.zrange_withscores("my_sorted_set", RangeByScore(ScoreBoundary(10), ScoreBoundary(20)))
{'member1': 10.5, 'member2': 15.2} # Returns members with scores between 10 and 20 with their scores.
>>> await client.zrange("my_sorted_set", RangeByScore(start=InfBound.NEG_INF, stop=ScoreBoundary(3)))
{'member4': -2.0, 'member7': 1.5} # Returns members with with scores within the range of negative infinity to 3, with their scores.
"""
args = _create_zrange_args(key, range_query, reverse, with_scores=True)

return cast(
Mapping[str, float], await self._execute_command(RequestType.Zrange, args)
)

async def zrem(
self,
key: str,
Expand Down
Loading

0 comments on commit a8e51b2

Please sign in to comment.