Skip to content

Commit 71aa1b0

Browse files
committed
Python: add SDIFF command (#295)
1 parent cf8301c commit 71aa1b0

File tree

6 files changed

+73
-2
lines changed

6 files changed

+73
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* Python: Added ZMPOP command ([#1417](https://github.com/aws/glide-for-redis/pull/1417))
3434
* Python: Added SMOVE command ([#1421](https://github.com/aws/glide-for-redis/pull/1421))
3535
* Python: Added SUNIONSTORE command ([#1423](https://github.com/aws/glide-for-redis/pull/1423))
36+
* Python: Added SDIFF command ([#1437](https://github.com/aws/glide-for-redis/pull/1437))
3637

3738

3839
#### Fixes

glide-core/src/client/value_conversion.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> {
501501
b"HEXISTS" | b"HSETNX" | b"EXPIRE" | b"EXPIREAT" | b"PEXPIRE" | b"PEXPIREAT"
502502
| b"SISMEMBER" | b"PERSIST" | b"SMOVE" | b"RENAMENX" => Some(ExpectedReturnType::Boolean),
503503
b"SMISMEMBER" => Some(ExpectedReturnType::ArrayOfBools),
504-
b"SMEMBERS" | b"SINTER" => Some(ExpectedReturnType::Set),
504+
b"SMEMBERS" | b"SINTER" | b"SDIFF" => Some(ExpectedReturnType::Set),
505505
b"ZSCORE" | b"GEODIST" => Some(ExpectedReturnType::DoubleOrNull),
506506
b"ZMSCORE" => Some(ExpectedReturnType::ArrayOfDoubleOrNull),
507507
b"ZPOPMIN" | b"ZPOPMAX" => Some(ExpectedReturnType::MapOfStringToDouble),

python/python/glide/async_commands/core.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,33 @@ async def sunionstore(
16011601
await self._execute_command(RequestType.SUnionStore, [destination] + keys),
16021602
)
16031603

1604+
async def sdiff(self, keys: List[str]) -> Set[str]:
1605+
"""
1606+
Computes the difference between the first set and all the successive sets in `keys`.
1607+
1608+
See https://valkey.io/commands/sdiff for more details.
1609+
1610+
Note:
1611+
When in cluster mode, all `keys` must map to the same hash slot.
1612+
1613+
Args:
1614+
keys (List[str]): The keys of the sets to diff.
1615+
1616+
Returns:
1617+
Set[str]: A set of elements representing the difference between the sets.
1618+
If any of the keys in `keys` do not exist, they are treated as empty sets.
1619+
1620+
Examples:
1621+
>>> await client.sadd("set1", ["member1", "member2"])
1622+
>>> await client.sadd("set2", ["member1"])
1623+
>>> await client.sdiff("set1", "set2")
1624+
{"member2"} # "member2" is in "set1" but not "set2"
1625+
"""
1626+
return cast(
1627+
Set[str],
1628+
await self._execute_command(RequestType.SDiff, keys),
1629+
)
1630+
16041631
async def ltrim(self, key: str, start: int, end: int) -> TOK:
16051632
"""
16061633
Trim an existing list so that it will contain only the specified range of elements specified.

python/python/glide/async_commands/transaction.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,21 @@ def sunionstore(
10521052
"""
10531053
return self.append_command(RequestType.SUnionStore, [destination] + keys)
10541054

1055+
def sdiff(self: TTransaction, keys: List[str]) -> TTransaction:
1056+
"""
1057+
Computes the difference between the first set and all the successive sets in `keys`.
1058+
1059+
See https://valkey.io/commands/sdiff for more details.
1060+
1061+
Args:
1062+
keys (List[str]): The keys of the sets to diff.
1063+
1064+
Command response:
1065+
Set[str]: A set of elements representing the difference between the sets.
1066+
If any of the keys in `keys` do not exist, they are treated as empty sets.
1067+
"""
1068+
return self.append_command(RequestType.SDiff, keys)
1069+
10551070
def ltrim(self: TTransaction, key: str, start: int, end: int) -> TTransaction:
10561071
"""
10571072
Trim an existing list so that it will contain only the specified range of elements specified.

python/python/tests/test_async_client.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1102,7 +1102,6 @@ async def test_smove(self, redis_client: TRedisClient):
11021102
key1 = f"{{testKey}}:1-{get_random_string(10)}"
11031103
key2 = f"{{testKey}}:2-{get_random_string(10)}"
11041104
key3 = f"{{testKey}}:3-{get_random_string(10)}"
1105-
key4 = f"{{testKey}}:4-{get_random_string(10)}"
11061105
string_key = f"{{testKey}}:4-{get_random_string(10)}"
11071106
non_existing_key = f"{{testKey}}:5-{get_random_string(10)}"
11081107

@@ -1190,6 +1189,32 @@ async def test_sunionstore(self, redis_client: TRedisClient):
11901189
"g",
11911190
}
11921191

1192+
@pytest.mark.parametrize("cluster_mode", [True, False])
1193+
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
1194+
async def test_sdiff(self, redis_client: TRedisClient):
1195+
key1 = f"{{testKey}}:1-{get_random_string(10)}"
1196+
key2 = f"{{testKey}}:2-{get_random_string(10)}"
1197+
string_key = f"{{testKey}}:4-{get_random_string(10)}"
1198+
non_existing_key = f"{{testKey}}:5-{get_random_string(10)}"
1199+
1200+
assert await redis_client.sadd(key1, ["a", "b", "c"]) == 3
1201+
assert await redis_client.sadd(key2, ["c", "d", "e"]) == 3
1202+
1203+
assert await redis_client.sdiff([key1, key2]) == {"a", "b"}
1204+
assert await redis_client.sdiff([key2, key1]) == {"d", "e"}
1205+
1206+
assert await redis_client.sdiff([key1, non_existing_key]) == {"a", "b", "c"}
1207+
assert await redis_client.sdiff([non_existing_key, key1]) == set()
1208+
1209+
# invalid argument - key list must not be empty
1210+
with pytest.raises(RequestError):
1211+
await redis_client.sdiff([])
1212+
1213+
# key exists, but it is not a set
1214+
assert await redis_client.set(string_key, "value") == OK
1215+
with pytest.raises(RequestError):
1216+
await redis_client.sdiff([string_key])
1217+
11931218
@pytest.mark.parametrize("cluster_mode", [True, False])
11941219
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
11951220
async def test_ltrim(self, redis_client: TRedisClient):
@@ -3129,6 +3154,7 @@ async def test_multi_key_command_returns_cross_slot_error(
31293154
redis_client.bzpopmax(["abc", "zxy", "lkn"], 0.5),
31303155
redis_client.smove("abc", "def", "_"),
31313156
redis_client.sunionstore("abc", ["zxy", "lkn"]),
3157+
redis_client.sdiff(["abc", "zxy", "lkn"]),
31323158
]
31333159

31343160
if not check_if_server_version_lt(redis_client, "7.0.0"):

python/python/tests/test_transaction.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ async def transaction_test(
205205
args.append(2)
206206
transaction.sunionstore(key7, [key7, key7])
207207
args.append(2)
208+
transaction.sdiff([key7, key7])
209+
args.append(set())
208210
transaction.spop_count(key7, 4)
209211
args.append({"foo", "bar"})
210212
transaction.smove(key7, key7, "non_existing_member")

0 commit comments

Comments
 (0)