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: adds ZRANGE command #906

Merged
merged 3 commits into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -3,6 +3,7 @@
- Node: Allow routing Cluster requests by address. ([#1021](https:/aws/glide-for-redis/pull/1021))
* Python: Added HSETNX command. ([#954](https:/aws/glide-for-redis/pull/954))
* Python: Added SISMEMBER command ([#971](https:/aws/glide-for-redis/pull/971))
* Python: Added ZRANGE command ([#906](https:/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.
barshaul marked this conversation as resolved.
Show resolved Hide resolved

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(
barshaul marked this conversation as resolved.
Show resolved Hide resolved
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:
barshaul marked this conversation as resolved.
Show resolved Hide resolved
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
Loading