Skip to content

Commit 7170163

Browse files
committed
Python: add SDIFF command (#295)
1 parent 4277b97 commit 7170163

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
@@ -34,6 +34,7 @@
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))
3636
* Python: Added SINTER command ([#1434](https://github.com/aws/glide-for-redis/pull/1434))
37+
* Python: Added SDIFF command ([#1437](https://github.com/aws/glide-for-redis/pull/1437))
3738

3839

3940
#### 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
@@ -1627,6 +1627,33 @@ async def sinter(self, keys: List[str]) -> Set[str]:
16271627
"""
16281628
return cast(Set[str], await self._execute_command(RequestType.SInter, keys))
16291629

1630+
async def sdiff(self, keys: List[str]) -> Set[str]:
1631+
"""
1632+
Computes the difference between the first set and all the successive sets in `keys`.
1633+
1634+
See https://valkey.io/commands/sdiff for more details.
1635+
1636+
Note:
1637+
When in cluster mode, all `keys` must map to the same hash slot.
1638+
1639+
Args:
1640+
keys (List[str]): The keys of the sets to diff.
1641+
1642+
Returns:
1643+
Set[str]: A set of elements representing the difference between the sets.
1644+
If any of the keys in `keys` do not exist, they are treated as empty sets.
1645+
1646+
Examples:
1647+
>>> await client.sadd("set1", ["member1", "member2"])
1648+
>>> await client.sadd("set2", ["member1"])
1649+
>>> await client.sdiff("set1", "set2")
1650+
{"member2"} # "member2" is in "set1" but not "set2"
1651+
"""
1652+
return cast(
1653+
Set[str],
1654+
await self._execute_command(RequestType.SDiff, keys),
1655+
)
1656+
16301657
async def ltrim(self, key: str, start: int, end: int) -> TOK:
16311658
"""
16321659
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
@@ -1067,6 +1067,21 @@ def sinter(self: TTransaction, keys: List[str]) -> TTransaction:
10671067
"""
10681068
return self.append_command(RequestType.SInter, keys)
10691069

1070+
def sdiff(self: TTransaction, keys: List[str]) -> TTransaction:
1071+
"""
1072+
Computes the difference between the first set and all the successive sets in `keys`.
1073+
1074+
See https://valkey.io/commands/sdiff for more details.
1075+
1076+
Args:
1077+
keys (List[str]): The keys of the sets to diff.
1078+
1079+
Command response:
1080+
Set[str]: A set of elements representing the difference between the sets.
1081+
If any of the keys in `keys` do not exist, they are treated as empty sets.
1082+
"""
1083+
return self.append_command(RequestType.SDiff, keys)
1084+
10701085
def ltrim(self: TTransaction, key: str, start: int, end: int) -> TTransaction:
10711086
"""
10721087
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

@@ -1217,6 +1216,32 @@ async def test_sinter(self, redis_client: TRedisClient):
12171216
await redis_client.sinter([key2])
12181217
assert "Operation against a key holding the wrong kind of value" in str(e)
12191218

1219+
@pytest.mark.parametrize("cluster_mode", [True, False])
1220+
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
1221+
async def test_sdiff(self, redis_client: TRedisClient):
1222+
key1 = f"{{testKey}}:1-{get_random_string(10)}"
1223+
key2 = f"{{testKey}}:2-{get_random_string(10)}"
1224+
string_key = f"{{testKey}}:4-{get_random_string(10)}"
1225+
non_existing_key = f"{{testKey}}:5-{get_random_string(10)}"
1226+
1227+
assert await redis_client.sadd(key1, ["a", "b", "c"]) == 3
1228+
assert await redis_client.sadd(key2, ["c", "d", "e"]) == 3
1229+
1230+
assert await redis_client.sdiff([key1, key2]) == {"a", "b"}
1231+
assert await redis_client.sdiff([key2, key1]) == {"d", "e"}
1232+
1233+
assert await redis_client.sdiff([key1, non_existing_key]) == {"a", "b", "c"}
1234+
assert await redis_client.sdiff([non_existing_key, key1]) == set()
1235+
1236+
# invalid argument - key list must not be empty
1237+
with pytest.raises(RequestError):
1238+
await redis_client.sdiff([])
1239+
1240+
# key exists, but it is not a set
1241+
assert await redis_client.set(string_key, "value") == OK
1242+
with pytest.raises(RequestError):
1243+
await redis_client.sdiff([string_key])
1244+
12201245
@pytest.mark.parametrize("cluster_mode", [True, False])
12211246
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
12221247
async def test_ltrim(self, redis_client: TRedisClient):
@@ -3157,6 +3182,7 @@ async def test_multi_key_command_returns_cross_slot_error(
31573182
redis_client.smove("abc", "def", "_"),
31583183
redis_client.sunionstore("abc", ["zxy", "lkn"]),
31593184
redis_client.sinter(["abc", "zxy", "lkn"]),
3185+
redis_client.sdiff(["abc", "zxy", "lkn"]),
31603186
]
31613187

31623188
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
@@ -207,6 +207,8 @@ async def transaction_test(
207207
args.append(2)
208208
transaction.sinter([key7, key7])
209209
args.append({"foo", "bar"})
210+
transaction.sdiff([key7, key7])
211+
args.append(set())
210212
transaction.spop_count(key7, 4)
211213
args.append({"foo", "bar"})
212214
transaction.smove(key7, key7, "non_existing_member")

0 commit comments

Comments
 (0)