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: add SDIFF command #295

Merged
merged 1 commit into from
May 22, 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 @@ -33,6 +33,7 @@
* Python: Added ZMPOP command ([#1417](https://github.com/aws/glide-for-redis/pull/1417))
* Python: Added SMOVE command ([#1421](https://github.com/aws/glide-for-redis/pull/1421))
* Python: Added SUNIONSTORE command ([#1423](https://github.com/aws/glide-for-redis/pull/1423))
* Python: Added SDIFF command (TODO: add PR link)


#### Fixes
Expand Down
2 changes: 1 addition & 1 deletion glide-core/src/client/value_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> {
b"HEXISTS" | b"HSETNX" | b"EXPIRE" | b"EXPIREAT" | b"PEXPIRE" | b"PEXPIREAT"
| b"SISMEMBER" | b"PERSIST" | b"SMOVE" | b"RENAMENX" => Some(ExpectedReturnType::Boolean),
b"SMISMEMBER" => Some(ExpectedReturnType::ArrayOfBools),
b"SMEMBERS" | b"SINTER" => Some(ExpectedReturnType::Set),
b"SMEMBERS" | b"SINTER" | b"SDIFF" => Some(ExpectedReturnType::Set),
b"ZSCORE" | b"GEODIST" => Some(ExpectedReturnType::DoubleOrNull),
b"ZMSCORE" => Some(ExpectedReturnType::ArrayOfDoubleOrNull),
b"ZPOPMIN" | b"ZPOPMAX" => Some(ExpectedReturnType::MapOfStringToDouble),
Expand Down
27 changes: 27 additions & 0 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,33 @@ async def sunionstore(
await self._execute_command(RequestType.SUnionStore, [destination] + keys),
)

async def sdiff(self, keys: List[str]) -> Set[str]:
"""
Computes the difference between the first set and all the successive sets in `keys`.

See https://valkey.io/commands/sdiff for more details.

Note:
When in cluster mode, all `keys` must map to the same hash slot.

Args:
keys (List[str]): The keys of the sets to diff.

Returns:
Set[str]: A set of elements representing the difference between the sets.
If any of the keys in `keys` do not exist, they are treated as empty sets.

Examples:
>>> await client.sadd("set1", ["member1", "member2"])
>>> await client.sadd("set2", ["member1"])
>>> await client.sdiff("set1", "set2")
{"member2"} # "member2" is in "set1" but not "set2"
"""
return cast(
Set[str],
await self._execute_command(RequestType.SDiff, keys),
)

async def ltrim(self, key: str, start: int, end: int) -> TOK:
"""
Trim an existing list so that it will contain only the specified range of elements specified.
Expand Down
15 changes: 15 additions & 0 deletions python/python/glide/async_commands/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,21 @@ def sunionstore(
"""
return self.append_command(RequestType.SUnionStore, [destination] + keys)

def sdiff(self: TTransaction, keys: List[str]) -> TTransaction:
"""
Computes the difference between the first set and all the successive sets in `keys`.

See https://valkey.io/commands/sdiff for more details.

Args:
keys (List[str]): The keys of the sets to diff.

Command response:
Set[str]: A set of elements representing the difference between the sets.
If any of the keys in `keys` do not exist, they are treated as empty sets.
"""
return self.append_command(RequestType.SDiff, keys)

def ltrim(self: TTransaction, key: str, start: int, end: int) -> TTransaction:
"""
Trim an existing list so that it will contain only the specified range of elements specified.
Expand Down
28 changes: 27 additions & 1 deletion python/python/tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,6 @@ async def test_smove(self, redis_client: TRedisClient):
key1 = f"{{testKey}}:1-{get_random_string(10)}"
key2 = f"{{testKey}}:2-{get_random_string(10)}"
key3 = f"{{testKey}}:3-{get_random_string(10)}"
key4 = f"{{testKey}}:4-{get_random_string(10)}"
string_key = f"{{testKey}}:4-{get_random_string(10)}"
non_existing_key = f"{{testKey}}:5-{get_random_string(10)}"

Expand Down Expand Up @@ -1190,6 +1189,32 @@ async def test_sunionstore(self, redis_client: TRedisClient):
"g",
}

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_sdiff(self, redis_client: TRedisClient):
key1 = f"{{testKey}}:1-{get_random_string(10)}"
key2 = f"{{testKey}}:2-{get_random_string(10)}"
string_key = f"{{testKey}}:4-{get_random_string(10)}"
non_existing_key = f"{{testKey}}:5-{get_random_string(10)}"

assert await redis_client.sadd(key1, ["a", "b", "c"]) == 3
assert await redis_client.sadd(key2, ["c", "d", "e"]) == 3

assert await redis_client.sdiff([key1, key2]) == {"a", "b"}
assert await redis_client.sdiff([key2, key1]) == {"d", "e"}

assert await redis_client.sdiff([key1, non_existing_key]) == {"a", "b", "c"}
assert await redis_client.sdiff([non_existing_key, key1]) == set()

# invalid argument - key list must not be empty
with pytest.raises(RequestError):
await redis_client.sdiff([])

# key exists, but it is not a set
assert await redis_client.set(string_key, "value") == OK
with pytest.raises(RequestError):
await redis_client.sdiff([string_key])

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_ltrim(self, redis_client: TRedisClient):
Expand Down Expand Up @@ -3129,6 +3154,7 @@ async def test_multi_key_command_returns_cross_slot_error(
redis_client.bzpopmax(["abc", "zxy", "lkn"], 0.5),
redis_client.smove("abc", "def", "_"),
redis_client.sunionstore("abc", ["zxy", "lkn"]),
redis_client.sdiff(["abc", "zxy", "lkn"]),
]

if not check_if_server_version_lt(redis_client, "7.0.0"):
Expand Down
2 changes: 2 additions & 0 deletions python/python/tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ async def transaction_test(
args.append(2)
transaction.sunionstore(key7, [key7, key7])
args.append(2)
transaction.sdiff([key7, key7])
args.append(set())
transaction.spop_count(key7, 4)
args.append({"foo", "bar"})
transaction.smove(key7, key7, "non_existing_member")
Expand Down
Loading