Skip to content

Commit

Permalink
Python: Adds Sort command (valkey-io#1439)
Browse files Browse the repository at this point in the history
* Adds sort command to python

Co-authored-by: Yury-Fridlyand
Co-authored-by: Shoham Elias
Co-authored-by: Aaron 
Co-authored-by: ikolomi
  • Loading branch information
GilboaAWS authored and yipin-chen committed Jun 7, 2024
1 parent f775561 commit d1fcc94
Show file tree
Hide file tree
Showing 12 changed files with 640 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Python: Added GETDEL command ([#1514](https://github.com/aws/glide-for-redis/pull/1514))
* Python: Added ZINTER, ZUNION commands ([#1478](https://github.com/aws/glide-for-redis/pull/1478))
* Python: Added SINTERCARD command ([#1511](https://github.com/aws/glide-for-redis/pull/1511))
* Python: Added SORT command ([#1439](https://github.com/aws/glide-for-redis/pull/1439))

### Breaking Changes
* Node: Update XREAD to return a Map of Map ([#1494](https://github.com/aws/glide-for-redis/pull/1494))
Expand Down
1 change: 1 addition & 0 deletions glide-core/src/protobuf/redis_request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ enum RequestType {
PExpireTime = 157;
BLMPop = 158;
XLen = 159;
Sort = 160;
LSet = 165;
XDel = 166;
XRange = 167;
Expand Down
3 changes: 3 additions & 0 deletions glide-core/src/request_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ pub enum RequestType {
PExpireTime = 157,
BLMPop = 158,
XLen = 159,
Sort = 160,
LSet = 165,
XDel = 166,
XRange = 167,
Expand Down Expand Up @@ -357,6 +358,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::BitFieldReadOnly => RequestType::BitFieldReadOnly,
ProtobufRequestType::Move => RequestType::Move,
ProtobufRequestType::SInterCard => RequestType::SInterCard,
ProtobufRequestType::Sort => RequestType::Sort,
}
}
}
Expand Down Expand Up @@ -533,6 +535,7 @@ impl RequestType {
RequestType::BitFieldReadOnly => Some(cmd("BITFIELD_RO")),
RequestType::Move => Some(cmd("MOVE")),
RequestType::SInterCard => Some(cmd("SINTERCARD")),
RequestType::Sort => Some(cmd("SORT")),
}
}
}
3 changes: 2 additions & 1 deletion python/python/glide/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0

from glide.async_commands.command_args import Limit, OrderBy
from glide.async_commands.core import (
ConditionalChange,
ExpireOptions,
Expand All @@ -20,7 +21,6 @@
AggregationType,
InfBound,
LexBoundary,
Limit,
RangeByIndex,
RangeByLex,
RangeByScore,
Expand Down Expand Up @@ -102,6 +102,7 @@
"RangeByLex",
"RangeByScore",
"ScoreFilter",
"OrderBy",
"StreamAddOptions",
"StreamTrimOptions",
"TrimByMaxLen",
Expand Down
87 changes: 86 additions & 1 deletion python/python/glide/async_commands/cluster_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from typing import Dict, List, Mapping, Optional, cast

from glide.async_commands.core import CoreCommands, InfoSection
from glide.async_commands.command_args import Limit, OrderBy
from glide.async_commands.core import CoreCommands, InfoSection, _build_sort_args
from glide.async_commands.transaction import BaseTransaction, ClusterTransaction
from glide.constants import TOK, TClusterResponse, TResult, TSingleNodeRoute
from glide.protobuf.redis_request_pb2 import RequestType
Expand Down Expand Up @@ -367,3 +368,87 @@ async def lastsave(self, route: Optional[Route] = None) -> TClusterResponse[int]
TClusterResponse[int],
await self._execute_command(RequestType.LastSave, [], route),
)

async def sort(
self,
key: str,
limit: Optional[Limit] = None,
order: Optional[OrderBy] = None,
alpha: Optional[bool] = None,
) -> List[str]:
"""
Sorts the elements in the list, set, or sorted set at `key` and returns the result.
To store the result into a new key, see `sort_store`.
By default, sorting is numeric, and elements are compared by their value interpreted as double precision floating point numbers.
See https://valkey.io/commands/sort for more details.
Args:
key (str): The key of the list, set, or sorted set to be sorted.
limit (Optional[Limit]): Limiting the range of the query by setting offset and result count. See `Limit` class for more information.
order (Optional[OrderBy]): Specifies the order to sort the elements.
Can be `OrderBy.ASC` (ascending) or `OrderBy.DESC` (descending).
alpha (Optional[bool]): When `True`, sorts elements lexicographically. When `False` (default), sorts elements numerically.
Use this when the list, set, or sorted set contains string values that cannot be converted into double precision floating point numbers.
Returns:
List[str]: A list of sorted elements.
Examples:
>>> await client.lpush("mylist", '3', '1', '2')
>>> await client.sort("mylist")
['1', '2', '3']
>>> await client.sort("mylist", order=OrderBy.DESC)
['3', '2', '1']
>>> await client.lpush("mylist", '2', '1', '2', '3', '3', '1')
>>> await client.sort("mylist", limit=Limit(2, 3))
['1', '2', '2']
>>> await client.lpush("mylist", "a", "b", "c", "d")
>>> await client.sort("mylist", limit=Limit(2, 2), order=OrderBy.DESC, alpha=True)
['b', 'a']
"""
args = _build_sort_args(key, None, limit, None, order, alpha)
result = await self._execute_command(RequestType.Sort, args)
return cast(List[str], result)

async def sort_store(
self,
key: str,
destination: str,
limit: Optional[Limit] = None,
order: Optional[OrderBy] = None,
alpha: Optional[bool] = None,
) -> int:
"""
Sorts the elements in the list, set, or sorted set at `key` and stores the result in `store`.
When in cluster mode, `key` and `store` must map to the same hash slot.
To get the sort result without storing it into a key, see `sort`.
See https://valkey.io/commands/sort for more details.
Args:
key (str): The key of the list, set, or sorted set to be sorted.
destination (str): The key where the sorted result will be stored.
limit (Optional[Limit]): Limiting the range of the query by setting offset and result count. See `Limit` class for more information.
order (Optional[OrderBy]): Specifies the order to sort the elements.
Can be `OrderBy.ASC` (ascending) or `OrderBy.DESC` (descending).
alpha (Optional[bool]): When `True`, sorts elements lexicographically. When `False` (default), sorts elements numerically.
Use this when the list, set, or sorted set contains string values that cannot be converted into double precision floating point numbers.
Returns:
int: The number of elements in the sorted key stored at `store`.
Examples:
>>> await client.lpush("mylist", 3, 1, 2)
>>> await client.sort_store("mylist", "sorted_list")
3 # Indicates that the sorted list "sorted_list" contains three elements.
>>> await client.lrange("sorted_list", 0, -1)
['1', '2', '3']
"""
args = _build_sort_args(key, None, limit, None, order, alpha, store=destination)
result = await self._execute_command(RequestType.Sort, args)
return cast(int, result)
45 changes: 45 additions & 0 deletions python/python/glide/async_commands/command_args.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0

from enum import Enum
from typing import List, Optional, Union


class Limit:
"""
Represents a limit argument for range queries in various Redis commands.
The `LIMIT` argument is commonly used to specify a subset of results from the matching elements,
similar to the `LIMIT` clause in SQL (e.g., `SELECT LIMIT offset, count`).
This class can be utilized in multiple Redis commands that support limit options,
such as [ZRANGE](https://valkey.io/commands/zrange), [SORT](https://valkey.io/commands/sort/), and others.
Args:
offset (int): The starting position of the range, zero based.
count (int): The maximum number of elements to include in the range.
A negative count returns all elements from the offset.
Examples:
>>> limit = Limit(0, 10) # Fetch the first 10 elements
>>> limit = Limit(5, -1) # Fetch all elements starting from the 5th element
"""

def __init__(self, offset: int, count: int):
self.offset = offset
self.count = count


class OrderBy(Enum):
"""
SORT order options: options for sorting elements.
"""

ASC = "ASC"
"""
ASC: Sort in ascending order.
"""

DESC = "DESC"
"""
DESC: Sort in descending order.
"""
34 changes: 34 additions & 0 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
get_args,
)

from glide.async_commands.command_args import Limit, OrderBy
from glide.async_commands.sorted_set import (
AggregationType,
InfBound,
Expand Down Expand Up @@ -361,6 +362,39 @@ class InsertPosition(Enum):
AFTER = "AFTER"


def _build_sort_args(
key: str,
by_pattern: Optional[str] = None,
limit: Optional[Limit] = None,
get_patterns: Optional[List[str]] = None,
order: Optional[OrderBy] = None,
alpha: Optional[bool] = None,
store: Optional[str] = None,
) -> List[str]:
args = [key]

if by_pattern:
args.extend(["BY", by_pattern])

if limit:
args.extend(["LIMIT", str(limit.offset), str(limit.count)])

if get_patterns:
for pattern in get_patterns:
args.extend(["GET", pattern])

if order:
args.append(order.value)

if alpha:
args.append("ALPHA")

if store:
args.extend(["STORE", store])

return args


class CoreCommands(Protocol):
async def _execute_command(
self,
Expand Down
19 changes: 2 additions & 17 deletions python/python/glide/async_commands/sorted_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from enum import Enum
from typing import List, Optional, Tuple, Union

from glide.async_commands.command_args import Limit


class InfBound(Enum):
"""
Expand Down Expand Up @@ -88,23 +90,6 @@ def __init__(self, value: str, is_inclusive: bool = True):
self.value = f"[{value}" if is_inclusive else f"({value}"


class Limit:
"""
Represents a limit argument for a range query in a sorted set to be used in [ZRANGE](https://redis.io/commands/zrange) command.
The optional LIMIT argument can be used to obtain a sub-range from the matching elements
(similar to SELECT LIMIT offset, count in SQL).
Args:
offset (int): The offset from the start of the range.
count (int): The number of elements to include in the range.
A negative count returns all elements from the offset.
"""

def __init__(self, offset: int, count: int):
self.offset = offset
self.count = count


class RangeByIndex:
"""
Represents a range by index (rank) in a sorted set.
Expand Down
Loading

0 comments on commit d1fcc94

Please sign in to comment.