diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py index 98b83f21fb..45efd16d7d 100644 --- a/python/python/glide/async_commands/cluster_commands.py +++ b/python/python/glide/async_commands/cluster_commands.py @@ -691,6 +691,7 @@ async def sort( ) -> List[bytes]: """ Sorts the elements in the list, set, or sorted set at `key` and returns the result. + This command is routed to primary nodes only. 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. @@ -728,6 +729,55 @@ async def sort( result = await self._execute_command(RequestType.Sort, args) return cast(List[bytes], result) + async def sort_ro( + self, + key: TEncodable, + limit: Optional[Limit] = None, + order: Optional[OrderBy] = None, + alpha: Optional[bool] = None, + ) -> List[bytes]: + """ + Sorts the elements in the list, set, or sorted set at `key` and returns the result. + The `sort_ro` command can be used to sort elements based on different criteria and apply transformations on sorted elements. + This command is routed depending on the client's `ReadFrom` strategy. + + 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 (TEncodable): 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[bytes]: A list of sorted elements. + + Examples: + >>> await client.lpush("mylist", '3', '1', '2') + >>> await client.sort_ro("mylist") + [b'1', b'2', b'3'] + + >>> await client.sort_ro("mylist", order=OrderBy.DESC) + [b'3', b'2', b'1'] + + >>> await client.lpush("mylist", '2', '1', '2', '3', '3', '1') + >>> await client.sort_ro("mylist", limit=Limit(2, 3)) + [b'1', b'2', b'2'] + + >>> await client.lpush("mylist", "a", "b", "c", "d") + >>> await client.sort_ro("mylist", limit=Limit(2, 2), order=OrderBy.DESC, alpha=True) + [b'b', b'a'] + + Since: Redis version 7.0.0. + """ + args = _build_sort_args(key, None, limit, None, order, alpha) + result = await self._execute_command(RequestType.SortReadOnly, args) + return cast(List[bytes], result) + async def sort_store( self, key: TEncodable, diff --git a/python/python/glide/async_commands/standalone_commands.py b/python/python/glide/async_commands/standalone_commands.py index 83823ca6b9..781a3ec6c0 100644 --- a/python/python/glide/async_commands/standalone_commands.py +++ b/python/python/glide/async_commands/standalone_commands.py @@ -487,6 +487,7 @@ async def sort( """ Sorts the elements in the list, set, or sorted set at `key` and returns the result. The `sort` command can be used to sort elements based on different criteria and apply transformations on sorted elements. + This command is routed to primary nodes only. To store the result into a new key, see `sort_store`. See https://valkey.io/commands/sort for more details. @@ -538,6 +539,71 @@ async def sort( result = await self._execute_command(RequestType.Sort, args) return cast(List[Optional[bytes]], result) + async def sort_ro( + self, + key: TEncodable, + by_pattern: Optional[TEncodable] = None, + limit: Optional[Limit] = None, + get_patterns: Optional[List[TEncodable]] = None, + order: Optional[OrderBy] = None, + alpha: Optional[bool] = None, + ) -> List[Optional[bytes]]: + """ + Sorts the elements in the list, set, or sorted set at `key` and returns the result. + The `sort_ro` command can be used to sort elements based on different criteria and apply transformations on sorted elements. + This command is routed depending on the client's `ReadFrom` strategy. + + See https://valkey.io/commands/sort for more details. + + Args: + key (TEncodable): The key of the list, set, or sorted set to be sorted. + by_pattern (Optional[TEncodable]): A pattern to sort by external keys instead of by the elements stored at the key themselves. + The pattern should contain an asterisk (*) as a placeholder for the element values, where the value + from the key replaces the asterisk to create the key name. For example, if `key` contains IDs of objects, + `by_pattern` can be used to sort these IDs based on an attribute of the objects, like their weights or + timestamps. + E.g., if `by_pattern` is `weight_*`, the command will sort the elements by the values of the + keys `weight_`. + If not provided, elements are sorted by their value. + limit (Optional[Limit]): Limiting the range of the query by setting offset and result count. See `Limit` class for more information. + get_pattern (Optional[TEncodable]): A pattern used to retrieve external keys' values, instead of the elements at `key`. + The pattern should contain an asterisk (*) as a placeholder for the element values, where the value + from `key` replaces the asterisk to create the key name. This allows the sorted elements to be + transformed based on the related keys values. For example, if `key` contains IDs of users, `get_pattern` + can be used to retrieve specific attributes of these users, such as their names or email addresses. + E.g., if `get_pattern` is `name_*`, the command will return the values of the keys `name_` + for each sorted element. Multiple `get_pattern` arguments can be provided to retrieve multiple attributes. + The special value `#` can be used to include the actual element from `key` being sorted. + If not provided, only the sorted elements themselves are returned. + 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 + + Returns: + List[Optional[bytes]]: Returns a list of sorted elements. + + Examples: + >>> await client.lpush("mylist", 3, 1, 2) + >>> await client.sort_ro("mylist") + [b'1', b'2', b'3'] + >>> await client.sort_ro("mylist", order=OrderBy.DESC) + [b'3', b'2', b'1'] + >>> await client.lpush("mylist2", 2, 1, 2, 3, 3, 1) + >>> await client.sort_ro("mylist2", limit=Limit(2, 3)) + [b'2', b'2', b'3'] + >>> await client.hset("user:1", "name", "Alice", "age", 30) + >>> await client.hset("user:2", "name", "Bob", "age", 25) + >>> await client.lpush("user_ids", 2, 1) + >>> await client.sort_ro("user_ids", by_pattern="user:*->age", get_patterns=["user:*->name"]) + [b'Bob', b'Alice'] + + Since: Redis version 7.0.0. + """ + args = _build_sort_args(key, by_pattern, limit, get_patterns, order, alpha) + result = await self._execute_command(RequestType.SortReadOnly, args) + return cast(List[Optional[bytes]], result) + async def sort_store( self, key: TEncodable, diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 607ccad1f6..af9c50ffcb 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -4722,6 +4722,55 @@ def sort( args = _build_sort_args(key, by_pattern, limit, get_patterns, order, alpha) return self.append_command(RequestType.Sort, args) + def sort_ro( + self: TTransaction, + key: TEncodable, + by_pattern: Optional[TEncodable] = None, + limit: Optional[Limit] = None, + get_patterns: Optional[List[TEncodable]] = None, + order: Optional[OrderBy] = None, + alpha: Optional[bool] = None, + ) -> TTransaction: + """ + Sorts the elements in the list, set, or sorted set at `key` and returns the result. + The `sort_ro` command can be used to sort elements based on different criteria and apply transformations on sorted elements. + This command is routed depending on the client's `ReadFrom` strategy. + + See https://valkey.io/commands/sort for more details. + + Args: + key (TEncodable): The key of the list, set, or sorted set to be sorted. + by_pattern (Optional[TEncodable]): A pattern to sort by external keys instead of by the elements stored at the key themselves. + The pattern should contain an asterisk (*) as a placeholder for the element values, where the value + from the key replaces the asterisk to create the key name. For example, if `key` contains IDs of objects, + `by_pattern` can be used to sort these IDs based on an attribute of the objects, like their weights or + timestamps. + E.g., if `by_pattern` is `weight_*`, the command will sort the elements by the values of the + keys `weight_`. + If not provided, elements are sorted by their value. + limit (Optional[Limit]): Limiting the range of the query by setting offset and result count. See `Limit` class for more information. + get_pattern (Optional[TEncodable]): A pattern used to retrieve external keys' values, instead of the elements at `key`. + The pattern should contain an asterisk (*) as a placeholder for the element values, where the value + from `key` replaces the asterisk to create the key name. This allows the sorted elements to be + transformed based on the related keys values. For example, if `key` contains IDs of users, `get_pattern` + can be used to retrieve specific attributes of these users, such as their names or email addresses. + E.g., if `get_pattern` is `name_*`, the command will return the values of the keys `name_` + for each sorted element. Multiple `get_pattern` arguments can be provided to retrieve multiple attributes. + The special value `#` can be used to include the actual element from `key` being sorted. + If not provided, only the sorted elements themselves are returned. + 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. + + Command response: + List[Optional[bytes]]: Returns a list of sorted elements. + + Since: Redis version 7.0.0. + """ + args = _build_sort_args(key, by_pattern, limit, get_patterns, order, alpha) + return self.append_command(RequestType.SortReadOnly, args) + def sort_store( self: TTransaction, key: TEncodable, @@ -4843,6 +4892,7 @@ def sort( ) -> TTransaction: """ Sorts the elements in the list, set, or sorted set at `key` and returns the result. + This command is routed to primary only. To store the result into a new key, see `sort_store`. See https://valkey.io/commands/sort for more details. @@ -4861,6 +4911,36 @@ def sort( args = _build_sort_args(key, None, limit, None, order, alpha) return self.append_command(RequestType.Sort, args) + def sort_ro( + self: TTransaction, + key: TEncodable, + limit: Optional[Limit] = None, + order: Optional[OrderBy] = None, + alpha: Optional[bool] = None, + ) -> TTransaction: + """ + Sorts the elements in the list, set, or sorted set at `key` and returns the result. + The `sort_ro` command can be used to sort elements based on different criteria and apply transformations on sorted elements. + This command is routed depending on the client's `ReadFrom` strategy. + + See https://valkey.io/commands/sort for more details. + + Args: + key (TEncodable): 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. + + Command response: + List[bytes]: A list of sorted elements. + + Since: Redis version 7.0.0. + """ + args = _build_sort_args(key, None, limit, None, order, alpha) + return self.append_command(RequestType.SortReadOnly, args) + def sort_store( self: TTransaction, key: TEncodable, diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index c8ed08d140..cc1c51eccf 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -4716,6 +4716,12 @@ async def test_sort_and_sort_store_with_get_or_by_args( assert await glide_client.hset(user_key5, {"name": "Eve", "age": "40"}) == 2 assert await glide_client.lpush("user_ids", ["5", "4", "3", "2", "1"]) == 5 + # SORT_RO Available since: 7.0.0 + skip_sort_ro_test = False + min_version = "7.0.0" + if await check_if_server_version_lt(glide_client, min_version): + skip_sort_ro_test = True + # Test sort with all arguments assert await glide_client.lpush(key, ["3", "1", "2"]) == 3 result = await glide_client.sort( @@ -4727,6 +4733,16 @@ async def test_sort_and_sort_store_with_get_or_by_args( ) assert result == [b"Alice", b"Bob"] + if not skip_sort_ro_test: + result_ro = await glide_client.sort_ro( + key, + limit=Limit(0, 2), + get_patterns=[b"user:*->name"], + order=OrderBy.ASC, + alpha=True, + ) + assert result_ro == [b"Alice", b"Bob"] + # Test sort_store with all arguments sort_store_result = await glide_client.sort_store( key, @@ -4747,9 +4763,16 @@ async def test_sort_and_sort_store_with_get_or_by_args( get_patterns=["user:*->name"], alpha=True, ) - assert result == convert_string_to_bytes_object( - ["Dave", "Bob", "Alice", "Charlie", "Eve"] - ) + assert result == [b"Dave", b"Bob", b"Alice", b"Charlie", b"Eve"] + + if not skip_sort_ro_test: + result_ro = await glide_client.sort_ro( + b"user_ids", + by_pattern=b"user:*->age", + get_patterns=["user:*->name"], + alpha=True, + ) + assert result_ro == [b"Dave", b"Bob", b"Alice", b"Charlie", b"Eve"] # Test sort with `by` argument with missing keys to sort by assert await glide_client.lpush("user_ids", ["a"]) == 6 @@ -4763,6 +4786,15 @@ async def test_sort_and_sort_store_with_get_or_by_args( [None, "Dave", "Bob", "Alice", "Charlie", "Eve"] ) + if not skip_sort_ro_test: + result_ro = await glide_client.sort_ro( + "user_ids", + by_pattern=b"user:*->age", + get_patterns=["user:*->name"], + alpha=True, + ) + assert result_ro == [None, b"Dave", b"Bob", b"Alice", b"Charlie", b"Eve"] + # Test sort with `by` argument with missing keys to sort by result = await glide_client.sort( "user_ids", @@ -4774,6 +4806,15 @@ async def test_sort_and_sort_store_with_get_or_by_args( [None, "30", "25", "35", "20", "40"] ) + if not skip_sort_ro_test: + result_ro = await glide_client.sort_ro( + "user_ids", + by_pattern=b"user:*->name", + get_patterns=[b"user:*->age"], + alpha=True, + ) + assert result_ro == [None, b"30", b"25", b"35", b"20", b"40"] + # Test Limit with count 0 result = await glide_client.sort( "user_ids", @@ -4782,6 +4823,14 @@ async def test_sort_and_sort_store_with_get_or_by_args( ) assert result == [] + if not skip_sort_ro_test: + result_ro = await glide_client.sort_ro( + "user_ids", + limit=Limit(0, 0), + alpha=True, + ) + assert result_ro == [] + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_sort_and_sort_store_without_get_or_by_args( @@ -4790,10 +4839,20 @@ async def test_sort_and_sort_store_without_get_or_by_args( key = "{SameSlotKey}" + get_random_string(10) store = "{SameSlotKey}" + get_random_string(10) + # SORT_RO Available since: 7.0.0 + skip_sort_ro_test = False + min_version = "7.0.0" + if await check_if_server_version_lt(glide_client, min_version): + skip_sort_ro_test = True + # Test sort with non-existing key result = await glide_client.sort("non_existing_key") assert result == [] + if not skip_sort_ro_test: + result_ro = await glide_client.sort_ro(b"non_existing_key") + assert result_ro == [] + # Test sort_store with non-existing key sort_store_result = await glide_client.sort_store( "{SameSlotKey}:non_existing_key", store @@ -4807,30 +4866,57 @@ async def test_sort_and_sort_store_without_get_or_by_args( result = await glide_client.sort(key) assert result == [b"1", b"2", b"3", b"4", b"5"] + if not skip_sort_ro_test: + result_ro = await glide_client.sort_ro(key) + assert result_ro == [b"1", b"2", b"3", b"4", b"5"] + # limit argument result = await glide_client.sort(key, limit=Limit(1, 3)) assert result == [b"2", b"3", b"4"] + if not skip_sort_ro_test: + result_ro = await glide_client.sort_ro(key, limit=Limit(1, 3)) + assert result_ro == [b"2", b"3", b"4"] + # order argument result = await glide_client.sort(key, order=OrderBy.DESC) assert result == [b"5", b"4", b"3", b"2", b"1"] + if not skip_sort_ro_test: + result_ro = await glide_client.sort_ro(key, order=OrderBy.DESC) + assert result_ro == [b"5", b"4", b"3", b"2", b"1"] + assert await glide_client.lpush(key, ["a"]) == 6 with pytest.raises(RequestError) as e: await glide_client.sort(key) assert "can't be converted into double" in str(e).lower() + if not skip_sort_ro_test: + with pytest.raises(RequestError) as e: + await glide_client.sort_ro(key) + assert "can't be converted into double" in str(e).lower() + # alpha argument result = await glide_client.sort(key, alpha=True) assert result == [b"1", b"2", b"3", b"4", b"5", b"a"] + if not skip_sort_ro_test: + result_ro = await glide_client.sort_ro(key, alpha=True) + assert result_ro == [b"1", b"2", b"3", b"4", b"5", b"a"] + # Combining multiple arguments result = await glide_client.sort( key, limit=Limit(1, 3), order=OrderBy.DESC, alpha=True ) assert result == [b"5", b"4", b"3"] + if not skip_sort_ro_test: + result_ro = await glide_client.sort_ro( + key, limit=Limit(1, 3), order=OrderBy.DESC, alpha=True + ) + assert result_ro == [b"5", b"4", b"3"] + # Test sort_store with combined arguments sort_store_result = await glide_client.sort_store( key, store, limit=Limit(1, 3), order=OrderBy.DESC, alpha=True @@ -9672,19 +9758,19 @@ async def test_script_binary(self, glide_client: TGlideClient): @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_script_large_keys_no_args(self, request, cluster_mode, protocol): - redis_client = await create_client( + glide_client = await create_client( request, cluster_mode=cluster_mode, protocol=protocol, timeout=5000 ) length = 2**13 # 8kb key = "0" * length script = Script("return KEYS[1]") - assert await redis_client.invoke_script(script, keys=[key]) == key.encode() - await redis_client.close() + assert await glide_client.invoke_script(script, keys=[key]) == key.encode() + await glide_client.close() @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_script_large_args_no_keys(self, request, cluster_mode, protocol): - redis_client = await create_client( + glide_client = await create_client( request, cluster_mode=cluster_mode, protocol=protocol, timeout=5000 ) length = 2**12 # 4kb @@ -9693,14 +9779,14 @@ async def test_script_large_args_no_keys(self, request, cluster_mode, protocol): script = Script("return ARGV[2]") assert ( - await redis_client.invoke_script(script, args=[arg1, arg2]) == arg2.encode() + await glide_client.invoke_script(script, args=[arg1, arg2]) == arg2.encode() ) - await redis_client.close() + await glide_client.close() @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_script_large_keys_and_args(self, request, cluster_mode, protocol): - redis_client = await create_client( + glide_client = await create_client( request, cluster_mode=cluster_mode, protocol=protocol, timeout=5000 ) length = 2**12 # 4kb @@ -9709,7 +9795,7 @@ async def test_script_large_keys_and_args(self, request, cluster_mode, protocol) script = Script("return KEYS[1]") assert ( - await redis_client.invoke_script(script, keys=[key], args=[arg]) + await glide_client.invoke_script(script, keys=[key], args=[arg]) == key.encode() ) - await redis_client.close() + await glide_client.close() diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index e980f2fef5..de878e5866 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -668,6 +668,14 @@ async def transaction_test( alpha=True, ) args.append([b"2", b"3", b"4", b"a"]) + if not await check_if_server_version_lt(glide_client, "7.0.0"): + transaction.sort_ro( + key17, + limit=Limit(1, 4), + order=OrderBy.ASC, + alpha=True, + ) + args.append([b"2", b"3", b"4", b"a"]) transaction.sort_store( key17, key18,