Skip to content
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
5 changes: 4 additions & 1 deletion redis/commands/bf/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,10 @@ def scandump(
(0, NULL) to indicate completion.
For more information see `CF.SCANDUMP <https://redis.io/commands/cf.scandump>`_.
""" # noqa
return self.execute_command(CF_SCANDUMP, key, iter)
params = [key, iter]
options = {}
options[NEVER_DECODE] = []
return self.execute_command(CF_SCANDUMP, *params, **options)

@overload
def loadchunk(self: SyncClientProtocol, key, iter, data) -> bytes | str: ...
Expand Down
23 changes: 23 additions & 0 deletions tests/test_asyncio/test_bloom.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,29 @@ async def do_verify():
await decoded_r.bf().client.delete("myBloom")


@pytest.mark.redismod
async def test_cf_scandump_and_loadchunk(decoded_r: redis.Redis):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip the async CF dump test on Redis Enterprise

When RE_CLUSTER=true routes the module URL to Redis Enterprise, this async test runs without the Enterprise guard on the synchronous test_cf_scandump_and_loadchunk twin. That test is explicitly excluded there, while this test uses a cursor loop with no timeout, so the same server behavior can fail or hang the async suite instead of being skipped; apply the same guard. The repository requires sync and async paths to remain aligned.

AGENTS.md reference: AGENTS.md:L252-L254

Useful? React with 👍 / 👎.

# The client decodes responses, but the dumped chunks are binary.
await decoded_r.cf().create("myCuckoo", 1000)
for x in range(100):
await decoded_r.cf().add("myCuckoo", x)

chunks = []
it = 0
while True:
it, data = await decoded_r.cf().scandump("myCuckoo", it)
if it == 0:
break
assert isinstance(data, bytes)
chunks.append((it, data))

await decoded_r.delete("myCuckoo")
for it, data in chunks:
await decoded_r.cf().loadchunk("myCuckoo", it, data)

assert all(await decoded_r.cf().mexists("myCuckoo", *range(100)))


@pytest.mark.redismod
async def test_bf_info(decoded_r: redis.Redis):
expansion = 4
Expand Down
34 changes: 34 additions & 0 deletions tests/test_bloom.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from math import inf
from unittest.mock import Mock

import pytest
import redis.commands.bf
from redis.client import NEVER_DECODE
from redis.exceptions import RedisError

from .conftest import (
Expand Down Expand Up @@ -188,6 +190,38 @@ def do_verify():
client.bf().client.delete("myBloom")


@pytest.mark.redismod
@skip_if_redis_enterprise()
def test_cf_scandump_and_loadchunk(client):
# The client decodes responses, but the dumped chunks are binary.
client.cf().create("myCuckoo", 1000)
for x in range(100):
client.cf().add("myCuckoo", x)

chunks = []
it = 0
while True:
it, data = client.cf().scandump("myCuckoo", it)
if it == 0:
break
assert isinstance(data, bytes)
chunks.append((it, data))

client.delete("myCuckoo")
for it, data in chunks:
client.cf().loadchunk("myCuckoo", it, data)

assert all(client.cf().mexists("myCuckoo", *range(100)))


def test_cf_scandump_does_not_decode_response():
mock_client = Mock()
redis.commands.bf.CFBloom(mock_client).scandump("myCuckoo", 0)
mock_client.execute_command.assert_called_once_with(
"CF.SCANDUMP", "myCuckoo", 0, **{NEVER_DECODE: []}
)


@pytest.mark.redismod
def test_bf_info(client):
expansion = 4
Expand Down
Loading